79 |
79 |
scm.tags
|
80 |
80 |
end
|
81 |
81 |
|
|
82 |
def heads
|
|
83 |
h = {}
|
|
84 |
scm.heads.each do |revision, branch|
|
|
85 |
h[branch] = { 'last_scmid' => revision }
|
|
86 |
end
|
|
87 |
h
|
|
88 |
end
|
|
89 |
|
82 |
90 |
def default_branch
|
83 |
91 |
scm.default_branch
|
84 |
92 |
rescue Exception => e
|
... | ... | |
99 |
107 |
options = {:report_last_commit => extra_report_last_commit})
|
100 |
108 |
end
|
101 |
109 |
|
102 |
|
# With SCMs that have a sequential commit numbering,
|
103 |
|
# such as Subversion and Mercurial,
|
104 |
|
# Redmine is able to be clever and only fetch changesets
|
105 |
|
# going forward from the most recent one it knows about.
|
|
110 |
# With SCMs that have a sequential commit numbering, such as Subversion and
|
|
111 |
# Mercurial, Redmine is able to be clever and only fetch changesets going
|
|
112 |
# forward from the most recent one it knows about.
|
106 |
113 |
#
|
107 |
114 |
# However, Git does not have a sequential commit numbering.
|
108 |
115 |
#
|
109 |
|
# In order to fetch only new adding revisions,
|
110 |
|
# Redmine needs to parse revisions per branch.
|
111 |
|
# Branch "last_scmid" is for this requirement.
|
|
116 |
# In order to fetch only new revisions, Redmine needs to parse revisions
|
|
117 |
# introduced by the new heads of new and existing branches since the last time
|
|
118 |
# the branch heads were processed. Branch "last_scmid" is for this
|
|
119 |
# requirement.
|
112 |
120 |
#
|
113 |
121 |
# In Git and Mercurial, revisions are not in date order.
|
114 |
122 |
# Redmine Mercurial fixed issues.
|
... | ... | |
118 |
126 |
# http://www.redmine.org/issues/3567
|
119 |
127 |
#
|
120 |
128 |
# Database revision column is text, so Redmine can not sort by revision.
|
121 |
|
# Mercurial has revision number, and revision number guarantees revision order.
|
122 |
|
# Redmine Mercurial model stored revisions ordered by database id to database.
|
123 |
|
# So, Redmine Mercurial model can use correct ordering revisions.
|
|
129 |
# Mercurial has revision number, and revision number guarantees revision
|
|
130 |
# order. Redmine Mercurial model stored revisions ordered by database id to
|
|
131 |
# database. So, Redmine Mercurial model can use correct ordering revisions.
|
124 |
132 |
#
|
125 |
|
# Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
|
126 |
|
# to get limited revisions from old to new.
|
127 |
|
# But, Git 1.7.3.4 does not support --reverse with -n or --skip.
|
|
133 |
# Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" to get limited
|
|
134 |
# revisions from old to new. But, Git 1.7.3.4 does not support --reverse with
|
|
135 |
# -n or --skip.
|
128 |
136 |
#
|
129 |
137 |
# The repository can still be fully reloaded by calling #clear_changesets
|
130 |
138 |
# before fetching changesets (eg. for offline resync)
|
131 |
139 |
def fetch_changesets
|
132 |
|
scm_brs = branches
|
133 |
|
return if scm_brs.nil? || scm_brs.empty?
|
134 |
|
h1 = extra_info || {}
|
135 |
|
h = h1.dup
|
136 |
|
h["branches"] ||= {}
|
137 |
|
h["db_consistent"] ||= {}
|
138 |
|
if changesets.count == 0
|
139 |
|
h["db_consistent"]["ordering"] = 1
|
140 |
|
merge_extra_info(h)
|
141 |
|
self.save
|
142 |
|
elsif ! h["db_consistent"].has_key?("ordering")
|
143 |
|
h["db_consistent"]["ordering"] = 0
|
|
140 |
transaction do
|
|
141 |
scm_heads = heads
|
|
142 |
return if scm_heads.nil? || scm_heads.empty?
|
|
143 |
h1 = extra_info || {}
|
|
144 |
h = h1.dup
|
|
145 |
h["branches"] ||= {}
|
|
146 |
h["db_consistent"] ||= {}
|
|
147 |
if changesets.count == 0
|
|
148 |
h["db_consistent"]["ordering"] = 1
|
|
149 |
elsif ! h["db_consistent"].has_key?("ordering")
|
|
150 |
h["db_consistent"]["ordering"] = 0
|
|
151 |
end
|
|
152 |
|
|
153 |
begin
|
|
154 |
save_revisions(h["branches"], scm_heads)
|
|
155 |
rescue Redmine::Scm::Adapters::CommandFailed
|
|
156 |
raise ActiveRecord::Rollback, $!.message
|
|
157 |
end
|
|
158 |
|
|
159 |
h["branches"] = scm_heads
|
144 |
160 |
merge_extra_info(h)
|
145 |
161 |
self.save
|
146 |
162 |
end
|
147 |
|
save_revisions(h, scm_brs)
|
148 |
163 |
end
|
149 |
164 |
|
150 |
|
def save_revisions(h, scm_brs)
|
151 |
|
# Remember what revisions we already processed (in any branches)
|
152 |
|
all_revisions = []
|
153 |
|
scm_brs.each do |br1|
|
154 |
|
br = br1.to_s
|
155 |
|
last_revision = nil
|
156 |
|
from_scmid = nil
|
157 |
|
from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br]
|
158 |
|
h["branches"][br] ||= {}
|
159 |
|
|
160 |
|
revisions = scm.revisions('', from_scmid, br, {:reverse => true})
|
161 |
|
next if revisions.blank?
|
162 |
|
|
163 |
|
# Remember the last commit id here, before we start removing revisions from the array.
|
164 |
|
# We'll do that for optimization, but it also means, that we may lose even all revisions.
|
165 |
|
last_revision = revisions.last
|
166 |
|
|
167 |
|
# remove revisions that we have already processed (possibly in other branches)
|
168 |
|
revisions.reject!{|r| all_revisions.include?(r.scmid)}
|
169 |
|
# add revisions that we are to parse now to 'all processed revisions'
|
170 |
|
# (this equals to a union, because we executed diff above)
|
171 |
|
all_revisions += revisions.map{|r| r.scmid}
|
172 |
|
|
173 |
|
# Make the search for existing revisions in the database in a more sufficient manner
|
174 |
|
# This is replacing the one-after-one queries.
|
175 |
|
# Find all revisions, that are in the database, and then remove them from the revision array.
|
176 |
|
# Then later we won't need any conditions for db existence.
|
177 |
|
# Query for several revisions at once, and remove them from the revisions array, if they are there.
|
178 |
|
# Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
|
179 |
|
# If there are no revisions (because the original code's algoritm filtered them),
|
180 |
|
# then this part will be stepped over.
|
181 |
|
# We make queries, just if there is any revision.
|
182 |
|
limit = 100
|
183 |
|
offset = 0
|
184 |
|
revisions_copy = revisions.clone # revisions will change
|
185 |
|
while offset < revisions_copy.size
|
186 |
|
recent_changesets_slice = changesets.find(
|
187 |
|
:all,
|
188 |
|
:conditions => [
|
189 |
|
'scmid IN (?)',
|
190 |
|
revisions_copy.slice(offset, limit).map{|x| x.scmid}
|
191 |
|
]
|
192 |
|
)
|
193 |
|
# Subtract revisions that redmine already knows about
|
194 |
|
recent_revisions = recent_changesets_slice.map{|c| c.scmid}
|
195 |
|
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
|
196 |
|
offset += limit
|
197 |
|
end
|
198 |
|
|
199 |
|
revisions.each do |rev|
|
200 |
|
transaction do
|
201 |
|
# There is no search in the db for this revision, because above we ensured,
|
202 |
|
# that it's not in the db.
|
203 |
|
db_saved_rev = save_revision(rev)
|
204 |
|
parents = {}
|
205 |
|
parents[db_saved_rev] = rev.parents unless rev.parents.nil?
|
206 |
|
parents.each do |ch, chparents|
|
207 |
|
ch.parents = chparents.collect{|rp| find_changeset_by_name(rp)}.compact
|
208 |
|
end
|
209 |
|
# saving the last scmid was moved from here, because we won't come in here,
|
210 |
|
# if the revision was already added for another branch
|
211 |
|
end
|
212 |
|
end
|
213 |
|
|
214 |
|
# save the data about the last revision for this branch
|
215 |
|
unless last_revision.nil?
|
216 |
|
h["branches"][br]["last_scmid"] = last_revision.scmid
|
217 |
|
merge_extra_info(h)
|
218 |
|
self.save
|
219 |
|
end
|
|
165 |
def save_revisions(old_scm_heads, new_scm_heads)
|
|
166 |
scm.revisions(
|
|
167 |
'', nil, nil,
|
|
168 |
:reverse => true,
|
|
169 |
:includes => heads_from_branches_hash(new_scm_heads),
|
|
170 |
:excludes => heads_from_branches_hash(old_scm_heads)
|
|
171 |
) do |revision|
|
|
172 |
save_revision(revision)
|
220 |
173 |
end
|
221 |
174 |
end
|
222 |
175 |
private :save_revisions
|
223 |
176 |
|
224 |
177 |
def save_revision(rev)
|
|
178 |
parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
|
225 |
179 |
changeset = Changeset.new(
|
226 |
180 |
:repository => self,
|
227 |
181 |
:revision => rev.identifier,
|
228 |
182 |
:scmid => rev.scmid,
|
229 |
183 |
:committer => rev.author,
|
230 |
184 |
:committed_on => rev.time,
|
231 |
|
:comments => rev.message
|
|
185 |
:comments => rev.message,
|
|
186 |
:parents => parents
|
232 |
187 |
)
|
233 |
188 |
if changeset.save
|
234 |
189 |
rev.paths.each do |file|
|
... | ... | |
242 |
197 |
end
|
243 |
198 |
private :save_revision
|
244 |
199 |
|
245 |
|
def heads_from_branches_hash
|
246 |
|
h1 = extra_info || {}
|
247 |
|
h = h1.dup
|
248 |
|
h["branches"] ||= {}
|
249 |
|
h['branches'].map{|br, hs| hs['last_scmid']}
|
|
200 |
def heads_from_branches_hash(branches)
|
|
201 |
branches.map{|br, hs| hs['last_scmid']}
|
250 |
202 |
end
|
251 |
203 |
|
252 |
204 |
def latest_changesets(path,rev,limit=10)
|