--- a/.hgtags Sun Jul 02 17:28:53 2017 +0200
+++ b/.hgtags Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/README Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/debian/changelog Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/evolve/__init__.py Tue Jul 25 04:02:16 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:02:16 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:53 2017 +0200
+++ b/hgext3rd/evolve/compat.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/evolve/hack/inhibit.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/evolve/metadata.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/evolve/obscache.py Tue Jul 25 04:02:16 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:02:16 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:53 2017 +0200
+++ b/hgext3rd/evolve/utility.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/topic/__init__.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/topic/revset.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/hgext3rd/topic/stack.py Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-amend.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-corrupt.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-divergent.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-evolve-order.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-evolve-split.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-evolve.t Tue Jul 25 04:02:16 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:02:16 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:53 2017 +0200
+++ b/tests/test-metaedit.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-obsolete-push.t Tue Jul 25 04:02:16 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}({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:53 2017 +0200
+++ b/tests/test-oldconvert.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-prev-next.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-prune.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-sharing.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-split.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-stabilize-order.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-stabilize-result.t Tue Jul 25 04:02:16 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:53 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:53 2017 +0200
+++ b/tests/test-tutorial.t Tue Jul 25 04:02:16 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:53 2017 +0200
+++ b/tests/test-uncommit.t Tue Jul 25 04:02:16 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}({obsolete}/{phase}) {desc|firstline}\n' "$@"
+ > hg log -G --template '{rev}:{node|short}@{branch}({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:53 2017 +0200
+++ b/tests/test-unstable.t Tue Jul 25 04:02:16 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() {