##// END OF EJS Templates
merge: move forgets to the beginning of the action list...
Siddharth Agarwal -
r19987:ba648607 stable
parent child Browse files
Show More
@@ -1,781 +1,781
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 from mercurial import obsolete
11 11 import error, util, filemerge, copies, subrepo, worker, dicthelpers
12 12 import errno, os, shutil
13 13
14 14 class mergestate(object):
15 15 '''track 3-way merge state of individual files'''
16 16 def __init__(self, repo):
17 17 self._repo = repo
18 18 self._dirty = False
19 19 self._read()
20 20 def reset(self, node=None):
21 21 self._state = {}
22 22 if node:
23 23 self._local = node
24 24 shutil.rmtree(self._repo.join("merge"), True)
25 25 self._dirty = False
26 26 def _read(self):
27 27 self._state = {}
28 28 try:
29 29 f = self._repo.opener("merge/state")
30 30 for i, l in enumerate(f):
31 31 if i == 0:
32 32 self._local = bin(l[:-1])
33 33 else:
34 34 bits = l[:-1].split("\0")
35 35 self._state[bits[0]] = bits[1:]
36 36 f.close()
37 37 except IOError, err:
38 38 if err.errno != errno.ENOENT:
39 39 raise
40 40 self._dirty = False
41 41 def commit(self):
42 42 if self._dirty:
43 43 f = self._repo.opener("merge/state", "w")
44 44 f.write(hex(self._local) + "\n")
45 45 for d, v in self._state.iteritems():
46 46 f.write("\0".join([d] + v) + "\n")
47 47 f.close()
48 48 self._dirty = False
49 49 def add(self, fcl, fco, fca, fd):
50 50 hash = util.sha1(fcl.path()).hexdigest()
51 51 self._repo.opener.write("merge/" + hash, fcl.data())
52 52 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
53 53 hex(fca.filenode()), fco.path(), fcl.flags()]
54 54 self._dirty = True
55 55 def __contains__(self, dfile):
56 56 return dfile in self._state
57 57 def __getitem__(self, dfile):
58 58 return self._state[dfile][0]
59 59 def __iter__(self):
60 60 l = self._state.keys()
61 61 l.sort()
62 62 for f in l:
63 63 yield f
64 64 def files(self):
65 65 return self._state.keys()
66 66 def mark(self, dfile, state):
67 67 self._state[dfile][0] = state
68 68 self._dirty = True
69 69 def resolve(self, dfile, wctx, octx):
70 70 if self[dfile] == 'r':
71 71 return 0
72 72 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
73 73 fcd = wctx[dfile]
74 74 fco = octx[ofile]
75 75 fca = self._repo.filectx(afile, fileid=anode)
76 76 # "premerge" x flags
77 77 flo = fco.flags()
78 78 fla = fca.flags()
79 79 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
80 80 if fca.node() == nullid:
81 81 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
82 82 afile)
83 83 elif flags == fla:
84 84 flags = flo
85 85 # restore local
86 86 f = self._repo.opener("merge/" + hash)
87 87 self._repo.wwrite(dfile, f.read(), flags)
88 88 f.close()
89 89 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
90 90 if r is None:
91 91 # no real conflict
92 92 del self._state[dfile]
93 93 elif not r:
94 94 self.mark(dfile, 'r')
95 95 return r
96 96
97 97 def _checkunknownfile(repo, wctx, mctx, f):
98 98 return (not repo.dirstate._ignore(f)
99 99 and os.path.isfile(repo.wjoin(f))
100 100 and repo.wopener.audit.check(f)
101 101 and repo.dirstate.normalize(f) not in repo.dirstate
102 102 and mctx[f].cmp(wctx[f]))
103 103
104 104 def _checkunknown(repo, wctx, mctx):
105 105 "check for collisions between unknown files and files in mctx"
106 106
107 107 error = False
108 108 for f in mctx:
109 109 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
110 110 error = True
111 111 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
112 112 if error:
113 113 raise util.Abort(_("untracked files in working directory differ "
114 114 "from files in requested revision"))
115 115
116 116 def _forgetremoved(wctx, mctx, branchmerge):
117 117 """
118 118 Forget removed files
119 119
120 120 If we're jumping between revisions (as opposed to merging), and if
121 121 neither the working directory nor the target rev has the file,
122 122 then we need to remove it from the dirstate, to prevent the
123 123 dirstate from listing the file when it is no longer in the
124 124 manifest.
125 125
126 126 If we're merging, and the other revision has removed a file
127 127 that is not present in the working directory, we need to mark it
128 128 as removed.
129 129 """
130 130
131 131 actions = []
132 132 state = branchmerge and 'r' or 'f'
133 133 for f in wctx.deleted():
134 134 if f not in mctx:
135 135 actions.append((f, state, None, "forget deleted"))
136 136
137 137 if not branchmerge:
138 138 for f in wctx.removed():
139 139 if f not in mctx:
140 140 actions.append((f, "f", None, "forget removed"))
141 141
142 142 return actions
143 143
144 144 def _checkcollision(repo, wmf, actions, prompts):
145 145 # build provisional merged manifest up
146 146 pmmf = set(wmf)
147 147
148 148 def addop(f, args):
149 149 pmmf.add(f)
150 150 def removeop(f, args):
151 151 pmmf.discard(f)
152 152 def nop(f, args):
153 153 pass
154 154
155 155 def renameop(f, args):
156 156 f2, fd, flags = args
157 157 if f:
158 158 pmmf.discard(f)
159 159 pmmf.add(fd)
160 160 def mergeop(f, args):
161 161 f2, fd, move = args
162 162 if move:
163 163 pmmf.discard(f)
164 164 pmmf.add(fd)
165 165
166 166 opmap = {
167 167 "a": addop,
168 168 "d": renameop,
169 169 "dr": nop,
170 170 "e": nop,
171 171 "f": addop, # untracked file should be kept in working directory
172 172 "g": addop,
173 173 "m": mergeop,
174 174 "r": removeop,
175 175 "rd": nop,
176 176 }
177 177 for f, m, args, msg in actions:
178 178 op = opmap.get(m)
179 179 assert op, m
180 180 op(f, args)
181 181
182 182 opmap = {
183 183 "cd": addop,
184 184 "dc": addop,
185 185 }
186 186 for f, m in prompts:
187 187 op = opmap.get(m)
188 188 assert op, m
189 189 op(f, None)
190 190
191 191 # check case-folding collision in provisional merged manifest
192 192 foldmap = {}
193 193 for f in sorted(pmmf):
194 194 fold = util.normcase(f)
195 195 if fold in foldmap:
196 196 raise util.Abort(_("case-folding collision between %s and %s")
197 197 % (f, foldmap[fold]))
198 198 foldmap[fold] = f
199 199
200 200 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
201 201 acceptremote=False):
202 202 """
203 203 Merge p1 and p2 with ancestor pa and generate merge action list
204 204
205 205 branchmerge and force are as passed in to update
206 206 partial = function to filter file lists
207 207 acceptremote = accept the incoming changes without prompting
208 208 """
209 209
210 210 overwrite = force and not branchmerge
211 211 actions, copy, movewithdir = [], {}, {}
212 212
213 213 followcopies = False
214 214 if overwrite:
215 215 pa = wctx
216 216 elif pa == p2: # backwards
217 217 pa = wctx.p1()
218 218 elif not branchmerge and not wctx.dirty(missing=True):
219 219 pass
220 220 elif pa and repo.ui.configbool("merge", "followcopies", True):
221 221 followcopies = True
222 222
223 223 # manifests fetched in order are going to be faster, so prime the caches
224 224 [x.manifest() for x in
225 225 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
226 226
227 227 if followcopies:
228 228 ret = copies.mergecopies(repo, wctx, p2, pa)
229 229 copy, movewithdir, diverge, renamedelete = ret
230 230 for of, fl in diverge.iteritems():
231 231 actions.append((of, "dr", (fl,), "divergent renames"))
232 232 for of, fl in renamedelete.iteritems():
233 233 actions.append((of, "rd", (fl,), "rename and delete"))
234 234
235 235 repo.ui.note(_("resolving manifests\n"))
236 236 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
237 237 % (bool(branchmerge), bool(force), bool(partial)))
238 238 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
239 239
240 240 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
241 241 copied = set(copy.values())
242 242 copied.update(movewithdir.values())
243 243
244 244 if '.hgsubstate' in m1:
245 245 # check whether sub state is modified
246 246 for s in sorted(wctx.substate):
247 247 if wctx.sub(s).dirty():
248 248 m1['.hgsubstate'] += "+"
249 249 break
250 250
251 251 aborts, prompts = [], []
252 252 # Compare manifests
253 253 fdiff = dicthelpers.diff(m1, m2)
254 254 flagsdiff = m1.flagsdiff(m2)
255 255 diff12 = dicthelpers.join(fdiff, flagsdiff)
256 256
257 257 for f, (n12, fl12) in diff12.iteritems():
258 258 if n12:
259 259 n1, n2 = n12
260 260 else: # file contents didn't change, but flags did
261 261 n1 = n2 = m1.get(f, None)
262 262 if n1 is None:
263 263 # Since n1 == n2, the file isn't present in m2 either. This
264 264 # means that the file was removed or deleted locally and
265 265 # removed remotely, but that residual entries remain in flags.
266 266 # This can happen in manifests generated by workingctx.
267 267 continue
268 268 if fl12:
269 269 fl1, fl2 = fl12
270 270 else: # flags didn't change, file contents did
271 271 fl1 = fl2 = m1.flags(f)
272 272
273 273 if partial and not partial(f):
274 274 continue
275 275 if n1 and n2:
276 276 fla = ma.flags(f)
277 277 nol = 'l' not in fl1 + fl2 + fla
278 278 a = ma.get(f, nullid)
279 279 if n2 == a and fl2 == fla:
280 280 pass # remote unchanged - keep local
281 281 elif n1 == a and fl1 == fla: # local unchanged - use remote
282 282 if n1 == n2: # optimization: keep local content
283 283 actions.append((f, "e", (fl2,), "update permissions"))
284 284 else:
285 285 actions.append((f, "g", (fl2,), "remote is newer"))
286 286 elif nol and n2 == a: # remote only changed 'x'
287 287 actions.append((f, "e", (fl2,), "update permissions"))
288 288 elif nol and n1 == a: # local only changed 'x'
289 289 actions.append((f, "g", (fl1,), "remote is newer"))
290 290 else: # both changed something
291 291 actions.append((f, "m", (f, f, False), "versions differ"))
292 292 elif f in copied: # files we'll deal with on m2 side
293 293 pass
294 294 elif n1 and f in movewithdir: # directory rename
295 295 f2 = movewithdir[f]
296 296 actions.append((f, "d", (None, f2, fl1),
297 297 "remote renamed directory to " + f2))
298 298 elif n1 and f in copy:
299 299 f2 = copy[f]
300 300 actions.append((f, "m", (f2, f, False),
301 301 "local copied/moved to " + f2))
302 302 elif n1 and f in ma: # clean, a different, no remote
303 303 if n1 != ma[f]:
304 304 prompts.append((f, "cd")) # prompt changed/deleted
305 305 elif n1[20:] == "a": # added, no remote
306 306 actions.append((f, "f", None, "remote deleted"))
307 307 else:
308 308 actions.append((f, "r", None, "other deleted"))
309 309 elif n2 and f in movewithdir:
310 310 f2 = movewithdir[f]
311 311 actions.append((None, "d", (f, f2, fl2),
312 312 "local renamed directory to " + f2))
313 313 elif n2 and f in copy:
314 314 f2 = copy[f]
315 315 if f2 in m2:
316 316 actions.append((f2, "m", (f, f, False),
317 317 "remote copied to " + f))
318 318 else:
319 319 actions.append((f2, "m", (f, f, True),
320 320 "remote moved to " + f))
321 321 elif n2 and f not in ma:
322 322 # local unknown, remote created: the logic is described by the
323 323 # following table:
324 324 #
325 325 # force branchmerge different | action
326 326 # n * n | get
327 327 # n * y | abort
328 328 # y n * | get
329 329 # y y n | get
330 330 # y y y | merge
331 331 #
332 332 # Checking whether the files are different is expensive, so we
333 333 # don't do that when we can avoid it.
334 334 if force and not branchmerge:
335 335 actions.append((f, "g", (fl2,), "remote created"))
336 336 else:
337 337 different = _checkunknownfile(repo, wctx, p2, f)
338 338 if force and branchmerge and different:
339 339 actions.append((f, "m", (f, f, False),
340 340 "remote differs from untracked local"))
341 341 elif not force and different:
342 342 aborts.append((f, "ud"))
343 343 else:
344 344 actions.append((f, "g", (fl2,), "remote created"))
345 345 elif n2 and n2 != ma[f]:
346 346 prompts.append((f, "dc")) # prompt deleted/changed
347 347
348 348 for f, m in sorted(aborts):
349 349 if m == "ud":
350 350 repo.ui.warn(_("%s: untracked file differs\n") % f)
351 351 else: assert False, m
352 352 if aborts:
353 353 raise util.Abort(_("untracked files in working directory differ "
354 354 "from files in requested revision"))
355 355
356 356 if not util.checkcase(repo.path):
357 357 # check collision between files only in p2 for clean update
358 358 if (not branchmerge and
359 359 (force or not wctx.dirty(missing=True, branch=False))):
360 360 _checkcollision(repo, m2, [], [])
361 361 else:
362 362 _checkcollision(repo, m1, actions, prompts)
363 363
364 364 for f, m in sorted(prompts):
365 365 if m == "cd":
366 366 if acceptremote:
367 367 actions.append((f, "r", None, "remote delete"))
368 368 elif repo.ui.promptchoice(
369 369 _("local changed %s which remote deleted\n"
370 370 "use (c)hanged version or (d)elete?"
371 371 "$$ &Changed $$ &Delete") % f, 0):
372 372 actions.append((f, "r", None, "prompt delete"))
373 373 else:
374 374 actions.append((f, "a", None, "prompt keep"))
375 375 elif m == "dc":
376 376 if acceptremote:
377 377 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
378 378 elif repo.ui.promptchoice(
379 379 _("remote changed %s which local deleted\n"
380 380 "use (c)hanged version or leave (d)eleted?"
381 381 "$$ &Changed $$ &Deleted") % f, 0) == 0:
382 382 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
383 383 else: assert False, m
384 384 return actions
385 385
386 386 def actionkey(a):
387 return a[1] == "r" and -1 or 0, a
387 return a[1] in "rf" and -1 or 0, a
388 388
389 389 def getremove(repo, mctx, overwrite, args):
390 390 """apply usually-non-interactive updates to the working directory
391 391
392 392 mctx is the context to be merged into the working copy
393 393
394 394 yields tuples for progress updates
395 395 """
396 396 verbose = repo.ui.verbose
397 397 unlink = util.unlinkpath
398 398 wjoin = repo.wjoin
399 399 fctx = mctx.filectx
400 400 wwrite = repo.wwrite
401 401 audit = repo.wopener.audit
402 402 i = 0
403 403 for arg in args:
404 404 f = arg[0]
405 405 if arg[1] == 'r':
406 406 if verbose:
407 407 repo.ui.note(_("removing %s\n") % f)
408 408 audit(f)
409 409 try:
410 410 unlink(wjoin(f), ignoremissing=True)
411 411 except OSError, inst:
412 412 repo.ui.warn(_("update failed to remove %s: %s!\n") %
413 413 (f, inst.strerror))
414 414 else:
415 415 if verbose:
416 416 repo.ui.note(_("getting %s\n") % f)
417 417 wwrite(f, fctx(f).data(), arg[2][0])
418 418 if i == 100:
419 419 yield i, f
420 420 i = 0
421 421 i += 1
422 422 if i > 0:
423 423 yield i, f
424 424
425 425 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
426 426 """apply the merge action list to the working directory
427 427
428 428 wctx is the working copy context
429 429 mctx is the context to be merged into the working copy
430 430 actx is the context of the common ancestor
431 431
432 432 Return a tuple of counts (updated, merged, removed, unresolved) that
433 433 describes how many files were affected by the update.
434 434 """
435 435
436 436 updated, merged, removed, unresolved = 0, 0, 0, 0
437 437 ms = mergestate(repo)
438 438 ms.reset(wctx.p1().node())
439 439 moves = []
440 440 actions.sort(key=actionkey)
441 441
442 442 # prescan for merges
443 443 for a in actions:
444 444 f, m, args, msg = a
445 445 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
446 446 if m == "m": # merge
447 447 f2, fd, move = args
448 448 if fd == '.hgsubstate': # merged internally
449 449 continue
450 450 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
451 451 fcl = wctx[f]
452 452 fco = mctx[f2]
453 453 if mctx == actx: # backwards, use working dir parent as ancestor
454 454 if fcl.parents():
455 455 fca = fcl.p1()
456 456 else:
457 457 fca = repo.filectx(f, fileid=nullrev)
458 458 else:
459 459 fca = fcl.ancestor(fco, actx)
460 460 if not fca:
461 461 fca = repo.filectx(f, fileid=nullrev)
462 462 ms.add(fcl, fco, fca, fd)
463 463 if f != fd and move:
464 464 moves.append(f)
465 465
466 466 audit = repo.wopener.audit
467 467
468 468 # remove renamed files after safely stored
469 469 for f in moves:
470 470 if os.path.lexists(repo.wjoin(f)):
471 471 repo.ui.debug("removing %s\n" % f)
472 472 audit(f)
473 473 util.unlinkpath(repo.wjoin(f))
474 474
475 475 numupdates = len(actions)
476 476 workeractions = [a for a in actions if a[1] in 'gr']
477 477 updateactions = [a for a in workeractions if a[1] == 'g']
478 478 updated = len(updateactions)
479 479 removeactions = [a for a in workeractions if a[1] == 'r']
480 480 removed = len(removeactions)
481 481 actions = [a for a in actions if a[1] not in 'gr']
482 482
483 483 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
484 484 if hgsub and hgsub[0] == 'r':
485 485 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
486 486
487 487 z = 0
488 488 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
489 489 removeactions)
490 490 for i, item in prog:
491 491 z += i
492 492 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
493 493 unit=_('files'))
494 494 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
495 495 updateactions)
496 496 for i, item in prog:
497 497 z += i
498 498 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
499 499 unit=_('files'))
500 500
501 501 if hgsub and hgsub[0] == 'g':
502 502 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
503 503
504 504 _updating = _('updating')
505 505 _files = _('files')
506 506 progress = repo.ui.progress
507 507
508 508 for i, a in enumerate(actions):
509 509 f, m, args, msg = a
510 510 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
511 511 if m == "m": # merge
512 512 f2, fd, move = args
513 513 if fd == '.hgsubstate': # subrepo states need updating
514 514 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
515 515 overwrite)
516 516 continue
517 517 audit(fd)
518 518 r = ms.resolve(fd, wctx, mctx)
519 519 if r is not None and r > 0:
520 520 unresolved += 1
521 521 else:
522 522 if r is None:
523 523 updated += 1
524 524 else:
525 525 merged += 1
526 526 elif m == "d": # directory rename
527 527 f2, fd, flags = args
528 528 if f:
529 529 repo.ui.note(_("moving %s to %s\n") % (f, fd))
530 530 audit(f)
531 531 repo.wwrite(fd, wctx.filectx(f).data(), flags)
532 532 util.unlinkpath(repo.wjoin(f))
533 533 if f2:
534 534 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
535 535 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
536 536 updated += 1
537 537 elif m == "dr": # divergent renames
538 538 fl, = args
539 539 repo.ui.warn(_("note: possible conflict - %s was renamed "
540 540 "multiple times to:\n") % f)
541 541 for nf in fl:
542 542 repo.ui.warn(" %s\n" % nf)
543 543 elif m == "rd": # rename and delete
544 544 fl, = args
545 545 repo.ui.warn(_("note: possible conflict - %s was deleted "
546 546 "and renamed to:\n") % f)
547 547 for nf in fl:
548 548 repo.ui.warn(" %s\n" % nf)
549 549 elif m == "e": # exec
550 550 flags, = args
551 551 audit(f)
552 552 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
553 553 updated += 1
554 554 ms.commit()
555 555 progress(_updating, None, total=numupdates, unit=_files)
556 556
557 557 return updated, merged, removed, unresolved
558 558
559 559 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
560 560 acceptremote=False):
561 561 "Calculate the actions needed to merge mctx into tctx"
562 562 actions = []
563 563 actions += manifestmerge(repo, tctx, mctx,
564 564 ancestor,
565 565 branchmerge, force,
566 566 partial, acceptremote)
567 567 if tctx.rev() is None:
568 568 actions += _forgetremoved(tctx, mctx, branchmerge)
569 569 return actions
570 570
571 571 def recordupdates(repo, actions, branchmerge):
572 572 "record merge actions to the dirstate"
573 573
574 574 for a in actions:
575 575 f, m, args, msg = a
576 576 if m == "r": # remove
577 577 if branchmerge:
578 578 repo.dirstate.remove(f)
579 579 else:
580 580 repo.dirstate.drop(f)
581 581 elif m == "a": # re-add
582 582 if not branchmerge:
583 583 repo.dirstate.add(f)
584 584 elif m == "f": # forget
585 585 repo.dirstate.drop(f)
586 586 elif m == "e": # exec change
587 587 repo.dirstate.normallookup(f)
588 588 elif m == "g": # get
589 589 if branchmerge:
590 590 repo.dirstate.otherparent(f)
591 591 else:
592 592 repo.dirstate.normal(f)
593 593 elif m == "m": # merge
594 594 f2, fd, move = args
595 595 if branchmerge:
596 596 # We've done a branch merge, mark this file as merged
597 597 # so that we properly record the merger later
598 598 repo.dirstate.merge(fd)
599 599 if f != f2: # copy/rename
600 600 if move:
601 601 repo.dirstate.remove(f)
602 602 if f != fd:
603 603 repo.dirstate.copy(f, fd)
604 604 else:
605 605 repo.dirstate.copy(f2, fd)
606 606 else:
607 607 # We've update-merged a locally modified file, so
608 608 # we set the dirstate to emulate a normal checkout
609 609 # of that file some time in the past. Thus our
610 610 # merge will appear as a normal local file
611 611 # modification.
612 612 if f2 == fd: # file not locally copied/moved
613 613 repo.dirstate.normallookup(fd)
614 614 if move:
615 615 repo.dirstate.drop(f)
616 616 elif m == "d": # directory rename
617 617 f2, fd, flag = args
618 618 if not f2 and f not in repo.dirstate:
619 619 # untracked file moved
620 620 continue
621 621 if branchmerge:
622 622 repo.dirstate.add(fd)
623 623 if f:
624 624 repo.dirstate.remove(f)
625 625 repo.dirstate.copy(f, fd)
626 626 if f2:
627 627 repo.dirstate.copy(f2, fd)
628 628 else:
629 629 repo.dirstate.normal(fd)
630 630 if f:
631 631 repo.dirstate.drop(f)
632 632
633 633 def update(repo, node, branchmerge, force, partial, ancestor=None,
634 634 mergeancestor=False):
635 635 """
636 636 Perform a merge between the working directory and the given node
637 637
638 638 node = the node to update to, or None if unspecified
639 639 branchmerge = whether to merge between branches
640 640 force = whether to force branch merging or file overwriting
641 641 partial = a function to filter file lists (dirstate not updated)
642 642 mergeancestor = whether it is merging with an ancestor. If true,
643 643 we should accept the incoming changes for any prompts that occur.
644 644 If false, merging with an ancestor (fast-forward) is only allowed
645 645 between different named branches. This flag is used by rebase extension
646 646 as a temporary fix and should be avoided in general.
647 647
648 648 The table below shows all the behaviors of the update command
649 649 given the -c and -C or no options, whether the working directory
650 650 is dirty, whether a revision is specified, and the relationship of
651 651 the parent rev to the target rev (linear, on the same named
652 652 branch, or on another named branch).
653 653
654 654 This logic is tested by test-update-branches.t.
655 655
656 656 -c -C dirty rev | linear same cross
657 657 n n n n | ok (1) x
658 658 n n n y | ok ok ok
659 659 n n y n | merge (2) (2)
660 660 n n y y | merge (3) (3)
661 661 n y * * | --- discard ---
662 662 y n y * | --- (4) ---
663 663 y n n * | --- ok ---
664 664 y y * * | --- (5) ---
665 665
666 666 x = can't happen
667 667 * = don't-care
668 668 1 = abort: not a linear update (merge or update --check to force update)
669 669 2 = abort: uncommitted changes (commit and merge, or update --clean to
670 670 discard changes)
671 671 3 = abort: uncommitted changes (commit or update --clean to discard changes)
672 672 4 = abort: uncommitted changes (checked in commands.py)
673 673 5 = incompatible options (checked in commands.py)
674 674
675 675 Return the same tuple as applyupdates().
676 676 """
677 677
678 678 onode = node
679 679 wlock = repo.wlock()
680 680 try:
681 681 wc = repo[None]
682 682 if node is None:
683 683 # tip of current branch
684 684 try:
685 685 node = repo.branchtip(wc.branch())
686 686 except error.RepoLookupError:
687 687 if wc.branch() == "default": # no default branch!
688 688 node = repo.lookup("tip") # update to tip
689 689 else:
690 690 raise util.Abort(_("branch %s not found") % wc.branch())
691 691 overwrite = force and not branchmerge
692 692 pl = wc.parents()
693 693 p1, p2 = pl[0], repo[node]
694 694 if ancestor:
695 695 pa = repo[ancestor]
696 696 else:
697 697 pa = p1.ancestor(p2)
698 698
699 699 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
700 700
701 701 ### check phase
702 702 if not overwrite and len(pl) > 1:
703 703 raise util.Abort(_("outstanding uncommitted merges"))
704 704 if branchmerge:
705 705 if pa == p2:
706 706 raise util.Abort(_("merging with a working directory ancestor"
707 707 " has no effect"))
708 708 elif pa == p1:
709 709 if not mergeancestor and p1.branch() == p2.branch():
710 710 raise util.Abort(_("nothing to merge"),
711 711 hint=_("use 'hg update' "
712 712 "or check 'hg heads'"))
713 713 if not force and (wc.files() or wc.deleted()):
714 714 raise util.Abort(_("uncommitted changes"),
715 715 hint=_("use 'hg status' to list changes"))
716 716 for s in sorted(wc.substate):
717 717 if wc.sub(s).dirty():
718 718 raise util.Abort(_("uncommitted changes in "
719 719 "subrepository '%s'") % s)
720 720
721 721 elif not overwrite:
722 722 if p1 == p2: # no-op update
723 723 # call the hooks and exit early
724 724 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
725 725 repo.hook('update', parent1=xp2, parent2='', error=0)
726 726 return 0, 0, 0, 0
727 727
728 728 if pa not in (p1, p2): # nonlinear
729 729 dirty = wc.dirty(missing=True)
730 730 if dirty or onode is None:
731 731 # Branching is a bit strange to ensure we do the minimal
732 732 # amount of call to obsolete.background.
733 733 foreground = obsolete.foreground(repo, [p1.node()])
734 734 # note: the <node> variable contains a random identifier
735 735 if repo[node].node() in foreground:
736 736 pa = p1 # allow updating to successors
737 737 elif dirty:
738 738 msg = _("uncommitted changes")
739 739 if onode is None:
740 740 hint = _("commit and merge, or update --clean to"
741 741 " discard changes")
742 742 else:
743 743 hint = _("commit or update --clean to discard"
744 744 " changes")
745 745 raise util.Abort(msg, hint=hint)
746 746 else: # node is none
747 747 msg = _("not a linear update")
748 748 hint = _("merge or update --check to force update")
749 749 raise util.Abort(msg, hint=hint)
750 750 else:
751 751 # Allow jumping branches if clean and specific rev given
752 752 pa = p1
753 753
754 754 ### calculate phase
755 755 actions = calculateupdates(repo, wc, p2, pa,
756 756 branchmerge, force, partial, mergeancestor)
757 757
758 758 ### apply phase
759 759 if not branchmerge: # just jump to the new rev
760 760 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
761 761 if not partial:
762 762 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
763 763 # note that we're in the middle of an update
764 764 repo.vfs.write('updatestate', p2.hex())
765 765
766 766 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
767 767
768 768 if not partial:
769 769 repo.setparents(fp1, fp2)
770 770 recordupdates(repo, actions, branchmerge)
771 771 # update completed, clear state
772 772 util.unlink(repo.join('updatestate'))
773 773
774 774 if not branchmerge:
775 775 repo.dirstate.setbranch(p2.branch())
776 776 finally:
777 777 wlock.release()
778 778
779 779 if not partial:
780 780 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
781 781 return stats
@@ -1,140 +1,158
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add -n
5 5 adding a
6 6 $ hg st
7 7 ? a
8 8 $ hg add
9 9 adding a
10 10 $ hg st
11 11 A a
12 12 $ hg forget a
13 13 $ hg add
14 14 adding a
15 15 $ hg st
16 16 A a
17 17
18 18 $ echo b > b
19 19 $ hg add -n b
20 20 $ hg st
21 21 A a
22 22 ? b
23 23 $ hg add b
24 24 $ hg st
25 25 A a
26 26 A b
27 27
28 28 should fail
29 29
30 30 $ hg add b
31 31 b already tracked!
32 32 $ hg st
33 33 A a
34 34 A b
35 35
36 36 #if no-windows
37 37 $ echo foo > con.xml
38 38 $ hg --config ui.portablefilenames=jump add con.xml
39 39 abort: ui.portablefilenames value is invalid ('jump')
40 40 [255]
41 41 $ hg --config ui.portablefilenames=abort add con.xml
42 42 abort: filename contains 'con', which is reserved on Windows: 'con.xml'
43 43 [255]
44 44 $ hg st
45 45 A a
46 46 A b
47 47 ? con.xml
48 48 $ hg add con.xml
49 49 warning: filename contains 'con', which is reserved on Windows: 'con.xml'
50 50 $ hg st
51 51 A a
52 52 A b
53 53 A con.xml
54 54 $ hg forget con.xml
55 55 $ rm con.xml
56 56 #endif
57 57
58 58 #if eol-in-paths
59 59 $ echo bla > 'hello:world'
60 60 $ hg --config ui.portablefilenames=abort add
61 61 adding hello:world
62 62 abort: filename contains ':', which is reserved on Windows: 'hello:world'
63 63 [255]
64 64 $ hg st
65 65 A a
66 66 A b
67 67 ? hello:world
68 68 $ hg --config ui.portablefilenames=ignore add
69 69 adding hello:world
70 70 $ hg st
71 71 A a
72 72 A b
73 73 A hello:world
74 74 #endif
75 75
76 76 $ hg ci -m 0 --traceback
77 77
78 78 should fail
79 79
80 80 $ hg add a
81 81 a already tracked!
82 82
83 83 $ echo aa > a
84 84 $ hg ci -m 1
85 85 $ hg up 0
86 86 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 87 $ echo aaa > a
88 88 $ hg ci -m 2
89 89 created new head
90 90
91 91 $ hg merge
92 92 merging a
93 93 warning: conflicts during merge.
94 94 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
95 95 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
96 96 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
97 97 [1]
98 98 $ hg st
99 99 M a
100 100 ? a.orig
101 101
102 102 should fail
103 103
104 104 $ hg add a
105 105 a already tracked!
106 106 $ hg st
107 107 M a
108 108 ? a.orig
109 109 $ hg resolve -m a
110 110 $ hg ci -m merge
111 111
112 112 Issue683: peculiarity with hg revert of an removed then added file
113 113
114 114 $ hg forget a
115 115 $ hg add a
116 116 $ hg st
117 117 ? a.orig
118 118 $ hg rm a
119 119 $ hg st
120 120 R a
121 121 ? a.orig
122 122 $ echo a > a
123 123 $ hg add a
124 124 $ hg st
125 125 M a
126 126 ? a.orig
127 127
128 128 $ hg add c && echo "unexpected addition of missing file"
129 129 c: * (glob)
130 130 [1]
131 131 $ echo c > c
132 132 $ hg add d c && echo "unexpected addition of missing file"
133 133 d: * (glob)
134 134 [1]
135 135 $ hg st
136 136 M a
137 137 A c
138 138 ? a.orig
139 $ hg up -C
140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141
142 forget and get should have the right order: added but missing dir should be
143 forgotten before file with same name is added
144
145 $ echo file d > d
146 $ hg add d
147 $ hg ci -md
148 $ hg rm d
149 $ mkdir d
150 $ echo a > d/a
151 $ hg add d/a
152 $ rm -r d
153 $ hg up -C
154 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 $ cat d
156 file d
139 157
140 158 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now