##// END OF EJS Templates
dirstate: properly clean-up some more merge state on setparents
Matt Mackall -
r22895:dfa44e25 default
parent child Browse files
Show More
@@ -1,923 +1,928
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-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 node import nullid
9 9 from i18n import _
10 10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
11 11 import os, stat, errno, gc
12 12
13 13 propertycache = util.propertycache
14 14 filecache = scmutil.filecache
15 15 _rangemask = 0x7fffffff
16 16
17 17 dirstatetuple = parsers.dirstatetuple
18 18
19 19 class repocache(filecache):
20 20 """filecache for files in .hg/"""
21 21 def join(self, obj, fname):
22 22 return obj._opener.join(fname)
23 23
24 24 class rootcache(filecache):
25 25 """filecache for files in the repository root"""
26 26 def join(self, obj, fname):
27 27 return obj._join(fname)
28 28
29 29 class dirstate(object):
30 30
31 31 def __init__(self, opener, ui, root, validate):
32 32 '''Create a new dirstate object.
33 33
34 34 opener is an open()-like callable that can be used to open the
35 35 dirstate file; root is the root of the directory tracked by
36 36 the dirstate.
37 37 '''
38 38 self._opener = opener
39 39 self._validate = validate
40 40 self._root = root
41 41 self._rootdir = os.path.join(root, '')
42 42 self._dirty = False
43 43 self._dirtypl = False
44 44 self._lastnormaltime = 0
45 45 self._ui = ui
46 46 self._filecache = {}
47 47 self._parentwriters = 0
48 48
49 49 def beginparentchange(self):
50 50 '''Marks the beginning of a set of changes that involve changing
51 51 the dirstate parents. If there is an exception during this time,
52 52 the dirstate will not be written when the wlock is released. This
53 53 prevents writing an incoherent dirstate where the parent doesn't
54 54 match the contents.
55 55 '''
56 56 self._parentwriters += 1
57 57
58 58 def endparentchange(self):
59 59 '''Marks the end of a set of changes that involve changing the
60 60 dirstate parents. Once all parent changes have been marked done,
61 61 the wlock will be free to write the dirstate on release.
62 62 '''
63 63 if self._parentwriters > 0:
64 64 self._parentwriters -= 1
65 65
66 66 def pendingparentchange(self):
67 67 '''Returns true if the dirstate is in the middle of a set of changes
68 68 that modify the dirstate parent.
69 69 '''
70 70 return self._parentwriters > 0
71 71
72 72 @propertycache
73 73 def _map(self):
74 74 '''Return the dirstate contents as a map from filename to
75 75 (state, mode, size, time).'''
76 76 self._read()
77 77 return self._map
78 78
79 79 @propertycache
80 80 def _copymap(self):
81 81 self._read()
82 82 return self._copymap
83 83
84 84 @propertycache
85 85 def _foldmap(self):
86 86 f = {}
87 87 normcase = util.normcase
88 88 for name, s in self._map.iteritems():
89 89 if s[0] != 'r':
90 90 f[normcase(name)] = name
91 91 for name in self._dirs:
92 92 f[normcase(name)] = name
93 93 f['.'] = '.' # prevents useless util.fspath() invocation
94 94 return f
95 95
96 96 @repocache('branch')
97 97 def _branch(self):
98 98 try:
99 99 return self._opener.read("branch").strip() or "default"
100 100 except IOError, inst:
101 101 if inst.errno != errno.ENOENT:
102 102 raise
103 103 return "default"
104 104
105 105 @propertycache
106 106 def _pl(self):
107 107 try:
108 108 fp = self._opener("dirstate")
109 109 st = fp.read(40)
110 110 fp.close()
111 111 l = len(st)
112 112 if l == 40:
113 113 return st[:20], st[20:40]
114 114 elif l > 0 and l < 40:
115 115 raise util.Abort(_('working directory state appears damaged!'))
116 116 except IOError, err:
117 117 if err.errno != errno.ENOENT:
118 118 raise
119 119 return [nullid, nullid]
120 120
121 121 @propertycache
122 122 def _dirs(self):
123 123 return scmutil.dirs(self._map, 'r')
124 124
125 125 def dirs(self):
126 126 return self._dirs
127 127
128 128 @rootcache('.hgignore')
129 129 def _ignore(self):
130 130 files = [self._join('.hgignore')]
131 131 for name, path in self._ui.configitems("ui"):
132 132 if name == 'ignore' or name.startswith('ignore.'):
133 133 files.append(util.expandpath(path))
134 134 return ignore.ignore(self._root, files, self._ui.warn)
135 135
136 136 @propertycache
137 137 def _slash(self):
138 138 return self._ui.configbool('ui', 'slash') and os.sep != '/'
139 139
140 140 @propertycache
141 141 def _checklink(self):
142 142 return util.checklink(self._root)
143 143
144 144 @propertycache
145 145 def _checkexec(self):
146 146 return util.checkexec(self._root)
147 147
148 148 @propertycache
149 149 def _checkcase(self):
150 150 return not util.checkcase(self._join('.hg'))
151 151
152 152 def _join(self, f):
153 153 # much faster than os.path.join()
154 154 # it's safe because f is always a relative path
155 155 return self._rootdir + f
156 156
157 157 def flagfunc(self, buildfallback):
158 158 if self._checklink and self._checkexec:
159 159 def f(x):
160 160 try:
161 161 st = os.lstat(self._join(x))
162 162 if util.statislink(st):
163 163 return 'l'
164 164 if util.statisexec(st):
165 165 return 'x'
166 166 except OSError:
167 167 pass
168 168 return ''
169 169 return f
170 170
171 171 fallback = buildfallback()
172 172 if self._checklink:
173 173 def f(x):
174 174 if os.path.islink(self._join(x)):
175 175 return 'l'
176 176 if 'x' in fallback(x):
177 177 return 'x'
178 178 return ''
179 179 return f
180 180 if self._checkexec:
181 181 def f(x):
182 182 if 'l' in fallback(x):
183 183 return 'l'
184 184 if util.isexec(self._join(x)):
185 185 return 'x'
186 186 return ''
187 187 return f
188 188 else:
189 189 return fallback
190 190
191 191 @propertycache
192 192 def _cwd(self):
193 193 return os.getcwd()
194 194
195 195 def getcwd(self):
196 196 cwd = self._cwd
197 197 if cwd == self._root:
198 198 return ''
199 199 # self._root ends with a path separator if self._root is '/' or 'C:\'
200 200 rootsep = self._root
201 201 if not util.endswithsep(rootsep):
202 202 rootsep += os.sep
203 203 if cwd.startswith(rootsep):
204 204 return cwd[len(rootsep):]
205 205 else:
206 206 # we're outside the repo. return an absolute path.
207 207 return cwd
208 208
209 209 def pathto(self, f, cwd=None):
210 210 if cwd is None:
211 211 cwd = self.getcwd()
212 212 path = util.pathto(self._root, cwd, f)
213 213 if self._slash:
214 214 return util.pconvert(path)
215 215 return path
216 216
217 217 def __getitem__(self, key):
218 218 '''Return the current state of key (a filename) in the dirstate.
219 219
220 220 States are:
221 221 n normal
222 222 m needs merging
223 223 r marked for removal
224 224 a marked for addition
225 225 ? not tracked
226 226 '''
227 227 return self._map.get(key, ("?",))[0]
228 228
229 229 def __contains__(self, key):
230 230 return key in self._map
231 231
232 232 def __iter__(self):
233 233 for x in sorted(self._map):
234 234 yield x
235 235
236 236 def iteritems(self):
237 237 return self._map.iteritems()
238 238
239 239 def parents(self):
240 240 return [self._validate(p) for p in self._pl]
241 241
242 242 def p1(self):
243 243 return self._validate(self._pl[0])
244 244
245 245 def p2(self):
246 246 return self._validate(self._pl[1])
247 247
248 248 def branch(self):
249 249 return encoding.tolocal(self._branch)
250 250
251 251 def setparents(self, p1, p2=nullid):
252 252 """Set dirstate parents to p1 and p2.
253 253
254 254 When moving from two parents to one, 'm' merged entries a
255 255 adjusted to normal and previous copy records discarded and
256 256 returned by the call.
257 257
258 258 See localrepo.setparents()
259 259 """
260 260 if self._parentwriters == 0:
261 261 raise ValueError("cannot set dirstate parent without "
262 262 "calling dirstate.beginparentchange")
263 263
264 264 self._dirty = self._dirtypl = True
265 265 oldp2 = self._pl[1]
266 266 self._pl = p1, p2
267 267 copies = {}
268 268 if oldp2 != nullid and p2 == nullid:
269 # Discard 'm' markers when moving away from a merge state
270 269 for f, s in self._map.iteritems():
270 # Discard 'm' markers when moving away from a merge state
271 271 if s[0] == 'm':
272 272 if f in self._copymap:
273 273 copies[f] = self._copymap[f]
274 274 self.normallookup(f)
275 # Also fix up otherparent markers
276 elif s[0] == 'n' and s[2] == -2:
277 if f in self._copymap:
278 copies[f] = self._copymap[f]
279 self.add(f)
275 280 return copies
276 281
277 282 def setbranch(self, branch):
278 283 self._branch = encoding.fromlocal(branch)
279 284 f = self._opener('branch', 'w', atomictemp=True)
280 285 try:
281 286 f.write(self._branch + '\n')
282 287 f.close()
283 288
284 289 # make sure filecache has the correct stat info for _branch after
285 290 # replacing the underlying file
286 291 ce = self._filecache['_branch']
287 292 if ce:
288 293 ce.refresh()
289 294 except: # re-raises
290 295 f.discard()
291 296 raise
292 297
293 298 def _read(self):
294 299 self._map = {}
295 300 self._copymap = {}
296 301 try:
297 302 st = self._opener.read("dirstate")
298 303 except IOError, err:
299 304 if err.errno != errno.ENOENT:
300 305 raise
301 306 return
302 307 if not st:
303 308 return
304 309
305 310 # Python's garbage collector triggers a GC each time a certain number
306 311 # of container objects (the number being defined by
307 312 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
308 313 # for each file in the dirstate. The C version then immediately marks
309 314 # them as not to be tracked by the collector. However, this has no
310 315 # effect on when GCs are triggered, only on what objects the GC looks
311 316 # into. This means that O(number of files) GCs are unavoidable.
312 317 # Depending on when in the process's lifetime the dirstate is parsed,
313 318 # this can get very expensive. As a workaround, disable GC while
314 319 # parsing the dirstate.
315 320 gcenabled = gc.isenabled()
316 321 gc.disable()
317 322 try:
318 323 p = parsers.parse_dirstate(self._map, self._copymap, st)
319 324 finally:
320 325 if gcenabled:
321 326 gc.enable()
322 327 if not self._dirtypl:
323 328 self._pl = p
324 329
325 330 def invalidate(self):
326 331 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
327 332 "_ignore"):
328 333 if a in self.__dict__:
329 334 delattr(self, a)
330 335 self._lastnormaltime = 0
331 336 self._dirty = False
332 337 self._parentwriters = 0
333 338
334 339 def copy(self, source, dest):
335 340 """Mark dest as a copy of source. Unmark dest if source is None."""
336 341 if source == dest:
337 342 return
338 343 self._dirty = True
339 344 if source is not None:
340 345 self._copymap[dest] = source
341 346 elif dest in self._copymap:
342 347 del self._copymap[dest]
343 348
344 349 def copied(self, file):
345 350 return self._copymap.get(file, None)
346 351
347 352 def copies(self):
348 353 return self._copymap
349 354
350 355 def _droppath(self, f):
351 356 if self[f] not in "?r" and "_dirs" in self.__dict__:
352 357 self._dirs.delpath(f)
353 358
354 359 def _addpath(self, f, state, mode, size, mtime):
355 360 oldstate = self[f]
356 361 if state == 'a' or oldstate == 'r':
357 362 scmutil.checkfilename(f)
358 363 if f in self._dirs:
359 364 raise util.Abort(_('directory %r already in dirstate') % f)
360 365 # shadows
361 366 for d in scmutil.finddirs(f):
362 367 if d in self._dirs:
363 368 break
364 369 if d in self._map and self[d] != 'r':
365 370 raise util.Abort(
366 371 _('file %r in dirstate clashes with %r') % (d, f))
367 372 if oldstate in "?r" and "_dirs" in self.__dict__:
368 373 self._dirs.addpath(f)
369 374 self._dirty = True
370 375 self._map[f] = dirstatetuple(state, mode, size, mtime)
371 376
372 377 def normal(self, f):
373 378 '''Mark a file normal and clean.'''
374 379 s = os.lstat(self._join(f))
375 380 mtime = int(s.st_mtime)
376 381 self._addpath(f, 'n', s.st_mode,
377 382 s.st_size & _rangemask, mtime & _rangemask)
378 383 if f in self._copymap:
379 384 del self._copymap[f]
380 385 if mtime > self._lastnormaltime:
381 386 # Remember the most recent modification timeslot for status(),
382 387 # to make sure we won't miss future size-preserving file content
383 388 # modifications that happen within the same timeslot.
384 389 self._lastnormaltime = mtime
385 390
386 391 def normallookup(self, f):
387 392 '''Mark a file normal, but possibly dirty.'''
388 393 if self._pl[1] != nullid and f in self._map:
389 394 # if there is a merge going on and the file was either
390 395 # in state 'm' (-1) or coming from other parent (-2) before
391 396 # being removed, restore that state.
392 397 entry = self._map[f]
393 398 if entry[0] == 'r' and entry[2] in (-1, -2):
394 399 source = self._copymap.get(f)
395 400 if entry[2] == -1:
396 401 self.merge(f)
397 402 elif entry[2] == -2:
398 403 self.otherparent(f)
399 404 if source:
400 405 self.copy(source, f)
401 406 return
402 407 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
403 408 return
404 409 self._addpath(f, 'n', 0, -1, -1)
405 410 if f in self._copymap:
406 411 del self._copymap[f]
407 412
408 413 def otherparent(self, f):
409 414 '''Mark as coming from the other parent, always dirty.'''
410 415 if self._pl[1] == nullid:
411 416 raise util.Abort(_("setting %r to other parent "
412 417 "only allowed in merges") % f)
413 418 self._addpath(f, 'n', 0, -2, -1)
414 419 if f in self._copymap:
415 420 del self._copymap[f]
416 421
417 422 def add(self, f):
418 423 '''Mark a file added.'''
419 424 self._addpath(f, 'a', 0, -1, -1)
420 425 if f in self._copymap:
421 426 del self._copymap[f]
422 427
423 428 def remove(self, f):
424 429 '''Mark a file removed.'''
425 430 self._dirty = True
426 431 self._droppath(f)
427 432 size = 0
428 433 if self._pl[1] != nullid and f in self._map:
429 434 # backup the previous state
430 435 entry = self._map[f]
431 436 if entry[0] == 'm': # merge
432 437 size = -1
433 438 elif entry[0] == 'n' and entry[2] == -2: # other parent
434 439 size = -2
435 440 self._map[f] = dirstatetuple('r', 0, size, 0)
436 441 if size == 0 and f in self._copymap:
437 442 del self._copymap[f]
438 443
439 444 def merge(self, f):
440 445 '''Mark a file merged.'''
441 446 if self._pl[1] == nullid:
442 447 return self.normallookup(f)
443 448 s = os.lstat(self._join(f))
444 449 self._addpath(f, 'm', s.st_mode,
445 450 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
446 451 if f in self._copymap:
447 452 del self._copymap[f]
448 453
449 454 def drop(self, f):
450 455 '''Drop a file from the dirstate'''
451 456 if f in self._map:
452 457 self._dirty = True
453 458 self._droppath(f)
454 459 del self._map[f]
455 460
456 461 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
457 462 normed = util.normcase(path)
458 463 folded = self._foldmap.get(normed, None)
459 464 if folded is None:
460 465 if isknown:
461 466 folded = path
462 467 else:
463 468 if exists is None:
464 469 exists = os.path.lexists(os.path.join(self._root, path))
465 470 if not exists:
466 471 # Maybe a path component exists
467 472 if not ignoremissing and '/' in path:
468 473 d, f = path.rsplit('/', 1)
469 474 d = self._normalize(d, isknown, ignoremissing, None)
470 475 folded = d + "/" + f
471 476 else:
472 477 # No path components, preserve original case
473 478 folded = path
474 479 else:
475 480 # recursively normalize leading directory components
476 481 # against dirstate
477 482 if '/' in normed:
478 483 d, f = normed.rsplit('/', 1)
479 484 d = self._normalize(d, isknown, ignoremissing, True)
480 485 r = self._root + "/" + d
481 486 folded = d + "/" + util.fspath(f, r)
482 487 else:
483 488 folded = util.fspath(normed, self._root)
484 489 self._foldmap[normed] = folded
485 490
486 491 return folded
487 492
488 493 def normalize(self, path, isknown=False, ignoremissing=False):
489 494 '''
490 495 normalize the case of a pathname when on a casefolding filesystem
491 496
492 497 isknown specifies whether the filename came from walking the
493 498 disk, to avoid extra filesystem access.
494 499
495 500 If ignoremissing is True, missing path are returned
496 501 unchanged. Otherwise, we try harder to normalize possibly
497 502 existing path components.
498 503
499 504 The normalized case is determined based on the following precedence:
500 505
501 506 - version of name already stored in the dirstate
502 507 - version of name stored on disk
503 508 - version provided via command arguments
504 509 '''
505 510
506 511 if self._checkcase:
507 512 return self._normalize(path, isknown, ignoremissing)
508 513 return path
509 514
510 515 def clear(self):
511 516 self._map = {}
512 517 if "_dirs" in self.__dict__:
513 518 delattr(self, "_dirs")
514 519 self._copymap = {}
515 520 self._pl = [nullid, nullid]
516 521 self._lastnormaltime = 0
517 522 self._dirty = True
518 523
519 524 def rebuild(self, parent, allfiles, changedfiles=None):
520 525 changedfiles = changedfiles or allfiles
521 526 oldmap = self._map
522 527 self.clear()
523 528 for f in allfiles:
524 529 if f not in changedfiles:
525 530 self._map[f] = oldmap[f]
526 531 else:
527 532 if 'x' in allfiles.flags(f):
528 533 self._map[f] = dirstatetuple('n', 0777, -1, 0)
529 534 else:
530 535 self._map[f] = dirstatetuple('n', 0666, -1, 0)
531 536 self._pl = (parent, nullid)
532 537 self._dirty = True
533 538
534 539 def write(self):
535 540 if not self._dirty:
536 541 return
537 542
538 543 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
539 544 # timestamp of each entries in dirstate, because of 'now > mtime'
540 545 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
541 546 if delaywrite:
542 547 import time # to avoid useless import
543 548 time.sleep(delaywrite)
544 549
545 550 st = self._opener("dirstate", "w", atomictemp=True)
546 551 # use the modification time of the newly created temporary file as the
547 552 # filesystem's notion of 'now'
548 553 now = util.fstat(st).st_mtime
549 554 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
550 555 st.close()
551 556 self._lastnormaltime = 0
552 557 self._dirty = self._dirtypl = False
553 558
554 559 def _dirignore(self, f):
555 560 if f == '.':
556 561 return False
557 562 if self._ignore(f):
558 563 return True
559 564 for p in scmutil.finddirs(f):
560 565 if self._ignore(p):
561 566 return True
562 567 return False
563 568
564 569 def _walkexplicit(self, match, subrepos):
565 570 '''Get stat data about the files explicitly specified by match.
566 571
567 572 Return a triple (results, dirsfound, dirsnotfound).
568 573 - results is a mapping from filename to stat result. It also contains
569 574 listings mapping subrepos and .hg to None.
570 575 - dirsfound is a list of files found to be directories.
571 576 - dirsnotfound is a list of files that the dirstate thinks are
572 577 directories and that were not found.'''
573 578
574 579 def badtype(mode):
575 580 kind = _('unknown')
576 581 if stat.S_ISCHR(mode):
577 582 kind = _('character device')
578 583 elif stat.S_ISBLK(mode):
579 584 kind = _('block device')
580 585 elif stat.S_ISFIFO(mode):
581 586 kind = _('fifo')
582 587 elif stat.S_ISSOCK(mode):
583 588 kind = _('socket')
584 589 elif stat.S_ISDIR(mode):
585 590 kind = _('directory')
586 591 return _('unsupported file type (type is %s)') % kind
587 592
588 593 matchedir = match.explicitdir
589 594 badfn = match.bad
590 595 dmap = self._map
591 596 normpath = util.normpath
592 597 lstat = os.lstat
593 598 getkind = stat.S_IFMT
594 599 dirkind = stat.S_IFDIR
595 600 regkind = stat.S_IFREG
596 601 lnkkind = stat.S_IFLNK
597 602 join = self._join
598 603 dirsfound = []
599 604 foundadd = dirsfound.append
600 605 dirsnotfound = []
601 606 notfoundadd = dirsnotfound.append
602 607
603 608 if match.matchfn != match.exact and self._checkcase:
604 609 normalize = self._normalize
605 610 else:
606 611 normalize = None
607 612
608 613 files = sorted(match.files())
609 614 subrepos.sort()
610 615 i, j = 0, 0
611 616 while i < len(files) and j < len(subrepos):
612 617 subpath = subrepos[j] + "/"
613 618 if files[i] < subpath:
614 619 i += 1
615 620 continue
616 621 while i < len(files) and files[i].startswith(subpath):
617 622 del files[i]
618 623 j += 1
619 624
620 625 if not files or '.' in files:
621 626 files = ['']
622 627 results = dict.fromkeys(subrepos)
623 628 results['.hg'] = None
624 629
625 630 for ff in files:
626 631 if normalize:
627 632 nf = normalize(normpath(ff), False, True)
628 633 else:
629 634 nf = normpath(ff)
630 635 if nf in results:
631 636 continue
632 637
633 638 try:
634 639 st = lstat(join(nf))
635 640 kind = getkind(st.st_mode)
636 641 if kind == dirkind:
637 642 if nf in dmap:
638 643 # file replaced by dir on disk but still in dirstate
639 644 results[nf] = None
640 645 if matchedir:
641 646 matchedir(nf)
642 647 foundadd(nf)
643 648 elif kind == regkind or kind == lnkkind:
644 649 results[nf] = st
645 650 else:
646 651 badfn(ff, badtype(kind))
647 652 if nf in dmap:
648 653 results[nf] = None
649 654 except OSError, inst: # nf not found on disk - it is dirstate only
650 655 if nf in dmap: # does it exactly match a missing file?
651 656 results[nf] = None
652 657 else: # does it match a missing directory?
653 658 prefix = nf + "/"
654 659 for fn in dmap:
655 660 if fn.startswith(prefix):
656 661 if matchedir:
657 662 matchedir(nf)
658 663 notfoundadd(nf)
659 664 break
660 665 else:
661 666 badfn(ff, inst.strerror)
662 667
663 668 return results, dirsfound, dirsnotfound
664 669
665 670 def walk(self, match, subrepos, unknown, ignored, full=True):
666 671 '''
667 672 Walk recursively through the directory tree, finding all files
668 673 matched by match.
669 674
670 675 If full is False, maybe skip some known-clean files.
671 676
672 677 Return a dict mapping filename to stat-like object (either
673 678 mercurial.osutil.stat instance or return value of os.stat()).
674 679
675 680 '''
676 681 # full is a flag that extensions that hook into walk can use -- this
677 682 # implementation doesn't use it at all. This satisfies the contract
678 683 # because we only guarantee a "maybe".
679 684
680 685 if ignored:
681 686 ignore = util.never
682 687 dirignore = util.never
683 688 elif unknown:
684 689 ignore = self._ignore
685 690 dirignore = self._dirignore
686 691 else:
687 692 # if not unknown and not ignored, drop dir recursion and step 2
688 693 ignore = util.always
689 694 dirignore = util.always
690 695
691 696 matchfn = match.matchfn
692 697 matchalways = match.always()
693 698 matchtdir = match.traversedir
694 699 dmap = self._map
695 700 listdir = osutil.listdir
696 701 lstat = os.lstat
697 702 dirkind = stat.S_IFDIR
698 703 regkind = stat.S_IFREG
699 704 lnkkind = stat.S_IFLNK
700 705 join = self._join
701 706
702 707 exact = skipstep3 = False
703 708 if matchfn == match.exact: # match.exact
704 709 exact = True
705 710 dirignore = util.always # skip step 2
706 711 elif match.files() and not match.anypats(): # match.match, no patterns
707 712 skipstep3 = True
708 713
709 714 if not exact and self._checkcase:
710 715 normalize = self._normalize
711 716 skipstep3 = False
712 717 else:
713 718 normalize = None
714 719
715 720 # step 1: find all explicit files
716 721 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
717 722
718 723 skipstep3 = skipstep3 and not (work or dirsnotfound)
719 724 work = [d for d in work if not dirignore(d)]
720 725 wadd = work.append
721 726
722 727 # step 2: visit subdirectories
723 728 while work:
724 729 nd = work.pop()
725 730 skip = None
726 731 if nd == '.':
727 732 nd = ''
728 733 else:
729 734 skip = '.hg'
730 735 try:
731 736 entries = listdir(join(nd), stat=True, skip=skip)
732 737 except OSError, inst:
733 738 if inst.errno in (errno.EACCES, errno.ENOENT):
734 739 match.bad(self.pathto(nd), inst.strerror)
735 740 continue
736 741 raise
737 742 for f, kind, st in entries:
738 743 if normalize:
739 744 nf = normalize(nd and (nd + "/" + f) or f, True, True)
740 745 else:
741 746 nf = nd and (nd + "/" + f) or f
742 747 if nf not in results:
743 748 if kind == dirkind:
744 749 if not ignore(nf):
745 750 if matchtdir:
746 751 matchtdir(nf)
747 752 wadd(nf)
748 753 if nf in dmap and (matchalways or matchfn(nf)):
749 754 results[nf] = None
750 755 elif kind == regkind or kind == lnkkind:
751 756 if nf in dmap:
752 757 if matchalways or matchfn(nf):
753 758 results[nf] = st
754 759 elif (matchalways or matchfn(nf)) and not ignore(nf):
755 760 results[nf] = st
756 761 elif nf in dmap and (matchalways or matchfn(nf)):
757 762 results[nf] = None
758 763
759 764 for s in subrepos:
760 765 del results[s]
761 766 del results['.hg']
762 767
763 768 # step 3: visit remaining files from dmap
764 769 if not skipstep3 and not exact:
765 770 # If a dmap file is not in results yet, it was either
766 771 # a) not matching matchfn b) ignored, c) missing, or d) under a
767 772 # symlink directory.
768 773 if not results and matchalways:
769 774 visit = dmap.keys()
770 775 else:
771 776 visit = [f for f in dmap if f not in results and matchfn(f)]
772 777 visit.sort()
773 778
774 779 if unknown:
775 780 # unknown == True means we walked all dirs under the roots
776 781 # that wasn't ignored, and everything that matched was stat'ed
777 782 # and is already in results.
778 783 # The rest must thus be ignored or under a symlink.
779 784 audit_path = pathutil.pathauditor(self._root)
780 785
781 786 for nf in iter(visit):
782 787 # Report ignored items in the dmap as long as they are not
783 788 # under a symlink directory.
784 789 if audit_path.check(nf):
785 790 try:
786 791 results[nf] = lstat(join(nf))
787 792 # file was just ignored, no links, and exists
788 793 except OSError:
789 794 # file doesn't exist
790 795 results[nf] = None
791 796 else:
792 797 # It's either missing or under a symlink directory
793 798 # which we in this case report as missing
794 799 results[nf] = None
795 800 else:
796 801 # We may not have walked the full directory tree above,
797 802 # so stat and check everything we missed.
798 803 nf = iter(visit).next
799 804 for st in util.statfiles([join(i) for i in visit]):
800 805 results[nf()] = st
801 806 return results
802 807
803 808 def status(self, match, subrepos, ignored, clean, unknown):
804 809 '''Determine the status of the working copy relative to the
805 810 dirstate and return a tuple of lists (unsure, modified, added,
806 811 removed, deleted, unknown, ignored, clean), where:
807 812
808 813 unsure:
809 814 files that might have been modified since the dirstate was
810 815 written, but need to be read to be sure (size is the same
811 816 but mtime differs)
812 817 modified:
813 818 files that have definitely been modified since the dirstate
814 819 was written (different size or mode)
815 820 added:
816 821 files that have been explicitly added with hg add
817 822 removed:
818 823 files that have been explicitly removed with hg remove
819 824 deleted:
820 825 files that have been deleted through other means ("missing")
821 826 unknown:
822 827 files not in the dirstate that are not ignored
823 828 ignored:
824 829 files not in the dirstate that are ignored
825 830 (by _dirignore())
826 831 clean:
827 832 files that have definitely not been modified since the
828 833 dirstate was written
829 834 '''
830 835 listignored, listclean, listunknown = ignored, clean, unknown
831 836 lookup, modified, added, unknown, ignored = [], [], [], [], []
832 837 removed, deleted, clean = [], [], []
833 838
834 839 dmap = self._map
835 840 ladd = lookup.append # aka "unsure"
836 841 madd = modified.append
837 842 aadd = added.append
838 843 uadd = unknown.append
839 844 iadd = ignored.append
840 845 radd = removed.append
841 846 dadd = deleted.append
842 847 cadd = clean.append
843 848 mexact = match.exact
844 849 dirignore = self._dirignore
845 850 checkexec = self._checkexec
846 851 copymap = self._copymap
847 852 lastnormaltime = self._lastnormaltime
848 853
849 854 # We need to do full walks when either
850 855 # - we're listing all clean files, or
851 856 # - match.traversedir does something, because match.traversedir should
852 857 # be called for every dir in the working dir
853 858 full = listclean or match.traversedir is not None
854 859 for fn, st in self.walk(match, subrepos, listunknown, listignored,
855 860 full=full).iteritems():
856 861 if fn not in dmap:
857 862 if (listignored or mexact(fn)) and dirignore(fn):
858 863 if listignored:
859 864 iadd(fn)
860 865 else:
861 866 uadd(fn)
862 867 continue
863 868
864 869 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
865 870 # written like that for performance reasons. dmap[fn] is not a
866 871 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
867 872 # opcode has fast paths when the value to be unpacked is a tuple or
868 873 # a list, but falls back to creating a full-fledged iterator in
869 874 # general. That is much slower than simply accessing and storing the
870 875 # tuple members one by one.
871 876 t = dmap[fn]
872 877 state = t[0]
873 878 mode = t[1]
874 879 size = t[2]
875 880 time = t[3]
876 881
877 882 if not st and state in "nma":
878 883 dadd(fn)
879 884 elif state == 'n':
880 885 mtime = int(st.st_mtime)
881 886 if (size >= 0 and
882 887 ((size != st.st_size and size != st.st_size & _rangemask)
883 888 or ((mode ^ st.st_mode) & 0100 and checkexec))
884 889 or size == -2 # other parent
885 890 or fn in copymap):
886 891 madd(fn)
887 892 elif time != mtime and time != mtime & _rangemask:
888 893 ladd(fn)
889 894 elif mtime == lastnormaltime:
890 895 # fn may have been changed in the same timeslot without
891 896 # changing its size. This can happen if we quickly do
892 897 # multiple commits in a single transaction.
893 898 # Force lookup, so we don't miss such a racy file change.
894 899 ladd(fn)
895 900 elif listclean:
896 901 cadd(fn)
897 902 elif state == 'm':
898 903 madd(fn)
899 904 elif state == 'a':
900 905 aadd(fn)
901 906 elif state == 'r':
902 907 radd(fn)
903 908
904 909 return (lookup, modified, added, removed, deleted, unknown, ignored,
905 910 clean)
906 911
907 912 def matches(self, match):
908 913 '''
909 914 return files in the dirstate (in whatever state) filtered by match
910 915 '''
911 916 dmap = self._map
912 917 if match.always():
913 918 return dmap.keys()
914 919 files = match.files()
915 920 if match.matchfn == match.exact:
916 921 # fast path -- filter the other way around, since typically files is
917 922 # much smaller than dmap
918 923 return [f for f in files if f in dmap]
919 924 if not match.anypats() and util.all(fn in dmap for fn in files):
920 925 # fast path -- all the values are known to be files, so just return
921 926 # that
922 927 return list(files)
923 928 return [f for f in dmap if match(f)]
@@ -1,211 +1,211
1 1 test for old histedit issue #6:
2 2 editing a changeset without any actual change would corrupt the repository
3 3
4 4 $ . "$TESTDIR/histedit-helpers.sh"
5 5
6 6 $ cat >> $HGRCPATH <<EOF
7 7 > [extensions]
8 8 > histedit=
9 9 > EOF
10 10
11 11 $ initrepo ()
12 12 > {
13 13 > dir="$1"
14 14 > comment="$2"
15 15 > if [ -n "${comment}" ]; then
16 16 > echo % ${comment}
17 17 > echo % ${comment} | sed 's:.:-:g'
18 18 > fi
19 19 > hg init ${dir}
20 20 > cd ${dir}
21 21 > for x in a b c d e f ; do
22 22 > echo $x > $x
23 23 > hg add $x
24 24 > hg ci -m $x
25 25 > done
26 26 > cd ..
27 27 > }
28 28
29 29 $ geneditor ()
30 30 > {
31 31 > # generate an editor script for selecting changesets to be edited
32 32 > choice=$1 # changesets that should be edited (using sed line ranges)
33 33 > cat <<EOF | sed 's:^....::'
34 34 > # editing the rules, replacing 'pick' with 'edit' for the chosen lines
35 35 > sed '${choice}s:^pick:edit:' "\$1" > "\${1}.tmp"
36 36 > mv "\${1}.tmp" "\$1"
37 37 > # displaying the resulting rules, minus comments and empty lines
38 38 > sed '/^#/d;/^$/d;s:^:| :' "\$1" >&2
39 39 > EOF
40 40 > }
41 41
42 42 $ startediting ()
43 43 > {
44 44 > # begin an editing session
45 45 > choice="$1" # changesets that should be edited
46 46 > number="$2" # number of changesets considered (from tip)
47 47 > comment="$3"
48 48 > geneditor "${choice}" > edit.sh
49 49 > echo % start editing the history ${comment}
50 50 > HGEDITOR="sh ./edit.sh" hg histedit -- -${number} 2>&1 | fixbundle
51 51 > }
52 52
53 53 $ continueediting ()
54 54 > {
55 55 > # continue an edit already in progress
56 56 > editor="$1" # message editor when finalizing editing
57 57 > comment="$2"
58 58 > echo % finalize changeset editing ${comment}
59 59 > HGEDITOR=${editor} hg histedit --continue 2>&1 | fixbundle
60 60 > }
61 61
62 62 $ graphlog ()
63 63 > {
64 64 > comment="${1:-log}"
65 65 > echo % "${comment}"
66 66 > hg log -G --template '{rev} {node} \"{desc|firstline}\"\n'
67 67 > }
68 68
69 69
70 70 $ initrepo r1 "test editing with no change"
71 71 % test editing with no change
72 72 -----------------------------
73 73 $ cd r1
74 74 $ graphlog "log before editing"
75 75 % log before editing
76 76 @ 5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
77 77 |
78 78 o 4 e860deea161a2f77de56603b340ebbb4536308ae "e"
79 79 |
80 80 o 3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
81 81 |
82 82 o 2 177f92b773850b59254aa5e923436f921b55483b "c"
83 83 |
84 84 o 1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
85 85 |
86 86 o 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
87 87
88 88 $ startediting 2 3 "(not changing anything)" # edit the 2nd of 3 changesets
89 89 % start editing the history (not changing anything)
90 90 | pick 055a42cdd887 3 d
91 91 | edit e860deea161a 4 e
92 92 | pick 652413bf663e 5 f
93 93 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
94 94 Make changes as needed, you may commit or record as needed now.
95 95 When you are finished, run hg histedit --continue to resume.
96 96 $ continueediting true "(leaving commit message unaltered)"
97 97 % finalize changeset editing (leaving commit message unaltered)
98 98 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 99 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 100
101 101
102 102 check state of working copy
103 103 $ hg id
104 104 794fe033d0a0 tip
105 105
106 106 $ graphlog "log after history editing"
107 107 % log after history editing
108 108 @ 5 794fe033d0a030f8df77c5de945fca35c9181c30 "f"
109 109 |
110 110 o 4 04d2fab980779f332dec458cc944f28de8b43435 "e"
111 111 |
112 112 o 3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
113 113 |
114 114 o 2 177f92b773850b59254aa5e923436f921b55483b "c"
115 115 |
116 116 o 1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
117 117 |
118 118 o 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
119 119
120 120
121 121 $ cd ..
122 122
123 123 $ initrepo r2 "test editing with no change, then abort"
124 124 % test editing with no change, then abort
125 125 -----------------------------------------
126 126 $ cd r2
127 127 $ graphlog "log before editing"
128 128 % log before editing
129 129 @ 5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
130 130 |
131 131 o 4 e860deea161a2f77de56603b340ebbb4536308ae "e"
132 132 |
133 133 o 3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
134 134 |
135 135 o 2 177f92b773850b59254aa5e923436f921b55483b "c"
136 136 |
137 137 o 1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
138 138 |
139 139 o 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
140 140
141 141 $ startediting 1,2 3 "(not changing anything)" # edit the 1st two of 3 changesets
142 142 % start editing the history (not changing anything)
143 143 | edit 055a42cdd887 3 d
144 144 | edit e860deea161a 4 e
145 145 | pick 652413bf663e 5 f
146 146 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
147 147 Make changes as needed, you may commit or record as needed now.
148 148 When you are finished, run hg histedit --continue to resume.
149 149 $ continueediting true "(leaving commit message unaltered)"
150 150 % finalize changeset editing (leaving commit message unaltered)
151 151 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152 Make changes as needed, you may commit or record as needed now.
153 153 When you are finished, run hg histedit --continue to resume.
154 154 $ graphlog "log after first edit"
155 155 % log after first edit
156 156 @ 6 e5ae3ca2f1ffdbd89ec41ebc273a231f7c3022f2 "d"
157 157 |
158 158 | o 5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
159 159 | |
160 160 | o 4 e860deea161a2f77de56603b340ebbb4536308ae "e"
161 161 | |
162 162 | o 3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
163 163 |/
164 164 o 2 177f92b773850b59254aa5e923436f921b55483b "c"
165 165 |
166 166 o 1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
167 167 |
168 168 o 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
169 169
170 170
171 171 abort editing session, after first forcibly updating away
172 172 $ hg up 0
173 173 abort: histedit in progress
174 174 (use 'hg histedit --continue' or 'hg histedit --abort')
175 175 [255]
176 176 $ mv .hg/histedit-state .hg/histedit-state-ignore
177 177 $ hg up 0
178 178 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
179 179 $ mv .hg/histedit-state-ignore .hg/histedit-state
180 180 $ hg sum
181 181 parent: 0:cb9a9f314b8b
182 182 a
183 183 branch: default
184 commit: 1 modified, 1 unknown (new branch head)
184 commit: 1 added, 1 unknown (new branch head)
185 185 update: 6 new changesets (update)
186 186 hist: 2 remaining (histedit --continue)
187 187
188 188 $ hg histedit --abort 2>&1 | fixbundle
189 189 [1]
190 190
191 191 modified files should survive the abort when we've moved away already
192 192 $ hg st
193 M e
193 A e
194 194 ? edit.sh
195 195
196 196 $ graphlog "log after abort"
197 197 % log after abort
198 198 o 5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
199 199 |
200 200 o 4 e860deea161a2f77de56603b340ebbb4536308ae "e"
201 201 |
202 202 o 3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
203 203 |
204 204 o 2 177f92b773850b59254aa5e923436f921b55483b "c"
205 205 |
206 206 o 1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
207 207 |
208 208 @ 0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
209 209
210 210
211 211 $ cd ..
@@ -1,781 +1,781
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > mq=
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 11 > tglogp = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
12 12 > EOF
13 13
14 14 Create repo a:
15 15
16 16 $ hg init a
17 17 $ cd a
18 18 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
19 19 adding changesets
20 20 adding manifests
21 21 adding file changes
22 22 added 8 changesets with 7 changes to 7 files (+2 heads)
23 23 (run 'hg heads' to see heads, 'hg merge' to merge)
24 24 $ hg up tip
25 25 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 26
27 27 $ hg tglog
28 28 @ 7: 'H'
29 29 |
30 30 | o 6: 'G'
31 31 |/|
32 32 o | 5: 'F'
33 33 | |
34 34 | o 4: 'E'
35 35 |/
36 36 | o 3: 'D'
37 37 | |
38 38 | o 2: 'C'
39 39 | |
40 40 | o 1: 'B'
41 41 |/
42 42 o 0: 'A'
43 43
44 44 $ cd ..
45 45
46 46
47 47 Rebasing B onto H and collapsing changesets with different phases:
48 48
49 49
50 50 $ hg clone -q -u 3 a a1
51 51 $ cd a1
52 52
53 53 $ hg phase --force --secret 3
54 54
55 55 $ cat > $TESTTMP/editor.sh <<EOF
56 56 > echo "==== before editing"
57 57 > cat \$1
58 58 > echo "===="
59 59 > echo "edited manually" >> \$1
60 60 > EOF
61 61 $ HGEDITOR="sh $TESTTMP/editor.sh" hg rebase --collapse --keepbranches -e
62 62 ==== before editing
63 63 Collapsed revision
64 64 * B
65 65 * C
66 66 * D
67 67
68 68
69 69 HG: Enter commit message. Lines beginning with 'HG:' are removed.
70 70 HG: Leave message empty to abort commit.
71 71 HG: --
72 72 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
73 73 HG: branch 'default'
74 HG: changed B
75 HG: changed C
76 HG: changed D
74 HG: added B
75 HG: added C
76 HG: added D
77 77 ====
78 78 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
79 79
80 80 $ hg tglogp
81 81 @ 5:secret 'Collapsed revision
82 82 | * B
83 83 | * C
84 84 | * D
85 85 |
86 86 |
87 87 | edited manually'
88 88 o 4:draft 'H'
89 89 |
90 90 | o 3:draft 'G'
91 91 |/|
92 92 o | 2:draft 'F'
93 93 | |
94 94 | o 1:draft 'E'
95 95 |/
96 96 o 0:draft 'A'
97 97
98 98 $ hg manifest --rev tip
99 99 A
100 100 B
101 101 C
102 102 D
103 103 F
104 104 H
105 105
106 106 $ cd ..
107 107
108 108
109 109 Rebasing E onto H:
110 110
111 111 $ hg clone -q -u . a a2
112 112 $ cd a2
113 113
114 114 $ hg phase --force --secret 6
115 115 $ hg rebase --source 4 --collapse
116 116 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
117 117
118 118 $ hg tglog
119 119 o 6: 'Collapsed revision
120 120 | * E
121 121 | * G'
122 122 @ 5: 'H'
123 123 |
124 124 o 4: 'F'
125 125 |
126 126 | o 3: 'D'
127 127 | |
128 128 | o 2: 'C'
129 129 | |
130 130 | o 1: 'B'
131 131 |/
132 132 o 0: 'A'
133 133
134 134 $ hg manifest --rev tip
135 135 A
136 136 E
137 137 F
138 138 H
139 139
140 140 $ cd ..
141 141
142 142 Rebasing G onto H with custom message:
143 143
144 144 $ hg clone -q -u . a a3
145 145 $ cd a3
146 146
147 147 $ hg rebase --base 6 -m 'custom message'
148 148 abort: message can only be specified with collapse
149 149 [255]
150 150
151 151 $ cat > $TESTTMP/checkeditform.sh <<EOF
152 152 > env | grep HGEDITFORM
153 153 > true
154 154 > EOF
155 155 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg rebase --source 4 --collapse -m 'custom message' -e
156 156 HGEDITFORM=rebase.collapse
157 157 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
158 158
159 159 $ hg tglog
160 160 o 6: 'custom message'
161 161 |
162 162 @ 5: 'H'
163 163 |
164 164 o 4: 'F'
165 165 |
166 166 | o 3: 'D'
167 167 | |
168 168 | o 2: 'C'
169 169 | |
170 170 | o 1: 'B'
171 171 |/
172 172 o 0: 'A'
173 173
174 174 $ hg manifest --rev tip
175 175 A
176 176 E
177 177 F
178 178 H
179 179
180 180 $ cd ..
181 181
182 182 Create repo b:
183 183
184 184 $ hg init b
185 185 $ cd b
186 186
187 187 $ echo A > A
188 188 $ hg ci -Am A
189 189 adding A
190 190 $ echo B > B
191 191 $ hg ci -Am B
192 192 adding B
193 193
194 194 $ hg up -q 0
195 195
196 196 $ echo C > C
197 197 $ hg ci -Am C
198 198 adding C
199 199 created new head
200 200
201 201 $ hg merge
202 202 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
203 203 (branch merge, don't forget to commit)
204 204
205 205 $ echo D > D
206 206 $ hg ci -Am D
207 207 adding D
208 208
209 209 $ hg up -q 1
210 210
211 211 $ echo E > E
212 212 $ hg ci -Am E
213 213 adding E
214 214 created new head
215 215
216 216 $ echo F > F
217 217 $ hg ci -Am F
218 218 adding F
219 219
220 220 $ hg merge
221 221 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 222 (branch merge, don't forget to commit)
223 223 $ hg ci -m G
224 224
225 225 $ hg up -q 0
226 226
227 227 $ echo H > H
228 228 $ hg ci -Am H
229 229 adding H
230 230 created new head
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 Rebase and collapse - more than one external (fail):
253 253
254 254 $ hg clone -q -u . b b1
255 255 $ cd b1
256 256
257 257 $ hg rebase -s 2 --collapse
258 258 abort: unable to collapse on top of 7, there is more than one external parent: 1, 5
259 259 [255]
260 260
261 261 Rebase and collapse - E onto H:
262 262
263 263 $ hg rebase -s 4 --collapse # root (4) is not a merge
264 264 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
265 265
266 266 $ hg tglog
267 267 o 5: 'Collapsed revision
268 268 |\ * E
269 269 | | * F
270 270 | | * G'
271 271 | @ 4: 'H'
272 272 | |
273 273 o | 3: 'D'
274 274 |\ \
275 275 | o | 2: 'C'
276 276 | |/
277 277 o / 1: 'B'
278 278 |/
279 279 o 0: 'A'
280 280
281 281 $ hg manifest --rev tip
282 282 A
283 283 C
284 284 D
285 285 E
286 286 F
287 287 H
288 288
289 289 $ cd ..
290 290
291 291
292 292
293 293
294 294 Test that branchheads cache is updated correctly when doing a strip in which
295 295 the parent of the ancestor node to be stripped does not become a head and also,
296 296 the parent of a node that is a child of the node stripped becomes a head (node
297 297 3). The code is now much simpler and we could just test a simpler scenario
298 298 We keep it the test this way in case new complexity is injected.
299 299
300 300 $ hg clone -q -u . b b2
301 301 $ cd b2
302 302
303 303 $ hg heads --template="{rev}:{node} {branch}\n"
304 304 7:c65502d4178782309ce0574c5ae6ee9485a9bafa default
305 305 6:c772a8b2dc17629cec88a19d09c926c4814b12c7 default
306 306
307 307 $ cat $TESTTMP/b2/.hg/cache/branch2-served
308 308 c65502d4178782309ce0574c5ae6ee9485a9bafa 7
309 309 c772a8b2dc17629cec88a19d09c926c4814b12c7 o default
310 310 c65502d4178782309ce0574c5ae6ee9485a9bafa o default
311 311
312 312 $ hg strip 4
313 313 saved backup bundle to $TESTTMP/b2/.hg/strip-backup/8a5212ebc852-backup.hg (glob)
314 314
315 315 $ cat $TESTTMP/b2/.hg/cache/branch2-served
316 316 c65502d4178782309ce0574c5ae6ee9485a9bafa 4
317 317 2870ad076e541e714f3c2bc32826b5c6a6e5b040 o default
318 318 c65502d4178782309ce0574c5ae6ee9485a9bafa o default
319 319
320 320 $ hg heads --template="{rev}:{node} {branch}\n"
321 321 4:c65502d4178782309ce0574c5ae6ee9485a9bafa default
322 322 3:2870ad076e541e714f3c2bc32826b5c6a6e5b040 default
323 323
324 324 $ cd ..
325 325
326 326
327 327
328 328
329 329
330 330
331 331 Create repo c:
332 332
333 333 $ hg init c
334 334 $ cd c
335 335
336 336 $ echo A > A
337 337 $ hg ci -Am A
338 338 adding A
339 339 $ echo B > B
340 340 $ hg ci -Am B
341 341 adding B
342 342
343 343 $ hg up -q 0
344 344
345 345 $ echo C > C
346 346 $ hg ci -Am C
347 347 adding C
348 348 created new head
349 349
350 350 $ hg merge
351 351 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
352 352 (branch merge, don't forget to commit)
353 353
354 354 $ echo D > D
355 355 $ hg ci -Am D
356 356 adding D
357 357
358 358 $ hg up -q 1
359 359
360 360 $ echo E > E
361 361 $ hg ci -Am E
362 362 adding E
363 363 created new head
364 364 $ echo F > E
365 365 $ hg ci -m 'F'
366 366
367 367 $ echo G > G
368 368 $ hg ci -Am G
369 369 adding G
370 370
371 371 $ hg merge
372 372 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 373 (branch merge, don't forget to commit)
374 374
375 375 $ hg ci -m H
376 376
377 377 $ hg up -q 0
378 378
379 379 $ echo I > I
380 380 $ hg ci -Am I
381 381 adding I
382 382 created new head
383 383
384 384 $ hg tglog
385 385 @ 8: 'I'
386 386 |
387 387 | o 7: 'H'
388 388 | |\
389 389 | | o 6: 'G'
390 390 | | |
391 391 | | o 5: 'F'
392 392 | | |
393 393 | | o 4: 'E'
394 394 | | |
395 395 | o | 3: 'D'
396 396 | |\|
397 397 | o | 2: 'C'
398 398 |/ /
399 399 | o 1: 'B'
400 400 |/
401 401 o 0: 'A'
402 402
403 403 $ cd ..
404 404
405 405
406 406 Rebase and collapse - E onto I:
407 407
408 408 $ hg clone -q -u . c c1
409 409 $ cd c1
410 410
411 411 $ hg rebase -s 4 --collapse # root (4) is not a merge
412 412 merging E
413 413 saved backup bundle to $TESTTMP/c1/.hg/strip-backup/*-backup.hg (glob)
414 414
415 415 $ hg tglog
416 416 o 5: 'Collapsed revision
417 417 |\ * E
418 418 | | * F
419 419 | | * G
420 420 | | * H'
421 421 | @ 4: 'I'
422 422 | |
423 423 o | 3: 'D'
424 424 |\ \
425 425 | o | 2: 'C'
426 426 | |/
427 427 o / 1: 'B'
428 428 |/
429 429 o 0: 'A'
430 430
431 431 $ hg manifest --rev tip
432 432 A
433 433 C
434 434 D
435 435 E
436 436 G
437 437 I
438 438
439 439 $ hg up tip -q
440 440 $ cat E
441 441 F
442 442
443 443 $ cd ..
444 444
445 445
446 446 Create repo d:
447 447
448 448 $ hg init d
449 449 $ cd d
450 450
451 451 $ echo A > A
452 452 $ hg ci -Am A
453 453 adding A
454 454 $ echo B > B
455 455 $ hg ci -Am B
456 456 adding B
457 457 $ echo C > C
458 458 $ hg ci -Am C
459 459 adding C
460 460
461 461 $ hg up -q 1
462 462
463 463 $ echo D > D
464 464 $ hg ci -Am D
465 465 adding D
466 466 created new head
467 467 $ hg merge
468 468 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
469 469 (branch merge, don't forget to commit)
470 470
471 471 $ hg ci -m E
472 472
473 473 $ hg up -q 0
474 474
475 475 $ echo F > F
476 476 $ hg ci -Am F
477 477 adding F
478 478 created new head
479 479
480 480 $ hg tglog
481 481 @ 5: 'F'
482 482 |
483 483 | o 4: 'E'
484 484 | |\
485 485 | | o 3: 'D'
486 486 | | |
487 487 | o | 2: 'C'
488 488 | |/
489 489 | o 1: 'B'
490 490 |/
491 491 o 0: 'A'
492 492
493 493 $ cd ..
494 494
495 495
496 496 Rebase and collapse - B onto F:
497 497
498 498 $ hg clone -q -u . d d1
499 499 $ cd d1
500 500
501 501 $ hg rebase -s 1 --collapse
502 502 saved backup bundle to $TESTTMP/d1/.hg/strip-backup/*-backup.hg (glob)
503 503
504 504 $ hg tglog
505 505 o 2: 'Collapsed revision
506 506 | * B
507 507 | * C
508 508 | * D
509 509 | * E'
510 510 @ 1: 'F'
511 511 |
512 512 o 0: 'A'
513 513
514 514 $ hg manifest --rev tip
515 515 A
516 516 B
517 517 C
518 518 D
519 519 F
520 520
521 521 Interactions between collapse and keepbranches
522 522 $ cd ..
523 523 $ hg init e
524 524 $ cd e
525 525 $ echo 'a' > a
526 526 $ hg ci -Am 'A'
527 527 adding a
528 528
529 529 $ hg branch 'one'
530 530 marked working directory as branch one
531 531 (branches are permanent and global, did you want a bookmark?)
532 532 $ echo 'b' > b
533 533 $ hg ci -Am 'B'
534 534 adding b
535 535
536 536 $ hg branch 'two'
537 537 marked working directory as branch two
538 538 (branches are permanent and global, did you want a bookmark?)
539 539 $ echo 'c' > c
540 540 $ hg ci -Am 'C'
541 541 adding c
542 542
543 543 $ hg up -q 0
544 544 $ echo 'd' > d
545 545 $ hg ci -Am 'D'
546 546 adding d
547 547
548 548 $ hg tglog
549 549 @ 3: 'D'
550 550 |
551 551 | o 2: 'C' two
552 552 | |
553 553 | o 1: 'B' one
554 554 |/
555 555 o 0: 'A'
556 556
557 557 $ hg rebase --keepbranches --collapse -s 1 -d 3
558 558 abort: cannot collapse multiple named branches
559 559 [255]
560 560
561 561 $ repeatchange() {
562 562 > hg checkout $1
563 563 > hg cp d z
564 564 > echo blah >> z
565 565 > hg commit -Am "$2" --user "$3"
566 566 > }
567 567 $ repeatchange 3 "E" "user1"
568 568 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
569 569 $ repeatchange 3 "E" "user2"
570 570 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
571 571 created new head
572 572 $ hg tglog
573 573 @ 5: 'E'
574 574 |
575 575 | o 4: 'E'
576 576 |/
577 577 o 3: 'D'
578 578 |
579 579 | o 2: 'C' two
580 580 | |
581 581 | o 1: 'B' one
582 582 |/
583 583 o 0: 'A'
584 584
585 585 $ hg rebase -s 5 -d 4
586 586 saved backup bundle to $TESTTMP/e/.hg/strip-backup/*-backup.hg (glob)
587 587 $ hg tglog
588 588 @ 4: 'E'
589 589 |
590 590 o 3: 'D'
591 591 |
592 592 | o 2: 'C' two
593 593 | |
594 594 | o 1: 'B' one
595 595 |/
596 596 o 0: 'A'
597 597
598 598 $ hg export tip
599 599 # HG changeset patch
600 600 # User user1
601 601 # Date 0 0
602 602 # Thu Jan 01 00:00:00 1970 +0000
603 603 # Node ID f338eb3c2c7cc5b5915676a2376ba7ac558c5213
604 604 # Parent 41acb9dca9eb976e84cd21fcb756b4afa5a35c09
605 605 E
606 606
607 607 diff -r 41acb9dca9eb -r f338eb3c2c7c z
608 608 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
609 609 +++ b/z Thu Jan 01 00:00:00 1970 +0000
610 610 @@ -0,0 +1,2 @@
611 611 +d
612 612 +blah
613 613
614 614 $ cd ..
615 615
616 616 Rebase, collapse and copies
617 617
618 618 $ hg init copies
619 619 $ cd copies
620 620 $ hg unbundle "$TESTDIR/bundles/renames.hg"
621 621 adding changesets
622 622 adding manifests
623 623 adding file changes
624 624 added 4 changesets with 11 changes to 7 files (+1 heads)
625 625 (run 'hg heads' to see heads, 'hg merge' to merge)
626 626 $ hg up -q tip
627 627 $ hg tglog
628 628 @ 3: 'move2'
629 629 |
630 630 o 2: 'move1'
631 631 |
632 632 | o 1: 'change'
633 633 |/
634 634 o 0: 'add'
635 635
636 636 $ hg rebase --collapse -d 1
637 637 merging a and d to d
638 638 merging b and e to e
639 639 merging c and f to f
640 640 merging f and c to c
641 641 merging e and g to g
642 642 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/*-backup.hg (glob)
643 643 $ hg st
644 644 $ hg st --copies --change tip
645 645 A d
646 646 a
647 647 A g
648 648 b
649 649 R b
650 650 $ hg up tip -q
651 651 $ cat c
652 652 c
653 653 c
654 654 $ cat d
655 655 a
656 656 a
657 657 $ cat g
658 658 b
659 659 b
660 660 $ hg log -r . --template "{file_copies}\n"
661 661 d (a)g (b)
662 662
663 663 Test collapsing a middle revision in-place
664 664
665 665 $ hg tglog
666 666 @ 2: 'Collapsed revision
667 667 | * move1
668 668 | * move2'
669 669 o 1: 'change'
670 670 |
671 671 o 0: 'add'
672 672
673 673 $ hg rebase --collapse -r 1 -d 0
674 674 abort: can't remove original changesets with unrebased descendants
675 675 (use --keep to keep original changesets)
676 676 [255]
677 677
678 678 Test collapsing in place
679 679
680 680 $ hg rebase --collapse -b . -d 0
681 681 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/*-backup.hg (glob)
682 682 $ hg st --change tip --copies
683 683 M a
684 684 M c
685 685 A d
686 686 a
687 687 A g
688 688 b
689 689 R b
690 690 $ hg up tip -q
691 691 $ cat a
692 692 a
693 693 a
694 694 $ cat c
695 695 c
696 696 c
697 697 $ cat d
698 698 a
699 699 a
700 700 $ cat g
701 701 b
702 702 b
703 703 $ cd ..
704 704
705 705
706 706 Test stripping a revision with another child
707 707
708 708 $ hg init f
709 709 $ cd f
710 710
711 711 $ echo A > A
712 712 $ hg ci -Am A
713 713 adding A
714 714 $ echo B > B
715 715 $ hg ci -Am B
716 716 adding B
717 717
718 718 $ hg up -q 0
719 719
720 720 $ echo C > C
721 721 $ hg ci -Am C
722 722 adding C
723 723 created new head
724 724
725 725 $ hg tglog
726 726 @ 2: 'C'
727 727 |
728 728 | o 1: 'B'
729 729 |/
730 730 o 0: 'A'
731 731
732 732
733 733
734 734 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
735 735 2:c5cefa58fd557f84b72b87f970135984337acbc5 default: C
736 736 1:27547f69f25460a52fff66ad004e58da7ad3fb56 default: B
737 737
738 738 $ hg strip 2
739 739 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
740 740 saved backup bundle to $TESTTMP/f/.hg/strip-backup/*-backup.hg (glob)
741 741
742 742 $ hg tglog
743 743 o 1: 'B'
744 744 |
745 745 @ 0: 'A'
746 746
747 747
748 748
749 749 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
750 750 1:27547f69f25460a52fff66ad004e58da7ad3fb56 default: B
751 751
752 752 $ cd ..
753 753
754 754 Test collapsing changes that add then remove a file
755 755
756 756 $ hg init collapseaddremove
757 757 $ cd collapseaddremove
758 758
759 759 $ touch base
760 760 $ hg commit -Am base
761 761 adding base
762 762 $ touch a
763 763 $ hg commit -Am a
764 764 adding a
765 765 $ hg rm a
766 766 $ touch b
767 767 $ hg commit -Am b
768 768 adding b
769 769 $ hg book foo
770 770 $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
771 771 saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/*-backup.hg (glob)
772 772 $ hg log -G --template "{rev}: '{desc}' {bookmarks}"
773 773 @ 1: 'collapsed' foo
774 774 |
775 775 o 0: 'base'
776 776
777 777 $ hg manifest --rev tip
778 778 b
779 779 base
780 780
781 781 $ cd ..
@@ -1,689 +1,689
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)
29 29
30 30 $ hg clone -q -u . a a1
31 31 $ cd a1
32 32
33 33 $ hg tglog
34 34 @ 7: 'H'
35 35 |
36 36 | o 6: 'G'
37 37 |/|
38 38 o | 5: 'F'
39 39 | |
40 40 | o 4: 'E'
41 41 |/
42 42 | o 3: 'D'
43 43 | |
44 44 | o 2: 'C'
45 45 | |
46 46 | o 1: 'B'
47 47 |/
48 48 o 0: 'A'
49 49
50 50
51 51 $ hg status --rev "3^1" --rev 3
52 52 A D
53 53 $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit
54 54 D
55 55
56 56
57 57 HG: Enter commit message. Lines beginning with 'HG:' are removed.
58 58 HG: Leave message empty to abort commit.
59 59 HG: --
60 60 HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
61 61 HG: branch 'default'
62 HG: changed D
62 HG: added D
63 63 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
64 64
65 65 $ hg tglog
66 66 o 7: 'D'
67 67 |
68 68 @ 6: 'H'
69 69 |
70 70 | o 5: 'G'
71 71 |/|
72 72 o | 4: 'F'
73 73 | |
74 74 | o 3: 'E'
75 75 |/
76 76 | o 2: 'C'
77 77 | |
78 78 | o 1: 'B'
79 79 |/
80 80 o 0: 'A'
81 81
82 82 $ cd ..
83 83
84 84
85 85 D onto F - intermediate point:
86 86 (this also tests that editor is not invoked if '--edit' is not specified)
87 87
88 88 $ hg clone -q -u . a a2
89 89 $ cd a2
90 90
91 91 $ HGEDITOR=cat hg rebase -s 3 -d 5
92 92 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
93 93
94 94 $ hg tglog
95 95 o 7: 'D'
96 96 |
97 97 | @ 6: 'H'
98 98 |/
99 99 | o 5: 'G'
100 100 |/|
101 101 o | 4: 'F'
102 102 | |
103 103 | o 3: 'E'
104 104 |/
105 105 | o 2: 'C'
106 106 | |
107 107 | o 1: 'B'
108 108 |/
109 109 o 0: 'A'
110 110
111 111 $ cd ..
112 112
113 113
114 114 E onto H - skip of G:
115 115
116 116 $ hg clone -q -u . a a3
117 117 $ cd a3
118 118
119 119 $ hg rebase -s 4 -d 7
120 120 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
121 121
122 122 $ hg tglog
123 123 o 6: 'E'
124 124 |
125 125 @ 5: 'H'
126 126 |
127 127 o 4: 'F'
128 128 |
129 129 | o 3: 'D'
130 130 | |
131 131 | o 2: 'C'
132 132 | |
133 133 | o 1: 'B'
134 134 |/
135 135 o 0: 'A'
136 136
137 137 $ cd ..
138 138
139 139
140 140 F onto E - rebase of a branching point (skip G):
141 141
142 142 $ hg clone -q -u . a a4
143 143 $ cd a4
144 144
145 145 $ hg rebase -s 5 -d 4
146 146 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
147 147
148 148 $ hg tglog
149 149 @ 6: 'H'
150 150 |
151 151 o 5: 'F'
152 152 |
153 153 o 4: 'E'
154 154 |
155 155 | o 3: 'D'
156 156 | |
157 157 | o 2: 'C'
158 158 | |
159 159 | o 1: 'B'
160 160 |/
161 161 o 0: 'A'
162 162
163 163 $ cd ..
164 164
165 165
166 166 G onto H - merged revision having a parent in ancestors of target:
167 167
168 168 $ hg clone -q -u . a a5
169 169 $ cd a5
170 170
171 171 $ hg rebase -s 6 -d 7
172 172 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
173 173
174 174 $ hg tglog
175 175 o 7: 'G'
176 176 |\
177 177 | @ 6: 'H'
178 178 | |
179 179 | o 5: 'F'
180 180 | |
181 181 o | 4: 'E'
182 182 |/
183 183 | o 3: 'D'
184 184 | |
185 185 | o 2: 'C'
186 186 | |
187 187 | o 1: 'B'
188 188 |/
189 189 o 0: 'A'
190 190
191 191 $ cd ..
192 192
193 193
194 194 F onto B - G maintains E as parent:
195 195
196 196 $ hg clone -q -u . a a6
197 197 $ cd a6
198 198
199 199 $ hg rebase -s 5 -d 1
200 200 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
201 201
202 202 $ hg tglog
203 203 @ 7: 'H'
204 204 |
205 205 | o 6: 'G'
206 206 |/|
207 207 o | 5: 'F'
208 208 | |
209 209 | o 4: 'E'
210 210 | |
211 211 | | o 3: 'D'
212 212 | | |
213 213 +---o 2: 'C'
214 214 | |
215 215 o | 1: 'B'
216 216 |/
217 217 o 0: 'A'
218 218
219 219 $ cd ..
220 220
221 221
222 222 These will fail (using --source):
223 223
224 224 G onto F - rebase onto an ancestor:
225 225
226 226 $ hg clone -q -u . a a7
227 227 $ cd a7
228 228
229 229 $ hg rebase -s 6 -d 5
230 230 nothing to rebase
231 231 [1]
232 232
233 233 F onto G - rebase onto a descendant:
234 234
235 235 $ hg rebase -s 5 -d 6
236 236 abort: source is ancestor of destination
237 237 [255]
238 238
239 239 G onto B - merge revision with both parents not in ancestors of target:
240 240
241 241 $ hg rebase -s 6 -d 1
242 242 abort: cannot use revision 6 as base, result would have 3 parents
243 243 [255]
244 244
245 245
246 246 These will abort gracefully (using --base):
247 247
248 248 G onto G - rebase onto same changeset:
249 249
250 250 $ hg rebase -b 6 -d 6
251 251 nothing to rebase - eea13746799a is both "base" and destination
252 252 [1]
253 253
254 254 G onto F - rebase onto an ancestor:
255 255
256 256 $ hg rebase -b 6 -d 5
257 257 nothing to rebase
258 258 [1]
259 259
260 260 F onto G - rebase onto a descendant:
261 261
262 262 $ hg rebase -b 5 -d 6
263 263 nothing to rebase - "base" 24b6387c8c8c is already an ancestor of destination eea13746799a
264 264 [1]
265 265
266 266 C onto A - rebase onto an ancestor:
267 267
268 268 $ hg rebase -d 0 -s 2
269 269 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-backup.hg (glob)
270 270 $ hg tglog
271 271 o 7: 'D'
272 272 |
273 273 o 6: 'C'
274 274 |
275 275 | @ 5: 'H'
276 276 | |
277 277 | | o 4: 'G'
278 278 | |/|
279 279 | o | 3: 'F'
280 280 |/ /
281 281 | o 2: 'E'
282 282 |/
283 283 | o 1: 'B'
284 284 |/
285 285 o 0: 'A'
286 286
287 287
288 288 Check rebasing public changeset
289 289
290 290 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
291 291 $ hg rebase -d 0 -b 6
292 292 nothing to rebase
293 293 [1]
294 294 $ hg rebase -d 5 -b 6
295 295 abort: can't rebase immutable changeset e1c4361dd923
296 296 (see hg help phases for details)
297 297 [255]
298 298
299 299 $ hg rebase -d 5 -b 6 --keep
300 300
301 301 Check rebasing mutable changeset
302 302 Source phase greater or equal to destination phase: new changeset get the phase of source:
303 303 $ hg rebase -s9 -d0
304 304 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-backup.hg (glob)
305 305 $ hg log --template "{phase}\n" -r 9
306 306 draft
307 307 $ hg rebase -s9 -d1
308 308 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-backup.hg (glob)
309 309 $ hg log --template "{phase}\n" -r 9
310 310 draft
311 311 $ hg phase --force --secret 9
312 312 $ hg rebase -s9 -d0
313 313 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-backup.hg (glob)
314 314 $ hg log --template "{phase}\n" -r 9
315 315 secret
316 316 $ hg rebase -s9 -d1
317 317 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-backup.hg (glob)
318 318 $ hg log --template "{phase}\n" -r 9
319 319 secret
320 320 Source phase lower than destination phase: new changeset get the phase of destination:
321 321 $ hg rebase -s8 -d9
322 322 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-backup.hg (glob)
323 323 $ hg log --template "{phase}\n" -r 'rev(9)'
324 324 secret
325 325
326 326 $ cd ..
327 327
328 328 Test for revset
329 329
330 330 We need a bit different graph
331 331 All destination are B
332 332
333 333 $ hg init ah
334 334 $ cd ah
335 335 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
336 336 adding changesets
337 337 adding manifests
338 338 adding file changes
339 339 added 9 changesets with 9 changes to 9 files (+2 heads)
340 340 (run 'hg heads' to see heads, 'hg merge' to merge)
341 341 $ hg tglog
342 342 o 8: 'I'
343 343 |
344 344 o 7: 'H'
345 345 |
346 346 o 6: 'G'
347 347 |
348 348 | o 5: 'F'
349 349 | |
350 350 | o 4: 'E'
351 351 |/
352 352 o 3: 'D'
353 353 |
354 354 o 2: 'C'
355 355 |
356 356 | o 1: 'B'
357 357 |/
358 358 o 0: 'A'
359 359
360 360 $ cd ..
361 361
362 362
363 363 Simple case with keep:
364 364
365 365 Source on have two descendant heads but ask for one
366 366
367 367 $ hg clone -q -u . ah ah1
368 368 $ cd ah1
369 369 $ hg rebase -r '2::8' -d 1
370 370 abort: can't remove original changesets with unrebased descendants
371 371 (use --keep to keep original changesets)
372 372 [255]
373 373 $ hg rebase -r '2::8' -d 1 --keep
374 374 $ hg tglog
375 375 o 13: 'I'
376 376 |
377 377 o 12: 'H'
378 378 |
379 379 o 11: 'G'
380 380 |
381 381 o 10: 'D'
382 382 |
383 383 o 9: 'C'
384 384 |
385 385 | o 8: 'I'
386 386 | |
387 387 | o 7: 'H'
388 388 | |
389 389 | o 6: 'G'
390 390 | |
391 391 | | o 5: 'F'
392 392 | | |
393 393 | | o 4: 'E'
394 394 | |/
395 395 | o 3: 'D'
396 396 | |
397 397 | o 2: 'C'
398 398 | |
399 399 o | 1: 'B'
400 400 |/
401 401 o 0: 'A'
402 402
403 403
404 404 $ cd ..
405 405
406 406 Base on have one descendant heads we ask for but common ancestor have two
407 407
408 408 $ hg clone -q -u . ah ah2
409 409 $ cd ah2
410 410 $ hg rebase -r '3::8' -d 1
411 411 abort: can't remove original changesets with unrebased descendants
412 412 (use --keep to keep original changesets)
413 413 [255]
414 414 $ hg rebase -r '3::8' -d 1 --keep
415 415 $ hg tglog
416 416 o 12: 'I'
417 417 |
418 418 o 11: 'H'
419 419 |
420 420 o 10: 'G'
421 421 |
422 422 o 9: 'D'
423 423 |
424 424 | o 8: 'I'
425 425 | |
426 426 | o 7: 'H'
427 427 | |
428 428 | o 6: 'G'
429 429 | |
430 430 | | o 5: 'F'
431 431 | | |
432 432 | | o 4: 'E'
433 433 | |/
434 434 | o 3: 'D'
435 435 | |
436 436 | o 2: 'C'
437 437 | |
438 438 o | 1: 'B'
439 439 |/
440 440 o 0: 'A'
441 441
442 442
443 443 $ cd ..
444 444
445 445 rebase subset
446 446
447 447 $ hg clone -q -u . ah ah3
448 448 $ cd ah3
449 449 $ hg rebase -r '3::7' -d 1
450 450 abort: can't remove original changesets with unrebased descendants
451 451 (use --keep to keep original changesets)
452 452 [255]
453 453 $ hg rebase -r '3::7' -d 1 --keep
454 454 $ hg tglog
455 455 o 11: 'H'
456 456 |
457 457 o 10: 'G'
458 458 |
459 459 o 9: 'D'
460 460 |
461 461 | o 8: 'I'
462 462 | |
463 463 | o 7: 'H'
464 464 | |
465 465 | o 6: 'G'
466 466 | |
467 467 | | o 5: 'F'
468 468 | | |
469 469 | | o 4: 'E'
470 470 | |/
471 471 | o 3: 'D'
472 472 | |
473 473 | o 2: 'C'
474 474 | |
475 475 o | 1: 'B'
476 476 |/
477 477 o 0: 'A'
478 478
479 479
480 480 $ cd ..
481 481
482 482 rebase subset with multiple head
483 483
484 484 $ hg clone -q -u . ah ah4
485 485 $ cd ah4
486 486 $ hg rebase -r '3::(7+5)' -d 1
487 487 abort: can't remove original changesets with unrebased descendants
488 488 (use --keep to keep original changesets)
489 489 [255]
490 490 $ hg rebase -r '3::(7+5)' -d 1 --keep
491 491 $ hg tglog
492 492 o 13: 'H'
493 493 |
494 494 o 12: 'G'
495 495 |
496 496 | o 11: 'F'
497 497 | |
498 498 | o 10: 'E'
499 499 |/
500 500 o 9: 'D'
501 501 |
502 502 | o 8: 'I'
503 503 | |
504 504 | o 7: 'H'
505 505 | |
506 506 | o 6: 'G'
507 507 | |
508 508 | | o 5: 'F'
509 509 | | |
510 510 | | o 4: 'E'
511 511 | |/
512 512 | o 3: 'D'
513 513 | |
514 514 | o 2: 'C'
515 515 | |
516 516 o | 1: 'B'
517 517 |/
518 518 o 0: 'A'
519 519
520 520
521 521 $ cd ..
522 522
523 523 More advanced tests
524 524
525 525 rebase on ancestor with revset
526 526
527 527 $ hg clone -q -u . ah ah5
528 528 $ cd ah5
529 529 $ hg rebase -r '6::' -d 2
530 530 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
531 531 $ hg tglog
532 532 o 8: 'I'
533 533 |
534 534 o 7: 'H'
535 535 |
536 536 o 6: 'G'
537 537 |
538 538 | o 5: 'F'
539 539 | |
540 540 | o 4: 'E'
541 541 | |
542 542 | o 3: 'D'
543 543 |/
544 544 o 2: 'C'
545 545 |
546 546 | o 1: 'B'
547 547 |/
548 548 o 0: 'A'
549 549
550 550 $ cd ..
551 551
552 552
553 553 rebase with multiple root.
554 554 We rebase E and G on B
555 555 We would expect heads are I, F if it was supported
556 556
557 557 $ hg clone -q -u . ah ah6
558 558 $ cd ah6
559 559 $ hg rebase -r '(4+6)::' -d 1
560 560 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
561 561 $ hg tglog
562 562 o 8: 'I'
563 563 |
564 564 o 7: 'H'
565 565 |
566 566 o 6: 'G'
567 567 |
568 568 | o 5: 'F'
569 569 | |
570 570 | o 4: 'E'
571 571 |/
572 572 | o 3: 'D'
573 573 | |
574 574 | o 2: 'C'
575 575 | |
576 576 o | 1: 'B'
577 577 |/
578 578 o 0: 'A'
579 579
580 580 $ cd ..
581 581
582 582 More complex rebase with multiple roots
583 583 each root have a different common ancestor with the destination and this is a detach
584 584
585 585 (setup)
586 586
587 587 $ hg clone -q -u . a a8
588 588 $ cd a8
589 589 $ echo I > I
590 590 $ hg add I
591 591 $ hg commit -m I
592 592 $ hg up 4
593 593 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
594 594 $ echo I > J
595 595 $ hg add J
596 596 $ hg commit -m J
597 597 created new head
598 598 $ echo I > K
599 599 $ hg add K
600 600 $ hg commit -m K
601 601 $ hg tglog
602 602 @ 10: 'K'
603 603 |
604 604 o 9: 'J'
605 605 |
606 606 | o 8: 'I'
607 607 | |
608 608 | o 7: 'H'
609 609 | |
610 610 +---o 6: 'G'
611 611 | |/
612 612 | o 5: 'F'
613 613 | |
614 614 o | 4: 'E'
615 615 |/
616 616 | o 3: 'D'
617 617 | |
618 618 | o 2: 'C'
619 619 | |
620 620 | o 1: 'B'
621 621 |/
622 622 o 0: 'A'
623 623
624 624 (actual test)
625 625
626 626 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
627 627 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-backup.hg (glob)
628 628 $ hg log --rev 'children(desc(G))'
629 629 changeset: 9:adb617877056
630 630 parent: 6:eea13746799a
631 631 user: test
632 632 date: Thu Jan 01 00:00:00 1970 +0000
633 633 summary: I
634 634
635 635 changeset: 10:882431a34a0e
636 636 tag: tip
637 637 parent: 6:eea13746799a
638 638 user: test
639 639 date: Thu Jan 01 00:00:00 1970 +0000
640 640 summary: K
641 641
642 642 $ hg tglog
643 643 @ 10: 'K'
644 644 |
645 645 | o 9: 'I'
646 646 |/
647 647 | o 8: 'J'
648 648 | |
649 649 | | o 7: 'H'
650 650 | | |
651 651 o---+ 6: 'G'
652 652 |/ /
653 653 | o 5: 'F'
654 654 | |
655 655 o | 4: 'E'
656 656 |/
657 657 | o 3: 'D'
658 658 | |
659 659 | o 2: 'C'
660 660 | |
661 661 | o 1: 'B'
662 662 |/
663 663 o 0: 'A'
664 664
665 665
666 666 Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
667 667
668 668 $ cd ..
669 669 $ hg init cwd-vanish
670 670 $ cd cwd-vanish
671 671 $ touch initial-file
672 672 $ hg add initial-file
673 673 $ hg commit -m 'initial commit'
674 674 $ touch dest-file
675 675 $ hg add dest-file
676 676 $ hg commit -m 'dest commit'
677 677 $ hg up 0
678 678 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
679 679 $ touch other-file
680 680 $ hg add other-file
681 681 $ hg commit -m 'first source commit'
682 682 created new head
683 683 $ mkdir subdir
684 684 $ cd subdir
685 685 $ touch subfile
686 686 $ hg add subfile
687 687 $ hg commit -m 'second source with subdir'
688 688 $ hg rebase -b . -d 1 --traceback
689 689 saved backup bundle to $TESTTMP/cwd-vanish/.hg/strip-backup/779a07b1b7a0-backup.hg (glob)
General Comments 0
You need to be logged in to leave comments. Login now