##// END OF EJS Templates
Don't validate the repository when updating/clearing extra info (#19400)....
Jean-Philippe Lang -
r13761:98c28b467b30
parent child
Show More
@@ -1,263 +1,263
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com
3 # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require 'redmine/scm/adapters/git_adapter'
19 require 'redmine/scm/adapters/git_adapter'
20
20
21 class Repository::Git < Repository
21 class Repository::Git < Repository
22 attr_protected :root_url
22 attr_protected :root_url
23 validates_presence_of :url
23 validates_presence_of :url
24
24
25 def self.human_attribute_name(attribute_key_name, *args)
25 def self.human_attribute_name(attribute_key_name, *args)
26 attr_name = attribute_key_name.to_s
26 attr_name = attribute_key_name.to_s
27 if attr_name == "url"
27 if attr_name == "url"
28 attr_name = "path_to_repository"
28 attr_name = "path_to_repository"
29 end
29 end
30 super(attr_name, *args)
30 super(attr_name, *args)
31 end
31 end
32
32
33 def self.scm_adapter_class
33 def self.scm_adapter_class
34 Redmine::Scm::Adapters::GitAdapter
34 Redmine::Scm::Adapters::GitAdapter
35 end
35 end
36
36
37 def self.scm_name
37 def self.scm_name
38 'Git'
38 'Git'
39 end
39 end
40
40
41 def report_last_commit
41 def report_last_commit
42 extra_report_last_commit
42 extra_report_last_commit
43 end
43 end
44
44
45 def extra_report_last_commit
45 def extra_report_last_commit
46 return false if extra_info.nil?
46 return false if extra_info.nil?
47 v = extra_info["extra_report_last_commit"]
47 v = extra_info["extra_report_last_commit"]
48 return false if v.nil?
48 return false if v.nil?
49 v.to_s != '0'
49 v.to_s != '0'
50 end
50 end
51
51
52 def supports_directory_revisions?
52 def supports_directory_revisions?
53 true
53 true
54 end
54 end
55
55
56 def supports_revision_graph?
56 def supports_revision_graph?
57 true
57 true
58 end
58 end
59
59
60 def repo_log_encoding
60 def repo_log_encoding
61 'UTF-8'
61 'UTF-8'
62 end
62 end
63
63
64 # Returns the identifier for the given git changeset
64 # Returns the identifier for the given git changeset
65 def self.changeset_identifier(changeset)
65 def self.changeset_identifier(changeset)
66 changeset.scmid
66 changeset.scmid
67 end
67 end
68
68
69 # Returns the readable identifier for the given git changeset
69 # Returns the readable identifier for the given git changeset
70 def self.format_changeset_identifier(changeset)
70 def self.format_changeset_identifier(changeset)
71 changeset.revision[0, 8]
71 changeset.revision[0, 8]
72 end
72 end
73
73
74 def branches
74 def branches
75 scm.branches
75 scm.branches
76 end
76 end
77
77
78 def tags
78 def tags
79 scm.tags
79 scm.tags
80 end
80 end
81
81
82 def default_branch
82 def default_branch
83 scm.default_branch
83 scm.default_branch
84 rescue Exception => e
84 rescue Exception => e
85 logger.error "git: error during get default branch: #{e.message}"
85 logger.error "git: error during get default branch: #{e.message}"
86 nil
86 nil
87 end
87 end
88
88
89 def find_changeset_by_name(name)
89 def find_changeset_by_name(name)
90 if name.present?
90 if name.present?
91 changesets.where(:revision => name.to_s).first ||
91 changesets.where(:revision => name.to_s).first ||
92 changesets.where('scmid LIKE ?', "#{name}%").first
92 changesets.where('scmid LIKE ?', "#{name}%").first
93 end
93 end
94 end
94 end
95
95
96 def scm_entries(path=nil, identifier=nil)
96 def scm_entries(path=nil, identifier=nil)
97 scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
97 scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
98 end
98 end
99 protected :scm_entries
99 protected :scm_entries
100
100
101 # With SCMs that have a sequential commit numbering,
101 # With SCMs that have a sequential commit numbering,
102 # such as Subversion and Mercurial,
102 # such as Subversion and Mercurial,
103 # Redmine is able to be clever and only fetch changesets
103 # Redmine is able to be clever and only fetch changesets
104 # going forward from the most recent one it knows about.
104 # going forward from the most recent one it knows about.
105 #
105 #
106 # However, Git does not have a sequential commit numbering.
106 # However, Git does not have a sequential commit numbering.
107 #
107 #
108 # In order to fetch only new adding revisions,
108 # In order to fetch only new adding revisions,
109 # Redmine needs to save "heads".
109 # Redmine needs to save "heads".
110 #
110 #
111 # In Git and Mercurial, revisions are not in date order.
111 # In Git and Mercurial, revisions are not in date order.
112 # Redmine Mercurial fixed issues.
112 # Redmine Mercurial fixed issues.
113 # * Redmine Takes Too Long On Large Mercurial Repository
113 # * Redmine Takes Too Long On Large Mercurial Repository
114 # http://www.redmine.org/issues/3449
114 # http://www.redmine.org/issues/3449
115 # * Sorting for changesets might go wrong on Mercurial repos
115 # * Sorting for changesets might go wrong on Mercurial repos
116 # http://www.redmine.org/issues/3567
116 # http://www.redmine.org/issues/3567
117 #
117 #
118 # Database revision column is text, so Redmine can not sort by revision.
118 # Database revision column is text, so Redmine can not sort by revision.
119 # Mercurial has revision number, and revision number guarantees revision order.
119 # Mercurial has revision number, and revision number guarantees revision order.
120 # Redmine Mercurial model stored revisions ordered by database id to database.
120 # Redmine Mercurial model stored revisions ordered by database id to database.
121 # So, Redmine Mercurial model can use correct ordering revisions.
121 # So, Redmine Mercurial model can use correct ordering revisions.
122 #
122 #
123 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
123 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
124 # to get limited revisions from old to new.
124 # to get limited revisions from old to new.
125 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
125 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
126 #
126 #
127 # The repository can still be fully reloaded by calling #clear_changesets
127 # The repository can still be fully reloaded by calling #clear_changesets
128 # before fetching changesets (eg. for offline resync)
128 # before fetching changesets (eg. for offline resync)
129 def fetch_changesets
129 def fetch_changesets
130 scm_brs = branches
130 scm_brs = branches
131 return if scm_brs.nil? || scm_brs.empty?
131 return if scm_brs.nil? || scm_brs.empty?
132
132
133 h1 = extra_info || {}
133 h1 = extra_info || {}
134 h = h1.dup
134 h = h1.dup
135 repo_heads = scm_brs.map{ |br| br.scmid }
135 repo_heads = scm_brs.map{ |br| br.scmid }
136 h["heads"] ||= []
136 h["heads"] ||= []
137 prev_db_heads = h["heads"].dup
137 prev_db_heads = h["heads"].dup
138 if prev_db_heads.empty?
138 if prev_db_heads.empty?
139 prev_db_heads += heads_from_branches_hash
139 prev_db_heads += heads_from_branches_hash
140 end
140 end
141 return if prev_db_heads.sort == repo_heads.sort
141 return if prev_db_heads.sort == repo_heads.sort
142
142
143 h["db_consistent"] ||= {}
143 h["db_consistent"] ||= {}
144 if changesets.count == 0
144 if changesets.count == 0
145 h["db_consistent"]["ordering"] = 1
145 h["db_consistent"]["ordering"] = 1
146 merge_extra_info(h)
146 merge_extra_info(h)
147 self.save
147 self.save
148 elsif ! h["db_consistent"].has_key?("ordering")
148 elsif ! h["db_consistent"].has_key?("ordering")
149 h["db_consistent"]["ordering"] = 0
149 h["db_consistent"]["ordering"] = 0
150 merge_extra_info(h)
150 merge_extra_info(h)
151 self.save
151 self.save
152 end
152 end
153 save_revisions(prev_db_heads, repo_heads)
153 save_revisions(prev_db_heads, repo_heads)
154 end
154 end
155
155
156 def save_revisions(prev_db_heads, repo_heads)
156 def save_revisions(prev_db_heads, repo_heads)
157 h = {}
157 h = {}
158 opts = {}
158 opts = {}
159 opts[:reverse] = true
159 opts[:reverse] = true
160 opts[:excludes] = prev_db_heads
160 opts[:excludes] = prev_db_heads
161 opts[:includes] = repo_heads
161 opts[:includes] = repo_heads
162
162
163 revisions = scm.revisions('', nil, nil, opts)
163 revisions = scm.revisions('', nil, nil, opts)
164 return if revisions.blank?
164 return if revisions.blank?
165
165
166 # Make the search for existing revisions in the database in a more sufficient manner
166 # Make the search for existing revisions in the database in a more sufficient manner
167 #
167 #
168 # Git branch is the reference to the specific revision.
168 # Git branch is the reference to the specific revision.
169 # Git can *delete* remote branch and *re-push* branch.
169 # Git can *delete* remote branch and *re-push* branch.
170 #
170 #
171 # $ git push remote :branch
171 # $ git push remote :branch
172 # $ git push remote branch
172 # $ git push remote branch
173 #
173 #
174 # After deleting branch, revisions remain in repository until "git gc".
174 # After deleting branch, revisions remain in repository until "git gc".
175 # On git 1.7.2.3, default pruning date is 2 weeks.
175 # On git 1.7.2.3, default pruning date is 2 weeks.
176 # So, "git log --not deleted_branch_head_revision" return code is 0.
176 # So, "git log --not deleted_branch_head_revision" return code is 0.
177 #
177 #
178 # After re-pushing branch, "git log" returns revisions which are saved in database.
178 # After re-pushing branch, "git log" returns revisions which are saved in database.
179 # So, Redmine needs to scan revisions and database every time.
179 # So, Redmine needs to scan revisions and database every time.
180 #
180 #
181 # This is replacing the one-after-one queries.
181 # This is replacing the one-after-one queries.
182 # Find all revisions, that are in the database, and then remove them
182 # Find all revisions, that are in the database, and then remove them
183 # from the revision array.
183 # from the revision array.
184 # Then later we won't need any conditions for db existence.
184 # Then later we won't need any conditions for db existence.
185 # Query for several revisions at once, and remove them
185 # Query for several revisions at once, and remove them
186 # from the revisions array, if they are there.
186 # from the revisions array, if they are there.
187 # Do this in chunks, to avoid eventual memory problems
187 # Do this in chunks, to avoid eventual memory problems
188 # (in case of tens of thousands of commits).
188 # (in case of tens of thousands of commits).
189 # If there are no revisions (because the original code's algorithm filtered them),
189 # If there are no revisions (because the original code's algorithm filtered them),
190 # then this part will be stepped over.
190 # then this part will be stepped over.
191 # We make queries, just if there is any revision.
191 # We make queries, just if there is any revision.
192 limit = 100
192 limit = 100
193 offset = 0
193 offset = 0
194 revisions_copy = revisions.clone # revisions will change
194 revisions_copy = revisions.clone # revisions will change
195 while offset < revisions_copy.size
195 while offset < revisions_copy.size
196 scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
196 scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
197 recent_changesets_slice = changesets.where(:scmid => scmids)
197 recent_changesets_slice = changesets.where(:scmid => scmids)
198 # Subtract revisions that redmine already knows about
198 # Subtract revisions that redmine already knows about
199 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
199 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
200 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
200 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
201 offset += limit
201 offset += limit
202 end
202 end
203 revisions.each do |rev|
203 revisions.each do |rev|
204 transaction do
204 transaction do
205 # There is no search in the db for this revision, because above we ensured,
205 # There is no search in the db for this revision, because above we ensured,
206 # that it's not in the db.
206 # that it's not in the db.
207 save_revision(rev)
207 save_revision(rev)
208 end
208 end
209 end
209 end
210 h["heads"] = repo_heads.dup
210 h["heads"] = repo_heads.dup
211 merge_extra_info(h)
211 merge_extra_info(h)
212 self.save
212 save(:validate => false)
213 end
213 end
214 private :save_revisions
214 private :save_revisions
215
215
216 def save_revision(rev)
216 def save_revision(rev)
217 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
217 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
218 changeset = Changeset.create(
218 changeset = Changeset.create(
219 :repository => self,
219 :repository => self,
220 :revision => rev.identifier,
220 :revision => rev.identifier,
221 :scmid => rev.scmid,
221 :scmid => rev.scmid,
222 :committer => rev.author,
222 :committer => rev.author,
223 :committed_on => rev.time,
223 :committed_on => rev.time,
224 :comments => rev.message,
224 :comments => rev.message,
225 :parents => parents
225 :parents => parents
226 )
226 )
227 unless changeset.new_record?
227 unless changeset.new_record?
228 rev.paths.each { |change| changeset.create_change(change) }
228 rev.paths.each { |change| changeset.create_change(change) }
229 end
229 end
230 changeset
230 changeset
231 end
231 end
232 private :save_revision
232 private :save_revision
233
233
234 def heads_from_branches_hash
234 def heads_from_branches_hash
235 h1 = extra_info || {}
235 h1 = extra_info || {}
236 h = h1.dup
236 h = h1.dup
237 h["branches"] ||= {}
237 h["branches"] ||= {}
238 h['branches'].map{|br, hs| hs['last_scmid']}
238 h['branches'].map{|br, hs| hs['last_scmid']}
239 end
239 end
240
240
241 def latest_changesets(path,rev,limit=10)
241 def latest_changesets(path,rev,limit=10)
242 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
242 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
243 return [] if revisions.nil? || revisions.empty?
243 return [] if revisions.nil? || revisions.empty?
244 changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
244 changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
245 end
245 end
246
246
247 def clear_extra_info_of_changesets
247 def clear_extra_info_of_changesets
248 return if extra_info.nil?
248 return if extra_info.nil?
249 v = extra_info["extra_report_last_commit"]
249 v = extra_info["extra_report_last_commit"]
250 write_attribute(:extra_info, nil)
250 write_attribute(:extra_info, nil)
251 h = {}
251 h = {}
252 h["extra_report_last_commit"] = v
252 h["extra_report_last_commit"] = v
253 merge_extra_info(h)
253 merge_extra_info(h)
254 self.save
254 save(:validate => false)
255 end
255 end
256 private :clear_extra_info_of_changesets
256 private :clear_extra_info_of_changesets
257
257
258 def clear_changesets
258 def clear_changesets
259 super
259 super
260 clear_extra_info_of_changesets
260 clear_extra_info_of_changesets
261 end
261 end
262 private :clear_changesets
262 private :clear_changesets
263 end
263 end
General Comments 0
You need to be logged in to leave comments. Login now