##// END OF EJS Templates
merge: keep destination filename as key in actions for merge with dir rename...
Mads Kiilerich -
r20944:5b8d5803 default
parent child Browse files
Show More
@@ -1,1013 +1,1020 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 def renameop(f, args):
338 f2, fd, flags = args
339 if f:
340 pmmf.discard(f)
341 pmmf.add(fd)
337 def renamemoveop(f, args):
338 f2, flags = args
339 pmmf.discard(f2)
340 pmmf.add(f)
341 def renamegetop(f, args):
342 f2, flags = args
343 pmmf.add(f)
342 344 def mergeop(f, args):
343 345 f2, fa, fd, move, anc = args
344 346 if move:
345 347 pmmf.discard(f)
346 348 pmmf.add(fd)
347 349
348 350 opmap = {
349 351 "a": addop,
350 "d": renameop,
352 "dm": renamemoveop,
353 "dg": renamegetop,
351 354 "dr": nop,
352 355 "e": nop,
353 356 "f": addop, # untracked file should be kept in working directory
354 357 "g": addop,
355 358 "m": mergeop,
356 359 "r": removeop,
357 360 "rd": nop,
358 361 "cd": addop,
359 362 "dc": addop,
360 363 }
361 364 for f, m, args, msg in actions:
362 365 op = opmap.get(m)
363 366 assert op, m
364 367 op(f, args)
365 368
366 369 # check case-folding collision in provisional merged manifest
367 370 foldmap = {}
368 371 for f in sorted(pmmf):
369 372 fold = util.normcase(f)
370 373 if fold in foldmap:
371 374 raise util.Abort(_("case-folding collision between %s and %s")
372 375 % (f, foldmap[fold]))
373 376 foldmap[fold] = f
374 377
375 378 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
376 379 acceptremote=False):
377 380 """
378 381 Merge p1 and p2 with ancestor pa and generate merge action list
379 382
380 383 branchmerge and force are as passed in to update
381 384 partial = function to filter file lists
382 385 acceptremote = accept the incoming changes without prompting
383 386 """
384 387
385 388 overwrite = force and not branchmerge
386 389 actions, copy, movewithdir = [], {}, {}
387 390
388 391 followcopies = False
389 392 if overwrite:
390 393 pa = wctx
391 394 elif pa == p2: # backwards
392 395 pa = wctx.p1()
393 396 elif not branchmerge and not wctx.dirty(missing=True):
394 397 pass
395 398 elif pa and repo.ui.configbool("merge", "followcopies", True):
396 399 followcopies = True
397 400
398 401 # manifests fetched in order are going to be faster, so prime the caches
399 402 [x.manifest() for x in
400 403 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
401 404
402 405 if followcopies:
403 406 ret = copies.mergecopies(repo, wctx, p2, pa)
404 407 copy, movewithdir, diverge, renamedelete = ret
405 408 for of, fl in diverge.iteritems():
406 409 actions.append((of, "dr", (fl,), "divergent renames"))
407 410 for of, fl in renamedelete.iteritems():
408 411 actions.append((of, "rd", (fl,), "rename and delete"))
409 412
410 413 repo.ui.note(_("resolving manifests\n"))
411 414 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
412 415 % (bool(branchmerge), bool(force), bool(partial)))
413 416 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
414 417
415 418 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
416 419 copied = set(copy.values())
417 420 copied.update(movewithdir.values())
418 421
419 422 if '.hgsubstate' in m1:
420 423 # check whether sub state is modified
421 424 for s in sorted(wctx.substate):
422 425 if wctx.sub(s).dirty():
423 426 m1['.hgsubstate'] += "+"
424 427 break
425 428
426 429 aborts = []
427 430 # Compare manifests
428 431 fdiff = dicthelpers.diff(m1, m2)
429 432 flagsdiff = m1.flagsdiff(m2)
430 433 diff12 = dicthelpers.join(fdiff, flagsdiff)
431 434
432 435 for f, (n12, fl12) in diff12.iteritems():
433 436 if n12:
434 437 n1, n2 = n12
435 438 else: # file contents didn't change, but flags did
436 439 n1 = n2 = m1.get(f, None)
437 440 if n1 is None:
438 441 # Since n1 == n2, the file isn't present in m2 either. This
439 442 # means that the file was removed or deleted locally and
440 443 # removed remotely, but that residual entries remain in flags.
441 444 # This can happen in manifests generated by workingctx.
442 445 continue
443 446 if fl12:
444 447 fl1, fl2 = fl12
445 448 else: # flags didn't change, file contents did
446 449 fl1 = fl2 = m1.flags(f)
447 450
448 451 if partial and not partial(f):
449 452 continue
450 453 if n1 and n2:
451 454 fa = f
452 455 a = ma.get(f, nullid)
453 456 if a == nullid:
454 457 fa = copy.get(f, f)
455 458 # Note: f as default is wrong - we can't really make a 3-way
456 459 # merge without an ancestor file.
457 460 fla = ma.flags(fa)
458 461 nol = 'l' not in fl1 + fl2 + fla
459 462 if n2 == a and fl2 == fla:
460 463 pass # remote unchanged - keep local
461 464 elif n1 == a and fl1 == fla: # local unchanged - use remote
462 465 if n1 == n2: # optimization: keep local content
463 466 actions.append((f, "e", (fl2,), "update permissions"))
464 467 else:
465 468 actions.append((f, "g", (fl2,), "remote is newer"))
466 469 elif nol and n2 == a: # remote only changed 'x'
467 470 actions.append((f, "e", (fl2,), "update permissions"))
468 471 elif nol and n1 == a: # local only changed 'x'
469 472 actions.append((f, "g", (fl1,), "remote is newer"))
470 473 else: # both changed something
471 474 actions.append((f, "m", (f, fa, f, False, pa.node()),
472 475 "versions differ"))
473 476 elif f in copied: # files we'll deal with on m2 side
474 477 pass
475 elif n1 and f in movewithdir: # directory rename
478 elif n1 and f in movewithdir: # directory rename, move local
476 479 f2 = movewithdir[f]
477 actions.append((f, "d", (None, f2, fl1),
478 "remote renamed directory to " + f2))
480 actions.append((f2, "dm", (f, fl1),
481 "remote directory rename - move from " + f))
479 482 elif n1 and f in copy:
480 483 f2 = copy[f]
481 484 actions.append((f, "m", (f2, f2, f, False, pa.node()),
482 485 "local copied/moved to " + f2))
483 486 elif n1 and f in ma: # clean, a different, no remote
484 487 if n1 != ma[f]:
485 488 if acceptremote:
486 489 actions.append((f, "r", None, "remote delete"))
487 490 else:
488 491 actions.append((f, "cd", None, "prompt changed/deleted"))
489 492 elif n1[20:] == "a": # added, no remote
490 493 actions.append((f, "f", None, "remote deleted"))
491 494 else:
492 495 actions.append((f, "r", None, "other deleted"))
493 496 elif n2 and f in movewithdir:
494 497 f2 = movewithdir[f]
495 actions.append((None, "d", (f, f2, fl2),
496 "local renamed directory to " + f2))
498 actions.append((f2, "dg", (f, fl2),
499 "local directory rename - get from " + f))
497 500 elif n2 and f in copy:
498 501 f2 = copy[f]
499 502 if f2 in m2:
500 503 actions.append((f2, "m", (f, f2, f, False, pa.node()),
501 504 "remote copied to " + f))
502 505 else:
503 506 actions.append((f2, "m", (f, f2, f, True, pa.node()),
504 507 "remote moved to " + f))
505 508 elif n2 and f not in ma:
506 509 # local unknown, remote created: the logic is described by the
507 510 # following table:
508 511 #
509 512 # force branchmerge different | action
510 513 # n * n | get
511 514 # n * y | abort
512 515 # y n * | get
513 516 # y y n | get
514 517 # y y y | merge
515 518 #
516 519 # Checking whether the files are different is expensive, so we
517 520 # don't do that when we can avoid it.
518 521 if force and not branchmerge:
519 522 actions.append((f, "g", (fl2,), "remote created"))
520 523 else:
521 524 different = _checkunknownfile(repo, wctx, p2, f)
522 525 if force and branchmerge and different:
523 526 # FIXME: This is wrong - f is not in ma ...
524 527 actions.append((f, "m", (f, f, f, False, pa.node()),
525 528 "remote differs from untracked local"))
526 529 elif not force and different:
527 530 aborts.append((f, "ud"))
528 531 else:
529 532 actions.append((f, "g", (fl2,), "remote created"))
530 533 elif n2 and n2 != ma[f]:
531 534 different = _checkunknownfile(repo, wctx, p2, f)
532 535 if not force and different:
533 536 aborts.append((f, "ud"))
534 537 else:
535 538 # if different: old untracked f may be overwritten and lost
536 539 if acceptremote:
537 540 actions.append((f, "g", (m2.flags(f),),
538 541 "remote recreating"))
539 542 else:
540 543 actions.append((f, "dc", (m2.flags(f),),
541 544 "prompt deleted/changed"))
542 545
543 546 for f, m in sorted(aborts):
544 547 if m == "ud":
545 548 repo.ui.warn(_("%s: untracked file differs\n") % f)
546 549 else: assert False, m
547 550 if aborts:
548 551 raise util.Abort(_("untracked files in working directory differ "
549 552 "from files in requested revision"))
550 553
551 554 if not util.checkcase(repo.path):
552 555 # check collision between files only in p2 for clean update
553 556 if (not branchmerge and
554 557 (force or not wctx.dirty(missing=True, branch=False))):
555 558 _checkcollision(repo, m2, [])
556 559 else:
557 560 _checkcollision(repo, m1, actions)
558 561
559 562 return actions
560 563
561 564 def actionkey(a):
562 565 return a[1] in "rf" and -1 or 0, a
563 566
564 567 def getremove(repo, mctx, overwrite, args):
565 568 """apply usually-non-interactive updates to the working directory
566 569
567 570 mctx is the context to be merged into the working copy
568 571
569 572 yields tuples for progress updates
570 573 """
571 574 verbose = repo.ui.verbose
572 575 unlink = util.unlinkpath
573 576 wjoin = repo.wjoin
574 577 fctx = mctx.filectx
575 578 wwrite = repo.wwrite
576 579 audit = repo.wopener.audit
577 580 i = 0
578 581 for arg in args:
579 582 f = arg[0]
580 583 if arg[1] == 'r':
581 584 if verbose:
582 585 repo.ui.note(_("removing %s\n") % f)
583 586 audit(f)
584 587 try:
585 588 unlink(wjoin(f), ignoremissing=True)
586 589 except OSError, inst:
587 590 repo.ui.warn(_("update failed to remove %s: %s!\n") %
588 591 (f, inst.strerror))
589 592 else:
590 593 if verbose:
591 594 repo.ui.note(_("getting %s\n") % f)
592 595 wwrite(f, fctx(f).data(), arg[2][0])
593 596 if i == 100:
594 597 yield i, f
595 598 i = 0
596 599 i += 1
597 600 if i > 0:
598 601 yield i, f
599 602
600 603 def applyupdates(repo, actions, wctx, mctx, overwrite):
601 604 """apply the merge action list to the working directory
602 605
603 606 wctx is the working copy context
604 607 mctx is the context to be merged into the working copy
605 608
606 609 Return a tuple of counts (updated, merged, removed, unresolved) that
607 610 describes how many files were affected by the update.
608 611 """
609 612
610 613 updated, merged, removed, unresolved = 0, 0, 0, 0
611 614 ms = mergestate(repo)
612 615 ms.reset(wctx.p1().node(), mctx.node())
613 616 moves = []
614 617 actions.sort(key=actionkey)
615 618
616 619 # prescan for merges
617 620 for a in actions:
618 621 f, m, args, msg = a
619 622 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
620 623 if m == "m": # merge
621 624 f2, fa, fd, move, anc = args
622 625 if fd == '.hgsubstate': # merged internally
623 626 continue
624 627 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
625 628 fcl = wctx[f]
626 629 fco = mctx[f2]
627 630 actx = repo[anc]
628 631 if fa in actx:
629 632 fca = actx[fa]
630 633 else:
631 634 fca = repo.filectx(f, fileid=nullrev)
632 635 ms.add(fcl, fco, fca, fd)
633 636 if f != fd and move:
634 637 moves.append(f)
635 638
636 639 audit = repo.wopener.audit
637 640
638 641 # remove renamed files after safely stored
639 642 for f in moves:
640 643 if os.path.lexists(repo.wjoin(f)):
641 644 repo.ui.debug("removing %s\n" % f)
642 645 audit(f)
643 646 util.unlinkpath(repo.wjoin(f))
644 647
645 648 numupdates = len(actions)
646 649 workeractions = [a for a in actions if a[1] in 'gr']
647 650 updateactions = [a for a in workeractions if a[1] == 'g']
648 651 updated = len(updateactions)
649 652 removeactions = [a for a in workeractions if a[1] == 'r']
650 653 removed = len(removeactions)
651 654 actions = [a for a in actions if a[1] not in 'gr']
652 655
653 656 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
654 657 if hgsub and hgsub[0] == 'r':
655 658 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
656 659
657 660 z = 0
658 661 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
659 662 removeactions)
660 663 for i, item in prog:
661 664 z += i
662 665 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
663 666 unit=_('files'))
664 667 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
665 668 updateactions)
666 669 for i, item in prog:
667 670 z += i
668 671 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
669 672 unit=_('files'))
670 673
671 674 if hgsub and hgsub[0] == 'g':
672 675 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
673 676
674 677 _updating = _('updating')
675 678 _files = _('files')
676 679 progress = repo.ui.progress
677 680
678 681 for i, a in enumerate(actions):
679 682 f, m, args, msg = a
680 683 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
681 684 if m == "m": # merge
682 685 f2, fa, fd, move, anc = args
683 686 if fd == '.hgsubstate': # subrepo states need updating
684 687 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
685 688 overwrite)
686 689 continue
687 690 audit(fd)
688 691 r = ms.resolve(fd, wctx)
689 692 if r is not None and r > 0:
690 693 unresolved += 1
691 694 else:
692 695 if r is None:
693 696 updated += 1
694 697 else:
695 698 merged += 1
696 elif m == "d": # directory rename
697 f2, fd, flags = args
698 if f:
699 repo.ui.note(_("moving %s to %s\n") % (f, fd))
700 audit(fd)
701 repo.wwrite(fd, wctx.filectx(f).data(), flags)
702 util.unlinkpath(repo.wjoin(f))
703 if f2:
704 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
705 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
699 elif m == "dm": # directory rename, move local
700 f0, flags = args
701 repo.ui.note(_("moving %s to %s\n") % (f0, f))
702 audit(f)
703 repo.wwrite(f, wctx.filectx(f0).data(), flags)
704 util.unlinkpath(repo.wjoin(f0))
705 updated += 1
706 elif m == "dg": # local directory rename, get
707 f0, flags = args
708 repo.ui.note(_("getting %s to %s\n") % (f0, f))
709 repo.wwrite(f, mctx.filectx(f0).data(), flags)
706 710 updated += 1
707 711 elif m == "dr": # divergent renames
708 712 fl, = args
709 713 repo.ui.warn(_("note: possible conflict - %s was renamed "
710 714 "multiple times to:\n") % f)
711 715 for nf in fl:
712 716 repo.ui.warn(" %s\n" % nf)
713 717 elif m == "rd": # rename and delete
714 718 fl, = args
715 719 repo.ui.warn(_("note: possible conflict - %s was deleted "
716 720 "and renamed to:\n") % f)
717 721 for nf in fl:
718 722 repo.ui.warn(" %s\n" % nf)
719 723 elif m == "e": # exec
720 724 flags, = args
721 725 audit(f)
722 726 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
723 727 updated += 1
724 728 ms.commit()
725 729 progress(_updating, None, total=numupdates, unit=_files)
726 730
727 731 return updated, merged, removed, unresolved
728 732
729 733 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
730 734 acceptremote=False):
731 735 "Calculate the actions needed to merge mctx into tctx"
732 736 actions = []
733 737 actions += manifestmerge(repo, tctx, mctx,
734 738 ancestor,
735 739 branchmerge, force,
736 740 partial, acceptremote)
737 741
738 742 # Filter out prompts.
739 743 newactions, prompts = [], []
740 744 for a in actions:
741 745 if a[1] in ("cd", "dc"):
742 746 prompts.append(a)
743 747 else:
744 748 newactions.append(a)
745 749 # Prompt and create actions. TODO: Move this towards resolve phase.
746 750 for f, m, args, msg in sorted(prompts):
747 751 if m == "cd":
748 752 if repo.ui.promptchoice(
749 753 _("local changed %s which remote deleted\n"
750 754 "use (c)hanged version or (d)elete?"
751 755 "$$ &Changed $$ &Delete") % f, 0):
752 756 newactions.append((f, "r", None, "prompt delete"))
753 757 else:
754 758 newactions.append((f, "a", None, "prompt keep"))
755 759 elif m == "dc":
756 760 flags, = args
757 761 if repo.ui.promptchoice(
758 762 _("remote changed %s which local deleted\n"
759 763 "use (c)hanged version or leave (d)eleted?"
760 764 "$$ &Changed $$ &Deleted") % f, 0) == 0:
761 765 newactions.append((f, "g", (flags,), "prompt recreating"))
762 766 else: assert False, m
763 767
764 768 if tctx.rev() is None:
765 769 newactions += _forgetremoved(tctx, mctx, branchmerge)
766 770
767 771 return newactions
768 772
769 773 def recordupdates(repo, actions, branchmerge):
770 774 "record merge actions to the dirstate"
771 775
772 776 for a in actions:
773 777 f, m, args, msg = a
774 778 if m == "r": # remove
775 779 if branchmerge:
776 780 repo.dirstate.remove(f)
777 781 else:
778 782 repo.dirstate.drop(f)
779 783 elif m == "a": # re-add
780 784 if not branchmerge:
781 785 repo.dirstate.add(f)
782 786 elif m == "f": # forget
783 787 repo.dirstate.drop(f)
784 788 elif m == "e": # exec change
785 789 repo.dirstate.normallookup(f)
786 790 elif m == "g": # get
787 791 if branchmerge:
788 792 repo.dirstate.otherparent(f)
789 793 else:
790 794 repo.dirstate.normal(f)
791 795 elif m == "m": # merge
792 796 f2, fa, fd, move, anc = args
793 797 if branchmerge:
794 798 # We've done a branch merge, mark this file as merged
795 799 # so that we properly record the merger later
796 800 repo.dirstate.merge(fd)
797 801 if f != f2: # copy/rename
798 802 if move:
799 803 repo.dirstate.remove(f)
800 804 if f != fd:
801 805 repo.dirstate.copy(f, fd)
802 806 else:
803 807 repo.dirstate.copy(f2, fd)
804 808 else:
805 809 # We've update-merged a locally modified file, so
806 810 # we set the dirstate to emulate a normal checkout
807 811 # of that file some time in the past. Thus our
808 812 # merge will appear as a normal local file
809 813 # modification.
810 814 if f2 == fd: # file not locally copied/moved
811 815 repo.dirstate.normallookup(fd)
812 816 if move:
813 817 repo.dirstate.drop(f)
814 elif m == "d": # directory rename
815 f2, fd, flag = args
816 if not f2 and f not in repo.dirstate:
818 elif m == "dm": # directory rename, move local
819 f0, flag = args
820 if f0 not in repo.dirstate:
817 821 # untracked file moved
818 822 continue
819 823 if branchmerge:
820 repo.dirstate.add(fd)
821 if f:
822 repo.dirstate.remove(f)
823 repo.dirstate.copy(f, fd)
824 if f2:
825 repo.dirstate.copy(f2, fd)
824 repo.dirstate.add(f)
825 repo.dirstate.remove(f0)
826 repo.dirstate.copy(f0, f)
826 827 else:
827 repo.dirstate.normal(fd)
828 if f:
829 repo.dirstate.drop(f)
828 repo.dirstate.normal(f)
829 repo.dirstate.drop(f0)
830 elif m == "dg": # directory rename, get
831 f0, flag = args
832 if branchmerge:
833 repo.dirstate.add(f)
834 repo.dirstate.copy(f0, f)
835 else:
836 repo.dirstate.normal(f)
830 837
831 838 def update(repo, node, branchmerge, force, partial, ancestor=None,
832 839 mergeancestor=False):
833 840 """
834 841 Perform a merge between the working directory and the given node
835 842
836 843 node = the node to update to, or None if unspecified
837 844 branchmerge = whether to merge between branches
838 845 force = whether to force branch merging or file overwriting
839 846 partial = a function to filter file lists (dirstate not updated)
840 847 mergeancestor = whether it is merging with an ancestor. If true,
841 848 we should accept the incoming changes for any prompts that occur.
842 849 If false, merging with an ancestor (fast-forward) is only allowed
843 850 between different named branches. This flag is used by rebase extension
844 851 as a temporary fix and should be avoided in general.
845 852
846 853 The table below shows all the behaviors of the update command
847 854 given the -c and -C or no options, whether the working directory
848 855 is dirty, whether a revision is specified, and the relationship of
849 856 the parent rev to the target rev (linear, on the same named
850 857 branch, or on another named branch).
851 858
852 859 This logic is tested by test-update-branches.t.
853 860
854 861 -c -C dirty rev | linear same cross
855 862 n n n n | ok (1) x
856 863 n n n y | ok ok ok
857 864 n n y n | merge (2) (2)
858 865 n n y y | merge (3) (3)
859 866 n y * * | --- discard ---
860 867 y n y * | --- (4) ---
861 868 y n n * | --- ok ---
862 869 y y * * | --- (5) ---
863 870
864 871 x = can't happen
865 872 * = don't-care
866 873 1 = abort: not a linear update (merge or update --check to force update)
867 874 2 = abort: uncommitted changes (commit and merge, or update --clean to
868 875 discard changes)
869 876 3 = abort: uncommitted changes (commit or update --clean to discard changes)
870 877 4 = abort: uncommitted changes (checked in commands.py)
871 878 5 = incompatible options (checked in commands.py)
872 879
873 880 Return the same tuple as applyupdates().
874 881 """
875 882
876 883 onode = node
877 884 wlock = repo.wlock()
878 885 try:
879 886 wc = repo[None]
880 887 pl = wc.parents()
881 888 p1 = pl[0]
882 889 pa = None
883 890 if ancestor:
884 891 pa = repo[ancestor]
885 892
886 893 if node is None:
887 894 # Here is where we should consider bookmarks, divergent bookmarks,
888 895 # foreground changesets (successors), and tip of current branch;
889 896 # but currently we are only checking the branch tips.
890 897 try:
891 898 node = repo.branchtip(wc.branch())
892 899 except error.RepoLookupError:
893 900 if wc.branch() == "default": # no default branch!
894 901 node = repo.lookup("tip") # update to tip
895 902 else:
896 903 raise util.Abort(_("branch %s not found") % wc.branch())
897 904
898 905 if p1.obsolete() and not p1.children():
899 906 # allow updating to successors
900 907 successors = obsolete.successorssets(repo, p1.node())
901 908
902 909 # behavior of certain cases is as follows,
903 910 #
904 911 # divergent changesets: update to highest rev, similar to what
905 912 # is currently done when there are more than one head
906 913 # (i.e. 'tip')
907 914 #
908 915 # replaced changesets: same as divergent except we know there
909 916 # is no conflict
910 917 #
911 918 # pruned changeset: no update is done; though, we could
912 919 # consider updating to the first non-obsolete parent,
913 920 # similar to what is current done for 'hg prune'
914 921
915 922 if successors:
916 923 # flatten the list here handles both divergent (len > 1)
917 924 # and the usual case (len = 1)
918 925 successors = [n for sub in successors for n in sub]
919 926
920 927 # get the max revision for the given successors set,
921 928 # i.e. the 'tip' of a set
922 929 node = repo.revs("max(%ln)", successors)[0]
923 930 pa = p1
924 931
925 932 overwrite = force and not branchmerge
926 933
927 934 p2 = repo[node]
928 935 if pa is None:
929 936 pa = p1.ancestor(p2)
930 937
931 938 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
932 939
933 940 ### check phase
934 941 if not overwrite and len(pl) > 1:
935 942 raise util.Abort(_("outstanding uncommitted merges"))
936 943 if branchmerge:
937 944 if pa == p2:
938 945 raise util.Abort(_("merging with a working directory ancestor"
939 946 " has no effect"))
940 947 elif pa == p1:
941 948 if not mergeancestor and p1.branch() == p2.branch():
942 949 raise util.Abort(_("nothing to merge"),
943 950 hint=_("use 'hg update' "
944 951 "or check 'hg heads'"))
945 952 if not force and (wc.files() or wc.deleted()):
946 953 raise util.Abort(_("uncommitted changes"),
947 954 hint=_("use 'hg status' to list changes"))
948 955 for s in sorted(wc.substate):
949 956 if wc.sub(s).dirty():
950 957 raise util.Abort(_("uncommitted changes in "
951 958 "subrepository '%s'") % s)
952 959
953 960 elif not overwrite:
954 961 if p1 == p2: # no-op update
955 962 # call the hooks and exit early
956 963 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
957 964 repo.hook('update', parent1=xp2, parent2='', error=0)
958 965 return 0, 0, 0, 0
959 966
960 967 if pa not in (p1, p2): # nonlinear
961 968 dirty = wc.dirty(missing=True)
962 969 if dirty or onode is None:
963 970 # Branching is a bit strange to ensure we do the minimal
964 971 # amount of call to obsolete.background.
965 972 foreground = obsolete.foreground(repo, [p1.node()])
966 973 # note: the <node> variable contains a random identifier
967 974 if repo[node].node() in foreground:
968 975 pa = p1 # allow updating to successors
969 976 elif dirty:
970 977 msg = _("uncommitted changes")
971 978 if onode is None:
972 979 hint = _("commit and merge, or update --clean to"
973 980 " discard changes")
974 981 else:
975 982 hint = _("commit or update --clean to discard"
976 983 " changes")
977 984 raise util.Abort(msg, hint=hint)
978 985 else: # node is none
979 986 msg = _("not a linear update")
980 987 hint = _("merge or update --check to force update")
981 988 raise util.Abort(msg, hint=hint)
982 989 else:
983 990 # Allow jumping branches if clean and specific rev given
984 991 pa = p1
985 992
986 993 ### calculate phase
987 994 actions = calculateupdates(repo, wc, p2, pa,
988 995 branchmerge, force, partial, mergeancestor)
989 996
990 997 ### apply phase
991 998 if not branchmerge: # just jump to the new rev
992 999 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
993 1000 if not partial:
994 1001 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
995 1002 # note that we're in the middle of an update
996 1003 repo.vfs.write('updatestate', p2.hex())
997 1004
998 1005 stats = applyupdates(repo, actions, wc, p2, overwrite)
999 1006
1000 1007 if not partial:
1001 1008 repo.setparents(fp1, fp2)
1002 1009 recordupdates(repo, actions, branchmerge)
1003 1010 # update completed, clear state
1004 1011 util.unlink(repo.join('updatestate'))
1005 1012
1006 1013 if not branchmerge:
1007 1014 repo.dirstate.setbranch(p2.branch())
1008 1015 finally:
1009 1016 wlock.release()
1010 1017
1011 1018 if not partial:
1012 1019 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1013 1020 return stats
@@ -1,157 +1,157 b''
1 1 $ hg init t
2 2 $ cd t
3 3
4 4 $ mkdir a
5 5 $ echo foo > a/a
6 6 $ echo bar > a/b
7 7 $ hg ci -Am "0"
8 8 adding a/a
9 9 adding a/b
10 10
11 11 $ hg co -C 0
12 12 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 13 $ hg mv a b
14 14 moving a/a to b/a (glob)
15 15 moving a/b to b/b (glob)
16 16 $ hg ci -m "1 mv a/ b/"
17 17
18 18 $ hg co -C 0
19 19 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
20 20 $ echo baz > a/c
21 21 $ echo quux > a/d
22 22 $ hg add a/c
23 23 $ hg ci -m "2 add a/c"
24 24 created new head
25 25
26 26 $ hg merge --debug 1
27 27 searching for copies back to rev 1
28 28 unmatched files in local:
29 29 a/c
30 30 unmatched files in other:
31 31 b/a
32 32 b/b
33 33 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
34 34 src: 'a/a' -> dst: 'b/a'
35 35 src: 'a/b' -> dst: 'b/b'
36 36 checking for directory renames
37 37 discovered dir src: 'a/' -> dst: 'b/'
38 38 pending file src: 'a/c' -> dst: 'b/c'
39 39 resolving manifests
40 40 branchmerge: True, force: False, partial: False
41 41 ancestor: f9b20c0d4c51, local: ce36d17b18fb+, remote: 397f8b00a740
42 42 a/a: other deleted -> r
43 43 a/b: other deleted -> r
44 a/c: remote renamed directory to b/c -> d
45 44 b/a: remote created -> g
46 45 b/b: remote created -> g
46 b/c: remote directory rename - move from a/c -> dm
47 47 removing a/a
48 48 removing a/b
49 49 updating: a/b 2/5 files (40.00%)
50 50 getting b/a
51 51 getting b/b
52 52 updating: b/b 4/5 files (80.00%)
53 updating: a/c 5/5 files (100.00%)
53 updating: b/c 5/5 files (100.00%)
54 54 moving a/c to b/c (glob)
55 55 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
56 56 (branch merge, don't forget to commit)
57 57
58 58 $ echo a/* b/*
59 59 a/d b/a b/b b/c
60 60 $ hg st -C
61 61 M b/a
62 62 M b/b
63 63 A b/c
64 64 a/c
65 65 R a/a
66 66 R a/b
67 67 R a/c
68 68 ? a/d
69 69 $ hg ci -m "3 merge 2+1"
70 70 $ hg debugrename b/c
71 71 b/c renamed from a/c:354ae8da6e890359ef49ade27b68bbc361f3ca88 (glob)
72 72
73 73 $ hg co -C 1
74 74 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
75 75 $ hg merge --debug 2
76 76 searching for copies back to rev 1
77 77 unmatched files in local:
78 78 b/a
79 79 b/b
80 80 unmatched files in other:
81 81 a/c
82 82 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
83 83 src: 'a/a' -> dst: 'b/a'
84 84 src: 'a/b' -> dst: 'b/b'
85 85 checking for directory renames
86 86 discovered dir src: 'a/' -> dst: 'b/'
87 87 pending file src: 'a/c' -> dst: 'b/c'
88 88 resolving manifests
89 89 branchmerge: True, force: False, partial: False
90 90 ancestor: f9b20c0d4c51, local: 397f8b00a740+, remote: ce36d17b18fb
91 None: local renamed directory to b/c -> d
92 updating:None 1/1 files (100.00%)
91 b/c: local directory rename - get from a/c -> dg
92 updating: b/c 1/1 files (100.00%)
93 93 getting a/c to b/c
94 94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 95 (branch merge, don't forget to commit)
96 96
97 97 $ echo a/* b/*
98 98 a/d b/a b/b b/c
99 99 $ hg st -C
100 100 A b/c
101 101 a/c
102 102 ? a/d
103 103 $ hg ci -m "4 merge 1+2"
104 104 created new head
105 105 $ hg debugrename b/c
106 106 b/c renamed from a/c:354ae8da6e890359ef49ade27b68bbc361f3ca88 (glob)
107 107
108 108
109 109 Second scenario with two repos:
110 110
111 111 $ cd ..
112 112 $ hg init r1
113 113 $ cd r1
114 114 $ mkdir a
115 115 $ echo foo > a/f
116 116 $ hg add a
117 117 adding a/f (glob)
118 118 $ hg ci -m "a/f == foo"
119 119 $ cd ..
120 120
121 121 $ hg clone r1 r2
122 122 updating to branch default
123 123 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
124 124 $ cd r2
125 125 $ hg mv a b
126 126 moving a/f to b/f (glob)
127 127 $ echo foo1 > b/f
128 128 $ hg ci -m" a -> b, b/f == foo1"
129 129 $ cd ..
130 130
131 131 $ cd r1
132 132 $ mkdir a/aa
133 133 $ echo bar > a/aa/g
134 134 $ hg add a/aa
135 135 adding a/aa/g (glob)
136 136 $ hg ci -m "a/aa/g"
137 137 $ hg pull ../r2
138 138 pulling from ../r2
139 139 searching for changes
140 140 adding changesets
141 141 adding manifests
142 142 adding file changes
143 143 added 1 changesets with 1 changes to 1 files (+1 heads)
144 144 (run 'hg heads' to see heads, 'hg merge' to merge)
145 145
146 146 $ hg merge
147 147 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
148 148 (branch merge, don't forget to commit)
149 149
150 150 $ hg st -C
151 151 M b/f
152 152 A b/aa/g
153 153 a/aa/g
154 154 R a/aa/g
155 155 R a/f
156 156
157 157 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now