diff --git a/hgext3rd/undo.py b/hgext3rd/undo.py --- a/hgext3rd/undo.py +++ b/hgext3rd/undo.py @@ -180,10 +180,10 @@ revstring = "\n".join(sorted('%s %s' % (k, v) for k, v in nodes.items())) return writelog(repo, tr, "index.i", revstring) -def _logundoredoindex(repo, tr, reverseindex): +def _logundoredoindex(repo, tr, reverseindex, branch=""): rlog = _getrevlog(repo, 'index.i') hexnode = hex(rlog.node(_invertindex(rlog, reverseindex))) - return repo.vfs.write("undolog/redonode", str(hexnode)) + return repo.vfs.write("undolog/redonode", str(hexnode) + "\0" + branch) def _delundoredo(repo): path = 'undolog' + '/' + 'redonode' @@ -469,8 +469,9 @@ cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) repo = repo.unfiltered() - if relativeundo: - reverseindex = _computerelative(repo, reverseindex) + reverseindex = _computerelative(repo, reverseindex, + absolute=not relativeundo, + branch=branch) if not (opts.get("force") or _gapcheck(repo, reverseindex)): raise error.Abort(_("attempted risky undo across" " missing history")) @@ -479,9 +480,11 @@ # store undo data # for absolute undos, think of this as a reset # for relative undos, think of this as an update - _logundoredoindex(repo, repo.currenttransaction(), reverseindex) + _logundoredoindex(repo, repo.currenttransaction(), reverseindex, branch) @command('redo', [ + ('b', 'branch', "", _("single branch undo, accepts commit hash " + "(EXPERIMENTAL)")), ('n', 'index', 1, _("how many commands to redo")), ]) def redo(ui, repo, *args, **opts): @@ -494,14 +497,15 @@ and you can use any number of undos/redos up to the current state or back to when the undo extension was first active. """ + branch = opts.get("branch") reverseindex = -1 * abs(opts.get("index")) - reverseindex = _computerelative(repo, reverseindex) with repo.wlock(), repo.lock(), repo.transaction("redo"): cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) repo = repo.unfiltered() - _undoto(ui, repo, reverseindex) + reverseindex = _computerelative(repo, reverseindex, branch=branch) + _undoto(ui, repo, reverseindex, branch=branch) _logundoredoindex(repo, repo.currenttransaction(), reverseindex) def _undoto(ui, repo, reverseindex, keep=False, branch=None): @@ -594,18 +598,122 @@ smarthide(repo, addedrevs, localremoves, local=True) revealcommits(repo, localremoves) -def _computerelative(repo, reverseindex): +def _computerelative(repo, reverseindex, absolute=False, branch=""): # allows for relative undos using # redonode storage + # allows for branch undos using + # findnextdelta logic + if not absolute: + try:# attempt to get relative shift + nodebranch = repo.vfs.read("undolog/redonode").split("\0") + hexnode = nodebranch[0] + oldbranch = nodebranch[1] + rlog = _getrevlog(repo, 'index.i') + rev = rlog.rev(bin(hexnode)) + shiftedindex = _invertindex(rlog, rev) + except IOError: + # no shift + shiftedindex = 0 + oldbranch = "" + else: + shiftedindex = 0 + oldbranch = "" + + if not branch: + reverseindex = shiftedindex + reverseindex + else: + # check if relative branch + if branch == oldbranch: + # common case we are using the same hash to iden + pass + elif oldbranch != "": + rootdelta = revsetlang.formatspec('roots(_localbranch(%s)) -' + ' roots(_localbranch(%s))', branch, oldbranch) + if repo.revs(rootdelta): + # different group of commits + shiftedindex = 0 + + # from shifted index, find reverse index # of states that change + # branch + # remember that reverseindex can be negative + sign = reverseindex / abs(reverseindex) + for count in range(abs(reverseindex)): + shiftedindex = _findnextdelta(repo, shiftedindex, branch, + direction=sign) + reverseindex = shiftedindex + return reverseindex + +def _findnextdelta(repo, reverseindex, branch, direction=1): + # finds closest repos state making changes to branch in direction + # input: + # repo: mercurial.localrepo + # index: positive int for index.i + # branch: string changectx (commit hash) + # direction: positive or negative int + # output: + # int index with next branch delta + assert 0 != direction # no infinite cycles guarantee + repo = repo.unfiltered() + # current state try: - hexnode = repo.vfs.read("undolog/redonode") - rlog = _getrevlog(repo, 'index.i') - rev = rlog.rev(bin(hexnode)) - reverseindex = _invertindex(rlog, rev) + reverseindex - except IOError: - # return input index - pass - return reverseindex + nodedict = _readindex(repo, reverseindex) + except IndexError: + raise error.Abort(_("index out of bounds")) + alphaworkingcopyparent = _readnode(repo, "workingparent.i", + nodedict["workingparent"]) + alphabookstring = _readnode(repo, "bookmarks.i", + nodedict["bookmarks"]) + incrementalindex = reverseindex + + done = False + while not done: + # move index + incrementalindex = incrementalindex + direction + #check this index + try: + nodedict = _readindex(repo, incrementalindex) + except IndexError: + raise error.Abort(_("index out of bounds")) + # check wkp, commits, bookmarks + workingcopyparent = _readnode(repo, "workingparent.i", + nodedict["workingparent"]) + bookstring = _readnode(repo, "bookmarks.i", nodedict["bookmarks"]) + # draft changes # + # disjunctive union of present and state = changes + # changes and local = localchanges + localctxchanges = revsetlang.formatspec( + '(olddraft(%d) + olddraft(%d)) -' + '(olddraft(%d) and olddraft(%d))' + ' and _localbranch(%s)', + incrementalindex, reverseindex, + incrementalindex, reverseindex, + branch) + done = done or repo.revs(localctxchanges) + if done:# perf boost + break + localbranch = repo.revs( + revsetlang.formatspec("_localbranch(%s)", branch)) + tonode = repo.changelog.node + hexnodes = [hex(tonode(x)) for x in localbranch] + # bookmark changes + if alphabookstring != bookstring: + diff = set(alphabookstring.split("\n")) ^\ + set(bookstring.split("\n")) + for mark in diff: + if mark: + kv = mark.rsplit(" ", 1) + # was or will the mark be in the localbranch + if kv[1] in hexnodes: + done = True + break + + # working copy parent changes + # for workingcopyparent, only changes within the scope are intereting + if alphaworkingcopyparent != workingcopyparent: + done = done or (workingcopyparent in hexnodes and + alphaworkingcopyparent in hexnodes) + + return incrementalindex # hide and reveal commits def smarthide(repo, revhide, revshow, local=False): diff --git a/tests/test-undo.t b/tests/test-undo.t --- a/tests/test-undo.t +++ b/tests/test-undo.t @@ -574,7 +574,8 @@ $ hg up 75f63379f12b 7 files updated, 0 files merged, 1 files removed, 0 files unresolved (leaving bookmark newbook) - $ hg undo -b 75f63379f12b -n 6 +3 changes within local scope: commit, book, update + $ hg undo -b 75f63379f12b -n 3 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -l 2 changeset: 20:805791ba4bcd @@ -633,3 +634,44 @@ > [extensions] > rebase = ! > EOF + +Check local redo works +Simple test should be sufficient, as per usual, redo is an undo with some index +logic + $ hg redo -b 3532 + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -l 2 + changeset: 21:35324a911c0d + bookmark: newbook + tag: tip + parent: 18:75f63379f12b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: newbranch + + changeset: 18:75f63379f12b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: newfiles + + $ touch a9 && hg add a9 && hg ci -m a9 + created new head + $ hg undo -b 3532 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg redo -b 75f6 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -l 2 + changeset: 22:3ee6a6880888 + tag: tip + parent: 18:75f63379f12b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a9 + + changeset: 21:35324a911c0d + bookmark: newbook + parent: 18:75f63379f12b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: newbranch +