chiark / gitweb /
New command: stg undo
authorKarl Hasselström <kha@treskal.com>
Sun, 21 Sep 2008 12:17:41 +0000 (14:17 +0200)
committerKarl Hasselström <kha@treskal.com>
Sun, 21 Sep 2008 12:19:07 +0000 (14:19 +0200)
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 [new file with mode: 0644]
stgit/lib/log.py
t/t3102-undo.sh [new file with mode: 0755]
t/t3103-undo-hard.sh [new file with mode: 0755]

diff --git a/stgit/commands/undo.py b/stgit/commands/undo.py
new file mode 100644 (file)
index 0000000..4f782a0
--- /dev/null
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2008, Karl Hasselström <kha@treskal.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+from stgit.argparse import opt
+from stgit.commands import common
+from stgit.lib import git, log, transaction
+from stgit.out import out
+
+help = 'Undo the last operation'
+kind = 'stack'
+usage = ['']
+description = """
+Reset the patch stack to the previous state. Consecutive invocations
+of "stg undo" will take you ever further into the past."""
+
+options = [
+    opt('-n', '--number', type = 'int', metavar = 'N', default = 1,
+        short = 'Undo the last N commands'),
+    opt('--hard', action = 'store_true',
+        short = 'Discard changes in your index/worktree')]
+
+directory = common.DirectoryHasRepositoryLib()
+
+def func(parser, options, args):
+    stack = directory.repository.current_stack
+    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, [])
+    except transaction.TransactionHalted:
+        pass
+    return trans.run(stack.repository.default_iw)
index 43f87ce3d68ce6e637b19e1d2ce9b361b86e450c..8b7d2e420207e6a16a24d972462c771d2f6d15d8 100644 (file)
@@ -99,6 +99,7 @@ The simplified log is exactly like the full log, except that its only
 parent is the (simplified) previous log entry, if any. It's purpose is
 mainly ease of visualization."""
 
 parent is the (simplified) previous log entry, if any. It's purpose is
 mainly ease of visualization."""
 
+import re
 from stgit.lib import git, stack as libstack
 from stgit import exception, utils
 from stgit.out import out
 from stgit.lib import git, stack as libstack
 from stgit import exception, utils
 from stgit.out import out
@@ -157,6 +158,12 @@ class LogEntry(object):
             return self.patches[self.applied[0]].data.parent
         else:
             return self.head
             return self.patches[self.applied[0]].data.parent
         else:
             return self.head
+    @property
+    def top(self):
+        if self.applied:
+            return self.patches[self.applied[-1]]
+        else:
+            return self.head
     @classmethod
     def from_stack(cls, prev, stack, message):
         return cls(
     @classmethod
     def from_stack(cls, prev, stack, message):
         return cls(
@@ -447,3 +454,30 @@ 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, state.hidden, iw)
     else:
         # Recreate the exact order specified by the goal state.
         trans.reorder_patches(state.applied, state.unapplied, state.hidden, iw)
+
+def undo_state(stack, undo_steps):
+    """Find the log entry C{undo_steps} steps in the past. (Successive
+    undo operations are supposed to "add up", so if we find other undo
+    operations along the way we have to add those undo steps to
+    C{undo_steps}.)
+
+    @return: The log entry that is the destination of the undo
+             operation
+    @rtype: L{LogEntry}"""
+    ref = log_ref(stack.name)
+    try:
+        commit = stack.repository.refs.get(ref)
+    except KeyError:
+        raise LogException('Log is empty')
+    log = get_log_entry(stack.repository, ref, commit)
+    while undo_steps > 0:
+        msg = log.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.prev:
+            raise LogException('Not enough undo information available')
+        log = log.prev
+    return log
diff --git a/t/t3102-undo.sh b/t/t3102-undo.sh
new file mode 100755 (executable)
index 0000000..9373522
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 series))" = "> p1 - 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 series))" = "+ p1 > p2 - 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 series))" = "- 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 series))" = "+ p1 > p2 - p3" &&
+    test_cmp expected.txt a
+'
+
+cat > expected.txt <<EOF
+000
+111
+222
+EOF
+test_expect_success 'Undo past end of history' '
+    command_error stg undo -n 100 &&
+    test "$(echo $(stg series))" = "+ p1 > p2 - p3" &&
+    test_cmp expected.txt a
+'
+
+test_done
diff --git a/t/t3103-undo-hard.sh b/t/t3103-undo-hard.sh
new file mode 100755 (executable)
index 0000000..599aa43
--- /dev/null
@@ -0,0 +1,53 @@
+#!/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
+/actual.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
+'
+
+cat > expected.txt <<EOF
+C a
+EOF
+test_expect_success 'Pop middle patch, creating a conflict' '
+    conflict_old stg pop p2 &&
+    stg status a > actual.txt &&
+    test_cmp expected.txt actual.txt &&
+    test "$(echo $(stg series))" = "+ p1 > p3 - p2"
+'
+
+test_expect_success 'Try to undo without --hard' '
+    command_error stg undo &&
+    stg status a > actual.txt &&
+    test_cmp expected.txt actual.txt &&
+    test "$(echo $(stg series))" = "+ p1 > p3 - p2"
+'
+
+cat > expected.txt <<EOF
+EOF
+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 series))" = "> p1 - p3 - p2"
+'
+
+test_done