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