Mercurial > evolve
changeset 2801:49494d0155b7 mercurial-3.9
test-compat: merge with mercurial-4.0
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Tue, 25 Jul 2017 04:01:10 +0200 |
parents | 13c4d518327a (diff) 167567795f6c (current diff) |
children | 41c9a4df628e 35d053d7bd87 |
files | tests/test-check-setup-manifest.t tests/test-discovery-obshashrange.t tests/test-evolve-cycles.t tests/test-evolve-effectflags.t tests/test-evolve-obshistory-complex.t tests/test-evolve-obshistory.t tests/test-evolve-serveronly.t tests/test-evolve-templates.t tests/test-evolve-topic.t tests/test-evolve.t tests/test-obsolete.t tests/test-prev-next.t tests/test-split.t tests/test-stabilize-result.t tests/test-stack-branch.t tests/test-topic-dest.t tests/test-topic-fold.t tests/test-topic-push-concurrent-on.t tests/test-topic-push.t tests/test-topic-rebase.t tests/test-topic-shelve.t tests/test-topic-stack-data.t tests/test-topic-stack.t tests/test-topic-tutorial.t tests/test-topic.t |
diffstat | 34 files changed, 2049 insertions(+), 1727 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Sun Jul 02 17:28:38 2017 +0200 +++ b/.hgtags Tue Jul 25 04:01:10 2017 +0200 @@ -54,3 +54,4 @@ 6da4ca7b3e4fc214a363a5cf723554f114b7f51e 6.3.0 e358c0263e4629746a7c925429c951f67d4b02b1 6.3.1 e60248f26f923f4460682252f7863ff86f7b86b0 6.4.0 +734c0bc066cdc0121a20a9cb44c8cc30c653be94 6.5.0
--- a/README Sun Jul 02 17:28:38 2017 +0200 +++ b/README Tue Jul 25 04:01:10 2017 +0200 @@ -121,26 +121,52 @@ Changelog ========= -6.5.0 - in progress +6.6.0 - in progress ------------------- + - amend: add a --extract flag to move change back to the working copy + (same as uncommit, but accessible through the amend commit) + - split: now properly refuse to split public changeset + - commands: unify and improve the pre-rewrite validation and error message + - uncommit: add support for --current-date and --current-user option + - fold: add support for --current-date and --current-user option + - metaedit: add support for --current-date and --current-user option + - split add support for --current-date and --current-user option + + - topic: add --age option to sort topic by the most recently touched, + - topic: add a 't0' to access the root of a topic while keeping it active, + - topic: allow 'hg prev' to me move to 't0', + - topic: add a config option to enforce topic on new commit + (experimental.enforce-topic) + +6.5.0 -- 2017-07-02 +------------------- + +features: + - obslog: gain a --patch flag to display changes introduced by the evolution (Currently limited to in simple case only) + - log: display obsolescence fate by default, (future 4.3 only) + - doc: various minor improvement. - - stack: also show the unstable status for the current changeset (issue5553) +bugfixes: + + - evolve: fix branch preservation for merge, + - obsfate: improve support for advanced template reformating, + - split: preserve author of the splitted changeset. + - grab: properly fix hg executable on windows. + +topic (0.1.0): + + - stack: also show the unstable status for the current changeset, (issue5553) - stack: properly abort when and unknown topic is requested, - - stack: add basic and raw support for named branches - - topic: changing topic on revs no longer adds extra instability (issue5441) + - stack: add basic and raw support for named branches, + - topic: changing topic on revs no longer adds extra instability, (issue5441) - topic: topics: rename '--change' flag to '--rev' flag, - topic: multiple large performance improvements, - topic: various small output improvement, - -6.4.1 - in progress -------------------- + - topic: improved topic preservation for various commands. - - evolve: fix branch preservation for merge - - obsfate: improve support for advanced template reformating - - split: preserve author of the splitted changeset, 6.4.0 -- 2017-06-16 -------------------
--- a/debian/changelog Sun Jul 02 17:28:38 2017 +0200 +++ b/debian/changelog Tue Jul 25 04:01:10 2017 +0200 @@ -1,3 +1,9 @@ +mercurial-evolve (6.5.0-1) UNRELEASED; urgency=medium + + * new upstream release + + -- Pierre-Yves David <marmoute@nodosa.octopoid.net> Sun, 02 Jul 2017 19:35:17 +0200 + mercurial-evolve (6.4.0-1) unstable; urgency=medium * new upstream release
--- a/hgext3rd/evolve/__init__.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/evolve/__init__.py Tue Jul 25 04:01:10 2017 +0200 @@ -77,6 +77,11 @@ # (recommended 'yes' for server (default)) obshashrange.warm-cache = no +The initial cache warming is currently a bit slow. To make sure it is build you +can run the following commands in your repository:: + + $ hg debugobshashrange --rev 'head() + It is recommended to enable the blackbox extension. It gathers useful data about the experiment. It is shipped with Mercurial so no extra install is needed:: @@ -96,6 +101,13 @@ # ensuring no large repository will get affected. obshashrange.max-revs = 100000 # default is None +For very large repositories. it is currently recommended to disable obsmarkers +discovery (Make sure you follow release announcement to know when you can turn +it back on). + + [experimental] + evolution.obsdiscovery = no + Effect Flag Experiment ====================== @@ -202,12 +214,45 @@ Obsolescence markers will be exchanged between repositories that explicitly assert support for the obsolescence feature (this can currently only be done -via an extension).""".strip() +via an extension). + +Instability +========== + +(note: the vocabulary is in the process of being updated) + +Rewriting changesets might introduce instability (currently 'trouble'). + +There are two main kinds of instability: orphaning and diverging. + +Orphans are changesets left behind when their ancestors are rewritten, (currently: 'unstable'). +Divergence has two variants: + +* Content-divergence occurs when independent rewrites of the same changesets + lead to different results. (currently: 'divergent') + +* Phase-divergence occurs when the old (obsolete) version of a changeset + becomes public. (currently: 'bumped') + +If it possible to prevent local creation of orphans by using the following config:: + + [experimental] + evolution=createmarkers,allnewcommands,exchange + +You can also enable that option explicitly:: + + [experimental] + evolution=createmarkers,allnewcommands,allowunstable,exchange + +or simply:: + + [experimental] + evolution=all +""".strip() import os import sys -import random import re import collections import errno @@ -231,7 +276,6 @@ import mercurial from mercurial import util -from mercurial import repair from mercurial import obsolete if not obsolete._enabled: @@ -258,7 +302,7 @@ scmutil, ) -from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts +from mercurial.commands import mergetoolopts from mercurial.i18n import _ from mercurial.node import nullid @@ -266,11 +310,13 @@ checkheads, compat, debugcmd, + cmdrewrite, exthelper, metadata, obscache, obsexchange, obshistory, + rewriteutil, safeguard, templatekw, utility, @@ -287,6 +333,7 @@ commandopt = 'allnewcommands' obsexcmsg = utility.obsexcmsg +shorttemplate = utility.shorttemplate colortable = {'evolve.node': 'yellow', 'evolve.user': 'green', @@ -301,7 +348,11 @@ _unpack = struct.unpack aliases, entry = cmdutil.findcmd('commit', commands.table) -interactiveopt = [['i', 'interactive', None, _('use interactive mode')]] +commitopts3 = cmdrewrite.commitopts3 +interactiveopt = cmdrewrite.interactiveopt +_bookmarksupdater = rewriteutil.bookmarksupdater +rewrite = rewriteutil.rewrite + # This extension contains the following code # # - Extension Helper code @@ -318,6 +369,7 @@ eh.merge(obshistory.eh) eh.merge(templatekw.eh) eh.merge(compat.eh) +eh.merge(cmdrewrite.eh) uisetup = eh.final_uisetup extsetup = eh.final_extsetup reposetup = eh.final_reposetup @@ -388,25 +440,6 @@ ### experimental behavior ### ##################################################################### -commitopts3 = [ - ('D', 'current-date', None, - _('record the current date as commit date')), - ('U', 'current-user', None, - _('record the current user as committer')), -] - -def _resolveoptions(ui, opts): - """modify commit options dict to handle related options - - For now, all it does is figure out the commit date: respect -D unless - -d was supplied. - """ - # N.B. this is extremely similar to setupheaderopts() in mq.py - if not opts.get('date') and opts.get('current_date'): - opts['date'] = '%d %d' % util.makedate() - if not opts.get('user') and opts.get('current_user'): - opts['user'] = ui.username() - getrevs = obsolete.getrevs ##################################################################### @@ -808,92 +841,6 @@ ### changeset rewriting logic ############################# -def rewrite(repo, old, updates, head, newbases, commitopts): - """Return (nodeid, created) where nodeid is the identifier of the - changeset generated by the rewrite process, and created is True if - nodeid was actually created. If created is False, nodeid - references a changeset existing before the rewrite call. - """ - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('rewrite') - if len(old.parents()) > 1: # XXX remove this unnecessary limitation. - raise error.Abort(_('cannot amend merge changesets')) - base = old.p1() - updatebookmarks = _bookmarksupdater(repo, old.node(), tr) - - # commit a new version of the old changeset, including the update - # collect all files which might be affected - files = set(old.files()) - for u in updates: - files.update(u.files()) - - # Recompute copies (avoid recording a -> b -> a) - copied = copies.pathcopies(base, head) - - # prune files which were reverted by the updates - def samefile(f): - if f in head.manifest(): - a = head.filectx(f) - if f in base.manifest(): - b = base.filectx(f) - return (a.data() == b.data() - and a.flags() == b.flags()) - else: - return False - else: - return f not in base.manifest() - files = [f for f in files if not samefile(f)] - # commit version of these files as defined by head - headmf = head.manifest() - - def filectxfn(repo, ctx, path): - if path in headmf: - fctx = head[path] - flags = fctx.flags() - mctx = context.memfilectx(repo, fctx.path(), fctx.data(), - islink='l' in flags, - isexec='x' in flags, - copied=copied.get(path)) - return mctx - return None - - message = cmdutil.logmessage(repo.ui, commitopts) - if not message: - message = old.description() - - user = commitopts.get('user') or old.user() - # TODO: In case not date is given, we should take the old commit date - # if we are working one one changeset or mimic the fold behavior about - # date - date = commitopts.get('date') or None - extra = dict(commitopts.get('extra', old.extra())) - extra['branch'] = head.branch() - - new = context.memctx(repo, - parents=newbases, - text=message, - files=files, - filectxfn=filectxfn, - user=user, - date=date, - extra=extra) - - if commitopts.get('edit'): - new._text = cmdutil.commitforceeditor(repo, new, []) - revcount = len(repo) - newid = repo.commitctx(new) - new = repo[newid] - created = len(repo) != revcount - updatebookmarks(newid) - - tr.close() - return newid, created - finally: - lockmod.release(tr, lock, wlock) - class MergeFailure(error.Abort): pass @@ -962,29 +909,8 @@ _finalizerelocate(repo, orig, dest, nodenew, tr) return nodenew -def _bookmarksupdater(repo, oldid, tr): - """Return a callable update(newid) updating the current bookmark - and bookmarks bound to oldid to newid. - """ - def updatebookmarks(newid): - dirty = False - oldbookmarks = repo.nodebookmarks(oldid) - if oldbookmarks: - for b in oldbookmarks: - repo._bookmarks[b] = newid - dirty = True - if dirty: - repo._bookmarks.recordchange(tr) - return updatebookmarks - ### new command ############################# -metadataopts = [ - ('d', 'date', '', - _('record the specified date in metadata'), _('DATE')), - ('u', 'user', '', - _('record the specified user in metadata'), _('USER')), -] @eh.uisetup def _installimportobsolete(ui): @@ -1966,7 +1892,7 @@ with repo.dirstate.parentchange(): repo.dirstate.setparents(divergent.node(), node.nullid) oldlen = len(repo) - amend(ui, repo, message='', logfile='') + cmdrewrite.amend(ui, repo, message='', logfile='') if oldlen == len(repo): new = divergent # no changes @@ -1994,7 +1920,68 @@ raise error.Abort("base of divergent changeset %s not found" % ctx, hint='this case is not yet handled') -shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n" +def _gettopic(ctx): + """handle topic fetching with or without the extension""" + return getattr(ctx, 'topic', lambda: '')() + +def _gettopicidx(ctx): + """handle topic fetching with or without the extension""" + return getattr(ctx, 'topicidx', lambda: None)() + +def _getcurrenttopic(repo): + return getattr(repo, 'currenttopic', '') + +def _prevupdate(repo, displayer, target, bookmark, dryrun): + if dryrun: + repo.ui.write(('hg update %s;\n' % target.rev())) + if bookmark is not None: + repo.ui.write(('hg bookmark %s -r %s;\n' + % (bookmark, target.rev()))) + else: + ret = hg.update(repo, target.rev()) + if not ret: + tr = lock = None + try: + lock = repo.lock() + tr = repo.transaction('previous') + if bookmark is not None: + bmchanges = [(bookmark, target.node())] + compat.bookmarkapplychanges(repo, tr, bmchanges) + else: + bookmarksmod.deactivate(repo) + tr.close() + finally: + lockmod.release(tr, lock) + + displayer.show(target) + +def _findprevtarget(repo, displayer, movebookmark=False, topic=True): + target = bookmark = None + wkctx = repo[None] + p1 = wkctx.parents()[0] + parents = p1.parents() + currenttopic = _getcurrenttopic(repo) + + # we do not filter in the 1 case to allow prev to t0 + if currenttopic and topic and _gettopicidx(p1) != 1: + parents = [ctx for ctx in parents if ctx.topic() == currenttopic] + + # issue message for the various case + if p1.node() == node.nullid: + repo.ui.warn(_('already at repository root\n')) + elif not parents and currenttopic: + repo.ui.warn(_('no parent in topic "%s"\n') % currenttopic) + repo.ui.warn(_('(do you want --no-topic)\n')) + elif len(parents) == 1: + target = parents[0] + bookmark = None + if movebookmark: + bookmark = repo._activebookmark + else: + for p in parents: + displayer.show(p) + repo.ui.warn(_('multiple parents, explicitly update to one\n')) + return target, bookmark @eh.command( '^previous', @@ -2025,44 +2012,22 @@ exc.hint = _('do you want --merge?') raise - parents = wparents[0].parents() - topic = getattr(repo, 'currenttopic', '') - if topic and not opts.get("no_topic", False): - parents = [ctx for ctx in parents if ctx.topic() == topic] displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - if not parents: - ui.warn(_('no parent in topic "%s"\n') % topic) - ui.warn(_('(do you want --no-topic)\n')) - elif len(parents) == 1: - p = parents[0] - bm = repo._activebookmark - shouldmove = opts.get('move_bookmark') and bm is not None - if dryrunopt: - ui.write(('hg update %s;\n' % p.rev())) - if shouldmove: - ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev()))) - else: - ret = hg.update(repo, p.rev()) - if not ret: - tr = lock = None - try: - lock = repo.lock() - tr = repo.transaction('previous') - if shouldmove: - repo._bookmarks[bm] = p.node() - repo._bookmarks.recordchange(tr) - else: - bookmarksmod.deactivate(repo) - tr.close() - finally: - lockmod.release(tr, lock) + topic = not opts.get("no_topic", False) - displayer.show(p) + target, bookmark = _findprevtarget(repo, displayer, + opts.get('move_bookmark'), topic) + if target is not None: + backup = repo.ui.backupconfig('_internal', 'keep-topic') + try: + if topic and _getcurrenttopic(repo) != _gettopic(target): + repo.ui.setconfig('_internal', 'keep-topic', 'yes', + source='topic-extension') + _prevupdate(repo, displayer, target, bookmark, dryrunopt) + finally: + repo.ui.restoreconfig(backup) return 0 else: - for p in parents: - displayer.show(p) - ui.warn(_('multiple parents, explicitly update to one\n')) return 1 finally: lockmod.release(wlock) @@ -2124,8 +2089,8 @@ lock = repo.lock() tr = repo.transaction('next') if shouldmove: - repo._bookmarks[bm] = c.node() - repo._bookmarks.recordchange(tr) + bmchanges = [(bm, c.node())] + compat.bookmarkapplychanges(repo, tr, bmchanges) else: bookmarksmod.deactivate(repo) tr.close() @@ -2176,458 +2141,6 @@ finally: lockmod.release(wlock) -def _reachablefrombookmark(repo, revs, bookmarks): - """filter revisions and bookmarks reachable from the given bookmark - yoinked from mq.py - """ - repomarks = repo._bookmarks - if not bookmarks.issubset(repomarks): - raise error.Abort(_("bookmark '%s' not found") % - ','.join(sorted(bookmarks - set(repomarks.keys())))) - - # If the requested bookmark is not the only one pointing to a - # a revision we have to only delete the bookmark and not strip - # anything. revsets cannot detect that case. - nodetobookmarks = {} - for mark, bnode in repomarks.iteritems(): - nodetobookmarks.setdefault(bnode, []).append(mark) - for marks in nodetobookmarks.values(): - if bookmarks.issuperset(marks): - rsrevs = repair.stripbmrevset(repo, marks[0]) - revs = set(revs) - revs.update(set(rsrevs)) - revs = sorted(revs) - return repomarks, revs - -def _deletebookmark(repo, repomarks, bookmarks): - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('prune') - for bookmark in bookmarks: - del repomarks[bookmark] - repomarks.recordchange(tr) - tr.close() - for bookmark in sorted(bookmarks): - repo.ui.write(_("bookmark '%s' deleted\n") % bookmark) - finally: - lockmod.release(tr, lock, wlock) - -def _getmetadata(**opts): - metadata = {} - date = opts.get('date') - user = opts.get('user') - if date: - metadata['date'] = '%i %i' % util.parsedate(date) - if user: - metadata['user'] = user - return metadata - -@eh.command( - '^prune|obsolete', - [('n', 'new', [], _("successor changeset (DEPRECATED)")), - ('s', 'succ', [], _("successor changeset")), - ('r', 'rev', [], _("revisions to prune")), - ('k', 'keep', None, _("does not modify working copy during prune")), - ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")), - ('', 'fold', False, - _("record a fold (multiple precursors, one successors)")), - ('', 'split', False, - _("record a split (on precursor, multiple successors)")), - ('B', 'bookmark', [], _("remove revs only reachable from given" - " bookmark"))] + metadataopts, - _('[OPTION] [-r] REV...')) -# XXX -U --noupdate option to prevent wc update and or bookmarks update ? -def cmdprune(ui, repo, *revs, **opts): - """hide changesets by marking them obsolete - - Pruned changesets are obsolete with no successors. If they also have no - descendants, they are hidden (invisible to all commands). - - Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve` - to handle this situation. - - When you prune the parent of your working copy, Mercurial updates the working - copy to a non-obsolete parent. - - You can use ``--succ`` to tell Mercurial that a newer version (successor) of the - pruned changeset exists. Mercurial records successor revisions in obsolescence - markers. - - You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between - revisions to pruned (precursor) and successor changesets. This option may be - removed in a future release (with the functionality provided automatically). - - If you specify multiple revisions in ``--succ``, you are recording a "split" and - must acknowledge it by passing ``--split``. Similarly, when you prune multiple - changesets with a single successor, you must pass the ``--fold`` option. - """ - revs = scmutil.revrange(repo, list(revs) + opts.get('rev')) - succs = opts['new'] + opts['succ'] - bookmarks = set(opts.get('bookmark')) - metadata = _getmetadata(**opts) - biject = opts.get('biject') - fold = opts.get('fold') - split = opts.get('split') - - options = [o for o in ('biject', 'fold', 'split') if opts.get(o)] - if 1 < len(options): - raise error.Abort(_("can only specify one of %s") % ', '.join(options)) - - if bookmarks: - repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks) - if not revs: - # no revisions to prune - delete bookmark immediately - _deletebookmark(repo, repomarks, bookmarks) - - if not revs: - raise error.Abort(_('nothing to prune')) - - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('prune') - # defines pruned changesets - precs = [] - revs.sort() - for p in revs: - cp = repo[p] - if not cp.mutable(): - # note: createmarkers() would have raised something anyway - raise error.Abort('cannot prune immutable changeset: %s' % cp, - hint="see 'hg help phases' for details") - precs.append(cp) - if not precs: - raise error.Abort('nothing to prune') - - if _disallowednewunstable(repo, revs): - raise error.Abort(_("cannot prune in the middle of a stack"), - hint=_("new unstable changesets are not allowed")) - - # defines successors changesets - sucs = scmutil.revrange(repo, succs) - sucs.sort() - sucs = tuple(repo[n] for n in sucs) - if not biject and len(sucs) > 1 and len(precs) > 1: - msg = "Can't use multiple successors for multiple precursors" - hint = _("use --biject to mark a series as a replacement" - " for another") - raise error.Abort(msg, hint=hint) - elif biject and len(sucs) != len(precs): - msg = "Can't use %d successors for %d precursors" \ - % (len(sucs), len(precs)) - raise error.Abort(msg) - elif (len(precs) == 1 and len(sucs) > 1) and not split: - msg = "please add --split if you want to do a split" - raise error.Abort(msg) - elif len(sucs) == 1 and len(precs) > 1 and not fold: - msg = "please add --fold if you want to do a fold" - raise error.Abort(msg) - elif biject: - relations = [(p, (s,)) for p, s in zip(precs, sucs)] - else: - relations = [(p, sucs) for p in precs] - - wdp = repo['.'] - - if len(sucs) == 1 and len(precs) == 1 and wdp in precs: - # '.' killed, so update to the successor - newnode = sucs[0] - else: - # update to an unkilled parent - newnode = wdp - - while newnode in precs or newnode.obsolete(): - newnode = newnode.parents()[0] - - if newnode.node() != wdp.node(): - if opts.get('keep', False): - # This is largely the same as the implementation in - # strip.stripcmd(). We might want to refactor this somewhere - # common at some point. - - # only reset the dirstate for files that would actually change - # between the working context and uctx - descendantrevs = repo.revs("%d::." % newnode.rev()) - changedfiles = [] - for rev in descendantrevs: - # blindly reset the files, regardless of what actually - # changed - changedfiles.extend(repo[rev].files()) - - # reset files that only changed in the dirstate too - dirstate = repo.dirstate - dirchanges = [f for f in dirstate if dirstate[f] != 'n'] - changedfiles.extend(dirchanges) - repo.dirstate.rebuild(newnode.node(), newnode.manifest(), - changedfiles) - dirstate.write(tr) - else: - bookactive = repo._activebookmark - # Active bookmark that we don't want to delete (with -B option) - # we deactivate and move it before the update and reactivate it - # after - movebookmark = bookactive and not bookmarks - if movebookmark: - bookmarksmod.deactivate(repo) - repo._bookmarks[bookactive] = newnode.node() - repo._bookmarks.recordchange(tr) - commands.update(ui, repo, newnode.rev()) - ui.status(_('working directory now at %s\n') - % ui.label(str(newnode), 'evolve.node')) - if movebookmark: - bookmarksmod.activate(repo, bookactive) - - # update bookmarks - if bookmarks: - _deletebookmark(repo, repomarks, bookmarks) - - # create markers - obsolete.createmarkers(repo, relations, metadata=metadata) - - # informs that changeset have been pruned - ui.status(_('%i changesets pruned\n') % len(precs)) - - for ctx in repo.unfiltered().set('bookmark() and %ld', precs): - # used to be: - # - # ldest = list(repo.set('max((::%d) - obsolete())', ctx)) - # if ldest: - # c = ldest[0] - # - # but then revset took a lazy arrow in the knee and became much - # slower. The new forms makes as much sense and a much faster. - for dest in ctx.ancestors(): - if not dest.obsolete(): - updatebookmarks = _bookmarksupdater(repo, ctx.node(), tr) - updatebookmarks(dest.node()) - break - - tr.close() - finally: - lockmod.release(tr, lock, wlock) - -@eh.command( - 'amend|refresh', - [('A', 'addremove', None, - _('mark new/missing files as added/removed before committing')), - ('e', 'edit', False, _('invoke editor on commit messages')), - ('', 'close-branch', None, - _('mark a branch as closed, hiding it from the branch list')), - ('s', 'secret', None, _('use the secret phase for committing')), - ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt, - _('[OPTION]... [FILE]...')) -def amend(ui, repo, *pats, **opts): - """combine a changeset with updates and replace it with a new one - - Commits a new changeset incorporating both the changes to the given files - and all the changes from the current parent changeset into the repository. - - See :hg:`commit` for details about committing changes. - - If you don't specify -m, the parent's message will be reused. - - Behind the scenes, Mercurial first commits the update as a regular child - of the current parent. Then it creates a new commit on the parent's parents - with the updated contents. Then it changes the working copy parent to this - new combined changeset. Finally, the old changeset and its update are hidden - from :hg:`log` (unless you use --hidden with log). - - Returns 0 on success, 1 if nothing changed. - """ - opts = opts.copy() - edit = opts.pop('edit', False) - log = opts.get('logfile') - opts['amend'] = True - if not (edit or opts['message'] or log): - opts['message'] = repo['.'].description() - _resolveoptions(ui, opts) - _alias, commitcmd = cmdutil.findcmd('commit', commands.table) - return commitcmd[0](ui, repo, *pats, **opts) - - -def _touchedbetween(repo, source, dest, match=None): - touched = set() - for files in repo.status(source, dest, match=match)[:3]: - touched.update(files) - return touched - -def _commitfiltered(repo, ctx, match, target=None): - """Recommit ctx with changed files not in match. Return the new - node identifier, or None if nothing changed. - """ - base = ctx.p1() - if target is None: - target = base - # ctx - initialfiles = _touchedbetween(repo, base, ctx) - if base == target: - affected = set(f for f in initialfiles if match(f)) - newcontent = set() - else: - affected = _touchedbetween(repo, target, ctx, match=match) - newcontent = _touchedbetween(repo, target, base, match=match) - # The commit touchs all existing files - # + all file that needs a new content - # - the file affected bny uncommit with the same content than base. - files = (initialfiles - affected) | newcontent - if not newcontent and files == initialfiles: - return None - - # Filter copies - copied = copies.pathcopies(target, ctx) - copied = dict((dst, src) for dst, src in copied.iteritems() - if dst in files) - - def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent): - if path in redirect: - return filectxfn(repo, memctx, path, contentctx=target, redirect=()) - if path not in contentctx: - return None - fctx = contentctx[path] - flags = fctx.flags() - mctx = context.memfilectx(repo, fctx.path(), fctx.data(), - islink='l' in flags, - isexec='x' in flags, - copied=copied.get(path)) - return mctx - - new = context.memctx(repo, - parents=[base.node(), node.nullid], - text=ctx.description(), - files=files, - filectxfn=filectxfn, - user=ctx.user(), - date=ctx.date(), - extra=ctx.extra()) - # commitctx always create a new revision, no need to check - newid = repo.commitctx(new) - return newid - -def _uncommitdirstate(repo, oldctx, match): - """Fix the dirstate after switching the working directory from - oldctx to a copy of oldctx not containing changed files matched by - match. - """ - ctx = repo['.'] - ds = repo.dirstate - copies = dict(ds.copies()) - m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3] - for f in m: - if ds[f] == 'r': - # modified + removed -> removed - continue - ds.normallookup(f) - - for f in a: - if ds[f] == 'r': - # added + removed -> unknown - ds.drop(f) - elif ds[f] != 'a': - ds.add(f) - - for f in r: - if ds[f] == 'a': - # removed + added -> normal - ds.normallookup(f) - elif ds[f] != 'r': - ds.remove(f) - - # Merge old parent and old working dir copies - oldcopies = {} - for f in (m + a): - src = oldctx[f].renamed() - if src: - oldcopies[f] = src[0] - oldcopies.update(copies) - copies = dict((dst, oldcopies.get(src, src)) - for dst, src in oldcopies.iteritems()) - # Adjust the dirstate copies - for dst, src in copies.iteritems(): - if (src not in ctx or dst in ctx or ds[dst] != 'a'): - src = None - ds.copy(src, dst) - -@eh.command( - '^uncommit', - [('a', 'all', None, _('uncommit all changes when no arguments given')), - ('r', 'rev', '', _('revert commit content to REV instead')), - ] + commands.walkopts, - _('[OPTION]... [NAME]')) -def uncommit(ui, repo, *pats, **opts): - """move changes from parent revision to working directory - - Changes to selected files in the checked out revision appear again as - uncommitted changed in the working directory. A new revision - without the selected changes is created, becomes the checked out - revision, and obsoletes the previous one. - - The --include option specifies patterns to uncommit. - The --exclude option specifies patterns to keep in the commit. - - The --rev argument let you change the commit file to a content of another - revision. It still does not change the content of your file in the working - directory. - - Return 0 if changed files are uncommitted. - """ - - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - wctx = repo[None] - if len(wctx.parents()) <= 0: - raise error.Abort(_("cannot uncommit null changeset")) - if len(wctx.parents()) > 1: - raise error.Abort(_("cannot uncommit while merging")) - old = repo['.'] - if old.phase() == phases.public: - raise error.Abort(_("cannot rewrite immutable changeset")) - if len(old.parents()) > 1: - raise error.Abort(_("cannot uncommit merge changeset")) - oldphase = old.phase() - - rev = None - if opts.get('rev'): - rev = scmutil.revsingle(repo, opts.get('rev')) - ctx = repo[None] - if ctx.p1() == rev or ctx.p2() == rev: - raise error.Abort(_("cannot uncommit to parent changeset")) - - onahead = old.rev() in repo.changelog.headrevs() - disallowunstable = not obsolete.isenabled(repo, - obsolete.allowunstableopt) - if disallowunstable and not onahead: - raise error.Abort(_("cannot uncommit in the middle of a stack")) - - # Recommit the filtered changeset - tr = repo.transaction('uncommit') - updatebookmarks = _bookmarksupdater(repo, old.node(), tr) - newid = None - includeorexclude = opts.get('include') or opts.get('exclude') - if (pats or includeorexclude or opts.get('all')): - match = scmutil.match(old, pats, opts) - newid = _commitfiltered(repo, old, match, target=rev) - if newid is None: - raise error.Abort(_('nothing to uncommit'), - hint=_("use --all to uncommit all files")) - # Move local changes on filtered changeset - obsolete.createmarkers(repo, [(old, (repo[newid],))]) - phases.retractboundary(repo, tr, oldphase, [newid]) - with repo.dirstate.parentchange(): - repo.dirstate.setparents(newid, node.nullid) - _uncommitdirstate(repo, old, match) - updatebookmarks(newid) - if not repo[newid].files(): - ui.warn(_("new changeset is empty\n")) - ui.status(_("(use 'hg prune .' to remove it)\n")) - tr.close() - finally: - lockmod.release(tr, lock, wlock) - @eh.wrapcommand('commit') def commitwrapper(orig, ui, repo, *arg, **kwargs): tr = None @@ -2650,110 +2163,21 @@ markers.append((old, (new,))) if markers: obsolete.createmarkers(repo, markers) + bmchanges = [] for book in oldbookmarks: - repo._bookmarks[book] = new.node() + bmchanges.append((book, new.node())) if oldbookmarks: if not wlock: wlock = repo.wlock() if not lock: lock = repo.lock() tr = repo.transaction('commit') - repo._bookmarks.recordchange(tr) + compat.bookmarkapplychanges(repo, tr, bmchanges) tr.close() return result finally: lockmod.release(tr, lock, wlock) -def presplitupdate(repo, ui, prev, ctx): - """prepare the working directory for a split (for topic hooking) - """ - hg.update(repo, prev) - commands.revert(ui, repo, rev=ctx.rev(), all=True) - -@eh.command( - '^split', - [('r', 'rev', [], _("revision to split")), - ] + commitopts + commitopts2, - _('hg split [OPTION]... [-r] REV')) -def cmdsplit(ui, repo, *revs, **opts): - """split a changeset into smaller changesets - - By default, split the current revision by prompting for all its hunks to be - redistributed into new changesets. - - Use --rev to split a given changeset instead. - """ - tr = wlock = lock = None - newcommits = [] - - revarg = (list(revs) + opts.get('rev')) or ['.'] - if len(revarg) != 1: - msg = _("more than one revset is given") - hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both") - raise error.Abort(msg, hint=hnt) - - rev = scmutil.revsingle(repo, revarg[0]) - try: - wlock = repo.wlock() - lock = repo.lock() - cmdutil.bailifchanged(repo) - tr = repo.transaction('split') - ctx = repo[rev] - r = ctx.rev() - disallowunstable = not obsolete.isenabled(repo, - obsolete.allowunstableopt) - if disallowunstable: - # XXX We should check head revs - if repo.revs("(%d::) - %d", rev, rev): - raise error.Abort(_("cannot split commit: %s not a head") % ctx) - - if len(ctx.parents()) > 1: - raise error.Abort(_("cannot split merge commits")) - prev = ctx.p1() - bmupdate = _bookmarksupdater(repo, ctx.node(), tr) - bookactive = repo._activebookmark - if bookactive is not None: - repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark) - bookmarksmod.deactivate(repo) - - # Prepare the working directory - presplitupdate(repo, ui, prev, ctx) - - def haschanges(): - modified, added, removed, deleted = repo.status()[:4] - return modified or added or removed or deleted - msg = ("HG: This is the original pre-split commit message. " - "Edit it as appropriate.\n\n") - msg += ctx.description() - opts['message'] = msg - opts['edit'] = True - opts['user'] = ctx.user() - while haschanges(): - pats = () - cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, - cmdutil.recordfilter, *pats, **opts) - # TODO: Does no seem like the best way to do this - # We should make dorecord return the newly created commit - newcommits.append(repo['.']) - if haschanges(): - if ui.prompt('Done splitting? [yN]', default='n') == 'y': - commands.commit(ui, repo, **opts) - newcommits.append(repo['.']) - break - else: - ui.status(_("no more change to split\n")) - - if newcommits: - tip = repo[newcommits[-1]] - bmupdate(tip.node()) - if bookactive is not None: - bookmarksmod.activate(repo, bookactive) - obsolete.createmarkers(repo, [(repo[r], newcommits)]) - tr.close() - finally: - lockmod.release(tr, lock, wlock) - - @eh.wrapcommand('strip', extension='strip', opts=[ ('', 'bundle', None, _("delete the commit entirely and move it to a " "backup bundle")), @@ -2777,354 +2201,7 @@ kwargs['new'] = [] kwargs['succ'] = [] kwargs['biject'] = False - return cmdprune(ui, repo, *revs, **kwargs) - -@eh.command( - '^touch', - [('r', 'rev', [], 'revision to update'), - ('D', 'duplicate', False, - 'do not mark the new revision as successor of the old one'), - ('A', 'allowdivergence', False, - 'mark the new revision as successor of the old one potentially creating ' - 'divergence')], - # allow to choose the seed ? - _('[-r] revs')) -def touch(ui, repo, *revs, **opts): - """create successors that are identical to their predecessors except - for the changeset ID - - This is used to "resurrect" changesets - """ - duplicate = opts['duplicate'] - allowdivergence = opts['allowdivergence'] - revs = list(revs) - revs.extend(opts['rev']) - if not revs: - revs = ['.'] - revs = scmutil.revrange(repo, revs) - if not revs: - ui.write_err('no revision to touch\n') - return 1 - if not duplicate and repo.revs('public() and %ld', revs): - raise error.Abort("can't touch public revision") - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('touch') - revs.sort() # ensure parent are run first - newmapping = {} - for r in revs: - ctx = repo[r] - extra = ctx.extra().copy() - extra['__touch-noise__'] = random.randint(0, 0xffffffff) - # search for touched parent - p1 = ctx.p1().node() - p2 = ctx.p2().node() - p1 = newmapping.get(p1, p1) - p2 = newmapping.get(p2, p2) - - if not (duplicate or allowdivergence): - # The user hasn't yet decided what to do with the revived - # cset, let's ask - sset = compat.successorssets(repo, ctx.node()) - nodivergencerisk = (len(sset) == 0 or - (len(sset) == 1 and - len(sset[0]) == 1 and - repo[sset[0][0]].rev() == ctx.rev() - )) - if nodivergencerisk: - duplicate = False - else: - displayer.show(ctx) - index = ui.promptchoice( - _("reviving this changeset will create divergence" - " unless you make a duplicate.\n(a)llow divergence or" - " (d)uplicate the changeset? $$ &Allowdivergence $$ " - "&Duplicate"), 0) - choice = ['allowdivergence', 'duplicate'][index] - if choice == 'allowdivergence': - duplicate = False - else: - duplicate = True - - new, unusedvariable = rewrite(repo, ctx, [], ctx, - [p1, p2], - commitopts={'extra': extra}) - # store touched version to help potential children - newmapping[ctx.node()] = new - - if not duplicate: - obsolete.createmarkers(repo, [(ctx, (repo[new],))]) - phases.retractboundary(repo, tr, ctx.phase(), [new]) - if ctx in repo[None].parents(): - with repo.dirstate.parentchange(): - repo.dirstate.setparents(new, node.nullid) - tr.close() - finally: - lockmod.release(tr, lock, wlock) - -@eh.command( - '^fold|squash', - [('r', 'rev', [], _("revision to fold")), - ('', 'exact', None, _("only fold specified revisions")), - ('', 'from', None, _("fold revisions linearly to working copy parent")) - ] + commitopts + commitopts2, - _('hg fold [OPTION]... [-r] REV')) -def fold(ui, repo, *revs, **opts): - """fold multiple revisions into a single one - - With --from, folds all the revisions linearly between the given revisions - and the parent of the working directory. - - With --exact, folds only the specified revisions while ignoring the - parent of the working directory. In this case, the given revisions must - form a linear unbroken chain. - - .. container:: verbose - - Some examples: - - - Fold the current revision with its parent:: - - hg fold --from .^ - - - Fold all draft revisions with working directory parent:: - - hg fold --from 'draft()' - - See :hg:`help phases` for more about draft revisions and - :hg:`help revsets` for more about the `draft()` keyword - - - Fold revisions between 3 and 6 with the working directory parent:: - - hg fold --from 3::6 - - - Fold revisions 3 and 4: - - hg fold "3 + 4" --exact - - - Only fold revisions linearly between foo and @:: - - hg fold foo::@ --exact - """ - revs = list(revs) - revs.extend(opts['rev']) - if not revs: - raise error.Abort(_('no revisions specified')) - - revs = scmutil.revrange(repo, revs) - - if opts['from'] and opts['exact']: - raise error.Abort(_('cannot use both --from and --exact')) - elif opts['from']: - # Try to extend given revision starting from the working directory - extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs) - discardedrevs = [r for r in revs if r not in extrevs] - if discardedrevs: - msg = _("cannot fold non-linear revisions") - hint = _("given revisions are unrelated to parent of working" - " directory") - raise error.Abort(msg, hint=hint) - revs = extrevs - elif opts['exact']: - # Nothing to do; "revs" is already set correctly - pass - else: - raise error.Abort(_('must specify either --from or --exact')) - - if not revs: - raise error.Abort(_('specified revisions evaluate to an empty set'), - hint=_('use different revision arguments')) - elif len(revs) == 1: - ui.write_err(_('single revision specified, nothing to fold\n')) - return 1 - - wlock = lock = None - try: - wlock = repo.wlock() - lock = repo.lock() - - root, head = _foldcheck(repo, revs) - - tr = repo.transaction('fold') - try: - commitopts = opts.copy() - allctx = [repo[r] for r in revs] - targetphase = max(c.phase() for c in allctx) - - if commitopts.get('message') or commitopts.get('logfile'): - commitopts['edit'] = False - else: - msgs = ["HG: This is a fold of %d changesets." % len(allctx)] - msgs += ["HG: Commit message of changeset %s.\n\n%s\n" % - (c.rev(), c.description()) for c in allctx] - commitopts['message'] = "\n".join(msgs) - commitopts['edit'] = True - - newid, unusedvariable = rewrite(repo, root, allctx, head, - [root.p1().node(), - root.p2().node()], - commitopts=commitopts) - phases.retractboundary(repo, tr, targetphase, [newid]) - obsolete.createmarkers(repo, [(ctx, (repo[newid],)) - for ctx in allctx]) - tr.close() - finally: - tr.release() - ui.status('%i changesets folded\n' % len(revs)) - if repo['.'].rev() in revs: - hg.update(repo, newid) - finally: - lockmod.release(lock, wlock) - -@eh.command( - '^metaedit', - [('r', 'rev', [], _("revision to edit")), - ('', 'fold', None, _("also fold specified revisions into one")), - ] + commitopts + commitopts2, - _('hg metaedit [OPTION]... [-r] [REV]')) -def metaedit(ui, repo, *revs, **opts): - """edit commit information - - Edits the commit information for the specified revisions. By default, edits - commit information for the working directory parent. - - With --fold, also folds multiple revisions into one if necessary. In this - case, the given revisions must form a linear unbroken chain. - - .. container:: verbose - - Some examples: - - - Edit the commit message for the working directory parent:: - - hg metaedit - - - Change the username for the working directory parent:: - - hg metaedit --user 'New User <new-email@example.com>' - - - Combine all draft revisions that are ancestors of foo but not of @ into - one:: - - hg metaedit --fold 'draft() and only(foo,@)' - - See :hg:`help phases` for more about draft revisions, and - :hg:`help revsets` for more about the `draft()` and `only()` keywords. - """ - revs = list(revs) - revs.extend(opts['rev']) - if not revs: - if opts['fold']: - raise error.Abort(_('revisions must be specified with --fold')) - revs = ['.'] - - wlock = lock = None - try: - wlock = repo.wlock() - lock = repo.lock() - - revs = scmutil.revrange(repo, revs) - if not opts['fold'] and len(revs) > 1: - # TODO: handle multiple revisions. This is somewhat tricky because - # if we want to edit a series of commits: - # - # a ---- b ---- c - # - # we need to rewrite a first, then directly rewrite b on top of the - # new a, then rewrite c on top of the new b. So we need to handle - # revisions in topological order. - raise error.Abort(_('editing multiple revisions without --fold is ' - 'not currently supported')) - - if opts['fold']: - root, head = _foldcheck(repo, revs) - else: - if repo.revs("%ld and public()", revs): - raise error.Abort(_('cannot edit commit information for public ' - 'revisions')) - newunstable = _disallowednewunstable(repo, revs) - if newunstable: - msg = _('cannot edit commit information in the middle' - ' of a stack') - hint = _('%s will become unstable and new unstable changes' - ' are not allowed') - hint %= repo[newunstable.first()] - raise error.Abort(msg, hint=hint) - root = head = repo[revs.first()] - - wctx = repo[None] - p1 = wctx.p1() - tr = repo.transaction('metaedit') - newp1 = None - try: - commitopts = opts.copy() - allctx = [repo[r] for r in revs] - targetphase = max(c.phase() for c in allctx) - - if commitopts.get('message') or commitopts.get('logfile'): - commitopts['edit'] = False - else: - if opts['fold']: - msgs = ["HG: This is a fold of %d changesets." % len(allctx)] - msgs += ["HG: Commit message of changeset %s.\n\n%s\n" % - (c.rev(), c.description()) for c in allctx] - else: - msgs = [head.description()] - commitopts['message'] = "\n".join(msgs) - commitopts['edit'] = True - - # TODO: if the author and message are the same, don't create a new - # hash. Right now we create a new hash because the date can be - # different. - newid, created = rewrite(repo, root, allctx, head, - [root.p1().node(), root.p2().node()], - commitopts=commitopts) - if created: - if p1.rev() in revs: - newp1 = newid - phases.retractboundary(repo, tr, targetphase, [newid]) - obsolete.createmarkers(repo, [(ctx, (repo[newid],)) - for ctx in allctx]) - else: - ui.status(_("nothing changed\n")) - tr.close() - finally: - tr.release() - - if opts['fold']: - ui.status('%i changesets folded\n' % len(revs)) - if newp1 is not None: - hg.update(repo, newp1) - finally: - lockmod.release(lock, wlock) - -def _foldcheck(repo, revs): - roots = repo.revs('roots(%ld)', revs) - if len(roots) > 1: - raise error.Abort(_("cannot fold non-linear revisions " - "(multiple roots given)")) - root = repo[roots.first()] - if root.phase() <= phases.public: - raise error.Abort(_("cannot fold public revisions")) - heads = repo.revs('heads(%ld)', revs) - if len(heads) > 1: - raise error.Abort(_("cannot fold non-linear revisions " - "(multiple heads given)")) - head = repo[heads.first()] - if _disallowednewunstable(repo, revs): - msg = _("cannot fold chain not ending with a head or with branching") - hint = _("new unstable changesets are not allowed") - raise error.Abort(msg, hint=hint) - return root, head - -def _disallowednewunstable(repo, revs): - allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt) - if allowunstable: - return revset.baseset() - return repo.revs("(%ld::) - %ld", revs, revs) + return cmdrewrite.cmdprune(ui, repo, *revs, **kwargs) @eh.wrapcommand('graft') def graftwrapper(orig, ui, repo, *revs, **kwargs): @@ -3196,23 +2273,24 @@ if new_format == repo.obsstore._version: msg = _('New format is the same as the old format, not upgrading!') raise error.Abort(msg) - f = repo.svfs('obsstore', 'wb', atomictemp=True) - known = set() - markers = [] - for m in origmarkers: - # filter out invalid markers - if nullid in m[1]: - m = list(m) - m[1] = tuple(s for s in m[1] if s != nullid) - m = tuple(m) - if m in known: - continue - known.add(m) - markers.append(m) - ui.write(_('Old store is version %d, will rewrite in version %d\n') % ( - repo.obsstore._version, new_format)) - map(f.write, obsolete.encodemarkers(markers, True, new_format)) - f.close() + with repo.lock(): + f = repo.svfs('obsstore', 'wb', atomictemp=True) + known = set() + markers = [] + for m in origmarkers: + # filter out invalid markers + if nullid in m[1]: + m = list(m) + m[1] = tuple(s for s in m[1] if s != nullid) + m = tuple(m) + if m in known: + continue + known.add(m) + markers.append(m) + ui.write(_('Old store is version %d, will rewrite in version %d\n') % ( + repo.obsstore._version, new_format)) + map(f.write, obsolete.encodemarkers(markers, True, new_format)) + f.close() ui.write(_('Done!\n')) @@ -3253,20 +2331,22 @@ nodesrc = orig.node() destphase = repo[nodesrc].phase() oldbookmarks = repo.nodebookmarks(nodesrc) + bmchanges = [] + if nodenew is not None: phases.retractboundary(repo, tr, destphase, [nodenew]) obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))]) for book in oldbookmarks: - repo._bookmarks[book] = nodenew + bmchanges.append((book, nodenew)) else: obsolete.createmarkers(repo, [(repo[nodesrc], ())]) # Behave like rebase, move bookmarks to dest for book in oldbookmarks: - repo._bookmarks[book] = dest.node() + bmchanges.append((book, dest.node())) for book in destbookmarks: # restore bookmark that rebase move - repo._bookmarks[book] = dest.node() - if oldbookmarks or destbookmarks: - repo._bookmarks.recordchange(tr) + bmchanges.append((book, dest.node())) + if bmchanges: + compat.bookmarkapplychanges(repo, tr, bmchanges) evolvestateversion = 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext3rd/evolve/cmdrewrite.py Tue Jul 25 04:01:10 2017 +0200 @@ -0,0 +1,919 @@ +# Module dedicated to host history rewriting commands +# +# Copyright 2017 Octobus <contact@octobus.net> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# Status: Stabilization of the API in progress +# +# The final set of command should go into core. + +from __future__ import absolute_import + +import random + +from mercurial import ( + bookmarks as bookmarksmod, + cmdutil, + commands, + context, + copies, + error, + hg, + lock as lockmod, + node, + obsolete, + phases, + scmutil, + util, +) + +from mercurial.i18n import _ + +from . import ( + compat, + exthelper, + rewriteutil, + utility, +) + +eh = exthelper.exthelper() + +walkopts = commands.walkopts +commitopts = commands.commitopts +commitopts2 = commands.commitopts2 +mergetoolopts = commands.mergetoolopts + +# option added by evolve + +def _resolveoptions(ui, opts): + """modify commit options dict to handle related options + + For now, all it does is figure out the commit date: respect -D unless + -d was supplied. + """ + # N.B. this is extremely similar to setupheaderopts() in mq.py + if not opts.get('date') and opts.get('current_date'): + opts['date'] = '%d %d' % util.makedate() + if not opts.get('user') and opts.get('current_user'): + opts['user'] = ui.username() + +commitopts3 = [ + ('D', 'current-date', None, + _('record the current date as commit date')), + ('U', 'current-user', None, + _('record the current user as committer')), +] + +interactiveopt = [['i', 'interactive', None, _('use interactive mode')]] + +@eh.command( + 'amend|refresh', + [('A', 'addremove', None, + _('mark new/missing files as added/removed before committing')), + ('a', 'all', False, _("match all files")), + ('e', 'edit', False, _('invoke editor on commit messages')), + ('', 'extract', False, _('extract changes from the commit to the working copy')), + ('', 'close-branch', None, + _('mark a branch as closed, hiding it from the branch list')), + ('s', 'secret', None, _('use the secret phase for committing')), + ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt, + _('[OPTION]... [FILE]...')) +def amend(ui, repo, *pats, **opts): + """combine a changeset with updates and replace it with a new one + + Commits a new changeset incorporating both the changes to the given files + and all the changes from the current parent changeset into the repository. + + See :hg:`commit` for details about committing changes. + + If you don't specify -m, the parent's message will be reused. + + If --extra is specified, the behavior of `hg amend` is reversed: Changes + to selected files in the checked out revision appear again as uncommitted + changed in the working directory. + + Returns 0 on success, 1 if nothing changed. + """ + opts = opts.copy() + if opts.get('extract'): + if opts.pop('interactive', False): + msg = _('not support for --interactive with --extract yet') + raise error.Abort(msg) + return uncommit(ui, repo, *pats, **opts) + else: + if opts.pop('all', False): + # add an include for all + include = list(opts.get('include')) + include.append('re:.*') + edit = opts.pop('edit', False) + log = opts.get('logfile') + opts['amend'] = True + if not (edit or opts['message'] or log): + opts['message'] = repo['.'].description() + _resolveoptions(ui, opts) + _alias, commitcmd = cmdutil.findcmd('commit', commands.table) + try: + wlock = repo.wlock() + lock = repo.lock() + rewriteutil.precheck(repo, [repo['.'].rev()], action='amend') + return commitcmd[0](ui, repo, *pats, **opts) + finally: + lockmod.release(lock, wlock) + +def _touchedbetween(repo, source, dest, match=None): + touched = set() + for files in repo.status(source, dest, match=match)[:3]: + touched.update(files) + return touched + +def _commitfiltered(repo, ctx, match, target=None, message=None, user=None, + date=None): + """Recommit ctx with changed files not in match. Return the new + node identifier, or None if nothing changed. + """ + base = ctx.p1() + if target is None: + target = base + # ctx + initialfiles = _touchedbetween(repo, base, ctx) + if base == target: + affected = set(f for f in initialfiles if match(f)) + newcontent = set() + else: + affected = _touchedbetween(repo, target, ctx, match=match) + newcontent = _touchedbetween(repo, target, base, match=match) + # The commit touchs all existing files + # + all file that needs a new content + # - the file affected bny uncommit with the same content than base. + files = (initialfiles - affected) | newcontent + if not newcontent and files == initialfiles: + return None + + # Filter copies + copied = copies.pathcopies(target, ctx) + copied = dict((dst, src) for dst, src in copied.iteritems() + if dst in files) + + def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent): + if path in redirect: + return filectxfn(repo, memctx, path, contentctx=target, redirect=()) + if path not in contentctx: + return None + fctx = contentctx[path] + flags = fctx.flags() + mctx = context.memfilectx(repo, fctx.path(), fctx.data(), + islink='l' in flags, + isexec='x' in flags, + copied=copied.get(path)) + return mctx + + if message is None: + message = ctx.description() + if not user: + user = ctx.user() + if not date: + date = ctx.date() + new = context.memctx(repo, + parents=[base.node(), node.nullid], + text=message, + files=files, + filectxfn=filectxfn, + user=user, + date=date, + extra=ctx.extra()) + # commitctx always create a new revision, no need to check + newid = repo.commitctx(new) + return newid + +def _uncommitdirstate(repo, oldctx, match): + """Fix the dirstate after switching the working directory from + oldctx to a copy of oldctx not containing changed files matched by + match. + """ + ctx = repo['.'] + ds = repo.dirstate + copies = dict(ds.copies()) + m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3] + for f in m: + if ds[f] == 'r': + # modified + removed -> removed + continue + ds.normallookup(f) + + for f in a: + if ds[f] == 'r': + # added + removed -> unknown + ds.drop(f) + elif ds[f] != 'a': + ds.add(f) + + for f in r: + if ds[f] == 'a': + # removed + added -> normal + ds.normallookup(f) + elif ds[f] != 'r': + ds.remove(f) + + # Merge old parent and old working dir copies + oldcopies = {} + for f in (m + a): + src = oldctx[f].renamed() + if src: + oldcopies[f] = src[0] + oldcopies.update(copies) + copies = dict((dst, oldcopies.get(src, src)) + for dst, src in oldcopies.iteritems()) + # Adjust the dirstate copies + for dst, src in copies.iteritems(): + if (src not in ctx or dst in ctx or ds[dst] != 'a'): + src = None + ds.copy(src, dst) + +@eh.command( + '^uncommit', + [('a', 'all', None, _('uncommit all changes when no arguments given')), + ('r', 'rev', '', _('revert commit content to REV instead')), + ] + commands.walkopts + commitopts + commitopts2 + commitopts3, + _('[OPTION]... [NAME]')) +def uncommit(ui, repo, *pats, **opts): + """move changes from parent revision to working directory + + Changes to selected files in the checked out revision appear again as + uncommitted changed in the working directory. A new revision + without the selected changes is created, becomes the checked out + revision, and obsoletes the previous one. + + The --include option specifies patterns to uncommit. + The --exclude option specifies patterns to keep in the commit. + + The --rev argument let you change the commit file to a content of another + revision. It still does not change the content of your file in the working + directory. + + Return 0 if changed files are uncommitted. + """ + + _resolveoptions(ui, opts) # process commitopts3 + wlock = lock = tr = None + try: + wlock = repo.wlock() + lock = repo.lock() + wctx = repo[None] + if len(wctx.parents()) <= 0: + raise error.Abort(_("cannot uncommit null changeset")) + if len(wctx.parents()) > 1: + raise error.Abort(_("cannot uncommit while merging")) + old = repo['.'] + rewriteutil.precheck(repo, [repo['.'].rev()], action='uncommit') + if len(old.parents()) > 1: + raise error.Abort(_("cannot uncommit merge changeset")) + oldphase = old.phase() + + rev = None + if opts.get('rev'): + rev = scmutil.revsingle(repo, opts.get('rev')) + ctx = repo[None] + if ctx.p1() == rev or ctx.p2() == rev: + raise error.Abort(_("cannot uncommit to parent changeset")) + + onahead = old.rev() in repo.changelog.headrevs() + disallowunstable = not obsolete.isenabled(repo, + obsolete.allowunstableopt) + if disallowunstable and not onahead: + raise error.Abort(_("cannot uncommit in the middle of a stack")) + + # Recommit the filtered changeset + tr = repo.transaction('uncommit') + updatebookmarks = rewriteutil.bookmarksupdater(repo, old.node(), tr) + newid = None + includeorexclude = opts.get('include') or opts.get('exclude') + if (pats or includeorexclude or opts.get('all')): + match = scmutil.match(old, pats, opts) + if not (opts['message'] or opts['logfile']): + opts['message'] = old.description() + message = cmdutil.logmessage(ui, opts) + newid = _commitfiltered(repo, old, match, target=rev, + message=message, user=opts.get('user'), + date=opts.get('date')) + if newid is None: + raise error.Abort(_('nothing to uncommit'), + hint=_("use --all to uncommit all files")) + # Move local changes on filtered changeset + obsolete.createmarkers(repo, [(old, (repo[newid],))]) + phases.retractboundary(repo, tr, oldphase, [newid]) + with repo.dirstate.parentchange(): + repo.dirstate.setparents(newid, node.nullid) + _uncommitdirstate(repo, old, match) + updatebookmarks(newid) + if not repo[newid].files(): + ui.warn(_("new changeset is empty\n")) + ui.status(_("(use 'hg prune .' to remove it)\n")) + tr.close() + finally: + lockmod.release(tr, lock, wlock) + +@eh.command( + '^fold|squash', + [('r', 'rev', [], _("revision to fold")), + ('', 'exact', None, _("only fold specified revisions")), + ('', 'from', None, _("fold revisions linearly to working copy parent")) + ] + commitopts + commitopts2 + commitopts3, + _('hg fold [OPTION]... [-r] REV')) +def fold(ui, repo, *revs, **opts): + """fold multiple revisions into a single one + + With --from, folds all the revisions linearly between the given revisions + and the parent of the working directory. + + With --exact, folds only the specified revisions while ignoring the + parent of the working directory. In this case, the given revisions must + form a linear unbroken chain. + + .. container:: verbose + + Some examples: + + - Fold the current revision with its parent:: + + hg fold --from .^ + + - Fold all draft revisions with working directory parent:: + + hg fold --from 'draft()' + + See :hg:`help phases` for more about draft revisions and + :hg:`help revsets` for more about the `draft()` keyword + + - Fold revisions between 3 and 6 with the working directory parent:: + + hg fold --from 3::6 + + - Fold revisions 3 and 4: + + hg fold "3 + 4" --exact + + - Only fold revisions linearly between foo and @:: + + hg fold foo::@ --exact + """ + _resolveoptions(ui, opts) + revs = list(revs) + revs.extend(opts['rev']) + if not revs: + raise error.Abort(_('no revisions specified')) + + revs = scmutil.revrange(repo, revs) + + if opts['from'] and opts['exact']: + raise error.Abort(_('cannot use both --from and --exact')) + elif opts['from']: + # Try to extend given revision starting from the working directory + extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs) + discardedrevs = [r for r in revs if r not in extrevs] + if discardedrevs: + msg = _("cannot fold non-linear revisions") + hint = _("given revisions are unrelated to parent of working" + " directory") + raise error.Abort(msg, hint=hint) + revs = extrevs + elif opts['exact']: + # Nothing to do; "revs" is already set correctly + pass + else: + raise error.Abort(_('must specify either --from or --exact')) + + if not revs: + raise error.Abort(_('specified revisions evaluate to an empty set'), + hint=_('use different revision arguments')) + elif len(revs) == 1: + ui.write_err(_('single revision specified, nothing to fold\n')) + return 1 + + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + + root, head = rewriteutil.foldcheck(repo, revs) + + tr = repo.transaction('fold') + try: + commitopts = opts.copy() + allctx = [repo[r] for r in revs] + targetphase = max(c.phase() for c in allctx) + + if commitopts.get('message') or commitopts.get('logfile'): + commitopts['edit'] = False + else: + msgs = ["HG: This is a fold of %d changesets." % len(allctx)] + msgs += ["HG: Commit message of changeset %s.\n\n%s\n" % + (c.rev(), c.description()) for c in allctx] + commitopts['message'] = "\n".join(msgs) + commitopts['edit'] = True + + newid, unusedvariable = rewriteutil.rewrite(repo, root, allctx, + head, + [root.p1().node(), + root.p2().node()], + commitopts=commitopts) + phases.retractboundary(repo, tr, targetphase, [newid]) + obsolete.createmarkers(repo, [(ctx, (repo[newid],)) + for ctx in allctx]) + tr.close() + finally: + tr.release() + ui.status('%i changesets folded\n' % len(revs)) + if repo['.'].rev() in revs: + hg.update(repo, newid) + finally: + lockmod.release(lock, wlock) + +@eh.command( + '^metaedit', + [('r', 'rev', [], _("revision to edit")), + ('', 'fold', None, _("also fold specified revisions into one")), + ] + commitopts + commitopts2 + commitopts3, + _('hg metaedit [OPTION]... [-r] [REV]')) +def metaedit(ui, repo, *revs, **opts): + """edit commit information + + Edits the commit information for the specified revisions. By default, edits + commit information for the working directory parent. + + With --fold, also folds multiple revisions into one if necessary. In this + case, the given revisions must form a linear unbroken chain. + + .. container:: verbose + + Some examples: + + - Edit the commit message for the working directory parent:: + + hg metaedit + + - Change the username for the working directory parent:: + + hg metaedit --user 'New User <new-email@example.com>' + + - Combine all draft revisions that are ancestors of foo but not of @ into + one:: + + hg metaedit --fold 'draft() and only(foo,@)' + + See :hg:`help phases` for more about draft revisions, and + :hg:`help revsets` for more about the `draft()` and `only()` keywords. + """ + _resolveoptions(ui, opts) + revs = list(revs) + revs.extend(opts['rev']) + if not revs: + if opts['fold']: + raise error.Abort(_('revisions must be specified with --fold')) + revs = ['.'] + + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + + revs = scmutil.revrange(repo, revs) + if not opts['fold'] and len(revs) > 1: + # TODO: handle multiple revisions. This is somewhat tricky because + # if we want to edit a series of commits: + # + # a ---- b ---- c + # + # we need to rewrite a first, then directly rewrite b on top of the + # new a, then rewrite c on top of the new b. So we need to handle + # revisions in topological order. + raise error.Abort(_('editing multiple revisions without --fold is ' + 'not currently supported')) + + if opts['fold']: + root, head = rewriteutil.foldcheck(repo, revs) + else: + if repo.revs("%ld and public()", revs): + raise error.Abort(_('cannot edit commit information for public ' + 'revisions')) + newunstable = rewriteutil.disallowednewunstable(repo, revs) + if newunstable: + msg = _('cannot edit commit information in the middle' + ' of a stack') + hint = _('%s will become unstable and new unstable changes' + ' are not allowed') + hint %= repo[newunstable.first()] + raise error.Abort(msg, hint=hint) + root = head = repo[revs.first()] + + wctx = repo[None] + p1 = wctx.p1() + tr = repo.transaction('metaedit') + newp1 = None + try: + commitopts = opts.copy() + allctx = [repo[r] for r in revs] + targetphase = max(c.phase() for c in allctx) + + if commitopts.get('message') or commitopts.get('logfile'): + commitopts['edit'] = False + else: + if opts['fold']: + msgs = ["HG: This is a fold of %d changesets." % len(allctx)] + msgs += ["HG: Commit message of changeset %s.\n\n%s\n" % + (c.rev(), c.description()) for c in allctx] + else: + msgs = [head.description()] + commitopts['message'] = "\n".join(msgs) + commitopts['edit'] = True + + # TODO: if the author and message are the same, don't create a new + # hash. Right now we create a new hash because the date can be + # different. + newid, created = rewriteutil.rewrite(repo, root, allctx, head, + [root.p1().node(), + root.p2().node()], + commitopts=commitopts) + if created: + if p1.rev() in revs: + newp1 = newid + phases.retractboundary(repo, tr, targetphase, [newid]) + obsolete.createmarkers(repo, [(ctx, (repo[newid],)) + for ctx in allctx]) + else: + ui.status(_("nothing changed\n")) + tr.close() + finally: + tr.release() + + if opts['fold']: + ui.status('%i changesets folded\n' % len(revs)) + if newp1 is not None: + hg.update(repo, newp1) + finally: + lockmod.release(lock, wlock) + +metadataopts = [ + ('d', 'date', '', + _('record the specified date in metadata'), _('DATE')), + ('u', 'user', '', + _('record the specified user in metadata'), _('USER')), +] + +def _getmetadata(**opts): + metadata = {} + date = opts.get('date') + user = opts.get('user') + if date: + metadata['date'] = '%i %i' % util.parsedate(date) + if user: + metadata['user'] = user + return metadata + +@eh.command( + '^prune|obsolete', + [('n', 'new', [], _("successor changeset (DEPRECATED)")), + ('s', 'succ', [], _("successor changeset")), + ('r', 'rev', [], _("revisions to prune")), + ('k', 'keep', None, _("does not modify working copy during prune")), + ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")), + ('', 'fold', False, + _("record a fold (multiple precursors, one successors)")), + ('', 'split', False, + _("record a split (on precursor, multiple successors)")), + ('B', 'bookmark', [], _("remove revs only reachable from given" + " bookmark"))] + metadataopts, + _('[OPTION] [-r] REV...')) +# XXX -U --noupdate option to prevent wc update and or bookmarks update ? +def cmdprune(ui, repo, *revs, **opts): + """hide changesets by marking them obsolete + + Pruned changesets are obsolete with no successors. If they also have no + descendants, they are hidden (invisible to all commands). + + Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve` + to handle this situation. + + When you prune the parent of your working copy, Mercurial updates the working + copy to a non-obsolete parent. + + You can use ``--succ`` to tell Mercurial that a newer version (successor) of the + pruned changeset exists. Mercurial records successor revisions in obsolescence + markers. + + You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between + revisions to pruned (precursor) and successor changesets. This option may be + removed in a future release (with the functionality provided automatically). + + If you specify multiple revisions in ``--succ``, you are recording a "split" and + must acknowledge it by passing ``--split``. Similarly, when you prune multiple + changesets with a single successor, you must pass the ``--fold`` option. + """ + revs = scmutil.revrange(repo, list(revs) + opts.get('rev')) + succs = opts['new'] + opts['succ'] + bookmarks = set(opts.get('bookmark')) + metadata = _getmetadata(**opts) + biject = opts.get('biject') + fold = opts.get('fold') + split = opts.get('split') + + options = [o for o in ('biject', 'fold', 'split') if opts.get(o)] + if 1 < len(options): + raise error.Abort(_("can only specify one of %s") % ', '.join(options)) + + if bookmarks: + reachablefrombookmark = rewriteutil.reachablefrombookmark + repomarks, revs = reachablefrombookmark(repo, revs, bookmarks) + if not revs: + # no revisions to prune - delete bookmark immediately + rewriteutil.deletebookmark(repo, repomarks, bookmarks) + + if not revs: + raise error.Abort(_('nothing to prune')) + + wlock = lock = tr = None + try: + wlock = repo.wlock() + lock = repo.lock() + rewriteutil.precheck(repo, revs, 'touch') + tr = repo.transaction('prune') + # defines pruned changesets + precs = [] + revs.sort() + for p in revs: + cp = repo[p] + precs.append(cp) + if not precs: + raise error.Abort('nothing to prune') + + # defines successors changesets + sucs = scmutil.revrange(repo, succs) + sucs.sort() + sucs = tuple(repo[n] for n in sucs) + if not biject and len(sucs) > 1 and len(precs) > 1: + msg = "Can't use multiple successors for multiple precursors" + hint = _("use --biject to mark a series as a replacement" + " for another") + raise error.Abort(msg, hint=hint) + elif biject and len(sucs) != len(precs): + msg = "Can't use %d successors for %d precursors" \ + % (len(sucs), len(precs)) + raise error.Abort(msg) + elif (len(precs) == 1 and len(sucs) > 1) and not split: + msg = "please add --split if you want to do a split" + raise error.Abort(msg) + elif len(sucs) == 1 and len(precs) > 1 and not fold: + msg = "please add --fold if you want to do a fold" + raise error.Abort(msg) + elif biject: + relations = [(p, (s,)) for p, s in zip(precs, sucs)] + else: + relations = [(p, sucs) for p in precs] + + wdp = repo['.'] + + if len(sucs) == 1 and len(precs) == 1 and wdp in precs: + # '.' killed, so update to the successor + newnode = sucs[0] + else: + # update to an unkilled parent + newnode = wdp + + while newnode in precs or newnode.obsolete(): + newnode = newnode.parents()[0] + + if newnode.node() != wdp.node(): + if opts.get('keep', False): + # This is largely the same as the implementation in + # strip.stripcmd(). We might want to refactor this somewhere + # common at some point. + + # only reset the dirstate for files that would actually change + # between the working context and uctx + descendantrevs = repo.revs("%d::." % newnode.rev()) + changedfiles = [] + for rev in descendantrevs: + # blindly reset the files, regardless of what actually + # changed + changedfiles.extend(repo[rev].files()) + + # reset files that only changed in the dirstate too + dirstate = repo.dirstate + dirchanges = [f for f in dirstate if dirstate[f] != 'n'] + changedfiles.extend(dirchanges) + repo.dirstate.rebuild(newnode.node(), newnode.manifest(), + changedfiles) + dirstate.write(tr) + else: + bookactive = repo._activebookmark + # Active bookmark that we don't want to delete (with -B option) + # we deactivate and move it before the update and reactivate it + # after + movebookmark = bookactive and not bookmarks + if movebookmark: + bookmarksmod.deactivate(repo) + bmchanges = [(bookactive, newnode.node())] + compat.bookmarkapplychanges(repo, tr, bmchanges) + commands.update(ui, repo, newnode.rev()) + ui.status(_('working directory now at %s\n') + % ui.label(str(newnode), 'evolve.node')) + if movebookmark: + bookmarksmod.activate(repo, bookactive) + + # update bookmarks + if bookmarks: + rewriteutil.deletebookmark(repo, repomarks, bookmarks) + + # create markers + obsolete.createmarkers(repo, relations, metadata=metadata) + + # informs that changeset have been pruned + ui.status(_('%i changesets pruned\n') % len(precs)) + + for ctx in repo.unfiltered().set('bookmark() and %ld', precs): + # used to be: + # + # ldest = list(repo.set('max((::%d) - obsolete())', ctx)) + # if ldest: + # c = ldest[0] + # + # but then revset took a lazy arrow in the knee and became much + # slower. The new forms makes as much sense and a much faster. + for dest in ctx.ancestors(): + if not dest.obsolete(): + bookmarksupdater = rewriteutil.bookmarksupdater + updatebookmarks = bookmarksupdater(repo, ctx.node(), tr) + updatebookmarks(dest.node()) + break + + tr.close() + finally: + lockmod.release(tr, lock, wlock) + +@eh.command( + '^split', + [('r', 'rev', [], _("revision to split")), + ] + commitopts + commitopts2 + commitopts3, + _('hg split [OPTION]... [-r] REV')) +def cmdsplit(ui, repo, *revs, **opts): + """split a changeset into smaller changesets + + By default, split the current revision by prompting for all its hunks to be + redistributed into new changesets. + + Use --rev to split a given changeset instead. + """ + _resolveoptions(ui, opts) + tr = wlock = lock = None + newcommits = [] + + revarg = (list(revs) + opts.get('rev')) or ['.'] + if len(revarg) != 1: + msg = _("more than one revset is given") + hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both") + raise error.Abort(msg, hint=hnt) + + try: + wlock = repo.wlock() + lock = repo.lock() + rev = scmutil.revsingle(repo, revarg[0]) + cmdutil.bailifchanged(repo) + rewriteutil.precheck(repo, [rev], action='split') + tr = repo.transaction('split') + ctx = repo[rev] + + if len(ctx.parents()) > 1: + raise error.Abort(_("cannot split merge commits")) + prev = ctx.p1() + bmupdate = rewriteutil.bookmarksupdater(repo, ctx.node(), tr) + bookactive = repo._activebookmark + if bookactive is not None: + repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark) + bookmarksmod.deactivate(repo) + + # Prepare the working directory + rewriteutil.presplitupdate(repo, ui, prev, ctx) + + def haschanges(): + modified, added, removed, deleted = repo.status()[:4] + return modified or added or removed or deleted + msg = ("HG: This is the original pre-split commit message. " + "Edit it as appropriate.\n\n") + msg += ctx.description() + opts['message'] = msg + opts['edit'] = True + if not opts['user']: + opts['user'] = ctx.user() + while haschanges(): + pats = () + cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, + cmdutil.recordfilter, *pats, **opts) + # TODO: Does no seem like the best way to do this + # We should make dorecord return the newly created commit + newcommits.append(repo['.']) + if haschanges(): + if ui.prompt('Done splitting? [yN]', default='n') == 'y': + commands.commit(ui, repo, **opts) + newcommits.append(repo['.']) + break + else: + ui.status(_("no more change to split\n")) + + if newcommits: + tip = repo[newcommits[-1]] + bmupdate(tip.node()) + if bookactive is not None: + bookmarksmod.activate(repo, bookactive) + obsolete.createmarkers(repo, [(repo[rev], newcommits)]) + tr.close() + finally: + lockmod.release(tr, lock, wlock) + +@eh.command( + '^touch', + [('r', 'rev', [], 'revision to update'), + ('D', 'duplicate', False, + 'do not mark the new revision as successor of the old one'), + ('A', 'allowdivergence', False, + 'mark the new revision as successor of the old one potentially creating ' + 'divergence')], + # allow to choose the seed ? + _('[-r] revs')) +def touch(ui, repo, *revs, **opts): + """create successors that are identical to their predecessors except + for the changeset ID + + This is used to "resurrect" changesets + """ + duplicate = opts['duplicate'] + allowdivergence = opts['allowdivergence'] + revs = list(revs) + revs.extend(opts['rev']) + if not revs: + revs = ['.'] + revs = scmutil.revrange(repo, revs) + if not revs: + ui.write_err('no revision to touch\n') + return 1 + if not duplicate: + rewriteutil.precheck(repo, revs, touch) + tmpl = utility.shorttemplate + displayer = cmdutil.show_changeset(ui, repo, {'template': tmpl}) + wlock = lock = tr = None + try: + wlock = repo.wlock() + lock = repo.lock() + tr = repo.transaction('touch') + revs.sort() # ensure parent are run first + newmapping = {} + for r in revs: + ctx = repo[r] + extra = ctx.extra().copy() + extra['__touch-noise__'] = random.randint(0, 0xffffffff) + # search for touched parent + p1 = ctx.p1().node() + p2 = ctx.p2().node() + p1 = newmapping.get(p1, p1) + p2 = newmapping.get(p2, p2) + + if not (duplicate or allowdivergence): + # The user hasn't yet decided what to do with the revived + # cset, let's ask + sset = compat.successorssets(repo, ctx.node()) + nodivergencerisk = (len(sset) == 0 or + (len(sset) == 1 and + len(sset[0]) == 1 and + repo[sset[0][0]].rev() == ctx.rev() + )) + if nodivergencerisk: + duplicate = False + else: + displayer.show(ctx) + index = ui.promptchoice( + _("reviving this changeset will create divergence" + " unless you make a duplicate.\n(a)llow divergence or" + " (d)uplicate the changeset? $$ &Allowdivergence $$ " + "&Duplicate"), 0) + choice = ['allowdivergence', 'duplicate'][index] + if choice == 'allowdivergence': + duplicate = False + else: + duplicate = True + + extradict = {'extra': extra} + new, unusedvariable = rewriteutil.rewrite(repo, ctx, [], ctx, + [p1, p2], + commitopts=extradict) + # store touched version to help potential children + newmapping[ctx.node()] = new + + if not duplicate: + obsolete.createmarkers(repo, [(ctx, (repo[new],))]) + phases.retractboundary(repo, tr, ctx.phase(), [new]) + if ctx in repo[None].parents(): + with repo.dirstate.parentchange(): + repo.dirstate.setparents(new, node.nullid) + tr.close() + finally: + lockmod.release(tr, lock, wlock)
--- a/hgext3rd/evolve/compat.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/evolve/compat.py Tue Jul 25 04:01:10 2017 +0200 @@ -8,7 +8,8 @@ from mercurial import ( hg, - obsolete + obsolete, + util, ) try: @@ -23,7 +24,7 @@ eh = exthelper.exthelper() -if not hasattr(hg, '_copycache'): +if not util.safehasattr(hg, '_copycache'): # exact copy of relevantmarkers as in Mercurial-176d1a0ce385 # this fixes relevant markers computation for version < hg-4.3 @eh.wrapfunction(obsolete.obsstore, 'relevantmarkers') @@ -75,3 +76,17 @@ if func is None: func = obsolete.allprecursors return func(*args, **kwargs) + +# compatibility layer for mercurial < 4.3 +def bookmarkapplychanges(repo, tr, changes): + """Apply a list of changes to bookmarks + """ + bookmarks = repo._bookmarks + if util.safehasattr(bookmarks, 'applychanges'): + return bookmarks.applychanges(repo, tr, changes) + for name, node in changes: + if node is None: + del bookmarks[name] + else: + bookmarks[name] = node + bookmarks.recordchange(tr)
--- a/hgext3rd/evolve/hack/inhibit.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/evolve/hack/inhibit.py Tue Jul 25 04:01:10 2017 +0200 @@ -106,7 +106,7 @@ 'keep': None, 'biject': False, } - evolve.cmdprune(ui, repo, **optsdict) + evolve.cmdrewrite.cmdprune(ui, repo, **optsdict) # obsolescence inhibitor ########################
--- a/hgext3rd/evolve/metadata.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/evolve/metadata.py Tue Jul 25 04:01:10 2017 +0200 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -__version__ = '6.5.0.dev' -testedwith = '3.8.4 3.9.2 4.0.2 4.1.2 4.2' +__version__ = '6.6.0.dev' +testedwith = '3.8.4 3.9.2 4.0.2 4.1.3 4.2.1' minimumhgversion = '3.8' buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/evolve/obscache.py Tue Jul 25 04:01:10 2017 +0200 @@ -42,6 +42,12 @@ else: timer = time.time +# hg < 4.2 compat +try: + from mercurial import vfs as vfsmod + vfsmod.vfs +except ImportError: + from mercurial import scmutil as vfsmod try: obsstorefilecache = localrepo.localrepository.obsstore @@ -320,6 +326,12 @@ return reset, revs, markers, (obssize, obskey) +def getcachevfs(repo): + cachevfs = getattr(repo, 'cachevfs', None) + if cachevfs is None: + cachevfs = vfsmod.vfs(repo.vfs.join('cache')) + cachevfs.createmode = repo.store.createmode + return cachevfs class obscache(dualsourcecache): """cache the "does a rev" is the precursors of some obsmarkers data @@ -355,7 +367,7 @@ zero. That would be especially useful for the '.pending' overlay. """ - _filepath = 'cache/evoext-obscache-00' + _filepath = 'evoext-obscache-00' _headerformat = '>q20sQQ20s' _cachename = 'evo-ext-obscache' # used for error message @@ -363,8 +375,7 @@ def __init__(self, repo): super(obscache, self).__init__() self._ondiskkey = None - self._vfs = repo.vfs - self._setdata(bytearray()) + self._vfs = getcachevfs(repo) @util.propertycache def get(self): @@ -445,7 +456,7 @@ if self._cachekey is None or self._cachekey == self._ondiskkey: return - cachefile = repo.vfs(self._filepath, 'w', atomictemp=True) + cachefile = self._vfs(self._filepath, 'w', atomictemp=True) headerdata = struct.pack(self._headerformat, *self._cachekey) cachefile.write(headerdata) cachefile.write(self._data) @@ -455,7 +466,7 @@ """load data from disk""" assert repo.filtername is None - data = repo.vfs.tryread(self._filepath) + data = self._vfs.tryread(self._filepath) if not data: self._cachekey = self.emptykey self._setdata(bytearray())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext3rd/evolve/rewriteutil.py Tue Jul 25 04:01:10 2017 +0200 @@ -0,0 +1,247 @@ +# Module dedicated to host utility code dedicated to changeset rewrite +# +# Copyright 2017 Octobus <contact@octobus.net> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# Status: Stabilization of the API in progress +# +# The content of this module should move into core incrementally once we are +# happy one piece of it (and hopefully, able to reuse it in other core +# commands). + +from mercurial import ( + cmdutil, + commands, + context, + copies, + error, + hg, + lock as lockmod, + node, + obsolete, + phases, + repair, + revset, + util, +) + +from mercurial.i18n import _ + +from . import ( + compat, +) + +def _formatrevs(repo, revs, maxrevs=4): + """return a string summarising revision in a descent size + + If there is few enough revision, we list them otherwise we display a + summary in the form: + + 1ea73414a91b and 5 others + """ + tonode = repo.changelog.node + numrevs = len(revs) + if numrevs < maxrevs: + shorts = [node.short(tonode(r)) for r in revs] + summary = ', '.join(shorts) + else: + if util.safehasattr(revs, 'first'): + first = revs.first() + else: + first = revs[0] + summary = _('%s and %d others') + summary %= (node.short(tonode(first)), numrevs - 1) + return summary + +def precheck(repo, revs, action='rewrite'): + """check if <revs> can be rewritten + + <action> can be used to control the commit message. + """ + if node.nullrev in revs: + msg = _("cannot %s the null revision") % (action) + hint = _("no changeset checked out") + raise error.Abort(msg, hint=hint) + publicrevs = repo.revs('%ld and public()', revs) + if publicrevs: + summary = _formatrevs(repo, publicrevs) + msg = _("cannot %s public changesets: %s") % (action, summary) + hint = _("see 'hg help phases' for details") + raise error.Abort(msg, hint=hint) + newunstable = disallowednewunstable(repo, revs) + if newunstable: + msg = _("%s will orphan %i descendants") + msg %= (action, len(newunstable)) + hint = _("see 'hg help evolution.instability'") + raise error.Abort(msg, hint=hint) + +def bookmarksupdater(repo, oldid, tr): + """Return a callable update(newid) updating the current bookmark + and bookmarks bound to oldid to newid. + """ + def updatebookmarks(newid): + oldbookmarks = repo.nodebookmarks(oldid) + bmchanges = [(b, newid) for b in oldbookmarks] + if bmchanges: + compat.bookmarkapplychanges(repo, tr, bmchanges) + return updatebookmarks + +def disallowednewunstable(repo, revs): + """Check that editing <revs> will not create disallowed unstable + + (unstable creation is controled by some special config). + """ + allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt) + if allowunstable: + return revset.baseset() + return repo.revs("(%ld::) - %ld", revs, revs) + +def foldcheck(repo, revs): + """check that <revs> can be folded""" + precheck(repo, revs, action='fold') + roots = repo.revs('roots(%ld)', revs) + if len(roots) > 1: + raise error.Abort(_("cannot fold non-linear revisions " + "(multiple roots given)")) + root = repo[roots.first()] + if root.phase() <= phases.public: + raise error.Abort(_("cannot fold public revisions")) + heads = repo.revs('heads(%ld)', revs) + if len(heads) > 1: + raise error.Abort(_("cannot fold non-linear revisions " + "(multiple heads given)")) + head = repo[heads.first()] + return root, head + +def deletebookmark(repo, repomarks, bookmarks): + wlock = lock = tr = None + try: + wlock = repo.wlock() + lock = repo.lock() + tr = repo.transaction('prune') + bmchanges = [] + for bookmark in bookmarks: + bmchanges.append((bookmark, None)) + compat.bookmarkapplychanges(repo, tr, bmchanges) + tr.close() + for bookmark in sorted(bookmarks): + repo.ui.write(_("bookmark '%s' deleted\n") % bookmark) + finally: + lockmod.release(tr, lock, wlock) + +def presplitupdate(repo, ui, prev, ctx): + """prepare the working directory for a split (for topic hooking) + """ + hg.update(repo, prev) + commands.revert(ui, repo, rev=ctx.rev(), all=True) + +def reachablefrombookmark(repo, revs, bookmarks): + """filter revisions and bookmarks reachable from the given bookmark + yoinked from mq.py + """ + repomarks = repo._bookmarks + if not bookmarks.issubset(repomarks): + raise error.Abort(_("bookmark '%s' not found") % + ','.join(sorted(bookmarks - set(repomarks.keys())))) + + # If the requested bookmark is not the only one pointing to a + # a revision we have to only delete the bookmark and not strip + # anything. revsets cannot detect that case. + nodetobookmarks = {} + for mark, bnode in repomarks.iteritems(): + nodetobookmarks.setdefault(bnode, []).append(mark) + for marks in nodetobookmarks.values(): + if bookmarks.issuperset(marks): + rsrevs = repair.stripbmrevset(repo, marks[0]) + revs = set(revs) + revs.update(set(rsrevs)) + revs = sorted(revs) + return repomarks, revs + +def rewrite(repo, old, updates, head, newbases, commitopts): + """Return (nodeid, created) where nodeid is the identifier of the + changeset generated by the rewrite process, and created is True if + nodeid was actually created. If created is False, nodeid + references a changeset existing before the rewrite call. + """ + wlock = lock = tr = None + try: + wlock = repo.wlock() + lock = repo.lock() + tr = repo.transaction('rewrite') + if len(old.parents()) > 1: # XXX remove this unnecessary limitation. + raise error.Abort(_('cannot amend merge changesets')) + base = old.p1() + updatebookmarks = bookmarksupdater(repo, old.node(), tr) + + # commit a new version of the old changeset, including the update + # collect all files which might be affected + files = set(old.files()) + for u in updates: + files.update(u.files()) + + # Recompute copies (avoid recording a -> b -> a) + copied = copies.pathcopies(base, head) + + # prune files which were reverted by the updates + def samefile(f): + if f in head.manifest(): + a = head.filectx(f) + if f in base.manifest(): + b = base.filectx(f) + return (a.data() == b.data() + and a.flags() == b.flags()) + else: + return False + else: + return f not in base.manifest() + files = [f for f in files if not samefile(f)] + # commit version of these files as defined by head + headmf = head.manifest() + + def filectxfn(repo, ctx, path): + if path in headmf: + fctx = head[path] + flags = fctx.flags() + mctx = context.memfilectx(repo, fctx.path(), fctx.data(), + islink='l' in flags, + isexec='x' in flags, + copied=copied.get(path)) + return mctx + return None + + message = cmdutil.logmessage(repo.ui, commitopts) + if not message: + message = old.description() + + user = commitopts.get('user') or old.user() + # TODO: In case not date is given, we should take the old commit date + # if we are working one one changeset or mimic the fold behavior about + # date + date = commitopts.get('date') or None + extra = dict(commitopts.get('extra', old.extra())) + extra['branch'] = head.branch() + + new = context.memctx(repo, + parents=newbases, + text=message, + files=files, + filectxfn=filectxfn, + user=user, + date=date, + extra=extra) + + if commitopts.get('edit'): + new._text = cmdutil.commitforceeditor(repo, new, []) + revcount = len(repo) + newid = repo.commitctx(new) + new = repo[newid] + created = len(repo) != revcount + updatebookmarks(newid) + + tr.close() + return newid, created + finally: + lockmod.release(tr, lock, wlock)
--- a/hgext3rd/evolve/utility.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/evolve/utility.py Tue Jul 25 04:01:10 2017 +0200 @@ -5,6 +5,8 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n" + def obsexcmsg(ui, message, important=False): verbose = ui.configbool('experimental', 'verbose-obsolescence-exchange', False)
--- a/hgext3rd/topic/__init__.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/topic/__init__.py Tue Jul 25 04:01:10 2017 +0200 @@ -53,6 +53,7 @@ from __future__ import absolute_import import re +import time from mercurial.i18n import _ from mercurial import ( @@ -68,9 +69,11 @@ namespaces, node, obsolete, + obsutil, patch, phases, registrar, + templatefilters, util, ) @@ -98,11 +101,13 @@ 'topic.stack.index': 'yellow', 'topic.stack.index.base': 'none dim', 'topic.stack.desc.base': 'none dim', + 'topic.stack.shortnode.base': 'none dim', 'topic.stack.state.base': 'dim', 'topic.stack.state.clean': 'green', 'topic.stack.index.current': 'cyan', # random pick 'topic.stack.state.current': 'cyan bold', # random pick 'topic.stack.desc.current': 'cyan', # random pick + 'topic.stack.shortnode.current': 'cyan', # random pick 'topic.stack.state.unstable': 'red', 'topic.stack.summary.behindcount': 'cyan', 'topic.stack.summary.behinderror': 'red', @@ -113,13 +118,29 @@ 'topic.active': 'green', } -testedwith = '4.0.2 4.1.3 4.2' +version = '0.2.0.dev' +testedwith = '4.0.2 4.1.3 4.2.1' +minimumhgversion = '4.0' +buglink = 'https://bz.mercurial-scm.org/' def _contexttopic(self, force=False): if not (force or self.mutable()): return '' return self.extra().get(constants.extrakey, '') context.basectx.topic = _contexttopic +def _contexttopicidx(self): + topic = self.topic() + if not topic: + # XXX we might want to include t0 here, + # however t0 is related to 'currenttopic' which has no place here. + return None + revlist = stack.getstack(self._repo, topic=topic) + try: + return revlist.index(self.rev()) + except IndexError: + # Lets move to the last ctx of the current topic + return None +context.basectx.topicidx = _contexttopicidx topicrev = re.compile(r'^t\d+$') branchrev = re.compile(r'^b\d+$') @@ -141,10 +162,14 @@ if revs is not None: try: - r = revs[idx - 1] + r = revs[idx] except IndexError: msg = _('cannot resolve "%s": %s "%s" has only %d changesets') - raise error.Abort(msg % (name, ttype, tname, len(revs))) + raise error.Abort(msg % (name, ttype, tname, len(revs) - 1)) + # b0 or t0 can be None + if r == -1 and idx == 0: + msg = _('the %s "%s" has no %s') + raise error.Abort(msg % (ttype, tname, name)) return [repo[r].node()] if name not in repo.topics: return [] @@ -173,10 +198,16 @@ extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap) extensions.wrapfunction(merge, 'update', mergeupdatewrap) + # We need to check whether t0 or b0 is passed to override the default update + # behaviour of changing topic and I can't find a better way + # to do that as scmutil.revsingle returns the rev number and hence we can't + # plug into logic for this into mergemod.update(). + extensions.wrapcommand(commands.table, 'update', checkt0) try: evolve = extensions.find('evolve') - extensions.wrapfunction(evolve, "presplitupdate", presplitupdatetopic) + extensions.wrapfunction(evolve.rewriteutil, "presplitupdate", + presplitupdatetopic) except (KeyError, AttributeError): pass @@ -280,9 +311,30 @@ ('', 'clear', False, 'clear active topic if any'), ('r', 'rev', '', 'revset of existing revisions', _('REV')), ('l', 'list', False, 'show the stack of changeset in the topic'), + ('', 'age', False, 'show when you last touched the topics') ] + commands.formatteropts) def topics(ui, repo, topic='', clear=False, rev=None, list=False, **opts): - """View current topic, set current topic, or see all topics. + """View current topic, set current topic, change topic for a set of revisions, or see all topics. + + Clear topic on existing topiced revisions: + `hg topic --rev <related revset> --clear` + + Change topic on some revisions: + `hg topic <newtopicname> --rev <related revset>` + + Clear current topic: + `hg topic --clear` + + Set current topic: + `hg topic <topicname>` + + List of topics: + `hg topics` + + List of topics with their last touched time sorted according to it: + `hg topic --age` + + The active topic (if any) will be prepended with a "*". The --verbose version of this command display various information on the state of each topic.""" if list: @@ -317,7 +369,10 @@ def cmdstack(ui, repo, topic='', **opts): """list all changesets in a topic and other information - List the current topic by default.""" + List the current topic by default. + + The --verbose version shows short nodes for the commits also. + """ if not topic: topic = None branch = None @@ -408,6 +463,11 @@ def _listtopics(ui, repo, opts): fm = ui.formatter('topics', opts) + showlast = opts.get('age') + if showlast: + # we have a new function as plugging logic into existing function is + # pretty much difficult + return _showlasttouched(repo, fm, opts) activetopic = repo.currenttopic namemask = '%s' if repo.topics and ui.verbose: @@ -460,6 +520,76 @@ fm.plain('\n') fm.end() +def _showlasttouched(repo, fm, opts): + topics = repo.topics + timedict = _getlasttouched(repo, topics) + times = timedict.keys() + times.sort() + if topics: + maxwidth = max(len(t) for t in topics) + namemask = '%%-%is' % maxwidth + activetopic = repo.currenttopic + for timevalue in times: + curtopics = timedict[timevalue][1] + for topic in curtopics: + fm.startitem() + marker = ' ' + label = 'topic' + active = (topic == activetopic) + if active: + marker = '*' + label = 'topic.active' + fm.plain(' %s ' % marker, label=label) + fm.write('topic', namemask, topic, label=label) + fm.data(active=active) + fm.plain(' (') + if timevalue == -1: + timestr = 'not yet touched' + else: + timestr = templatefilters.age(timedict[timevalue][0]) + fm.write('lasttouched', '%s', timestr, label='topic.list.time') + fm.plain(')') + fm.plain('\n') + fm.end() + +def _getlasttouched(repo, topics): + """ + Calculates the last time a topic was used. Returns a dictionary of seconds + passed from current time for a topic as keys and topic name as values. + """ + topicstime = {} + curtime = time.time() + for t in topics: + maxtime = (0, 0) + trevs = repo.revs("topic(%s)", t) + # Need to check for the time of all changesets in the topic, whether + # they are obsolete of non-heads + # XXX: can we just rely on the max rev number for this + for revs in trevs: + rt = repo[revs].date() + if rt[0] > maxtime[0]: + # Can store the rev to gather more info + # latesthead = revs + maxtime = rt + # looking on the markers also to get more information and accurate + # last touch time. + obsmarkers = obsutil.getmarkers(repo, [repo[revs].node()]) + for marker in obsmarkers: + rt = marker.date() + if rt[0] > maxtime[0]: + maxtime = rt + # is the topic still yet untouched + if not trevs: + secspassed = -1 + else: + secspassed = (curtime - maxtime[0]) + try: + topicstime[secspassed][1].append(t) + except KeyError: + topicstime[secspassed] = (maxtime, [t]) + + return topicstime + def summaryhook(ui, repo): t = repo.currenttopic if not t: @@ -469,10 +599,16 @@ def commitwrap(orig, ui, repo, *args, **opts): with repo.wlock(): + enforcetopic = ui.configbool('experimental', 'enforce-topic') if opts.get('topic'): t = opts['topic'] with repo.vfs.open('topic', 'w') as f: f.write(t) + elif not repo.currenttopic and enforcetopic: + msg = _("no active topic") + hint = _("set a current topic or use '--config " + + "experimental.enforce-topic=no' to commit without a topic") + raise error.Abort(msg, hint=hint) return orig(ui, repo, *args, **opts) def committextwrap(orig, repo, ctx, subs, extramsg): @@ -488,6 +624,7 @@ partial = not (matcher is None or matcher.always()) wlock = repo.wlock() isrebase = False + ist0 = False try: ret = orig(repo, node, branchmerge, force, *args, **kwargs) # The mergeupdatewrap function makes the destination's topic as the @@ -497,7 +634,9 @@ # running. if repo.ui.hasconfig('experimental', 'topicrebase'): isrebase = True - if (not partial and not branchmerge) or isrebase: + if repo.ui.configbool('_internal', 'keep-topic'): + ist0 = True + if ((not partial and not branchmerge) or isrebase) and not ist0: ot = repo.currenttopic t = '' pctx = repo[node] @@ -507,10 +646,25 @@ f.write(t) if t and t != ot: repo.ui.status(_("switching to topic %s\n") % t) + elif ist0: + repo.ui.status(_("preserving the current topic '%s'\n") % + repo.currenttopic) return ret finally: wlock.release() +def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs): + + thezeros = set(['t0', 'b0']) + backup = repo.ui.backupconfig('_internal', 'keep-topic') + try: + if node in thezeros or rev in thezeros: + repo.ui.setconfig('_internal', 'keep-topic', 'yes', + source='topic-extension') + return orig(ui, repo, node, rev, *args, **kwargs) + finally: + repo.ui.restoreconfig(backup) + def _fixrebase(loaded): if not loaded: return
--- a/hgext3rd/topic/revset.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/topic/revset.py Tue Jul 25 04:01:10 2017 +0200 @@ -78,7 +78,7 @@ topic = repo.currenttopic if not topic: branch = repo[None].branch() - return revset.baseset(stack.getstack(repo, branch=branch, topic=topic)) & subset + return revset.baseset(stack.getstack(repo, branch=branch, topic=topic)[1:]) & subset def modsetup(ui):
--- a/hgext3rd/topic/stack.py Sun Jul 02 17:28:38 2017 +0200 +++ b/hgext3rd/topic/stack.py Tue Jul 25 04:01:10 2017 +0200 @@ -10,6 +10,8 @@ ) from .evolvebits import builddependencies, _orderrevs, _singlesuccessor +short = node.short + def getstack(repo, branch=None, topic=None): # XXX need sorting if topic is not None and branch is not None: @@ -20,7 +22,13 @@ trevs = repo.revs("branch(%s) - public() - obsolete() - topic()", branch) else: raise error.ProgrammingError('neither branch and topic specified (not defined yet)') - return _orderrevs(repo, trevs) + revs = _orderrevs(repo, trevs) + if revs: + pt1 = repo[revs[0]].p1() + if pt1.obsolete(): + pt1 = repo[_singlesuccessor(repo, pt1)] + revs.insert(0, pt1.rev()) + return revs def labelsgen(prefix, labelssuffix): """ Takes a label prefix and a list of suffixes. Returns a string of the prefix @@ -84,8 +92,16 @@ fm.plain('%d behind' % data['behindcount'], label='topic.stack.summary.behindcount') fm.plain('\n') - for idx, r in enumerate(getstack(repo, branch=branch, topic=topic), 1): + for idx, r in enumerate(getstack(repo, branch=branch, topic=topic), 0): ctx = repo[r] + # special case for t0, b0 as it's hard to plugin into rest of the logic + if idx == 0: + # t0, b0 can be None + if r == -1: + continue + entries.append((idx, False, ctx)) + prev = ctx.rev() + continue p1 = ctx.p1() if p1.obsolete(): p1 = repo[_singlesuccessor(repo, p1)] @@ -125,9 +141,14 @@ if idx is None: fm.plain(' ') + if ui.verbose: + fm.plain(' ') else: fm.write('topic.stack.index', '%s%%d' % prefix, idx, label='topic.stack.index ' + labelsgen('topic.stack.index.%s', states)) + if ui.verbose: + fm.write('topic.stack.shortnode', '(%s)', short(ctx.node()), + label='topic.stack.shortnode ' + labelsgen('topic.stack.shortnode.%s', states)) fm.write('topic.stack.state.symbol', '%s', symbol, label='topic.stack.state ' + labelsgen('topic.stack.state.%s', states)) fm.plain(' ') @@ -148,7 +169,7 @@ :behindcount: number of changeset on rebase destination """ data = {} - revs = getstack(repo, branch, topic) + revs = getstack(repo, branch, topic)[1:] data['changesetcount'] = len(revs) data['troubledcount'] = len([r for r in revs if repo[r].troubled()]) deps, rdeps = builddependencies(repo, revs)
--- a/tests/test-amend.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-amend.t Tue Jul 25 04:01:10 2017 +0200 @@ -1,11 +1,10 @@ $ cat >> $HGRCPATH <<EOF > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ glog() { - > hg glog --template '{rev}@{branch}({phase}) {desc|firstline}\n' "$@" + > hg log -G --template '{rev}@{branch}({phase}) {desc|firstline}\n' "$@" > } $ hg init repo --traceback @@ -132,11 +131,9 @@ If you don't specify -m, the parent's message will be reused. - Behind the scenes, Mercurial first commits the update as a regular child - of the current parent. Then it creates a new commit on the parent's - parents with the updated contents. Then it changes the working copy parent - to this new combined changeset. Finally, the old changeset and its update - are hidden from 'hg log' (unless you use --hidden with log). + If --extra is specified, the behavior of 'hg amend' is reversed: Changes + to selected files in the checked out revision appear again as uncommitted + changed in the working directory. Returns 0 on success, 1 if nothing changed. @@ -144,7 +141,9 @@ -A --addremove mark new/missing files as added/removed before committing + -a --all match all files -e --edit invoke editor on commit messages + --extract extract changes from the commit to the working copy --close-branch mark a branch as closed, hiding it from the branch list -s --secret use the secret phase for committing
--- a/tests/test-corrupt.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-corrupt.t Tue Jul 25 04:01:10 2017 +0200 @@ -13,7 +13,6 @@ > git = 1 > unified = 0 > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() { @@ -30,7 +29,7 @@ $ mkcommit A $ mkcommit B $ mkcommit C - $ hg glog + $ hg log -G @ changeset: 2:829b19580856 | tag: tip | user: test @@ -67,7 +66,7 @@ $ hg up -q .^^ $ hg revert -r tip -a -q $ hg ci -m 'coin' -q - $ hg glog + $ hg log -G @ changeset: 5:8313a6afebbb | tag: tip | parent: 2:829b19580856
--- a/tests/test-divergent.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-divergent.t Tue Jul 25 04:01:10 2017 +0200 @@ -15,7 +15,6 @@ > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline} [{troubles}]\n > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() {
--- a/tests/test-evolve-order.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-evolve-order.t Tue Jul 25 04:01:10 2017 +0200 @@ -16,7 +16,6 @@ > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() {
--- a/tests/test-evolve-split.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-evolve-split.t Tue Jul 25 04:01:10 2017 +0200 @@ -15,7 +15,6 @@ > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() {
--- a/tests/test-evolve.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-evolve.t Tue Jul 25 04:01:10 2017 +0200 @@ -14,7 +14,6 @@ > git = 1 > unified = 0 > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() { @@ -31,7 +30,7 @@ > } $ glog() { - > hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" + > hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" > } $ shaof() { @@ -81,6 +80,39 @@ Obsolescence markers will be exchanged between repositories that explicitly assert support for the obsolescence feature (this can currently only be done via an extension). + + Instability ========== + + (note: the vocabulary is in the process of being updated) + + Rewriting changesets might introduce instability (currently 'trouble'). + + There are two main kinds of instability: orphaning and diverging. + + Orphans are changesets left behind when their ancestors are rewritten, + (currently: 'unstable'). Divergence has two variants: + + * Content-divergence occurs when independent rewrites of the same changesets + lead to different results. (currently: 'divergent') + + * Phase-divergence occurs when the old (obsolete) version of a changeset + becomes public. (currently: 'bumped') + + If it possible to prevent local creation of orphans by using the following + config: + + [experimental] + evolution=createmarkers,allnewcommands,exchange + + You can also enable that option explicitly: + + [experimental] + evolution=createmarkers,allnewcommands,allowunstable,exchange + + or simply: + + [experimental] + evolution=all various init @@ -114,7 +146,7 @@ $ hg log -r 1 --template '{rev} {phase} {obsolete}\n' 1 public $ hg prune 1 - abort: cannot prune immutable changeset: 7c3bad9141dc + abort: cannot touch public changesets: 7c3bad9141dc (see 'hg help phases' for details) [255] $ hg log -r 1 --template '{rev} {phase} {obsolete}\n' @@ -372,7 +404,7 @@ all solving bumped troubled - $ hg glog + $ hg log -G @ 8 feature-B: another feature that rox - test | | o 7 : another feature (child of ba0ec09b1bab) - test @@ -387,7 +419,7 @@ computing new diff committed as 6707c5e1c49d working directory is now at 6707c5e1c49d - $ hg glog + $ hg log -G @ 9 feature-B: bumped update to 99833d22b0c6: - test | o 7 : another feature (child of ba0ec09b1bab) - test @@ -446,7 +478,7 @@ atop:[14] dansk 2! merging main-file-1 working directory is now at 68557e4f0048 - $ hg glog + $ hg log -G @ 15 : dansk 3! - test | o 14 : dansk 2! - test @@ -683,48 +715,13 @@ Test fold +(most of the testing have been moved to test-fold $ rm *.orig - $ hg fold - abort: no revisions specified - [255] - $ hg fold --from - abort: no revisions specified - [255] - $ hg fold . - abort: must specify either --from or --exact - [255] - $ hg fold --from . --exact - abort: cannot use both --from and --exact - [255] - $ hg fold --from . - single revision specified, nothing to fold - [1] - $ hg fold 0::10 --rev 1 --exact - abort: cannot fold non-linear revisions (multiple heads given) - [255] - $ hg fold -r 4 -r 6 --exact - abort: cannot fold non-linear revisions (multiple roots given) - [255] - $ hg fold --from 10 1 - abort: cannot fold non-linear revisions - (given revisions are unrelated to parent of working directory) - [255] - $ hg fold --exact -r "4 and not 4" - abort: specified revisions evaluate to an empty set - (use different revision arguments) - [255] $ hg phase --public 0 - $ hg fold --from -r 0 - abort: cannot fold public revisions - [255] $ hg fold --from -r 5 3 changesets folded 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg fold --from 6 # want to run hg fold 6 - abort: hidden revision '6'! - (use --hidden to access hidden revisions) - [255] $ hg log -r 11 --template '{desc}\n' add 3 @@ -1005,7 +1002,7 @@ $ hg commit -m "add new file bumped" -o 11 $ hg phase --public --hidden 11 1 new bumped changesets - $ hg glog + $ hg log -G @ 12 : add new file bumped - test | | o 11 : a2 - test @@ -1024,7 +1021,7 @@ Now we have a bumped and an unstable changeset, we solve the bumped first normally the unstable changeset would be solve first - $ hg glog + $ hg log -G @ 12 : add new file bumped - test | | o 11 : a2 - test @@ -1060,7 +1057,7 @@ $ hg up 14 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ printf "newline\nnewline\n" >> a - $ hg glog + $ hg log -G o 16 : add gh - test | | o 15 : add gg - test @@ -1077,7 +1074,7 @@ $ hg amend 2 new unstable changesets - $ hg glog + $ hg log -G @ 18 : a3 - test | | o 16 : add gh - test @@ -1110,7 +1107,7 @@ move:[16] add gh atop:[18] a3 working directory is now at e02107f98737 - $ hg glog + $ hg log -G @ 20 : add gh - test | | o 19 : add gg - test @@ -1270,7 +1267,8 @@ $ hg up 8dc373be86d9^ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg uncommit --all - abort: cannot uncommit in the middle of a stack + abort: uncommit will orphan 4 descendants + (see 'hg help evolution.instability') [255] $ hg up 8dc373be86d9 2 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -1328,12 +1326,12 @@ $ mkcommit c5_ created new head $ hg prune '26 + 27' - abort: cannot prune in the middle of a stack - (new unstable changesets are not allowed) + abort: touch will orphan 1 descendants + (see 'hg help evolution.instability') [255] $ hg prune '19::28' - abort: cannot prune in the middle of a stack - (new unstable changesets are not allowed) + abort: touch will orphan 1 descendants + (see 'hg help evolution.instability') [255] $ hg prune '26::' 3 changesets pruned @@ -1349,6 +1347,9 @@ ~ Check that fold respects the allowunstable option + +(most of this has been moved to test-fold.t) + $ hg up edc3c9de504e 0 files updated, 0 files merged, 2 files removed, 0 files unresolved $ mkcommit unstableifparentisfolded @@ -1366,14 +1367,6 @@ | ~ - $ hg fold --exact "19 + 18" - abort: cannot fold chain not ending with a head or with branching - (new unstable changesets are not allowed) - [255] - $ hg fold --exact "18::29" - abort: cannot fold chain not ending with a head or with branching - (new unstable changesets are not allowed) - [255] $ hg fold --exact "19::" 2 changesets folded
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-fold.t Tue Jul 25 04:01:10 2017 +0200 @@ -0,0 +1,225 @@ + $ . $TESTDIR/testlib/common.sh + +setup + + $ cat >> $HGRCPATH <<EOF + > [defaults] + > fold=-d "0 0" + > [extensions] + > evolve= + > [ui] + > logtemplate = '{rev} - {node|short} {desc|firstline} [{author}] ({phase})\n' + > EOF + + $ hg init fold-tests + $ cd fold-tests/ + $ hg debugbuilddag .+3:branchpoint+4*branchpoint+2 + $ hg up 'desc("r7")' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -G + o 10 - a8407f9a3dc1 r10 [debugbuilddag] (draft) + | + o 9 - 529dfc5bb875 r9 [debugbuilddag] (draft) + | + o 8 - abf57d94268b r8 [debugbuilddag] (draft) + | + | @ 7 - 4de32a90b66c r7 [debugbuilddag] (draft) + | | + | o 6 - f69452c5b1af r6 [debugbuilddag] (draft) + | | + | o 5 - c8d03c1b5e94 r5 [debugbuilddag] (draft) + | | + | o 4 - bebd167eb94d r4 [debugbuilddag] (draft) + |/ + o 3 - 2dc09a01254d r3 [debugbuilddag] (draft) + | + o 2 - 01241442b3c2 r2 [debugbuilddag] (draft) + | + o 1 - 66f7d451a68b r1 [debugbuilddag] (draft) + | + o 0 - 1ea73414a91b r0 [debugbuilddag] (draft) + + +Test various error case + + $ hg fold --exact null:: + abort: cannot fold the null revision + (no changeset checked out) + [255] + $ hg fold + abort: no revisions specified + [255] + $ hg fold --from + abort: no revisions specified + [255] + $ hg fold . + abort: must specify either --from or --exact + [255] + $ hg fold --from . --exact + abort: cannot use both --from and --exact + [255] + $ hg fold --from . + single revision specified, nothing to fold + [1] + $ hg fold '0::(7+10)' --exact + abort: cannot fold non-linear revisions (multiple heads given) + [255] + $ hg fold -r 4 -r 6 --exact + abort: cannot fold non-linear revisions (multiple roots given) + [255] + $ hg fold --from 10 1 + abort: cannot fold non-linear revisions + (given revisions are unrelated to parent of working directory) + [255] + $ hg fold --exact -r "4 and not 4" + abort: specified revisions evaluate to an empty set + (use different revision arguments) + [255] + $ hg phase --public 0 + $ hg fold --from -r 0 + abort: cannot fold public changesets: 1ea73414a91b + (see 'hg help phases' for details) + [255] + +Test actual folding + + $ hg fold --from -r 'desc("r5")' + 3 changesets folded + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + +(test inherited from test-evolve.t) + + $ hg fold --from 6 # want to run hg fold 6 + abort: hidden revision '6'! + (use --hidden to access hidden revisions) + [255] + + $ hg log -G + @ 11 - 198b5c405d01 r5 [debugbuilddag] (draft) + | + | o 10 - a8407f9a3dc1 r10 [debugbuilddag] (draft) + | | + | o 9 - 529dfc5bb875 r9 [debugbuilddag] (draft) + | | + | o 8 - abf57d94268b r8 [debugbuilddag] (draft) + | | + o | 4 - bebd167eb94d r4 [debugbuilddag] (draft) + |/ + o 3 - 2dc09a01254d r3 [debugbuilddag] (draft) + | + o 2 - 01241442b3c2 r2 [debugbuilddag] (draft) + | + o 1 - 66f7d451a68b r1 [debugbuilddag] (draft) + | + o 0 - 1ea73414a91b r0 [debugbuilddag] (public) + + +test fold --exact + + $ hg fold --exact 'desc("r8") + desc("r10")' + abort: cannot fold non-linear revisions (multiple roots given) + [255] + $ hg fold --exact 'desc("r8")::desc("r10")' + 3 changesets folded + $ hg log -G + o 12 - b568edbee6e0 r8 [debugbuilddag] (draft) + | + | @ 11 - 198b5c405d01 r5 [debugbuilddag] (draft) + | | + | o 4 - bebd167eb94d r4 [debugbuilddag] (draft) + |/ + o 3 - 2dc09a01254d r3 [debugbuilddag] (draft) + | + o 2 - 01241442b3c2 r2 [debugbuilddag] (draft) + | + o 1 - 66f7d451a68b r1 [debugbuilddag] (draft) + | + o 0 - 1ea73414a91b r0 [debugbuilddag] (public) + + +Test allow unstable + + $ echo a > a + $ hg add a + $ hg commit '-m r11' + $ hg up '.^' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg log -G + o 13 - 14d0e0da8e91 r11 [test] (draft) + | + | o 12 - b568edbee6e0 r8 [debugbuilddag] (draft) + | | + @ | 11 - 198b5c405d01 r5 [debugbuilddag] (draft) + | | + o | 4 - bebd167eb94d r4 [debugbuilddag] (draft) + |/ + o 3 - 2dc09a01254d r3 [debugbuilddag] (draft) + | + o 2 - 01241442b3c2 r2 [debugbuilddag] (draft) + | + o 1 - 66f7d451a68b r1 [debugbuilddag] (draft) + | + o 0 - 1ea73414a91b r0 [debugbuilddag] (public) + + + $ cat << EOF >> .hg/hgrc + > [experimental] + > evolution = createmarkers, allnewcommands + > EOF + $ hg fold --from 'desc("r4")' + abort: fold will orphan 1 descendants + (see 'hg help evolution.instability') + [255] + $ hg fold --from 'desc("r3")::desc("r11")' + abort: fold will orphan 1 descendants + (see 'hg help evolution.instability') + [255] + +test --user variant + + $ cat << EOF >> .hg/hgrc + > [experimental] + > evolution = createmarkers, allnewcommands + > EOF + $ cat << EOF >> .hg/hgrc + > [experimental] + > evolution = all + > EOF + + $ hg fold --exact 'desc("r5") + desc("r11")' --user 'Victor Rataxes <victor@rhino.savannah>' + 2 changesets folded + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -G + @ 14 - 29b470a33594 r5 [Victor Rataxes <victor@rhino.savannah>] (draft) + | + | o 12 - b568edbee6e0 r8 [debugbuilddag] (draft) + | | + o | 4 - bebd167eb94d r4 [debugbuilddag] (draft) + |/ + o 3 - 2dc09a01254d r3 [debugbuilddag] (draft) + | + o 2 - 01241442b3c2 r2 [debugbuilddag] (draft) + | + o 1 - 66f7d451a68b r1 [debugbuilddag] (draft) + | + o 0 - 1ea73414a91b r0 [debugbuilddag] (public) + + + $ hg fold --from 'desc("r4")' -U + 2 changesets folded + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -G + @ 15 - 91880abed0f2 r4 [test] (draft) + | + | o 12 - b568edbee6e0 r8 [debugbuilddag] (draft) + |/ + o 3 - 2dc09a01254d r3 [debugbuilddag] (draft) + | + o 2 - 01241442b3c2 r2 [debugbuilddag] (draft) + | + o 1 - 66f7d451a68b r1 [debugbuilddag] (draft) + | + o 0 - 1ea73414a91b r0 [debugbuilddag] (public) + + $ cd .. +
--- a/tests/test-metaedit.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-metaedit.t Tue Jul 25 04:01:10 2017 +0200 @@ -14,7 +14,6 @@ > git = 1 > unified = 0 > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() { @@ -31,7 +30,7 @@ > } $ glog() { - > hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" + > hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" > } $ shaof() { @@ -104,7 +103,8 @@ abort: revisions must be specified with --fold [255] $ hg metaedit -r 0 --fold - abort: cannot fold public revisions + abort: cannot fold public changesets: ea207398892e + (see 'hg help phases' for details) [255] $ hg metaedit 'desc(C) + desc(F)' --fold abort: cannot fold non-linear revisions (multiple roots given) @@ -118,8 +118,8 @@ (587528abfffe will become unstable and new unstable changes are not allowed) [255] $ hg metaedit 'desc(A)::desc(B)' --fold --config 'experimental.evolution=createmarkers, allnewcommands' - abort: cannot fold chain not ending with a head or with branching - (new unstable changesets are not allowed) + abort: fold will orphan 4 descendants + (see 'hg help evolution.instability') [255] $ hg metaedit --user foobar 0 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -209,7 +209,7 @@ $ hg diff -r "10" -r "11" --hidden 'fold' one commit - $ hg metaedit "desc(D2)" --fold --user foobar3 + $ HGUSER=foobar3 hg metaedit "desc(D2)" --fold -U --config 1 changesets folded $ hg log -r "tip" --template '{rev}: {author}\n' 13: foobar3
--- a/tests/test-obsolete-push.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-obsolete-push.t Tue Jul 25 04:01:10 2017 +0200 @@ -2,13 +2,12 @@ > [defaults] > amend=-d "0 0" > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ template='{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' $ glog() { - > hg glog --template "$template" "$@" + > hg log -G --template "$template" "$@" > } Test outgoing, common A is suspended, B unstable and C secret, remote
--- a/tests/test-oldconvert.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-oldconvert.t Tue Jul 25 04:01:10 2017 +0200 @@ -7,7 +7,6 @@ > [alias] > odiff=diff --rev 'limit(obsparents(.),1)' --rev . > [extensions] - > hgext.graphlog= > EOF $ mkcommit() { > echo "$1" > "$1" @@ -34,13 +33,13 @@ $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/legacy.py" >> $HGRCPATH - $ hg glog + $ hg log -G abort: old format of obsolete marker detected! run `hg debugconvertobsolete` once. [255] $ hg debugconvertobsolete --traceback 1 obsolete marker converted - $ hg glog + $ hg log -G @ changeset: 2:d67cd0334eee | tag: tip | parent: 0:1f0dee641bb7 @@ -101,7 +100,7 @@ > } > ] > EOF - $ hg glog + $ hg log -G abort: old format of obsolete marker detected! run `hg debugconvertobsolete` once. [255]
--- a/tests/test-prev-next.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-prev-next.t Tue Jul 25 04:01:10 2017 +0200 @@ -1,12 +1,12 @@ $ cat >> $HGRCPATH <<EOF > [extensions] - > hgext.graphlog= > color = > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH hg prev -B should move active bookmark - $ hg init + $ hg init test-repo + $ cd test-repo $ touch a $ hg add a $ hg commit -m 'added a' @@ -94,6 +94,15 @@ mark 2:4e26ef31f919 no-move 2:4e26ef31f919 +test prev on root + + $ hg up null + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg prev + already at repository root + [1] + $ hg up 1 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved Behavior with local modification -------------------------------- @@ -217,6 +226,8 @@ atop:[6] added b (3) working directory is now at 47ea25be8aea + $ cd .. + prev and next should lock properly against other commands $ hg init repo
--- a/tests/test-prune.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-prune.t Tue Jul 25 04:01:10 2017 +0200 @@ -87,7 +87,7 @@ cannot prune public changesets $ hg prune 0 - abort: cannot prune immutable changeset: 1f0dee641bb7 + abort: cannot touch public changesets: 1f0dee641bb7 (see 'hg help phases' for details) [255] $ hg debugobsolete
--- a/tests/test-sharing.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-sharing.t Tue Jul 25 04:01:10 2017 +0200 @@ -144,7 +144,8 @@ Now that the fix is public, we cannot amend it any more. $ hg amend -m 'fix bug 37' - abort: cannot amend public changesets + abort: cannot amend public changesets: de6151c48e1c + (see 'hg help phases' for details) [255] Figure SG05
--- a/tests/test-split.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-split.t Tue Jul 25 04:01:10 2017 +0200 @@ -1,6 +1,8 @@ test of the split command ----------------------- + $ . $TESTDIR/testlib/common.sh + $ cat >> $HGRCPATH <<EOF > [defaults] > amend=-d "0 0" @@ -18,9 +20,8 @@ > [ui] > interactive = true > [extensions] - > hgext.graphlog= + > evolve = > EOF - $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() { > echo "$1" > "$1" > hg add "$1" @@ -94,7 +95,7 @@ 1334a80b33c3f9873edab728fbbcf500eab61d2e d2fe56e71366c2c5376c89960c281395062c0619 0 (*) {'ef1': '8', 'user': 'test'} (glob) 06be89dfe2ae447383f30a2984933352757b6fb4 0 {1334a80b33c3f9873edab728fbbcf500eab61d2e} (*) {'ef1': '0', 'user': 'test'} (glob) d2fe56e71366c2c5376c89960c281395062c0619 2d8abdb827cdf71ca477ef6985d7ceb257c53c1b 033b3f5ae73db67c10de938fb6f26b949aaef172 0 (*) {'ef1': '13', 'user': 'test'} (glob) - $ hg glog + $ hg log -G @ changeset: 7:033b3f5ae73d | tag: tip | user: test @@ -130,10 +131,21 @@ $ hg split abort: uncommitted changes [255] + $ hg up "desc(_c)" -C + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Cannot split public changeset + + $ hg phase --rev 'desc("_a")' + 0: draft + $ hg phase --rev 'desc("_a")' --public + $ hg split --rev 'desc("_a")' + abort: cannot split public changesets: 135f39f4bd78 + (see 'hg help phases' for details) + [255] + $ hg phase --rev 'desc("_a")' --draft --force Split a revision specified with -r - $ hg up "desc(_c)" -C - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo "change to b" >> _b $ hg amend -m "_cprim" 2 new unstable changesets @@ -212,7 +224,7 @@ move:[11] split2 atop:[14] split1 working directory is now at d74c6715e706 - $ hg glog + $ hg log -G @ changeset: 15:d74c6715e706 | tag: tip | user: test @@ -255,7 +267,7 @@ $ hg book bookA 17:7a6b35779b85 * bookB 17:7a6b35779b85 - $ hg glog -r "14::" + $ hg log -G -r "14::" @ changeset: 17:7a6b35779b85 | bookmark: bookA | bookmark: bookB @@ -270,7 +282,7 @@ ~ date: Thu Jan 01 00:00:00 1970 +0000 summary: split1 - $ hg split <<EOF + $ hg split --user victor <<EOF > y > y > n @@ -296,18 +308,18 @@ created new head Done splitting? [yN] y - $ hg glog -r "14::" - @ changeset: 19:60ea019b0f8d + $ hg log -G -r "14::" + @ changeset: 19:452a26648478 | bookmark: bookA | bookmark: bookB | tag: tip - | user: test + | user: victor | date: Thu Jan 01 00:00:00 1970 +0000 | summary: split6 | - o changeset: 18:2c7a2e53f23b + o changeset: 18:1315679b77dc | parent: 14:3f134f739075 - | user: test + | user: victor | date: Thu Jan 01 00:00:00 1970 +0000 | summary: split5 | @@ -317,8 +329,8 @@ summary: split1 $ hg book - bookA 19:60ea019b0f8d - * bookB 19:60ea019b0f8d + bookA 19:452a26648478 + * bookB 19:452a26648478 Lastest revision is selected if multiple are given to -r $ hg split -r "desc(_a)::" @@ -337,7 +349,8 @@ > evolutioncommands=split > EOF $ hg split -r "desc(split3)" - abort: cannot split commit: ead2066d1dbf not a head + abort: split will orphan 4 descendants + (see 'hg help evolution.instability') [255] Changing evolution level to createmarkers @@ -385,84 +398,3 @@ $ hg commit -m "empty" $ hg split 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - -Check that split keeps the right topic - - $ hg up -r tip - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - -Add topic to the hgrc - - $ echo "[extensions]" >> $HGRCPATH - $ echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic/" >> $HGRCPATH - $ hg topic mytopic - $ echo babar > babar - $ echo celeste > celeste - $ hg add babar celeste - $ hg commit -m "Works on mytopic" babar celeste - $ hg summary - parent: 21:615c369f47f0 tip - Works on mytopic - branch: new-branch - commit: 2 unknown (clean) - update: (current) - phases: 9 draft - topic: mytopic - -Split it - - $ hg split << EOF - > Y - > Y - > N - > Y - > Y - > Y - > EOF - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - adding babar - adding celeste - diff --git a/babar b/babar - new file mode 100644 - examine changes to 'babar'? [Ynesfdaq?] Y - - @@ -0,0 +1,1 @@ - +babar - record change 1/2 to 'babar'? [Ynesfdaq?] Y - - diff --git a/celeste b/celeste - new file mode 100644 - examine changes to 'celeste'? [Ynesfdaq?] N - - Done splitting? [yN] Y - diff --git a/celeste b/celeste - new file mode 100644 - examine changes to 'celeste'? [Ynesfdaq?] Y - - @@ -0,0 +1,1 @@ - +celeste - record this change to 'celeste'? [Ynesfdaq?] Y - - no more change to split - -Check that the topic is still here - - $ hg log -r "tip~1::" - changeset: 22:f879ab83f991 - branch: new-branch - topic: mytopic - parent: 20:89e64a2c68b3 - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: split7 - - changeset: 23:db75dbc1a3a6 - branch: new-branch - tag: tip - topic: mytopic - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: split8 - - $ hg topic - * mytopic
--- a/tests/test-stabilize-order.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-stabilize-order.t Tue Jul 25 04:01:10 2017 +0200 @@ -2,12 +2,11 @@ > [defaults] > amend=-d "0 0" > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ glog() { - > hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" + > hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" > } $ hg init repo
--- a/tests/test-stabilize-result.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-stabilize-result.t Tue Jul 25 04:01:10 2017 +0200 @@ -3,12 +3,11 @@ > amend=-d "0 0" > [extensions] > hgext.rebase= - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ glog() { - > hg glog --template \ + > hg log -G --template \ > '{rev}:{node|short}@{branch}({phase}) bk:[{bookmarks}] {desc|firstline}\n' "$@" > }
--- a/tests/test-topic-push-concurrent-on.t Sun Jul 02 17:28:38 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,417 +0,0 @@ -# same as test-topic-push but with the concurrent push feature on - - $ . "$TESTDIR/testlib/topic_setup.sh" - - $ cat << EOF >> $HGRCPATH - > [ui] - > logtemplate = {rev} {branch} {get(namespaces, "topics")} {phase} {desc|firstline}\n - > ssh =python "$RUNTESTDIR/dummyssh" - > [server] - > concurrent-push-mode=check-related - > EOF - - $ hg init main - $ hg init draft - $ cat << EOF >> draft/.hg/hgrc - > [phases] - > publish=False - > EOF - $ hg clone main client - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cat << EOF >> client/.hg/hgrc - > [paths] - > draft=../draft - > EOF - - -Testing core behavior to make sure we did not break anything -============================================================ - -Pushing a first changeset - - $ cd client - $ echo aaa > aaa - $ hg add aaa - $ hg commit -m 'CA' - $ hg outgoing -G - comparing with $TESTTMP/main (glob) - searching for changes - @ 0 default draft CA - - $ hg push - pushing to $TESTTMP/main (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - -Pushing two heads - - $ echo aaa > bbb - $ hg add bbb - $ hg commit -m 'CB' - $ echo aaa > ccc - $ hg up 'desc(CA)' - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg add ccc - $ hg commit -m 'CC' - created new head - $ hg outgoing -G - comparing with $TESTTMP/main (glob) - searching for changes - @ 2 default draft CC - - o 1 default draft CB - - $ hg push - pushing to $TESTTMP/main (glob) - searching for changes - abort: push creates new remote head 9fe81b7f425d! - (merge or see "hg help push" for details about pushing new heads) - [255] - $ hg outgoing -r 'desc(CB)' -G - comparing with $TESTTMP/main (glob) - searching for changes - o 1 default draft CB - - $ hg push -r 'desc(CB)' - pushing to $TESTTMP/main (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - -Pushing a new branch - - $ hg branch mountain - marked working directory as branch mountain - (branches are permanent and global, did you want a bookmark?) - $ hg commit --amend - $ hg outgoing -G - comparing with $TESTTMP/main (glob) - searching for changes - @ 4 mountain draft CC - - $ hg push - pushing to $TESTTMP/main (glob) - searching for changes - abort: push creates new remote branches: mountain! - (use 'hg push --new-branch' to create new remote branches) - [255] - $ hg push --new-branch - pushing to $TESTTMP/main (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files (+1 heads) - 2 new obsolescence markers - -Including on non-publishing - - $ hg push --new-branch draft - pushing to $TESTTMP/draft (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 3 changes to 3 files (+1 heads) - 2 new obsolescence markers - -Testing topic behavior -====================== - -Local peer tests ----------------- - - $ hg up -r 'desc(CA)' - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg topic babar - $ echo aaa > ddd - $ hg add ddd - $ hg commit -m 'CD' - $ hg log -G # keep track of phase because I saw some strange bug during developement - @ 5 default babar draft CD - | - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -Pushing a new topic to a non publishing server should not be seen as a new head - - $ hg push draft - pushing to $TESTTMP/draft (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files (+1 heads) - $ hg log -G - @ 5 default babar draft CD - | - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -Pushing a new topic to a publishing server should be seen as a new head - - $ hg push - pushing to $TESTTMP/main (glob) - searching for changes - abort: push creates new remote head 67f579af159d! - (merge or see "hg help push" for details about pushing new heads) - [255] - $ hg log -G - @ 5 default babar draft CD - | - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -wireprotocol tests ------------------- - - $ hg up -r 'desc(CA)' - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg topic celeste - $ echo aaa > eee - $ hg add eee - $ hg commit -m 'CE' - $ hg log -G # keep track of phase because I saw some strange bug during developement - @ 6 default celeste draft CE - | - | o 5 default babar draft CD - |/ - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -Pushing a new topic to a non publishing server without topic -> new head - - $ cat << EOF >> ../draft/.hg/hgrc - > [extensions] - > topic=! - > EOF - $ hg push ssh://user@dummy/draft - pushing to ssh://user@dummy/draft - searching for changes - abort: push creates new remote head 84eaf32db6c3! - (merge or see "hg help push" for details about pushing new heads) - [255] - $ hg log -G - @ 6 default celeste draft CE - | - | o 5 default babar draft CD - |/ - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -Pushing a new topic to a non publishing server should not be seen as a new head - - $ printf "topic=" >> ../draft/.hg/hgrc - $ hg config extensions.topic >> ../draft/.hg/hgrc - $ hg push ssh://user@dummy/draft - pushing to ssh://user@dummy/draft - searching for changes - remote: adding changesets - remote: adding manifests - remote: adding file changes - remote: added 1 changesets with 1 changes to 1 files (+1 heads) - $ hg log -G - @ 6 default celeste draft CE - | - | o 5 default babar draft CD - |/ - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -Pushing a new topic to a publishing server should be seen as a new head - - $ hg push ssh://user@dummy/main - pushing to ssh://user@dummy/main - searching for changes - abort: push creates new remote head 67f579af159d! - (merge or see "hg help push" for details about pushing new heads) - [255] - $ hg log -G - @ 6 default celeste draft CE - | - | o 5 default babar draft CD - |/ - | o 4 mountain public CC - |/ - | o 1 default public CB - |/ - o 0 default public CA - - -Check that we reject multiple head on the same topic ----------------------------------------------------- - - $ hg up 'desc(CB)' - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg topic babar - $ echo aaa > fff - $ hg add fff - $ hg commit -m 'CF' - $ hg log -G - @ 7 default babar draft CF - | - | o 6 default celeste draft CE - | | - | | o 5 default babar draft CD - | |/ - | | o 4 mountain public CC - | |/ - o | 1 default public CB - |/ - o 0 default public CA - - - $ hg push draft - pushing to $TESTTMP/draft (glob) - searching for changes - abort: push creates new remote head f0bc62a661be on branch 'default:babar'! - (merge or see "hg help push" for details about pushing new heads) - [255] - -Multiple head on a branch merged in a topic changesets ------------------------------------------------------------------------- - - - $ hg up 'desc(CA)' - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ echo aaa > ggg - $ hg add ggg - $ hg commit -m 'CG' - created new head - $ hg up 'desc(CF)' - switching to topic babar - 2 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg merge 'desc(CG)' - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg commit -m 'CM' - $ hg log -G - @ 9 default babar draft CM - |\ - | o 8 default draft CG - | | - o | 7 default babar draft CF - | | - | | o 6 default celeste draft CE - | |/ - | | o 5 default babar draft CD - | |/ - | | o 4 mountain public CC - | |/ - o | 1 default public CB - |/ - o 0 default public CA - - -Reject when pushing to draft - - $ hg push draft -r . - pushing to $TESTTMP/draft (glob) - searching for changes - abort: push creates new remote head 4937c4cad39e! - (merge or see "hg help push" for details about pushing new heads) - [255] - - -Reject when pushing to publishing - - $ hg push -r . - pushing to $TESTTMP/main (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 2 changes to 2 files - - $ cd .. - -Test phase move -================================== - -setup, two repo knowns about two small topic branch - - $ hg init repoA - $ hg clone repoA repoB - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cat << EOF >> repoA/.hg/hgrc - > [phases] - > publish=False - > EOF - $ cat << EOF >> repoB/.hg/hgrc - > [phases] - > publish=False - > EOF - $ cd repoA - $ echo aaa > base - $ hg add base - $ hg commit -m 'CBASE' - $ echo aaa > aaa - $ hg add aaa - $ hg topic topicA - $ hg commit -m 'CA' - $ hg up 'desc(CBASE)' - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ echo aaa > bbb - $ hg add bbb - $ hg topic topicB - $ hg commit -m 'CB' - $ cd .. - $ hg push -R repoA repoB - pushing to repoB - searching for changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 3 changes to 3 files (+1 heads) - $ hg log -G -R repoA - @ 2 default topicB draft CB - | - | o 1 default topicA draft CA - |/ - o 0 default draft CBASE - - -We turn different topic to public on each side, - - $ hg -R repoA phase --public topicA - $ hg -R repoB phase --public topicB - -Pushing should complain because it create to heads on default - - $ hg push -R repoA repoB - pushing to repoB - searching for changes - no changes found - abort: push create a new head on branch "default" - [255]
--- a/tests/test-tutorial.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-tutorial.t Tue Jul 25 04:01:10 2017 +0200 @@ -461,6 +461,12 @@ -r --rev VALUE revert commit content to REV instead -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns + -m --message TEXT use text as commit message + -l --logfile FILE read commit message from file + -d --date DATE record the specified date as commit date + -u --user USER record the specified user as committer + -D --current-date record the current date as commit date + -U --current-user record the current user as committer (some details hidden, use --verbose to show complete help) @@ -496,6 +502,8 @@ -l --logfile FILE read commit message from file -d --date DATE record the specified date as commit date -u --user USER record the specified user as committer + -D --current-date record the current date as commit date + -U --current-user record the current user as committer (some details hidden, use --verbose to show complete help)
--- a/tests/test-uncommit.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-uncommit.t Tue Jul 25 04:01:10 2017 +0200 @@ -1,11 +1,10 @@ $ cat >> $HGRCPATH <<EOF > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ glog() { - > hg glog --template '{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' "$@" + > hg log -G --template '{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' "$@" > } $ hg init repo @@ -14,7 +13,8 @@ Cannot uncommit null changeset $ hg uncommit - abort: cannot rewrite immutable changeset + abort: cannot uncommit the null revision + (no changeset checked out) [255] Cannot uncommit public changeset @@ -23,7 +23,8 @@ $ hg ci -Am adda a $ hg phase --public . $ hg uncommit - abort: cannot rewrite immutable changeset + abort: cannot uncommit public changesets: 07f494440405 + (see 'hg help phases' for details) [255] $ hg phase --force --draft . @@ -358,7 +359,103 @@ Test uncommiting precursors - $ hg uncommit --hidden --rev 'precursors(.)' b + $ hg uncommit --hidden --rev 'precursors(.)' b --traceback $ hg cat b --rev . b b + +Test date, message and user update + + $ hg log -r . + changeset: 12:912ed871207c + branch: bar + tag: tip + parent: 7:4f1c269eab68 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: touncommit + + $ hg uncommit -m 'to-uncommit' d --user test2 --date '1337 0' + $ hg log -r . + changeset: 13:f1efd9ec508c + branch: bar + tag: tip + parent: 7:4f1c269eab68 + user: test2 + date: Thu Jan 01 00:22:17 1970 +0000 + summary: to-uncommit + + +test -U option + + $ hg uncommit -U b + $ hg log -r . + changeset: 14:288da4a95941 + branch: bar + tag: tip + parent: 7:4f1c269eab68 + user: test + date: Thu Jan 01 00:22:17 1970 +0000 + summary: to-uncommit + + +test the `hg amend --extract` entry point + + $ hg status --change . + M j + M o + A e + A ff + A h + A k + A l + R c + R f + R g + R m + R n + $ hg status + M d + A aa + R b + $ hg amend --extract j + $ hg status --change . + M o + A e + A ff + A h + A k + A l + R c + R f + R g + R m + R n + $ hg status + M d + M j + A aa + R b + +(with all) + + $ hg amend --extract --all + new changeset is empty + (use 'hg prune .' to remove it) + $ hg status --change . + $ hg status + M d + M j + M o + A aa + A e + A ff + A h + A k + A l + R b + R c + R f + R g + R m + R n
--- a/tests/test-unstable.t Sun Jul 02 17:28:38 2017 +0200 +++ b/tests/test-unstable.t Tue Jul 25 04:01:10 2017 +0200 @@ -13,7 +13,6 @@ > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n > [extensions] - > hgext.graphlog= > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH $ mkcommit() {