[PATCH RFC/WIP] introduce stash command
Idan Kamara
idankk86 at gmail.com
Sat Jun 2 08:25:11 CDT 2012
# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1338643506 -10800
# Node ID 6e5f3f08d11199f5893b501dfa8096979675314f
# Parent 357e6bcfb61973478bfbe4cf5652026a6bda7ef7
introduce stash command
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -17,7 +17,7 @@
import minirst, revset, fileset
import dagparser, context, simplemerge
import random, setdiscovery, treediscovery, dagutil, pvec
-import phases
+import phases, stash
table = {}
@@ -5159,6 +5159,104 @@
ui.configsource(section, name, untrusted))
ui.write('%s=%s\n' % (sectname, value))
+ at command("stash",
+ [('l', 'list', False, _('list all stashes')),
+ ('f', 'force', False, _('unstash with dirty working dir')),
+ ('a', 'apply', False, _('apply the named or most recent stash')),
+ ('', 'pop', False,
+ _('apply and pop the named or most recent stash')),
+ ('p', 'patch', False,
+ _('show patch of the named or most recent stash')),
+ ('d', 'delete', False, _('delete the named or most recent stash')),
+ ('A', 'addremove', None,
+ _('mark new/missing files as added/removed before stashing')),
+ ('m', 'message', '', _('use text as stash message'), _('TEXT'))]
+ + walkopts + mergetoolopts,
+ _('hg stash [-l|-a|--pop|-p|-d] [-f] [NAME]'))
+def stash_(ui, repo, name=None, pop=False, apply=False,
+ delete=False, patch=False, **opts):
+ """stash all changes in the working directory
+
+ Saves the state of the working directory so it can be
+ restored at a later time.
+
+ Stashes are unnamed (by default) and can be listed
+ using --list and inspected with -p/--patch.
+
+ Referring to stashes is done either by index (as
+ shown in --list) or name (if one was given). If neither
+ is specified, the most recent stash is chosen.
+
+ A stash is saved as a regular commit in the repository and
+ is identified by a bookmark. It is hidden from the user
+ (not yet). When a stash is popped or deleted, it is removed
+ from the repository.
+
+ .. container:: verbose
+
+ Examples:
+
+ - stash changes in a named stash::
+
+ hg stash wip
+
+ - unstash (but don't delete) a named stash::
+
+ hg stash -a wip
+
+ - unstash and remove previous stash::
+
+ hg stash --pop
+
+ - show previous stash::
+
+ hg stash -p
+
+ - list all stashes
+
+ hg stash -l
+ """
+ if opts.get('list'):
+ for i, (name, ctx) in enumerate(sorted(stash.list_(repo).iteritems(),
+ key=lambda k: k[1].rev())):
+ name = name[6:]
+ if name.startswith('unnamed'):
+ name = 'unnamed'
+ ui.write("%d %s@%d:%s - %s\n" % (i, name, ctx.p1().rev(),
+ short(ctx.p1().node()), ctx.description()))
+ return
+
+ if name:
+ try:
+ name = int(name)
+ except ValueError:
+ name = 'stash/' + name
+
+ if pop or apply:
+ name, ctx = stash.get(repo, name)
+ stats = stash.apply(repo, ctx, **opts)
+ if stats and stats[3] > 0:
+ if pop:
+ raise util.Abort(_('unresolved conflicts, not popping'),
+ hint=_('use hg resolve and '
+ 'hg stash --delete'))
+ else:
+ raise util.Abort(_('unresolved conflicts'),
+ hint=_('use hg resolve'))
+ if pop:
+ stash.delete(ui, repo, name)
+ elif delete:
+ name, ctx = stash.get(repo, name)
+ stash.delete(ui, repo, name)
+ elif patch:
+ name, ctx = stash.get(repo, name)
+ diff(ui, repo, change=ctx.rev())
+ else:
+ ret = stash.create(ui, repo, name, **opts)
+ if ret:
+ ui.write(_("stashed working dir "
+ "(hg stash --pop to unstash)\n"))
+
@command('^status|st',
[('A', 'all', None, _('show status of all files')),
('m', 'modified', None, _('show only modified files')),
diff --git a/mercurial/stash.py b/mercurial/stash.py
new file mode 100644
--- /dev/null
+++ b/mercurial/stash.py
@@ -0,0 +1,122 @@
+from node import hex, bin, nullid, nullrev, short
+from i18n import _, gettext
+import hg, context, bookmarks, cmdutil, util, repair
+import merge as mergemod
+
+def create(ui, repo, name=None, **opts):
+ # stashes are marked using a bookmark with a special 'stash/' prefix
+ #
+ # we might want to have a more generic 'hg/' namespace for future
+ # use (so stashes will be saved at hg/stash/*)
+ stashes = list_(repo)
+ if name is None:
+ counter = -1
+ for name, ctx in stashes.iteritems():
+ name = name[13:]
+ try:
+ i = int(name)
+ if i > counter:
+ counter = i
+ except ValueError:
+ pass
+ name = 'stash/unnamed%d' % (counter+1)
+ elif name:
+ if name in stashes:
+ raise util.Abort(_('stash %s aleady exists') % name)
+
+ # this should be at the top, but the circular import is iffy
+ import commands
+ commitopts = dict(opts)
+ if not commitopts['message']:
+ commitopts['message'] = _('Stashed working directory')
+
+ ret = commands.commit(ui, repo, **commitopts)
+ if ret:
+ return
+ ctx = repo['.']
+
+ # silence update
+ repo.ui.pushbuffer()
+ ret = hg.update(repo, node=ctx.p1().node())
+ assert not ret
+ repo.ui.popbuffer()
+
+ if ctx in set(stashes.values()):
+ raise util.Abort(_('same changes already stashed'))
+ marks = repo._bookmarks
+ marks[name] = ctx.node()
+ bookmarks.write(repo)
+
+ return name, ctx
+
+def list_(repo):
+ d = {}
+ for mark, n in repo._bookmarks.iteritems():
+ if mark.startswith('stash/'):
+ d[mark] = repo[n]
+ return d
+
+def get(repo, id_=None):
+ """
+ if id_ = None, get the most recent stash (the one whose revision
+ is maximal (XXX is this true all the time?)). when it's an int,
+ sort the stashes by revision and use it as an index. otherwise,
+ find a named stash with that name.
+ """
+ stashes = list_(repo)
+ if id_ is None:
+ if stashes:
+ return max(stashes.iteritems(), key=lambda kv: kv[1].rev())
+ else:
+ raise util.Abort(_('there are no stashes'))
+ else:
+ if isinstance(id_, int):
+ if id_ >= len(stashes):
+ raise util.Abort(_("stash index out of range"))
+ return sorted(stashes.iteritems(),
+ key=lambda kv: kv[1].rev())[id_]
+ else:
+ try:
+ return id_, stashes[id_]
+ except KeyError:
+ raise util.Abort(_("stash %s doesn't exist") % id_[6:])
+
+def apply(repo, ctx, force=False, **opts):
+ wctx = repo[None]
+ if not force:
+ if wctx.dirty(branch=False):
+ raise util.Abort(_('cannot apply with a dirty working directory'),
+ hint=_('use -f to force'))
+ #if wctx.p1() != ctx.p1():
+ # repo.ui.warn(_('stash parent and working directory parent'
+ # ' are different\n'))
+
+ wlock = repo.wlock()
+ try:
+ repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ try:
+ # ui.forcemerge is an internal variable, do not document
+ stats = mergemod.update(repo, ctx.node(), True, True,
+ False, ctx.p1().node(), True)
+ finally:
+ repo.ui.setconfig('ui', 'forcemerge', '')
+ # drop the second merge parent
+ repo.setparents(wctx.p1().node(), nullid)
+ # fix up dirstate for copies and renames
+ cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
+
+ return stats
+ finally:
+ wlock.release()
+
+def delete(ui, repo, name):
+ node = repo._bookmarks[name]
+ if repo[node].children():
+ raise util.Abort(_('stash has children changesets, cannot delete'))
+ lock = repo.lock()
+ try:
+ del repo._bookmarks[name]
+ bookmarks.write(repo)
+ repair.strip(ui, repo, [node], topic='unstash-backup')
+ finally:
+ lock.release()
diff --git a/tests/test-stash.t b/tests/test-stash.t
new file mode 100644
--- /dev/null
+++ b/tests/test-stash.t
@@ -0,0 +1,154 @@
+ $ printf '[extensions]\ngraphlog=\n' >> $HGRCPATH
+ $ printf '[defaults]\nglog = --template "{rev}:{node|short} {bookmarks} {desc}\\n"\n' >> $HGRCPATH
+ $ hg init
+
+basic setup and testing:
+
+ $ touch a
+ $ hg ci -qAm.
+ $ hg stash
+ nothing changed
+ $ hg stash -l
+ $ hg stash -d
+ abort: there are no stashes
+ [255]
+ $ hg stash -a
+ abort: there are no stashes
+ [255]
+ $ hg stash -p
+ abort: there are no stashes
+ [255]
+ $ hg stash -d foo
+ abort: stash foo doesn't exist
+ [255]
+ $ hg stash -a foo
+ abort: stash foo doesn't exist
+ [255]
+ $ hg stash -p foo
+ abort: stash foo doesn't exist
+ [255]
+
+stash a modification:
+
+ $ echo a >> a
+ $ hg stash
+ stashed working dir (hg stash --pop to unstash)
+ $ hg stash -l
+ 0 unnamed at 0:7f6b67e0497a - Stashed working directory
+ $ hg stash -p
+ diff -r 7f6b67e0497a -r * a (glob)
+ --- a/a Thu Jan 01 00:00:00 1970 +0000
+ +++ b/a * (glob)
+ @@ -0,0 +1,1 @@
+ +a
+
+apply it:
+
+ $ hg stash -a
+ $ hg diff --nodates
+ diff -r 7f6b67e0497a a
+ --- a/a
+ +++ b/a
+ @@ -0,0 +1,1 @@
+ +a
+ $ hg up -C .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo b >> b
+ $ hg ci -qAm.
+
+pop it (different parents):
+
+ $ hg stash --pop
+ saved backup bundle to $TESTTMP/.hg/strip-backup/*-unstash-backup.hg (glob)
+ $ hg stash -l
+ $ hg diff --nodates
+ diff -r 901c2d3ce0fb a
+ --- a/a
+ +++ b/a
+ @@ -0,0 +1,1 @@
+ +a
+
+named stash:
+
+ $ echo c > c
+ $ echo a >> a
+ $ hg stash -A foo
+ adding c
+ stashed working dir (hg stash --pop to unstash)
+ $ hg stash -l
+ 0 foo at 1:901c2d3ce0fb - Stashed working directory
+ $ hg stash -p foo
+ diff -r 901c2d3ce0fb -r * a (glob)
+ --- a/a Thu Jan 01 00:00:00 1970 +0000
+ +++ b/a * (glob)
+ @@ -0,0 +1,2 @@
+ +a
+ +a
+ diff -r 901c2d3ce0fb -r * c (glob)
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/c * (glob)
+ @@ -0,0 +1,1 @@
+ +c
+ $ hg stash -a
+ $ hg stash -d foo
+ saved backup bundle to $TESTTMP/.hg/strip-backup/*-unstash-backup.hg (glob)
+
+create several unnamed stashes:
+
+ $ echo a >> a
+ $ hg stash
+ stashed working dir (hg stash --pop to unstash)
+ $ echo aa >> a
+ $ hg stash -m wip
+ created new head
+ stashed working dir (hg stash --pop to unstash)
+ $ echo aaa >> a
+ $ hg stash
+ created new head
+ stashed working dir (hg stash --pop to unstash)
+ $ hg stash -l
+ 0 unnamed at 1:901c2d3ce0fb - Stashed working directory
+ 1 unnamed at 1:901c2d3ce0fb - wip
+ 2 unnamed at 1:901c2d3ce0fb - Stashed working directory
+
+apply them with a conflict:
+
+ $ hg stash --pop 1
+ saved backup bundle to $TESTTMP/.hg/strip-backup/*-unstash-backup.hg (glob)
+ $ hg stash -l
+ 0 unnamed at 1:901c2d3ce0fb - Stashed working directory
+ 1 unnamed at 1:901c2d3ce0fb - Stashed working directory
+ $ hg stash -f -a
+ merging a
+ warning: conflicts during merge.
+ merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+ abort: unresolved conflicts
+ (use hg resolve)
+ [255]
+
+use mergetool when applying:
+
+ $ mv a.orig a
+ $ hg stash -f --pop --tool "internal:other"
+ saved backup bundle to $TESTTMP/.hg/strip-backup/*-unstash-backup.hg (glob)
+ $ hg diff --nodates
+ diff -r 901c2d3ce0fb a
+ --- a/a
+ +++ b/a
+ @@ -0,0 +1,1 @@
+ +aaa
+ $ hg stash -d
+ saved backup bundle to $TESTTMP/.hg/strip-backup/*-unstash-backup.hg (glob)
+
+test that renames are preserved:
+
+ $ hg up -qC .
+ $ hg mv b c
+ $ hg stash
+ stashed working dir (hg stash --pop to unstash)
+ $ hg stash --pop
+ saved backup bundle to $TESTTMP/.hg/strip-backup/*-unstash-backup.hg (glob)
+ $ hg st --copies
+ M c
+ b
+ R b
More information about the Mercurial-devel
mailing list