##// END OF EJS Templates
update: warn if cwd was deleted...
Stanislau Hlebik -
r30172:90a6c18a default
parent child Browse files
Show More
@@ -1,1676 +1,1694 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 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import os
13 13 import shutil
14 14 import struct
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 bin,
19 19 hex,
20 20 nullhex,
21 21 nullid,
22 22 nullrev,
23 23 )
24 24 from . import (
25 25 copies,
26 26 destutil,
27 27 error,
28 28 filemerge,
29 29 obsolete,
30 30 scmutil,
31 31 subrepo,
32 32 util,
33 33 worker,
34 34 )
35 35
36 36 _pack = struct.pack
37 37 _unpack = struct.unpack
38 38
39 39 def _droponode(data):
40 40 # used for compatibility for v1
41 41 bits = data.split('\0')
42 42 bits = bits[:-2] + bits[-1:]
43 43 return '\0'.join(bits)
44 44
45 45 class mergestate(object):
46 46 '''track 3-way merge state of individual files
47 47
48 48 The merge state is stored on disk when needed. Two files are used: one with
49 49 an old format (version 1), and one with a new format (version 2). Version 2
50 50 stores a superset of the data in version 1, including new kinds of records
51 51 in the future. For more about the new format, see the documentation for
52 52 `_readrecordsv2`.
53 53
54 54 Each record can contain arbitrary content, and has an associated type. This
55 55 `type` should be a letter. If `type` is uppercase, the record is mandatory:
56 56 versions of Mercurial that don't support it should abort. If `type` is
57 57 lowercase, the record can be safely ignored.
58 58
59 59 Currently known records:
60 60
61 61 L: the node of the "local" part of the merge (hexified version)
62 62 O: the node of the "other" part of the merge (hexified version)
63 63 F: a file to be merged entry
64 64 C: a change/delete or delete/change conflict
65 65 D: a file that the external merge driver will merge internally
66 66 (experimental)
67 67 m: the external merge driver defined for this merge plus its run state
68 68 (experimental)
69 69 f: a (filename, dictonary) tuple of optional values for a given file
70 70 X: unsupported mandatory record type (used in tests)
71 71 x: unsupported advisory record type (used in tests)
72 72 l: the labels for the parts of the merge.
73 73
74 74 Merge driver run states (experimental):
75 75 u: driver-resolved files unmarked -- needs to be run next time we're about
76 76 to resolve or commit
77 77 m: driver-resolved files marked -- only needs to be run before commit
78 78 s: success/skipped -- does not need to be run any more
79 79
80 80 '''
81 81 statepathv1 = 'merge/state'
82 82 statepathv2 = 'merge/state2'
83 83
84 84 @staticmethod
85 85 def clean(repo, node=None, other=None, labels=None):
86 86 """Initialize a brand new merge state, removing any existing state on
87 87 disk."""
88 88 ms = mergestate(repo)
89 89 ms.reset(node, other, labels)
90 90 return ms
91 91
92 92 @staticmethod
93 93 def read(repo):
94 94 """Initialize the merge state, reading it from disk."""
95 95 ms = mergestate(repo)
96 96 ms._read()
97 97 return ms
98 98
99 99 def __init__(self, repo):
100 100 """Initialize the merge state.
101 101
102 102 Do not use this directly! Instead call read() or clean()."""
103 103 self._repo = repo
104 104 self._dirty = False
105 105 self._labels = None
106 106
107 107 def reset(self, node=None, other=None, labels=None):
108 108 self._state = {}
109 109 self._stateextras = {}
110 110 self._local = None
111 111 self._other = None
112 112 self._labels = labels
113 113 for var in ('localctx', 'otherctx'):
114 114 if var in vars(self):
115 115 delattr(self, var)
116 116 if node:
117 117 self._local = node
118 118 self._other = other
119 119 self._readmergedriver = None
120 120 if self.mergedriver:
121 121 self._mdstate = 's'
122 122 else:
123 123 self._mdstate = 'u'
124 124 shutil.rmtree(self._repo.join('merge'), True)
125 125 self._results = {}
126 126 self._dirty = False
127 127
128 128 def _read(self):
129 129 """Analyse each record content to restore a serialized state from disk
130 130
131 131 This function process "record" entry produced by the de-serialization
132 132 of on disk file.
133 133 """
134 134 self._state = {}
135 135 self._stateextras = {}
136 136 self._local = None
137 137 self._other = None
138 138 for var in ('localctx', 'otherctx'):
139 139 if var in vars(self):
140 140 delattr(self, var)
141 141 self._readmergedriver = None
142 142 self._mdstate = 's'
143 143 unsupported = set()
144 144 records = self._readrecords()
145 145 for rtype, record in records:
146 146 if rtype == 'L':
147 147 self._local = bin(record)
148 148 elif rtype == 'O':
149 149 self._other = bin(record)
150 150 elif rtype == 'm':
151 151 bits = record.split('\0', 1)
152 152 mdstate = bits[1]
153 153 if len(mdstate) != 1 or mdstate not in 'ums':
154 154 # the merge driver should be idempotent, so just rerun it
155 155 mdstate = 'u'
156 156
157 157 self._readmergedriver = bits[0]
158 158 self._mdstate = mdstate
159 159 elif rtype in 'FDC':
160 160 bits = record.split('\0')
161 161 self._state[bits[0]] = bits[1:]
162 162 elif rtype == 'f':
163 163 filename, rawextras = record.split('\0', 1)
164 164 extraparts = rawextras.split('\0')
165 165 extras = {}
166 166 i = 0
167 167 while i < len(extraparts):
168 168 extras[extraparts[i]] = extraparts[i + 1]
169 169 i += 2
170 170
171 171 self._stateextras[filename] = extras
172 172 elif rtype == 'l':
173 173 labels = record.split('\0', 2)
174 174 self._labels = [l for l in labels if len(l) > 0]
175 175 elif not rtype.islower():
176 176 unsupported.add(rtype)
177 177 self._results = {}
178 178 self._dirty = False
179 179
180 180 if unsupported:
181 181 raise error.UnsupportedMergeRecords(unsupported)
182 182
183 183 def _readrecords(self):
184 184 """Read merge state from disk and return a list of record (TYPE, data)
185 185
186 186 We read data from both v1 and v2 files and decide which one to use.
187 187
188 188 V1 has been used by version prior to 2.9.1 and contains less data than
189 189 v2. We read both versions and check if no data in v2 contradicts
190 190 v1. If there is not contradiction we can safely assume that both v1
191 191 and v2 were written at the same time and use the extract data in v2. If
192 192 there is contradiction we ignore v2 content as we assume an old version
193 193 of Mercurial has overwritten the mergestate file and left an old v2
194 194 file around.
195 195
196 196 returns list of record [(TYPE, data), ...]"""
197 197 v1records = self._readrecordsv1()
198 198 v2records = self._readrecordsv2()
199 199 if self._v1v2match(v1records, v2records):
200 200 return v2records
201 201 else:
202 202 # v1 file is newer than v2 file, use it
203 203 # we have to infer the "other" changeset of the merge
204 204 # we cannot do better than that with v1 of the format
205 205 mctx = self._repo[None].parents()[-1]
206 206 v1records.append(('O', mctx.hex()))
207 207 # add place holder "other" file node information
208 208 # nobody is using it yet so we do no need to fetch the data
209 209 # if mctx was wrong `mctx[bits[-2]]` may fails.
210 210 for idx, r in enumerate(v1records):
211 211 if r[0] == 'F':
212 212 bits = r[1].split('\0')
213 213 bits.insert(-2, '')
214 214 v1records[idx] = (r[0], '\0'.join(bits))
215 215 return v1records
216 216
217 217 def _v1v2match(self, v1records, v2records):
218 218 oldv2 = set() # old format version of v2 record
219 219 for rec in v2records:
220 220 if rec[0] == 'L':
221 221 oldv2.add(rec)
222 222 elif rec[0] == 'F':
223 223 # drop the onode data (not contained in v1)
224 224 oldv2.add(('F', _droponode(rec[1])))
225 225 for rec in v1records:
226 226 if rec not in oldv2:
227 227 return False
228 228 else:
229 229 return True
230 230
231 231 def _readrecordsv1(self):
232 232 """read on disk merge state for version 1 file
233 233
234 234 returns list of record [(TYPE, data), ...]
235 235
236 236 Note: the "F" data from this file are one entry short
237 237 (no "other file node" entry)
238 238 """
239 239 records = []
240 240 try:
241 241 f = self._repo.vfs(self.statepathv1)
242 242 for i, l in enumerate(f):
243 243 if i == 0:
244 244 records.append(('L', l[:-1]))
245 245 else:
246 246 records.append(('F', l[:-1]))
247 247 f.close()
248 248 except IOError as err:
249 249 if err.errno != errno.ENOENT:
250 250 raise
251 251 return records
252 252
253 253 def _readrecordsv2(self):
254 254 """read on disk merge state for version 2 file
255 255
256 256 This format is a list of arbitrary records of the form:
257 257
258 258 [type][length][content]
259 259
260 260 `type` is a single character, `length` is a 4 byte integer, and
261 261 `content` is an arbitrary byte sequence of length `length`.
262 262
263 263 Mercurial versions prior to 3.7 have a bug where if there are
264 264 unsupported mandatory merge records, attempting to clear out the merge
265 265 state with hg update --clean or similar aborts. The 't' record type
266 266 works around that by writing out what those versions treat as an
267 267 advisory record, but later versions interpret as special: the first
268 268 character is the 'real' record type and everything onwards is the data.
269 269
270 270 Returns list of records [(TYPE, data), ...]."""
271 271 records = []
272 272 try:
273 273 f = self._repo.vfs(self.statepathv2)
274 274 data = f.read()
275 275 off = 0
276 276 end = len(data)
277 277 while off < end:
278 278 rtype = data[off]
279 279 off += 1
280 280 length = _unpack('>I', data[off:(off + 4)])[0]
281 281 off += 4
282 282 record = data[off:(off + length)]
283 283 off += length
284 284 if rtype == 't':
285 285 rtype, record = record[0], record[1:]
286 286 records.append((rtype, record))
287 287 f.close()
288 288 except IOError as err:
289 289 if err.errno != errno.ENOENT:
290 290 raise
291 291 return records
292 292
293 293 @util.propertycache
294 294 def mergedriver(self):
295 295 # protect against the following:
296 296 # - A configures a malicious merge driver in their hgrc, then
297 297 # pauses the merge
298 298 # - A edits their hgrc to remove references to the merge driver
299 299 # - A gives a copy of their entire repo, including .hg, to B
300 300 # - B inspects .hgrc and finds it to be clean
301 301 # - B then continues the merge and the malicious merge driver
302 302 # gets invoked
303 303 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
304 304 if (self._readmergedriver is not None
305 305 and self._readmergedriver != configmergedriver):
306 306 raise error.ConfigError(
307 307 _("merge driver changed since merge started"),
308 308 hint=_("revert merge driver change or abort merge"))
309 309
310 310 return configmergedriver
311 311
312 312 @util.propertycache
313 313 def localctx(self):
314 314 if self._local is None:
315 315 raise RuntimeError("localctx accessed but self._local isn't set")
316 316 return self._repo[self._local]
317 317
318 318 @util.propertycache
319 319 def otherctx(self):
320 320 if self._other is None:
321 321 raise RuntimeError("otherctx accessed but self._other isn't set")
322 322 return self._repo[self._other]
323 323
324 324 def active(self):
325 325 """Whether mergestate is active.
326 326
327 327 Returns True if there appears to be mergestate. This is a rough proxy
328 328 for "is a merge in progress."
329 329 """
330 330 # Check local variables before looking at filesystem for performance
331 331 # reasons.
332 332 return bool(self._local) or bool(self._state) or \
333 333 self._repo.vfs.exists(self.statepathv1) or \
334 334 self._repo.vfs.exists(self.statepathv2)
335 335
336 336 def commit(self):
337 337 """Write current state on disk (if necessary)"""
338 338 if self._dirty:
339 339 records = self._makerecords()
340 340 self._writerecords(records)
341 341 self._dirty = False
342 342
343 343 def _makerecords(self):
344 344 records = []
345 345 records.append(('L', hex(self._local)))
346 346 records.append(('O', hex(self._other)))
347 347 if self.mergedriver:
348 348 records.append(('m', '\0'.join([
349 349 self.mergedriver, self._mdstate])))
350 350 for d, v in self._state.iteritems():
351 351 if v[0] == 'd':
352 352 records.append(('D', '\0'.join([d] + v)))
353 353 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
354 354 # older versions of Mercurial
355 355 elif v[1] == nullhex or v[6] == nullhex:
356 356 records.append(('C', '\0'.join([d] + v)))
357 357 else:
358 358 records.append(('F', '\0'.join([d] + v)))
359 359 for filename, extras in sorted(self._stateextras.iteritems()):
360 360 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
361 361 extras.iteritems())
362 362 records.append(('f', '%s\0%s' % (filename, rawextras)))
363 363 if self._labels is not None:
364 364 labels = '\0'.join(self._labels)
365 365 records.append(('l', labels))
366 366 return records
367 367
368 368 def _writerecords(self, records):
369 369 """Write current state on disk (both v1 and v2)"""
370 370 self._writerecordsv1(records)
371 371 self._writerecordsv2(records)
372 372
373 373 def _writerecordsv1(self, records):
374 374 """Write current state on disk in a version 1 file"""
375 375 f = self._repo.vfs(self.statepathv1, 'w')
376 376 irecords = iter(records)
377 377 lrecords = next(irecords)
378 378 assert lrecords[0] == 'L'
379 379 f.write(hex(self._local) + '\n')
380 380 for rtype, data in irecords:
381 381 if rtype == 'F':
382 382 f.write('%s\n' % _droponode(data))
383 383 f.close()
384 384
385 385 def _writerecordsv2(self, records):
386 386 """Write current state on disk in a version 2 file
387 387
388 388 See the docstring for _readrecordsv2 for why we use 't'."""
389 389 # these are the records that all version 2 clients can read
390 390 whitelist = 'LOF'
391 391 f = self._repo.vfs(self.statepathv2, 'w')
392 392 for key, data in records:
393 393 assert len(key) == 1
394 394 if key not in whitelist:
395 395 key, data = 't', '%s%s' % (key, data)
396 396 format = '>sI%is' % len(data)
397 397 f.write(_pack(format, key, len(data), data))
398 398 f.close()
399 399
400 400 def add(self, fcl, fco, fca, fd):
401 401 """add a new (potentially?) conflicting file the merge state
402 402 fcl: file context for local,
403 403 fco: file context for remote,
404 404 fca: file context for ancestors,
405 405 fd: file path of the resulting merge.
406 406
407 407 note: also write the local version to the `.hg/merge` directory.
408 408 """
409 409 if fcl.isabsent():
410 410 hash = nullhex
411 411 else:
412 412 hash = hashlib.sha1(fcl.path()).hexdigest()
413 413 self._repo.vfs.write('merge/' + hash, fcl.data())
414 414 self._state[fd] = ['u', hash, fcl.path(),
415 415 fca.path(), hex(fca.filenode()),
416 416 fco.path(), hex(fco.filenode()),
417 417 fcl.flags()]
418 418 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
419 419 self._dirty = True
420 420
421 421 def __contains__(self, dfile):
422 422 return dfile in self._state
423 423
424 424 def __getitem__(self, dfile):
425 425 return self._state[dfile][0]
426 426
427 427 def __iter__(self):
428 428 return iter(sorted(self._state))
429 429
430 430 def files(self):
431 431 return self._state.keys()
432 432
433 433 def mark(self, dfile, state):
434 434 self._state[dfile][0] = state
435 435 self._dirty = True
436 436
437 437 def mdstate(self):
438 438 return self._mdstate
439 439
440 440 def unresolved(self):
441 441 """Obtain the paths of unresolved files."""
442 442
443 443 for f, entry in self._state.items():
444 444 if entry[0] == 'u':
445 445 yield f
446 446
447 447 def driverresolved(self):
448 448 """Obtain the paths of driver-resolved files."""
449 449
450 450 for f, entry in self._state.items():
451 451 if entry[0] == 'd':
452 452 yield f
453 453
454 454 def extras(self, filename):
455 455 return self._stateextras.setdefault(filename, {})
456 456
457 457 def _resolve(self, preresolve, dfile, wctx):
458 458 """rerun merge process for file path `dfile`"""
459 459 if self[dfile] in 'rd':
460 460 return True, 0
461 461 stateentry = self._state[dfile]
462 462 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
463 463 octx = self._repo[self._other]
464 464 extras = self.extras(dfile)
465 465 anccommitnode = extras.get('ancestorlinknode')
466 466 if anccommitnode:
467 467 actx = self._repo[anccommitnode]
468 468 else:
469 469 actx = None
470 470 fcd = self._filectxorabsent(hash, wctx, dfile)
471 471 fco = self._filectxorabsent(onode, octx, ofile)
472 472 # TODO: move this to filectxorabsent
473 473 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
474 474 # "premerge" x flags
475 475 flo = fco.flags()
476 476 fla = fca.flags()
477 477 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
478 478 if fca.node() == nullid and flags != flo:
479 479 if preresolve:
480 480 self._repo.ui.warn(
481 481 _('warning: cannot merge flags for %s '
482 482 'without common ancestor - keeping local flags\n')
483 483 % afile)
484 484 elif flags == fla:
485 485 flags = flo
486 486 if preresolve:
487 487 # restore local
488 488 if hash != nullhex:
489 489 f = self._repo.vfs('merge/' + hash)
490 490 self._repo.wwrite(dfile, f.read(), flags)
491 491 f.close()
492 492 else:
493 493 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
494 494 complete, r, deleted = filemerge.premerge(self._repo, self._local,
495 495 lfile, fcd, fco, fca,
496 496 labels=self._labels)
497 497 else:
498 498 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
499 499 lfile, fcd, fco, fca,
500 500 labels=self._labels)
501 501 if r is None:
502 502 # no real conflict
503 503 del self._state[dfile]
504 504 self._stateextras.pop(dfile, None)
505 505 self._dirty = True
506 506 elif not r:
507 507 self.mark(dfile, 'r')
508 508
509 509 if complete:
510 510 action = None
511 511 if deleted:
512 512 if fcd.isabsent():
513 513 # dc: local picked. Need to drop if present, which may
514 514 # happen on re-resolves.
515 515 action = 'f'
516 516 else:
517 517 # cd: remote picked (or otherwise deleted)
518 518 action = 'r'
519 519 else:
520 520 if fcd.isabsent(): # dc: remote picked
521 521 action = 'g'
522 522 elif fco.isabsent(): # cd: local picked
523 523 if dfile in self.localctx:
524 524 action = 'am'
525 525 else:
526 526 action = 'a'
527 527 # else: regular merges (no action necessary)
528 528 self._results[dfile] = r, action
529 529
530 530 return complete, r
531 531
532 532 def _filectxorabsent(self, hexnode, ctx, f):
533 533 if hexnode == nullhex:
534 534 return filemerge.absentfilectx(ctx, f)
535 535 else:
536 536 return ctx[f]
537 537
538 538 def preresolve(self, dfile, wctx):
539 539 """run premerge process for dfile
540 540
541 541 Returns whether the merge is complete, and the exit code."""
542 542 return self._resolve(True, dfile, wctx)
543 543
544 544 def resolve(self, dfile, wctx):
545 545 """run merge process (assuming premerge was run) for dfile
546 546
547 547 Returns the exit code of the merge."""
548 548 return self._resolve(False, dfile, wctx)[1]
549 549
550 550 def counts(self):
551 551 """return counts for updated, merged and removed files in this
552 552 session"""
553 553 updated, merged, removed = 0, 0, 0
554 554 for r, action in self._results.itervalues():
555 555 if r is None:
556 556 updated += 1
557 557 elif r == 0:
558 558 if action == 'r':
559 559 removed += 1
560 560 else:
561 561 merged += 1
562 562 return updated, merged, removed
563 563
564 564 def unresolvedcount(self):
565 565 """get unresolved count for this merge (persistent)"""
566 566 return len([True for f, entry in self._state.iteritems()
567 567 if entry[0] == 'u'])
568 568
569 569 def actions(self):
570 570 """return lists of actions to perform on the dirstate"""
571 571 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
572 572 for f, (r, action) in self._results.iteritems():
573 573 if action is not None:
574 574 actions[action].append((f, None, "merge result"))
575 575 return actions
576 576
577 577 def recordactions(self):
578 578 """record remove/add/get actions in the dirstate"""
579 579 branchmerge = self._repo.dirstate.p2() != nullid
580 580 recordupdates(self._repo, self.actions(), branchmerge)
581 581
582 582 def queueremove(self, f):
583 583 """queues a file to be removed from the dirstate
584 584
585 585 Meant for use by custom merge drivers."""
586 586 self._results[f] = 0, 'r'
587 587
588 588 def queueadd(self, f):
589 589 """queues a file to be added to the dirstate
590 590
591 591 Meant for use by custom merge drivers."""
592 592 self._results[f] = 0, 'a'
593 593
594 594 def queueget(self, f):
595 595 """queues a file to be marked modified in the dirstate
596 596
597 597 Meant for use by custom merge drivers."""
598 598 self._results[f] = 0, 'g'
599 599
600 600 def _getcheckunknownconfig(repo, section, name):
601 601 config = repo.ui.config(section, name, default='abort')
602 602 valid = ['abort', 'ignore', 'warn']
603 603 if config not in valid:
604 604 validstr = ', '.join(["'" + v + "'" for v in valid])
605 605 raise error.ConfigError(_("%s.%s not valid "
606 606 "('%s' is none of %s)")
607 607 % (section, name, config, validstr))
608 608 return config
609 609
610 610 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
611 611 if f2 is None:
612 612 f2 = f
613 613 return (repo.wvfs.audit.check(f)
614 614 and repo.wvfs.isfileorlink(f)
615 615 and repo.dirstate.normalize(f) not in repo.dirstate
616 616 and mctx[f2].cmp(wctx[f]))
617 617
618 618 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
619 619 """
620 620 Considers any actions that care about the presence of conflicting unknown
621 621 files. For some actions, the result is to abort; for others, it is to
622 622 choose a different action.
623 623 """
624 624 conflicts = set()
625 625 warnconflicts = set()
626 626 abortconflicts = set()
627 627 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
628 628 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
629 629 if not force:
630 630 def collectconflicts(conflicts, config):
631 631 if config == 'abort':
632 632 abortconflicts.update(conflicts)
633 633 elif config == 'warn':
634 634 warnconflicts.update(conflicts)
635 635
636 636 for f, (m, args, msg) in actions.iteritems():
637 637 if m in ('c', 'dc'):
638 638 if _checkunknownfile(repo, wctx, mctx, f):
639 639 conflicts.add(f)
640 640 elif m == 'dg':
641 641 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
642 642 conflicts.add(f)
643 643
644 644 ignoredconflicts = set([c for c in conflicts
645 645 if repo.dirstate._ignore(c)])
646 646 unknownconflicts = conflicts - ignoredconflicts
647 647 collectconflicts(ignoredconflicts, ignoredconfig)
648 648 collectconflicts(unknownconflicts, unknownconfig)
649 649 else:
650 650 for f, (m, args, msg) in actions.iteritems():
651 651 if m == 'cm':
652 652 fl2, anc = args
653 653 different = _checkunknownfile(repo, wctx, mctx, f)
654 654 if repo.dirstate._ignore(f):
655 655 config = ignoredconfig
656 656 else:
657 657 config = unknownconfig
658 658
659 659 # The behavior when force is True is described by this table:
660 660 # config different mergeforce | action backup
661 661 # * n * | get n
662 662 # * y y | merge -
663 663 # abort y n | merge - (1)
664 664 # warn y n | warn + get y
665 665 # ignore y n | get y
666 666 #
667 667 # (1) this is probably the wrong behavior here -- we should
668 668 # probably abort, but some actions like rebases currently
669 669 # don't like an abort happening in the middle of
670 670 # merge.update.
671 671 if not different:
672 672 actions[f] = ('g', (fl2, False), "remote created")
673 673 elif mergeforce or config == 'abort':
674 674 actions[f] = ('m', (f, f, None, False, anc),
675 675 "remote differs from untracked local")
676 676 elif config == 'abort':
677 677 abortconflicts.add(f)
678 678 else:
679 679 if config == 'warn':
680 680 warnconflicts.add(f)
681 681 actions[f] = ('g', (fl2, True), "remote created")
682 682
683 683 for f in sorted(abortconflicts):
684 684 repo.ui.warn(_("%s: untracked file differs\n") % f)
685 685 if abortconflicts:
686 686 raise error.Abort(_("untracked files in working directory "
687 687 "differ from files in requested revision"))
688 688
689 689 for f in sorted(warnconflicts):
690 690 repo.ui.warn(_("%s: replacing untracked file\n") % f)
691 691
692 692 for f, (m, args, msg) in actions.iteritems():
693 693 backup = f in conflicts
694 694 if m == 'c':
695 695 flags, = args
696 696 actions[f] = ('g', (flags, backup), msg)
697 697
698 698 def _forgetremoved(wctx, mctx, branchmerge):
699 699 """
700 700 Forget removed files
701 701
702 702 If we're jumping between revisions (as opposed to merging), and if
703 703 neither the working directory nor the target rev has the file,
704 704 then we need to remove it from the dirstate, to prevent the
705 705 dirstate from listing the file when it is no longer in the
706 706 manifest.
707 707
708 708 If we're merging, and the other revision has removed a file
709 709 that is not present in the working directory, we need to mark it
710 710 as removed.
711 711 """
712 712
713 713 actions = {}
714 714 m = 'f'
715 715 if branchmerge:
716 716 m = 'r'
717 717 for f in wctx.deleted():
718 718 if f not in mctx:
719 719 actions[f] = m, None, "forget deleted"
720 720
721 721 if not branchmerge:
722 722 for f in wctx.removed():
723 723 if f not in mctx:
724 724 actions[f] = 'f', None, "forget removed"
725 725
726 726 return actions
727 727
728 728 def _checkcollision(repo, wmf, actions):
729 729 # build provisional merged manifest up
730 730 pmmf = set(wmf)
731 731
732 732 if actions:
733 733 # k, dr, e and rd are no-op
734 734 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
735 735 for f, args, msg in actions[m]:
736 736 pmmf.add(f)
737 737 for f, args, msg in actions['r']:
738 738 pmmf.discard(f)
739 739 for f, args, msg in actions['dm']:
740 740 f2, flags = args
741 741 pmmf.discard(f2)
742 742 pmmf.add(f)
743 743 for f, args, msg in actions['dg']:
744 744 pmmf.add(f)
745 745 for f, args, msg in actions['m']:
746 746 f1, f2, fa, move, anc = args
747 747 if move:
748 748 pmmf.discard(f1)
749 749 pmmf.add(f)
750 750
751 751 # check case-folding collision in provisional merged manifest
752 752 foldmap = {}
753 753 for f in sorted(pmmf):
754 754 fold = util.normcase(f)
755 755 if fold in foldmap:
756 756 raise error.Abort(_("case-folding collision between %s and %s")
757 757 % (f, foldmap[fold]))
758 758 foldmap[fold] = f
759 759
760 760 # check case-folding of directories
761 761 foldprefix = unfoldprefix = lastfull = ''
762 762 for fold, f in sorted(foldmap.items()):
763 763 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
764 764 # the folded prefix matches but actual casing is different
765 765 raise error.Abort(_("case-folding collision between "
766 766 "%s and directory of %s") % (lastfull, f))
767 767 foldprefix = fold + '/'
768 768 unfoldprefix = f + '/'
769 769 lastfull = f
770 770
771 771 def driverpreprocess(repo, ms, wctx, labels=None):
772 772 """run the preprocess step of the merge driver, if any
773 773
774 774 This is currently not implemented -- it's an extension point."""
775 775 return True
776 776
777 777 def driverconclude(repo, ms, wctx, labels=None):
778 778 """run the conclude step of the merge driver, if any
779 779
780 780 This is currently not implemented -- it's an extension point."""
781 781 return True
782 782
783 783 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
784 784 acceptremote, followcopies):
785 785 """
786 786 Merge wctx and p2 with ancestor pa and generate merge action list
787 787
788 788 branchmerge and force are as passed in to update
789 789 matcher = matcher to filter file lists
790 790 acceptremote = accept the incoming changes without prompting
791 791 """
792 792 if matcher is not None and matcher.always():
793 793 matcher = None
794 794
795 795 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
796 796
797 797 # manifests fetched in order are going to be faster, so prime the caches
798 798 [x.manifest() for x in
799 799 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
800 800
801 801 if followcopies:
802 802 ret = copies.mergecopies(repo, wctx, p2, pa)
803 803 copy, movewithdir, diverge, renamedelete = ret
804 804
805 805 repo.ui.note(_("resolving manifests\n"))
806 806 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
807 807 % (bool(branchmerge), bool(force), bool(matcher)))
808 808 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
809 809
810 810 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
811 811 copied = set(copy.values())
812 812 copied.update(movewithdir.values())
813 813
814 814 if '.hgsubstate' in m1:
815 815 # check whether sub state is modified
816 816 if any(wctx.sub(s).dirty() for s in wctx.substate):
817 817 m1['.hgsubstate'] += '+'
818 818
819 819 # Compare manifests
820 820 if matcher is not None:
821 821 m1 = m1.matches(matcher)
822 822 m2 = m2.matches(matcher)
823 823 diff = m1.diff(m2)
824 824
825 825 actions = {}
826 826 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
827 827 if n1 and n2: # file exists on both local and remote side
828 828 if f not in ma:
829 829 fa = copy.get(f, None)
830 830 if fa is not None:
831 831 actions[f] = ('m', (f, f, fa, False, pa.node()),
832 832 "both renamed from " + fa)
833 833 else:
834 834 actions[f] = ('m', (f, f, None, False, pa.node()),
835 835 "both created")
836 836 else:
837 837 a = ma[f]
838 838 fla = ma.flags(f)
839 839 nol = 'l' not in fl1 + fl2 + fla
840 840 if n2 == a and fl2 == fla:
841 841 actions[f] = ('k' , (), "remote unchanged")
842 842 elif n1 == a and fl1 == fla: # local unchanged - use remote
843 843 if n1 == n2: # optimization: keep local content
844 844 actions[f] = ('e', (fl2,), "update permissions")
845 845 else:
846 846 actions[f] = ('g', (fl2, False), "remote is newer")
847 847 elif nol and n2 == a: # remote only changed 'x'
848 848 actions[f] = ('e', (fl2,), "update permissions")
849 849 elif nol and n1 == a: # local only changed 'x'
850 850 actions[f] = ('g', (fl1, False), "remote is newer")
851 851 else: # both changed something
852 852 actions[f] = ('m', (f, f, f, False, pa.node()),
853 853 "versions differ")
854 854 elif n1: # file exists only on local side
855 855 if f in copied:
856 856 pass # we'll deal with it on m2 side
857 857 elif f in movewithdir: # directory rename, move local
858 858 f2 = movewithdir[f]
859 859 if f2 in m2:
860 860 actions[f2] = ('m', (f, f2, None, True, pa.node()),
861 861 "remote directory rename, both created")
862 862 else:
863 863 actions[f2] = ('dm', (f, fl1),
864 864 "remote directory rename - move from " + f)
865 865 elif f in copy:
866 866 f2 = copy[f]
867 867 actions[f] = ('m', (f, f2, f2, False, pa.node()),
868 868 "local copied/moved from " + f2)
869 869 elif f in ma: # clean, a different, no remote
870 870 if n1 != ma[f]:
871 871 if acceptremote:
872 872 actions[f] = ('r', None, "remote delete")
873 873 else:
874 874 actions[f] = ('cd', (f, None, f, False, pa.node()),
875 875 "prompt changed/deleted")
876 876 elif n1[20:] == 'a':
877 877 # This extra 'a' is added by working copy manifest to mark
878 878 # the file as locally added. We should forget it instead of
879 879 # deleting it.
880 880 actions[f] = ('f', None, "remote deleted")
881 881 else:
882 882 actions[f] = ('r', None, "other deleted")
883 883 elif n2: # file exists only on remote side
884 884 if f in copied:
885 885 pass # we'll deal with it on m1 side
886 886 elif f in movewithdir:
887 887 f2 = movewithdir[f]
888 888 if f2 in m1:
889 889 actions[f2] = ('m', (f2, f, None, False, pa.node()),
890 890 "local directory rename, both created")
891 891 else:
892 892 actions[f2] = ('dg', (f, fl2),
893 893 "local directory rename - get from " + f)
894 894 elif f in copy:
895 895 f2 = copy[f]
896 896 if f2 in m2:
897 897 actions[f] = ('m', (f2, f, f2, False, pa.node()),
898 898 "remote copied from " + f2)
899 899 else:
900 900 actions[f] = ('m', (f2, f, f2, True, pa.node()),
901 901 "remote moved from " + f2)
902 902 elif f not in ma:
903 903 # local unknown, remote created: the logic is described by the
904 904 # following table:
905 905 #
906 906 # force branchmerge different | action
907 907 # n * * | create
908 908 # y n * | create
909 909 # y y n | create
910 910 # y y y | merge
911 911 #
912 912 # Checking whether the files are different is expensive, so we
913 913 # don't do that when we can avoid it.
914 914 if not force:
915 915 actions[f] = ('c', (fl2,), "remote created")
916 916 elif not branchmerge:
917 917 actions[f] = ('c', (fl2,), "remote created")
918 918 else:
919 919 actions[f] = ('cm', (fl2, pa.node()),
920 920 "remote created, get or merge")
921 921 elif n2 != ma[f]:
922 922 if acceptremote:
923 923 actions[f] = ('c', (fl2,), "remote recreating")
924 924 else:
925 925 actions[f] = ('dc', (None, f, f, False, pa.node()),
926 926 "prompt deleted/changed")
927 927
928 928 return actions, diverge, renamedelete
929 929
930 930 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
931 931 """Resolves false conflicts where the nodeid changed but the content
932 932 remained the same."""
933 933
934 934 for f, (m, args, msg) in actions.items():
935 935 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
936 936 # local did change but ended up with same content
937 937 actions[f] = 'r', None, "prompt same"
938 938 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
939 939 # remote did change but ended up with same content
940 940 del actions[f] # don't get = keep local deleted
941 941
942 942 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
943 943 acceptremote, followcopies, matcher=None,
944 944 mergeforce=False):
945 945 "Calculate the actions needed to merge mctx into wctx using ancestors"
946 946 if len(ancestors) == 1: # default
947 947 actions, diverge, renamedelete = manifestmerge(
948 948 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
949 949 acceptremote, followcopies)
950 950 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
951 951
952 952 else: # only when merge.preferancestor=* - the default
953 953 repo.ui.note(
954 954 _("note: merging %s and %s using bids from ancestors %s\n") %
955 955 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
956 956
957 957 # Call for bids
958 958 fbids = {} # mapping filename to bids (action method to list af actions)
959 959 diverge, renamedelete = None, None
960 960 for ancestor in ancestors:
961 961 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
962 962 actions, diverge1, renamedelete1 = manifestmerge(
963 963 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
964 964 acceptremote, followcopies)
965 965 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
966 966
967 967 # Track the shortest set of warning on the theory that bid
968 968 # merge will correctly incorporate more information
969 969 if diverge is None or len(diverge1) < len(diverge):
970 970 diverge = diverge1
971 971 if renamedelete is None or len(renamedelete) < len(renamedelete1):
972 972 renamedelete = renamedelete1
973 973
974 974 for f, a in sorted(actions.iteritems()):
975 975 m, args, msg = a
976 976 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
977 977 if f in fbids:
978 978 d = fbids[f]
979 979 if m in d:
980 980 d[m].append(a)
981 981 else:
982 982 d[m] = [a]
983 983 else:
984 984 fbids[f] = {m: [a]}
985 985
986 986 # Pick the best bid for each file
987 987 repo.ui.note(_('\nauction for merging merge bids\n'))
988 988 actions = {}
989 989 for f, bids in sorted(fbids.items()):
990 990 # bids is a mapping from action method to list af actions
991 991 # Consensus?
992 992 if len(bids) == 1: # all bids are the same kind of method
993 993 m, l = bids.items()[0]
994 994 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
995 995 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
996 996 actions[f] = l[0]
997 997 continue
998 998 # If keep is an option, just do it.
999 999 if 'k' in bids:
1000 1000 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1001 1001 actions[f] = bids['k'][0]
1002 1002 continue
1003 1003 # If there are gets and they all agree [how could they not?], do it.
1004 1004 if 'g' in bids:
1005 1005 ga0 = bids['g'][0]
1006 1006 if all(a == ga0 for a in bids['g'][1:]):
1007 1007 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1008 1008 actions[f] = ga0
1009 1009 continue
1010 1010 # TODO: Consider other simple actions such as mode changes
1011 1011 # Handle inefficient democrazy.
1012 1012 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1013 1013 for m, l in sorted(bids.items()):
1014 1014 for _f, args, msg in l:
1015 1015 repo.ui.note(' %s -> %s\n' % (msg, m))
1016 1016 # Pick random action. TODO: Instead, prompt user when resolving
1017 1017 m, l = bids.items()[0]
1018 1018 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1019 1019 (f, m))
1020 1020 actions[f] = l[0]
1021 1021 continue
1022 1022 repo.ui.note(_('end of auction\n\n'))
1023 1023
1024 1024 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1025 1025
1026 1026 if wctx.rev() is None:
1027 1027 fractions = _forgetremoved(wctx, mctx, branchmerge)
1028 1028 actions.update(fractions)
1029 1029
1030 1030 return actions, diverge, renamedelete
1031 1031
1032 1032 def batchremove(repo, actions):
1033 1033 """apply removes to the working directory
1034 1034
1035 1035 yields tuples for progress updates
1036 1036 """
1037 1037 verbose = repo.ui.verbose
1038 1038 unlink = util.unlinkpath
1039 1039 wjoin = repo.wjoin
1040 1040 audit = repo.wvfs.audit
1041 try:
1042 cwd = os.getcwd()
1043 except OSError as err:
1044 if err.errno != errno.ENOENT:
1045 raise
1046 cwd = None
1041 1047 i = 0
1042 1048 for f, args, msg in actions:
1043 1049 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1044 1050 if verbose:
1045 1051 repo.ui.note(_("removing %s\n") % f)
1046 1052 audit(f)
1047 1053 try:
1048 1054 unlink(wjoin(f), ignoremissing=True)
1049 1055 except OSError as inst:
1050 1056 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1051 1057 (f, inst.strerror))
1052 1058 if i == 100:
1053 1059 yield i, f
1054 1060 i = 0
1055 1061 i += 1
1056 1062 if i > 0:
1057 1063 yield i, f
1064 if cwd:
1065 # cwd was present before we started to remove files
1066 # let's check if it is present after we removed them
1067 try:
1068 os.getcwd()
1069 except OSError as err:
1070 if err.errno != errno.ENOENT:
1071 raise
1072 # Print a warning if cwd was deleted
1073 repo.ui.warn(_("current directory was removed\n"
1074 "(consider changing to repo root: %s)\n") %
1075 repo.root)
1058 1076
1059 1077 def batchget(repo, mctx, actions):
1060 1078 """apply gets to the working directory
1061 1079
1062 1080 mctx is the context to get from
1063 1081
1064 1082 yields tuples for progress updates
1065 1083 """
1066 1084 verbose = repo.ui.verbose
1067 1085 fctx = mctx.filectx
1068 1086 wwrite = repo.wwrite
1069 1087 ui = repo.ui
1070 1088 i = 0
1071 1089 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1072 1090 for f, (flags, backup), msg in actions:
1073 1091 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1074 1092 if verbose:
1075 1093 repo.ui.note(_("getting %s\n") % f)
1076 1094
1077 1095 if backup:
1078 1096 absf = repo.wjoin(f)
1079 1097 orig = scmutil.origpath(ui, repo, absf)
1080 1098 try:
1081 1099 if repo.wvfs.isfileorlink(f):
1082 1100 util.rename(absf, orig)
1083 1101 except OSError as e:
1084 1102 if e.errno != errno.ENOENT:
1085 1103 raise
1086 1104
1087 1105 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1088 1106 repo.wvfs.removedirs(f)
1089 1107 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1090 1108 if i == 100:
1091 1109 yield i, f
1092 1110 i = 0
1093 1111 i += 1
1094 1112 if i > 0:
1095 1113 yield i, f
1096 1114
1097 1115 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1098 1116 """apply the merge action list to the working directory
1099 1117
1100 1118 wctx is the working copy context
1101 1119 mctx is the context to be merged into the working copy
1102 1120
1103 1121 Return a tuple of counts (updated, merged, removed, unresolved) that
1104 1122 describes how many files were affected by the update.
1105 1123 """
1106 1124
1107 1125 updated, merged, removed = 0, 0, 0
1108 1126 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1109 1127 moves = []
1110 1128 for m, l in actions.items():
1111 1129 l.sort()
1112 1130
1113 1131 # 'cd' and 'dc' actions are treated like other merge conflicts
1114 1132 mergeactions = sorted(actions['cd'])
1115 1133 mergeactions.extend(sorted(actions['dc']))
1116 1134 mergeactions.extend(actions['m'])
1117 1135 for f, args, msg in mergeactions:
1118 1136 f1, f2, fa, move, anc = args
1119 1137 if f == '.hgsubstate': # merged internally
1120 1138 continue
1121 1139 if f1 is None:
1122 1140 fcl = filemerge.absentfilectx(wctx, fa)
1123 1141 else:
1124 1142 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1125 1143 fcl = wctx[f1]
1126 1144 if f2 is None:
1127 1145 fco = filemerge.absentfilectx(mctx, fa)
1128 1146 else:
1129 1147 fco = mctx[f2]
1130 1148 actx = repo[anc]
1131 1149 if fa in actx:
1132 1150 fca = actx[fa]
1133 1151 else:
1134 1152 # TODO: move to absentfilectx
1135 1153 fca = repo.filectx(f1, fileid=nullrev)
1136 1154 ms.add(fcl, fco, fca, f)
1137 1155 if f1 != f and move:
1138 1156 moves.append(f1)
1139 1157
1140 1158 audit = repo.wvfs.audit
1141 1159 _updating = _('updating')
1142 1160 _files = _('files')
1143 1161 progress = repo.ui.progress
1144 1162
1145 1163 # remove renamed files after safely stored
1146 1164 for f in moves:
1147 1165 if os.path.lexists(repo.wjoin(f)):
1148 1166 repo.ui.debug("removing %s\n" % f)
1149 1167 audit(f)
1150 1168 util.unlinkpath(repo.wjoin(f))
1151 1169
1152 1170 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1153 1171
1154 1172 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1155 1173 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1156 1174
1157 1175 # remove in parallel (must come first)
1158 1176 z = 0
1159 1177 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1160 1178 for i, item in prog:
1161 1179 z += i
1162 1180 progress(_updating, z, item=item, total=numupdates, unit=_files)
1163 1181 removed = len(actions['r'])
1164 1182
1165 1183 # get in parallel
1166 1184 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1167 1185 for i, item in prog:
1168 1186 z += i
1169 1187 progress(_updating, z, item=item, total=numupdates, unit=_files)
1170 1188 updated = len(actions['g'])
1171 1189
1172 1190 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1173 1191 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1174 1192
1175 1193 # forget (manifest only, just log it) (must come first)
1176 1194 for f, args, msg in actions['f']:
1177 1195 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1178 1196 z += 1
1179 1197 progress(_updating, z, item=f, total=numupdates, unit=_files)
1180 1198
1181 1199 # re-add (manifest only, just log it)
1182 1200 for f, args, msg in actions['a']:
1183 1201 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1184 1202 z += 1
1185 1203 progress(_updating, z, item=f, total=numupdates, unit=_files)
1186 1204
1187 1205 # re-add/mark as modified (manifest only, just log it)
1188 1206 for f, args, msg in actions['am']:
1189 1207 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1190 1208 z += 1
1191 1209 progress(_updating, z, item=f, total=numupdates, unit=_files)
1192 1210
1193 1211 # keep (noop, just log it)
1194 1212 for f, args, msg in actions['k']:
1195 1213 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1196 1214 # no progress
1197 1215
1198 1216 # directory rename, move local
1199 1217 for f, args, msg in actions['dm']:
1200 1218 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1201 1219 z += 1
1202 1220 progress(_updating, z, item=f, total=numupdates, unit=_files)
1203 1221 f0, flags = args
1204 1222 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1205 1223 audit(f)
1206 1224 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1207 1225 util.unlinkpath(repo.wjoin(f0))
1208 1226 updated += 1
1209 1227
1210 1228 # local directory rename, get
1211 1229 for f, args, msg in actions['dg']:
1212 1230 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1213 1231 z += 1
1214 1232 progress(_updating, z, item=f, total=numupdates, unit=_files)
1215 1233 f0, flags = args
1216 1234 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1217 1235 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1218 1236 updated += 1
1219 1237
1220 1238 # exec
1221 1239 for f, args, msg in actions['e']:
1222 1240 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1223 1241 z += 1
1224 1242 progress(_updating, z, item=f, total=numupdates, unit=_files)
1225 1243 flags, = args
1226 1244 audit(f)
1227 1245 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1228 1246 updated += 1
1229 1247
1230 1248 # the ordering is important here -- ms.mergedriver will raise if the merge
1231 1249 # driver has changed, and we want to be able to bypass it when overwrite is
1232 1250 # True
1233 1251 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1234 1252
1235 1253 if usemergedriver:
1236 1254 ms.commit()
1237 1255 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1238 1256 # the driver might leave some files unresolved
1239 1257 unresolvedf = set(ms.unresolved())
1240 1258 if not proceed:
1241 1259 # XXX setting unresolved to at least 1 is a hack to make sure we
1242 1260 # error out
1243 1261 return updated, merged, removed, max(len(unresolvedf), 1)
1244 1262 newactions = []
1245 1263 for f, args, msg in mergeactions:
1246 1264 if f in unresolvedf:
1247 1265 newactions.append((f, args, msg))
1248 1266 mergeactions = newactions
1249 1267
1250 1268 # premerge
1251 1269 tocomplete = []
1252 1270 for f, args, msg in mergeactions:
1253 1271 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1254 1272 z += 1
1255 1273 progress(_updating, z, item=f, total=numupdates, unit=_files)
1256 1274 if f == '.hgsubstate': # subrepo states need updating
1257 1275 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1258 1276 overwrite, labels)
1259 1277 continue
1260 1278 audit(f)
1261 1279 complete, r = ms.preresolve(f, wctx)
1262 1280 if not complete:
1263 1281 numupdates += 1
1264 1282 tocomplete.append((f, args, msg))
1265 1283
1266 1284 # merge
1267 1285 for f, args, msg in tocomplete:
1268 1286 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1269 1287 z += 1
1270 1288 progress(_updating, z, item=f, total=numupdates, unit=_files)
1271 1289 ms.resolve(f, wctx)
1272 1290
1273 1291 ms.commit()
1274 1292
1275 1293 unresolved = ms.unresolvedcount()
1276 1294
1277 1295 if usemergedriver and not unresolved and ms.mdstate() != 's':
1278 1296 if not driverconclude(repo, ms, wctx, labels=labels):
1279 1297 # XXX setting unresolved to at least 1 is a hack to make sure we
1280 1298 # error out
1281 1299 unresolved = max(unresolved, 1)
1282 1300
1283 1301 ms.commit()
1284 1302
1285 1303 msupdated, msmerged, msremoved = ms.counts()
1286 1304 updated += msupdated
1287 1305 merged += msmerged
1288 1306 removed += msremoved
1289 1307
1290 1308 extraactions = ms.actions()
1291 1309 if extraactions:
1292 1310 mfiles = set(a[0] for a in actions['m'])
1293 1311 for k, acts in extraactions.iteritems():
1294 1312 actions[k].extend(acts)
1295 1313 # Remove these files from actions['m'] as well. This is important
1296 1314 # because in recordupdates, files in actions['m'] are processed
1297 1315 # after files in other actions, and the merge driver might add
1298 1316 # files to those actions via extraactions above. This can lead to a
1299 1317 # file being recorded twice, with poor results. This is especially
1300 1318 # problematic for actions['r'] (currently only possible with the
1301 1319 # merge driver in the initial merge process; interrupted merges
1302 1320 # don't go through this flow).
1303 1321 #
1304 1322 # The real fix here is to have indexes by both file and action so
1305 1323 # that when the action for a file is changed it is automatically
1306 1324 # reflected in the other action lists. But that involves a more
1307 1325 # complex data structure, so this will do for now.
1308 1326 #
1309 1327 # We don't need to do the same operation for 'dc' and 'cd' because
1310 1328 # those lists aren't consulted again.
1311 1329 mfiles.difference_update(a[0] for a in acts)
1312 1330
1313 1331 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1314 1332
1315 1333 progress(_updating, None, total=numupdates, unit=_files)
1316 1334
1317 1335 return updated, merged, removed, unresolved
1318 1336
1319 1337 def recordupdates(repo, actions, branchmerge):
1320 1338 "record merge actions to the dirstate"
1321 1339 # remove (must come first)
1322 1340 for f, args, msg in actions.get('r', []):
1323 1341 if branchmerge:
1324 1342 repo.dirstate.remove(f)
1325 1343 else:
1326 1344 repo.dirstate.drop(f)
1327 1345
1328 1346 # forget (must come first)
1329 1347 for f, args, msg in actions.get('f', []):
1330 1348 repo.dirstate.drop(f)
1331 1349
1332 1350 # re-add
1333 1351 for f, args, msg in actions.get('a', []):
1334 1352 repo.dirstate.add(f)
1335 1353
1336 1354 # re-add/mark as modified
1337 1355 for f, args, msg in actions.get('am', []):
1338 1356 if branchmerge:
1339 1357 repo.dirstate.normallookup(f)
1340 1358 else:
1341 1359 repo.dirstate.add(f)
1342 1360
1343 1361 # exec change
1344 1362 for f, args, msg in actions.get('e', []):
1345 1363 repo.dirstate.normallookup(f)
1346 1364
1347 1365 # keep
1348 1366 for f, args, msg in actions.get('k', []):
1349 1367 pass
1350 1368
1351 1369 # get
1352 1370 for f, args, msg in actions.get('g', []):
1353 1371 if branchmerge:
1354 1372 repo.dirstate.otherparent(f)
1355 1373 else:
1356 1374 repo.dirstate.normal(f)
1357 1375
1358 1376 # merge
1359 1377 for f, args, msg in actions.get('m', []):
1360 1378 f1, f2, fa, move, anc = args
1361 1379 if branchmerge:
1362 1380 # We've done a branch merge, mark this file as merged
1363 1381 # so that we properly record the merger later
1364 1382 repo.dirstate.merge(f)
1365 1383 if f1 != f2: # copy/rename
1366 1384 if move:
1367 1385 repo.dirstate.remove(f1)
1368 1386 if f1 != f:
1369 1387 repo.dirstate.copy(f1, f)
1370 1388 else:
1371 1389 repo.dirstate.copy(f2, f)
1372 1390 else:
1373 1391 # We've update-merged a locally modified file, so
1374 1392 # we set the dirstate to emulate a normal checkout
1375 1393 # of that file some time in the past. Thus our
1376 1394 # merge will appear as a normal local file
1377 1395 # modification.
1378 1396 if f2 == f: # file not locally copied/moved
1379 1397 repo.dirstate.normallookup(f)
1380 1398 if move:
1381 1399 repo.dirstate.drop(f1)
1382 1400
1383 1401 # directory rename, move local
1384 1402 for f, args, msg in actions.get('dm', []):
1385 1403 f0, flag = args
1386 1404 if branchmerge:
1387 1405 repo.dirstate.add(f)
1388 1406 repo.dirstate.remove(f0)
1389 1407 repo.dirstate.copy(f0, f)
1390 1408 else:
1391 1409 repo.dirstate.normal(f)
1392 1410 repo.dirstate.drop(f0)
1393 1411
1394 1412 # directory rename, get
1395 1413 for f, args, msg in actions.get('dg', []):
1396 1414 f0, flag = args
1397 1415 if branchmerge:
1398 1416 repo.dirstate.add(f)
1399 1417 repo.dirstate.copy(f0, f)
1400 1418 else:
1401 1419 repo.dirstate.normal(f)
1402 1420
1403 1421 def update(repo, node, branchmerge, force, ancestor=None,
1404 1422 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1405 1423 """
1406 1424 Perform a merge between the working directory and the given node
1407 1425
1408 1426 node = the node to update to, or None if unspecified
1409 1427 branchmerge = whether to merge between branches
1410 1428 force = whether to force branch merging or file overwriting
1411 1429 matcher = a matcher to filter file lists (dirstate not updated)
1412 1430 mergeancestor = whether it is merging with an ancestor. If true,
1413 1431 we should accept the incoming changes for any prompts that occur.
1414 1432 If false, merging with an ancestor (fast-forward) is only allowed
1415 1433 between different named branches. This flag is used by rebase extension
1416 1434 as a temporary fix and should be avoided in general.
1417 1435 labels = labels to use for base, local and other
1418 1436 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1419 1437 this is True, then 'force' should be True as well.
1420 1438
1421 1439 The table below shows all the behaviors of the update command
1422 1440 given the -c and -C or no options, whether the working directory
1423 1441 is dirty, whether a revision is specified, and the relationship of
1424 1442 the parent rev to the target rev (linear, on the same named
1425 1443 branch, or on another named branch).
1426 1444
1427 1445 This logic is tested by test-update-branches.t.
1428 1446
1429 1447 -c -C dirty rev | linear same cross
1430 1448 n n n n | ok (1) x
1431 1449 n n n y | ok ok ok
1432 1450 n n y n | merge (2) (2)
1433 1451 n n y y | merge (3) (3)
1434 1452 n y * * | discard discard discard
1435 1453 y n y * | (4) (4) (4)
1436 1454 y n n * | ok ok ok
1437 1455 y y * * | (5) (5) (5)
1438 1456
1439 1457 x = can't happen
1440 1458 * = don't-care
1441 1459 1 = abort: not a linear update (merge or update --check to force update)
1442 1460 2 = abort: uncommitted changes (commit and merge, or update --clean to
1443 1461 discard changes)
1444 1462 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1445 1463 4 = abort: uncommitted changes (checked in commands.py)
1446 1464 5 = incompatible options (checked in commands.py)
1447 1465
1448 1466 Return the same tuple as applyupdates().
1449 1467 """
1450 1468
1451 1469 onode = node
1452 1470 # If we're doing a partial update, we need to skip updating
1453 1471 # the dirstate, so make a note of any partial-ness to the
1454 1472 # update here.
1455 1473 if matcher is None or matcher.always():
1456 1474 partial = False
1457 1475 else:
1458 1476 partial = True
1459 1477 with repo.wlock():
1460 1478 wc = repo[None]
1461 1479 pl = wc.parents()
1462 1480 p1 = pl[0]
1463 1481 pas = [None]
1464 1482 if ancestor is not None:
1465 1483 pas = [repo[ancestor]]
1466 1484
1467 1485 if node is None:
1468 1486 repo.ui.deprecwarn('update with no target', '3.9')
1469 1487 rev, _mark, _act = destutil.destupdate(repo)
1470 1488 node = repo[rev].node()
1471 1489
1472 1490 overwrite = force and not branchmerge
1473 1491
1474 1492 p2 = repo[node]
1475 1493 if pas[0] is None:
1476 1494 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1477 1495 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1478 1496 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1479 1497 else:
1480 1498 pas = [p1.ancestor(p2, warn=branchmerge)]
1481 1499
1482 1500 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1483 1501
1484 1502 ### check phase
1485 1503 if not overwrite:
1486 1504 if len(pl) > 1:
1487 1505 raise error.Abort(_("outstanding uncommitted merge"))
1488 1506 ms = mergestate.read(repo)
1489 1507 if list(ms.unresolved()):
1490 1508 raise error.Abort(_("outstanding merge conflicts"))
1491 1509 if branchmerge:
1492 1510 if pas == [p2]:
1493 1511 raise error.Abort(_("merging with a working directory ancestor"
1494 1512 " has no effect"))
1495 1513 elif pas == [p1]:
1496 1514 if not mergeancestor and p1.branch() == p2.branch():
1497 1515 raise error.Abort(_("nothing to merge"),
1498 1516 hint=_("use 'hg update' "
1499 1517 "or check 'hg heads'"))
1500 1518 if not force and (wc.files() or wc.deleted()):
1501 1519 raise error.Abort(_("uncommitted changes"),
1502 1520 hint=_("use 'hg status' to list changes"))
1503 1521 for s in sorted(wc.substate):
1504 1522 wc.sub(s).bailifchanged()
1505 1523
1506 1524 elif not overwrite:
1507 1525 if p1 == p2: # no-op update
1508 1526 # call the hooks and exit early
1509 1527 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1510 1528 repo.hook('update', parent1=xp2, parent2='', error=0)
1511 1529 return 0, 0, 0, 0
1512 1530
1513 1531 if pas not in ([p1], [p2]): # nonlinear
1514 1532 dirty = wc.dirty(missing=True)
1515 1533 if dirty or onode is None:
1516 1534 # Branching is a bit strange to ensure we do the minimal
1517 1535 # amount of call to obsolete.background.
1518 1536 foreground = obsolete.foreground(repo, [p1.node()])
1519 1537 # note: the <node> variable contains a random identifier
1520 1538 if repo[node].node() in foreground:
1521 1539 pas = [p1] # allow updating to successors
1522 1540 elif dirty:
1523 1541 msg = _("uncommitted changes")
1524 1542 if onode is None:
1525 1543 hint = _("commit and merge, or update --clean to"
1526 1544 " discard changes")
1527 1545 else:
1528 1546 hint = _("commit or update --clean to discard"
1529 1547 " changes")
1530 1548 raise error.Abort(msg, hint=hint)
1531 1549 else: # node is none
1532 1550 msg = _("not a linear update")
1533 1551 hint = _("merge or update --check to force update")
1534 1552 raise error.Abort(msg, hint=hint)
1535 1553 else:
1536 1554 # Allow jumping branches if clean and specific rev given
1537 1555 pas = [p1]
1538 1556
1539 1557 # deprecated config: merge.followcopies
1540 1558 followcopies = False
1541 1559 if overwrite:
1542 1560 pas = [wc]
1543 1561 elif pas == [p2]: # backwards
1544 1562 pas = [wc.p1()]
1545 1563 elif not branchmerge and not wc.dirty(missing=True):
1546 1564 pass
1547 1565 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1548 1566 followcopies = True
1549 1567
1550 1568 ### calculate phase
1551 1569 actionbyfile, diverge, renamedelete = calculateupdates(
1552 1570 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1553 1571 followcopies, matcher=matcher, mergeforce=mergeforce)
1554 1572
1555 1573 # Prompt and create actions. Most of this is in the resolve phase
1556 1574 # already, but we can't handle .hgsubstate in filemerge or
1557 1575 # subrepo.submerge yet so we have to keep prompting for it.
1558 1576 if '.hgsubstate' in actionbyfile:
1559 1577 f = '.hgsubstate'
1560 1578 m, args, msg = actionbyfile[f]
1561 1579 prompts = filemerge.partextras(labels)
1562 1580 prompts['f'] = f
1563 1581 if m == 'cd':
1564 1582 if repo.ui.promptchoice(
1565 1583 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1566 1584 "use (c)hanged version or (d)elete?"
1567 1585 "$$ &Changed $$ &Delete") % prompts, 0):
1568 1586 actionbyfile[f] = ('r', None, "prompt delete")
1569 1587 elif f in p1:
1570 1588 actionbyfile[f] = ('am', None, "prompt keep")
1571 1589 else:
1572 1590 actionbyfile[f] = ('a', None, "prompt keep")
1573 1591 elif m == 'dc':
1574 1592 f1, f2, fa, move, anc = args
1575 1593 flags = p2[f2].flags()
1576 1594 if repo.ui.promptchoice(
1577 1595 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1578 1596 "use (c)hanged version or leave (d)eleted?"
1579 1597 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1580 1598 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1581 1599 else:
1582 1600 del actionbyfile[f]
1583 1601
1584 1602 # Convert to dictionary-of-lists format
1585 1603 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1586 1604 for f, (m, args, msg) in actionbyfile.iteritems():
1587 1605 if m not in actions:
1588 1606 actions[m] = []
1589 1607 actions[m].append((f, args, msg))
1590 1608
1591 1609 if not util.fscasesensitive(repo.path):
1592 1610 # check collision between files only in p2 for clean update
1593 1611 if (not branchmerge and
1594 1612 (force or not wc.dirty(missing=True, branch=False))):
1595 1613 _checkcollision(repo, p2.manifest(), None)
1596 1614 else:
1597 1615 _checkcollision(repo, wc.manifest(), actions)
1598 1616
1599 1617 # divergent renames
1600 1618 for f, fl in sorted(diverge.iteritems()):
1601 1619 repo.ui.warn(_("note: possible conflict - %s was renamed "
1602 1620 "multiple times to:\n") % f)
1603 1621 for nf in fl:
1604 1622 repo.ui.warn(" %s\n" % nf)
1605 1623
1606 1624 # rename and delete
1607 1625 for f, fl in sorted(renamedelete.iteritems()):
1608 1626 repo.ui.warn(_("note: possible conflict - %s was deleted "
1609 1627 "and renamed to:\n") % f)
1610 1628 for nf in fl:
1611 1629 repo.ui.warn(" %s\n" % nf)
1612 1630
1613 1631 ### apply phase
1614 1632 if not branchmerge: # just jump to the new rev
1615 1633 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1616 1634 if not partial:
1617 1635 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1618 1636 # note that we're in the middle of an update
1619 1637 repo.vfs.write('updatestate', p2.hex())
1620 1638
1621 1639 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1622 1640
1623 1641 if not partial:
1624 1642 repo.dirstate.beginparentchange()
1625 1643 repo.setparents(fp1, fp2)
1626 1644 recordupdates(repo, actions, branchmerge)
1627 1645 # update completed, clear state
1628 1646 util.unlink(repo.join('updatestate'))
1629 1647
1630 1648 if not branchmerge:
1631 1649 repo.dirstate.setbranch(p2.branch())
1632 1650 repo.dirstate.endparentchange()
1633 1651
1634 1652 if not partial:
1635 1653 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1636 1654 return stats
1637 1655
1638 1656 def graft(repo, ctx, pctx, labels, keepparent=False):
1639 1657 """Do a graft-like merge.
1640 1658
1641 1659 This is a merge where the merge ancestor is chosen such that one
1642 1660 or more changesets are grafted onto the current changeset. In
1643 1661 addition to the merge, this fixes up the dirstate to include only
1644 1662 a single parent (if keepparent is False) and tries to duplicate any
1645 1663 renames/copies appropriately.
1646 1664
1647 1665 ctx - changeset to rebase
1648 1666 pctx - merge base, usually ctx.p1()
1649 1667 labels - merge labels eg ['local', 'graft']
1650 1668 keepparent - keep second parent if any
1651 1669
1652 1670 """
1653 1671 # If we're grafting a descendant onto an ancestor, be sure to pass
1654 1672 # mergeancestor=True to update. This does two things: 1) allows the merge if
1655 1673 # the destination is the same as the parent of the ctx (so we can use graft
1656 1674 # to copy commits), and 2) informs update that the incoming changes are
1657 1675 # newer than the destination so it doesn't prompt about "remote changed foo
1658 1676 # which local deleted".
1659 1677 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1660 1678
1661 1679 stats = update(repo, ctx.node(), True, True, pctx.node(),
1662 1680 mergeancestor=mergeancestor, labels=labels)
1663 1681
1664 1682 pother = nullid
1665 1683 parents = ctx.parents()
1666 1684 if keepparent and len(parents) == 2 and pctx in parents:
1667 1685 parents.remove(pctx)
1668 1686 pother = parents[0].node()
1669 1687
1670 1688 repo.dirstate.beginparentchange()
1671 1689 repo.setparents(repo['.'].node(), pother)
1672 1690 repo.dirstate.write(repo.currenttransaction())
1673 1691 # fix up dirstate for copies and renames
1674 1692 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1675 1693 repo.dirstate.endparentchange()
1676 1694 return stats
@@ -1,897 +1,899 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [phases]
6 6 > publish=False
7 7 >
8 8 > [alias]
9 9 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 10 > EOF
11 11
12 12
13 13 $ hg init a
14 14 $ cd a
15 15 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
16 16 adding changesets
17 17 adding manifests
18 18 adding file changes
19 19 added 8 changesets with 7 changes to 7 files (+2 heads)
20 20 (run 'hg heads' to see heads, 'hg merge' to merge)
21 21 $ hg up tip
22 22 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 23 $ cd ..
24 24
25 25
26 26 Rebasing
27 27 D onto H - simple rebase:
28 28 (this also tests that editor is invoked if '--edit' is specified, and that we
29 29 can abort or warn for colliding untracked files)
30 30
31 31 $ hg clone -q -u . a a1
32 32 $ cd a1
33 33
34 34 $ hg tglog
35 35 @ 7: 'H'
36 36 |
37 37 | o 6: 'G'
38 38 |/|
39 39 o | 5: 'F'
40 40 | |
41 41 | o 4: 'E'
42 42 |/
43 43 | o 3: 'D'
44 44 | |
45 45 | o 2: 'C'
46 46 | |
47 47 | o 1: 'B'
48 48 |/
49 49 o 0: 'A'
50 50
51 51
52 52 $ hg status --rev "3^1" --rev 3
53 53 A D
54 54 $ echo collide > D
55 55 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
56 56 rebasing 3:32af7686d403 "D"
57 57 D: replacing untracked file
58 58 D
59 59
60 60
61 61 HG: Enter commit message. Lines beginning with 'HG:' are removed.
62 62 HG: Leave message empty to abort commit.
63 63 HG: --
64 64 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
65 65 HG: branch 'default'
66 66 HG: added D
67 67 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
68 68 $ cat D.orig
69 69 collide
70 70 $ rm D.orig
71 71
72 72 $ hg tglog
73 73 o 7: 'D'
74 74 |
75 75 @ 6: 'H'
76 76 |
77 77 | o 5: 'G'
78 78 |/|
79 79 o | 4: 'F'
80 80 | |
81 81 | o 3: 'E'
82 82 |/
83 83 | o 2: 'C'
84 84 | |
85 85 | o 1: 'B'
86 86 |/
87 87 o 0: 'A'
88 88
89 89 $ cd ..
90 90
91 91
92 92 D onto F - intermediate point:
93 93 (this also tests that editor is not invoked if '--edit' is not specified, and
94 94 that we can ignore for colliding untracked files)
95 95
96 96 $ hg clone -q -u . a a2
97 97 $ cd a2
98 98 $ echo collide > D
99 99
100 100 $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
101 101 rebasing 3:32af7686d403 "D"
102 102 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
103 103 $ cat D.orig
104 104 collide
105 105 $ rm D.orig
106 106
107 107 $ hg tglog
108 108 o 7: 'D'
109 109 |
110 110 | @ 6: 'H'
111 111 |/
112 112 | o 5: 'G'
113 113 |/|
114 114 o | 4: 'F'
115 115 | |
116 116 | o 3: 'E'
117 117 |/
118 118 | o 2: 'C'
119 119 | |
120 120 | o 1: 'B'
121 121 |/
122 122 o 0: 'A'
123 123
124 124 $ cd ..
125 125
126 126
127 127 E onto H - skip of G:
128 128 (this also tests that we can overwrite untracked files and don't create backups
129 129 if they have the same contents)
130 130
131 131 $ hg clone -q -u . a a3
132 132 $ cd a3
133 133 $ hg cat -r 4 E | tee E
134 134 E
135 135
136 136 $ hg rebase -s 4 -d 7
137 137 rebasing 4:9520eea781bc "E"
138 138 rebasing 6:eea13746799a "G"
139 139 note: rebase of 6:eea13746799a created no changes to commit
140 140 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
141 141 $ f E.orig
142 142 E.orig: file not found
143 143
144 144 $ hg tglog
145 145 o 6: 'E'
146 146 |
147 147 @ 5: 'H'
148 148 |
149 149 o 4: 'F'
150 150 |
151 151 | o 3: 'D'
152 152 | |
153 153 | o 2: 'C'
154 154 | |
155 155 | o 1: 'B'
156 156 |/
157 157 o 0: 'A'
158 158
159 159 $ cd ..
160 160
161 161
162 162 F onto E - rebase of a branching point (skip G):
163 163
164 164 $ hg clone -q -u . a a4
165 165 $ cd a4
166 166
167 167 $ hg rebase -s 5 -d 4
168 168 rebasing 5:24b6387c8c8c "F"
169 169 rebasing 6:eea13746799a "G"
170 170 note: rebase of 6:eea13746799a created no changes to commit
171 171 rebasing 7:02de42196ebe "H" (tip)
172 172 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
173 173
174 174 $ hg tglog
175 175 @ 6: 'H'
176 176 |
177 177 o 5: 'F'
178 178 |
179 179 o 4: 'E'
180 180 |
181 181 | o 3: 'D'
182 182 | |
183 183 | o 2: 'C'
184 184 | |
185 185 | o 1: 'B'
186 186 |/
187 187 o 0: 'A'
188 188
189 189 $ cd ..
190 190
191 191
192 192 G onto H - merged revision having a parent in ancestors of target:
193 193
194 194 $ hg clone -q -u . a a5
195 195 $ cd a5
196 196
197 197 $ hg rebase -s 6 -d 7
198 198 rebasing 6:eea13746799a "G"
199 199 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/eea13746799a-883828ed-backup.hg (glob)
200 200
201 201 $ hg tglog
202 202 o 7: 'G'
203 203 |\
204 204 | @ 6: 'H'
205 205 | |
206 206 | o 5: 'F'
207 207 | |
208 208 o | 4: 'E'
209 209 |/
210 210 | o 3: 'D'
211 211 | |
212 212 | o 2: 'C'
213 213 | |
214 214 | o 1: 'B'
215 215 |/
216 216 o 0: 'A'
217 217
218 218 $ cd ..
219 219
220 220
221 221 F onto B - G maintains E as parent:
222 222
223 223 $ hg clone -q -u . a a6
224 224 $ cd a6
225 225
226 226 $ hg rebase -s 5 -d 1
227 227 rebasing 5:24b6387c8c8c "F"
228 228 rebasing 6:eea13746799a "G"
229 229 rebasing 7:02de42196ebe "H" (tip)
230 230 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/24b6387c8c8c-c3fe765d-backup.hg (glob)
231 231
232 232 $ hg tglog
233 233 @ 7: 'H'
234 234 |
235 235 | o 6: 'G'
236 236 |/|
237 237 o | 5: 'F'
238 238 | |
239 239 | o 4: 'E'
240 240 | |
241 241 | | o 3: 'D'
242 242 | | |
243 243 +---o 2: 'C'
244 244 | |
245 245 o | 1: 'B'
246 246 |/
247 247 o 0: 'A'
248 248
249 249 $ cd ..
250 250
251 251
252 252 These will fail (using --source):
253 253
254 254 G onto F - rebase onto an ancestor:
255 255
256 256 $ hg clone -q -u . a a7
257 257 $ cd a7
258 258
259 259 $ hg rebase -s 6 -d 5
260 260 nothing to rebase
261 261 [1]
262 262
263 263 F onto G - rebase onto a descendant:
264 264
265 265 $ hg rebase -s 5 -d 6
266 266 abort: source is ancestor of destination
267 267 [255]
268 268
269 269 G onto B - merge revision with both parents not in ancestors of target:
270 270
271 271 $ hg rebase -s 6 -d 1
272 272 rebasing 6:eea13746799a "G"
273 273 abort: cannot use revision 6 as base, result would have 3 parents
274 274 [255]
275 275
276 276
277 277 These will abort gracefully (using --base):
278 278
279 279 G onto G - rebase onto same changeset:
280 280
281 281 $ hg rebase -b 6 -d 6
282 282 nothing to rebase - eea13746799a is both "base" and destination
283 283 [1]
284 284
285 285 G onto F - rebase onto an ancestor:
286 286
287 287 $ hg rebase -b 6 -d 5
288 288 nothing to rebase
289 289 [1]
290 290
291 291 F onto G - rebase onto a descendant:
292 292
293 293 $ hg rebase -b 5 -d 6
294 294 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
295 295 [1]
296 296
297 297 C onto A - rebase onto an ancestor:
298 298
299 299 $ hg rebase -d 0 -s 2
300 300 rebasing 2:5fddd98957c8 "C"
301 301 rebasing 3:32af7686d403 "D"
302 302 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-f9244fa1-backup.hg (glob)
303 303 $ hg tglog
304 304 o 7: 'D'
305 305 |
306 306 o 6: 'C'
307 307 |
308 308 | @ 5: 'H'
309 309 | |
310 310 | | o 4: 'G'
311 311 | |/|
312 312 | o | 3: 'F'
313 313 |/ /
314 314 | o 2: 'E'
315 315 |/
316 316 | o 1: 'B'
317 317 |/
318 318 o 0: 'A'
319 319
320 320
321 321 Check rebasing public changeset
322 322
323 323 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
324 324 $ hg rebase -d 0 -b 6
325 325 nothing to rebase
326 326 [1]
327 327 $ hg rebase -d 5 -b 6
328 328 abort: can't rebase public changeset e1c4361dd923
329 329 (see 'hg help phases' for details)
330 330 [255]
331 331
332 332 $ hg rebase -d 5 -b 6 --keep
333 333 rebasing 6:e1c4361dd923 "C"
334 334 rebasing 7:c9659aac0000 "D" (tip)
335 335
336 336 Check rebasing mutable changeset
337 337 Source phase greater or equal to destination phase: new changeset get the phase of source:
338 338 $ hg id -n
339 339 5
340 340 $ hg rebase -s9 -d0
341 341 rebasing 9:2b23e52411f4 "D" (tip)
342 342 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-f942decf-backup.hg (glob)
343 343 $ hg id -n # check we updated back to parent
344 344 5
345 345 $ hg log --template "{phase}\n" -r 9
346 346 draft
347 347 $ hg rebase -s9 -d1
348 348 rebasing 9:2cb10d0cfc6c "D" (tip)
349 349 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-ddb0f256-backup.hg (glob)
350 350 $ hg log --template "{phase}\n" -r 9
351 351 draft
352 352 $ hg phase --force --secret 9
353 353 $ hg rebase -s9 -d0
354 354 rebasing 9:c5b12b67163a "D" (tip)
355 355 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-4e372053-backup.hg (glob)
356 356 $ hg log --template "{phase}\n" -r 9
357 357 secret
358 358 $ hg rebase -s9 -d1
359 359 rebasing 9:2a0524f868ac "D" (tip)
360 360 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-cefd8574-backup.hg (glob)
361 361 $ hg log --template "{phase}\n" -r 9
362 362 secret
363 363 Source phase lower than destination phase: new changeset get the phase of destination:
364 364 $ hg rebase -s8 -d9
365 365 rebasing 8:6d4f22462821 "C"
366 366 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-3441f70b-backup.hg (glob)
367 367 $ hg log --template "{phase}\n" -r 'rev(9)'
368 368 secret
369 369
370 370 $ cd ..
371 371
372 372 Test for revset
373 373
374 374 We need a bit different graph
375 375 All destination are B
376 376
377 377 $ hg init ah
378 378 $ cd ah
379 379 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
380 380 adding changesets
381 381 adding manifests
382 382 adding file changes
383 383 added 9 changesets with 9 changes to 9 files (+2 heads)
384 384 (run 'hg heads' to see heads, 'hg merge' to merge)
385 385 $ hg tglog
386 386 o 8: 'I'
387 387 |
388 388 o 7: 'H'
389 389 |
390 390 o 6: 'G'
391 391 |
392 392 | o 5: 'F'
393 393 | |
394 394 | o 4: 'E'
395 395 |/
396 396 o 3: 'D'
397 397 |
398 398 o 2: 'C'
399 399 |
400 400 | o 1: 'B'
401 401 |/
402 402 o 0: 'A'
403 403
404 404 $ cd ..
405 405
406 406
407 407 Simple case with keep:
408 408
409 409 Source on have two descendant heads but ask for one
410 410
411 411 $ hg clone -q -u . ah ah1
412 412 $ cd ah1
413 413 $ hg rebase -r '2::8' -d 1
414 414 abort: can't remove original changesets with unrebased descendants
415 415 (use --keep to keep original changesets)
416 416 [255]
417 417 $ hg rebase -r '2::8' -d 1 -k
418 418 rebasing 2:c9e50f6cdc55 "C"
419 419 rebasing 3:ffd453c31098 "D"
420 420 rebasing 6:3d8a618087a7 "G"
421 421 rebasing 7:72434a4e60b0 "H"
422 422 rebasing 8:479ddb54a924 "I" (tip)
423 423 $ hg tglog
424 424 o 13: 'I'
425 425 |
426 426 o 12: 'H'
427 427 |
428 428 o 11: 'G'
429 429 |
430 430 o 10: 'D'
431 431 |
432 432 o 9: 'C'
433 433 |
434 434 | o 8: 'I'
435 435 | |
436 436 | o 7: 'H'
437 437 | |
438 438 | o 6: 'G'
439 439 | |
440 440 | | o 5: 'F'
441 441 | | |
442 442 | | o 4: 'E'
443 443 | |/
444 444 | o 3: 'D'
445 445 | |
446 446 | o 2: 'C'
447 447 | |
448 448 o | 1: 'B'
449 449 |/
450 450 o 0: 'A'
451 451
452 452
453 453 $ cd ..
454 454
455 455 Base on have one descendant heads we ask for but common ancestor have two
456 456
457 457 $ hg clone -q -u . ah ah2
458 458 $ cd ah2
459 459 $ hg rebase -r '3::8' -d 1
460 460 abort: can't remove original changesets with unrebased descendants
461 461 (use --keep to keep original changesets)
462 462 [255]
463 463 $ hg rebase -r '3::8' -d 1 --keep
464 464 rebasing 3:ffd453c31098 "D"
465 465 rebasing 6:3d8a618087a7 "G"
466 466 rebasing 7:72434a4e60b0 "H"
467 467 rebasing 8:479ddb54a924 "I" (tip)
468 468 $ hg tglog
469 469 o 12: 'I'
470 470 |
471 471 o 11: 'H'
472 472 |
473 473 o 10: 'G'
474 474 |
475 475 o 9: 'D'
476 476 |
477 477 | o 8: 'I'
478 478 | |
479 479 | o 7: 'H'
480 480 | |
481 481 | o 6: 'G'
482 482 | |
483 483 | | o 5: 'F'
484 484 | | |
485 485 | | o 4: 'E'
486 486 | |/
487 487 | o 3: 'D'
488 488 | |
489 489 | o 2: 'C'
490 490 | |
491 491 o | 1: 'B'
492 492 |/
493 493 o 0: 'A'
494 494
495 495
496 496 $ cd ..
497 497
498 498 rebase subset
499 499
500 500 $ hg clone -q -u . ah ah3
501 501 $ cd ah3
502 502 $ hg rebase -r '3::7' -d 1
503 503 abort: can't remove original changesets with unrebased descendants
504 504 (use --keep to keep original changesets)
505 505 [255]
506 506 $ hg rebase -r '3::7' -d 1 --keep
507 507 rebasing 3:ffd453c31098 "D"
508 508 rebasing 6:3d8a618087a7 "G"
509 509 rebasing 7:72434a4e60b0 "H"
510 510 $ hg tglog
511 511 o 11: 'H'
512 512 |
513 513 o 10: 'G'
514 514 |
515 515 o 9: 'D'
516 516 |
517 517 | o 8: 'I'
518 518 | |
519 519 | o 7: 'H'
520 520 | |
521 521 | o 6: 'G'
522 522 | |
523 523 | | o 5: 'F'
524 524 | | |
525 525 | | o 4: 'E'
526 526 | |/
527 527 | o 3: 'D'
528 528 | |
529 529 | o 2: 'C'
530 530 | |
531 531 o | 1: 'B'
532 532 |/
533 533 o 0: 'A'
534 534
535 535
536 536 $ cd ..
537 537
538 538 rebase subset with multiple head
539 539
540 540 $ hg clone -q -u . ah ah4
541 541 $ cd ah4
542 542 $ hg rebase -r '3::(7+5)' -d 1
543 543 abort: can't remove original changesets with unrebased descendants
544 544 (use --keep to keep original changesets)
545 545 [255]
546 546 $ hg rebase -r '3::(7+5)' -d 1 --keep
547 547 rebasing 3:ffd453c31098 "D"
548 548 rebasing 4:c01897464e7f "E"
549 549 rebasing 5:41bfcc75ed73 "F"
550 550 rebasing 6:3d8a618087a7 "G"
551 551 rebasing 7:72434a4e60b0 "H"
552 552 $ hg tglog
553 553 o 13: 'H'
554 554 |
555 555 o 12: 'G'
556 556 |
557 557 | o 11: 'F'
558 558 | |
559 559 | o 10: 'E'
560 560 |/
561 561 o 9: 'D'
562 562 |
563 563 | o 8: 'I'
564 564 | |
565 565 | o 7: 'H'
566 566 | |
567 567 | o 6: 'G'
568 568 | |
569 569 | | o 5: 'F'
570 570 | | |
571 571 | | o 4: 'E'
572 572 | |/
573 573 | o 3: 'D'
574 574 | |
575 575 | o 2: 'C'
576 576 | |
577 577 o | 1: 'B'
578 578 |/
579 579 o 0: 'A'
580 580
581 581
582 582 $ cd ..
583 583
584 584 More advanced tests
585 585
586 586 rebase on ancestor with revset
587 587
588 588 $ hg clone -q -u . ah ah5
589 589 $ cd ah5
590 590 $ hg rebase -r '6::' -d 2
591 591 rebasing 6:3d8a618087a7 "G"
592 592 rebasing 7:72434a4e60b0 "H"
593 593 rebasing 8:479ddb54a924 "I" (tip)
594 594 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-b4f73f31-backup.hg (glob)
595 595 $ hg tglog
596 596 o 8: 'I'
597 597 |
598 598 o 7: 'H'
599 599 |
600 600 o 6: 'G'
601 601 |
602 602 | o 5: 'F'
603 603 | |
604 604 | o 4: 'E'
605 605 | |
606 606 | o 3: 'D'
607 607 |/
608 608 o 2: 'C'
609 609 |
610 610 | o 1: 'B'
611 611 |/
612 612 o 0: 'A'
613 613
614 614 $ cd ..
615 615
616 616
617 617 rebase with multiple root.
618 618 We rebase E and G on B
619 619 We would expect heads are I, F if it was supported
620 620
621 621 $ hg clone -q -u . ah ah6
622 622 $ cd ah6
623 623 $ hg rebase -r '(4+6)::' -d 1
624 624 rebasing 4:c01897464e7f "E"
625 625 rebasing 5:41bfcc75ed73 "F"
626 626 rebasing 6:3d8a618087a7 "G"
627 627 rebasing 7:72434a4e60b0 "H"
628 628 rebasing 8:479ddb54a924 "I" (tip)
629 629 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-aae93a24-backup.hg (glob)
630 630 $ hg tglog
631 631 o 8: 'I'
632 632 |
633 633 o 7: 'H'
634 634 |
635 635 o 6: 'G'
636 636 |
637 637 | o 5: 'F'
638 638 | |
639 639 | o 4: 'E'
640 640 |/
641 641 | o 3: 'D'
642 642 | |
643 643 | o 2: 'C'
644 644 | |
645 645 o | 1: 'B'
646 646 |/
647 647 o 0: 'A'
648 648
649 649 $ cd ..
650 650
651 651 More complex rebase with multiple roots
652 652 each root have a different common ancestor with the destination and this is a detach
653 653
654 654 (setup)
655 655
656 656 $ hg clone -q -u . a a8
657 657 $ cd a8
658 658 $ echo I > I
659 659 $ hg add I
660 660 $ hg commit -m I
661 661 $ hg up 4
662 662 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
663 663 $ echo I > J
664 664 $ hg add J
665 665 $ hg commit -m J
666 666 created new head
667 667 $ echo I > K
668 668 $ hg add K
669 669 $ hg commit -m K
670 670 $ hg tglog
671 671 @ 10: 'K'
672 672 |
673 673 o 9: 'J'
674 674 |
675 675 | o 8: 'I'
676 676 | |
677 677 | o 7: 'H'
678 678 | |
679 679 +---o 6: 'G'
680 680 | |/
681 681 | o 5: 'F'
682 682 | |
683 683 o | 4: 'E'
684 684 |/
685 685 | o 3: 'D'
686 686 | |
687 687 | o 2: 'C'
688 688 | |
689 689 | o 1: 'B'
690 690 |/
691 691 o 0: 'A'
692 692
693 693 (actual test)
694 694
695 695 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
696 696 rebasing 8:e7ec4e813ba6 "I"
697 697 rebasing 10:23a4ace37988 "K" (tip)
698 698 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-b06984b3-backup.hg (glob)
699 699 $ hg log --rev 'children(desc(G))'
700 700 changeset: 9:adb617877056
701 701 parent: 6:eea13746799a
702 702 user: test
703 703 date: Thu Jan 01 00:00:00 1970 +0000
704 704 summary: I
705 705
706 706 changeset: 10:882431a34a0e
707 707 tag: tip
708 708 parent: 6:eea13746799a
709 709 user: test
710 710 date: Thu Jan 01 00:00:00 1970 +0000
711 711 summary: K
712 712
713 713 $ hg tglog
714 714 @ 10: 'K'
715 715 |
716 716 | o 9: 'I'
717 717 |/
718 718 | o 8: 'J'
719 719 | |
720 720 | | o 7: 'H'
721 721 | | |
722 722 o---+ 6: 'G'
723 723 |/ /
724 724 | o 5: 'F'
725 725 | |
726 726 o | 4: 'E'
727 727 |/
728 728 | o 3: 'D'
729 729 | |
730 730 | o 2: 'C'
731 731 | |
732 732 | o 1: 'B'
733 733 |/
734 734 o 0: 'A'
735 735
736 736
737 737 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
738 738
739 739 $ cd ..
740 740 $ hg init cwd-vanish
741 741 $ cd cwd-vanish
742 742 $ touch initial-file
743 743 $ hg add initial-file
744 744 $ hg commit -m 'initial commit'
745 745 $ touch dest-file
746 746 $ hg add dest-file
747 747 $ hg commit -m 'dest commit'
748 748 $ hg up 0
749 749 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
750 750 $ touch other-file
751 751 $ hg add other-file
752 752 $ hg commit -m 'first source commit'
753 753 created new head
754 754 $ mkdir subdir
755 755 $ cd subdir
756 756 $ touch subfile
757 757 $ hg add subfile
758 758 $ hg commit -m 'second source with subdir'
759 759 $ hg rebase -b . -d 1 --traceback
760 760 rebasing 2:779a07b1b7a0 "first source commit"
761 current directory was removed
762 (consider changing to repo root: $TESTTMP/cwd-vanish)
761 763 rebasing 3:a7d6f3a00bf3 "second source with subdir" (tip)
762 764 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-853e0073-backup.hg (glob)
763 765
764 766 Test that rebase is done in topo order (issue5370)
765 767
766 768 $ cd ..
767 769 $ hg init order
768 770 $ cd order
769 771 $ touch a && hg add a && hg ci -m A
770 772 $ touch b && hg add b && hg ci -m B
771 773 $ touch c && hg add c && hg ci -m C
772 774 $ hg up 1
773 775 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
774 776 $ touch d && hg add d && hg ci -m D
775 777 created new head
776 778 $ hg up 2
777 779 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
778 780 $ touch e && hg add e && hg ci -m E
779 781 $ hg up 3
780 782 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
781 783 $ touch f && hg add f && hg ci -m F
782 784 $ hg up 0
783 785 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
784 786 $ touch g && hg add g && hg ci -m G
785 787 created new head
786 788
787 789 $ hg tglog
788 790 @ 6: 'G'
789 791 |
790 792 | o 5: 'F'
791 793 | |
792 794 | | o 4: 'E'
793 795 | | |
794 796 | o | 3: 'D'
795 797 | | |
796 798 | | o 2: 'C'
797 799 | |/
798 800 | o 1: 'B'
799 801 |/
800 802 o 0: 'A'
801 803
802 804
803 805 $ hg rebase -s 1 -d 6
804 806 rebasing 1:76035bbd54bd "B"
805 807 rebasing 2:d84f5cfaaf14 "C"
806 808 rebasing 4:82ae8dc7a9b7 "E"
807 809 rebasing 3:ab709c9f7171 "D"
808 810 rebasing 5:412b391de760 "F"
809 811 saved backup bundle to $TESTTMP/cwd-vanish/order/.hg/strip-backup/76035bbd54bd-e341bc99-backup.hg (glob)
810 812
811 813 $ hg tglog
812 814 o 6: 'F'
813 815 |
814 816 o 5: 'D'
815 817 |
816 818 | o 4: 'E'
817 819 | |
818 820 | o 3: 'C'
819 821 |/
820 822 o 2: 'B'
821 823 |
822 824 @ 1: 'G'
823 825 |
824 826 o 0: 'A'
825 827
826 828
827 829 Test experimental revset
828 830 ========================
829 831
830 832 $ cd ..
831 833
832 834 Make the repo a bit more interresting
833 835
834 836 $ hg up 1
835 837 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
836 838 $ echo aaa > aaa
837 839 $ hg add aaa
838 840 $ hg commit -m aaa
839 841 created new head
840 842 $ hg log -G
841 843 @ changeset: 4:5f7bc9025ed2
842 844 | tag: tip
843 845 | parent: 1:58d79cc1cf43
844 846 | user: test
845 847 | date: Thu Jan 01 00:00:00 1970 +0000
846 848 | summary: aaa
847 849 |
848 850 | o changeset: 3:1910d5ff34ea
849 851 | | user: test
850 852 | | date: Thu Jan 01 00:00:00 1970 +0000
851 853 | | summary: second source with subdir
852 854 | |
853 855 | o changeset: 2:82901330b6ef
854 856 |/ user: test
855 857 | date: Thu Jan 01 00:00:00 1970 +0000
856 858 | summary: first source commit
857 859 |
858 860 o changeset: 1:58d79cc1cf43
859 861 | user: test
860 862 | date: Thu Jan 01 00:00:00 1970 +0000
861 863 | summary: dest commit
862 864 |
863 865 o changeset: 0:e94b687f7da3
864 866 user: test
865 867 date: Thu Jan 01 00:00:00 1970 +0000
866 868 summary: initial commit
867 869
868 870
869 871 Testing from lower head
870 872
871 873 $ hg up 3
872 874 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
873 875 $ hg log -r '_destrebase()'
874 876 changeset: 4:5f7bc9025ed2
875 877 tag: tip
876 878 parent: 1:58d79cc1cf43
877 879 user: test
878 880 date: Thu Jan 01 00:00:00 1970 +0000
879 881 summary: aaa
880 882
881 883
882 884 Testing from upper head
883 885
884 886 $ hg log -r '_destrebase(4)'
885 887 changeset: 3:1910d5ff34ea
886 888 user: test
887 889 date: Thu Jan 01 00:00:00 1970 +0000
888 890 summary: second source with subdir
889 891
890 892 $ hg up 4
891 893 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
892 894 $ hg log -r '_destrebase()'
893 895 changeset: 3:1910d5ff34ea
894 896 user: test
895 897 date: Thu Jan 01 00:00:00 1970 +0000
896 898 summary: second source with subdir
897 899
@@ -1,74 +1,86 b''
1 1 Test update logic when there are renames or weird same-name cases between dirs
2 2 and files
3 3
4 4 Update with local changes across a file rename
5 5
6 6 $ hg init r1 && cd r1
7 7
8 8 $ echo a > a
9 9 $ hg add a
10 10 $ hg ci -m a
11 11
12 12 $ hg mv a b
13 13 $ hg ci -m rename
14 14
15 15 $ echo b > b
16 16 $ hg ci -m change
17 17
18 18 $ hg up -q 0
19 19
20 20 $ echo c > a
21 21
22 22 $ hg up
23 23 merging a and b to b
24 24 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
25 25 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
26 26 use 'hg resolve' to retry unresolved file merges
27 27 [1]
28 28
29 29 Test update when local untracked directory exists with the same name as a
30 30 tracked file in a commit we are updating to
31 31 $ hg init r2 && cd r2
32 32 $ echo root > root && hg ci -Am root # rev 0
33 33 adding root
34 34 $ echo text > name && hg ci -Am "name is a file" # rev 1
35 35 adding name
36 36 $ hg up 0
37 37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
38 38 $ mkdir name
39 39 $ hg up 1
40 40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 41
42 42 Test update when local untracked directory exists with some files in it and has
43 43 the same name a tracked file in a commit we are updating to. In future this
44 44 should be updated to give an friendlier error message, but now we should just
45 45 make sure that this does not erase untracked data
46 46 $ hg up 0
47 47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 48 $ mkdir name
49 49 $ echo text > name/file
50 50 $ hg st
51 51 ? name/file
52 52 $ hg up 1
53 53 abort: *: '$TESTTMP/r1/r2/name' (glob)
54 54 [255]
55 55 $ cd ..
56 56
57 57 #if symlink
58 58
59 59 Test update when two commits have symlinks that point to different folders
60 60 $ hg init r3 && cd r3
61 61 $ echo root > root && hg ci -Am root
62 62 adding root
63 63 $ mkdir folder1 && mkdir folder2
64 64 $ ln -s folder1 folder
65 65 $ hg ci -Am "symlink to folder1"
66 66 adding folder
67 67 $ rm folder
68 68 $ ln -s folder2 folder
69 69 $ hg ci -Am "symlink to folder2"
70 70 $ hg up 1
71 71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 72 $ cd ..
73 73
74 74 #endif
75
76 Test that warning is printed if cwd is deleted during update
77 $ hg init r4 && cd r4
78 $ mkdir dir
79 $ cd dir
80 $ echo a > a
81 $ echo b > b
82 $ hg add a b
83 $ hg ci -m "file and dir"
84 $ hg up -q null
85 current directory was removed
86 (consider changing to repo root: $TESTTMP/r1/r4)
General Comments 0
You need to be logged in to leave comments. Login now