This is an archive of the discontinued Mercurial Phabricator instance.

undo: localbrach fancy indexes
ClosedPublic

Authored by felixmerk on Jul 26 2017, 2:42 PM.
Tags
None
Subscribers

Details

Reviewers
quark
stash
Group Reviewers
Restricted Project
Commits
rFBHGX3a71483bbf1d: undo: localbrach fancy indexes
Summary

Adds smart indexing to localbranch undoes. This eliminates the mental burden of
the user of keeping track in what order and when changes where made. Drawback
is that it raises the computational complexity from the number of draft
commits to draft commits times how many repo states you are undoing. Common use
case should be to undo a small number of changes, so this is acceptable.

hg undo -b changectx finds the closest pertinent change and undoes that. -b
with -n is not supported. The reason it is not supported is that local branch
level undoes are delta specific and not state specific, which in essence means
that running hg undo -b # twice is not guaranteed to give you the same resault
as running hg undo -b # -n 2. We could run -b multiple times for a -n, but the
cost of a -b increases with a larger -n (which isn't the case for a standard
undo), and abort handling would be more complicated (I'm not sure how well we
can roll back a tree of obs markers?). In essence -n with -b is not that
usefull a feature (bc perf limits) and would potentially require a decently large
amount of time to implement correctly.

Also adds redo -b since this command now makes a lot more sense when we use the
smarter indexing. Adds logic to seperate undoes and redoes in different
branches. You can perform relative undoes and redoes within the latest scope,
while undoes and redoes within a new scope will start at the present (absolute)
state.

Diff Detail

Repository
rFBHGX Facebook Mercurial Extensions
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.

Event Timeline

felixmerk created this revision.Jul 26 2017, 2:42 PM
Herald added a reviewer: Restricted Project. · View Herald TranscriptJul 26 2017, 2:42 PM
felixmerk retitled this revision from undo: localbrach fancing indexes to undo: localbrach fancy indexes and redos.Jul 26 2017, 2:42 PM
felixmerk retitled this revision from undo: localbrach fancy indexes and redos to undo: localbrach fancing indexes.Jul 26 2017, 6:39 PM
felixmerk added a child revision: D188: undo: clean up bin/hex.
felixmerk updated this revision to Diff 422.Jul 26 2017, 8:27 PM
stash added a subscriber: stash.Jul 27 2017, 7:31 AM

sorry, didn't have time to look more closely, so just nits

hgext3rd/undo.py
211

Does it make sense to make os.path.join("undolog", "redonode") a module-level constant?

Also it will break clients if they have undolog/redonode in old format i.e. just str(hexnode). That should be fine because undo is a relatively new feature

214

os.path.join('undolog', 'redonode')

640–642

Do we need this if at all?

641

What does iden mean here?

tests/test-undo.t
661

It would be nice to run hg log -r . -T {node} to print the node

felixmerk added inline comments.Jul 27 2017, 2:23 PM
hgext3rd/undo.py
211

Jun gave me the impression that repo.vfs.write handles OS specific file paths, so os.path.join is at best redundant or at worst possibly breaking things. But having file locations as module-level constants does make sense.

I believe this would only break clients if they run undo or redo right after updating (since otherwise the file gets overwritten). Considering the small number of clients using undo this shouldn't be a problem.

640–642

No, I can merge it into a != and an and with the elif.

641

identify (the branch)

felixmerk updated this revision to Diff 428.Jul 27 2017, 2:29 PM
felixmerk marked 5 inline comments as done.Jul 27 2017, 2:29 PM
quark requested changes to this revision.EditedJul 27 2017, 6:40 PM
quark added a subscriber: quark.

I don't think --index works well with branch undo using the current implementation. Consider the following case:

  $ cat >> $HGRCPATH <<EOF
  > [extensions]
  > undo = $TESTDIR/../hgext3rd/undo.py
  > drawdag=$RUNTESTDIR/drawdag.py
  > inhibit=$TESTDIR/../hgext3rd/inhibit.py
  > rebase=
  > [experimental]
  > evolution=createmarkers
  > EOF

  $ hg init repo
  $ cd repo
  $ hg debugdrawdag <<'EOS'
  > G F
  > | |
  > | E
  > |/
  > D C
  > | |
  > | B
  > |/
  > A
  > EOS

  $ hg phase --public G
  $ rm .hg/localtags
  $ hg rebase -s 'desc(F)' -d 'desc(G)'
  rebasing 6:b5e305ce895c "F" (tip)
  $ hg rebase -s 'desc(C)' -d tip
  rebasing 3:26805aba1e60 "C"

