StGit commands resulting in a conflicting patch pushing record two
transactions in the log (with one of them being inconsistent with HEAD
!= top). Undoing such operations requires two "stg undo" (possibly with
--hard) commands which is unintuitive. This patch changes such
operations to only record one log entry and "stg undo" reverts the stack
to the state prior to the operation.
Signed-off-by: Catalin Marinas <redacted>
Cc: Gustav Hållberg <redacted>
Cc: Karl Wiberg <redacted>
---
stgit/lib/transaction.py | 5 +++--
t/t3103-undo-hard.sh | 4 ++--
2 files changed, 5 insertions(+), 4 deletions(-)
From: Karl Wiberg <hidden> Date: 2016-06-15 22:47:55
On Fri, Dec 18, 2009 at 12:22 AM, Catalin Marinas
[off-list ref] wrote:
StGit commands resulting in a conflicting patch pushing record two
transactions in the log (with one of them being inconsistent with
HEAD != top). Undoing such operations requires two "stg undo"
(possibly with --hard) commands which is unintuitive. This patch
changes such operations to only record one log entry and "stg undo"
reverts the stack to the state prior to the operation.
Hmm, OK. It was convenient to be able to undo just the last
conflicting step, but I guess the increase in UI complexity wasn't
worth it.
I think your patch doesn't go quite far enough, though.
self.__conflicting_push is currently set to a function that will do
the extra updates that take us from the first to the second state to
save in the log; if we'll be saving at only one point, we might as
well run those updates immediately instead of deferring them. In other
words, the entire __conflicting_push variable could be removed.
--
Karl Wiberg, kha@treskal.com
subrabbit.wordpress.com
www.treskal.com/kalle
On Fri, Dec 18, 2009 at 12:22 AM, Catalin Marinas
[off-list ref] wrote:
quoted
StGit commands resulting in a conflicting patch pushing record two
transactions in the log (with one of them being inconsistent with
HEAD != top). Undoing such operations requires two "stg undo"
(possibly with --hard) commands which is unintuitive. This patch
changes such operations to only record one log entry and "stg undo"
reverts the stack to the state prior to the operation.
Hmm, OK. It was convenient to be able to undo just the last
conflicting step, but I guess the increase in UI complexity wasn't
worth it.
I think your patch doesn't go quite far enough, though.
self.__conflicting_push is currently set to a function that will do
the extra updates that take us from the first to the second state to
save in the log; if we'll be saving at only one point, we might as
well run those updates immediately instead of deferring them. In other
words, the entire __conflicting_push variable could be removed.
See below for an updated patch:
Record a single transaction for conflicting push operations
From: Catalin Marinas <redacted>
StGit commands resulting in a conflicting patch pushing record two
transactions in the log (with one of them being inconsistent with HEAD
!= top). Undoing such operations requires two "stg undo" (possibly with
--hard) commands which is unintuitive. This patch changes such
operations to only record one log entry and "stg undo" reverts the stack
to the state prior to the operation.
Signed-off-by: Catalin Marinas <redacted>
Cc: Gustav Hållberg <redacted>
Cc: Karl Wiberg <redacted>
---
stgit/lib/transaction.py | 14 +++++---------
t/t3101-reset-hard.sh | 2 +-
t/t3103-undo-hard.sh | 4 ++--
3 files changed, 8 insertions(+), 12 deletions(-)
@@ -90,7 +90,6 @@ class StackTransaction(object):self.__applied=list(self.__stack.patchorder.applied)self.__unapplied=list(self.__stack.patchorder.unapplied)self.__hidden=list(self.__stack.patchorder.hidden)-self.__conflicting_push=Noneself.__error=Noneself.__current_tree=self.__stack.head.data.treeself.__base=self.__stack.base
@@ -232,10 +231,9 @@ class StackTransaction(object):self.__stack.patchorder.hidden=self.__hiddenlog.log_entry(self.__stack,msg)old_applied=self.__stack.patchorder.applied-write(self.__msg)-ifself.__conflicting_push!=None:-self.__patches=_TransPatchMap(self.__stack)-self.__conflicting_push()+ifnotself.__conflicts:+write(self.__msg)+else:write(self.__msg+' (CONFLICT)')ifprint_current_patch:_print_current_patch(old_applied,self.__applied)
@@ -371,12 +369,10 @@ class StackTransaction(object):# We've just caused conflicts, so we must allow them in# the final checkout.self.__allow_conflicts=lambdatrans:True--# Save this update so that we can run it a little later.-self.__conflicting_push=update+self.__patches=_TransPatchMap(self.__stack)+update()self.__halt("%d merge conflict(s)"%len(self.__conflicts))else:-# Update immediately.update()defpush_tree(self,pn):
From: Karl Wiberg <hidden> Date: 2016-06-15 22:47:55
On Fri, Dec 18, 2009 at 4:49 PM, Catalin Marinas
[off-list ref] wrote:
quoted hunk
@@ -371,12 +369,10 @@ class StackTransaction(object): # We've just caused conflicts, so we must allow them in # the final checkout. self.__allow_conflicts = lambda trans: True-- # Save this update so that we can run it a little later.- self.__conflicting_push = update+ self.__patches = _TransPatchMap(self.__stack)+ update() self.__halt("%d merge conflict(s)" % len(self.__conflicts)) else:- # Update immediately. update() def push_tree(self, pn):
Better. But couldn't you remove the update function completely and
just inline the code in it, since it's called immediately?
--
Karl Wiberg, kha@treskal.com
subrabbit.wordpress.com
www.treskal.com/kalle
On Fri, Dec 18, 2009 at 4:49 PM, Catalin Marinas
[off-list ref] wrote:
quoted
@@ -371,12 +369,10 @@ class StackTransaction(object):
# We've just caused conflicts, so we must allow them in
# the final checkout.
self.__allow_conflicts = lambda trans: True
-
- # Save this update so that we can run it a little later.
- self.__conflicting_push = update
+ self.__patches = _TransPatchMap(self.__stack)
+ update()
self.__halt("%d merge conflict(s)" % len(self.__conflicts))
else:
- # Update immediately.
update()
def push_tree(self, pn):
Better. But couldn't you remove the update function completely and
just inline the code in it, since it's called immediately?
Of course, I tried, but couldn't get it to work. I get HEAD and top
not equal unless I call update() between _TransPatchMap and
self.__halt(). For the non-conflicting case we need to call update
before or after this "if merge_conflict".
One solution is to split the "if merge_conflict" in two but maybe you
have a better idea.
Thanks,
--
Catalin
From: Karl Wiberg <hidden> Date: 2016-06-15 22:47:55
On Mon, Dec 21, 2009 at 12:21 AM, Catalin Marinas
[off-list ref] wrote:
2009/12/19 Karl Wiberg [off-list ref]:
quoted
Better. But couldn't you remove the update function completely and
just inline the code in it, since it's called immediately?
Of course, I tried, but couldn't get it to work. I get HEAD and top
not equal unless I call update() between _TransPatchMap and
self.__halt(). For the non-conflicting case we need to call update
before or after this "if merge_conflict".
One solution is to split the "if merge_conflict" in two but maybe
you have a better idea.
Yes, duplicating the conditional was what I had in mind. But if you
don't find it to improve the readability of the code (as compared to
having a function), I certainly won't insist.
Thanks for working on this.
By the way, you do realize there's another command that requires two
steps to undo completely: refresh? And that one is harder to get out
of---undoing it all in one step would mean throwing away the updates
to the patch.
--
Karl Wiberg, kha@treskal.com
subrabbit.wordpress.com
www.treskal.com/kalle
By the way, you do realize there's another command that requires two
steps to undo completely: refresh? And that one is harder to get out
of---undoing it all in one step would mean throwing away the updates
to the patch.
But it looks to me like refresh does this by running separate
transactions. The push command does this in a single transaction, so
the quickest fix for the HEAD != top undo problem was to only record
one log per transaction.
If we keep the current behaviour with two logs per transaction, we
need to preserve the HEAD prior to the conflict so that logging
doesn't get the wrong HEAD (which is the new conflicting HEAD
currently). The patch below appears to fix this problem and still
generate two logs per transaction. While I'm more in favour of a
single log per transaction, if people find it useful I'm happy to keep
the current behaviour.
@@ -197,18 +197,14 @@ class StackTransaction(object):exception)anddonothing."""self.__check_consistency()log.log_external_mods(self.__stack)-new_head=self.head--# Set branch head.-ifset_head:-ifiw:-try:-self.__checkout(new_head.data.tree,iw,allow_bad_head)-exceptgit.CheckoutException:-# We have to abort the transaction.-self.abort(iw)-self.__abort()-self.__stack.set_head(new_head,self.__msg)++ifset_headandiw:+try:+self.__checkout(self.head.data.tree,iw,allow_bad_head)+exceptgit.CheckoutException:+# We have to abort the transaction.+self.abort(iw)+self.__abort()ifself.__error:ifself.__conflicts:
@@ -216,8 +212,11 @@ class StackTransaction(object):else:out.error(self.__error)-# Write patches.-defwrite(msg):+# Write patches and update the branch head.+defwrite(msg,new_head):+# Set branch head.+ifnew_head:+self.__stack.set_head(new_head,self.__msg)forpn,commitinself.__patches.iteritems():ifself.__stack.patches.exists(pn):p=self.__stack.patches.get(pn)
@@ -231,12 +230,16 @@ class StackTransaction(object):self.__stack.patchorder.unapplied=self.__unappliedself.__stack.patchorder.hidden=self.__hiddenlog.log_entry(self.__stack,msg)+old_applied=self.__stack.patchorder.applied-write(self.__msg)ifself.__conflicting_push!=None:+write(self.__msg,set_headandself.head)self.__patches=_TransPatchMap(self.__stack)self.__conflicting_push()-write(self.__msg+' (CONFLICT)')+write(self.__msg+' (CONFLICT)',set_headandself.head)+else:+write(self.__msg,set_headandself.head)+ifprint_current_patch:_print_current_patch(old_applied,self.__applied)
@@ -346,10 +349,10 @@ class StackTransaction(object):ifmerge_conflict:# When we produce a conflict, we'll run the update()# function defined below _after_ having done the-# checkout in run(). To make sure that we check out-# the real stack top (as it will look after update()-# has been run), set it hard here.-self.head=comm+# checkout in run(). Make sure that we have a consistent+# HEAD before the update function is called below (which+# sets the real HEAD).+self.head=self.topelse:comm=Nones='unmodified'
@@ -367,6 +370,8 @@ class StackTransaction(object):x=self.unapplieddelx[x.index(pn)]self.applied.append(pn)+# Set the real conflicting HEAD.+self.head=commifmerge_conflict:# We've just caused conflicts, so we must allow them in# the final checkout.
@@ -46,7 +46,7 @@ test_expect_success 'Try to undo without --hard' ' cat>expected.txt<<EOF EOF-test_expect_failure'Try to undo with --hard''+test_expect_success'Try to undo with --hard''stgundo--hard&&stgstatusa>actual.txt&&test_cmpexpected.txtactual.txt&&
From: Karl Wiberg <hidden> Date: 2016-06-15 22:47:56
On Mon, Dec 21, 2009 at 12:48 PM, Catalin Marinas
[off-list ref] wrote:
2009/12/21 Karl Wiberg [off-list ref]:
quoted
By the way, you do realize there's another command that requires
two steps to undo completely: refresh? And that one is harder to
get out of---undoing it all in one step would mean throwing away
the updates to the patch.
But it looks to me like refresh does this by running separate
transactions.
Yes. So it won't be affected by whatever you do here. (Unless you
consider that refresh -p needs to reorder patches, which can result in
conflicts---right now, refresh -p can result in three log entries.)
The push command does this in a single transaction, so the quickest
fix for the HEAD != top undo problem was to only record one log per
transaction.
I've seen more than one complaint that the current behavior is
confusing even if we don't count the bug, so I thought this was part
of the motivation.
If we keep the current behaviour with two logs per transaction, we
need to preserve the HEAD prior to the conflict so that logging
doesn't get the wrong HEAD (which is the new conflicting HEAD
currently). The patch below appears to fix this problem and still
generate two logs per transaction. While I'm more in favour of a
single log per transaction, if people find it useful I'm happy to
keep the current behaviour.
I haven't seen anyone but me defent the current design, and it's not a
big deal for me either, so I'd say go with just one transaction.
--
Karl Wiberg, kha@treskal.com
subrabbit.wordpress.com
www.treskal.com/kalle
From: Gustav Hållberg <hidden> Date: 2016-06-15 22:47:56
On 2009-12-21 14:48, Karl Wiberg wrote:
I've seen more than one complaint that the current behavior is
confusing even if we don't count the bug, so I thought this was part
of the motivation.
I don't know if this would be better than the other suggested solutions,
but if "stg log" would clearly identify multi-stage entries as such, the
current confusion would probably mostly go away.
Currently this is done reasonably well for make_temp_patch(), which says
"refresh (create temporary patch)" in the log, but I think this could be
taken further.
For example, if such annotations said "foo: stage N" or similar,
indicating that this was the Nth step in the "foo" command (think
"rebase" or whatever), it would be good enough for me least.
- Gustav
Updated patch below:
Record a single transaction for conflicting push operations
From: Catalin Marinas <redacted>
StGit commands resulting in a conflicting patch pushing record two
transactions in the log (with one of them being inconsistent with HEAD
!= top). Undoing such operations requires two "stg undo" (possibly with
--hard) commands which is unintuitive. This patch changes such
operations to only record one log entry and "stg undo" reverts the stack
to the state prior to the operation.
Signed-off-by: Catalin Marinas <redacted>
Cc: Gustav Hållberg <redacted>
Cc: Karl Wiberg <redacted>
---
stgit/lib/transaction.py | 35 ++++++++++++++++-------------------
t/t3101-reset-hard.sh | 2 +-
t/t3103-undo-hard.sh | 4 ++--
3 files changed, 19 insertions(+), 22 deletions(-)
@@ -90,7 +90,6 @@ class StackTransaction(object):self.__applied=list(self.__stack.patchorder.applied)self.__unapplied=list(self.__stack.patchorder.unapplied)self.__hidden=list(self.__stack.patchorder.hidden)-self.__conflicting_push=Noneself.__error=Noneself.__current_tree=self.__stack.head.data.treeself.__base=self.__stack.base
@@ -232,10 +231,9 @@ class StackTransaction(object):self.__stack.patchorder.hidden=self.__hiddenlog.log_entry(self.__stack,msg)old_applied=self.__stack.patchorder.applied-write(self.__msg)-ifself.__conflicting_push!=None:-self.__patches=_TransPatchMap(self.__stack)-self.__conflicting_push()+ifnotself.__conflicts:+write(self.__msg)+else:write(self.__msg+' (CONFLICT)')ifprint_current_patch:_print_current_patch(old_applied,self.__applied)
@@ -358,26 +356,25 @@ class StackTransaction(object):elifnotmerge_conflictandcd.is_nochange():s='empty'out.done(s)-defupdate():-ifcomm:-self.patches[pn]=comm-ifpninself.hidden:-x=self.hidden-else:-x=self.unapplied-delx[x.index(pn)]-self.applied.append(pn)+ifmerge_conflict:# We've just caused conflicts, so we must allow them in# the final checkout.self.__allow_conflicts=lambdatrans:True+self.__patches=_TransPatchMap(self.__stack)-# Save this update so that we can run it a little later.-self.__conflicting_push=update-self.__halt("%d merge conflict(s)"%len(self.__conflicts))+# Update the stack state+ifcomm:+self.patches[pn]=comm+ifpninself.hidden:+x=self.hiddenelse:-# Update immediately.-update()+x=self.unapplied+delx[x.index(pn)]+self.applied.append(pn)++ifmerge_conflict:+self.__halt("%d merge conflict(s)"%len(self.__conflicts))defpush_tree(self,pn):"""Push the named patch without updating its tree."""