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."""
 
+import re
 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
+    @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(
@@ -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)
+
+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