#testcases expected unexpected

#if expected

Running "undo" twice works:

  $ hg undo -b 7
  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo)'
  o  7 F draft
  |
  o  5 G public
  |
  | o  4 E draft
  |/
  o  2 D public
  |
  | o  3 C draft
  | |
  | o  1 B draft
  |/
  o  0 A public
  
  $ hg undo -b 7
  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo)'
  o  6 F draft
  |
  o  4 E draft
  |
  | o  5 G public
  |/
  o  2 D public
  |
  | o  3 C draft
  | |
  | o  1 B draft
  |/
  o  0 A public
  
#else

"--index 2" produces wrong result - "C" will be missed:

  $ hg undo -b 7 --index 2
  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo)'
  o  6 F draft
  |
  o  4 E draft
  |
  | o  5 G public
  |/
  o  2 D public
  |
  | o  1 B draft
  |/
  o  0 A public
  
#endif

I think implementing local branch --index correctly could be very tricky and could easily be wrong for corner cases. So I'd vote to make it simple - only support 1-step undo for the local branch case and disallow --index to be used together with --branch.

hgext3rd/undo.py
211

@stash I think / is preferred over os.path.join. Mercurial internal always uses /. os.path.join is only useful when interacting with filesystem directly (i.e. bypassing the vfs layer).

214

See the above explanation why / is better.

624

Might also handle IndexError here so people with the old (or corrupted) redonode state file would not crash the code here.

626

Since our transaction is not safe, might handle error.RevlogError too.

661

nit: reverseindex

690

nit: need space

This revision now requires changes to proceed.Jul 27 2017, 6:40 PM
felixmerk edited edge metadata.Jul 31 2017, 6:04 PM
felixmerk updated this revision to Diff 464.
felixmerk updated this revision to Diff 467.Jul 31 2017, 6:15 PM
stash added a comment.Aug 1 2017, 10:39 AM

The logic also handles --index fine.

Please update the summary, this is no longer true

stash requested changes to this revision.Aug 1 2017, 11:03 AM

The logic to handle -b correctly is quite complicated. Personally I'm not convinced that increased code complexity is worth new feature, but I maybe wrong here.

hgext3rd/undo.py
497–498

Since this option is experimental than this is probably fine, but as a user I'd have had a hard time understanding what single branch undo means. Better description would be great.

512–515

It's not clear for me why we don't allow --branch and --index in undo, but allow in redo. Can you explain and preferably add a comment? Also you don't test hg redo -b ... --index ....

644–645

Nit: this formatting is wrong:

rootdelta = revsetlang.formatspec(
      'roots(_localbranch(%s)) - roots(_localbranch(%s))', branch, oldbranch)
654–657

If redo accepts either --index or --branch then this logic can be simplified

This revision now requires changes to proceed.Aug 1 2017, 11:03 AM

