##// END OF EJS Templates
merge: don't ignore conflicting file in remote renamed directory...
Martin von Zweigbergk -
r23475:67f1d688 default
parent child Browse files
Show More
@@ -1,1158 +1,1162 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 if f2 in m2:
450 actions['m'].append((f2, (f, f2, None, True, pa.node()),
451 "remote directory rename, both created"))
452 else:
449 453 actions['dm'].append((f2, (f, fl1),
450 454 "remote directory rename - move from " + f))
451 455 elif f in copy:
452 456 f2 = copy[f]
453 457 actions['m'].append((f, (f, f2, f2, False, pa.node()),
454 458 "local copied/moved from " + f2))
455 459 elif f in ma: # clean, a different, no remote
456 460 if n1 != ma[f]:
457 461 if acceptremote:
458 462 actions['r'].append((f, None, "remote delete"))
459 463 else:
460 464 actions['cd'].append((f, None,
461 465 "prompt changed/deleted"))
462 466 elif n1[20:] == 'a':
463 467 # This extra 'a' is added by working copy manifest to mark
464 468 # the file as locally added. We should forget it instead of
465 469 # deleting it.
466 470 actions['f'].append((f, None, "remote deleted"))
467 471 else:
468 472 actions['r'].append((f, None, "other deleted"))
469 473 elif n2: # file exists only on remote side
470 474 if f in copied:
471 475 pass # we'll deal with it on m1 side
472 476 elif f in movewithdir:
473 477 f2 = movewithdir[f]
474 478 actions['dg'].append((f2, (f, fl2),
475 479 "local directory rename - get from " + f))
476 480 elif f in copy:
477 481 f2 = copy[f]
478 482 if f2 in m2:
479 483 actions['m'].append((f, (f2, f, f2, False, pa.node()),
480 484 "remote copied from " + f2))
481 485 else:
482 486 actions['m'].append((f, (f2, f, f2, True, pa.node()),
483 487 "remote moved from " + f2))
484 488 elif f not in ma:
485 489 # local unknown, remote created: the logic is described by the
486 490 # following table:
487 491 #
488 492 # force branchmerge different | action
489 493 # n * n | get
490 494 # n * y | abort
491 495 # y n * | get
492 496 # y y n | get
493 497 # y y y | merge
494 498 #
495 499 # Checking whether the files are different is expensive, so we
496 500 # don't do that when we can avoid it.
497 501 if force and not branchmerge:
498 502 actions['g'].append((f, (fl2,), "remote created"))
499 503 else:
500 504 different = _checkunknownfile(repo, wctx, p2, f)
501 505 if force and branchmerge and different:
502 506 actions['m'].append((f, (f, f, None, False, pa.node()),
503 507 "remote differs from untracked local"))
504 508 elif not force and different:
505 509 aborts.append((f, 'ud'))
506 510 else:
507 511 actions['g'].append((f, (fl2,), "remote created"))
508 512 elif n2 != ma[f]:
509 513 different = _checkunknownfile(repo, wctx, p2, f)
510 514 if not force and different:
511 515 aborts.append((f, 'ud'))
512 516 else:
513 517 if acceptremote:
514 518 actions['g'].append((f, (fl2,), "remote recreating"))
515 519 else:
516 520 actions['dc'].append((f, (fl2,),
517 521 "prompt deleted/changed"))
518 522
519 523 for f, m in sorted(aborts):
520 524 if m == 'ud':
521 525 repo.ui.warn(_("%s: untracked file differs\n") % f)
522 526 else: assert False, m
523 527 if aborts:
524 528 raise util.Abort(_("untracked files in working directory differ "
525 529 "from files in requested revision"))
526 530
527 531 if not util.checkcase(repo.path):
528 532 # check collision between files only in p2 for clean update
529 533 if (not branchmerge and
530 534 (force or not wctx.dirty(missing=True, branch=False))):
531 535 _checkcollision(repo, m2, None)
532 536 else:
533 537 _checkcollision(repo, m1, actions)
534 538
535 539 return actions
536 540
537 541 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
538 542 acceptremote, followcopies):
539 543 "Calculate the actions needed to merge mctx into wctx using ancestors"
540 544
541 545 if len(ancestors) == 1: # default
542 546 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
543 547 branchmerge, force,
544 548 partial, acceptremote, followcopies)
545 549
546 550 else: # only when merge.preferancestor=* - the default
547 551 repo.ui.note(
548 552 _("note: merging %s and %s using bids from ancestors %s\n") %
549 553 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
550 554
551 555 # Call for bids
552 556 fbids = {} # mapping filename to bids (action method to list af actions)
553 557 for ancestor in ancestors:
554 558 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
555 559 actions = manifestmerge(repo, wctx, mctx, ancestor,
556 560 branchmerge, force,
557 561 partial, acceptremote, followcopies)
558 562 for m, l in sorted(actions.items()):
559 563 for a in l:
560 564 f, args, msg = a
561 565 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
562 566 if f in fbids:
563 567 d = fbids[f]
564 568 if m in d:
565 569 d[m].append(a)
566 570 else:
567 571 d[m] = [a]
568 572 else:
569 573 fbids[f] = {m: [a]}
570 574
571 575 # Pick the best bid for each file
572 576 repo.ui.note(_('\nauction for merging merge bids\n'))
573 577 actions = dict((m, []) for m in actions.keys())
574 578 for f, bids in sorted(fbids.items()):
575 579 # bids is a mapping from action method to list af actions
576 580 # Consensus?
577 581 if len(bids) == 1: # all bids are the same kind of method
578 582 m, l = bids.items()[0]
579 583 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
580 584 repo.ui.note(" %s: consensus for %s\n" % (f, m))
581 585 actions[m].append(l[0])
582 586 continue
583 587 # If keep is an option, just do it.
584 588 if 'k' in bids:
585 589 repo.ui.note(" %s: picking 'keep' action\n" % f)
586 590 actions['k'].append(bids['k'][0])
587 591 continue
588 592 # If there are gets and they all agree [how could they not?], do it.
589 593 if 'g' in bids:
590 594 ga0 = bids['g'][0]
591 595 if util.all(a == ga0 for a in bids['g'][1:]):
592 596 repo.ui.note(" %s: picking 'get' action\n" % f)
593 597 actions['g'].append(ga0)
594 598 continue
595 599 # TODO: Consider other simple actions such as mode changes
596 600 # Handle inefficient democrazy.
597 601 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
598 602 for m, l in sorted(bids.items()):
599 603 for _f, args, msg in l:
600 604 repo.ui.note(' %s -> %s\n' % (msg, m))
601 605 # Pick random action. TODO: Instead, prompt user when resolving
602 606 m, l = bids.items()[0]
603 607 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
604 608 (f, m))
605 609 actions[m].append(l[0])
606 610 continue
607 611 repo.ui.note(_('end of auction\n\n'))
608 612
609 613 # Prompt and create actions. TODO: Move this towards resolve phase.
610 614 for f, args, msg in sorted(actions['cd']):
611 615 if f in ancestors[0] and not wctx[f].cmp(ancestors[0][f]):
612 616 # local did change but ended up with same content
613 617 actions['r'].append((f, None, "prompt same"))
614 618 elif repo.ui.promptchoice(
615 619 _("local changed %s which remote deleted\n"
616 620 "use (c)hanged version or (d)elete?"
617 621 "$$ &Changed $$ &Delete") % f, 0):
618 622 actions['r'].append((f, None, "prompt delete"))
619 623 else:
620 624 actions['a'].append((f, None, "prompt keep"))
621 625 del actions['cd'][:]
622 626
623 627 for f, args, msg in sorted(actions['dc']):
624 628 flags, = args
625 629 if f in ancestors[0] and not mctx[f].cmp(ancestors[0][f]):
626 630 # remote did change but ended up with same content
627 631 pass # don't get = keep local deleted
628 632 elif repo.ui.promptchoice(
629 633 _("remote changed %s which local deleted\n"
630 634 "use (c)hanged version or leave (d)eleted?"
631 635 "$$ &Changed $$ &Deleted") % f, 0) == 0:
632 636 actions['g'].append((f, (flags,), "prompt recreating"))
633 637 del actions['dc'][:]
634 638
635 639 if wctx.rev() is None:
636 640 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
637 641 actions['r'].extend(ractions)
638 642 actions['f'].extend(factions)
639 643
640 644 return actions
641 645
642 646 def batchremove(repo, actions):
643 647 """apply removes to the working directory
644 648
645 649 yields tuples for progress updates
646 650 """
647 651 verbose = repo.ui.verbose
648 652 unlink = util.unlinkpath
649 653 wjoin = repo.wjoin
650 654 audit = repo.wopener.audit
651 655 i = 0
652 656 for f, args, msg in actions:
653 657 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
654 658 if verbose:
655 659 repo.ui.note(_("removing %s\n") % f)
656 660 audit(f)
657 661 try:
658 662 unlink(wjoin(f), ignoremissing=True)
659 663 except OSError, inst:
660 664 repo.ui.warn(_("update failed to remove %s: %s!\n") %
661 665 (f, inst.strerror))
662 666 if i == 100:
663 667 yield i, f
664 668 i = 0
665 669 i += 1
666 670 if i > 0:
667 671 yield i, f
668 672
669 673 def batchget(repo, mctx, actions):
670 674 """apply gets to the working directory
671 675
672 676 mctx is the context to get from
673 677
674 678 yields tuples for progress updates
675 679 """
676 680 verbose = repo.ui.verbose
677 681 fctx = mctx.filectx
678 682 wwrite = repo.wwrite
679 683 i = 0
680 684 for f, args, msg in actions:
681 685 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
682 686 if verbose:
683 687 repo.ui.note(_("getting %s\n") % f)
684 688 wwrite(f, fctx(f).data(), args[0])
685 689 if i == 100:
686 690 yield i, f
687 691 i = 0
688 692 i += 1
689 693 if i > 0:
690 694 yield i, f
691 695
692 696 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
693 697 """apply the merge action list to the working directory
694 698
695 699 wctx is the working copy context
696 700 mctx is the context to be merged into the working copy
697 701
698 702 Return a tuple of counts (updated, merged, removed, unresolved) that
699 703 describes how many files were affected by the update.
700 704 """
701 705
702 706 updated, merged, removed, unresolved = 0, 0, 0, 0
703 707 ms = mergestate(repo)
704 708 ms.reset(wctx.p1().node(), mctx.node())
705 709 moves = []
706 710 for m, l in actions.items():
707 711 l.sort()
708 712
709 713 # prescan for merges
710 714 for f, args, msg in actions['m']:
711 715 f1, f2, fa, move, anc = args
712 716 if f == '.hgsubstate': # merged internally
713 717 continue
714 718 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
715 719 fcl = wctx[f1]
716 720 fco = mctx[f2]
717 721 actx = repo[anc]
718 722 if fa in actx:
719 723 fca = actx[fa]
720 724 else:
721 725 fca = repo.filectx(f1, fileid=nullrev)
722 726 ms.add(fcl, fco, fca, f)
723 727 if f1 != f and move:
724 728 moves.append(f1)
725 729
726 730 audit = repo.wopener.audit
727 731 _updating = _('updating')
728 732 _files = _('files')
729 733 progress = repo.ui.progress
730 734
731 735 # remove renamed files after safely stored
732 736 for f in moves:
733 737 if os.path.lexists(repo.wjoin(f)):
734 738 repo.ui.debug("removing %s\n" % f)
735 739 audit(f)
736 740 util.unlinkpath(repo.wjoin(f))
737 741
738 742 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
739 743
740 744 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
741 745 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
742 746
743 747 # remove in parallel (must come first)
744 748 z = 0
745 749 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
746 750 for i, item in prog:
747 751 z += i
748 752 progress(_updating, z, item=item, total=numupdates, unit=_files)
749 753 removed = len(actions['r'])
750 754
751 755 # get in parallel
752 756 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
753 757 for i, item in prog:
754 758 z += i
755 759 progress(_updating, z, item=item, total=numupdates, unit=_files)
756 760 updated = len(actions['g'])
757 761
758 762 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
759 763 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
760 764
761 765 # forget (manifest only, just log it) (must come first)
762 766 for f, args, msg in actions['f']:
763 767 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
764 768 z += 1
765 769 progress(_updating, z, item=f, total=numupdates, unit=_files)
766 770
767 771 # re-add (manifest only, just log it)
768 772 for f, args, msg in actions['a']:
769 773 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
770 774 z += 1
771 775 progress(_updating, z, item=f, total=numupdates, unit=_files)
772 776
773 777 # keep (noop, just log it)
774 778 for f, args, msg in actions['k']:
775 779 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
776 780 # no progress
777 781
778 782 # merge
779 783 for f, args, msg in actions['m']:
780 784 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
781 785 z += 1
782 786 progress(_updating, z, item=f, total=numupdates, unit=_files)
783 787 if f == '.hgsubstate': # subrepo states need updating
784 788 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
785 789 overwrite)
786 790 continue
787 791 audit(f)
788 792 r = ms.resolve(f, wctx, labels=labels)
789 793 if r is not None and r > 0:
790 794 unresolved += 1
791 795 else:
792 796 if r is None:
793 797 updated += 1
794 798 else:
795 799 merged += 1
796 800
797 801 # directory rename, move local
798 802 for f, args, msg in actions['dm']:
799 803 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
800 804 z += 1
801 805 progress(_updating, z, item=f, total=numupdates, unit=_files)
802 806 f0, flags = args
803 807 repo.ui.note(_("moving %s to %s\n") % (f0, f))
804 808 audit(f)
805 809 repo.wwrite(f, wctx.filectx(f0).data(), flags)
806 810 util.unlinkpath(repo.wjoin(f0))
807 811 updated += 1
808 812
809 813 # local directory rename, get
810 814 for f, args, msg in actions['dg']:
811 815 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
812 816 z += 1
813 817 progress(_updating, z, item=f, total=numupdates, unit=_files)
814 818 f0, flags = args
815 819 repo.ui.note(_("getting %s to %s\n") % (f0, f))
816 820 repo.wwrite(f, mctx.filectx(f0).data(), flags)
817 821 updated += 1
818 822
819 823 # divergent renames
820 824 for f, args, msg in actions['dr']:
821 825 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
822 826 z += 1
823 827 progress(_updating, z, item=f, total=numupdates, unit=_files)
824 828 fl, = args
825 829 repo.ui.warn(_("note: possible conflict - %s was renamed "
826 830 "multiple times to:\n") % f)
827 831 for nf in fl:
828 832 repo.ui.warn(" %s\n" % nf)
829 833
830 834 # rename and delete
831 835 for f, args, msg in actions['rd']:
832 836 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
833 837 z += 1
834 838 progress(_updating, z, item=f, total=numupdates, unit=_files)
835 839 fl, = args
836 840 repo.ui.warn(_("note: possible conflict - %s was deleted "
837 841 "and renamed to:\n") % f)
838 842 for nf in fl:
839 843 repo.ui.warn(" %s\n" % nf)
840 844
841 845 # exec
842 846 for f, args, msg in actions['e']:
843 847 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
844 848 z += 1
845 849 progress(_updating, z, item=f, total=numupdates, unit=_files)
846 850 flags, = args
847 851 audit(f)
848 852 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
849 853 updated += 1
850 854
851 855 ms.commit()
852 856 progress(_updating, None, total=numupdates, unit=_files)
853 857
854 858 return updated, merged, removed, unresolved
855 859
856 860 def recordupdates(repo, actions, branchmerge):
857 861 "record merge actions to the dirstate"
858 862 # remove (must come first)
859 863 for f, args, msg in actions['r']:
860 864 if branchmerge:
861 865 repo.dirstate.remove(f)
862 866 else:
863 867 repo.dirstate.drop(f)
864 868
865 869 # forget (must come first)
866 870 for f, args, msg in actions['f']:
867 871 repo.dirstate.drop(f)
868 872
869 873 # re-add
870 874 for f, args, msg in actions['a']:
871 875 if not branchmerge:
872 876 repo.dirstate.add(f)
873 877
874 878 # exec change
875 879 for f, args, msg in actions['e']:
876 880 repo.dirstate.normallookup(f)
877 881
878 882 # keep
879 883 for f, args, msg in actions['k']:
880 884 pass
881 885
882 886 # get
883 887 for f, args, msg in actions['g']:
884 888 if branchmerge:
885 889 repo.dirstate.otherparent(f)
886 890 else:
887 891 repo.dirstate.normal(f)
888 892
889 893 # merge
890 894 for f, args, msg in actions['m']:
891 895 f1, f2, fa, move, anc = args
892 896 if branchmerge:
893 897 # We've done a branch merge, mark this file as merged
894 898 # so that we properly record the merger later
895 899 repo.dirstate.merge(f)
896 900 if f1 != f2: # copy/rename
897 901 if move:
898 902 repo.dirstate.remove(f1)
899 903 if f1 != f:
900 904 repo.dirstate.copy(f1, f)
901 905 else:
902 906 repo.dirstate.copy(f2, f)
903 907 else:
904 908 # We've update-merged a locally modified file, so
905 909 # we set the dirstate to emulate a normal checkout
906 910 # of that file some time in the past. Thus our
907 911 # merge will appear as a normal local file
908 912 # modification.
909 913 if f2 == f: # file not locally copied/moved
910 914 repo.dirstate.normallookup(f)
911 915 if move:
912 916 repo.dirstate.drop(f1)
913 917
914 918 # directory rename, move local
915 919 for f, args, msg in actions['dm']:
916 920 f0, flag = args
917 921 if branchmerge:
918 922 repo.dirstate.add(f)
919 923 repo.dirstate.remove(f0)
920 924 repo.dirstate.copy(f0, f)
921 925 else:
922 926 repo.dirstate.normal(f)
923 927 repo.dirstate.drop(f0)
924 928
925 929 # directory rename, get
926 930 for f, args, msg in actions['dg']:
927 931 f0, flag = args
928 932 if branchmerge:
929 933 repo.dirstate.add(f)
930 934 repo.dirstate.copy(f0, f)
931 935 else:
932 936 repo.dirstate.normal(f)
933 937
934 938 def update(repo, node, branchmerge, force, partial, ancestor=None,
935 939 mergeancestor=False, labels=None):
936 940 """
937 941 Perform a merge between the working directory and the given node
938 942
939 943 node = the node to update to, or None if unspecified
940 944 branchmerge = whether to merge between branches
941 945 force = whether to force branch merging or file overwriting
942 946 partial = a function to filter file lists (dirstate not updated)
943 947 mergeancestor = whether it is merging with an ancestor. If true,
944 948 we should accept the incoming changes for any prompts that occur.
945 949 If false, merging with an ancestor (fast-forward) is only allowed
946 950 between different named branches. This flag is used by rebase extension
947 951 as a temporary fix and should be avoided in general.
948 952
949 953 The table below shows all the behaviors of the update command
950 954 given the -c and -C or no options, whether the working directory
951 955 is dirty, whether a revision is specified, and the relationship of
952 956 the parent rev to the target rev (linear, on the same named
953 957 branch, or on another named branch).
954 958
955 959 This logic is tested by test-update-branches.t.
956 960
957 961 -c -C dirty rev | linear same cross
958 962 n n n n | ok (1) x
959 963 n n n y | ok ok ok
960 964 n n y n | merge (2) (2)
961 965 n n y y | merge (3) (3)
962 966 n y * * | --- discard ---
963 967 y n y * | --- (4) ---
964 968 y n n * | --- ok ---
965 969 y y * * | --- (5) ---
966 970
967 971 x = can't happen
968 972 * = don't-care
969 973 1 = abort: not a linear update (merge or update --check to force update)
970 974 2 = abort: uncommitted changes (commit and merge, or update --clean to
971 975 discard changes)
972 976 3 = abort: uncommitted changes (commit or update --clean to discard changes)
973 977 4 = abort: uncommitted changes (checked in commands.py)
974 978 5 = incompatible options (checked in commands.py)
975 979
976 980 Return the same tuple as applyupdates().
977 981 """
978 982
979 983 onode = node
980 984 wlock = repo.wlock()
981 985 try:
982 986 wc = repo[None]
983 987 pl = wc.parents()
984 988 p1 = pl[0]
985 989 pas = [None]
986 990 if ancestor is not None:
987 991 pas = [repo[ancestor]]
988 992
989 993 if node is None:
990 994 # Here is where we should consider bookmarks, divergent bookmarks,
991 995 # foreground changesets (successors), and tip of current branch;
992 996 # but currently we are only checking the branch tips.
993 997 try:
994 998 node = repo.branchtip(wc.branch())
995 999 except errormod.RepoLookupError:
996 1000 if wc.branch() == 'default': # no default branch!
997 1001 node = repo.lookup('tip') # update to tip
998 1002 else:
999 1003 raise util.Abort(_("branch %s not found") % wc.branch())
1000 1004
1001 1005 if p1.obsolete() and not p1.children():
1002 1006 # allow updating to successors
1003 1007 successors = obsolete.successorssets(repo, p1.node())
1004 1008
1005 1009 # behavior of certain cases is as follows,
1006 1010 #
1007 1011 # divergent changesets: update to highest rev, similar to what
1008 1012 # is currently done when there are more than one head
1009 1013 # (i.e. 'tip')
1010 1014 #
1011 1015 # replaced changesets: same as divergent except we know there
1012 1016 # is no conflict
1013 1017 #
1014 1018 # pruned changeset: no update is done; though, we could
1015 1019 # consider updating to the first non-obsolete parent,
1016 1020 # similar to what is current done for 'hg prune'
1017 1021
1018 1022 if successors:
1019 1023 # flatten the list here handles both divergent (len > 1)
1020 1024 # and the usual case (len = 1)
1021 1025 successors = [n for sub in successors for n in sub]
1022 1026
1023 1027 # get the max revision for the given successors set,
1024 1028 # i.e. the 'tip' of a set
1025 1029 node = repo.revs('max(%ln)', successors).first()
1026 1030 pas = [p1]
1027 1031
1028 1032 overwrite = force and not branchmerge
1029 1033
1030 1034 p2 = repo[node]
1031 1035 if pas[0] is None:
1032 1036 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1033 1037 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1034 1038 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1035 1039 else:
1036 1040 pas = [p1.ancestor(p2, warn=branchmerge)]
1037 1041
1038 1042 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1039 1043
1040 1044 ### check phase
1041 1045 if not overwrite and len(pl) > 1:
1042 1046 raise util.Abort(_("outstanding uncommitted merge"))
1043 1047 if branchmerge:
1044 1048 if pas == [p2]:
1045 1049 raise util.Abort(_("merging with a working directory ancestor"
1046 1050 " has no effect"))
1047 1051 elif pas == [p1]:
1048 1052 if not mergeancestor and p1.branch() == p2.branch():
1049 1053 raise util.Abort(_("nothing to merge"),
1050 1054 hint=_("use 'hg update' "
1051 1055 "or check 'hg heads'"))
1052 1056 if not force and (wc.files() or wc.deleted()):
1053 1057 raise util.Abort(_("uncommitted changes"),
1054 1058 hint=_("use 'hg status' to list changes"))
1055 1059 for s in sorted(wc.substate):
1056 1060 if wc.sub(s).dirty():
1057 1061 raise util.Abort(_("uncommitted changes in "
1058 1062 "subrepository '%s'") % s)
1059 1063
1060 1064 elif not overwrite:
1061 1065 if p1 == p2: # no-op update
1062 1066 # call the hooks and exit early
1063 1067 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1064 1068 repo.hook('update', parent1=xp2, parent2='', error=0)
1065 1069 return 0, 0, 0, 0
1066 1070
1067 1071 if pas not in ([p1], [p2]): # nonlinear
1068 1072 dirty = wc.dirty(missing=True)
1069 1073 if dirty or onode is None:
1070 1074 # Branching is a bit strange to ensure we do the minimal
1071 1075 # amount of call to obsolete.background.
1072 1076 foreground = obsolete.foreground(repo, [p1.node()])
1073 1077 # note: the <node> variable contains a random identifier
1074 1078 if repo[node].node() in foreground:
1075 1079 pas = [p1] # allow updating to successors
1076 1080 elif dirty:
1077 1081 msg = _("uncommitted changes")
1078 1082 if onode is None:
1079 1083 hint = _("commit and merge, or update --clean to"
1080 1084 " discard changes")
1081 1085 else:
1082 1086 hint = _("commit or update --clean to discard"
1083 1087 " changes")
1084 1088 raise util.Abort(msg, hint=hint)
1085 1089 else: # node is none
1086 1090 msg = _("not a linear update")
1087 1091 hint = _("merge or update --check to force update")
1088 1092 raise util.Abort(msg, hint=hint)
1089 1093 else:
1090 1094 # Allow jumping branches if clean and specific rev given
1091 1095 pas = [p1]
1092 1096
1093 1097 followcopies = False
1094 1098 if overwrite:
1095 1099 pas = [wc]
1096 1100 elif pas == [p2]: # backwards
1097 1101 pas = [wc.p1()]
1098 1102 elif not branchmerge and not wc.dirty(missing=True):
1099 1103 pass
1100 1104 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1101 1105 followcopies = True
1102 1106
1103 1107 ### calculate phase
1104 1108 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1105 1109 partial, mergeancestor, followcopies)
1106 1110
1107 1111 ### apply phase
1108 1112 if not branchmerge: # just jump to the new rev
1109 1113 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1110 1114 if not partial:
1111 1115 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1112 1116 # note that we're in the middle of an update
1113 1117 repo.vfs.write('updatestate', p2.hex())
1114 1118
1115 1119 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1116 1120
1117 1121 if not partial:
1118 1122 repo.dirstate.beginparentchange()
1119 1123 repo.setparents(fp1, fp2)
1120 1124 recordupdates(repo, actions, branchmerge)
1121 1125 # update completed, clear state
1122 1126 util.unlink(repo.join('updatestate'))
1123 1127
1124 1128 if not branchmerge:
1125 1129 repo.dirstate.setbranch(p2.branch())
1126 1130 repo.dirstate.endparentchange()
1127 1131 finally:
1128 1132 wlock.release()
1129 1133
1130 1134 if not partial:
1131 1135 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1132 1136 return stats
1133 1137
1134 1138 def graft(repo, ctx, pctx, labels):
1135 1139 """Do a graft-like merge.
1136 1140
1137 1141 This is a merge where the merge ancestor is chosen such that one
1138 1142 or more changesets are grafted onto the current changeset. In
1139 1143 addition to the merge, this fixes up the dirstate to include only
1140 1144 a single parent and tries to duplicate any renames/copies
1141 1145 appropriately.
1142 1146
1143 1147 ctx - changeset to rebase
1144 1148 pctx - merge base, usually ctx.p1()
1145 1149 labels - merge labels eg ['local', 'graft']
1146 1150
1147 1151 """
1148 1152
1149 1153 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1150 1154 labels=labels)
1151 1155 # drop the second merge parent
1152 1156 repo.dirstate.beginparentchange()
1153 1157 repo.setparents(repo['.'].node(), nullid)
1154 1158 repo.dirstate.write()
1155 1159 # fix up dirstate for copies and renames
1156 1160 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1157 1161 repo.dirstate.endparentchange()
1158 1162 return stats
@@ -1,223 +1,230 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 130 BROKEN: the local file is overwritten; it should be merged
131 131
132 132 $ hg co -qC 1
133 133 $ echo target > b/c
134 134 $ hg add b/c
135 135 $ hg commit -qm 'new file in target directory'
136 136 $ hg merge 2
137 137 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 138 (branch merge, don't forget to commit)
139 139 $ hg st -A
140 140 A b/c
141 141 a/c
142 142 ? a/d
143 143 C b/a
144 144 C b/b
145 145 $ cat b/c
146 146 baz
147 147
148 148 Remote directory rename with conflicting file added in remote target directory
149 149 and committed in local source directory.
150 150
151 BROKEN: the remote is ignored; it should be merged
152
153 151 $ hg co -qC 2
154 152 $ rm b/c
155 153 $ hg st -A
156 154 ? a/d
157 155 C a/a
158 156 C a/b
159 157 C a/c
160 158 $ hg merge 5
161 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
162 (branch merge, don't forget to commit)
159 merging a/c and b/c to b/c
160 warning: conflicts during merge.
161 merging b/c incomplete! (edit conflicts, then use 'hg resolve --mark')
162 2 files updated, 0 files merged, 2 files removed, 1 files unresolved
163 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
164 [1]
163 165 $ hg st -A
164 166 M b/a
165 167 M b/b
166 A b/c
168 M b/c
167 169 a/c
168 170 R a/a
169 171 R a/b
170 172 R a/c
171 173 ? a/d
174 ? b/c.orig
172 175 $ cat b/c
176 <<<<<<< local: ce36d17b18fb - test: 2 add a/c
173 177 baz
178 =======
179 target
180 >>>>>>> other: f1c50ca4f127 - test: new file in target directory
174 181
175 182 Second scenario with two repos:
176 183
177 184 $ cd ..
178 185 $ hg init r1
179 186 $ cd r1
180 187 $ mkdir a
181 188 $ echo foo > a/f
182 189 $ hg add a
183 190 adding a/f (glob)
184 191 $ hg ci -m "a/f == foo"
185 192 $ cd ..
186 193
187 194 $ hg clone r1 r2
188 195 updating to branch default
189 196 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 197 $ cd r2
191 198 $ hg mv a b
192 199 moving a/f to b/f (glob)
193 200 $ echo foo1 > b/f
194 201 $ hg ci -m" a -> b, b/f == foo1"
195 202 $ cd ..
196 203
197 204 $ cd r1
198 205 $ mkdir a/aa
199 206 $ echo bar > a/aa/g
200 207 $ hg add a/aa
201 208 adding a/aa/g (glob)
202 209 $ hg ci -m "a/aa/g"
203 210 $ hg pull ../r2
204 211 pulling from ../r2
205 212 searching for changes
206 213 adding changesets
207 214 adding manifests
208 215 adding file changes
209 216 added 1 changesets with 1 changes to 1 files (+1 heads)
210 217 (run 'hg heads' to see heads, 'hg merge' to merge)
211 218
212 219 $ hg merge
213 220 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
214 221 (branch merge, don't forget to commit)
215 222
216 223 $ hg st -C
217 224 M b/f
218 225 A b/aa/g
219 226 a/aa/g
220 227 R a/aa/g
221 228 R a/f
222 229
223 230 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now