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