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