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