##// END OF EJS Templates
merge: handle acceptremove of create+delete early in manifest merge
Mads Kiilerich -
r20639:1df03364 default
parent child Browse files
Show More
@@ -1,942 +1,945 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 import struct
9 9
10 10 from node import nullid, nullrev, hex, bin
11 11 from i18n import _
12 12 from mercurial import obsolete
13 13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
14 14 import errno, os, shutil
15 15
16 16 _pack = struct.pack
17 17 _unpack = struct.unpack
18 18
19 19 def _droponode(data):
20 20 # used for compatibility for v1
21 21 bits = data.split("\0")
22 22 bits = bits[:-2] + bits[-1:]
23 23 return "\0".join(bits)
24 24
25 25 class mergestate(object):
26 26 '''track 3-way merge state of individual files
27 27
28 28 it is stored on disk when needed. Two file are used, one with an old
29 29 format, one with a new format. Both contains similar data, but the new
30 30 format can store new kind of field.
31 31
32 32 Current new format is a list of arbitrary record of the form:
33 33
34 34 [type][length][content]
35 35
36 36 Type is a single character, length is a 4 bytes integer, content is an
37 37 arbitrary suites of bytes of length `length`.
38 38
39 39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 40 should abort if they are unknown. lower case record can be safely ignored.
41 41
42 42 Currently known record:
43 43
44 44 L: the node of the "local" part of the merge (hexified version)
45 45 O: the node of the "other" part of the merge (hexified version)
46 46 F: a file to be merged entry
47 47 '''
48 48 statepathv1 = "merge/state"
49 49 statepathv2 = "merge/state2"
50 50 def __init__(self, repo):
51 51 self._repo = repo
52 52 self._dirty = False
53 53 self._read()
54 54 def reset(self, node=None, other=None):
55 55 self._state = {}
56 56 if node:
57 57 self._local = node
58 58 self._other = other
59 59 shutil.rmtree(self._repo.join("merge"), True)
60 60 self._dirty = False
61 61 def _read(self):
62 62 self._state = {}
63 63 records = self._readrecords()
64 64 for rtype, record in records:
65 65 if rtype == 'L':
66 66 self._local = bin(record)
67 67 elif rtype == 'O':
68 68 self._other = bin(record)
69 69 elif rtype == "F":
70 70 bits = record.split("\0")
71 71 self._state[bits[0]] = bits[1:]
72 72 elif not rtype.islower():
73 73 raise util.Abort(_('unsupported merge state record:'
74 74 % rtype))
75 75 self._dirty = False
76 76 def _readrecords(self):
77 77 v1records = self._readrecordsv1()
78 78 v2records = self._readrecordsv2()
79 79 oldv2 = set() # old format version of v2 record
80 80 for rec in v2records:
81 81 if rec[0] == 'L':
82 82 oldv2.add(rec)
83 83 elif rec[0] == 'F':
84 84 # drop the onode data (not contained in v1)
85 85 oldv2.add(('F', _droponode(rec[1])))
86 86 for rec in v1records:
87 87 if rec not in oldv2:
88 88 # v1 file is newer than v2 file, use it
89 89 # we have to infer the "other" changeset of the merge
90 90 # we cannot do better than that with v1 of the format
91 91 mctx = self._repo[None].parents()[-1]
92 92 v1records.append(('O', mctx.hex()))
93 93 # add place holder "other" file node information
94 94 # nobody is using it yet so we do no need to fetch the data
95 95 # if mctx was wrong `mctx[bits[-2]]` may fails.
96 96 for idx, r in enumerate(v1records):
97 97 if r[0] == 'F':
98 98 bits = r[1].split("\0")
99 99 bits.insert(-2, '')
100 100 v1records[idx] = (r[0], "\0".join(bits))
101 101 return v1records
102 102 else:
103 103 return v2records
104 104 def _readrecordsv1(self):
105 105 records = []
106 106 try:
107 107 f = self._repo.opener(self.statepathv1)
108 108 for i, l in enumerate(f):
109 109 if i == 0:
110 110 records.append(('L', l[:-1]))
111 111 else:
112 112 records.append(('F', l[:-1]))
113 113 f.close()
114 114 except IOError, err:
115 115 if err.errno != errno.ENOENT:
116 116 raise
117 117 return records
118 118 def _readrecordsv2(self):
119 119 records = []
120 120 try:
121 121 f = self._repo.opener(self.statepathv2)
122 122 data = f.read()
123 123 off = 0
124 124 end = len(data)
125 125 while off < end:
126 126 rtype = data[off]
127 127 off += 1
128 128 length = _unpack('>I', data[off:(off + 4)])[0]
129 129 off += 4
130 130 record = data[off:(off + length)]
131 131 off += length
132 132 records.append((rtype, record))
133 133 f.close()
134 134 except IOError, err:
135 135 if err.errno != errno.ENOENT:
136 136 raise
137 137 return records
138 138 def commit(self):
139 139 if self._dirty:
140 140 records = []
141 141 records.append(("L", hex(self._local)))
142 142 records.append(("O", hex(self._other)))
143 143 for d, v in self._state.iteritems():
144 144 records.append(("F", "\0".join([d] + v)))
145 145 self._writerecords(records)
146 146 self._dirty = False
147 147 def _writerecords(self, records):
148 148 self._writerecordsv1(records)
149 149 self._writerecordsv2(records)
150 150 def _writerecordsv1(self, records):
151 151 f = self._repo.opener(self.statepathv1, "w")
152 152 irecords = iter(records)
153 153 lrecords = irecords.next()
154 154 assert lrecords[0] == 'L'
155 155 f.write(hex(self._local) + "\n")
156 156 for rtype, data in irecords:
157 157 if rtype == "F":
158 158 f.write("%s\n" % _droponode(data))
159 159 f.close()
160 160 def _writerecordsv2(self, records):
161 161 f = self._repo.opener(self.statepathv2, "w")
162 162 for key, data in records:
163 163 assert len(key) == 1
164 164 format = ">sI%is" % len(data)
165 165 f.write(_pack(format, key, len(data), data))
166 166 f.close()
167 167 def add(self, fcl, fco, fca, fd):
168 168 hash = util.sha1(fcl.path()).hexdigest()
169 169 self._repo.opener.write("merge/" + hash, fcl.data())
170 170 self._state[fd] = ['u', hash, fcl.path(),
171 171 fca.path(), hex(fca.filenode()),
172 172 fco.path(), hex(fco.filenode()),
173 173 fcl.flags()]
174 174 self._dirty = True
175 175 def __contains__(self, dfile):
176 176 return dfile in self._state
177 177 def __getitem__(self, dfile):
178 178 return self._state[dfile][0]
179 179 def __iter__(self):
180 180 l = self._state.keys()
181 181 l.sort()
182 182 for f in l:
183 183 yield f
184 184 def files(self):
185 185 return self._state.keys()
186 186 def mark(self, dfile, state):
187 187 self._state[dfile][0] = state
188 188 self._dirty = True
189 189 def resolve(self, dfile, wctx):
190 190 if self[dfile] == 'r':
191 191 return 0
192 192 stateentry = self._state[dfile]
193 193 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
194 194 octx = self._repo[self._other]
195 195 fcd = wctx[dfile]
196 196 fco = octx[ofile]
197 197 fca = self._repo.filectx(afile, fileid=anode)
198 198 # "premerge" x flags
199 199 flo = fco.flags()
200 200 fla = fca.flags()
201 201 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
202 202 if fca.node() == nullid:
203 203 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
204 204 afile)
205 205 elif flags == fla:
206 206 flags = flo
207 207 # restore local
208 208 f = self._repo.opener("merge/" + hash)
209 209 self._repo.wwrite(dfile, f.read(), flags)
210 210 f.close()
211 211 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
212 212 if r is None:
213 213 # no real conflict
214 214 del self._state[dfile]
215 215 elif not r:
216 216 self.mark(dfile, 'r')
217 217 return r
218 218
219 219 def _checkunknownfile(repo, wctx, mctx, f):
220 220 return (not repo.dirstate._ignore(f)
221 221 and os.path.isfile(repo.wjoin(f))
222 222 and repo.wopener.audit.check(f)
223 223 and repo.dirstate.normalize(f) not in repo.dirstate
224 224 and mctx[f].cmp(wctx[f]))
225 225
226 226 def _checkunknown(repo, wctx, mctx):
227 227 "check for collisions between unknown files and files in mctx"
228 228
229 229 error = False
230 230 for f in mctx:
231 231 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
232 232 error = True
233 233 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
234 234 if error:
235 235 raise util.Abort(_("untracked files in working directory differ "
236 236 "from files in requested revision"))
237 237
238 238 def _forgetremoved(wctx, mctx, branchmerge):
239 239 """
240 240 Forget removed files
241 241
242 242 If we're jumping between revisions (as opposed to merging), and if
243 243 neither the working directory nor the target rev has the file,
244 244 then we need to remove it from the dirstate, to prevent the
245 245 dirstate from listing the file when it is no longer in the
246 246 manifest.
247 247
248 248 If we're merging, and the other revision has removed a file
249 249 that is not present in the working directory, we need to mark it
250 250 as removed.
251 251 """
252 252
253 253 actions = []
254 254 state = branchmerge and 'r' or 'f'
255 255 for f in wctx.deleted():
256 256 if f not in mctx:
257 257 actions.append((f, state, None, "forget deleted"))
258 258
259 259 if not branchmerge:
260 260 for f in wctx.removed():
261 261 if f not in mctx:
262 262 actions.append((f, "f", None, "forget removed"))
263 263
264 264 return actions
265 265
266 266 def _checkcollision(repo, wmf, actions, prompts):
267 267 # build provisional merged manifest up
268 268 pmmf = set(wmf)
269 269
270 270 def addop(f, args):
271 271 pmmf.add(f)
272 272 def removeop(f, args):
273 273 pmmf.discard(f)
274 274 def nop(f, args):
275 275 pass
276 276
277 277 def renameop(f, args):
278 278 f2, fd, flags = args
279 279 if f:
280 280 pmmf.discard(f)
281 281 pmmf.add(fd)
282 282 def mergeop(f, args):
283 283 f2, fd, move = args
284 284 if move:
285 285 pmmf.discard(f)
286 286 pmmf.add(fd)
287 287
288 288 opmap = {
289 289 "a": addop,
290 290 "d": renameop,
291 291 "dr": nop,
292 292 "e": nop,
293 293 "f": addop, # untracked file should be kept in working directory
294 294 "g": addop,
295 295 "m": mergeop,
296 296 "r": removeop,
297 297 "rd": nop,
298 298 }
299 299 for f, m, args, msg in actions:
300 300 op = opmap.get(m)
301 301 assert op, m
302 302 op(f, args)
303 303
304 304 opmap = {
305 305 "cd": addop,
306 306 "dc": addop,
307 307 }
308 308 for f, m in prompts:
309 309 op = opmap.get(m)
310 310 assert op, m
311 311 op(f, None)
312 312
313 313 # check case-folding collision in provisional merged manifest
314 314 foldmap = {}
315 315 for f in sorted(pmmf):
316 316 fold = util.normcase(f)
317 317 if fold in foldmap:
318 318 raise util.Abort(_("case-folding collision between %s and %s")
319 319 % (f, foldmap[fold]))
320 320 foldmap[fold] = f
321 321
322 322 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
323 323 acceptremote=False):
324 324 """
325 325 Merge p1 and p2 with ancestor pa and generate merge action list
326 326
327 327 branchmerge and force are as passed in to update
328 328 partial = function to filter file lists
329 329 acceptremote = accept the incoming changes without prompting
330 330 """
331 331
332 332 overwrite = force and not branchmerge
333 333 actions, copy, movewithdir = [], {}, {}
334 334
335 335 followcopies = False
336 336 if overwrite:
337 337 pa = wctx
338 338 elif pa == p2: # backwards
339 339 pa = wctx.p1()
340 340 elif not branchmerge and not wctx.dirty(missing=True):
341 341 pass
342 342 elif pa and repo.ui.configbool("merge", "followcopies", True):
343 343 followcopies = True
344 344
345 345 # manifests fetched in order are going to be faster, so prime the caches
346 346 [x.manifest() for x in
347 347 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
348 348
349 349 if followcopies:
350 350 ret = copies.mergecopies(repo, wctx, p2, pa)
351 351 copy, movewithdir, diverge, renamedelete = ret
352 352 for of, fl in diverge.iteritems():
353 353 actions.append((of, "dr", (fl,), "divergent renames"))
354 354 for of, fl in renamedelete.iteritems():
355 355 actions.append((of, "rd", (fl,), "rename and delete"))
356 356
357 357 repo.ui.note(_("resolving manifests\n"))
358 358 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
359 359 % (bool(branchmerge), bool(force), bool(partial)))
360 360 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
361 361
362 362 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
363 363 copied = set(copy.values())
364 364 copied.update(movewithdir.values())
365 365
366 366 if '.hgsubstate' in m1:
367 367 # check whether sub state is modified
368 368 for s in sorted(wctx.substate):
369 369 if wctx.sub(s).dirty():
370 370 m1['.hgsubstate'] += "+"
371 371 break
372 372
373 373 aborts, prompts = [], []
374 374 # Compare manifests
375 375 fdiff = dicthelpers.diff(m1, m2)
376 376 flagsdiff = m1.flagsdiff(m2)
377 377 diff12 = dicthelpers.join(fdiff, flagsdiff)
378 378
379 379 for f, (n12, fl12) in diff12.iteritems():
380 380 if n12:
381 381 n1, n2 = n12
382 382 else: # file contents didn't change, but flags did
383 383 n1 = n2 = m1.get(f, None)
384 384 if n1 is None:
385 385 # Since n1 == n2, the file isn't present in m2 either. This
386 386 # means that the file was removed or deleted locally and
387 387 # removed remotely, but that residual entries remain in flags.
388 388 # This can happen in manifests generated by workingctx.
389 389 continue
390 390 if fl12:
391 391 fl1, fl2 = fl12
392 392 else: # flags didn't change, file contents did
393 393 fl1 = fl2 = m1.flags(f)
394 394
395 395 if partial and not partial(f):
396 396 continue
397 397 if n1 and n2:
398 398 fla = ma.flags(f)
399 399 nol = 'l' not in fl1 + fl2 + fla
400 400 a = ma.get(f, nullid)
401 401 if n2 == a and fl2 == fla:
402 402 pass # remote unchanged - keep local
403 403 elif n1 == a and fl1 == fla: # local unchanged - use remote
404 404 if n1 == n2: # optimization: keep local content
405 405 actions.append((f, "e", (fl2,), "update permissions"))
406 406 else:
407 407 actions.append((f, "g", (fl2,), "remote is newer"))
408 408 elif nol and n2 == a: # remote only changed 'x'
409 409 actions.append((f, "e", (fl2,), "update permissions"))
410 410 elif nol and n1 == a: # local only changed 'x'
411 411 actions.append((f, "g", (fl1,), "remote is newer"))
412 412 else: # both changed something
413 413 actions.append((f, "m", (f, f, False), "versions differ"))
414 414 elif f in copied: # files we'll deal with on m2 side
415 415 pass
416 416 elif n1 and f in movewithdir: # directory rename
417 417 f2 = movewithdir[f]
418 418 actions.append((f, "d", (None, f2, fl1),
419 419 "remote renamed directory to " + f2))
420 420 elif n1 and f in copy:
421 421 f2 = copy[f]
422 422 actions.append((f, "m", (f2, f, False),
423 423 "local copied/moved to " + f2))
424 424 elif n1 and f in ma: # clean, a different, no remote
425 425 if n1 != ma[f]:
426 if acceptremote:
427 actions.append((f, "r", None, "remote delete"))
428 else:
426 429 prompts.append((f, "cd")) # prompt changed/deleted
427 430 elif n1[20:] == "a": # added, no remote
428 431 actions.append((f, "f", None, "remote deleted"))
429 432 else:
430 433 actions.append((f, "r", None, "other deleted"))
431 434 elif n2 and f in movewithdir:
432 435 f2 = movewithdir[f]
433 436 actions.append((None, "d", (f, f2, fl2),
434 437 "local renamed directory to " + f2))
435 438 elif n2 and f in copy:
436 439 f2 = copy[f]
437 440 if f2 in m2:
438 441 actions.append((f2, "m", (f, f, False),
439 442 "remote copied to " + f))
440 443 else:
441 444 actions.append((f2, "m", (f, f, True),
442 445 "remote moved to " + f))
443 446 elif n2 and f not in ma:
444 447 # local unknown, remote created: the logic is described by the
445 448 # following table:
446 449 #
447 450 # force branchmerge different | action
448 451 # n * n | get
449 452 # n * y | abort
450 453 # y n * | get
451 454 # y y n | get
452 455 # y y y | merge
453 456 #
454 457 # Checking whether the files are different is expensive, so we
455 458 # don't do that when we can avoid it.
456 459 if force and not branchmerge:
457 460 actions.append((f, "g", (fl2,), "remote created"))
458 461 else:
459 462 different = _checkunknownfile(repo, wctx, p2, f)
460 463 if force and branchmerge and different:
461 464 actions.append((f, "m", (f, f, False),
462 465 "remote differs from untracked local"))
463 466 elif not force and different:
464 467 aborts.append((f, "ud"))
465 468 else:
466 469 actions.append((f, "g", (fl2,), "remote created"))
467 470 elif n2 and n2 != ma[f]:
468 471 different = _checkunknownfile(repo, wctx, p2, f)
469 472 if not force and different:
470 473 aborts.append((f, "ud"))
471 474 else:
472 475 # if different: old untracked f may be overwritten and lost
476 if acceptremote:
477 actions.append((f, "g", (m2.flags(f),),
478 "remote recreating"))
479 else:
473 480 prompts.append((f, "dc")) # prompt deleted/changed
474 481
475 482 for f, m in sorted(aborts):
476 483 if m == "ud":
477 484 repo.ui.warn(_("%s: untracked file differs\n") % f)
478 485 else: assert False, m
479 486 if aborts:
480 487 raise util.Abort(_("untracked files in working directory differ "
481 488 "from files in requested revision"))
482 489
483 490 if not util.checkcase(repo.path):
484 491 # check collision between files only in p2 for clean update
485 492 if (not branchmerge and
486 493 (force or not wctx.dirty(missing=True, branch=False))):
487 494 _checkcollision(repo, m2, [], [])
488 495 else:
489 496 _checkcollision(repo, m1, actions, prompts)
490 497
491 498 for f, m in sorted(prompts):
492 499 if m == "cd":
493 if acceptremote:
494 actions.append((f, "r", None, "remote delete"))
495 elif repo.ui.promptchoice(
500 if repo.ui.promptchoice(
496 501 _("local changed %s which remote deleted\n"
497 502 "use (c)hanged version or (d)elete?"
498 503 "$$ &Changed $$ &Delete") % f, 0):
499 504 actions.append((f, "r", None, "prompt delete"))
500 505 else:
501 506 actions.append((f, "a", None, "prompt keep"))
502 507 elif m == "dc":
503 if acceptremote:
504 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
505 elif repo.ui.promptchoice(
508 if repo.ui.promptchoice(
506 509 _("remote changed %s which local deleted\n"
507 510 "use (c)hanged version or leave (d)eleted?"
508 511 "$$ &Changed $$ &Deleted") % f, 0) == 0:
509 512 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
510 513 else: assert False, m
511 514 return actions
512 515
513 516 def actionkey(a):
514 517 return a[1] in "rf" and -1 or 0, a
515 518
516 519 def getremove(repo, mctx, overwrite, args):
517 520 """apply usually-non-interactive updates to the working directory
518 521
519 522 mctx is the context to be merged into the working copy
520 523
521 524 yields tuples for progress updates
522 525 """
523 526 verbose = repo.ui.verbose
524 527 unlink = util.unlinkpath
525 528 wjoin = repo.wjoin
526 529 fctx = mctx.filectx
527 530 wwrite = repo.wwrite
528 531 audit = repo.wopener.audit
529 532 i = 0
530 533 for arg in args:
531 534 f = arg[0]
532 535 if arg[1] == 'r':
533 536 if verbose:
534 537 repo.ui.note(_("removing %s\n") % f)
535 538 audit(f)
536 539 try:
537 540 unlink(wjoin(f), ignoremissing=True)
538 541 except OSError, inst:
539 542 repo.ui.warn(_("update failed to remove %s: %s!\n") %
540 543 (f, inst.strerror))
541 544 else:
542 545 if verbose:
543 546 repo.ui.note(_("getting %s\n") % f)
544 547 wwrite(f, fctx(f).data(), arg[2][0])
545 548 if i == 100:
546 549 yield i, f
547 550 i = 0
548 551 i += 1
549 552 if i > 0:
550 553 yield i, f
551 554
552 555 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
553 556 """apply the merge action list to the working directory
554 557
555 558 wctx is the working copy context
556 559 mctx is the context to be merged into the working copy
557 560 actx is the context of the common ancestor
558 561
559 562 Return a tuple of counts (updated, merged, removed, unresolved) that
560 563 describes how many files were affected by the update.
561 564 """
562 565
563 566 updated, merged, removed, unresolved = 0, 0, 0, 0
564 567 ms = mergestate(repo)
565 568 ms.reset(wctx.p1().node(), mctx.node())
566 569 moves = []
567 570 actions.sort(key=actionkey)
568 571
569 572 # prescan for merges
570 573 for a in actions:
571 574 f, m, args, msg = a
572 575 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
573 576 if m == "m": # merge
574 577 f2, fd, move = args
575 578 if fd == '.hgsubstate': # merged internally
576 579 continue
577 580 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
578 581 fcl = wctx[f]
579 582 fco = mctx[f2]
580 583 if mctx == actx: # backwards, use working dir parent as ancestor
581 584 if fcl.parents():
582 585 fca = fcl.p1()
583 586 else:
584 587 fca = repo.filectx(f, fileid=nullrev)
585 588 else:
586 589 fca = fcl.ancestor(fco, actx)
587 590 if not fca:
588 591 fca = repo.filectx(f, fileid=nullrev)
589 592 ms.add(fcl, fco, fca, fd)
590 593 if f != fd and move:
591 594 moves.append(f)
592 595
593 596 audit = repo.wopener.audit
594 597
595 598 # remove renamed files after safely stored
596 599 for f in moves:
597 600 if os.path.lexists(repo.wjoin(f)):
598 601 repo.ui.debug("removing %s\n" % f)
599 602 audit(f)
600 603 util.unlinkpath(repo.wjoin(f))
601 604
602 605 numupdates = len(actions)
603 606 workeractions = [a for a in actions if a[1] in 'gr']
604 607 updateactions = [a for a in workeractions if a[1] == 'g']
605 608 updated = len(updateactions)
606 609 removeactions = [a for a in workeractions if a[1] == 'r']
607 610 removed = len(removeactions)
608 611 actions = [a for a in actions if a[1] not in 'gr']
609 612
610 613 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
611 614 if hgsub and hgsub[0] == 'r':
612 615 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
613 616
614 617 z = 0
615 618 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
616 619 removeactions)
617 620 for i, item in prog:
618 621 z += i
619 622 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
620 623 unit=_('files'))
621 624 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
622 625 updateactions)
623 626 for i, item in prog:
624 627 z += i
625 628 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
626 629 unit=_('files'))
627 630
628 631 if hgsub and hgsub[0] == 'g':
629 632 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
630 633
631 634 _updating = _('updating')
632 635 _files = _('files')
633 636 progress = repo.ui.progress
634 637
635 638 for i, a in enumerate(actions):
636 639 f, m, args, msg = a
637 640 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
638 641 if m == "m": # merge
639 642 f2, fd, move = args
640 643 if fd == '.hgsubstate': # subrepo states need updating
641 644 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
642 645 overwrite)
643 646 continue
644 647 audit(fd)
645 648 r = ms.resolve(fd, wctx)
646 649 if r is not None and r > 0:
647 650 unresolved += 1
648 651 else:
649 652 if r is None:
650 653 updated += 1
651 654 else:
652 655 merged += 1
653 656 elif m == "d": # directory rename
654 657 f2, fd, flags = args
655 658 if f:
656 659 repo.ui.note(_("moving %s to %s\n") % (f, fd))
657 660 audit(fd)
658 661 repo.wwrite(fd, wctx.filectx(f).data(), flags)
659 662 util.unlinkpath(repo.wjoin(f))
660 663 if f2:
661 664 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
662 665 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
663 666 updated += 1
664 667 elif m == "dr": # divergent renames
665 668 fl, = args
666 669 repo.ui.warn(_("note: possible conflict - %s was renamed "
667 670 "multiple times to:\n") % f)
668 671 for nf in fl:
669 672 repo.ui.warn(" %s\n" % nf)
670 673 elif m == "rd": # rename and delete
671 674 fl, = args
672 675 repo.ui.warn(_("note: possible conflict - %s was deleted "
673 676 "and renamed to:\n") % f)
674 677 for nf in fl:
675 678 repo.ui.warn(" %s\n" % nf)
676 679 elif m == "e": # exec
677 680 flags, = args
678 681 audit(f)
679 682 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
680 683 updated += 1
681 684 ms.commit()
682 685 progress(_updating, None, total=numupdates, unit=_files)
683 686
684 687 return updated, merged, removed, unresolved
685 688
686 689 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
687 690 acceptremote=False):
688 691 "Calculate the actions needed to merge mctx into tctx"
689 692 actions = []
690 693 actions += manifestmerge(repo, tctx, mctx,
691 694 ancestor,
692 695 branchmerge, force,
693 696 partial, acceptremote)
694 697 if tctx.rev() is None:
695 698 actions += _forgetremoved(tctx, mctx, branchmerge)
696 699 return actions
697 700
698 701 def recordupdates(repo, actions, branchmerge):
699 702 "record merge actions to the dirstate"
700 703
701 704 for a in actions:
702 705 f, m, args, msg = a
703 706 if m == "r": # remove
704 707 if branchmerge:
705 708 repo.dirstate.remove(f)
706 709 else:
707 710 repo.dirstate.drop(f)
708 711 elif m == "a": # re-add
709 712 if not branchmerge:
710 713 repo.dirstate.add(f)
711 714 elif m == "f": # forget
712 715 repo.dirstate.drop(f)
713 716 elif m == "e": # exec change
714 717 repo.dirstate.normallookup(f)
715 718 elif m == "g": # get
716 719 if branchmerge:
717 720 repo.dirstate.otherparent(f)
718 721 else:
719 722 repo.dirstate.normal(f)
720 723 elif m == "m": # merge
721 724 f2, fd, move = args
722 725 if branchmerge:
723 726 # We've done a branch merge, mark this file as merged
724 727 # so that we properly record the merger later
725 728 repo.dirstate.merge(fd)
726 729 if f != f2: # copy/rename
727 730 if move:
728 731 repo.dirstate.remove(f)
729 732 if f != fd:
730 733 repo.dirstate.copy(f, fd)
731 734 else:
732 735 repo.dirstate.copy(f2, fd)
733 736 else:
734 737 # We've update-merged a locally modified file, so
735 738 # we set the dirstate to emulate a normal checkout
736 739 # of that file some time in the past. Thus our
737 740 # merge will appear as a normal local file
738 741 # modification.
739 742 if f2 == fd: # file not locally copied/moved
740 743 repo.dirstate.normallookup(fd)
741 744 if move:
742 745 repo.dirstate.drop(f)
743 746 elif m == "d": # directory rename
744 747 f2, fd, flag = args
745 748 if not f2 and f not in repo.dirstate:
746 749 # untracked file moved
747 750 continue
748 751 if branchmerge:
749 752 repo.dirstate.add(fd)
750 753 if f:
751 754 repo.dirstate.remove(f)
752 755 repo.dirstate.copy(f, fd)
753 756 if f2:
754 757 repo.dirstate.copy(f2, fd)
755 758 else:
756 759 repo.dirstate.normal(fd)
757 760 if f:
758 761 repo.dirstate.drop(f)
759 762
760 763 def update(repo, node, branchmerge, force, partial, ancestor=None,
761 764 mergeancestor=False):
762 765 """
763 766 Perform a merge between the working directory and the given node
764 767
765 768 node = the node to update to, or None if unspecified
766 769 branchmerge = whether to merge between branches
767 770 force = whether to force branch merging or file overwriting
768 771 partial = a function to filter file lists (dirstate not updated)
769 772 mergeancestor = whether it is merging with an ancestor. If true,
770 773 we should accept the incoming changes for any prompts that occur.
771 774 If false, merging with an ancestor (fast-forward) is only allowed
772 775 between different named branches. This flag is used by rebase extension
773 776 as a temporary fix and should be avoided in general.
774 777
775 778 The table below shows all the behaviors of the update command
776 779 given the -c and -C or no options, whether the working directory
777 780 is dirty, whether a revision is specified, and the relationship of
778 781 the parent rev to the target rev (linear, on the same named
779 782 branch, or on another named branch).
780 783
781 784 This logic is tested by test-update-branches.t.
782 785
783 786 -c -C dirty rev | linear same cross
784 787 n n n n | ok (1) x
785 788 n n n y | ok ok ok
786 789 n n y n | merge (2) (2)
787 790 n n y y | merge (3) (3)
788 791 n y * * | --- discard ---
789 792 y n y * | --- (4) ---
790 793 y n n * | --- ok ---
791 794 y y * * | --- (5) ---
792 795
793 796 x = can't happen
794 797 * = don't-care
795 798 1 = abort: not a linear update (merge or update --check to force update)
796 799 2 = abort: uncommitted changes (commit and merge, or update --clean to
797 800 discard changes)
798 801 3 = abort: uncommitted changes (commit or update --clean to discard changes)
799 802 4 = abort: uncommitted changes (checked in commands.py)
800 803 5 = incompatible options (checked in commands.py)
801 804
802 805 Return the same tuple as applyupdates().
803 806 """
804 807
805 808 onode = node
806 809 wlock = repo.wlock()
807 810 try:
808 811 wc = repo[None]
809 812 pl = wc.parents()
810 813 p1 = pl[0]
811 814 pa = None
812 815 if ancestor:
813 816 pa = repo[ancestor]
814 817
815 818 if node is None:
816 819 # Here is where we should consider bookmarks, divergent bookmarks,
817 820 # foreground changesets (successors), and tip of current branch;
818 821 # but currently we are only checking the branch tips.
819 822 try:
820 823 node = repo.branchtip(wc.branch())
821 824 except error.RepoLookupError:
822 825 if wc.branch() == "default": # no default branch!
823 826 node = repo.lookup("tip") # update to tip
824 827 else:
825 828 raise util.Abort(_("branch %s not found") % wc.branch())
826 829
827 830 if p1.obsolete() and not p1.children():
828 831 # allow updating to successors
829 832 successors = obsolete.successorssets(repo, p1.node())
830 833
831 834 # behavior of certain cases is as follows,
832 835 #
833 836 # divergent changesets: update to highest rev, similar to what
834 837 # is currently done when there are more than one head
835 838 # (i.e. 'tip')
836 839 #
837 840 # replaced changesets: same as divergent except we know there
838 841 # is no conflict
839 842 #
840 843 # pruned changeset: no update is done; though, we could
841 844 # consider updating to the first non-obsolete parent,
842 845 # similar to what is current done for 'hg prune'
843 846
844 847 if successors:
845 848 # flatten the list here handles both divergent (len > 1)
846 849 # and the usual case (len = 1)
847 850 successors = [n for sub in successors for n in sub]
848 851
849 852 # get the max revision for the given successors set,
850 853 # i.e. the 'tip' of a set
851 854 node = repo.revs("max(%ln)", successors)[0]
852 855 pa = p1
853 856
854 857 overwrite = force and not branchmerge
855 858
856 859 p2 = repo[node]
857 860 if pa is None:
858 861 pa = p1.ancestor(p2)
859 862
860 863 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
861 864
862 865 ### check phase
863 866 if not overwrite and len(pl) > 1:
864 867 raise util.Abort(_("outstanding uncommitted merges"))
865 868 if branchmerge:
866 869 if pa == p2:
867 870 raise util.Abort(_("merging with a working directory ancestor"
868 871 " has no effect"))
869 872 elif pa == p1:
870 873 if not mergeancestor and p1.branch() == p2.branch():
871 874 raise util.Abort(_("nothing to merge"),
872 875 hint=_("use 'hg update' "
873 876 "or check 'hg heads'"))
874 877 if not force and (wc.files() or wc.deleted()):
875 878 raise util.Abort(_("uncommitted changes"),
876 879 hint=_("use 'hg status' to list changes"))
877 880 for s in sorted(wc.substate):
878 881 if wc.sub(s).dirty():
879 882 raise util.Abort(_("uncommitted changes in "
880 883 "subrepository '%s'") % s)
881 884
882 885 elif not overwrite:
883 886 if p1 == p2: # no-op update
884 887 # call the hooks and exit early
885 888 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
886 889 repo.hook('update', parent1=xp2, parent2='', error=0)
887 890 return 0, 0, 0, 0
888 891
889 892 if pa not in (p1, p2): # nonlinear
890 893 dirty = wc.dirty(missing=True)
891 894 if dirty or onode is None:
892 895 # Branching is a bit strange to ensure we do the minimal
893 896 # amount of call to obsolete.background.
894 897 foreground = obsolete.foreground(repo, [p1.node()])
895 898 # note: the <node> variable contains a random identifier
896 899 if repo[node].node() in foreground:
897 900 pa = p1 # allow updating to successors
898 901 elif dirty:
899 902 msg = _("uncommitted changes")
900 903 if onode is None:
901 904 hint = _("commit and merge, or update --clean to"
902 905 " discard changes")
903 906 else:
904 907 hint = _("commit or update --clean to discard"
905 908 " changes")
906 909 raise util.Abort(msg, hint=hint)
907 910 else: # node is none
908 911 msg = _("not a linear update")
909 912 hint = _("merge or update --check to force update")
910 913 raise util.Abort(msg, hint=hint)
911 914 else:
912 915 # Allow jumping branches if clean and specific rev given
913 916 pa = p1
914 917
915 918 ### calculate phase
916 919 actions = calculateupdates(repo, wc, p2, pa,
917 920 branchmerge, force, partial, mergeancestor)
918 921
919 922 ### apply phase
920 923 if not branchmerge: # just jump to the new rev
921 924 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
922 925 if not partial:
923 926 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
924 927 # note that we're in the middle of an update
925 928 repo.vfs.write('updatestate', p2.hex())
926 929
927 930 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
928 931
929 932 if not partial:
930 933 repo.setparents(fp1, fp2)
931 934 recordupdates(repo, actions, branchmerge)
932 935 # update completed, clear state
933 936 util.unlink(repo.join('updatestate'))
934 937
935 938 if not branchmerge:
936 939 repo.dirstate.setbranch(p2.branch())
937 940 finally:
938 941 wlock.release()
939 942
940 943 if not partial:
941 944 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
942 945 return stats
General Comments 0
You need to be logged in to leave comments. Login now