##// END OF EJS Templates
merge: require --force when there are deleted files
Alexis S. L. Carvalho -
r6256:69c75d06 default
parent child Browse files
Show More
@@ -1,641 +1,641 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import nullid, nullrev
9 9 from i18n import _
10 10 import errno, util, os, heapq, filemerge
11 11
12 12 def checkunknown(wctx, mctx):
13 13 "check for collisions between unknown files and files in mctx"
14 14 man = mctx.manifest()
15 15 for f in wctx.unknown():
16 16 if f in man:
17 17 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
18 18 raise util.Abort(_("untracked file in working directory differs"
19 19 " from file in requested revision: '%s'")
20 20 % f)
21 21
22 22 def checkcollision(mctx):
23 23 "check for case folding collisions in the destination context"
24 24 folded = {}
25 25 for fn in mctx.manifest():
26 26 fold = fn.lower()
27 27 if fold in folded:
28 28 raise util.Abort(_("case-folding collision between %s and %s")
29 29 % (fn, folded[fold]))
30 30 folded[fold] = fn
31 31
32 32 def forgetremoved(wctx, mctx, branchmerge):
33 33 """
34 34 Forget removed files
35 35
36 36 If we're jumping between revisions (as opposed to merging), and if
37 37 neither the working directory nor the target rev has the file,
38 38 then we need to remove it from the dirstate, to prevent the
39 39 dirstate from listing the file when it is no longer in the
40 40 manifest.
41 41
42 42 If we're merging, and the other revision has removed a file
43 43 that is not present in the working directory, we need to mark it
44 44 as removed.
45 45 """
46 46
47 47 action = []
48 48 man = mctx.manifest()
49 49 state = branchmerge and 'r' or 'f'
50 50 for f in wctx.deleted():
51 51 if f not in man:
52 52 action.append((f, state))
53 53
54 54 if not branchmerge:
55 55 for f in wctx.removed():
56 56 if f not in man:
57 57 action.append((f, "f"))
58 58
59 59 return action
60 60
61 61 def findcopies(repo, m1, m2, ma, limit):
62 62 """
63 63 Find moves and copies between m1 and m2 back to limit linkrev
64 64 """
65 65
66 66 def nonoverlap(d1, d2, d3):
67 67 "Return list of elements in d1 not in d2 or d3"
68 68 l = [d for d in d1 if d not in d3 and d not in d2]
69 69 l.sort()
70 70 return l
71 71
72 72 def dirname(f):
73 73 s = f.rfind("/")
74 74 if s == -1:
75 75 return ""
76 76 return f[:s]
77 77
78 78 def dirs(files):
79 79 d = {}
80 80 for f in files:
81 81 f = dirname(f)
82 82 while f not in d:
83 83 d[f] = True
84 84 f = dirname(f)
85 85 return d
86 86
87 87 wctx = repo.workingctx()
88 88
89 89 def makectx(f, n):
90 90 if len(n) == 20:
91 91 return repo.filectx(f, fileid=n)
92 92 return wctx.filectx(f)
93 93 ctx = util.cachefunc(makectx)
94 94
95 95 def findold(fctx):
96 96 "find files that path was copied from, back to linkrev limit"
97 97 old = {}
98 98 seen = {}
99 99 orig = fctx.path()
100 100 visit = [fctx]
101 101 while visit:
102 102 fc = visit.pop()
103 103 s = str(fc)
104 104 if s in seen:
105 105 continue
106 106 seen[s] = 1
107 107 if fc.path() != orig and fc.path() not in old:
108 108 old[fc.path()] = 1
109 109 if fc.rev() < limit:
110 110 continue
111 111 visit += fc.parents()
112 112
113 113 old = old.keys()
114 114 old.sort()
115 115 return old
116 116
117 117 copy = {}
118 118 fullcopy = {}
119 119 diverge = {}
120 120
121 121 def checkcopies(c, man, aman):
122 122 '''check possible copies for filectx c'''
123 123 for of in findold(c):
124 124 fullcopy[c.path()] = of # remember for dir rename detection
125 125 if of not in man: # original file not in other manifest?
126 126 if of in ma:
127 127 diverge.setdefault(of, []).append(c.path())
128 128 continue
129 129 # if the original file is unchanged on the other branch,
130 130 # no merge needed
131 131 if man[of] == aman.get(of):
132 132 continue
133 133 c2 = ctx(of, man[of])
134 134 ca = c.ancestor(c2)
135 135 if not ca: # unrelated?
136 136 continue
137 137 # named changed on only one side?
138 138 if ca.path() == c.path() or ca.path() == c2.path():
139 139 if c == ca and c2 == ca: # no merge needed, ignore copy
140 140 continue
141 141 copy[c.path()] = of
142 142
143 143 if not repo.ui.configbool("merge", "followcopies", True):
144 144 return {}, {}
145 145
146 146 # avoid silly behavior for update from empty dir
147 147 if not m1 or not m2 or not ma:
148 148 return {}, {}
149 149
150 150 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
151 151
152 152 u1 = nonoverlap(m1, m2, ma)
153 153 u2 = nonoverlap(m2, m1, ma)
154 154
155 155 if u1:
156 156 repo.ui.debug(_(" unmatched files in local:\n %s\n")
157 157 % "\n ".join(u1))
158 158 if u2:
159 159 repo.ui.debug(_(" unmatched files in other:\n %s\n")
160 160 % "\n ".join(u2))
161 161
162 162 for f in u1:
163 163 checkcopies(ctx(f, m1[f]), m2, ma)
164 164
165 165 for f in u2:
166 166 checkcopies(ctx(f, m2[f]), m1, ma)
167 167
168 168 diverge2 = {}
169 169 for of, fl in diverge.items():
170 170 if len(fl) == 1:
171 171 del diverge[of] # not actually divergent
172 172 else:
173 173 diverge2.update(dict.fromkeys(fl)) # reverse map for below
174 174
175 175 if fullcopy:
176 176 repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
177 177 for f in fullcopy:
178 178 note = ""
179 179 if f in copy: note += "*"
180 180 if f in diverge2: note += "!"
181 181 repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note))
182 182
183 183 del diverge2
184 184
185 185 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
186 186 return copy, diverge
187 187
188 188 repo.ui.debug(_(" checking for directory renames\n"))
189 189
190 190 # generate a directory move map
191 191 d1, d2 = dirs(m1), dirs(m2)
192 192 invalid = {}
193 193 dirmove = {}
194 194
195 195 # examine each file copy for a potential directory move, which is
196 196 # when all the files in a directory are moved to a new directory
197 197 for dst, src in fullcopy.items():
198 198 dsrc, ddst = dirname(src), dirname(dst)
199 199 if dsrc in invalid:
200 200 # already seen to be uninteresting
201 201 continue
202 202 elif dsrc in d1 and ddst in d1:
203 203 # directory wasn't entirely moved locally
204 204 invalid[dsrc] = True
205 205 elif dsrc in d2 and ddst in d2:
206 206 # directory wasn't entirely moved remotely
207 207 invalid[dsrc] = True
208 208 elif dsrc in dirmove and dirmove[dsrc] != ddst:
209 209 # files from the same directory moved to two different places
210 210 invalid[dsrc] = True
211 211 else:
212 212 # looks good so far
213 213 dirmove[dsrc + "/"] = ddst + "/"
214 214
215 215 for i in invalid:
216 216 if i in dirmove:
217 217 del dirmove[i]
218 218
219 219 del d1, d2, invalid
220 220
221 221 if not dirmove:
222 222 return copy, diverge
223 223
224 224 for d in dirmove:
225 225 repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
226 226
227 227 # check unaccounted nonoverlapping files against directory moves
228 228 for f in u1 + u2:
229 229 if f not in fullcopy:
230 230 for d in dirmove:
231 231 if f.startswith(d):
232 232 # new file added in a directory that was moved, move it
233 233 copy[f] = dirmove[d] + f[len(d):]
234 234 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
235 235 break
236 236
237 237 return copy, diverge
238 238
239 239 def symmetricdifference(repo, rev1, rev2):
240 240 """symmetric difference of the sets of ancestors of rev1 and rev2
241 241
242 242 I.e. revisions that are ancestors of rev1 or rev2, but not both.
243 243 """
244 244 # basic idea:
245 245 # - mark rev1 and rev2 with different colors
246 246 # - walk the graph in topological order with the help of a heap;
247 247 # for each revision r:
248 248 # - if r has only one color, we want to return it
249 249 # - add colors[r] to its parents
250 250 #
251 251 # We keep track of the number of revisions in the heap that
252 252 # we may be interested in. We stop walking the graph as soon
253 253 # as this number reaches 0.
254 254 WHITE = 1
255 255 BLACK = 2
256 256 ALLCOLORS = WHITE | BLACK
257 257 colors = {rev1: WHITE, rev2: BLACK}
258 258
259 259 cl = repo.changelog
260 260
261 261 visit = [-rev1, -rev2]
262 262 heapq.heapify(visit)
263 263 n_wanted = len(visit)
264 264 ret = []
265 265
266 266 while n_wanted:
267 267 r = -heapq.heappop(visit)
268 268 wanted = colors[r] != ALLCOLORS
269 269 n_wanted -= wanted
270 270 if wanted:
271 271 ret.append(r)
272 272
273 273 for p in cl.parentrevs(r):
274 274 if p == nullrev:
275 275 continue
276 276 if p not in colors:
277 277 # first time we see p; add it to visit
278 278 n_wanted += wanted
279 279 colors[p] = colors[r]
280 280 heapq.heappush(visit, -p)
281 281 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
282 282 # at first we thought we wanted p, but now
283 283 # we know we don't really want it
284 284 n_wanted -= 1
285 285 colors[p] |= colors[r]
286 286
287 287 del colors[r]
288 288
289 289 return ret
290 290
291 291 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
292 292 """
293 293 Merge p1 and p2 with ancestor ma and generate merge action list
294 294
295 295 overwrite = whether we clobber working files
296 296 partial = function to filter file lists
297 297 """
298 298
299 299 repo.ui.note(_("resolving manifests\n"))
300 300 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
301 301 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
302 302
303 303 m1 = p1.manifest()
304 304 m2 = p2.manifest()
305 305 ma = pa.manifest()
306 306 backwards = (pa == p2)
307 307 action = []
308 308 copy = {}
309 309 diverge = {}
310 310
311 311 def fmerge(f, f2=None, fa=None):
312 312 """merge flags"""
313 313 if not f2:
314 314 f2 = f
315 315 fa = f
316 316 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
317 317 if m == n: # flags agree
318 318 return m # unchanged
319 319 if m and n: # flags are set but don't agree
320 320 if not a: # both differ from parent
321 321 r = repo.ui.prompt(
322 322 _(" conflicting flags for %s\n"
323 323 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
324 324 return r != "n" and r or ''
325 325 if m == a:
326 326 return n # changed from m to n
327 327 return m # changed from n to m
328 328 if m and m != a: # changed from a to m
329 329 return m
330 330 if n and n != a: # changed from a to n
331 331 return n
332 332 return '' # flag was cleared
333 333
334 334 def act(msg, m, f, *args):
335 335 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
336 336 action.append((f, m) + args)
337 337
338 338 if not (backwards or overwrite):
339 339 rev1 = p1.rev()
340 340 if rev1 is None:
341 341 # p1 is a workingctx
342 342 rev1 = p1.parents()[0].rev()
343 343 limit = min(symmetricdifference(repo, rev1, p2.rev()))
344 344 copy, diverge = findcopies(repo, m1, m2, ma, limit)
345 345
346 346 for of, fl in diverge.items():
347 347 act("divergent renames", "dr", of, fl)
348 348
349 349 copied = dict.fromkeys(copy.values())
350 350
351 351 # Compare manifests
352 352 for f, n in m1.iteritems():
353 353 if partial and not partial(f):
354 354 continue
355 355 if f in m2:
356 356 if overwrite or backwards:
357 357 rflags = m2.flags(f)
358 358 else:
359 359 rflags = fmerge(f)
360 360 # are files different?
361 361 if n != m2[f]:
362 362 a = ma.get(f, nullid)
363 363 # are we clobbering?
364 364 if overwrite:
365 365 act("clobbering", "g", f, rflags)
366 366 # or are we going back in time and clean?
367 367 elif backwards and not n[20:]:
368 368 act("reverting", "g", f, rflags)
369 369 # are both different from the ancestor?
370 370 elif n != a and m2[f] != a:
371 371 act("versions differ", "m", f, f, f, rflags, False)
372 372 # is remote's version newer?
373 373 elif m2[f] != a:
374 374 act("remote is newer", "g", f, rflags)
375 375 # local is newer, not overwrite, check mode bits
376 376 elif m1.flags(f) != rflags:
377 377 act("update permissions", "e", f, rflags)
378 378 # contents same, check mode bits
379 379 elif m1.flags(f) != rflags:
380 380 act("update permissions", "e", f, rflags)
381 381 elif f in copied:
382 382 continue
383 383 elif f in copy:
384 384 f2 = copy[f]
385 385 if f2 not in m2: # directory rename
386 386 act("remote renamed directory to " + f2, "d",
387 387 f, None, f2, m1.flags(f))
388 388 elif f2 in m1: # case 2 A,B/B/B
389 389 act("local copied to " + f2, "m",
390 390 f, f2, f, fmerge(f, f2, f2), False)
391 391 else: # case 4,21 A/B/B
392 392 act("local moved to " + f2, "m",
393 393 f, f2, f, fmerge(f, f2, f2), False)
394 394 elif f in ma:
395 395 if n != ma[f] and not overwrite:
396 396 if repo.ui.prompt(
397 397 _(" local changed %s which remote deleted\n"
398 398 "use (c)hanged version or (d)elete?") % f,
399 399 _("[cd]"), _("c")) == _("d"):
400 400 act("prompt delete", "r", f)
401 401 else:
402 402 act("other deleted", "r", f)
403 403 else:
404 404 # file is created on branch or in working directory
405 405 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
406 406 act("remote deleted", "r", f)
407 407
408 408 for f, n in m2.iteritems():
409 409 if partial and not partial(f):
410 410 continue
411 411 if f in m1:
412 412 continue
413 413 if f in copied:
414 414 continue
415 415 if f in copy:
416 416 f2 = copy[f]
417 417 if f2 not in m1: # directory rename
418 418 act("local renamed directory to " + f2, "d",
419 419 None, f, f2, m2.flags(f))
420 420 elif f2 in m2: # rename case 1, A/A,B/A
421 421 act("remote copied to " + f, "m",
422 422 f2, f, f, fmerge(f2, f, f2), False)
423 423 else: # case 3,20 A/B/A
424 424 act("remote moved to " + f, "m",
425 425 f2, f, f, fmerge(f2, f, f2), True)
426 426 elif f in ma:
427 427 if overwrite or backwards:
428 428 act("recreating", "g", f, m2.flags(f))
429 429 elif n != ma[f]:
430 430 if repo.ui.prompt(
431 431 _("remote changed %s which local deleted\n"
432 432 "use (c)hanged version or leave (d)eleted?") % f,
433 433 _("[cd]"), _("c")) == _("c"):
434 434 act("prompt recreating", "g", f, m2.flags(f))
435 435 else:
436 436 act("remote created", "g", f, m2.flags(f))
437 437
438 438 return action
439 439
440 440 def applyupdates(repo, action, wctx, mctx):
441 441 "apply the merge action list to the working directory"
442 442
443 443 updated, merged, removed, unresolved = 0, 0, 0, 0
444 444 action.sort()
445 445 # prescan for copy/renames
446 446 for a in action:
447 447 f, m = a[:2]
448 448 if m == 'm': # merge
449 449 f2, fd, flags, move = a[2:]
450 450 if f != fd:
451 451 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
452 452 repo.wwrite(fd, repo.wread(f), flags)
453 453
454 454 audit_path = util.path_auditor(repo.root)
455 455
456 456 for a in action:
457 457 f, m = a[:2]
458 458 if f and f[0] == "/":
459 459 continue
460 460 if m == "r": # remove
461 461 repo.ui.note(_("removing %s\n") % f)
462 462 audit_path(f)
463 463 try:
464 464 util.unlink(repo.wjoin(f))
465 465 except OSError, inst:
466 466 if inst.errno != errno.ENOENT:
467 467 repo.ui.warn(_("update failed to remove %s: %s!\n") %
468 468 (f, inst.strerror))
469 469 removed += 1
470 470 elif m == "m": # merge
471 471 f2, fd, flags, move = a[2:]
472 472 r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx)
473 473 if r > 0:
474 474 unresolved += 1
475 475 else:
476 476 if r is None:
477 477 updated += 1
478 478 else:
479 479 merged += 1
480 480 util.set_flags(repo.wjoin(fd), flags)
481 481 if f != fd and move and util.lexists(repo.wjoin(f)):
482 482 repo.ui.debug(_("removing %s\n") % f)
483 483 os.unlink(repo.wjoin(f))
484 484 elif m == "g": # get
485 485 flags = a[2]
486 486 repo.ui.note(_("getting %s\n") % f)
487 487 t = mctx.filectx(f).data()
488 488 repo.wwrite(f, t, flags)
489 489 updated += 1
490 490 elif m == "d": # directory rename
491 491 f2, fd, flags = a[2:]
492 492 if f:
493 493 repo.ui.note(_("moving %s to %s\n") % (f, fd))
494 494 t = wctx.filectx(f).data()
495 495 repo.wwrite(fd, t, flags)
496 496 util.unlink(repo.wjoin(f))
497 497 if f2:
498 498 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
499 499 t = mctx.filectx(f2).data()
500 500 repo.wwrite(fd, t, flags)
501 501 updated += 1
502 502 elif m == "dr": # divergent renames
503 503 fl = a[2]
504 504 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
505 505 for nf in fl:
506 506 repo.ui.warn(" %s\n" % nf)
507 507 elif m == "e": # exec
508 508 flags = a[2]
509 509 util.set_flags(repo.wjoin(f), flags)
510 510
511 511 return updated, merged, removed, unresolved
512 512
513 513 def recordupdates(repo, action, branchmerge):
514 514 "record merge actions to the dirstate"
515 515
516 516 for a in action:
517 517 f, m = a[:2]
518 518 if m == "r": # remove
519 519 if branchmerge:
520 520 repo.dirstate.remove(f)
521 521 else:
522 522 repo.dirstate.forget(f)
523 523 elif m == "f": # forget
524 524 repo.dirstate.forget(f)
525 525 elif m in "ge": # get or exec change
526 526 if branchmerge:
527 527 repo.dirstate.normaldirty(f)
528 528 else:
529 529 repo.dirstate.normal(f)
530 530 elif m == "m": # merge
531 531 f2, fd, flag, move = a[2:]
532 532 if branchmerge:
533 533 # We've done a branch merge, mark this file as merged
534 534 # so that we properly record the merger later
535 535 repo.dirstate.merge(fd)
536 536 if f != f2: # copy/rename
537 537 if move:
538 538 repo.dirstate.remove(f)
539 539 if f != fd:
540 540 repo.dirstate.copy(f, fd)
541 541 else:
542 542 repo.dirstate.copy(f2, fd)
543 543 else:
544 544 # We've update-merged a locally modified file, so
545 545 # we set the dirstate to emulate a normal checkout
546 546 # of that file some time in the past. Thus our
547 547 # merge will appear as a normal local file
548 548 # modification.
549 549 repo.dirstate.normallookup(fd)
550 550 if move:
551 551 repo.dirstate.forget(f)
552 552 elif m == "d": # directory rename
553 553 f2, fd, flag = a[2:]
554 554 if not f2 and f not in repo.dirstate:
555 555 # untracked file moved
556 556 continue
557 557 if branchmerge:
558 558 repo.dirstate.add(fd)
559 559 if f:
560 560 repo.dirstate.remove(f)
561 561 repo.dirstate.copy(f, fd)
562 562 if f2:
563 563 repo.dirstate.copy(f2, fd)
564 564 else:
565 565 repo.dirstate.normal(fd)
566 566 if f:
567 567 repo.dirstate.forget(f)
568 568
569 569 def update(repo, node, branchmerge, force, partial):
570 570 """
571 571 Perform a merge between the working directory and the given node
572 572
573 573 branchmerge = whether to merge between branches
574 574 force = whether to force branch merging or file overwriting
575 575 partial = a function to filter file lists (dirstate not updated)
576 576 """
577 577
578 578 wlock = repo.wlock()
579 579 try:
580 580 wc = repo.workingctx()
581 581 if node is None:
582 582 # tip of current branch
583 583 try:
584 584 node = repo.branchtags()[wc.branch()]
585 585 except KeyError:
586 586 if wc.branch() == "default": # no default branch!
587 587 node = repo.lookup("tip") # update to tip
588 588 else:
589 589 raise util.Abort(_("branch %s not found") % wc.branch())
590 590 overwrite = force and not branchmerge
591 591 forcemerge = force and branchmerge
592 592 pl = wc.parents()
593 593 p1, p2 = pl[0], repo.changectx(node)
594 594 pa = p1.ancestor(p2)
595 595 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
596 596 fastforward = False
597 597
598 598 ### check phase
599 599 if not overwrite and len(pl) > 1:
600 600 raise util.Abort(_("outstanding uncommitted merges"))
601 601 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
602 602 if branchmerge:
603 603 if p1.branch() != p2.branch() and pa != p2:
604 604 fastforward = True
605 605 else:
606 606 raise util.Abort(_("there is nothing to merge, just use "
607 607 "'hg update' or look at 'hg heads'"))
608 608 elif not (overwrite or branchmerge):
609 609 raise util.Abort(_("update spans branches, use 'hg merge' "
610 610 "or 'hg update -C' to lose changes"))
611 611 if branchmerge and not forcemerge:
612 if wc.files():
612 if wc.files() or wc.deleted():
613 613 raise util.Abort(_("outstanding uncommitted changes"))
614 614
615 615 ### calculate phase
616 616 action = []
617 617 if not force:
618 618 checkunknown(wc, p2)
619 619 if not util.checkfolding(repo.path):
620 620 checkcollision(p2)
621 621 action += forgetremoved(wc, p2, branchmerge)
622 622 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
623 623
624 624 ### apply phase
625 625 if not branchmerge: # just jump to the new rev
626 626 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
627 627 if not partial:
628 628 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
629 629
630 630 stats = applyupdates(repo, action, wc, p2)
631 631
632 632 if not partial:
633 633 recordupdates(repo, action, branchmerge)
634 634 repo.dirstate.setparents(fp1, fp2)
635 635 if not branchmerge and not fastforward:
636 636 repo.dirstate.setbranch(p2.branch())
637 637 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
638 638
639 639 return stats
640 640 finally:
641 641 del wlock
@@ -1,26 +1,27 b''
1 1 #!/bin/sh
2 2
3 3 hg init repo
4 4 cd repo
5 5
6 6 echo a > a
7 7 hg ci -qAm 'add a'
8 8
9 9 echo b > b
10 10 hg ci -qAm 'add b'
11 11
12 12 hg up -qC 0
13 13 hg rm a
14 14 hg ci -m 'rm a'
15 15
16 16 hg up -qC 1
17 17 rm a
18 18
19 19 echo '% local deleted a file, remote removed'
20 hg -v merge
20 hg merge # should fail, since there are deleted files
21 hg -v merge --force
21 22 echo % should show a as removed
22 23 hg st
23 24
24 25 hg ci -m merge
25 26 echo % manifest. should not have a:
26 27 hg manifest
@@ -1,9 +1,10 b''
1 1 % local deleted a file, remote removed
2 abort: outstanding uncommitted changes
2 3 resolving manifests
3 4 removing a
4 5 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
5 6 (branch merge, don't forget to commit)
6 7 % should show a as removed
7 8 R a
8 9 % manifest. should not have a:
9 10 b
General Comments 0
You need to be logged in to leave comments. Login now