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@xxxxxxxxxxx> --- stgit/commands/undo.py | 40 +++++++--------------- stgit/lib/log.py | 38 +++++++++++++++++++++ stgit/main.py | 2 + t/t3102-undo.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++ t/t3103-undo-hard.sh | 10 +++-- 5 files changed, 144 insertions(+), 32 deletions(-) copy stgit/commands/{reset.py => undo.py} (56%) 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 56% copy from stgit/commands/reset.py copy to stgit/commands/undo.py index 5ad9914..b1d7de9 100644 --- a/stgit/commands/reset.py +++ b/stgit/commands/undo.py @@ -22,42 +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 (or rather, -the last N log entries; there is not an exact one-to-one -relationship), 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 2449913..3b242cd 100644 --- a/stgit/lib/log.py +++ b/stgit/lib/log.py @@ -95,6 +95,7 @@ except for the following: The simplified log contains no information not in the full log; its purpose is ease of visualization.""" +import re from stgit.lib import git, stack from stgit import exception from stgit.out import out @@ -258,6 +259,16 @@ class Log(object): self.base = self.patches[self.applied[0]].data.parent else: self.base = self.head + @property + def top(self): + if self.applied: + return self.patches[self.applied[-1]] + else: + return self.head + @property + def parents(self): + num_refs = len(set([self.top, self.head])) + return self.commit.data.parents[(1 + num_refs):] class FullLog(Log): full_log = property(lambda self: self.commit) @@ -347,3 +358,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, 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{Log}""" + ref = log_ref(stack.name) + try: + commit = stack.repository.refs.get(ref) + except KeyError: + raise LogException('Log is empty') + log = Log(stack.repository, ref, commit) + 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" && -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html