cvs.rb
208 lines
| 7.6 KiB
| text/x-ruby
|
RubyLexer
|
r5633 | # Redmine - project management software | ||
|
r9453 | # Copyright (C) 2006-2012 Jean-Philippe Lang | ||
|
r556 | # | ||
# 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. | ||||
|
r5633 | # | ||
|
r556 | # 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. | ||||
|
r5633 | # | ||
|
r556 | # 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 | ||||
|
r4862 | validates_presence_of :url, :root_url, :log_encoding | ||
|
r556 | |||
|
r9695 | safe_attributes 'root_url', | ||
:if => lambda {|repository, user| repository.new_record?} | ||||
|
r9693 | |||
|
r8166 | def self.human_attribute_name(attribute_key_name, *args) | ||
|
r8852 | attr_name = attribute_key_name.to_s | ||
|
r5415 | if attr_name == "root_url" | ||
attr_name = "cvsroot" | ||||
elsif attr_name == "url" | ||||
attr_name = "cvs_module" | ||||
end | ||||
|
r8166 | super(attr_name, *args) | ||
|
r4855 | end | ||
|
r4702 | def self.scm_adapter_class | ||
|
r556 | Redmine::Scm::Adapters::CvsAdapter | ||
end | ||||
|
r4702 | |||
|
r556 | def self.scm_name | ||
'CVS' | ||||
end | ||||
|
r4702 | |||
|
r1539 | def entry(path=nil, identifier=nil) | ||
rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) | ||||
scm.entry(path, rev.nil? ? nil : rev.committed_on) | ||||
|
r556 | end | ||
|
r5311 | |||
|
r556 | def entries(path=nil, identifier=nil) | ||
|
r5311 | rev = nil | ||
if ! identifier.nil? | ||||
rev = changesets.find_by_revision(identifier) | ||||
return nil if rev.nil? | ||||
end | ||||
|
r1314 | entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) | ||
|
r556 | if entries | ||
entries.each() do |entry| | ||||
|
r4973 | if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? ) | ||
|
r9576 | change = filechanges.find_by_revision_and_path( | ||
|
r4964 | entry.lastrev.revision, | ||
scm.with_leading_slash(entry.path) ) | ||||
|
r556 | if change | ||
|
r4964 | entry.lastrev.identifier = change.changeset.revision | ||
|
r4973 | entry.lastrev.revision = change.changeset.revision | ||
|
r4964 | entry.lastrev.author = change.changeset.committer | ||
|
r4973 | # entry.lastrev.branch = change.branch | ||
|
r556 | end | ||
end | ||||
end | ||||
end | ||||
|
r9622 | load_entries_changesets(entries) | ||
|
r556 | entries | ||
end | ||||
|
r5290 | |||
|
r1539 | def cat(path, identifier=nil) | ||
|
r5288 | rev = nil | ||
if ! identifier.nil? | ||||
rev = changesets.find_by_revision(identifier) | ||||
return nil if rev.nil? | ||||
end | ||||
|
r1539 | scm.cat(path, rev.nil? ? nil : rev.committed_on) | ||
end | ||||
|
r5290 | |||
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 | ||||
|
r1499 | def diff(path, rev, rev_to) | ||
|
r5286 | # convert rev to revision. CVS can't handle changesets here | ||
|
r556 | diff=[] | ||
|
r5286 | changeset_from = changesets.find_by_revision(rev) | ||
|
r5633 | if rev_to.to_i > 0 | ||
|
r5286 | changeset_to = changesets.find_by_revision(rev_to) | ||
|
r556 | end | ||
|
r9576 | changeset_from.filechanges.each() do |change_from| | ||
|
r5256 | revision_from = nil | ||
|
r5633 | revision_to = nil | ||
|
r5256 | if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) | ||
|
r5286 | revision_from = change_from.revision | ||
|
r5256 | end | ||
|
r556 | if revision_from | ||
if changeset_to | ||||
|
r9576 | changeset_to.filechanges.each() do |change_to| | ||
|
r5633 | revision_to = change_to.revision if change_to.path == change_from.path | ||
|
r556 | end | ||
end | ||||
unless revision_to | ||||
|
r5633 | revision_to = scm.get_previous_revision(revision_from) | ||
|
r556 | end | ||
|
r1511 | file_diff = scm.diff(change_from.path, revision_from, revision_to) | ||
diff = diff + file_diff unless file_diff.nil? | ||||
|
r556 | end | ||
end | ||||
return diff | ||||
end | ||||
|
r5256 | |||
|
r556 | def fetch_changesets | ||
# some nifty bits to introduce a commit-id with cvs | ||||
|
r5256 | # natively cvs doesn't provide any kind of changesets, | ||
# there is only a revision per file. | ||||
|
r556 | # we now take a guess using the author, the commitlog and the commit-date. | ||
|
r5633 | |||
# last one is the next step to take. the commit-date is not equal for all | ||||
|
r556 | # 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 | ||||
|
r5330 | time_delta = 10.seconds | ||
|
r1222 | fetch_since = latest_changeset ? latest_changeset.committed_on : nil | ||
|
r556 | transaction do | ||
|
r1222 | tmp_rev_num = 1 | ||
|
r5336 | scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision| | ||
|
r556 | # only add the change to the database, if it doen't exists. the cvs log | ||
|
r5633 | # is not exclusive at all. | ||
|
r4682 | tmp_time = revision.time.clone | ||
|
r9576 | unless filechanges.find_by_path_and_revision( | ||
|
r5330 | scm.with_leading_slash(revision.paths[0][:path]), | ||
revision.paths[0][:revision] | ||||
) | ||||
|
r4842 | cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) | ||
|
r5335 | author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) | ||
|
r5330 | cs = changesets.find( | ||
:first, | ||||
:conditions => { | ||||
:committed_on => tmp_time - time_delta .. tmp_time + time_delta, | ||||
|
r5335 | :committer => author_utf8, | ||
|
r5330 | :comments => cmt | ||
} | ||||
) | ||||
|
r5633 | # create a new changeset.... | ||
|
r1222 | unless cs | ||
# we use a temporaray revision number here (just for inserting) | ||||
|
r556 | # later on, we calculate a continous positive number | ||
|
r4682 | tmp_time2 = tmp_time.clone.gmtime | ||
|
r5330 | 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, | ||||
|
r5633 | :committer => revision.author, | ||
|
r4682 | :committed_on => tmp_time, | ||
|
r5330 | :comments => revision.message) | ||
|
r1222 | tmp_rev_num += 1 | ||
|
r556 | end | ||
|
r5330 | # convert CVS-File-States to internal Action-abbrevations | ||
# 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 | ||||
|
r556 | end | ||
|
r5256 | 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] | ||||
) | ||||
|
r556 | end | ||
end | ||||
|
r5633 | |||
|
r1222 | # Renumber new changesets in chronological order | ||
|
r8895 | Changeset.all( | ||
|
r5256 | :order => 'committed_on ASC, id ASC', | ||
|
r8895 | :conditions => ["repository_id = ? AND revision LIKE 'tmp%'", id] | ||
|
r4670 | ).each do |changeset| | ||
|
r1340 | changeset.update_attribute :revision, next_revision_number | ||
|
r556 | end | ||
|
r1222 | end # transaction | ||
|
r4681 | @current_revision_number = nil | ||
|
r556 | end | ||
|
r5256 | |||
|
r1340 | private | ||
|
r5256 | |||
|
r1340 | # 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 | ||||
|
r4681 | sql = "SELECT revision FROM #{Changeset.table_name} " | ||
sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'" | ||||
@current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0) | ||||
|
r1340 | @current_revision_number += 1 | ||
end | ||||
|
r556 | end | ||