| 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)
|