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