Mercurial > public > mercurial-scm > hg
comparison mercurial/merge.py @ 18822:381c0ef72a56
manifestmerge: use dicthelpers.diff and join
This patch improves manifestmerge performance significantly.
In a repository with 170,000 files, the following results were observed on a
clean working directory. Revision '.' adds one file.
hg perfmergecalculate -r .
- before: 0.41 seconds
- after: 0.13 seconds
hg perfmergecalculate -r .^
- before: 0.53 seconds
- after: 0.24 seconds
Comparing against '.' is much faster than comparing against '.^' because with
'.', the wctx and p2 manifest strings have the same identity, so comparisons
are simply pointer equality. With '.^', the strings have different identities
so we need to perform memcmps.
Any operation that uses manifestmerge benefits.
- hg update . goes from 2.04 seconds to 1.75
- hg update .^ goes from 2.52 seconds to 2.25
- hg rebase -r . -d .~6 (involves 4 merges) goes from 11.8 seconds to 10.8
author | Siddharth Agarwal <sid0@fb.com> |
---|---|
date | Mon, 25 Mar 2013 17:41:06 -0700 |
parents | a0bff3d4f67b |
children | b2a36e9b9ccc |
comparison
equal
deleted
inserted
replaced
18821:40b4b1f9b7a0 | 18822:381c0ef72a56 |
---|---|
5 # This software may be used and distributed according to the terms of the | 5 # This software may be used and distributed according to the terms of the |
6 # GNU General Public License version 2 or any later version. | 6 # GNU General Public License version 2 or any later version. |
7 | 7 |
8 from node import nullid, nullrev, hex, bin | 8 from node import nullid, nullrev, hex, bin |
9 from i18n import _ | 9 from i18n import _ |
10 import error, util, filemerge, copies, subrepo, worker | 10 import error, util, filemerge, copies, subrepo, worker, dicthelpers |
11 import errno, os, shutil | 11 import errno, os, shutil |
12 | 12 |
13 class mergestate(object): | 13 class mergestate(object): |
14 '''track 3-way merge state of individual files''' | 14 '''track 3-way merge state of individual files''' |
15 def __init__(self, repo): | 15 def __init__(self, repo): |
236 m1['.hgsubstate'] += "+" | 236 m1['.hgsubstate'] += "+" |
237 break | 237 break |
238 | 238 |
239 aborts, prompts = [], [] | 239 aborts, prompts = [], [] |
240 # Compare manifests | 240 # Compare manifests |
241 for f, n1 in m1.iteritems(): | 241 fdiff = dicthelpers.diff(m1, m2) |
242 flagsdiff = m1.flagsdiff(m2) | |
243 diff12 = dicthelpers.join(fdiff, flagsdiff) | |
244 | |
245 for f, (n12, fl12) in diff12.iteritems(): | |
246 if n12: | |
247 n1, n2 = n12 | |
248 else: # file contents didn't change, but flags did | |
249 n1 = n2 = m1[f] | |
250 if fl12: | |
251 fl1, fl2 = fl12 | |
252 else: # flags didn't change, file contents did | |
253 fl1 = fl2 = m1.flags(f) | |
254 | |
242 if partial and not partial(f): | 255 if partial and not partial(f): |
243 continue | 256 continue |
244 if f in m2: | 257 if n1 and n2: |
245 n2 = m2[f] | 258 fla = ma.flags(f) |
246 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f) | |
247 nol = 'l' not in fl1 + fl2 + fla | 259 nol = 'l' not in fl1 + fl2 + fla |
248 a = ma.get(f, nullid) | 260 a = ma.get(f, nullid) |
249 if n1 == n2 and fl1 == fl2: | 261 if n2 == a and fl2 == fla: |
250 pass # same - keep local | |
251 elif n2 == a and fl2 == fla: | |
252 pass # remote unchanged - keep local | 262 pass # remote unchanged - keep local |
253 elif n1 == a and fl1 == fla: # local unchanged - use remote | 263 elif n1 == a and fl1 == fla: # local unchanged - use remote |
254 if n1 == n2: # optimization: keep local content | 264 if n1 == n2: # optimization: keep local content |
255 actions.append((f, "e", (fl2,), "update permissions")) | 265 actions.append((f, "e", (fl2,), "update permissions")) |
256 else: | 266 else: |
261 actions.append((f, "g", (fl1,), "remote is newer")) | 271 actions.append((f, "g", (fl1,), "remote is newer")) |
262 else: # both changed something | 272 else: # both changed something |
263 actions.append((f, "m", (f, f, False), "versions differ")) | 273 actions.append((f, "m", (f, f, False), "versions differ")) |
264 elif f in copied: # files we'll deal with on m2 side | 274 elif f in copied: # files we'll deal with on m2 side |
265 pass | 275 pass |
266 elif f in movewithdir: # directory rename | 276 elif n1 and f in movewithdir: # directory rename |
267 f2 = movewithdir[f] | 277 f2 = movewithdir[f] |
268 actions.append((f, "d", (None, f2, m1.flags(f)), | 278 actions.append((f, "d", (None, f2, m1.flags(f)), |
269 "remote renamed directory to " + f2)) | 279 "remote renamed directory to " + f2)) |
270 elif f in copy: | 280 elif n1 and f in copy: |
271 f2 = copy[f] | 281 f2 = copy[f] |
272 actions.append((f, "m", (f2, f, False), | 282 actions.append((f, "m", (f2, f, False), |
273 "local copied/moved to " + f2)) | 283 "local copied/moved to " + f2)) |
274 elif f in ma: # clean, a different, no remote | 284 elif n1 and f in ma: # clean, a different, no remote |
275 if n1 != ma[f]: | 285 if n1 != ma[f]: |
276 prompts.append((f, "cd")) # prompt changed/deleted | 286 prompts.append((f, "cd")) # prompt changed/deleted |
277 elif n1[20:] == "a": # added, no remote | 287 elif n1[20:] == "a": # added, no remote |
278 actions.append((f, "f", None, "remote deleted")) | 288 actions.append((f, "f", None, "remote deleted")) |
279 else: | 289 else: |
280 actions.append((f, "r", None, "other deleted")) | 290 actions.append((f, "r", None, "other deleted")) |
281 | 291 elif n2 and f in movewithdir: |
282 for f, n2 in m2.iteritems(): | |
283 if partial and not partial(f): | |
284 continue | |
285 if f in m1 or f in copied: # files already visited | |
286 continue | |
287 if f in movewithdir: | |
288 f2 = movewithdir[f] | 292 f2 = movewithdir[f] |
289 actions.append((None, "d", (f, f2, m2.flags(f)), | 293 actions.append((None, "d", (f, f2, m2.flags(f)), |
290 "local renamed directory to " + f2)) | 294 "local renamed directory to " + f2)) |
291 elif f in copy: | 295 elif n2 and f in copy: |
292 f2 = copy[f] | 296 f2 = copy[f] |
293 if f2 in m2: | 297 if f2 in m2: |
294 actions.append((f2, "m", (f, f, False), | 298 actions.append((f2, "m", (f, f, False), |
295 "remote copied to " + f)) | 299 "remote copied to " + f)) |
296 else: | 300 else: |
297 actions.append((f2, "m", (f, f, True), | 301 actions.append((f2, "m", (f, f, True), |
298 "remote moved to " + f)) | 302 "remote moved to " + f)) |
299 elif f not in ma: | 303 elif n2 and f not in ma: |
300 # local unknown, remote created: the logic is described by the | 304 # local unknown, remote created: the logic is described by the |
301 # following table: | 305 # following table: |
302 # | 306 # |
303 # force branchmerge different | action | 307 # force branchmerge different | action |
304 # n * n | get | 308 # n * n | get |
318 "remote differs from untracked local")) | 322 "remote differs from untracked local")) |
319 elif not force and different: | 323 elif not force and different: |
320 aborts.append((f, "ud")) | 324 aborts.append((f, "ud")) |
321 else: | 325 else: |
322 actions.append((f, "g", (m2.flags(f),), "remote created")) | 326 actions.append((f, "g", (m2.flags(f),), "remote created")) |
323 elif n2 != ma[f]: | 327 elif n2 and n2 != ma[f]: |
324 prompts.append((f, "dc")) # prompt deleted/changed | 328 prompts.append((f, "dc")) # prompt deleted/changed |
325 | 329 |
326 for f, m in sorted(aborts): | 330 for f, m in sorted(aborts): |
327 if m == "ud": | 331 if m == "ud": |
328 repo.ui.warn(_("%s: untracked file differs\n") % f) | 332 repo.ui.warn(_("%s: untracked file differs\n") % f) |