##// END OF EJS Templates
Refactor: Extract method to create a Change from a Changeset....
Eric Davis -
r3246:39c585740d45
parent child
Show More
@@ -1,181 +1,190
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'iconv'
18 require 'iconv'
19
19
20 class Changeset < ActiveRecord::Base
20 class Changeset < ActiveRecord::Base
21 belongs_to :repository
21 belongs_to :repository
22 belongs_to :user
22 belongs_to :user
23 has_many :changes, :dependent => :delete_all
23 has_many :changes, :dependent => :delete_all
24 has_and_belongs_to_many :issues
24 has_and_belongs_to_many :issues
25
25
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 :description => :long_comments,
27 :description => :long_comments,
28 :datetime => :committed_on,
28 :datetime => :committed_on,
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
30
30
31 acts_as_searchable :columns => 'comments',
31 acts_as_searchable :columns => 'comments',
32 :include => {:repository => :project},
32 :include => {:repository => :project},
33 :project_key => "#{Repository.table_name}.project_id",
33 :project_key => "#{Repository.table_name}.project_id",
34 :date_column => 'committed_on'
34 :date_column => 'committed_on'
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 :author_key => :user_id,
37 :author_key => :user_id,
38 :find_options => {:include => [:user, {:repository => :project}]}
38 :find_options => {:include => [:user, {:repository => :project}]}
39
39
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
41 validates_uniqueness_of :revision, :scope => :repository_id
41 validates_uniqueness_of :revision, :scope => :repository_id
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
43
43
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
45 :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
45 :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
46
46
47 def revision=(r)
47 def revision=(r)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
49 end
49 end
50
50
51 def comments=(comment)
51 def comments=(comment)
52 write_attribute(:comments, Changeset.normalize_comments(comment))
52 write_attribute(:comments, Changeset.normalize_comments(comment))
53 end
53 end
54
54
55 def committed_on=(date)
55 def committed_on=(date)
56 self.commit_date = date
56 self.commit_date = date
57 super
57 super
58 end
58 end
59
59
60 def project
60 def project
61 repository.project
61 repository.project
62 end
62 end
63
63
64 def author
64 def author
65 user || committer.to_s.split('<').first
65 user || committer.to_s.split('<').first
66 end
66 end
67
67
68 def before_create
68 def before_create
69 self.user = repository.find_committer_user(committer)
69 self.user = repository.find_committer_user(committer)
70 end
70 end
71
71
72 def after_create
72 def after_create
73 scan_comment_for_issue_ids
73 scan_comment_for_issue_ids
74 end
74 end
75 require 'pp'
75 require 'pp'
76
76
77 def scan_comment_for_issue_ids
77 def scan_comment_for_issue_ids
78 return if comments.blank?
78 return if comments.blank?
79 # keywords used to reference issues
79 # keywords used to reference issues
80 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
80 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
81 # keywords used to fix issues
81 # keywords used to fix issues
82 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
82 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
83 # status and optional done ratio applied
83 # status and optional done ratio applied
84 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
84 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
85 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
85 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
86
86
87 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
87 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
88 return if kw_regexp.blank?
88 return if kw_regexp.blank?
89
89
90 referenced_issues = []
90 referenced_issues = []
91
91
92 if ref_keywords.delete('*')
92 if ref_keywords.delete('*')
93 # find any issue ID in the comments
93 # find any issue ID in the comments
94 target_issue_ids = []
94 target_issue_ids = []
95 comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
95 comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
96 referenced_issues += find_referenced_issues_by_id(target_issue_ids)
96 referenced_issues += find_referenced_issues_by_id(target_issue_ids)
97 end
97 end
98
98
99 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
99 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
100 action = match[0]
100 action = match[0]
101 target_issue_ids = match[1].scan(/\d+/)
101 target_issue_ids = match[1].scan(/\d+/)
102 target_issues = find_referenced_issues_by_id(target_issue_ids)
102 target_issues = find_referenced_issues_by_id(target_issue_ids)
103 if fix_status && fix_keywords.include?(action.downcase)
103 if fix_status && fix_keywords.include?(action.downcase)
104 # update status of issues
104 # update status of issues
105 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
105 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
106 target_issues.each do |issue|
106 target_issues.each do |issue|
107 # the issue may have been updated by the closure of another one (eg. duplicate)
107 # the issue may have been updated by the closure of another one (eg. duplicate)
108 issue.reload
108 issue.reload
109 # don't change the status is the issue is closed
109 # don't change the status is the issue is closed
110 next if issue.status.is_closed?
110 next if issue.status.is_closed?
111 csettext = "r#{self.revision}"
111 csettext = "r#{self.revision}"
112 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
112 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
113 csettext = "commit:\"#{self.scmid}\""
113 csettext = "commit:\"#{self.scmid}\""
114 end
114 end
115 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
115 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
116 issue.status = fix_status
116 issue.status = fix_status
117 issue.done_ratio = done_ratio if done_ratio
117 issue.done_ratio = done_ratio if done_ratio
118 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
118 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
119 { :changeset => self, :issue => issue })
119 { :changeset => self, :issue => issue })
120 issue.save
120 issue.save
121 end
121 end
122 end
122 end
123 referenced_issues += target_issues
123 referenced_issues += target_issues
124 end
124 end
125
125
126 self.issues = referenced_issues.uniq
126 self.issues = referenced_issues.uniq
127 end
127 end
128
128
129 def short_comments
129 def short_comments
130 @short_comments || split_comments.first
130 @short_comments || split_comments.first
131 end
131 end
132
132
133 def long_comments
133 def long_comments
134 @long_comments || split_comments.last
134 @long_comments || split_comments.last
135 end
135 end
136
136
137 # Returns the previous changeset
137 # Returns the previous changeset
138 def previous
138 def previous
139 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
139 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
140 end
140 end
141
141
142 # Returns the next changeset
142 # Returns the next changeset
143 def next
143 def next
144 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
144 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
145 end
145 end
146
146
147 # Strips and reencodes a commit log before insertion into the database
147 # Strips and reencodes a commit log before insertion into the database
148 def self.normalize_comments(str)
148 def self.normalize_comments(str)
149 to_utf8(str.to_s.strip)
149 to_utf8(str.to_s.strip)
150 end
150 end
151
152 # Creates a new Change from it's common parameters
153 def create_change(change)
154 Change.create(:changeset => self,
155 :action => change[:action],
156 :path => change[:path],
157 :from_path => change[:from_path],
158 :from_revision => change[:from_revision])
159 end
151
160
152 private
161 private
153
162
154 # Finds issues that can be referenced by the commit message
163 # Finds issues that can be referenced by the commit message
155 # i.e. issues that belong to the repository project, a subproject or a parent project
164 # i.e. issues that belong to the repository project, a subproject or a parent project
156 def find_referenced_issues_by_id(ids)
165 def find_referenced_issues_by_id(ids)
157 Issue.find_all_by_id(ids, :include => :project).select {|issue|
166 Issue.find_all_by_id(ids, :include => :project).select {|issue|
158 project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
167 project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
159 }
168 }
160 end
169 end
161
170
162 def split_comments
171 def split_comments
163 comments =~ /\A(.+?)\r?\n(.*)$/m
172 comments =~ /\A(.+?)\r?\n(.*)$/m
164 @short_comments = $1 || comments
173 @short_comments = $1 || comments
165 @long_comments = $2.to_s.strip
174 @long_comments = $2.to_s.strip
166 return @short_comments, @long_comments
175 return @short_comments, @long_comments
167 end
176 end
168
177
169 def self.to_utf8(str)
178 def self.to_utf8(str)
170 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
179 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
171 encoding = Setting.commit_logs_encoding.to_s.strip
180 encoding = Setting.commit_logs_encoding.to_s.strip
172 unless encoding.blank? || encoding == 'UTF-8'
181 unless encoding.blank? || encoding == 'UTF-8'
173 begin
182 begin
174 return Iconv.conv('UTF-8', encoding, str)
183 return Iconv.conv('UTF-8', encoding, str)
175 rescue Iconv::Failure
184 rescue Iconv::Failure
176 # do nothing here
185 # do nothing here
177 end
186 end
178 end
187 end
179 str
188 str
180 end
189 end
181 end
190 end
@@ -1,100 +1,96
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/darcs_adapter'
18 require 'redmine/scm/adapters/darcs_adapter'
19
19
20 class Repository::Darcs < Repository
20 class Repository::Darcs < Repository
21 validates_presence_of :url
21 validates_presence_of :url
22
22
23 def scm_adapter
23 def scm_adapter
24 Redmine::Scm::Adapters::DarcsAdapter
24 Redmine::Scm::Adapters::DarcsAdapter
25 end
25 end
26
26
27 def self.scm_name
27 def self.scm_name
28 'Darcs'
28 'Darcs'
29 end
29 end
30
30
31 def entry(path=nil, identifier=nil)
31 def entry(path=nil, identifier=nil)
32 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
32 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
33 scm.entry(path, patch.nil? ? nil : patch.scmid)
33 scm.entry(path, patch.nil? ? nil : patch.scmid)
34 end
34 end
35
35
36 def entries(path=nil, identifier=nil)
36 def entries(path=nil, identifier=nil)
37 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
37 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
38 entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
38 entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
39 if entries
39 if entries
40 entries.each do |entry|
40 entries.each do |entry|
41 # Search the DB for the entry's last change
41 # Search the DB for the entry's last change
42 changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
42 changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
43 if changeset
43 if changeset
44 entry.lastrev.identifier = changeset.revision
44 entry.lastrev.identifier = changeset.revision
45 entry.lastrev.name = changeset.revision
45 entry.lastrev.name = changeset.revision
46 entry.lastrev.time = changeset.committed_on
46 entry.lastrev.time = changeset.committed_on
47 entry.lastrev.author = changeset.committer
47 entry.lastrev.author = changeset.committer
48 end
48 end
49 end
49 end
50 end
50 end
51 entries
51 entries
52 end
52 end
53
53
54 def cat(path, identifier=nil)
54 def cat(path, identifier=nil)
55 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
55 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
56 scm.cat(path, patch.nil? ? nil : patch.scmid)
56 scm.cat(path, patch.nil? ? nil : patch.scmid)
57 end
57 end
58
58
59 def diff(path, rev, rev_to)
59 def diff(path, rev, rev_to)
60 patch_from = changesets.find_by_revision(rev)
60 patch_from = changesets.find_by_revision(rev)
61 return nil if patch_from.nil?
61 return nil if patch_from.nil?
62 patch_to = changesets.find_by_revision(rev_to) if rev_to
62 patch_to = changesets.find_by_revision(rev_to) if rev_to
63 if path.blank?
63 if path.blank?
64 path = patch_from.changes.collect{|change| change.path}.join(' ')
64 path = patch_from.changes.collect{|change| change.path}.join(' ')
65 end
65 end
66 patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil
66 patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil
67 end
67 end
68
68
69 def fetch_changesets
69 def fetch_changesets
70 scm_info = scm.info
70 scm_info = scm.info
71 if scm_info
71 if scm_info
72 db_last_id = latest_changeset ? latest_changeset.scmid : nil
72 db_last_id = latest_changeset ? latest_changeset.scmid : nil
73 next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1
73 next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1
74 # latest revision in the repository
74 # latest revision in the repository
75 scm_revision = scm_info.lastrev.scmid
75 scm_revision = scm_info.lastrev.scmid
76 unless changesets.find_by_scmid(scm_revision)
76 unless changesets.find_by_scmid(scm_revision)
77 revisions = scm.revisions('', db_last_id, nil, :with_path => true)
77 revisions = scm.revisions('', db_last_id, nil, :with_path => true)
78 transaction do
78 transaction do
79 revisions.reverse_each do |revision|
79 revisions.reverse_each do |revision|
80 changeset = Changeset.create(:repository => self,
80 changeset = Changeset.create(:repository => self,
81 :revision => next_rev,
81 :revision => next_rev,
82 :scmid => revision.scmid,
82 :scmid => revision.scmid,
83 :committer => revision.author,
83 :committer => revision.author,
84 :committed_on => revision.time,
84 :committed_on => revision.time,
85 :comments => revision.message)
85 :comments => revision.message)
86
86
87 revision.paths.each do |change|
87 revision.paths.each do |change|
88 Change.create(:changeset => changeset,
88 changeset.create_change(change)
89 :action => change[:action],
90 :path => change[:path],
91 :from_path => change[:from_path],
92 :from_revision => change[:from_revision])
93 end
89 end
94 next_rev += 1
90 next_rev += 1
95 end if revisions
91 end if revisions
96 end
92 end
97 end
93 end
98 end
94 end
99 end
95 end
100 end
96 end
@@ -1,94 +1,90
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/mercurial_adapter'
18 require 'redmine/scm/adapters/mercurial_adapter'
19
19
20 class Repository::Mercurial < Repository
20 class Repository::Mercurial < Repository
21 attr_protected :root_url
21 attr_protected :root_url
22 validates_presence_of :url
22 validates_presence_of :url
23
23
24 def scm_adapter
24 def scm_adapter
25 Redmine::Scm::Adapters::MercurialAdapter
25 Redmine::Scm::Adapters::MercurialAdapter
26 end
26 end
27
27
28 def self.scm_name
28 def self.scm_name
29 'Mercurial'
29 'Mercurial'
30 end
30 end
31
31
32 def entries(path=nil, identifier=nil)
32 def entries(path=nil, identifier=nil)
33 entries=scm.entries(path, identifier)
33 entries=scm.entries(path, identifier)
34 if entries
34 if entries
35 entries.each do |entry|
35 entries.each do |entry|
36 next unless entry.is_file?
36 next unless entry.is_file?
37 # Set the filesize unless browsing a specific revision
37 # Set the filesize unless browsing a specific revision
38 if identifier.nil?
38 if identifier.nil?
39 full_path = File.join(root_url, entry.path)
39 full_path = File.join(root_url, entry.path)
40 entry.size = File.stat(full_path).size if File.file?(full_path)
40 entry.size = File.stat(full_path).size if File.file?(full_path)
41 end
41 end
42 # Search the DB for the entry's last change
42 # Search the DB for the entry's last change
43 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
43 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
44 if change
44 if change
45 entry.lastrev.identifier = change.changeset.revision
45 entry.lastrev.identifier = change.changeset.revision
46 entry.lastrev.name = change.changeset.revision
46 entry.lastrev.name = change.changeset.revision
47 entry.lastrev.author = change.changeset.committer
47 entry.lastrev.author = change.changeset.committer
48 entry.lastrev.revision = change.revision
48 entry.lastrev.revision = change.revision
49 end
49 end
50 end
50 end
51 end
51 end
52 entries
52 entries
53 end
53 end
54
54
55 def fetch_changesets
55 def fetch_changesets
56 scm_info = scm.info
56 scm_info = scm.info
57 if scm_info
57 if scm_info
58 # latest revision found in database
58 # latest revision found in database
59 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
59 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
60 # latest revision in the repository
60 # latest revision in the repository
61 latest_revision = scm_info.lastrev
61 latest_revision = scm_info.lastrev
62 return if latest_revision.nil?
62 return if latest_revision.nil?
63 scm_revision = latest_revision.identifier.to_i
63 scm_revision = latest_revision.identifier.to_i
64 if db_revision < scm_revision
64 if db_revision < scm_revision
65 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
65 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
66 identifier_from = db_revision + 1
66 identifier_from = db_revision + 1
67 while (identifier_from <= scm_revision)
67 while (identifier_from <= scm_revision)
68 # loads changesets by batches of 100
68 # loads changesets by batches of 100
69 identifier_to = [identifier_from + 99, scm_revision].min
69 identifier_to = [identifier_from + 99, scm_revision].min
70 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
70 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
71 transaction do
71 transaction do
72 revisions.each do |revision|
72 revisions.each do |revision|
73 changeset = Changeset.create(:repository => self,
73 changeset = Changeset.create(:repository => self,
74 :revision => revision.identifier,
74 :revision => revision.identifier,
75 :scmid => revision.scmid,
75 :scmid => revision.scmid,
76 :committer => revision.author,
76 :committer => revision.author,
77 :committed_on => revision.time,
77 :committed_on => revision.time,
78 :comments => revision.message)
78 :comments => revision.message)
79
79
80 revision.paths.each do |change|
80 revision.paths.each do |change|
81 Change.create(:changeset => changeset,
81 changeset.create_change(change)
82 :action => change[:action],
83 :path => change[:path],
84 :from_path => change[:from_path],
85 :from_revision => change[:from_revision])
86 end
82 end
87 end
83 end
88 end unless revisions.nil?
84 end unless revisions.nil?
89 identifier_from = identifier_to + 1
85 identifier_from = identifier_to + 1
90 end
86 end
91 end
87 end
92 end
88 end
93 end
89 end
94 end
90 end
@@ -1,89 +1,85
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/subversion_adapter'
18 require 'redmine/scm/adapters/subversion_adapter'
19
19
20 class Repository::Subversion < Repository
20 class Repository::Subversion < Repository
21 attr_protected :root_url
21 attr_protected :root_url
22 validates_presence_of :url
22 validates_presence_of :url
23 validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
23 validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
24
24
25 def scm_adapter
25 def scm_adapter
26 Redmine::Scm::Adapters::SubversionAdapter
26 Redmine::Scm::Adapters::SubversionAdapter
27 end
27 end
28
28
29 def self.scm_name
29 def self.scm_name
30 'Subversion'
30 'Subversion'
31 end
31 end
32
32
33 def latest_changesets(path, rev, limit=10)
33 def latest_changesets(path, rev, limit=10)
34 revisions = scm.revisions(path, rev, nil, :limit => limit)
34 revisions = scm.revisions(path, rev, nil, :limit => limit)
35 revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
35 revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
36 end
36 end
37
37
38 # Returns a path relative to the url of the repository
38 # Returns a path relative to the url of the repository
39 def relative_path(path)
39 def relative_path(path)
40 path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
40 path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
41 end
41 end
42
42
43 def fetch_changesets
43 def fetch_changesets
44 scm_info = scm.info
44 scm_info = scm.info
45 if scm_info
45 if scm_info
46 # latest revision found in database
46 # latest revision found in database
47 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
47 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
48 # latest revision in the repository
48 # latest revision in the repository
49 scm_revision = scm_info.lastrev.identifier.to_i
49 scm_revision = scm_info.lastrev.identifier.to_i
50 if db_revision < scm_revision
50 if db_revision < scm_revision
51 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
51 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
52 identifier_from = db_revision + 1
52 identifier_from = db_revision + 1
53 while (identifier_from <= scm_revision)
53 while (identifier_from <= scm_revision)
54 # loads changesets by batches of 200
54 # loads changesets by batches of 200
55 identifier_to = [identifier_from + 199, scm_revision].min
55 identifier_to = [identifier_from + 199, scm_revision].min
56 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
56 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
57 revisions.reverse_each do |revision|
57 revisions.reverse_each do |revision|
58 transaction do
58 transaction do
59 changeset = Changeset.create(:repository => self,
59 changeset = Changeset.create(:repository => self,
60 :revision => revision.identifier,
60 :revision => revision.identifier,
61 :committer => revision.author,
61 :committer => revision.author,
62 :committed_on => revision.time,
62 :committed_on => revision.time,
63 :comments => revision.message)
63 :comments => revision.message)
64
64
65 revision.paths.each do |change|
65 revision.paths.each do |change|
66 Change.create(:changeset => changeset,
66 changeset.create_change(change)
67 :action => change[:action],
68 :path => change[:path],
69 :from_path => change[:from_path],
70 :from_revision => change[:from_revision])
71 end unless changeset.new_record?
67 end unless changeset.new_record?
72 end
68 end
73 end unless revisions.nil?
69 end unless revisions.nil?
74 identifier_from = identifier_to + 1
70 identifier_from = identifier_to + 1
75 end
71 end
76 end
72 end
77 end
73 end
78 end
74 end
79
75
80 private
76 private
81
77
82 # Returns the relative url of the repository
78 # Returns the relative url of the repository
83 # Eg: root_url = file:///var/svn/foo
79 # Eg: root_url = file:///var/svn/foo
84 # url = file:///var/svn/foo/bar
80 # url = file:///var/svn/foo/bar
85 # => returns /bar
81 # => returns /bar
86 def relative_url
82 def relative_url
87 @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url || scm.root_url)}", Regexp::IGNORECASE), '')
83 @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url || scm.root_url)}", Regexp::IGNORECASE), '')
88 end
84 end
89 end
85 end
General Comments 0
You need to be logged in to leave comments. Login now