Redmine 3.4.4
This commit is contained in:
commit
64924a6376
2112 changed files with 259028 additions and 0 deletions
124
app/models/repository/bazaar.rb
Normal file
124
app/models/repository/bazaar.rb
Normal file
|
@ -0,0 +1,124 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/bazaar_adapter'
|
||||
|
||||
class Repository::Bazaar < Repository
|
||||
attr_protected :root_url
|
||||
validates_presence_of :url, :log_encoding
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "url"
|
||||
attr_name = "path_to_repository"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::BazaarAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'Bazaar'
|
||||
end
|
||||
|
||||
def entry(path=nil, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.entry(path, identifier)
|
||||
end
|
||||
|
||||
def cat(path, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.cat(path, identifier)
|
||||
end
|
||||
|
||||
def annotate(path, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.annotate(path, identifier)
|
||||
end
|
||||
|
||||
def diff(path, rev, rev_to)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.diff(path, rev, rev_to)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
entries = scm.entries(path, identifier)
|
||||
if entries
|
||||
entries.each do |e|
|
||||
next if e.lastrev.revision.blank?
|
||||
# Set the filesize unless browsing a specific revision
|
||||
if identifier.nil? && e.is_file?
|
||||
full_path = File.join(root_url, e.path)
|
||||
e.size = File.stat(full_path).size if File.file?(full_path)
|
||||
end
|
||||
c = Change.
|
||||
includes(:changeset).
|
||||
where("#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id).
|
||||
order("#{Changeset.table_name}.revision DESC").
|
||||
first
|
||||
if c
|
||||
e.lastrev.identifier = c.changeset.revision
|
||||
e.lastrev.name = c.changeset.revision
|
||||
e.lastrev.author = c.changeset.committer
|
||||
end
|
||||
end
|
||||
end
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def fetch_changesets
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm_info = scm.info
|
||||
if scm_info
|
||||
# latest revision found in database
|
||||
db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
|
||||
# latest revision in the repository
|
||||
scm_revision = scm_info.lastrev.identifier.to_i
|
||||
if db_revision < scm_revision
|
||||
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
|
||||
identifier_from = db_revision + 1
|
||||
while (identifier_from <= scm_revision)
|
||||
# loads changesets by batches of 200
|
||||
identifier_to = [identifier_from + 199, scm_revision].min
|
||||
revisions = scm.revisions('', identifier_to, identifier_from)
|
||||
transaction do
|
||||
revisions.reverse_each do |revision|
|
||||
changeset = Changeset.create(:repository => self,
|
||||
:revision => revision.identifier,
|
||||
:committer => revision.author,
|
||||
:committed_on => revision.time,
|
||||
:scmid => revision.scmid,
|
||||
:comments => revision.message)
|
||||
|
||||
revision.paths.each do |change|
|
||||
Change.create(:changeset => changeset,
|
||||
:action => change[:action],
|
||||
:path => change[:path],
|
||||
:revision => change[:revision])
|
||||
end
|
||||
end
|
||||
end unless revisions.nil?
|
||||
identifier_from = identifier_to + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
213
app/models/repository/cvs.rb
Normal file
213
app/models/repository/cvs.rb
Normal file
|
@ -0,0 +1,213 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/cvs_adapter'
|
||||
require 'digest/sha1'
|
||||
|
||||
class Repository::Cvs < Repository
|
||||
validates_presence_of :url, :root_url, :log_encoding
|
||||
|
||||
safe_attributes 'root_url',
|
||||
:if => lambda {|repository, user| repository.new_record?}
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "root_url"
|
||||
attr_name = "cvsroot"
|
||||
elsif attr_name == "url"
|
||||
attr_name = "cvs_module"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::CvsAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'CVS'
|
||||
end
|
||||
|
||||
def entry(path=nil, identifier=nil)
|
||||
rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
|
||||
scm.entry(path, rev.nil? ? nil : rev.committed_on)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
rev = nil
|
||||
if ! identifier.nil?
|
||||
rev = changesets.find_by_revision(identifier)
|
||||
return nil if rev.nil?
|
||||
end
|
||||
entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
|
||||
if entries
|
||||
entries.each() do |entry|
|
||||
if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? )
|
||||
change = filechanges.where(
|
||||
:revision => entry.lastrev.revision,
|
||||
:path => scm.with_leading_slash(entry.path)).first
|
||||
if change
|
||||
entry.lastrev.identifier = change.changeset.revision
|
||||
entry.lastrev.revision = change.changeset.revision
|
||||
entry.lastrev.author = change.changeset.committer
|
||||
# entry.lastrev.branch = change.branch
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def cat(path, identifier=nil)
|
||||
rev = nil
|
||||
if ! identifier.nil?
|
||||
rev = changesets.find_by_revision(identifier)
|
||||
return nil if rev.nil?
|
||||
end
|
||||
scm.cat(path, rev.nil? ? nil : rev.committed_on)
|
||||
end
|
||||
|
||||
def annotate(path, identifier=nil)
|
||||
rev = nil
|
||||
if ! identifier.nil?
|
||||
rev = changesets.find_by_revision(identifier)
|
||||
return nil if rev.nil?
|
||||
end
|
||||
scm.annotate(path, rev.nil? ? nil : rev.committed_on)
|
||||
end
|
||||
|
||||
def diff(path, rev, rev_to)
|
||||
# convert rev to revision. CVS can't handle changesets here
|
||||
diff=[]
|
||||
changeset_from = changesets.find_by_revision(rev)
|
||||
if rev_to.to_i > 0
|
||||
changeset_to = changesets.find_by_revision(rev_to)
|
||||
end
|
||||
changeset_from.filechanges.each() do |change_from|
|
||||
revision_from = nil
|
||||
revision_to = nil
|
||||
if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
|
||||
revision_from = change_from.revision
|
||||
end
|
||||
if revision_from
|
||||
if changeset_to
|
||||
changeset_to.filechanges.each() do |change_to|
|
||||
revision_to = change_to.revision if change_to.path == change_from.path
|
||||
end
|
||||
end
|
||||
unless revision_to
|
||||
revision_to = scm.get_previous_revision(revision_from)
|
||||
end
|
||||
file_diff = scm.diff(change_from.path, revision_from, revision_to)
|
||||
diff = diff + file_diff unless file_diff.nil?
|
||||
end
|
||||
end
|
||||
return diff
|
||||
end
|
||||
|
||||
def fetch_changesets
|
||||
# some nifty bits to introduce a commit-id with cvs
|
||||
# natively cvs doesn't provide any kind of changesets,
|
||||
# there is only a revision per file.
|
||||
# we now take a guess using the author, the commitlog and the commit-date.
|
||||
|
||||
# last one is the next step to take. the commit-date is not equal for all
|
||||
# commits in one changeset. cvs update the commit-date when the *,v file was touched. so
|
||||
# we use a small delta here, to merge all changes belonging to _one_ changeset
|
||||
time_delta = 10.seconds
|
||||
fetch_since = latest_changeset ? latest_changeset.committed_on : nil
|
||||
transaction do
|
||||
tmp_rev_num = 1
|
||||
scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision|
|
||||
# only add the change to the database, if it doen't exists. the cvs log
|
||||
# is not exclusive at all.
|
||||
tmp_time = revision.time.clone
|
||||
unless filechanges.find_by_path_and_revision(
|
||||
scm.with_leading_slash(revision.paths[0][:path]),
|
||||
revision.paths[0][:revision]
|
||||
)
|
||||
cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
|
||||
author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
|
||||
cs = changesets.where(
|
||||
:committed_on => tmp_time - time_delta .. tmp_time + time_delta,
|
||||
:committer => author_utf8,
|
||||
:comments => cmt
|
||||
).first
|
||||
# create a new changeset....
|
||||
unless cs
|
||||
# we use a temporary revision number here (just for inserting)
|
||||
# later on, we calculate a continuous positive number
|
||||
tmp_time2 = tmp_time.clone.gmtime
|
||||
branch = revision.paths[0][:branch]
|
||||
scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
|
||||
cs = Changeset.create(:repository => self,
|
||||
:revision => "tmp#{tmp_rev_num}",
|
||||
:scmid => scmid,
|
||||
:committer => revision.author,
|
||||
:committed_on => tmp_time,
|
||||
:comments => revision.message)
|
||||
tmp_rev_num += 1
|
||||
end
|
||||
# convert CVS-File-States to internal Action-abbreviations
|
||||
# default action is (M)odified
|
||||
action = "M"
|
||||
if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1"
|
||||
action = "A" # add-action always at first revision (= 1.1)
|
||||
elsif revision.paths[0][:action] == "dead"
|
||||
action = "D" # dead-state is similar to Delete
|
||||
end
|
||||
Change.create(
|
||||
:changeset => cs,
|
||||
:action => action,
|
||||
:path => scm.with_leading_slash(revision.paths[0][:path]),
|
||||
:revision => revision.paths[0][:revision],
|
||||
:branch => revision.paths[0][:branch]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Renumber new changesets in chronological order
|
||||
Changeset.
|
||||
order('committed_on ASC, id ASC').
|
||||
where("repository_id = ? AND revision LIKE 'tmp%'", id).
|
||||
each do |changeset|
|
||||
changeset.update_attribute :revision, next_revision_number
|
||||
end
|
||||
end # transaction
|
||||
@current_revision_number = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Overrides Repository#validate_repository_path to validate
|
||||
# against root_url attribute.
|
||||
def validate_repository_path(attribute=:root_url)
|
||||
super(attribute)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the next revision number to assign to a CVS changeset
|
||||
def next_revision_number
|
||||
# Need to retrieve existing revision numbers to sort them as integers
|
||||
sql = "SELECT revision FROM #{Changeset.table_name} "
|
||||
sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
|
||||
@current_revision_number ||= (self.class.connection.select_values(sql).collect(&:to_i).max || 0)
|
||||
@current_revision_number += 1
|
||||
end
|
||||
end
|
114
app/models/repository/darcs.rb
Normal file
114
app/models/repository/darcs.rb
Normal file
|
@ -0,0 +1,114 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/darcs_adapter'
|
||||
|
||||
class Repository::Darcs < Repository
|
||||
validates_presence_of :url, :log_encoding
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "url"
|
||||
attr_name = "path_to_repository"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::DarcsAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'Darcs'
|
||||
end
|
||||
|
||||
def supports_directory_revisions?
|
||||
true
|
||||
end
|
||||
|
||||
def entry(path=nil, identifier=nil)
|
||||
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
|
||||
scm.entry(path, patch.nil? ? nil : patch.scmid)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
patch = nil
|
||||
if ! identifier.nil?
|
||||
patch = changesets.find_by_revision(identifier)
|
||||
return nil if patch.nil?
|
||||
end
|
||||
entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
|
||||
if entries
|
||||
entries.each do |entry|
|
||||
# Search the DB for the entry's last change
|
||||
if entry.lastrev && !entry.lastrev.scmid.blank?
|
||||
changeset = changesets.find_by_scmid(entry.lastrev.scmid)
|
||||
end
|
||||
if changeset
|
||||
entry.lastrev.identifier = changeset.revision
|
||||
entry.lastrev.name = changeset.revision
|
||||
entry.lastrev.time = changeset.committed_on
|
||||
entry.lastrev.author = changeset.committer
|
||||
end
|
||||
end
|
||||
end
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def cat(path, identifier=nil)
|
||||
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
|
||||
scm.cat(path, patch.nil? ? nil : patch.scmid)
|
||||
end
|
||||
|
||||
def diff(path, rev, rev_to)
|
||||
patch_from = changesets.find_by_revision(rev)
|
||||
return nil if patch_from.nil?
|
||||
patch_to = changesets.find_by_revision(rev_to) if rev_to
|
||||
if path.blank?
|
||||
path = patch_from.filechanges.collect{|change| change.path}.join(' ')
|
||||
end
|
||||
patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil
|
||||
end
|
||||
|
||||
def fetch_changesets
|
||||
scm_info = scm.info
|
||||
if scm_info
|
||||
db_last_id = latest_changeset ? latest_changeset.scmid : nil
|
||||
next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1
|
||||
# latest revision in the repository
|
||||
scm_revision = scm_info.lastrev.scmid
|
||||
unless changesets.find_by_scmid(scm_revision)
|
||||
revisions = scm.revisions('', db_last_id, nil, :with_path => true)
|
||||
transaction do
|
||||
revisions.reverse_each do |revision|
|
||||
changeset = Changeset.create(:repository => self,
|
||||
:revision => next_rev,
|
||||
:scmid => revision.scmid,
|
||||
:committer => revision.author,
|
||||
:committed_on => revision.time,
|
||||
:comments => revision.message)
|
||||
revision.paths.each do |change|
|
||||
changeset.create_change(change)
|
||||
end
|
||||
next_rev += 1
|
||||
end if revisions
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
50
app/models/repository/filesystem.rb
Normal file
50
app/models/repository/filesystem.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# FileSystem adapter
|
||||
# File written by Paul Rivier, at Demotera.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/filesystem_adapter'
|
||||
|
||||
class Repository::Filesystem < Repository
|
||||
attr_protected :root_url
|
||||
validates_presence_of :url
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "url"
|
||||
attr_name = "root_directory"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::FilesystemAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'Filesystem'
|
||||
end
|
||||
|
||||
def supports_all_revisions?
|
||||
false
|
||||
end
|
||||
|
||||
def fetch_changesets
|
||||
nil
|
||||
end
|
||||
end
|
265
app/models/repository/git.rb
Normal file
265
app/models/repository/git.rb
Normal file
|
@ -0,0 +1,265 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/git_adapter'
|
||||
|
||||
class Repository::Git < Repository
|
||||
attr_protected :root_url
|
||||
validates_presence_of :url
|
||||
|
||||
safe_attributes 'report_last_commit'
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "url"
|
||||
attr_name = "path_to_repository"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::GitAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'Git'
|
||||
end
|
||||
|
||||
def report_last_commit
|
||||
return false if extra_info.nil?
|
||||
v = extra_info["extra_report_last_commit"]
|
||||
return false if v.nil?
|
||||
v.to_s != '0'
|
||||
end
|
||||
|
||||
def report_last_commit=(arg)
|
||||
merge_extra_info "extra_report_last_commit" => arg
|
||||
end
|
||||
|
||||
def supports_directory_revisions?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_revision_graph?
|
||||
true
|
||||
end
|
||||
|
||||
def repo_log_encoding
|
||||
'UTF-8'
|
||||
end
|
||||
|
||||
# Returns the identifier for the given git changeset
|
||||
def self.changeset_identifier(changeset)
|
||||
changeset.scmid
|
||||
end
|
||||
|
||||
# Returns the readable identifier for the given git changeset
|
||||
def self.format_changeset_identifier(changeset)
|
||||
changeset.revision[0, 8]
|
||||
end
|
||||
|
||||
def branches
|
||||
scm.branches
|
||||
end
|
||||
|
||||
def tags
|
||||
scm.tags
|
||||
end
|
||||
|
||||
def default_branch
|
||||
scm.default_branch
|
||||
rescue Exception => e
|
||||
logger.error "git: error during get default branch: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
def find_changeset_by_name(name)
|
||||
if name.present?
|
||||
changesets.where(:revision => name.to_s).first ||
|
||||
changesets.where('scmid LIKE ?', "#{name}%").first
|
||||
end
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
scm.entries(path, identifier, :report_last_commit => report_last_commit)
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
# With SCMs that have a sequential commit numbering,
|
||||
# such as Subversion and Mercurial,
|
||||
# Redmine is able to be clever and only fetch changesets
|
||||
# going forward from the most recent one it knows about.
|
||||
#
|
||||
# However, Git does not have a sequential commit numbering.
|
||||
#
|
||||
# In order to fetch only new adding revisions,
|
||||
# Redmine needs to save "heads".
|
||||
#
|
||||
# In Git and Mercurial, revisions are not in date order.
|
||||
# Redmine Mercurial fixed issues.
|
||||
# * Redmine Takes Too Long On Large Mercurial Repository
|
||||
# http://www.redmine.org/issues/3449
|
||||
# * Sorting for changesets might go wrong on Mercurial repos
|
||||
# http://www.redmine.org/issues/3567
|
||||
#
|
||||
# Database revision column is text, so Redmine can not sort by revision.
|
||||
# Mercurial has revision number, and revision number guarantees revision order.
|
||||
# Redmine Mercurial model stored revisions ordered by database id to database.
|
||||
# So, Redmine Mercurial model can use correct ordering revisions.
|
||||
#
|
||||
# Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
|
||||
# to get limited revisions from old to new.
|
||||
# But, Git 1.7.3.4 does not support --reverse with -n or --skip.
|
||||
#
|
||||
# The repository can still be fully reloaded by calling #clear_changesets
|
||||
# before fetching changesets (eg. for offline resync)
|
||||
def fetch_changesets
|
||||
scm_brs = branches
|
||||
return if scm_brs.nil? || scm_brs.empty?
|
||||
|
||||
h1 = extra_info || {}
|
||||
h = h1.dup
|
||||
repo_heads = scm_brs.map{ |br| br.scmid }
|
||||
h["heads"] ||= []
|
||||
prev_db_heads = h["heads"].dup
|
||||
if prev_db_heads.empty?
|
||||
prev_db_heads += heads_from_branches_hash
|
||||
end
|
||||
return if prev_db_heads.sort == repo_heads.sort
|
||||
|
||||
h["db_consistent"] ||= {}
|
||||
if ! changesets.exists?
|
||||
h["db_consistent"]["ordering"] = 1
|
||||
merge_extra_info(h)
|
||||
self.save
|
||||
elsif ! h["db_consistent"].has_key?("ordering")
|
||||
h["db_consistent"]["ordering"] = 0
|
||||
merge_extra_info(h)
|
||||
self.save
|
||||
end
|
||||
save_revisions(prev_db_heads, repo_heads)
|
||||
end
|
||||
|
||||
def save_revisions(prev_db_heads, repo_heads)
|
||||
h = {}
|
||||
opts = {}
|
||||
opts[:reverse] = true
|
||||
opts[:excludes] = prev_db_heads
|
||||
opts[:includes] = repo_heads
|
||||
|
||||
revisions = scm.revisions('', nil, nil, opts)
|
||||
return if revisions.blank?
|
||||
|
||||
# Make the search for existing revisions in the database in a more sufficient manner
|
||||
#
|
||||
# Git branch is the reference to the specific revision.
|
||||
# Git can *delete* remote branch and *re-push* branch.
|
||||
#
|
||||
# $ git push remote :branch
|
||||
# $ git push remote branch
|
||||
#
|
||||
# After deleting branch, revisions remain in repository until "git gc".
|
||||
# On git 1.7.2.3, default pruning date is 2 weeks.
|
||||
# So, "git log --not deleted_branch_head_revision" return code is 0.
|
||||
#
|
||||
# After re-pushing branch, "git log" returns revisions which are saved in database.
|
||||
# So, Redmine needs to scan revisions and database every time.
|
||||
#
|
||||
# This is replacing the one-after-one queries.
|
||||
# Find all revisions, that are in the database, and then remove them
|
||||
# from the revision array.
|
||||
# Then later we won't need any conditions for db existence.
|
||||
# Query for several revisions at once, and remove them
|
||||
# from the revisions array, if they are there.
|
||||
# Do this in chunks, to avoid eventual memory problems
|
||||
# (in case of tens of thousands of commits).
|
||||
# If there are no revisions (because the original code's algorithm filtered them),
|
||||
# then this part will be stepped over.
|
||||
# We make queries, just if there is any revision.
|
||||
limit = 100
|
||||
offset = 0
|
||||
revisions_copy = revisions.clone # revisions will change
|
||||
while offset < revisions_copy.size
|
||||
scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
|
||||
recent_changesets_slice = changesets.where(:scmid => scmids)
|
||||
# Subtract revisions that redmine already knows about
|
||||
recent_revisions = recent_changesets_slice.map{|c| c.scmid}
|
||||
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
|
||||
offset += limit
|
||||
end
|
||||
revisions.each do |rev|
|
||||
transaction do
|
||||
# There is no search in the db for this revision, because above we ensured,
|
||||
# that it's not in the db.
|
||||
save_revision(rev)
|
||||
end
|
||||
end
|
||||
h["heads"] = repo_heads.dup
|
||||
merge_extra_info(h)
|
||||
save(:validate => false)
|
||||
end
|
||||
private :save_revisions
|
||||
|
||||
def save_revision(rev)
|
||||
parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
|
||||
changeset = Changeset.create(
|
||||
:repository => self,
|
||||
:revision => rev.identifier,
|
||||
:scmid => rev.scmid,
|
||||
:committer => rev.author,
|
||||
:committed_on => rev.time,
|
||||
:comments => rev.message,
|
||||
:parents => parents
|
||||
)
|
||||
unless changeset.new_record?
|
||||
rev.paths.each { |change| changeset.create_change(change) }
|
||||
end
|
||||
changeset
|
||||
end
|
||||
private :save_revision
|
||||
|
||||
def heads_from_branches_hash
|
||||
h1 = extra_info || {}
|
||||
h = h1.dup
|
||||
h["branches"] ||= {}
|
||||
h['branches'].map{|br, hs| hs['last_scmid']}
|
||||
end
|
||||
|
||||
def latest_changesets(path,rev,limit=10)
|
||||
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
|
||||
return [] if revisions.nil? || revisions.empty?
|
||||
changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
|
||||
end
|
||||
|
||||
def clear_extra_info_of_changesets
|
||||
return if extra_info.nil?
|
||||
v = extra_info["extra_report_last_commit"]
|
||||
write_attribute(:extra_info, nil)
|
||||
h = {}
|
||||
h["extra_report_last_commit"] = v
|
||||
merge_extra_info(h)
|
||||
save(:validate => false)
|
||||
end
|
||||
private :clear_extra_info_of_changesets
|
||||
|
||||
def clear_changesets
|
||||
super
|
||||
clear_extra_info_of_changesets
|
||||
end
|
||||
private :clear_changesets
|
||||
end
|
211
app/models/repository/mercurial.rb
Normal file
211
app/models/repository/mercurial.rb
Normal file
|
@ -0,0 +1,211 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/mercurial_adapter'
|
||||
|
||||
class Repository::Mercurial < Repository
|
||||
# sort changesets by revision number
|
||||
has_many :changesets,
|
||||
lambda {order("#{Changeset.table_name}.id DESC")},
|
||||
:foreign_key => 'repository_id'
|
||||
|
||||
attr_protected :root_url
|
||||
validates_presence_of :url
|
||||
|
||||
# number of changesets to fetch at once
|
||||
FETCH_AT_ONCE = 100
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "url"
|
||||
attr_name = "path_to_repository"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::MercurialAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'Mercurial'
|
||||
end
|
||||
|
||||
def supports_directory_revisions?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_revision_graph?
|
||||
true
|
||||
end
|
||||
|
||||
def repo_log_encoding
|
||||
'UTF-8'
|
||||
end
|
||||
|
||||
# Returns the readable identifier for the given mercurial changeset
|
||||
def self.format_changeset_identifier(changeset)
|
||||
"#{changeset.revision}:#{changeset.scmid[0, 12]}"
|
||||
end
|
||||
|
||||
# Returns the identifier for the given Mercurial changeset
|
||||
def self.changeset_identifier(changeset)
|
||||
changeset.scmid
|
||||
end
|
||||
|
||||
def diff_format_revisions(cs, cs_to, sep=':')
|
||||
super(cs, cs_to, ' ')
|
||||
end
|
||||
|
||||
def modify_entry_lastrev_identifier(entry)
|
||||
if entry.lastrev && entry.lastrev.identifier
|
||||
entry.lastrev.identifier = scmid_for_inserting_db(entry.lastrev.identifier)
|
||||
end
|
||||
end
|
||||
private :modify_entry_lastrev_identifier
|
||||
|
||||
def entry(path=nil, identifier=nil)
|
||||
entry = scm.entry(path, identifier)
|
||||
return nil if entry.nil?
|
||||
modify_entry_lastrev_identifier(entry)
|
||||
entry
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
entries = scm.entries(path, identifier)
|
||||
return nil if entries.nil?
|
||||
entries.each {|entry| modify_entry_lastrev_identifier(entry)}
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
# Finds and returns a revision with a number or the beginning of a hash
|
||||
def find_changeset_by_name(name)
|
||||
return nil if name.blank?
|
||||
s = name.to_s
|
||||
if /[^\d]/ =~ s or s.size > 8
|
||||
cs = changesets.where(:scmid => s).first
|
||||
else
|
||||
cs = changesets.where(:revision => s).first
|
||||
end
|
||||
return cs if cs
|
||||
changesets.where('scmid LIKE ?', "#{s}%").first
|
||||
end
|
||||
|
||||
# Returns the latest changesets for +path+; sorted by revision number
|
||||
#
|
||||
# Because :order => 'id DESC' is defined at 'has_many',
|
||||
# there is no need to set 'order'.
|
||||
# But, MySQL test fails.
|
||||
# Sqlite3 and PostgreSQL pass.
|
||||
# Is this MySQL bug?
|
||||
def latest_changesets(path, rev, limit=10)
|
||||
changesets.
|
||||
includes(:user).
|
||||
where(latest_changesets_cond(path, rev, limit)).
|
||||
references(:user).
|
||||
limit(limit).
|
||||
order("#{Changeset.table_name}.id DESC").
|
||||
to_a
|
||||
end
|
||||
|
||||
def is_short_id_in_db?
|
||||
return @is_short_id_in_db unless @is_short_id_in_db.nil?
|
||||
cs = changesets.first
|
||||
@is_short_id_in_db = (!cs.nil? && cs.scmid.length != 40)
|
||||
end
|
||||
private :is_short_id_in_db?
|
||||
|
||||
def scmid_for_inserting_db(scmid)
|
||||
is_short_id_in_db? ? scmid[0, 12] : scmid
|
||||
end
|
||||
|
||||
def nodes_in_branch(rev, branch_limit)
|
||||
scm.nodes_in_branch(rev, :limit => branch_limit).collect do |b|
|
||||
scmid_for_inserting_db(b)
|
||||
end
|
||||
end
|
||||
|
||||
def tag_scmid(rev)
|
||||
scmid = scm.tagmap[rev]
|
||||
scmid.nil? ? nil : scmid_for_inserting_db(scmid)
|
||||
end
|
||||
|
||||
def latest_changesets_cond(path, rev, limit)
|
||||
cond, args = [], []
|
||||
if scm.branchmap.member? rev
|
||||
# Mercurial named branch is *stable* in each revision.
|
||||
# So, named branch can be stored in database.
|
||||
# Mercurial provides *bookmark* which is equivalent with git branch.
|
||||
# But, bookmark is not implemented.
|
||||
cond << "#{Changeset.table_name}.scmid IN (?)"
|
||||
# Revisions in root directory and sub directory are not equal.
|
||||
# So, in order to get correct limit, we need to get all revisions.
|
||||
# But, it is very heavy.
|
||||
# Mercurial does not treat directory.
|
||||
# So, "hg log DIR" is very heavy.
|
||||
branch_limit = path.blank? ? limit : ( limit * 5 )
|
||||
args << nodes_in_branch(rev, branch_limit)
|
||||
elsif last = rev ? find_changeset_by_name(tag_scmid(rev) || rev) : nil
|
||||
cond << "#{Changeset.table_name}.id <= ?"
|
||||
args << last.id
|
||||
end
|
||||
unless path.blank?
|
||||
cond << "EXISTS (SELECT * FROM #{Change.table_name}
|
||||
WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
|
||||
AND (#{Change.table_name}.path = ?
|
||||
OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
|
||||
args << path.with_leading_slash
|
||||
args << "#{path.with_leading_slash.gsub(%r{[%_\\]}) { |s| "\\#{s}" }}/%" << '\\'
|
||||
end
|
||||
[cond.join(' AND '), *args] unless cond.empty?
|
||||
end
|
||||
private :latest_changesets_cond
|
||||
|
||||
def fetch_changesets
|
||||
return if scm.info.nil?
|
||||
scm_rev = scm.info.lastrev.revision.to_i
|
||||
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
|
||||
return unless db_rev < scm_rev # already up-to-date
|
||||
|
||||
logger.debug "Fetching changesets for repository #{url}" if logger
|
||||
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
|
||||
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
|
||||
transaction do
|
||||
parents = (re.parents || []).collect do |rp|
|
||||
find_changeset_by_name(scmid_for_inserting_db(rp))
|
||||
end.compact
|
||||
cs = Changeset.create(:repository => self,
|
||||
:revision => re.revision,
|
||||
:scmid => scmid_for_inserting_db(re.scmid),
|
||||
:committer => re.author,
|
||||
:committed_on => re.time,
|
||||
:comments => re.message,
|
||||
:parents => parents)
|
||||
unless cs.new_record?
|
||||
re.paths.each do |e|
|
||||
if from_revision = e[:from_revision]
|
||||
e[:from_revision] = scmid_for_inserting_db(from_revision)
|
||||
end
|
||||
cs.create_change(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
117
app/models/repository/subversion.rb
Normal file
117
app/models/repository/subversion.rb
Normal file
|
@ -0,0 +1,117 @@
|
|||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/scm/adapters/subversion_adapter'
|
||||
|
||||
class Repository::Subversion < Repository
|
||||
attr_protected :root_url
|
||||
validates_presence_of :url
|
||||
validates_format_of :url, :with => %r{\A(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+}i
|
||||
|
||||
def self.scm_adapter_class
|
||||
Redmine::Scm::Adapters::SubversionAdapter
|
||||
end
|
||||
|
||||
def self.scm_name
|
||||
'Subversion'
|
||||
end
|
||||
|
||||
def supports_directory_revisions?
|
||||
true
|
||||
end
|
||||
|
||||
def repo_log_encoding
|
||||
'UTF-8'
|
||||
end
|
||||
|
||||
def latest_changesets(path, rev, limit=10)
|
||||
revisions = scm.revisions(path, rev, nil, :limit => limit)
|
||||
if revisions
|
||||
identifiers = revisions.collect(&:identifier).compact
|
||||
changesets.where(:revision => identifiers).reorder("committed_on DESC").includes(:repository, :user).to_a
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a path relative to the url of the repository
|
||||
def relative_path(path)
|
||||
path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
|
||||
end
|
||||
|
||||
def fetch_changesets
|
||||
scm_info = scm.info
|
||||
if scm_info
|
||||
# latest revision found in database
|
||||
db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
|
||||
# latest revision in the repository
|
||||
scm_revision = scm_info.lastrev.identifier.to_i
|
||||
if db_revision < scm_revision
|
||||
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
|
||||
identifier_from = db_revision + 1
|
||||
while (identifier_from <= scm_revision)
|
||||
# loads changesets by batches of 200
|
||||
identifier_to = [identifier_from + 199, scm_revision].min
|
||||
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
|
||||
revisions.reverse_each do |revision|
|
||||
transaction do
|
||||
changeset = Changeset.create(:repository => self,
|
||||
:revision => revision.identifier,
|
||||
:committer => revision.author,
|
||||
:committed_on => revision.time,
|
||||
:comments => revision.message)
|
||||
|
||||
revision.paths.each do |change|
|
||||
changeset.create_change(change)
|
||||
end unless changeset.new_record?
|
||||
end
|
||||
end unless revisions.nil?
|
||||
identifier_from = identifier_to + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def load_entries_changesets(entries)
|
||||
return unless entries
|
||||
entries_with_identifier =
|
||||
entries.select {|entry| entry.lastrev && entry.lastrev.identifier.present?}
|
||||
identifiers = entries_with_identifier.map {|entry| entry.lastrev.identifier}.compact.uniq
|
||||
if identifiers.any?
|
||||
changesets_by_identifier =
|
||||
changesets.where(:revision => identifiers).
|
||||
includes(:user, :repository).group_by(&:revision)
|
||||
entries_with_identifier.each do |entry|
|
||||
if m = changesets_by_identifier[entry.lastrev.identifier]
|
||||
entry.changeset = m.first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the relative url of the repository
|
||||
# Eg: root_url = file:///var/svn/foo
|
||||
# url = file:///var/svn/foo/bar
|
||||
# => returns /bar
|
||||
def relative_url
|
||||
@relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url || scm.root_url)}", Regexp::IGNORECASE), '')
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue