git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [StGit PATCH 0/2] stg undo
@ 2008-05-09  0:44 Karl Hasselström
  2008-05-09  0:44 ` [StGit PATCH 1/2] Move stack reset function to a shared location Karl Hasselström
  2008-05-09  0:44 ` [StGit PATCH 2/2] New command: stg undo Karl Hasselström
  0 siblings, 2 replies; 3+ messages in thread
From: Karl Hasselström @ 2008-05-09  0:44 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Here's a fully general undo command for StGit. It uses the same
machinery as the "stg reset" I posted previously, but is more
convenient to use when all you want to do is undo the last operation.

The next step would be to remove all traces of "push --undo" et al.,
but I'll save that for another day.

For trying this out, pull from

  git://repo.or.cz/stgit/kha.git experimental

since a few of the patches below these two have been updated as well.
(For example, the log branch in <branchname>.stgit^ is now much more
human-readable.)

---

Karl Hasselström (2):
      New command: stg undo
      Move stack reset function to a shared location


 stgit/commands/reset.py  |   70 ++++---------------------------------
 stgit/commands/undo.py   |   49 ++++++++++++++++++++++++++
 stgit/lib/log.py         |   76 +++++++++++++++++++++++++++++++++++++++++
 stgit/lib/transaction.py |    1 +
 stgit/main.py            |    2 +
 t/t3102-undo.sh          |   86 ++++++++++++++++++++++++++++++++++++++++++++++
 t/t3103-undo-hard.sh     |   56 ++++++++++++++++++++++++++++++
 7 files changed, 277 insertions(+), 63 deletions(-)
 create mode 100644 stgit/commands/undo.py
 create mode 100755 t/t3102-undo.sh
 create mode 100755 t/t3103-undo-hard.sh

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [StGit PATCH 1/2] Move stack reset function to a shared location
  2008-05-09  0:44 [StGit PATCH 0/2] stg undo Karl Hasselström
@ 2008-05-09  0:44 ` Karl Hasselström
  2008-05-09  0:44 ` [StGit PATCH 2/2] New command: stg undo Karl Hasselström
  1 sibling, 0 replies; 3+ messages in thread
From: Karl Hasselström @ 2008-05-09  0:44 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Move reset_stack() from commands/reset.py to lib/log.py, so that more
commands besides reset can use it. (No such commands exist currently,
but undo and redo will use it.)

Signed-off-by: Karl Hasselström <kha@treskal.com>

---

 stgit/commands/reset.py  |   70 +++++-----------------------------------------
 stgit/lib/log.py         |   57 +++++++++++++++++++++++++++++++++++++
 stgit/lib/transaction.py |    1 +
 3 files changed, 65 insertions(+), 63 deletions(-)


diff --git a/stgit/commands/reset.py b/stgit/commands/reset.py
index bcc4c6a..2bbac8b 100644
--- a/stgit/commands/reset.py
+++ b/stgit/commands/reset.py
@@ -45,67 +45,6 @@ directory = common.DirectoryHasRepositoryLib()
 options = [make_option('--hard', action = 'store_true',
                        help = 'discard changes in your index/worktree')]
 
-def reset_stack(stack, iw, state, only_patches, hard):
-    only_patches = set(only_patches)
-    def mask(s):
-        if only_patches:
-            return s & only_patches
-        else:
-            return s
-    patches_to_reset = mask(set(state.applied + state.unapplied))
-    existing_patches = set(stack.patchorder.all)
-    to_delete = mask(existing_patches - patches_to_reset)
-    trans = transaction.StackTransaction(stack, 'reset', discard_changes = hard)
-
-    # If we have to change the stack base, we need to pop all patches
-    # first.
-    if not only_patches and trans.base != state.base:
-        trans.pop_patches(lambda pn: True)
-        out.info('Setting stack base to %s' % state.base.sha1)
-        trans.base = state.base
-
-    # In one go, do all the popping we have to in order to pop the
-    # patches we're going to delete or modify.
-    def mod(pn):
-        if only_patches and not pn in only_patches:
-            return False
-        if pn in to_delete:
-            return True
-        if stack.patches.get(pn).commit != state.patches.get(pn, None):
-            return True
-        return False
-    trans.pop_patches(mod)
-
-    # Delete and modify/create patches. We've previously popped all
-    # patches that we touch in this step.
-    trans.delete_patches(lambda pn: pn in to_delete)
-    for pn in patches_to_reset:
-        if pn in existing_patches:
-            if trans.patches[pn] == state.patches[pn]:
-                continue
-            else:
-                out.info('Resetting %s' % pn)
-        else:
-            trans.unapplied.append(pn)
-            out.info('Resurrecting %s' % pn)
-        trans.patches[pn] = state.patches[pn]
-
-    # Push/pop patches as necessary.
-    try:
-        if only_patches:
-            # Push all the patches that we've popped, if they still
-            # exist.
-            pushable = set(trans.unapplied)
-            for pn in stack.patchorder.applied:
-                if pn in pushable:
-                    trans.push_patch(pn, iw)
-        else:
-            # Recreate the exact order specified by the goal state.
-            trans.reorder_patches(state.applied, state.unapplied, iw)
-    except transaction.TransactionHalted:
-        pass
-    return trans.run(iw)
-
 def func(parser, options, args):
     stack = directory.repository.current_stack
     if len(args) >= 1:
@@ -113,5 +52,10 @@ def func(parser, options, args):
         state = log.Log(stack.repository, ref, stack.repository.rev_parse(ref))
     else:
         raise common.CmdException('Wrong number of arguments')
-    return reset_stack(stack, stack.repository.default_iw, state, patches,
-                       options.hard)
+    trans = transaction.StackTransaction(stack, 'reset',
+                                         discard_changes = options.hard)
+    try:
+        log.reset_stack(trans, stack.repository.default_iw, state, patches)
+    except transaction.TransactionHalted:
+        pass
+    return trans.run(stack.repository.default_iw)
diff --git a/stgit/lib/log.py b/stgit/lib/log.py
index e9e89ff..dfc2b9f 100644
--- a/stgit/lib/log.py
+++ b/stgit/lib/log.py
@@ -180,3 +180,60 @@ def copy_log(repo, src_branch, dst_branch, msg):
 
 def default_repo():
     return stack.Repository.default()
+
+def reset_stack(trans, iw, state, only_patches):
+    only_patches = set(only_patches)
+    def mask(s):
+        if only_patches:
+            return s & only_patches
+        else:
+            return s
+    patches_to_reset = mask(set(state.applied + state.unapplied))
+    existing_patches = set(trans.all_patches)
+    original_applied_order = list(trans.applied)
+    to_delete = mask(existing_patches - patches_to_reset)
+
+    # If we have to change the stack base, we need to pop all patches
+    # first.
+    if not only_patches and trans.base != state.base:
+        trans.pop_patches(lambda pn: True)
+        out.info('Setting stack base to %s' % state.base.sha1)
+        trans.base = state.base
+
+    # In one go, do all the popping we have to in order to pop the
+    # patches we're going to delete or modify.
+    def mod(pn):
+        if only_patches and not pn in only_patches:
+            return False
+        if pn in to_delete:
+            return True
+        if trans.patches[pn] != state.patches.get(pn, None):
+            return True
+        return False
+    trans.pop_patches(mod)
+
+    # Delete and modify/create patches. We've previously popped all
+    # patches that we touch in this step.
+    trans.delete_patches(lambda pn: pn in to_delete)
+    for pn in patches_to_reset:
+        if pn in existing_patches:
+            if trans.patches[pn] == state.patches[pn]:
+                continue
+            else:
+                out.info('Resetting %s' % pn)
+        else:
+            trans.unapplied.append(pn)
+            out.info('Resurrecting %s' % pn)
+        trans.patches[pn] = state.patches[pn]
+
+    # Push/pop patches as necessary.
+    if only_patches:
+        # Push all the patches that we've popped, if they still
+        # exist.
+        pushable = set(trans.unapplied)
+        for pn in original_applied_order:
+            if pn in pushable:
+                trans.push_patch(pn, iw)
+    else:
+        # Recreate the exact order specified by the goal state.
+        trans.reorder_patches(state.applied, state.unapplied, iw)
diff --git a/stgit/lib/transaction.py b/stgit/lib/transaction.py
index a008780..dd64eb6 100644
--- a/stgit/lib/transaction.py
+++ b/stgit/lib/transaction.py
@@ -60,6 +60,7 @@ class StackTransaction(object):
     def __set_unapplied(self, val):
         self.__unapplied = list(val)
     unapplied = property(lambda self: self.__unapplied, __set_unapplied)
+    all_patches = property(lambda self: self.__applied + self.__unapplied)
     def __set_base(self, val):
         assert (not self.__applied
                 or self.patches[self.applied[0]].data.parent == val)

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [StGit PATCH 2/2] New command: stg undo
  2008-05-09  0:44 [StGit PATCH 0/2] stg undo Karl Hasselström
  2008-05-09  0:44 ` [StGit PATCH 1/2] Move stack reset function to a shared location Karl Hasselström
@ 2008-05-09  0:44 ` Karl Hasselström
  1 sibling, 0 replies; 3+ messages in thread
From: Karl Hasselström @ 2008-05-09  0:44 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Basically, this is just a user-friendly way to access a subset of the
functionality of "stg reset".

Signed-off-by: Karl Hasselström <kha@treskal.com>

---

 stgit/commands/undo.py  |   38 +++++++--------------
 stgit/lib/log.py        |   19 ++++++++++
 stgit/main.py           |    2 +
 t/t3102-undo.sh         |   86 +++++++++++++++++++++++++++++++++++++++++++++++
 t/t3103-undo-hard.sh    |   10 +++--
 5 files changed, 125 insertions(+), 30 deletions(-)
 copy stgit/commands/{reset.py => undo.py} (58%)
 create mode 100755 t/t3102-undo.sh
 copy t/{t3101-reset-hard.sh => t3103-undo-hard.sh} (82%)


diff --git a/stgit/commands/reset.py b/stgit/commands/undo.py
similarity index 58%
copy from stgit/commands/reset.py
copy to stgit/commands/undo.py
index 2bbac8b..b1d7de9 100644
--- a/stgit/commands/reset.py
+++ b/stgit/commands/undo.py
@@ -22,40 +22,28 @@ from stgit.commands import common
 from stgit.lib import git, log, transaction
 from stgit.out import out
 
-help = 'reset the patch stack to an earlier state'
-usage = """%prog [options] <state> [<patchnames>]
+help = 'undo the last operation'
+usage = """%prog [options]
 
-Reset the patch stack to an earlier state. The state is specified with
-a commit from a stack log; for a branch foo, StGit stores the stack
-log in foo.stgit^. So to undo the last N StGit commands, you would say
-
-  stg reset foo.stgit^~N
-
-or, if you are not sure how many steps to undo, you can view the log
-with "git log" or gitk
-
-  gitk foo.stgit^
-
-and then reset to any sha1 you see in the log.
-
-If one or more patch names are given, reset only those patches, and
-leave the rest alone."""
+Reset the patch stack to the previous state. Consecutive invocations
+of "stg undo" will take you ever further into the past."""
 
 directory = common.DirectoryHasRepositoryLib()
-options = [make_option('--hard', action = 'store_true',
+options = [make_option('-n', '--number', type = 'int', metavar = 'N',
+                       default = 1,
+                       help = 'undo the last N commands'),
+           make_option('--hard', action = 'store_true',
                        help = 'discard changes in your index/worktree')]
 
 def func(parser, options, args):
     stack = directory.repository.current_stack
-    if len(args) >= 1:
-        ref, patches = args[0], args[1:]
-        state = log.Log(stack.repository, ref, stack.repository.rev_parse(ref))
-    else:
-        raise common.CmdException('Wrong number of arguments')
-    trans = transaction.StackTransaction(stack, 'reset',
+    if options.number < 1:
+        raise common.CmdException('Bad number of commands to undo')
+    state = log.undo_state(stack, options.number)
+    trans = transaction.StackTransaction(stack, 'undo %d' % options.number,
                                          discard_changes = options.hard)
     try:
-        log.reset_stack(trans, stack.repository.default_iw, state, patches)
+        log.reset_stack(trans, stack.repository.default_iw, state, [])
     except transaction.TransactionHalted:
         pass
     return trans.run(stack.repository.default_iw)
diff --git a/stgit/lib/log.py b/stgit/lib/log.py
index dfc2b9f..6d9fc6e 100644
--- a/stgit/lib/log.py
+++ b/stgit/lib/log.py
@@ -1,3 +1,4 @@
+import re
 from stgit.lib import git, stack
 from stgit import exception
 from stgit.out import out
@@ -155,6 +156,9 @@ class Log(object):
             self.base = self.patches[self.applied[0]].data.parent
         else:
             self.base = self.head
+    @property
+    def parents(self):
+        return self.commit.data.parents[2:]
 
 class FullLog(Log):
     full_log = property(lambda self: self.commit)
@@ -237,3 +241,18 @@ def reset_stack(trans, iw, state, only_patches):
     else:
         # Recreate the exact order specified by the goal state.
         trans.reorder_patches(state.applied, state.unapplied, iw)
+
+def undo_state(stack, undo_steps):
+    ref = log_ref(stack.name)
+    log = Log(stack.repository, ref, stack.repository.refs.get(ref))
+    while undo_steps > 0:
+        msg = log.commit.data.message.strip()
+        m = re.match(r'^undo\s+(\d+)$', msg)
+        if m:
+            undo_steps += int(m.group(1))
+        else:
+            undo_steps -= 1
+        if not log.parents:
+            raise LogException('Not enough undo information available')
+        log = Log(stack.repository, log.parents[0].sha1, log.parents[0])
+    return log
diff --git a/stgit/main.py b/stgit/main.py
index 83e6b08..cf7b404 100644
--- a/stgit/main.py
+++ b/stgit/main.py
@@ -99,6 +99,7 @@ commands = Commands({
     'top':              'top',
     'unapplied':        'unapplied',
     'uncommit':         'uncommit',
+    'undo':             'undo',
     'unhide':           'unhide'
     })
 
@@ -129,6 +130,7 @@ stackcommands = (
     'top',
     'unapplied',
     'uncommit',
+    'undo',
     'unhide',
     )
 patchcommands = (
diff --git a/t/t3102-undo.sh b/t/t3102-undo.sh
new file mode 100755
index 0000000..1093f70
--- /dev/null
+++ b/t/t3102-undo.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='Simple test cases for "stg undo"'
+
+. ./test-lib.sh
+
+# Ignore our own output files.
+cat > .git/info/exclude <<EOF
+/expected.txt
+EOF
+
+test_expect_success 'Initialize StGit stack with three patches' '
+    stg init &&
+    echo 000 >> a &&
+    git add a &&
+    git commit -m a &&
+    echo 111 >> a &&
+    git commit -a -m p1 &&
+    echo 222 >> a &&
+    git commit -a -m p2 &&
+    echo 333 >> a &&
+    git commit -a -m p3 &&
+    stg uncommit -n 3 &&
+    stg pop
+'
+
+cat > expected.txt <<EOF
+000
+111
+EOF
+test_expect_success 'Pop one patch ...' '
+    stg pop &&
+    test "$(echo $(stg applied))" = "p1" &&
+    test "$(echo $(stg unapplied))" = "p2 p3" &&
+    test_cmp expected.txt a
+'
+
+cat > expected.txt <<EOF
+000
+111
+222
+EOF
+test_expect_success '... and undo it' '
+    stg undo &&
+    test "$(echo $(stg applied))" = "p1 p2" &&
+    test "$(echo $(stg unapplied))" = "p3" &&
+    test_cmp expected.txt a
+'
+
+cat > expected.txt <<EOF
+000
+EOF
+test_expect_success 'Pop two patches ...' '
+    stg pop &&
+    stg pop &&
+    test "$(echo $(stg applied))" = "" &&
+    test "$(echo $(stg unapplied))" = "p1 p2 p3" &&
+    test_cmp expected.txt a
+'
+
+cat > expected.txt <<EOF
+000
+111
+222
+EOF
+test_expect_success '... and undo it' '
+    stg undo &&
+    stg undo &&
+    test "$(echo $(stg applied))" = "p1 p2" &&
+    test "$(echo $(stg unapplied))" = "p3" &&
+    test_cmp expected.txt a
+'
+
+cat > expected.txt <<EOF
+000
+111
+222
+EOF
+test_expect_success 'Undo past end of history' '
+    ! stg undo -n 100 &&
+    test "$(echo $(stg applied))" = "p1 p2" &&
+    test "$(echo $(stg unapplied))" = "p3" &&
+    test_cmp expected.txt a
+'
+
+test_done
diff --git a/t/t3101-reset-hard.sh b/t/t3103-undo-hard.sh
similarity index 82%
copy from t/t3101-reset-hard.sh
copy to t/t3103-undo-hard.sh
index 1e02805..21412f7 100755
--- a/t/t3101-reset-hard.sh
+++ b/t/t3103-undo-hard.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='Simple test cases for "stg reset"'
+test_description='Simple test cases for "stg undo"'
 
 . ./test-lib.sh
 
@@ -35,8 +35,8 @@ test_expect_success 'Pop middle patch, creating a conflict' '
     test "$(echo $(stg unapplied))" = "p2"
 '
 
-test_expect_success 'Try to reset without --hard' '
-    ! stg reset master.stgit^~1 &&
+test_expect_success 'Try to undo without --hard' '
+    ! stg undo &&
     stg status a > actual.txt &&
     test_cmp expected.txt actual.txt &&
     test "$(echo $(stg applied))" = "p1 p3" &&
@@ -45,8 +45,8 @@ test_expect_success 'Try to reset without --hard' '
 
 cat > expected.txt <<EOF
 EOF
-test_expect_success 'Try to reset with --hard' '
-    stg reset --hard master.stgit^~1 &&
+test_expect_success 'Try to undo with --hard' '
+    stg undo --hard &&
     stg status a > actual.txt &&
     test_cmp expected.txt actual.txt &&
     test "$(echo $(stg applied))" = "p1" &&

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2008-05-09  0:45 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-05-09  0:44 [StGit PATCH 0/2] stg undo Karl Hasselström
2008-05-09  0:44 ` [StGit PATCH 1/2] Move stack reset function to a shared location Karl Hasselström
2008-05-09  0:44 ` [StGit PATCH 2/2] New command: stg undo Karl Hasselström

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).