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