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)