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