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