In defense of --branch:
While I concur that the command (at least currently) is obtuse to users (I'll try to improve the documentation) and the code that goes along with it is somewhat complex, I think its a very powerful feature. As a feature is allows you to undo changes within a much smaller scope than the entire repo and lays the ground work for undoes with arbitrary sets of commits. Probably only power users will use it as a command line tool and as such the impact is limited (although it does simplify things greatly in certain cases). However, as a nuclide tool (you select a local branch and then hit undo) I think the feature would be very easy to use and very powerful. That being said, I don't believe I'll have enough time to implement that level of nuclide support (will see), but that is where I see this feature having impact and where the future of undo might lie.

felixmerk edited edge metadata.Aug 1 2017, 2:20 PM
felixmerk edited the summary of this revision. (Show Details)
felixmerk retitled this revision from undo: localbrach fancing indexes to undo: localbrach fancy indexes.
felixmerk updated this revision to Diff 477.
stash requested changes to this revision.Aug 2 2017, 5:19 AM
stash added inline comments.
hgext3rd/undo.py
517–523

BTW, undo and redo code implementations are quite similar and potentially it can be unified. But probably not worth doing right now.

521–523

Is there a reason why branch is not recorded here? As far as I understand if our repo in STATE1 and we do:

hg undo -b HASH 
hg undo -b HASH 
hg redo -b HASH 
hg redo -b HASH

it may result in STATE2 != STATE1. I think that's not what we want, isn't it?
I'm right then please add a test for this case.

680–681

Nit:

alphaworkingcopyparent = _readnode(repo, "workingparent.i",
                                   nodedict["workingparent"])
689

Nit:

incrementalindex += direction
699–701

This comment is quite unclear. Can you elaborate?

702–705

Am I right about the priorities here:

((olddraft(%d) + olddraft(%d)) - (olddraft(%d) and olddraft(%d))) and _localbranch(%s)

?
If yes then please add the parens

BTW, the fact that - and and have the same priorities seems a bit weird for me

710

Nit: missing space

if done: # perf boost
712–713

It can be moved outside of while loop

726

Why break here? What if a few bookmarks were moved at once, for example, during histedit?

729

Typo:

intereSting
This revision now requires changes to proceed.Aug 2 2017, 5:19 AM
felixmerk added inline comments.Aug 2 2017, 2:32 PM
hgext3rd/undo.py
726

Find next delta finds the next command that changed something within a limited scope ("localbranch"). At this point we don't care wether one bookmark or a million changed, we are looking for at least one thing (working copy parent, bookmark, draft commits) to have changed. Find next delta returns the index at which the next delta occurred.

stash added inline comments.Aug 2 2017, 4:09 PM
hgext3rd/undo.py
726

That makes sense, thank you for explanation. Probably worth adding a comment though

felixmerk edited edge metadata.Aug 2 2017, 4:31 PM
felixmerk updated this revision to Diff 509.
stash accepted this revision.Aug 3 2017, 5:18 AM

I don't want to block you, especially since this feature is experimental. But consider the cases below.

hgext3rd/undo.py
621

Nit: try: #

661

Not sure if we need a default value for direction

707–710

Let's say we have a following situation:

We had a stack

   o - B
    |
   @  - A (draft)
  /
o
...

Then we stripped commit B

   @  - A (draft)
  /
o
...

Then we run hg undo -b A.

Here _localbranch(A) returns A, olddraft(-1) returns (A, B), olddraft(0) returns A. Then we have ((A) + (A, B)) - ((A) & (A, B)) & (A) = ((A, B) - (A) ) & (A) = (). So undo won't undo the strip. Is it expected?

732–733

I've just tested it and looks like there is a difference between D160 and this diff.

Let's say we have a stack of commits

   o
    |
   @  - A (draft)
  /
o
|
o - B (public)

Then we checkout B, and then we run hg undo -b A. In D160 it would just update to A, but with current changes it will move backwards.

felixmerk added inline comments.Aug 3 2017, 1:35 PM
hgext3rd/undo.py
621

Wow, I'll make sure I stop doing this. I guess I'm too used not leaving trailing white spaces, but leaving a space before the hashtag is definitely cleaner.

707–710

This isn't expected but this isn't what happens. localbranch() includes hidden commits. I'll add a test.

732–733

Yes, this was intended. Moving the working copy parent from or to a branch doesn't really 'feel' like a change to this branch. Moving a working copy parent within a branch on the other hand does. This is an implementation detail (documented on line 729). I'm fine going either way with this.

quark accepted this revision.Aug 3 2017, 1:45 PM
quark added inline comments.
hgext3rd/undo.py
521–523

Ideally we want redo to precisely revert what undo does. An alternative is to make redo accept zero flags and just read the operation log, undoing the latest undo command (so redo will not have branch concept and performs a whole-repo undo so the state must match).

Per discussion yesterday, the current design allows things like:

hg undo -n 5
hg redo

to be equivalent to

hg undo -n 4

The alternative approach will make redo undoes 5 steps, which is different but may also be explainable since redo does not have -n.

Another UX difference is if a user runs hg undo -b NAME and that NAME gets disappeared as a side effect of undo, currently, the user might lose a way to redo. So a redo without any flags seems easier to use here.

@stash Do you have preference or some tricky cases in mind that worth considering?

I'm fine with the code as-is. We can revisit it before enabling it to all users.

This revision is now accepted and ready to land.Aug 3 2017, 1:45 PM
felixmerk updated this revision to Diff 527.Aug 3 2017, 1:58 PM
This revision was automatically updated to reflect the committed changes.
stash added a comment.Aug 7 2017, 11:19 AM

I actually like the idea about redo not taking any parameters, I think it will be less surprising for users.