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