OSDN Git Service

scm: git: use the model value of whether reporting last commit in repository tree...
[redminele/redmine.git] / app / models / repository / cvs.rb
1 # redMine - project management software
2 # Copyright (C) 2006-2007  Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17
18 require 'redmine/scm/adapters/cvs_adapter'
19 require 'digest/sha1'
20
21 class Repository::Cvs < Repository
22   validates_presence_of :url, :root_url, :log_encoding
23
24   def self.human_attribute_name(attribute_key_name)
25     attr_name = attribute_key_name
26     if attr_name == "root_url"
27       attr_name = "cvsroot"
28     elsif attr_name == "url"
29       attr_name = "cvs_module"
30     end
31     super(attr_name)
32   end
33
34   def self.scm_adapter_class
35     Redmine::Scm::Adapters::CvsAdapter
36   end
37
38   def self.scm_name
39     'CVS'
40   end
41
42   def entry(path=nil, identifier=nil)
43     rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
44     scm.entry(path, rev.nil? ? nil : rev.committed_on)
45   end
46
47   def entries(path=nil, identifier=nil)
48     rev = nil
49     if ! identifier.nil?
50       rev = changesets.find_by_revision(identifier)
51       return nil if rev.nil?
52     end
53     entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
54     if entries
55       entries.each() do |entry|
56         if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? )
57           change=changes.find_by_revision_and_path(
58                      entry.lastrev.revision,
59                      scm.with_leading_slash(entry.path) )
60           if change
61             entry.lastrev.identifier = change.changeset.revision
62             entry.lastrev.revision   = change.changeset.revision
63             entry.lastrev.author     = change.changeset.committer
64             # entry.lastrev.branch     = change.branch
65           end
66         end
67       end
68     end
69     entries
70   end
71
72   def cat(path, identifier=nil)
73     rev = nil
74     if ! identifier.nil?
75       rev = changesets.find_by_revision(identifier)
76       return nil if rev.nil?
77     end
78     scm.cat(path, rev.nil? ? nil : rev.committed_on)
79   end
80
81   def annotate(path, identifier=nil)
82     rev = nil
83     if ! identifier.nil?
84       rev = changesets.find_by_revision(identifier)
85       return nil if rev.nil?
86     end
87     scm.annotate(path, rev.nil? ? nil : rev.committed_on)
88   end
89
90   def diff(path, rev, rev_to)
91     # convert rev to revision. CVS can't handle changesets here
92     diff=[]
93     changeset_from = changesets.find_by_revision(rev)
94     if rev_to.to_i > 0 
95       changeset_to = changesets.find_by_revision(rev_to)
96     end
97     changeset_from.changes.each() do |change_from|
98       revision_from = nil
99       revision_to   = nil      
100       if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
101         revision_from = change_from.revision
102       end
103       if revision_from
104         if changeset_to
105           changeset_to.changes.each() do |change_to|
106             revision_to=change_to.revision if change_to.path==change_from.path 
107           end
108         end
109         unless revision_to
110           revision_to=scm.get_previous_revision(revision_from)
111         end
112         file_diff = scm.diff(change_from.path, revision_from, revision_to)
113         diff = diff + file_diff unless file_diff.nil?
114       end
115     end
116     return diff
117   end
118
119   def fetch_changesets
120     # some nifty bits to introduce a commit-id with cvs
121     # natively cvs doesn't provide any kind of changesets,
122     # there is only a revision per file.
123     # we now take a guess using the author, the commitlog and the commit-date.
124     
125     # last one is the next step to take. the commit-date is not equal for all 
126     # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
127     # we use a small delta here, to merge all changes belonging to _one_ changeset
128     time_delta  = 10.seconds
129     fetch_since = latest_changeset ? latest_changeset.committed_on : nil
130     transaction do
131       tmp_rev_num = 1
132       scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision|
133         # only add the change to the database, if it doen't exists. the cvs log
134         # is not exclusive at all. 
135         tmp_time = revision.time.clone
136         unless changes.find_by_path_and_revision(
137                                  scm.with_leading_slash(revision.paths[0][:path]),
138                                  revision.paths[0][:revision]
139                                    )
140           cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
141           author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
142           cs  = changesets.find(
143             :first,
144             :conditions => {
145                 :committed_on => tmp_time - time_delta .. tmp_time + time_delta,
146                 :committer    => author_utf8,
147                 :comments     => cmt
148                 }
149              )
150           # create a new changeset.... 
151           unless cs
152             # we use a temporaray revision number here (just for inserting)
153             # later on, we calculate a continous positive number
154             tmp_time2 = tmp_time.clone.gmtime
155             branch    = revision.paths[0][:branch]
156             scmid     = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
157             cs = Changeset.create(:repository   => self,
158                                   :revision     => "tmp#{tmp_rev_num}",
159                                   :scmid        => scmid,
160                                   :committer    => revision.author, 
161                                   :committed_on => tmp_time,
162                                   :comments     => revision.message)
163             tmp_rev_num += 1
164           end
165           # convert CVS-File-States to internal Action-abbrevations
166           # default action is (M)odified
167           action = "M"
168           if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1"
169             action = "A" # add-action always at first revision (= 1.1)
170           elsif revision.paths[0][:action] == "dead"
171             action = "D" # dead-state is similar to Delete
172           end
173           Change.create(
174              :changeset => cs,
175              :action    => action,
176              :path      => scm.with_leading_slash(revision.paths[0][:path]),
177              :revision  => revision.paths[0][:revision],
178              :branch    => revision.paths[0][:branch]
179               )
180         end
181       end
182  
183       # Renumber new changesets in chronological order
184       changesets.find(
185               :all,
186               :order => 'committed_on ASC, id ASC',
187               :conditions => "revision LIKE 'tmp%'"
188            ).each do |changeset|
189         changeset.update_attribute :revision, next_revision_number
190       end
191     end # transaction
192     @current_revision_number = nil
193   end
194
195   private
196
197   # Returns the next revision number to assign to a CVS changeset
198   def next_revision_number
199     # Need to retrieve existing revision numbers to sort them as integers
200     sql = "SELECT revision FROM #{Changeset.table_name} "
201     sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
202     @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
203     @current_revision_number += 1
204   end
205 end