##// END OF EJS Templates
merge: exit early during a no-op update (BC)...
Siddharth Agarwal -
r19929:ab2362e1 default
parent child Browse files
Show More
@@ -1,775 +1,781 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 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 387 return a[1] == "r" 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 if p1 == p2: # no-op update
723 # call the hooks and exit early
724 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
725 repo.hook('update', parent1=xp2, parent2='', error=0)
726 return 0, 0, 0, 0
727
722 728 if pa not in (p1, p2): # nolinear
723 729 dirty = wc.dirty(missing=True)
724 730 if dirty or onode is None:
725 731 # Branching is a bit strange to ensure we do the minimal
726 732 # amount of call to obsolete.background.
727 733 foreground = obsolete.foreground(repo, [p1.node()])
728 734 # note: the <node> variable contains a random identifier
729 735 if repo[node].node() in foreground:
730 736 pa = p1 # allow updating to successors
731 737 elif dirty:
732 738 msg = _("uncommitted changes")
733 739 if onode is None:
734 740 hint = _("commit and merge, or update --clean to"
735 741 " discard changes")
736 742 else:
737 743 hint = _("commit or update --clean to discard"
738 744 " changes")
739 745 raise util.Abort(msg, hint=hint)
740 746 else: # node is none
741 747 msg = _("not a linear update")
742 748 hint = _("merge or update --check to force update")
743 749 raise util.Abort(msg, hint=hint)
744 750 else:
745 751 # Allow jumping branches if clean and specific rev given
746 752 pa = p1
747 753
748 754 ### calculate phase
749 755 actions = calculateupdates(repo, wc, p2, pa,
750 756 branchmerge, force, partial, mergeancestor)
751 757
752 758 ### apply phase
753 759 if not branchmerge: # just jump to the new rev
754 760 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
755 761 if not partial:
756 762 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
757 763 # note that we're in the middle of an update
758 764 repo.vfs.write('updatestate', p2.hex())
759 765
760 766 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
761 767
762 768 if not partial:
763 769 repo.setparents(fp1, fp2)
764 770 recordupdates(repo, actions, branchmerge)
765 771 # update completed, clear state
766 772 util.unlink(repo.join('updatestate'))
767 773
768 774 if not branchmerge:
769 775 repo.dirstate.setbranch(p2.branch())
770 776 finally:
771 777 wlock.release()
772 778
773 779 if not partial:
774 780 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
775 781 return stats
@@ -1,55 +1,53 b''
1 1 Create an empty repo:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5
6 6 Try some commands:
7 7
8 8 $ hg log
9 9 $ hg grep wah
10 10 [1]
11 11 $ hg manifest
12 12 $ hg verify
13 13 checking changesets
14 14 checking manifests
15 15 crosschecking files in changesets and manifests
16 16 checking files
17 17 0 files, 0 changesets, 0 total revisions
18 18
19 19 Check the basic files created:
20 20
21 21 $ ls .hg
22 22 00changelog.i
23 23 requires
24 24 store
25 25
26 26 Should be empty:
27 27
28 28 $ ls .hg/store
29 29
30 30 Poke at a clone:
31 31
32 32 $ cd ..
33 33 $ hg clone a b
34 34 updating to branch default
35 35 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ cd b
37 37 $ hg verify
38 38 checking changesets
39 39 checking manifests
40 40 crosschecking files in changesets and manifests
41 41 checking files
42 42 0 files, 0 changesets, 0 total revisions
43 43 $ ls .hg
44 44 00changelog.i
45 branch
46 dirstate
47 45 hgrc
48 46 requires
49 47 store
50 48
51 49 Should be empty:
52 50
53 51 $ ls .hg/store
54 52
55 53 $ cd ..
@@ -1,289 +1,290 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "mq=" >> $HGRCPATH
3 3 $ echo "graphlog=" >> $HGRCPATH
4 4
5 5 make a test repository that looks like this:
6 6
7 7 o 2:28bc7b1afd6a
8 8 |
9 9 | @ 1:d7fe2034f71b
10 10 |/
11 11 o 0/62ecad8b70e5
12 12
13 13 $ hg init r0
14 14 $ cd r0
15 15 $ touch f0
16 16 $ hg ci -m0 -Aq
17 17 $ touch f1
18 18 $ hg ci -m1 -Aq
19 19
20 20 $ hg update 0 -q
21 21 $ touch f2
22 22 $ hg ci -m2 -Aq
23 23 $ hg update 1 -q
24 24
25 25 make some patches with a parent: 1:d7fe2034f71b -> p0 -> p1
26 26
27 27 $ echo cp0 >> fp0
28 28 $ hg add fp0
29 29 $ hg ci -m p0 -d "0 0"
30 30 $ hg export -r. > p0
31 31 $ hg strip -qn .
32 32 $ hg qimport p0
33 33 adding p0 to series file
34 34 $ hg qpush
35 35 applying p0
36 36 now at: p0
37 37
38 38 $ echo cp1 >> fp1
39 39 $ hg add fp1
40 40 $ hg qnew p1 -d "0 0"
41 41
42 42 $ hg qpop -aq
43 43 patch queue now empty
44 44
45 45 qpush --exact when at the parent
46 46
47 47 $ hg update 1 -q
48 48 $ hg qpush -e
49 49 applying p0
50 50 now at: p0
51 51 $ hg parents -qr qbase
52 52 1:d7fe2034f71b
53 53 $ hg qpop -aq
54 54 patch queue now empty
55 55
56 56 $ hg qpush -e p0
57 57 applying p0
58 58 now at: p0
59 59 $ hg parents -qr qbase
60 60 1:d7fe2034f71b
61 61 $ hg qpop -aq
62 62 patch queue now empty
63 63
64 64 $ hg qpush -e p1
65 65 applying p0
66 66 applying p1
67 67 now at: p1
68 68 $ hg parents -qr qbase
69 69 1:d7fe2034f71b
70 70 $ hg qpop -aq
71 71 patch queue now empty
72 72
73 73 qpush --exact when at another rev
74 74
75 75 $ hg update 0 -q
76 76 $ hg qpush -e
77 77 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 78 applying p0
79 79 now at: p0
80 80 $ hg parents -qr qbase
81 81 1:d7fe2034f71b
82 82 $ hg qpop -aq
83 83 patch queue now empty
84 84
85 85 $ hg update 0 -q
86 86 $ hg qpush -e p0
87 87 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 88 applying p0
89 89 now at: p0
90 90 $ hg parents -qr qbase
91 91 1:d7fe2034f71b
92 92 $ hg qpop -aq
93 93 patch queue now empty
94 94
95 95 $ hg update 0 -q
96 96 $ hg qpush -e p1
97 97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 98 applying p0
99 99 applying p1
100 100 now at: p1
101 101 $ hg parents -qr qbase
102 102 1:d7fe2034f71b
103 103 $ hg qpop -aq
104 104 patch queue now empty
105 105
106 106 $ hg update 0 -q
107 107 $ hg qpush -ea
108 108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 109 applying p0
110 110 applying p1
111 111 now at: p1
112 112 $ hg parents -qr qbase
113 113 1:d7fe2034f71b
114 114 $ hg qpop -aq
115 115 patch queue now empty
116 116
117 117 qpush --exact while crossing branches
118 118
119 119 $ hg update 2 -q
120 120 $ hg qpush -e
121 121 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
122 122 applying p0
123 123 now at: p0
124 124 $ hg parents -qr qbase
125 125 1:d7fe2034f71b
126 126 $ hg qpop -aq
127 127 patch queue now empty
128 128
129 129 $ hg update 2 -q
130 130 $ hg qpush -e p0
131 131 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
132 132 applying p0
133 133 now at: p0
134 134 $ hg parents -qr qbase
135 135 1:d7fe2034f71b
136 136 $ hg qpop -aq
137 137 patch queue now empty
138 138
139 139 $ hg update 2 -q
140 140 $ hg qpush -e p1
141 141 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
142 142 applying p0
143 143 applying p1
144 144 now at: p1
145 145 $ hg parents -qr qbase
146 146 1:d7fe2034f71b
147 147 $ hg qpop -aq
148 148 patch queue now empty
149 149
150 150 $ hg update 2 -q
151 151 $ hg qpush -ea
152 152 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
153 153 applying p0
154 154 applying p1
155 155 now at: p1
156 156 $ hg parents -qr qbase
157 157 1:d7fe2034f71b
158 158 $ hg qpop -aq
159 159 patch queue now empty
160 160
161 161 qpush --exact --force with changes to an unpatched file
162 162
163 163 $ hg update 1 -q
164 164 $ echo c0 >> f0
165 165 $ hg qpush -e
166 166 abort: local changes found
167 167 [255]
168 168 $ hg qpush -ef
169 169 applying p0
170 170 now at: p0
171 171 $ cat f0
172 172 c0
173 173 $ rm f0
174 174 $ touch f0
175 175 $ hg qpop -aq
176 176 patch queue now empty
177 177
178 178 $ hg update 1 -q
179 179 $ echo c0 >> f0
180 180 $ hg qpush -e p1
181 181 abort: local changes found
182 182 [255]
183 183 $ hg qpush -e p1 -f
184 184 applying p0
185 185 applying p1
186 186 now at: p1
187 187 $ cat f0
188 188 c0
189 189 $ rm f0
190 190 $ touch f0
191 191 $ hg qpop -aq
192 192 patch queue now empty
193 193
194 194 qpush --exact --force with changes to a patched file
195 195
196 196 $ hg update 1 -q
197 197 $ echo cp0-bad >> fp0
198 198 $ hg add fp0
199 199 $ hg qpush -e
200 200 abort: local changes found
201 201 [255]
202 202 $ hg qpush -ef
203 203 applying p0
204 204 file fp0 already exists
205 205 1 out of 1 hunks FAILED -- saving rejects to file fp0.rej
206 206 patch failed, unable to continue (try -v)
207 207 patch failed, rejects left in working dir
208 208 errors during apply, please fix and refresh p0
209 209 [2]
210 210 $ cat fp0
211 211 cp0-bad
212 212 $ cat fp0.rej
213 213 --- fp0
214 214 +++ fp0
215 215 @@ -0,0 +1,1 @@
216 216 +cp0
217 217 $ hg qpop -aqf
218 218 patch queue now empty
219 219 $ rm fp0
220 220 $ rm fp0.rej
221 221
222 222 $ hg update 1 -q
223 223 $ echo cp1-bad >> fp1
224 224 $ hg add fp1
225 225 $ hg qpush -e p1
226 226 abort: local changes found
227 227 [255]
228 228 $ hg qpush -e p1 -f
229 229 applying p0
230 230 applying p1
231 231 file fp1 already exists
232 232 1 out of 1 hunks FAILED -- saving rejects to file fp1.rej
233 233 patch failed, unable to continue (try -v)
234 234 patch failed, rejects left in working dir
235 235 errors during apply, please fix and refresh p1
236 236 [2]
237 237 $ cat fp1
238 238 cp1-bad
239 239 $ cat fp1.rej
240 240 --- fp1
241 241 +++ fp1
242 242 @@ -0,0 +1,1 @@
243 243 +cp1
244 244 $ hg qpop -aqf
245 245 patch queue now empty
246 $ hg forget fp1
246 247 $ rm fp1
247 248 $ rm fp1.rej
248 249
249 250 qpush --exact when already at a patch
250 251
251 252 $ hg update 1
252 253 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 254 $ hg qpush -e p0
254 255 applying p0
255 256 now at: p0
256 257 $ hg qpush -e p1
257 258 abort: cannot push --exact with applied patches
258 259 [255]
259 260 $ hg qpop -aq
260 261 patch queue now empty
261 262
262 263 qpush --exact --move should fail
263 264
264 265 $ hg qpush -e --move p1
265 266 abort: cannot use --exact and --move together
266 267 [255]
267 268
268 269 qpush --exact a patch without a parent recorded
269 270
270 271 $ hg qpush -q
271 272 now at: p0
272 273 $ grep -v '# Parent' .hg/patches/p0 > p0.new
273 274 $ mv p0.new .hg/patches/p0
274 275 $ hg qpop -aq
275 276 patch queue now empty
276 277 $ hg qpush -e
277 278 abort: p0 does not have a parent recorded
278 279 [255]
279 280 $ hg qpush -e p0
280 281 abort: p0 does not have a parent recorded
281 282 [255]
282 283 $ hg qpush -e p1
283 284 abort: p0 does not have a parent recorded
284 285 [255]
285 286 $ hg qpush -ea
286 287 abort: p0 does not have a parent recorded
287 288 [255]
288 289
289 290 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now