##// END OF EJS Templates
merge: use repo.ui directly instead local variable...
Martin Geisler -
r15041:3afe5edd default
parent child Browse files
Show More
@@ -1,566 +1,565
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import scmutil, util, filemerge, copies, subrepo, encoding
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd, flags):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), flags]
53 53 self._dirty = True
54 54 def __contains__(self, dfile):
55 55 return dfile in self._state
56 56 def __getitem__(self, dfile):
57 57 return self._state[dfile][0]
58 58 def __iter__(self):
59 59 l = self._state.keys()
60 60 l.sort()
61 61 for f in l:
62 62 yield f
63 63 def mark(self, dfile, state):
64 64 self._state[dfile][0] = state
65 65 self._dirty = True
66 66 def resolve(self, dfile, wctx, octx):
67 67 if self[dfile] == 'r':
68 68 return 0
69 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 70 f = self._repo.opener("merge/" + hash)
71 71 self._repo.wwrite(dfile, f.read(), flags)
72 72 f.close()
73 73 fcd = wctx[dfile]
74 74 fco = octx[ofile]
75 75 fca = self._repo.filectx(afile, fileid=anode)
76 76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 77 if r is None:
78 78 # no real conflict
79 79 del self._state[dfile]
80 80 elif not r:
81 81 self.mark(dfile, 'r')
82 82 return r
83 83
84 84 def _checkunknown(wctx, mctx):
85 85 "check for collisions between unknown files and files in mctx"
86 86 for f in wctx.unknown():
87 87 if f in mctx and mctx[f].cmp(wctx[f]):
88 88 raise util.Abort(_("untracked file in working directory differs"
89 89 " from file in requested revision: '%s'") % f)
90 90
91 91 def _checkcollision(mctx):
92 92 "check for case folding collisions in the destination context"
93 93 folded = {}
94 94 for fn in mctx:
95 95 fold = encoding.lower(fn)
96 96 if fold in folded:
97 97 raise util.Abort(_("case-folding collision between %s and %s")
98 98 % (fn, folded[fold]))
99 99 folded[fold] = fn
100 100
101 101 def _forgetremoved(wctx, mctx, branchmerge):
102 102 """
103 103 Forget removed files
104 104
105 105 If we're jumping between revisions (as opposed to merging), and if
106 106 neither the working directory nor the target rev has the file,
107 107 then we need to remove it from the dirstate, to prevent the
108 108 dirstate from listing the file when it is no longer in the
109 109 manifest.
110 110
111 111 If we're merging, and the other revision has removed a file
112 112 that is not present in the working directory, we need to mark it
113 113 as removed.
114 114 """
115 115
116 116 action = []
117 117 state = branchmerge and 'r' or 'f'
118 118 for f in wctx.deleted():
119 119 if f not in mctx:
120 120 action.append((f, state))
121 121
122 122 if not branchmerge:
123 123 for f in wctx.removed():
124 124 if f not in mctx:
125 125 action.append((f, "f"))
126 126
127 127 return action
128 128
129 129 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
130 130 """
131 131 Merge p1 and p2 with ancestor pa and generate merge action list
132 132
133 133 overwrite = whether we clobber working files
134 134 partial = function to filter file lists
135 135 """
136 136
137 137 def fmerge(f, f2, fa):
138 138 """merge flags"""
139 139 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
140 140 if m == n: # flags agree
141 141 return m # unchanged
142 142 if m and n and not a: # flags set, don't agree, differ from parent
143 143 r = repo.ui.promptchoice(
144 144 _(" conflicting flags for %s\n"
145 145 "(n)one, e(x)ec or sym(l)ink?") % f,
146 146 (_("&None"), _("E&xec"), _("Sym&link")), 0)
147 147 if r == 1:
148 148 return "x" # Exec
149 149 if r == 2:
150 150 return "l" # Symlink
151 151 return ""
152 152 if m and m != a: # changed from a to m
153 153 return m
154 154 if n and n != a: # changed from a to n
155 155 return n
156 156 return '' # flag was cleared
157 157
158 158 def act(msg, m, f, *args):
159 159 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
160 160 action.append((f, m) + args)
161 161
162 162 action, copy = [], {}
163 163
164 164 if overwrite:
165 165 pa = p1
166 166 elif pa == p2: # backwards
167 167 pa = p1.p1()
168 168 elif pa and repo.ui.configbool("merge", "followcopies", True):
169 169 dirs = repo.ui.configbool("merge", "followdirs", True)
170 170 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
171 171 for of, fl in diverge.iteritems():
172 172 act("divergent renames", "dr", of, fl)
173 173
174 174 repo.ui.note(_("resolving manifests\n"))
175 175 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
176 176 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
177 177
178 178 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
179 179 copied = set(copy.values())
180 180
181 181 if '.hgsubstate' in m1:
182 182 # check whether sub state is modified
183 183 for s in p1.substate:
184 184 if p1.sub(s).dirty():
185 185 m1['.hgsubstate'] += "+"
186 186 break
187 187
188 188 # Compare manifests
189 189 for f, n in m1.iteritems():
190 190 if partial and not partial(f):
191 191 continue
192 192 if f in m2:
193 193 rflags = fmerge(f, f, f)
194 194 a = ma.get(f, nullid)
195 195 if n == m2[f] or m2[f] == a: # same or local newer
196 196 # is file locally modified or flags need changing?
197 197 # dirstate flags may need to be made current
198 198 if m1.flags(f) != rflags or n[20:]:
199 199 act("update permissions", "e", f, rflags)
200 200 elif n == a: # remote newer
201 201 act("remote is newer", "g", f, rflags)
202 202 else: # both changed
203 203 act("versions differ", "m", f, f, f, rflags, False)
204 204 elif f in copied: # files we'll deal with on m2 side
205 205 pass
206 206 elif f in copy:
207 207 f2 = copy[f]
208 208 if f2 not in m2: # directory rename
209 209 act("remote renamed directory to " + f2, "d",
210 210 f, None, f2, m1.flags(f))
211 211 else: # case 2 A,B/B/B or case 4,21 A/B/B
212 212 act("local copied/moved to " + f2, "m",
213 213 f, f2, f, fmerge(f, f2, f2), False)
214 214 elif f in ma: # clean, a different, no remote
215 215 if n != ma[f]:
216 216 if repo.ui.promptchoice(
217 217 _(" local changed %s which remote deleted\n"
218 218 "use (c)hanged version or (d)elete?") % f,
219 219 (_("&Changed"), _("&Delete")), 0):
220 220 act("prompt delete", "r", f)
221 221 else:
222 222 act("prompt keep", "a", f)
223 223 elif n[20:] == "a": # added, no remote
224 224 act("remote deleted", "f", f)
225 225 elif n[20:] != "u":
226 226 act("other deleted", "r", f)
227 227
228 228 for f, n in m2.iteritems():
229 229 if partial and not partial(f):
230 230 continue
231 231 if f in m1 or f in copied: # files already visited
232 232 continue
233 233 if f in copy:
234 234 f2 = copy[f]
235 235 if f2 not in m1: # directory rename
236 236 act("local renamed directory to " + f2, "d",
237 237 None, f, f2, m2.flags(f))
238 238 elif f2 in m2: # rename case 1, A/A,B/A
239 239 act("remote copied to " + f, "m",
240 240 f2, f, f, fmerge(f2, f, f2), False)
241 241 else: # case 3,20 A/B/A
242 242 act("remote moved to " + f, "m",
243 243 f2, f, f, fmerge(f2, f, f2), True)
244 244 elif f not in ma:
245 245 act("remote created", "g", f, m2.flags(f))
246 246 elif n != ma[f]:
247 247 if repo.ui.promptchoice(
248 248 _("remote changed %s which local deleted\n"
249 249 "use (c)hanged version or leave (d)eleted?") % f,
250 250 (_("&Changed"), _("&Deleted")), 0) == 0:
251 251 act("prompt recreating", "g", f, m2.flags(f))
252 252
253 253 return action
254 254
255 255 def actionkey(a):
256 256 return a[1] == 'r' and -1 or 0, a
257 257
258 258 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
259 259 """apply the merge action list to the working directory
260 260
261 261 wctx is the working copy context
262 262 mctx is the context to be merged into the working copy
263 263 actx is the context of the common ancestor
264 264
265 265 Return a tuple of counts (updated, merged, removed, unresolved) that
266 266 describes how many files were affected by the update.
267 267 """
268 268
269 269 updated, merged, removed, unresolved = 0, 0, 0, 0
270 270 ms = mergestate(repo)
271 271 ms.reset(wctx.p1().node())
272 272 moves = []
273 273 action.sort(key=actionkey)
274 274
275 275 # prescan for merges
276 u = repo.ui
277 276 for a in action:
278 277 f, m = a[:2]
279 278 if m == 'm': # merge
280 279 f2, fd, flags, move = a[2:]
281 280 if f == '.hgsubstate': # merged internally
282 281 continue
283 282 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
284 283 fcl = wctx[f]
285 284 fco = mctx[f2]
286 285 if mctx == actx: # backwards, use working dir parent as ancestor
287 286 if fcl.parents():
288 287 fca = fcl.p1()
289 288 else:
290 289 fca = repo.filectx(f, fileid=nullrev)
291 290 else:
292 291 fca = fcl.ancestor(fco, actx)
293 292 if not fca:
294 293 fca = repo.filectx(f, fileid=nullrev)
295 294 ms.add(fcl, fco, fca, fd, flags)
296 295 if f != fd and move:
297 296 moves.append(f)
298 297
299 298 audit = scmutil.pathauditor(repo.root)
300 299
301 300 # remove renamed files after safely stored
302 301 for f in moves:
303 302 if os.path.lexists(repo.wjoin(f)):
304 303 repo.ui.debug("removing %s\n" % f)
305 304 audit(f)
306 305 os.unlink(repo.wjoin(f))
307 306
308 307 numupdates = len(action)
309 308 for i, a in enumerate(action):
310 309 f, m = a[:2]
311 u.progress(_('updating'), i + 1, item=f, total=numupdates,
310 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
312 311 unit=_('files'))
313 312 if f and f[0] == "/":
314 313 continue
315 314 if m == "r": # remove
316 315 repo.ui.note(_("removing %s\n") % f)
317 316 audit(f)
318 317 if f == '.hgsubstate': # subrepo states need updating
319 318 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
320 319 try:
321 320 util.unlinkpath(repo.wjoin(f))
322 321 except OSError, inst:
323 322 if inst.errno != errno.ENOENT:
324 323 repo.ui.warn(_("update failed to remove %s: %s!\n") %
325 324 (f, inst.strerror))
326 325 removed += 1
327 326 elif m == "m": # merge
328 327 if f == '.hgsubstate': # subrepo states need updating
329 328 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
330 329 continue
331 330 f2, fd, flags, move = a[2:]
332 331 repo.wopener.audit(fd)
333 332 r = ms.resolve(fd, wctx, mctx)
334 333 if r is not None and r > 0:
335 334 unresolved += 1
336 335 else:
337 336 if r is None:
338 337 updated += 1
339 338 else:
340 339 merged += 1
341 340 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
342 341 if (move and repo.dirstate.normalize(fd) != f
343 342 and os.path.lexists(repo.wjoin(f))):
344 343 repo.ui.debug("removing %s\n" % f)
345 344 audit(f)
346 345 os.unlink(repo.wjoin(f))
347 346 elif m == "g": # get
348 347 flags = a[2]
349 348 repo.ui.note(_("getting %s\n") % f)
350 349 t = mctx.filectx(f).data()
351 350 repo.wwrite(f, t, flags)
352 351 t = None
353 352 updated += 1
354 353 if f == '.hgsubstate': # subrepo states need updating
355 354 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
356 355 elif m == "d": # directory rename
357 356 f2, fd, flags = a[2:]
358 357 if f:
359 358 repo.ui.note(_("moving %s to %s\n") % (f, fd))
360 359 audit(f)
361 360 t = wctx.filectx(f).data()
362 361 repo.wwrite(fd, t, flags)
363 362 util.unlinkpath(repo.wjoin(f))
364 363 if f2:
365 364 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
366 365 t = mctx.filectx(f2).data()
367 366 repo.wwrite(fd, t, flags)
368 367 updated += 1
369 368 elif m == "dr": # divergent renames
370 369 fl = a[2]
371 370 repo.ui.warn(_("note: possible conflict - %s was renamed "
372 371 "multiple times to:\n") % f)
373 372 for nf in fl:
374 373 repo.ui.warn(" %s\n" % nf)
375 374 elif m == "e": # exec
376 375 flags = a[2]
377 376 repo.wopener.audit(f)
378 377 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
379 378 ms.commit()
380 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
379 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
381 380
382 381 return updated, merged, removed, unresolved
383 382
384 383 def recordupdates(repo, action, branchmerge):
385 384 "record merge actions to the dirstate"
386 385
387 386 for a in action:
388 387 f, m = a[:2]
389 388 if m == "r": # remove
390 389 if branchmerge:
391 390 repo.dirstate.remove(f)
392 391 else:
393 392 repo.dirstate.drop(f)
394 393 elif m == "a": # re-add
395 394 if not branchmerge:
396 395 repo.dirstate.add(f)
397 396 elif m == "f": # forget
398 397 repo.dirstate.drop(f)
399 398 elif m == "e": # exec change
400 399 repo.dirstate.normallookup(f)
401 400 elif m == "g": # get
402 401 if branchmerge:
403 402 repo.dirstate.otherparent(f)
404 403 else:
405 404 repo.dirstate.normal(f)
406 405 elif m == "m": # merge
407 406 f2, fd, flag, move = a[2:]
408 407 if branchmerge:
409 408 # We've done a branch merge, mark this file as merged
410 409 # so that we properly record the merger later
411 410 repo.dirstate.merge(fd)
412 411 if f != f2: # copy/rename
413 412 if move:
414 413 repo.dirstate.remove(f)
415 414 if f != fd:
416 415 repo.dirstate.copy(f, fd)
417 416 else:
418 417 repo.dirstate.copy(f2, fd)
419 418 else:
420 419 # We've update-merged a locally modified file, so
421 420 # we set the dirstate to emulate a normal checkout
422 421 # of that file some time in the past. Thus our
423 422 # merge will appear as a normal local file
424 423 # modification.
425 424 if f2 == fd: # file not locally copied/moved
426 425 repo.dirstate.normallookup(fd)
427 426 if move:
428 427 repo.dirstate.drop(f)
429 428 elif m == "d": # directory rename
430 429 f2, fd, flag = a[2:]
431 430 if not f2 and f not in repo.dirstate:
432 431 # untracked file moved
433 432 continue
434 433 if branchmerge:
435 434 repo.dirstate.add(fd)
436 435 if f:
437 436 repo.dirstate.remove(f)
438 437 repo.dirstate.copy(f, fd)
439 438 if f2:
440 439 repo.dirstate.copy(f2, fd)
441 440 else:
442 441 repo.dirstate.normal(fd)
443 442 if f:
444 443 repo.dirstate.drop(f)
445 444
446 445 def update(repo, node, branchmerge, force, partial, ancestor=None):
447 446 """
448 447 Perform a merge between the working directory and the given node
449 448
450 449 node = the node to update to, or None if unspecified
451 450 branchmerge = whether to merge between branches
452 451 force = whether to force branch merging or file overwriting
453 452 partial = a function to filter file lists (dirstate not updated)
454 453
455 454 The table below shows all the behaviors of the update command
456 455 given the -c and -C or no options, whether the working directory
457 456 is dirty, whether a revision is specified, and the relationship of
458 457 the parent rev to the target rev (linear, on the same named
459 458 branch, or on another named branch).
460 459
461 460 This logic is tested by test-update-branches.t.
462 461
463 462 -c -C dirty rev | linear same cross
464 463 n n n n | ok (1) x
465 464 n n n y | ok ok ok
466 465 n n y * | merge (2) (2)
467 466 n y * * | --- discard ---
468 467 y n y * | --- (3) ---
469 468 y n n * | --- ok ---
470 469 y y * * | --- (4) ---
471 470
472 471 x = can't happen
473 472 * = don't-care
474 473 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
475 474 2 = abort: crosses branches (use 'hg merge' to merge or
476 475 use 'hg update -C' to discard changes)
477 476 3 = abort: uncommitted local changes
478 477 4 = incompatible options (checked in commands.py)
479 478
480 479 Return the same tuple as applyupdates().
481 480 """
482 481
483 482 onode = node
484 483 wlock = repo.wlock()
485 484 try:
486 485 wc = repo[None]
487 486 if node is None:
488 487 # tip of current branch
489 488 try:
490 489 node = repo.branchtags()[wc.branch()]
491 490 except KeyError:
492 491 if wc.branch() == "default": # no default branch!
493 492 node = repo.lookup("tip") # update to tip
494 493 else:
495 494 raise util.Abort(_("branch %s not found") % wc.branch())
496 495 overwrite = force and not branchmerge
497 496 pl = wc.parents()
498 497 p1, p2 = pl[0], repo[node]
499 498 if ancestor:
500 499 pa = repo[ancestor]
501 500 else:
502 501 pa = p1.ancestor(p2)
503 502
504 503 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
505 504
506 505 ### check phase
507 506 if not overwrite and len(pl) > 1:
508 507 raise util.Abort(_("outstanding uncommitted merges"))
509 508 if branchmerge:
510 509 if pa == p2:
511 510 raise util.Abort(_("merging with a working directory ancestor"
512 511 " has no effect"))
513 512 elif pa == p1:
514 513 if p1.branch() == p2.branch():
515 514 raise util.Abort(_("nothing to merge (use 'hg update'"
516 515 " or check 'hg heads')"))
517 516 if not force and (wc.files() or wc.deleted()):
518 517 raise util.Abort(_("outstanding uncommitted changes "
519 518 "(use 'hg status' to list changes)"))
520 519 for s in wc.substate:
521 520 if wc.sub(s).dirty():
522 521 raise util.Abort(_("outstanding uncommitted changes in "
523 522 "subrepository '%s'") % s)
524 523
525 524 elif not overwrite:
526 525 if pa == p1 or pa == p2: # linear
527 526 pass # all good
528 527 elif wc.dirty(missing=True):
529 528 raise util.Abort(_("crosses branches (merge branches or use"
530 529 " --clean to discard changes)"))
531 530 elif onode is None:
532 531 raise util.Abort(_("crosses branches (merge branches or update"
533 532 " --check to force update)"))
534 533 else:
535 534 # Allow jumping branches if clean and specific rev given
536 535 overwrite = True
537 536
538 537 ### calculate phase
539 538 action = []
540 539 wc.status(unknown=True) # prime cache
541 540 if not force:
542 541 _checkunknown(wc, p2)
543 542 if not util.checkcase(repo.path):
544 543 _checkcollision(p2)
545 544 action += _forgetremoved(wc, p2, branchmerge)
546 545 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
547 546
548 547 ### apply phase
549 548 if not branchmerge: # just jump to the new rev
550 549 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
551 550 if not partial:
552 551 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
553 552
554 553 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
555 554
556 555 if not partial:
557 556 repo.dirstate.setparents(fp1, fp2)
558 557 recordupdates(repo, action, branchmerge)
559 558 if not branchmerge:
560 559 repo.dirstate.setbranch(p2.branch())
561 560 finally:
562 561 wlock.release()
563 562
564 563 if not partial:
565 564 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
566 565 return stats
General Comments 0
You need to be logged in to leave comments. Login now