##// END OF EJS Templates
shelve: make unshelve work even if it don't run in repository root...
Takumi IINO -
r19943:4de11687 stable
parent child Browse files
Show More
@@ -1,633 +1,640 b''
1 1 # shelve.py - save/restore working directory state
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
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 """save and restore changes to the working directory
9 9
10 10 The "hg shelve" command saves changes made to the working directory
11 11 and reverts those changes, resetting the working directory to a clean
12 12 state.
13 13
14 14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 15 shelve". Changes can be restored even after updating to a different
16 16 parent, in which case Mercurial's merge machinery will resolve any
17 17 conflicts if necessary.
18 18
19 19 You can have more than one shelved change outstanding at a time; each
20 20 shelved change has a distinct name. For details, see the help for "hg
21 21 shelve".
22 22 """
23 23
24 24 from mercurial.i18n import _
25 25 from mercurial.node import nullid, bin, hex
26 26 from mercurial import changegroup, cmdutil, scmutil, phases
27 27 from mercurial import error, hg, mdiff, merge, patch, repair, util
28 28 from mercurial import templatefilters
29 29 from mercurial import lock as lockmod
30 30 import errno
31 31
32 32 cmdtable = {}
33 33 command = cmdutil.command(cmdtable)
34 34 testedwith = 'internal'
35 35
36 36 class shelvedfile(object):
37 37 """Helper for the file storing a single shelve
38 38
39 39 Handles common functions on shelve files (.hg/.files/.patch) using
40 40 the vfs layer"""
41 41 def __init__(self, repo, name, filetype=None):
42 42 self.repo = repo
43 43 self.name = name
44 44 self.vfs = scmutil.vfs(repo.join('shelved'))
45 45 if filetype:
46 46 self.fname = name + '.' + filetype
47 47 else:
48 48 self.fname = name
49 49
50 50 def exists(self):
51 51 return self.vfs.exists(self.fname)
52 52
53 53 def filename(self):
54 54 return self.vfs.join(self.fname)
55 55
56 56 def unlink(self):
57 57 util.unlink(self.filename())
58 58
59 59 def stat(self):
60 60 return self.vfs.stat(self.fname)
61 61
62 62 def opener(self, mode='rb'):
63 63 try:
64 64 return self.vfs(self.fname, mode)
65 65 except IOError, err:
66 66 if err.errno != errno.ENOENT:
67 67 raise
68 68 if mode[0] in 'wa':
69 69 try:
70 70 self.vfs.mkdir()
71 71 return self.vfs(self.fname, mode)
72 72 except IOError, err:
73 73 if err.errno != errno.EEXIST:
74 74 raise
75 75 elif mode[0] == 'r':
76 76 raise util.Abort(_("shelved change '%s' not found") %
77 77 self.name)
78 78
79 79 class shelvedstate(object):
80 80 """Handle persistence during unshelving operations.
81 81
82 82 Handles saving and restoring a shelved state. Ensures that different
83 83 versions of a shelved state are possible and handles them appropriately.
84 84 """
85 85 _version = 1
86 86 _filename = 'shelvedstate'
87 87
88 88 @classmethod
89 89 def load(cls, repo):
90 90 fp = repo.opener(cls._filename)
91 91 try:
92 92 version = int(fp.readline().strip())
93 93
94 94 if version != cls._version:
95 95 raise util.Abort(_('this version of shelve is incompatible '
96 96 'with the version used in this repo'))
97 97 name = fp.readline().strip()
98 98 parents = [bin(h) for h in fp.readline().split()]
99 99 stripnodes = [bin(h) for h in fp.readline().split()]
100 100 finally:
101 101 fp.close()
102 102
103 103 obj = cls()
104 104 obj.name = name
105 105 obj.parents = parents
106 106 obj.stripnodes = stripnodes
107 107
108 108 return obj
109 109
110 110 @classmethod
111 111 def save(cls, repo, name, stripnodes):
112 112 fp = repo.opener(cls._filename, 'wb')
113 113 fp.write('%i\n' % cls._version)
114 114 fp.write('%s\n' % name)
115 115 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
116 116 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
117 117 fp.close()
118 118
119 119 @classmethod
120 120 def clear(cls, repo):
121 121 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
122 122
123 123 def createcmd(ui, repo, pats, opts):
124 124 """subcommand that creates a new shelve"""
125 125
126 126 def publicancestors(ctx):
127 127 """Compute the heads of the public ancestors of a commit.
128 128
129 129 Much faster than the revset heads(ancestors(ctx) - draft())"""
130 130 seen = set()
131 131 visit = util.deque()
132 132 visit.append(ctx)
133 133 while visit:
134 134 ctx = visit.popleft()
135 135 for parent in ctx.parents():
136 136 rev = parent.rev()
137 137 if rev not in seen:
138 138 seen.add(rev)
139 139 if parent.mutable():
140 140 visit.append(parent)
141 141 else:
142 142 yield parent.node()
143 143
144 144 wctx = repo[None]
145 145 parents = wctx.parents()
146 146 if len(parents) > 1:
147 147 raise util.Abort(_('cannot shelve while merging'))
148 148 parent = parents[0]
149 149
150 150 # we never need the user, so we use a generic user for all shelve operations
151 151 user = 'shelve@localhost'
152 152 label = repo._bookmarkcurrent or parent.branch() or 'default'
153 153
154 154 # slashes aren't allowed in filenames, therefore we rename it
155 155 origlabel, label = label, label.replace('/', '_')
156 156
157 157 def gennames():
158 158 yield label
159 159 for i in xrange(1, 100):
160 160 yield '%s-%02d' % (label, i)
161 161
162 162 shelvedfiles = []
163 163
164 164 def commitfunc(ui, repo, message, match, opts):
165 165 # check modified, added, removed, deleted only
166 166 for flist in repo.status(match=match)[:4]:
167 167 shelvedfiles.extend(flist)
168 168 hasmq = util.safehasattr(repo, 'mq')
169 169 if hasmq:
170 170 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
171 171 try:
172 172 return repo.commit(message, user, opts.get('date'), match)
173 173 finally:
174 174 if hasmq:
175 175 repo.mq.checkapplied = saved
176 176
177 177 if parent.node() != nullid:
178 178 desc = parent.description().split('\n', 1)[0]
179 179 else:
180 180 desc = '(empty repository)'
181 181
182 182 if not opts['message']:
183 183 opts['message'] = desc
184 184
185 185 name = opts['name']
186 186
187 187 wlock = lock = tr = bms = None
188 188 try:
189 189 wlock = repo.wlock()
190 190 lock = repo.lock()
191 191
192 192 bms = repo._bookmarks.copy()
193 193 # use an uncommited transaction to generate the bundle to avoid
194 194 # pull races. ensure we don't print the abort message to stderr.
195 195 tr = repo.transaction('commit', report=lambda x: None)
196 196
197 197 if name:
198 198 if shelvedfile(repo, name, 'hg').exists():
199 199 raise util.Abort(_("a shelved change named '%s' already exists")
200 200 % name)
201 201 else:
202 202 for n in gennames():
203 203 if not shelvedfile(repo, n, 'hg').exists():
204 204 name = n
205 205 break
206 206 else:
207 207 raise util.Abort(_("too many shelved changes named '%s'") %
208 208 label)
209 209
210 210 # ensure we are not creating a subdirectory or a hidden file
211 211 if '/' in name or '\\' in name:
212 212 raise util.Abort(_('shelved change names may not contain slashes'))
213 213 if name.startswith('.'):
214 214 raise util.Abort(_("shelved change names may not start with '.'"))
215 215
216 216 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
217 217
218 218 if not node:
219 219 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
220 220 if stat[3]:
221 221 ui.status(_("nothing changed (%d missing files, see "
222 222 "'hg status')\n") % len(stat[3]))
223 223 else:
224 224 ui.status(_("nothing changed\n"))
225 225 return 1
226 226
227 227 phases.retractboundary(repo, phases.secret, [node])
228 228
229 229 fp = shelvedfile(repo, name, 'files').opener('wb')
230 230 fp.write('\0'.join(shelvedfiles))
231 231
232 232 bases = list(publicancestors(repo[node]))
233 233 cg = repo.changegroupsubset(bases, [node], 'shelve')
234 234 changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
235 235 'HG10UN')
236 236 cmdutil.export(repo, [node],
237 237 fp=shelvedfile(repo, name, 'patch').opener('wb'),
238 238 opts=mdiff.diffopts(git=True))
239 239
240 240
241 241 if ui.formatted():
242 242 desc = util.ellipsis(desc, ui.termwidth())
243 243 ui.status(_('shelved as %s\n') % name)
244 244 hg.update(repo, parent.node())
245 245 finally:
246 246 if bms:
247 247 # restore old bookmarks
248 248 repo._bookmarks.update(bms)
249 249 repo._bookmarks.write()
250 250 if tr:
251 251 tr.abort()
252 252 lockmod.release(lock, wlock)
253 253
254 254 def cleanupcmd(ui, repo):
255 255 """subcommand that deletes all shelves"""
256 256
257 257 wlock = None
258 258 try:
259 259 wlock = repo.wlock()
260 260 for (name, _) in repo.vfs.readdir('shelved'):
261 261 suffix = name.rsplit('.', 1)[-1]
262 262 if suffix in ('hg', 'files', 'patch'):
263 263 shelvedfile(repo, name).unlink()
264 264 finally:
265 265 lockmod.release(wlock)
266 266
267 267 def deletecmd(ui, repo, pats):
268 268 """subcommand that deletes a specific shelve"""
269 269 if not pats:
270 270 raise util.Abort(_('no shelved changes specified!'))
271 271 wlock = None
272 272 try:
273 273 wlock = repo.wlock()
274 274 try:
275 275 for name in pats:
276 276 for suffix in 'hg files patch'.split():
277 277 shelvedfile(repo, name, suffix).unlink()
278 278 except OSError, err:
279 279 if err.errno != errno.ENOENT:
280 280 raise
281 281 raise util.Abort(_("shelved change '%s' not found") % name)
282 282 finally:
283 283 lockmod.release(wlock)
284 284
285 285 def listshelves(repo):
286 286 """return all shelves in repo as list of (time, filename)"""
287 287 try:
288 288 names = repo.vfs.readdir('shelved')
289 289 except OSError, err:
290 290 if err.errno != errno.ENOENT:
291 291 raise
292 292 return []
293 293 info = []
294 294 for (name, _) in names:
295 295 pfx, sfx = name.rsplit('.', 1)
296 296 if not pfx or sfx != 'patch':
297 297 continue
298 298 st = shelvedfile(repo, name).stat()
299 299 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
300 300 return sorted(info, reverse=True)
301 301
302 302 def listcmd(ui, repo, pats, opts):
303 303 """subcommand that displays the list of shelves"""
304 304 pats = set(pats)
305 305 width = 80
306 306 if not ui.plain():
307 307 width = ui.termwidth()
308 308 namelabel = 'shelve.newest'
309 309 for mtime, name in listshelves(repo):
310 310 sname = util.split(name)[1]
311 311 if pats and sname not in pats:
312 312 continue
313 313 ui.write(sname, label=namelabel)
314 314 namelabel = 'shelve.name'
315 315 if ui.quiet:
316 316 ui.write('\n')
317 317 continue
318 318 ui.write(' ' * (16 - len(sname)))
319 319 used = 16
320 320 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
321 321 ui.write(age, label='shelve.age')
322 322 ui.write(' ' * (12 - len(age)))
323 323 used += 12
324 324 fp = open(name + '.patch', 'rb')
325 325 try:
326 326 while True:
327 327 line = fp.readline()
328 328 if not line:
329 329 break
330 330 if not line.startswith('#'):
331 331 desc = line.rstrip()
332 332 if ui.formatted():
333 333 desc = util.ellipsis(desc, width - used)
334 334 ui.write(desc)
335 335 break
336 336 ui.write('\n')
337 337 if not (opts['patch'] or opts['stat']):
338 338 continue
339 339 difflines = fp.readlines()
340 340 if opts['patch']:
341 341 for chunk, label in patch.difflabel(iter, difflines):
342 342 ui.write(chunk, label=label)
343 343 if opts['stat']:
344 344 for chunk, label in patch.diffstatui(difflines, width=width,
345 345 git=True):
346 346 ui.write(chunk, label=label)
347 347 finally:
348 348 fp.close()
349 349
350 350 def readshelvedfiles(repo, basename):
351 351 """return the list of files touched in a shelve"""
352 352 fp = shelvedfile(repo, basename, 'files').opener()
353 353 return fp.read().split('\0')
354 354
355 355 def checkparents(repo, state):
356 356 """check parent while resuming an unshelve"""
357 357 if state.parents != repo.dirstate.parents():
358 358 raise util.Abort(_('working directory parents do not match unshelve '
359 359 'state'))
360 360
361 def pathtofiles(repo, files):
362 cwd = repo.getcwd()
363 return [repo.pathto(f, cwd) for f in files]
364
361 365 def unshelveabort(ui, repo, state, opts):
362 366 """subcommand that abort an in-progress unshelve"""
363 367 wlock = repo.wlock()
364 368 lock = None
365 369 try:
366 370 checkparents(repo, state)
367 371 lock = repo.lock()
368 372 merge.mergestate(repo).reset()
369 373 if opts['keep']:
370 374 repo.setparents(repo.dirstate.parents()[0])
371 375 else:
372 376 revertfiles = readshelvedfiles(repo, state.name)
373 377 wctx = repo.parents()[0]
374 378 cmdutil.revert(ui, repo, wctx, [wctx.node(), nullid],
375 *revertfiles, **{'no_backup': True})
379 *pathtofiles(repo, revertfiles),
380 **{'no_backup': True})
376 381 # fix up the weird dirstate states the merge left behind
377 382 mf = wctx.manifest()
378 383 dirstate = repo.dirstate
379 384 for f in revertfiles:
380 385 if f in mf:
381 386 dirstate.normallookup(f)
382 387 else:
383 388 dirstate.drop(f)
384 389 dirstate._pl = (wctx.node(), nullid)
385 390 dirstate._dirty = True
386 391 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
387 392 shelvedstate.clear(repo)
388 393 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
389 394 finally:
390 395 lockmod.release(lock, wlock)
391 396
392 397 def unshelvecleanup(ui, repo, name, opts):
393 398 """remove related files after an unshelve"""
394 399 if not opts['keep']:
395 400 for filetype in 'hg files patch'.split():
396 401 shelvedfile(repo, name, filetype).unlink()
397 402
398 403 def finishmerge(ui, repo, ms, stripnodes, name, opts):
399 404 # Reset the working dir so it's no longer in a merge state.
400 405 dirstate = repo.dirstate
401 406 dirstate.setparents(dirstate._pl[0])
402 407 shelvedstate.clear(repo)
403 408
404 409 def unshelvecontinue(ui, repo, state, opts):
405 410 """subcommand to continue an in-progress unshelve"""
406 411 # We're finishing off a merge. First parent is our original
407 412 # parent, second is the temporary "fake" commit we're unshelving.
408 413 wlock = repo.wlock()
409 414 lock = None
410 415 try:
411 416 checkparents(repo, state)
412 417 ms = merge.mergestate(repo)
413 418 if [f for f in ms if ms[f] == 'u']:
414 419 raise util.Abort(
415 420 _("unresolved conflicts, can't continue"),
416 421 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
417 422 finishmerge(ui, repo, ms, state.stripnodes, state.name, opts)
418 423 lock = repo.lock()
419 424 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
420 425 unshelvecleanup(ui, repo, state.name, opts)
421 426 ui.status(_("unshelve of '%s' complete\n") % state.name)
422 427 finally:
423 428 lockmod.release(lock, wlock)
424 429
425 430 @command('unshelve',
426 431 [('a', 'abort', None,
427 432 _('abort an incomplete unshelve operation')),
428 433 ('c', 'continue', None,
429 434 _('continue an incomplete unshelve operation')),
430 435 ('', 'keep', None,
431 436 _('keep shelve after unshelving'))],
432 437 _('hg unshelve [SHELVED]'))
433 438 def unshelve(ui, repo, *shelved, **opts):
434 439 """restore a shelved change to the working directory
435 440
436 441 This command accepts an optional name of a shelved change to
437 442 restore. If none is given, the most recent shelved change is used.
438 443
439 444 If a shelved change is applied successfully, the bundle that
440 445 contains the shelved changes is deleted afterwards.
441 446
442 447 Since you can restore a shelved change on top of an arbitrary
443 448 commit, it is possible that unshelving will result in a conflict
444 449 between your changes and the commits you are unshelving onto. If
445 450 this occurs, you must resolve the conflict, then use
446 451 ``--continue`` to complete the unshelve operation. (The bundle
447 452 will not be deleted until you successfully complete the unshelve.)
448 453
449 454 (Alternatively, you can use ``--abort`` to abandon an unshelve
450 455 that causes a conflict. This reverts the unshelved changes, and
451 456 does not delete the bundle.)
452 457 """
453 458 abortf = opts['abort']
454 459 continuef = opts['continue']
455 460 if not abortf and not continuef:
456 461 cmdutil.checkunfinished(repo)
457 462
458 463 if abortf or continuef:
459 464 if abortf and continuef:
460 465 raise util.Abort(_('cannot use both abort and continue'))
461 466 if shelved:
462 467 raise util.Abort(_('cannot combine abort/continue with '
463 468 'naming a shelved change'))
464 469
465 470 try:
466 471 state = shelvedstate.load(repo)
467 472 except IOError, err:
468 473 if err.errno != errno.ENOENT:
469 474 raise
470 475 raise util.Abort(_('no unshelve operation underway'))
471 476
472 477 if abortf:
473 478 return unshelveabort(ui, repo, state, opts)
474 479 elif continuef:
475 480 return unshelvecontinue(ui, repo, state, opts)
476 481 elif len(shelved) > 1:
477 482 raise util.Abort(_('can only unshelve one change at a time'))
478 483 elif not shelved:
479 484 shelved = listshelves(repo)
480 485 if not shelved:
481 486 raise util.Abort(_('no shelved changes to apply!'))
482 487 basename = util.split(shelved[0][1])[1]
483 488 ui.status(_("unshelving change '%s'\n") % basename)
484 489 else:
485 490 basename = shelved[0]
486 491
487 492 shelvedfiles = readshelvedfiles(repo, basename)
488 493
489 494 m, a, r, d = repo.status()[:4]
490 495 unsafe = set(m + a + r + d).intersection(shelvedfiles)
491 496 if unsafe:
492 497 ui.warn(_('the following shelved files have been modified:\n'))
493 498 for f in sorted(unsafe):
494 499 ui.warn(' %s\n' % f)
495 500 ui.warn(_('you must commit, revert, or shelve your changes before you '
496 501 'can proceed\n'))
497 502 raise util.Abort(_('cannot unshelve due to local changes\n'))
498 503
499 504 wlock = lock = tr = None
500 505 try:
501 506 lock = repo.lock()
502 507
503 508 tr = repo.transaction('unshelve', report=lambda x: None)
504 509 oldtiprev = len(repo)
505 510 try:
506 511 fp = shelvedfile(repo, basename, 'hg').opener()
507 512 gen = changegroup.readbundle(fp, fp.name)
508 513 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
509 514 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
510 515 phases.retractboundary(repo, phases.secret, nodes)
511 516 tr.close()
512 517 finally:
513 518 fp.close()
514 519
515 520 tip = repo['tip']
516 521 wctx = repo['.']
517 522 ancestor = tip.ancestor(wctx)
518 523
519 524 wlock = repo.wlock()
520 525
521 526 if ancestor.node() != wctx.node():
522 527 conflicts = hg.merge(repo, tip.node(), force=True, remind=False)
523 528 ms = merge.mergestate(repo)
524 529 stripnodes = [repo.changelog.node(rev)
525 530 for rev in xrange(oldtiprev, len(repo))]
526 531 if conflicts:
527 532 shelvedstate.save(repo, basename, stripnodes)
528 533 # Fix up the dirstate entries of files from the second
529 534 # parent as if we were not merging, except for those
530 535 # with unresolved conflicts.
531 536 parents = repo.parents()
532 537 revertfiles = set(parents[1].files()).difference(ms)
533 538 cmdutil.revert(ui, repo, parents[1],
534 539 (parents[0].node(), nullid),
535 *revertfiles, **{'no_backup': True})
540 *pathtofiles(repo, revertfiles),
541 **{'no_backup': True})
536 542 raise error.InterventionRequired(
537 543 _("unresolved conflicts (see 'hg resolve', then "
538 544 "'hg unshelve --continue')"))
539 545 finishmerge(ui, repo, ms, stripnodes, basename, opts)
540 546 else:
541 547 parent = tip.parents()[0]
542 548 hg.update(repo, parent.node())
543 cmdutil.revert(ui, repo, tip, repo.dirstate.parents(), *tip.files(),
549 cmdutil.revert(ui, repo, tip, repo.dirstate.parents(),
550 *pathtofiles(repo, tip.files()),
544 551 **{'no_backup': True})
545 552
546 553 prevquiet = ui.quiet
547 554 ui.quiet = True
548 555 try:
549 556 repo.rollback(force=True)
550 557 finally:
551 558 ui.quiet = prevquiet
552 559
553 560 unshelvecleanup(ui, repo, basename, opts)
554 561 finally:
555 562 if tr:
556 563 tr.release()
557 564 lockmod.release(lock, wlock)
558 565
559 566 @command('shelve',
560 567 [('A', 'addremove', None,
561 568 _('mark new/missing files as added/removed before shelving')),
562 569 ('', 'cleanup', None,
563 570 _('delete all shelved changes')),
564 571 ('', 'date', '',
565 572 _('shelve with the specified commit date'), _('DATE')),
566 573 ('d', 'delete', None,
567 574 _('delete the named shelved change(s)')),
568 575 ('l', 'list', None,
569 576 _('list current shelves')),
570 577 ('m', 'message', '',
571 578 _('use text as shelve message'), _('TEXT')),
572 579 ('n', 'name', '',
573 580 _('use the given name for the shelved commit'), _('NAME')),
574 581 ('p', 'patch', None,
575 582 _('show patch')),
576 583 ('', 'stat', None,
577 584 _('output diffstat-style summary of changes'))],
578 585 _('hg shelve'))
579 586 def shelvecmd(ui, repo, *pats, **opts):
580 587 '''save and set aside changes from the working directory
581 588
582 589 Shelving takes files that "hg status" reports as not clean, saves
583 590 the modifications to a bundle (a shelved change), and reverts the
584 591 files so that their state in the working directory becomes clean.
585 592
586 593 To restore these changes to the working directory, using "hg
587 594 unshelve"; this will work even if you switch to a different
588 595 commit.
589 596
590 597 When no files are specified, "hg shelve" saves all not-clean
591 598 files. If specific files or directories are named, only changes to
592 599 those files are shelved.
593 600
594 601 Each shelved change has a name that makes it easier to find later.
595 602 The name of a shelved change defaults to being based on the active
596 603 bookmark, or if there is no active bookmark, the current named
597 604 branch. To specify a different name, use ``--name``.
598 605
599 606 To see a list of existing shelved changes, use the ``--list``
600 607 option. For each shelved change, this will print its name, age,
601 608 and description; use ``--patch`` or ``--stat`` for more details.
602 609
603 610 To delete specific shelved changes, use ``--delete``. To delete
604 611 all shelved changes, use ``--cleanup``.
605 612 '''
606 613 cmdutil.checkunfinished(repo)
607 614
608 615 def checkopt(opt, incompatible):
609 616 if opts[opt]:
610 617 for i in incompatible.split():
611 618 if opts[i]:
612 619 raise util.Abort(_("options '--%s' and '--%s' may not be "
613 620 "used together") % (opt, i))
614 621 return True
615 622 if checkopt('cleanup', 'addremove delete list message name patch stat'):
616 623 if pats:
617 624 raise util.Abort(_("cannot specify names when using '--cleanup'"))
618 625 return cleanupcmd(ui, repo)
619 626 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
620 627 return deletecmd(ui, repo, pats)
621 628 elif checkopt('list', 'addremove cleanup delete message name'):
622 629 return listcmd(ui, repo, pats, opts)
623 630 else:
624 631 for i in ('patch', 'stat'):
625 632 if opts[i]:
626 633 raise util.Abort(_("option '--%s' may not be "
627 634 "used when shelving a change") % (i,))
628 635 return createcmd(ui, repo, pats, opts)
629 636
630 637 def extsetup(ui):
631 638 cmdutil.unfinishedstates.append(
632 639 [shelvedstate._filename, False, True, _('unshelve already in progress'),
633 640 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
@@ -1,484 +1,486 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "mq=" >> $HGRCPATH
3 3 $ echo "shelve=" >> $HGRCPATH
4 4 $ echo "[defaults]" >> $HGRCPATH
5 5 $ echo "diff = --nodates --git" >> $HGRCPATH
6 6 $ echo "qnew = --date '0 0'" >> $HGRCPATH
7 7
8 8 $ hg init repo
9 9 $ cd repo
10 10 $ mkdir a b
11 11 $ echo a > a/a
12 12 $ echo b > b/b
13 13 $ echo c > c
14 14 $ echo d > d
15 15 $ echo x > x
16 16 $ hg addremove -q
17 17
18 18 shelving in an empty repo should be possible
19 19
20 20 $ hg shelve
21 21 shelved as default
22 22 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
23 23
24 24 $ hg unshelve
25 25 unshelving change 'default'
26 26 adding changesets
27 27 adding manifests
28 28 adding file changes
29 29 added 1 changesets with 5 changes to 5 files
30 30 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 31
32 32 $ hg commit -q -m 'initial commit'
33 33
34 34 $ hg shelve
35 35 nothing changed
36 36 [1]
37 37
38 38 create an mq patch - shelving should work fine with a patch applied
39 39
40 40 $ echo n > n
41 41 $ hg add n
42 42 $ hg commit n -m second
43 43 $ hg qnew second.patch
44 44
45 45 shelve a change that we will delete later
46 46
47 47 $ echo a >> a/a
48 48 $ hg shelve
49 49 shelved as default
50 50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 51
52 52 set up some more complex changes to shelve
53 53
54 54 $ echo a >> a/a
55 55 $ hg mv b b.rename
56 56 moving b/b to b.rename/b (glob)
57 57 $ hg cp c c.copy
58 58 $ hg status -C
59 59 M a/a
60 60 A b.rename/b
61 61 b/b
62 62 A c.copy
63 63 c
64 64 R b/b
65 65
66 66 prevent some foot-shooting
67 67
68 68 $ hg shelve -n foo/bar
69 69 abort: shelved change names may not contain slashes
70 70 [255]
71 71 $ hg shelve -n .baz
72 72 abort: shelved change names may not start with '.'
73 73 [255]
74 74
75 75 the common case - no options or filenames
76 76
77 77 $ hg shelve
78 78 shelved as default-01
79 79 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
80 80 $ hg status -C
81 81
82 82 ensure that our shelved changes exist
83 83
84 84 $ hg shelve -l
85 85 default-01 (*) [mq]: second.patch (glob)
86 86 default (*) [mq]: second.patch (glob)
87 87
88 88 $ hg shelve -l -p default
89 89 default (*) [mq]: second.patch (glob)
90 90
91 91 diff --git a/a/a b/a/a
92 92 --- a/a/a
93 93 +++ b/a/a
94 94 @@ -1,1 +1,2 @@
95 95 a
96 96 +a
97 97
98 98 delete our older shelved change
99 99
100 100 $ hg shelve -d default
101 101 $ hg qfinish -a -q
102 102
103 103 local edits should prevent a shelved change from applying
104 104
105 105 $ echo e>>a/a
106 106 $ hg unshelve
107 107 unshelving change 'default-01'
108 108 the following shelved files have been modified:
109 109 a/a
110 110 you must commit, revert, or shelve your changes before you can proceed
111 111 abort: cannot unshelve due to local changes
112 112
113 113 [255]
114 114
115 115 $ hg revert -C a/a
116 116
117 117 apply it and make sure our state is as expected
118 118
119 119 $ hg unshelve
120 120 unshelving change 'default-01'
121 121 adding changesets
122 122 adding manifests
123 123 adding file changes
124 124 added 1 changesets with 3 changes to 8 files
125 125 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 126 $ hg status -C
127 127 M a/a
128 128 A b.rename/b
129 129 b/b
130 130 A c.copy
131 131 c
132 132 R b/b
133 133 $ hg shelve -l
134 134
135 135 $ hg unshelve
136 136 abort: no shelved changes to apply!
137 137 [255]
138 138 $ hg unshelve foo
139 139 abort: shelved change 'foo' not found
140 140 [255]
141 141
142 142 named shelves, specific filenames, and "commit messages" should all work
143 143
144 144 $ hg status -C
145 145 M a/a
146 146 A b.rename/b
147 147 b/b
148 148 A c.copy
149 149 c
150 150 R b/b
151 151 $ hg shelve -q -n wibble -m wat a
152 152
153 153 expect "a" to no longer be present, but status otherwise unchanged
154 154
155 155 $ hg status -C
156 156 A b.rename/b
157 157 b/b
158 158 A c.copy
159 159 c
160 160 R b/b
161 161 $ hg shelve -l --stat
162 162 wibble (*) wat (glob)
163 163 a/a | 1 +
164 164 1 files changed, 1 insertions(+), 0 deletions(-)
165 165
166 166 and now "a/a" should reappear
167 167
168 $ cd a
168 169 $ hg unshelve -q wibble
170 $ cd ..
169 171 $ hg status -C
170 172 M a/a
171 173 A b.rename/b
172 174 b/b
173 175 A c.copy
174 176 c
175 177 R b/b
176 178
177 179 cause unshelving to result in a merge with 'a' conflicting
178 180
179 181 $ hg shelve -q
180 182 $ echo c>>a/a
181 183 $ hg commit -m second
182 184 $ hg tip --template '{files}\n'
183 185 a/a
184 186
185 187 add an unrelated change that should be preserved
186 188
187 189 $ mkdir foo
188 190 $ echo foo > foo/foo
189 191 $ hg add foo/foo
190 192
191 193 force a conflicted merge to occur
192 194
193 195 $ hg unshelve
194 196 unshelving change 'default'
195 197 adding changesets
196 198 adding manifests
197 199 adding file changes
198 200 added 1 changesets with 3 changes to 8 files (+1 heads)
199 201 merging a/a
200 202 warning: conflicts during merge.
201 203 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
202 204 2 files updated, 0 files merged, 1 files removed, 1 files unresolved
203 205 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
204 206 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
205 207 [1]
206 208
207 209 ensure that we have a merge with unresolved conflicts
208 210
209 211 $ hg heads -q
210 212 4:cebf2b8de087
211 213 3:2e69b451d1ea
212 214 $ hg parents -q
213 215 3:2e69b451d1ea
214 216 4:cebf2b8de087
215 217 $ hg status
216 218 M a/a
217 219 M b.rename/b
218 220 M c.copy
219 221 A foo/foo
220 222 R b/b
221 223 ? a/a.orig
222 224 $ hg diff
223 225 diff --git a/a/a b/a/a
224 226 --- a/a/a
225 227 +++ b/a/a
226 228 @@ -1,2 +1,6 @@
227 229 a
228 230 +<<<<<<< local
229 231 c
230 232 +=======
231 233 +a
232 234 +>>>>>>> other
233 235 diff --git a/b.rename/b b/b.rename/b
234 236 --- /dev/null
235 237 +++ b/b.rename/b
236 238 @@ -0,0 +1,1 @@
237 239 +b
238 240 diff --git a/b/b b/b/b
239 241 deleted file mode 100644
240 242 --- a/b/b
241 243 +++ /dev/null
242 244 @@ -1,1 +0,0 @@
243 245 -b
244 246 diff --git a/c.copy b/c.copy
245 247 --- /dev/null
246 248 +++ b/c.copy
247 249 @@ -0,0 +1,1 @@
248 250 +c
249 251 diff --git a/foo/foo b/foo/foo
250 252 new file mode 100644
251 253 --- /dev/null
252 254 +++ b/foo/foo
253 255 @@ -0,0 +1,1 @@
254 256 +foo
255 257 $ hg resolve -l
256 258 U a/a
257 259
258 260 $ hg shelve
259 261 abort: unshelve already in progress
260 262 (use 'hg unshelve --continue' or 'hg unshelve --abort')
261 263 [255]
262 264
263 265 abort the unshelve and be happy
264 266
265 267 $ hg status
266 268 M a/a
267 269 M b.rename/b
268 270 M c.copy
269 271 A foo/foo
270 272 R b/b
271 273 ? a/a.orig
272 274 $ hg unshelve -a
273 275 unshelve of 'default' aborted
274 276 $ hg heads -q
275 277 3:2e69b451d1ea
276 278 $ hg parents
277 279 changeset: 3:2e69b451d1ea
278 280 tag: tip
279 281 user: test
280 282 date: Thu Jan 01 00:00:00 1970 +0000
281 283 summary: second
282 284
283 285 $ hg resolve -l
284 286 $ hg status
285 287 A foo/foo
286 288 ? a/a.orig
287 289
288 290 try to continue with no unshelve underway
289 291
290 292 $ hg unshelve -c
291 293 abort: no unshelve operation underway
292 294 [255]
293 295 $ hg status
294 296 A foo/foo
295 297 ? a/a.orig
296 298
297 299 redo the unshelve to get a conflict
298 300
299 301 $ hg unshelve -q
300 302 warning: conflicts during merge.
301 303 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
302 304 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
303 305 [1]
304 306
305 307 attempt to continue
306 308
307 309 $ hg unshelve -c
308 310 abort: unresolved conflicts, can't continue
309 311 (see 'hg resolve', then 'hg unshelve --continue')
310 312 [255]
311 313
312 314 $ hg revert -r . a/a
313 315 $ hg resolve -m a/a
314 316
315 317 $ hg unshelve -c
316 318 unshelve of 'default' complete
317 319
318 320 ensure the repo is as we hope
319 321
320 322 $ hg parents
321 323 changeset: 3:2e69b451d1ea
322 324 tag: tip
323 325 user: test
324 326 date: Thu Jan 01 00:00:00 1970 +0000
325 327 summary: second
326 328
327 329 $ hg heads -q
328 330 3:2e69b451d1ea
329 331
330 332 $ hg status -C
331 333 M b.rename/b
332 334 b/b
333 335 M c.copy
334 336 c
335 337 A foo/foo
336 338 R b/b
337 339 ? a/a.orig
338 340
339 341 there should be no shelves left
340 342
341 343 $ hg shelve -l
342 344
343 345 #if execbit
344 346
345 347 ensure that metadata-only changes are shelved
346 348
347 349 $ chmod +x a/a
348 350 $ hg shelve -q -n execbit a/a
349 351 $ hg status a/a
350 352 $ hg unshelve -q execbit
351 353 $ hg status a/a
352 354 M a/a
353 355 $ hg revert a/a
354 356
355 357 #endif
356 358
357 359 #if symlink
358 360
359 361 $ rm a/a
360 362 $ ln -s foo a/a
361 363 $ hg shelve -q -n symlink a/a
362 364 $ hg status a/a
363 365 $ hg unshelve -q symlink
364 366 $ hg status a/a
365 367 M a/a
366 368 $ hg revert a/a
367 369
368 370 #endif
369 371
370 372 set up another conflict between a commit and a shelved change
371 373
372 374 $ hg revert -q -C -a
373 375 $ echo a >> a/a
374 376 $ hg shelve -q
375 377 $ echo x >> a/a
376 378 $ hg ci -m 'create conflict'
377 379 $ hg add foo/foo
378 380
379 381 if we resolve a conflict while unshelving, the unshelve should succeed
380 382
381 383 $ HGMERGE=true hg unshelve
382 384 unshelving change 'default'
383 385 adding changesets
384 386 adding manifests
385 387 adding file changes
386 388 added 1 changesets with 1 changes to 6 files (+1 heads)
387 389 merging a/a
388 390 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
389 391 $ hg parents -q
390 392 4:33f7f61e6c5e
391 393 $ hg shelve -l
392 394 $ hg status
393 395 A foo/foo
394 396 $ cat a/a
395 397 a
396 398 c
397 399 x
398 400
399 401 test keep and cleanup
400 402
401 403 $ hg shelve
402 404 shelved as default
403 405 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
404 406 $ hg shelve --list
405 407 default (*) create conflict (glob)
406 408 $ hg unshelve --keep
407 409 unshelving change 'default'
408 410 adding changesets
409 411 adding manifests
410 412 adding file changes
411 413 added 1 changesets with 1 changes to 7 files
412 414 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 415 $ hg shelve --list
414 416 default (*) create conflict (glob)
415 417 $ hg shelve --cleanup
416 418 $ hg shelve --list
417 419
418 420 test bookmarks
419 421
420 422 $ hg bookmark test
421 423 $ hg bookmark
422 424 * test 4:33f7f61e6c5e
423 425 $ hg shelve
424 426 shelved as test
425 427 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
426 428 $ hg bookmark
427 429 * test 4:33f7f61e6c5e
428 430 $ hg unshelve
429 431 unshelving change 'test'
430 432 adding changesets
431 433 adding manifests
432 434 adding file changes
433 435 added 1 changesets with 1 changes to 7 files
434 436 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 437 $ hg bookmark
436 438 * test 4:33f7f61e6c5e
437 439
438 440 shelve should still work even if mq is disabled
439 441
440 442 $ hg --config extensions.mq=! shelve
441 443 shelved as test
442 444 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
443 445 $ hg --config extensions.mq=! shelve --list
444 446 test (1s ago) create conflict
445 447 $ hg --config extensions.mq=! unshelve
446 448 unshelving change 'test'
447 449 adding changesets
448 450 adding manifests
449 451 adding file changes
450 452 added 1 changesets with 1 changes to 7 files
451 453 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
452 454
453 455 shelve should leave dirstate clean (issue 4055)
454 456
455 457 $ cd ..
456 458 $ hg init shelverebase
457 459 $ cd shelverebase
458 460 $ printf 'x\ny\n' > x
459 461 $ echo z > z
460 462 $ hg commit -Aqm xy
461 463 $ echo z >> x
462 464 $ hg commit -Aqm z
463 465 $ hg up 0
464 466 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
465 467 $ printf 'a\nx\ny\nz\n' > x
466 468 $ hg commit -Aqm xyz
467 469 $ echo c >> z
468 470 $ hg shelve
469 471 shelved as default
470 472 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
471 473 $ hg rebase -d 1 --config extensions.rebase=
472 474 merging x
473 475 saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-backup.hg (glob)
474 476 $ hg unshelve
475 477 unshelving change 'default'
476 478 adding changesets
477 479 adding manifests
478 480 adding file changes
479 481 added 2 changesets with 2 changes to 2 files (+1 heads)
480 482 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
481 483 $ hg status
482 484 M z
483 485
484 486 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now