[PATCH] add support for marking changesets as dead

Martin Geisler mg at aragost.com
Mon Jan 31 04:10:16 CST 2011


# HG changeset patch
# User Henrik Stuart <hg at hstuart.dk>
# Date 1295774638 -3600
# Node ID 7bd977fdbca15da3c69ea7ef01df987b95475b11
# Parent  0d1dca7d2a041cb1cb6c6bd90608aa87068bde02
add support for marking changesets as dead

Marking a changeset as dead is, if it is a head, a way to delete a
branch of development in a safe, append-only manner. A dead changeset
"kills" all its ancestors that are not reachable from a non-dead head
and make them subject to revised propagation rules that are outlined
below:

  A dead head is propagated if and only if it kills a head.

To illustrate this, consider the repository:

  0 ---- 1 ---- 2 (dead)
   \
    ---- 3

Where the other repository has:

  0

After a pull/push, the other repository would just get changeset 3.
Had the other repository been:

  0 ---- 1

It would receive both changesets 2 and 3 after a pull/push since 2
kills the local non-dead head 1.

Had the base repository been:

  0 ---- 1 ---- 2 (dead)
   \      \
    ------ 3 ---- 4

Then no matter if the other repository was 0, or 0 ---- 1, then it
would receive changesets 3 and 4 as new heads, but not changeset 2 as
4 ressurrects changeset 1.

hg log and the graphlog extension are extended with knowledge of dead
heads, so if no revision set is specified, changesets that are dead
will not be displayed, otherwise it is up to the revision set to
indicate whether dead changesets should or should not be displayed
using the new "dead(optionalset)" revision set function.

Co-authors: Erik Zielke and Martin Geisler

diff --git a/hgext/bookmarks.py b/hgext/bookmarks.py
--- a/hgext/bookmarks.py
+++ b/hgext/bookmarks.py
@@ -309,7 +309,7 @@
             finally:
                 wlock.release()
 
-        def pull(self, remote, heads=None, force=False):
+        def pull(self, remote, heads=None, force=False, dead=False):
             result = super(bookmark_repo, self).pull(remote, heads, force)
 
             self.ui.debug("checking for updated bookmarks\n")
@@ -335,7 +335,7 @@
 
             return result
 
-        def push(self, remote, force=False, revs=None, newbranch=False):
+        def push(self, remote, force=False, revs=None, newbranch=False, dead=False):
             result = super(bookmark_repo, self).push(remote, force, revs,
                                                      newbranch)
 
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -2919,7 +2919,8 @@
             return super(mqrepo, self).commit(text, user, date, match, force,
                                               editor, extra)
 
-        def push(self, remote, force=False, revs=None, newbranch=False):
+        def push(self, remote, force=False, revs=None, newbranch=False,
+                dead=False):
             if self.mq.applied and not force:
                 haspatches = True
                 if revs:
@@ -2930,7 +2931,8 @@
                     haspatches = bool([n for n in revs if n in applied])
                 if haspatches:
                     raise util.Abort(_('source has mq patches applied'))
-            return super(mqrepo, self).push(remote, force, revs, newbranch)
+            return super(mqrepo, self).push(remote, force, revs, newbranch,
+                    dead)
 
         def _findtags(self):
             '''augment tags from base class with patch tags'''
diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -291,8 +291,10 @@
         repopath, bundlename = parentpath, path
     return bundlerepository(ui, repopath, bundlename)
 
-def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
-    tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
+def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False,
+        dead=False):
+    tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force,
+            dead=dead)
     common, incoming, rheads = tmp
     if not incoming:
         try:
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -497,7 +497,7 @@
     else:
         ui.write("%s\n" % repo.dirstate.branch())
 
-def branches(ui, repo, active=False, closed=False):
+def branches(ui, repo, active=False, closed=False, dead=False):
     """list repository named branches
 
     List the repository's named branches, indicating which ones are
@@ -517,7 +517,8 @@
     def testactive(tag, node):
         realhead = tag in activebranches
         open = node in repo.branchheads(tag, closed=False)
-        return realhead and open
+        dead = repo[node].extra().get('dead')
+        return realhead and open and not dead
     branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
                           for tag, node in repo.branchtags().items()],
                       reverse=True)
@@ -536,6 +537,10 @@
                         continue
                     label = 'branches.closed'
                     notice = _(' (closed)')
+                elif repo[hn].extra().get('dead'):
+                    if not dead:
+                        continue
+                    notice = _(' (dead)')
                 else:
                     label = 'branches.inactive'
                     notice = _(' (inactive)')
@@ -731,11 +736,11 @@
         raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
 
     r = hg.clone(hg.remoteui(ui, opts), source, dest,
-                 pull=opts.get('pull'),
+                 pull=opts.get('pull') or opts.get('no_dead'),
                  stream=opts.get('uncompressed'),
                  rev=opts.get('rev'),
                  update=opts.get('updaterev') or not opts.get('noupdate'),
-                 branch=opts.get('branch'))
+                 branch=opts.get('branch'), dead=not opts.get('no_dead'))
 
     return r is None
 
@@ -768,6 +773,10 @@
             # current branch, so it's sufficient to test branchheads
             raise util.Abort(_('can only close branch heads'))
         extra['close'] = 1
+    if opts.get('kill'):
+        if repo['.'].node() not in repo.branchheads():
+            raise util.Abort(_('can only kill branch heads'))
+        extra['dead'] = 1
     e = cmdutil.commiteditor
     if opts.get('force_editor'):
         e = cmdutil.commitforceeditor
@@ -1814,7 +1823,7 @@
         start = cmdutil.revsingle(repo, opts['rev'], None).node()
 
     if opts.get('topo'):
-        heads = [repo[h] for h in repo.heads(start)]
+        heads = [repo[h] for h in repo.heads(start, dead=opts.get('dead'))]
     else:
         heads = []
         for b, ls in repo.branchmap().iteritems():
@@ -1834,6 +1843,9 @@
     if not opts.get('closed'):
         heads = [h for h in heads if not h.extra().get('close')]
 
+    if not opts.get('dead'):
+        heads = [h for h in heads if not h.extra().get('dead')]
+
     if opts.get('active') and branchrevs:
         dagheads = repo.heads(start)
         heads = [h for h in heads if h.node() in dagheads]
@@ -2515,6 +2527,13 @@
             return
         if opts.get('only_merges') and len(parents) != 2:
             return
+        if not opts.get('rev') and not opts.get('show_dead'):
+                for h in repo.changelog.heads(ctx.node()):
+                    if not repo[h].extra().get('dead'):
+                        break
+                else:
+                    return
+
         if opts.get('branch') and ctx.branch() not in opts['branch']:
             return
         if df and not df(ctx.date()[0]):
@@ -2622,6 +2641,7 @@
     if not node:
         branch = repo[None].branch()
         bheads = repo.branchheads(branch)
+        bheads = [bh for bh in bheads if not repo[bh].extra().get('dead')]
         if len(bheads) > 2:
             raise util.Abort(_(
                 'branch \'%s\' has %d heads - '
@@ -2803,7 +2823,8 @@
                     "so a rev cannot be specified.")
             raise util.Abort(err)
 
-    modheads = repo.pull(other, heads=revs, force=opts.get('force'))
+    modheads = repo.pull(other, heads=revs, force=opts.get('force'),
+            dead=opts.get('dead'))
     if checkout:
         checkout = str(repo.changelog.rev(other.lookup(checkout)))
     repo._subtoppath = source
@@ -2860,7 +2881,8 @@
     finally:
         del repo._subtoppath
     r = repo.push(other, opts.get('force'), revs=revs,
-                  newbranch=opts.get('new_branch'))
+                  newbranch=opts.get('new_branch'),
+                  dead=opts.get('dead'))
     return r == 0
 
 def recover(ui, repo):
@@ -4084,7 +4106,9 @@
          [('a', 'active', False,
            _('show only branches that have unmerged heads')),
           ('c', 'closed', False,
-           _('show normal and closed branches'))],
+           _('show normal and closed branches')),
+          ('', 'dead', False,
+           _('show dead branches'))],
          _('[-ac]')),
     "bundle":
         (bundle,
@@ -4126,6 +4150,8 @@
           ('', 'pull', None, _('use pull protocol to copy metadata')),
           ('', 'uncompressed', None,
            _('use uncompressed transfer (fast over LAN)')),
+          ('', 'no-dead', False,
+           _('clone only non-dead branches')),
          ] + remoteopts,
          _('[OPTION]... SOURCE [DEST]')),
     "^commit|ci":
@@ -4134,6 +4160,8 @@
            _('mark new/missing files as added/removed before committing')),
           ('', 'close-branch', None,
            _('mark a branch as closed, hiding it from the branch list')),
+          ('', 'kill', None,
+           _('mark the changeset as dead')),
          ] + walkopts + commitopts + commitopts2,
          _('[OPTION]... [FILE]...')),
     "copy|cp":
@@ -4250,6 +4278,8 @@
            _('show active branchheads only (DEPRECATED)')),
           ('c', 'closed', False,
            _('show normal and closed branch heads')),
+          ('', 'dead', False,
+           _('show dead heads')),
          ] + templateopts,
          _('[-ac] [-r STARTREV] [REV]...')),
     "help": (help_, [], _('[TOPIC]')),
@@ -4291,6 +4321,7 @@
            _('a remote changeset intended to be added'), _('REV')),
           ('b', 'branch', [],
            _('a specific branch you would like to pull'), _('BRANCH')),
+          ('', 'dead', None, _('include all dead heads')),
          ] + logopts + remoteopts + subrepoopts,
          _('[-p] [-n] [-M] [-f] [-r REV]...'
            ' [--bundle FILENAME] [SOURCE]')),
@@ -4323,6 +4354,7 @@
           ('r', 'rev', [],
            _('show the specified revision or range'), _('REV')),
           ('', 'removed', None, _('include revisions where files were removed')),
+          ('', 'show-dead', None, _('include dead changesets')),
           ('m', 'only-merges', None, _('show only merges')),
           ('u', 'user', [],
            _('revisions committed by user'), _('USER')),
@@ -4359,6 +4391,7 @@
           ('n', 'newest-first', None, _('show newest record first')),
           ('b', 'branch', [],
            _('a specific branch you would like to push'), _('BRANCH')),
+          ('', 'dead', None, _('include all dead heads')),
          ] + logopts + remoteopts + subrepoopts,
          _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
     "parents":
@@ -4378,6 +4411,7 @@
            _('a remote changeset intended to be added'), _('REV')),
           ('b', 'branch', [],
            _('a specific branch you would like to pull'), _('BRANCH')),
+          ('', 'dead', None, _('include all dead heads')),
          ] + remoteopts,
          _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
     "^push":
@@ -4389,6 +4423,7 @@
           ('b', 'branch', [],
            _('a specific branch you would like to push'), _('BRANCH')),
           ('', 'new-branch', False, _('allow pushing a new branch')),
+          ('', 'dead', False, _('allow pushing a dead branch')),
          ] + remoteopts,
          _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
     "recover": (recover, []),
diff --git a/mercurial/discovery.py b/mercurial/discovery.py
--- a/mercurial/discovery.py
+++ b/mercurial/discovery.py
@@ -9,7 +9,8 @@
 from i18n import _
 import util, error
 
-def findcommonincoming(repo, remote, heads=None, force=False):
+def findcommonincoming(repo, remote, heads=None, force=False,
+        dead=False):
     """Return a tuple (common, missing roots, heads) used to identify
     missing nodes from remote.
 
@@ -24,7 +25,11 @@
     base = set()
 
     if not heads:
-        heads = remote.heads()
+        heads = remote.heads(dead=dead)
+        if not dead:
+            lheads = repo.heads(dead=False)
+            rheads = remote.makesdead(lheads)
+            heads.extend([rhead for rhead in rheads if rhead not in heads])
 
     if repo.changelog.tip() == nullid:
         base.add(nullid)
@@ -147,7 +152,8 @@
 
     return base, list(fetch), heads
 
-def findoutgoing(repo, remote, base=None, remoteheads=None, force=False):
+def findoutgoing(repo, remote, base=None, remoteheads=None, force=False,
+        dead=False, revs=None):
     """Return list of nodes that are roots of subsets not in remote
 
     If base dict is specified, assume that these nodes and their parents
@@ -155,6 +161,9 @@
     If remotehead is specified, assume it is the list of the heads from
     the remote repository.
     """
+    if not remoteheads:
+        remoteheads = remote.heads(dead=True)
+
     if base is None:
         base = findcommonincoming(repo, remote, heads=remoteheads,
                                   force=force)[0]
@@ -169,6 +178,41 @@
     # prune everything remote has from the tree
     remain.remove(nullid)
     remove = base
+
+    # include dead heads not explicitly marked for inclusion from the tree
+    if not dead and not revs and remoteheads:
+        newremoteheads = []
+        localheads = repo.heads()
+        localdeadheads = repo.heads(dead=True)
+        for rh in remoteheads:
+            if rh not in repo:
+                newremoteheads.append(rh)
+                continue
+
+            found = False
+            for lh in localheads:
+                if repo.changelog.ancestor(rh, lh) == rh:
+                    newremoteheads.append(lh)
+                    found = True
+                    break
+
+            if not found:
+                newremoteheads.append(rh)
+
+        for lh in set(localdeadheads) - set(localheads):
+            include = False
+            for rh in newremoteheads:
+                if rh not in repo:
+                    continue
+
+                if repo.changelog.ancestor(lh, rh) == rh:
+                    include = True
+                    break
+
+            if not include:
+                remove.append(lh)
+
+    # iterate items to be removed from the tree
     while remove:
         n = remove.pop(0)
         if n in remain:
@@ -186,7 +230,7 @@
 
     return subset
 
-def prepush(repo, remote, force, revs, newbranch):
+def prepush(repo, remote, force, revs, newbranch, dead):
     '''Analyze the local and remote repositories and determine which
     changesets need to be pushed to the remote. Return value depends
     on circumstances:
@@ -200,12 +244,13 @@
     changegroup is a readable file-like object whose read() returns
     successive changegroup chunks ready to be sent over the wire and
     remoteheads is the list of remote heads.'''
-    remoteheads = remote.heads()
+    remoteheads = remote.heads(dead=True)
     common, inc, rheads = findcommonincoming(repo, remote, heads=remoteheads,
                                              force=force)
 
     cl = repo.changelog
-    update = findoutgoing(repo, remote, common, remoteheads)
+    update = findoutgoing(repo, remote, common, remoteheads, dead=dead,
+            revs=revs)
     outg, bases, heads = cl.nodesbetween(update, revs)
 
     if not bases:
@@ -307,10 +352,10 @@
         if unsynced:
             repo.ui.warn(_("note: unsynced remote changes!\n"))
 
-    if revs is None:
+    if revs is None and dead:
         # use the fast path, no race possible on push
-        nodes = repo.changelog.findmissing(common)
+        nodes = repo.changelog.findmissing(common, dead=dead)
         cg = repo._changegroup(nodes, 'push')
     else:
-        cg = repo.changegroupsubset(update, revs, 'push')
+        cg = repo.changegroupsubset(update, revs and revs or heads, 'push')
     return cg, remoteheads
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -179,7 +179,7 @@
         _update(r, uprev)
 
 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
-          stream=False, branch=None):
+          stream=False, branch=None, dead=False):
     """Make a copy of an existing repository.
 
     Create a copy of an existing repository in a new directory.  The
@@ -332,9 +332,9 @@
                 revs = [src_repo.lookup(r) for r in rev]
                 checkout = revs[0]
             if dest_repo.local():
-                dest_repo.clone(src_repo, heads=revs, stream=stream)
+                dest_repo.clone(src_repo, heads=revs, stream=stream, dead=dead)
             elif src_repo.local():
-                src_repo.push(dest_repo, revs=revs)
+                src_repo.push(dest_repo, revs=revs, dead=dead)
             else:
                 raise util.Abort(_("clone from remote to remote not supported"))
 
@@ -422,7 +422,8 @@
     if revs:
         revs = [other.lookup(rev) for rev in revs]
     other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
-                                opts["bundle"], opts["force"])
+                                opts["bundle"], opts["force"],
+                                dead=opts.get('dead'))
     if incoming is None:
         ui.status(_("no changes found\n"))
         return subreporecurse()
@@ -478,7 +479,8 @@
 
     other = repository(remoteui(repo, opts), dest)
     ui.status(_('comparing with %s\n') % url.hidepassword(dest))
-    o = discovery.findoutgoing(repo, other, force=opts.get('force'))
+    o = discovery.findoutgoing(repo, other, force=opts.get('force'),
+            dead=opts.get('dead'), revs=revs)
     if not o:
         ui.status(_("no changes found\n"))
         return None
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -16,6 +16,7 @@
 
 perms = {
     'changegroup': 'pull',
+    'deadchangegroup': 'pull',
     'changegroupsubset': 'pull',
     'stream_out': 'pull',
     'listkeys': 'pull',
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -20,7 +20,8 @@
 propertycache = util.propertycache
 
 class localrepository(repo.repository):
-    capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
+    capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
+        'dead'))
     supportedformats = set(('revlogv1', 'parentdelta'))
     supported = supportedformats | set(('store', 'fncache', 'shared',
                                         'dotencode'))
@@ -934,7 +935,8 @@
 
             if (not force and not extra.get("close") and not merge
                 and not (changes[0] or changes[1] or changes[2])
-                and wctx.branch() == wctx.p1().branch()):
+                and wctx.branch() == wctx.p1().branch()
+                and not extra.get('dead')):
                 return None
 
             ms = mergemod.mergestate(self)
@@ -1215,10 +1217,31 @@
         [l.sort() for l in r]
         return r
 
-    def heads(self, start=None):
+    def makesdead(self, heads):
+        newheads = []
+        for h in heads:
+            if h not in self:
+                continue
+
+            allheads = self.heads(start=h, dead=True)
+            if not [ah for ah in allheads if not self[ah].extra().get('dead')]:
+                newheads.extend(allheads)
+
+        return newheads
+
+    def heads(self, start=None, dead=False):
         heads = self.changelog.heads(start)
         # sort the output in rev descending order
-        return sorted(heads, key=self.changelog.rev, reverse=True)
+        heads = sorted(heads, key=self.changelog.rev, reverse=True)
+
+        def _include(mh):
+            mhdead = self[mh].extra().get('dead')
+            return mhdead and dead or not mhdead
+
+        heads = [h for h in heads if _include(h)]
+        if not heads:
+            heads = [self[-1].node()]
+        return heads
 
     def branchheads(self, branch=None, start=None, closed=False):
         '''return a (possibly filtered) list of heads for the given branch
@@ -1277,11 +1300,11 @@
 
         return r
 
-    def pull(self, remote, heads=None, force=False):
+    def pull(self, remote, heads=None, force=False, dead=False):
         lock = self.lock()
         try:
             tmp = discovery.findcommonincoming(self, remote, heads=heads,
-                                               force=force)
+                                               force=force, dead=dead)
             common, fetch, rheads = tmp
             if not fetch:
                 self.ui.status(_("no changes found\n"))
@@ -1294,7 +1317,7 @@
                 heads = rheads
 
             if heads is None:
-                cg = remote.changegroup(fetch, 'pull')
+                cg = remote.changegroup(fetch, 'pull', dead=dead)
             else:
                 if not remote.capable('changegroupsubset'):
                     raise util.Abort(_("partial pull cannot be done because "
@@ -1305,7 +1328,7 @@
         finally:
             lock.release()
 
-    def push(self, remote, force=False, revs=None, newbranch=False):
+    def push(self, remote, force=False, revs=None, newbranch=False, dead=False):
         '''Push outgoing changesets (limited by revs) from the current
         repository to remote. Return an integer:
           - 0 means HTTP error *or* nothing to push
@@ -1326,7 +1349,7 @@
         if not unbundle:
             lock = remote.lock()
         try:
-            ret = discovery.prepush(self, remote, force, revs, newbranch)
+            ret = discovery.prepush(self, remote, force, revs, newbranch, dead)
             if ret[0] is None:
                 # and here we return 0 for "nothing to push" or 1 for
                 # "something to push but I refuse"
@@ -1342,10 +1365,11 @@
                     remote_heads = ['force']
                 # ssh: return remote's addchangegroup()
                 # http: return remote's addchangegroup() or 0 for error
-                return remote.unbundle(cg, remote_heads, 'push')
+                return remote.unbundle(cg, remote_heads, 'push', dead=dead)
             else:
                 # we return an integer indicating remote head count change
-                return remote.addchangegroup(cg, 'push', self.url(), lock=lock)
+                return remote.addchangegroup(cg, 'push', self.url(), lock=lock,
+                        dead=dead)
         finally:
             if lock is not None:
                 lock.release()
@@ -1393,7 +1417,7 @@
         if extranodes is None:
             # can we go through the fast path ?
             heads.sort()
-            allheads = self.heads()
+            allheads = self.heads(dead=True)
             allheads.sort()
             if heads == allheads:
                 return self._changegroup(msng_cl_lst, source)
@@ -1581,9 +1605,9 @@
 
         return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
 
-    def changegroup(self, basenodes, source):
+    def changegroup(self, basenodes, source, dead=True):
         # to avoid a race we use changegroupsubset() (issue1320)
-        return self.changegroupsubset(basenodes, self.heads(), source)
+        return self.changegroupsubset(basenodes, self.heads(dead=dead), source)
 
     def _changegroup(self, nodes, source):
         """Compute the changegroup of all nodes that we have that a recipient
@@ -1669,7 +1693,8 @@
 
         return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
 
-    def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
+    def addchangegroup(self, source, srctype, url, emptyok=False, lock=None,
+            dead=False):
         """Add the changegroup returned by source.read() to this repo.
         srctype is a string like 'push', 'pull', or 'unbundle'.  url is
         the URL of the repo where this changegroup is coming from.
@@ -1889,7 +1914,7 @@
         self.invalidate()
         return len(self.heads()) + 1
 
-    def clone(self, remote, heads=[], stream=False):
+    def clone(self, remote, heads=[], stream=False, dead=False):
         '''clone remote repository.
 
         keyword arguments:
@@ -1904,7 +1929,7 @@
         # and format flags on "stream" capability, and use
         # uncompressed only if compatible.
 
-        if stream and not heads:
+        if stream and not heads and dead:
             # 'stream' means remote revlog format is revlogv1 only
             if remote.capable('stream'):
                 return self.stream_in(remote, set(('revlogv1',)))
@@ -1915,7 +1940,7 @@
                 # if we support it, stream in and adjust our requirements
                 if not streamreqs - self.supportedformats:
                     return self.stream_in(remote, streamreqs)
-        return self.pull(remote, heads)
+        return self.pull(remote, heads, dead=dead)
 
     def pushkey(self, namespace, key, old, new):
         return pushkey.push(self, namespace, key, old, new)
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -476,7 +476,11 @@
         if node is None:
             # tip of current branch
             try:
-                node = repo.branchtags()[wc.branch()]
+                candidates = [bh for bh in repo.branchmap()[wc.branch()]
+                        if not repo[bh].extra().get('dead')]
+                if not candidates:
+                    raise KeyError
+                node = sorted(candidates, key=lambda x: repo[x].rev())[-1]
             except KeyError:
                 if wc.branch() == "default": # no default branch!
                     node = repo.lookup("tip") # update to tip
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -398,7 +398,7 @@
                     yield i
                     break
 
-    def findmissing(self, common=None, heads=None):
+    def findmissing(self, common=None, heads=None, dead=False):
         """Return the ancestors of heads that are not ancestors of common.
 
         More specifically, return a list of nodes N such that every N
@@ -418,8 +418,12 @@
         if heads is None:
             heads = self.heads()
 
+        def _include_head(_head):
+            extra = self.read(_head)[5].get('dead')
+            return extra and dead or not extra
+
         common = [self.rev(n) for n in common]
-        heads = [self.rev(n) for n in heads]
+        heads = [self.rev(n) for n in heads if _include_head(n)]
 
         # we want the ancestors, but inclusive
         has = set(self.ancestors(*common))
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -333,6 +333,28 @@
     s = set(repo.changelog.ancestors(*args)) | set(args)
     return [r for r in subset if r in s]
 
+def dead(repo, subset, x):
+    """``dead(optionalset)``
+    Changesets which are in a dead branch
+    """
+    args = getargs(x, 0, 1, _("head takes zero or one argument"))
+    hs = set()
+    deads = set()
+
+    if args:
+        settocheck = getset(repo, subset, x)
+    else:
+        settocheck = subset
+
+    for s in settocheck:
+        ctx = repo[s]
+        for h in repo.changelog.heads(ctx.node()):
+            if not repo[h].extra().get('dead'):
+                break
+        else:
+            deads.add(ctx.rev())
+    return deads
+
 def descendants(repo, subset, x):
     """``descendants(set)``
     Changesets which are descendants of changesets in set.
@@ -675,6 +697,7 @@
     "closed": closed,
     "contains": contains,
     "date": date,
+    "dead": dead,
     "descendants": descendants,
     "file": hasfile,
     "follow": follow,
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -31,13 +31,35 @@
             return bin(data)
         self._abort(error.RepoError(data))
 
-    def heads(self):
-        d = self._call("heads")
+    def deadheads(self, dead):
+        d = self._call('deadheads', dead=str(dead))
         try:
             return decodelist(d[:-1])
         except:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
+    def makesdead(self, heads):
+        if not self.capable('dead'):
+            return []
+        enc = encodelist(heads)
+        d = self._call('makesdead', heads=encodelist(heads))
+        try:
+            if not d[:-1]:
+                return []
+            return decodelist(d[:-1])
+        except:
+            self._abort(error.ResponseError(_('unexpected response:'), d))
+
+    def heads(self, dead=False):
+        if self.capable('dead'):
+            return self.deadheads(dead=dead)
+
+        d = self._call('heads')
+        try:
+            return decodelist(d[:-1])
+        except:
+            self._abort(error.ResponseError(_('unexpected response:'), d))
+
     def branchmap(self):
         d = self._call("branchmap")
         try:
@@ -95,7 +117,14 @@
     def stream_out(self):
         return self._callstream('stream_out')
 
-    def changegroup(self, nodes, kind):
+    def deadchangegroup(self, nodes, kind, dead=False):
+        n = encodelist(nodes)
+        f = self._callstream("deadchangegroup", roots=n, dead=str(dead))
+        return changegroupmod.unbundle10(self._decompress(f), 'UN')
+
+    def changegroup(self, nodes, kind, dead=False):
+        if self.capable('dead'):
+            return self.deadchangegroup(nodes, kind, dead)
         n = encodelist(nodes)
         f = self._callstream("changegroup", roots=n)
         return changegroupmod.unbundle10(self._decompress(f), 'UN')
@@ -108,7 +137,7 @@
                              bases=bases, heads=heads)
         return changegroupmod.unbundle10(self._decompress(f), 'UN')
 
-    def unbundle(self, cg, heads, source):
+    def unbundle(self, cg, heads, source, dead=False):
         '''Send cg (a readable file-like object representing the
         changegroup to push, typically a chunkbuffer object) to the
         remote server as a bundle. Return an integer indicating the
@@ -171,7 +200,7 @@
     return "".join(r)
 
 def capabilities(repo, proto):
-    caps = 'lookup changegroupsubset branchmap pushkey'.split()
+    caps = 'lookup changegroupsubset branchmap pushkey dead'.split()
     if _allowstream(repo.ui):
         requiredformats = repo.requirements & repo.supportedformats
         # if our local revlogs are just revlogv1, add 'stream' cap
@@ -188,16 +217,29 @@
     cg = repo.changegroup(nodes, 'serve')
     return streamres(proto.groupchunks(cg))
 
+def deadchangegroup(repo, proto, roots, dead):
+    nodes = decodelist(roots)
+    cg = repo.changegroup(nodes, 'serve', dead=dead=='True')
+    return streamres(proto.groupchunks(cg))
+
 def changegroupsubset(repo, proto, bases, heads):
     bases = decodelist(bases)
     heads = decodelist(heads)
     cg = repo.changegroupsubset(bases, heads, 'serve')
     return streamres(proto.groupchunks(cg))
 
+def makesdead(repo, proto, heads):
+    heads = decodelist(heads)
+    return encodelist(repo.makesdead(heads)) + '\n'
+
 def heads(repo, proto):
     h = repo.heads()
     return encodelist(h) + "\n"
 
+def deadheads(repo, proto, dead):
+    h = repo.heads(dead=dead=='True')
+    return encodelist(h) + '\n'
+
 def hello(repo, proto):
     '''the hello command returns a set of lines describing various
     interesting things about the server, in an RFC822-like format.
@@ -337,12 +379,15 @@
     'branches': (branches, 'nodes'),
     'capabilities': (capabilities, ''),
     'changegroup': (changegroup, 'roots'),
+    'deadchangegroup': (deadchangegroup, 'roots dead'),
     'changegroupsubset': (changegroupsubset, 'bases heads'),
     'heads': (heads, ''),
+    'deadheads': (deadheads, 'dead'),
     'hello': (hello, ''),
     'listkeys': (listkeys, 'namespace'),
     'lookup': (lookup, 'key'),
     'pushkey': (pushkey, 'namespace key old new'),
     'stream_out': (stream, ''),
     'unbundle': (unbundle, 'heads'),
+    'makesdead': (makesdead, 'heads'),
 }
diff --git a/tests/test-dead-http.t b/tests/test-dead-http.t
new file mode 100644
--- /dev/null
+++ b/tests/test-dead-http.t
@@ -0,0 +1,105 @@
+setting up
+
+  $ hg init dead
+  $ cd dead
+  $ touch x
+  $ hg add x
+  $ hg ci -m initial
+  $ printf "foo" > x
+  $ hg ci -m foo
+  $ hg ci --kill -m dead
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "bar" > x
+  $ hg ci -m bar
+  created new head
+  $ cd ..
+  $ hg clone dead dead-in -r 1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg clone dead dead-out -r 1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+testing outgoing with dead head propagating
+
+  $ cd dead
+  $ hg serve -p $HGPORT -d --pid-file=../hg.pid
+  $ cat ../hg.pid >> $DAEMON_PIDS
+  $ cd ../dead-out
+  $ hg serve -p $HGPORT1 -d --pid-file=../hg2.pid
+  $ cat ../hg2.pid >> $DAEMON_PIDS
+  $ cd ../dead
+  $ hg out http://localhost:$HGPORT1/ --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT1/
+  searching for changes
+  2 dead
+  3 bar
+
+testing outgoing without dead head propagating
+
+  $ cd ../dead-out
+  $ printf "baz" > x
+  $ hg ci -m baz
+  $ cd ../dead
+  $ hg out http://localhost:$HGPORT1/ --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT1/
+  searching for changes
+  3 bar
+
+testing outgoing with dead head propagating using --dead
+
+  $ hg out http://localhost:$HGPORT1/ --dead --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT1/
+  searching for changes
+  2 dead
+  3 bar
+
+testing outgoing with explicit dead head
+
+  $ hg out http://localhost:$HGPORT1/ -r 2 --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT1/
+  searching for changes
+  2 dead
+
+testing incoming with dead head propagating
+
+  $ cd ../dead-in
+  $ hg in http://localhost:$HGPORT/ --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT/
+  searching for changes
+  2 dead
+  3 bar
+
+testing incoming with dead head propagating using --dead
+
+  $ hg in http://localhost:$HGPORT/ --dead --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT/
+  searching for changes
+  2 dead
+  3 bar
+
+testing incoming with explicit dead head
+
+  $ hg in http://localhost:$HGPORT/ -r 2 --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT/
+  searching for changes
+  2 dead
+
+testing incoming with no dead head propagating
+
+  $ printf "asjda" > quux
+  $ hg add quux
+  $ hg ci -m quux
+  $ hg in http://localhost:$HGPORT/ --template '{rev} {desc|firstline}\n'
+  comparing with http://localhost:$HGPORT/
+  searching for changes
+  3 bar
diff --git a/tests/test-dead-ssh.t b/tests/test-dead-ssh.t
new file mode 100644
--- /dev/null
+++ b/tests/test-dead-ssh.t
@@ -0,0 +1,126 @@
+  $ cat <<EOF > dummyssh
+  > import sys
+  > import os
+  > 
+  > os.chdir(os.path.dirname(sys.argv[0]))
+  > if sys.argv[1] != "user at dummy":
+  >     sys.exit(-1)
+  > 
+  > if not os.path.exists("dummyssh"):
+  >     sys.exit(-1)
+  > 
+  > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
+  > 
+  > log = open("dummylog", "ab")
+  > log.write("Got arguments")
+  > for i, arg in enumerate(sys.argv[1:]):
+  >     log.write(" %d:%s" % (i+1, arg))
+  > log.write("\n")
+  > log.close()
+  > r = os.system(sys.argv[2])
+  > sys.exit(bool(r))
+  > EOF
+
+setting up
+
+  $ hg init dead
+  $ cd dead
+  $ touch x
+  $ hg add x
+  $ hg ci -m initial
+  $ printf "foo" > x
+  $ hg ci -m foo
+  $ hg ci --kill -m dead
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "bar" > x
+  $ hg ci -m bar
+  created new head
+  $ cd ..
+
+cloning
+
+  $ hg clone -r 1 dead dead-out
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg clone -r 1 dead dead-in
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+testing outgoing with dead head propagating
+
+  $ cd dead
+  $ hg --config "ui.ssh=python ../dummyssh" out ssh://user@dummy/dead-out --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead-out
+  searching for changes
+  2 dead
+  3 bar
+
+testing outgoing without dead head propagating
+
+  $ cd ../dead-out
+  $ printf "baz" > x
+  $ hg ci -m baz
+  $ cd ../dead
+  $ hg --config "ui.ssh=python ../dummyssh" out ssh://user@dummy/dead-out --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead-out
+  searching for changes
+  3 bar
+
+testing outgoing with dead head propagating using --dead
+
+  $ hg --config "ui.ssh=python ../dummyssh" out ssh://user@dummy/dead-out --dead --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead-out
+  searching for changes
+  2 dead
+  3 bar
+
+testing outgoing with explicit dead head
+
+  $ hg --config "ui.ssh=python ../dummyssh" out ssh://user@dummy/dead-out -r 2 --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead-out
+  searching for changes
+  2 dead
+
+testing incoming with dead head propagating
+
+  $ cd ../dead-in
+  $ hg --config "ui.ssh=python ../dummyssh" in ssh://user@dummy/dead --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead
+  searching for changes
+  2 dead
+  3 bar
+
+testing incoming with dead head propagating using --dead
+
+  $ hg --config "ui.ssh=python ../dummyssh" in ssh://user@dummy/dead --dead --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead
+  searching for changes
+  2 dead
+  3 bar
+
+testing incoming with explicit dead head
+
+  $ hg --config "ui.ssh=python ../dummyssh" in ssh://user@dummy/dead -r 2 --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead
+  searching for changes
+  2 dead
+
+testing incoming with no dead head propagating
+
+  $ printf "asjda" > quux
+  $ hg add quux
+  $ hg ci -m quux
+  $ hg --config "ui.ssh=python ../dummyssh" in ssh://user@dummy/dead --template '{rev} {desc|firstline}\n'
+  comparing with ssh://user@dummy/dead
+  searching for changes
+  3 bar
+
diff --git a/tests/test-dead.t b/tests/test-dead.t
new file mode 100644
--- /dev/null
+++ b/tests/test-dead.t
@@ -0,0 +1,288 @@
+setting up
+
+  $ hg init dead
+  $ cd dead
+  $ touch x
+  $ hg add x
+  $ hg ci -m initial
+  $ printf "foo" > x
+  $ hg ci -m foo
+  $ hg ci --kill -m dead
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "bar" > x
+  $ hg ci -m bar
+  created new head
+  $ hg --config extensions.graphlog= glog --template '{rev} {desc|firstline}\n'
+  @  3 bar
+  |
+  | o  2 dead
+  | |
+  | o  1 foo
+  |/
+  o  0 initial
+  
+  $ cd ..
+  $ hg clone dead dead-in -r 1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg clone dead dead-out -r 1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+testing outgoing with dead head propagating
+
+  $ cd dead
+  $ hg out ../dead-out --template '{rev} {desc|firstline}\n'
+  comparing with ../dead-out
+  searching for changes
+  2 dead
+  3 bar
+
+testing outgoing without dead head propagating
+
+  $ cd ../dead-out
+  $ printf "baz" > x
+  $ hg ci -m baz
+  $ cd ../dead
+  $ hg out ../dead-out --template '{rev} {desc|firstline}\n'
+  comparing with ../dead-out
+  searching for changes
+  3 bar
+
+testing outgoing with dead head propagating using --dead
+
+  $ hg out ../dead-out --dead --template '{rev} {desc|firstline}\n'
+  comparing with ../dead-out
+  searching for changes
+  2 dead
+  3 bar
+
+testing outgoing with explicit dead head
+
+  $ hg out ../dead-out -r 2 --template '{rev} {desc|firstline}\n'
+  comparing with ../dead-out
+  searching for changes
+  2 dead
+
+testing incoming with dead head propagating
+
+  $ cd ../dead-in
+  $ hg in ../dead --template '{rev} {desc|firstline}\n'
+  comparing with ../dead
+  searching for changes
+  2 dead
+  3 bar
+
+testing incoming with dead head propagating using --dead
+
+  $ hg in ../dead --dead --template '{rev} {desc|firstline}\n'
+  comparing with ../dead
+  searching for changes
+  2 dead
+  3 bar
+
+testing incoming with explicit dead head
+
+  $ hg in ../dead -r 2 --template '{rev} {desc|firstline}\n'
+  comparing with ../dead
+  searching for changes
+  2 dead
+
+testing incoming with no dead head propagating
+
+  $ printf "asjda" > quux
+  $ hg add quux
+  $ hg ci -m quux
+  $ hg in ../dead --template '{rev} {desc|firstline}\n'
+  comparing with ../dead
+  searching for changes
+  3 bar
+
+testing incoming with no dead head propagating due to makesdead
+
+  $ cd ../dead
+  $ hg up 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "zooo" > x
+  $ hg ci -m zooo
+  created new head
+  $ cd ../dead-in
+  $ hg in ../dead --template '{rev} {desc|firstline}\n'
+  comparing with ../dead
+  searching for changes
+  3 bar
+  4 zooo
+
+setting up for test of log, branches and heads
+
+  $ cd ..
+  $ hg init logtest
+  $ cd logtest
+  $ touch f0
+  $ hg add f0
+  $ hg commit -m0
+  $ hg branch stable
+  marked working directory as branch stable
+  $ touch f1
+  $ hg add f1
+  $ hg commit -m1
+  $ hg commit --kill -m dead
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ touch f2
+  $ hg add f2
+  $ hg commit -m3
+  $ hg --config extensions.graphlog= glog --template '{rev} ({branches}) {desc|firstline}\n'  
+  @  3 () 3
+  |
+  | o  2 (stable) dead
+  | |
+  | o  1 (stable) 1
+  |/
+  o  0 () 0
+  
+
+test branches without dead
+
+  $ hg branches
+  default                        3:827d1b641f05
+
+test branches with dead
+
+  $ hg branches --dead
+  default                        3:827d1b641f05
+  stable                         2:3b4379e05029 (dead)
+
+test heads without dead
+
+  $ hg heads
+  changeset:   3:827d1b641f05
+  tag:         tip
+  parent:      0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     3
+  
+
+test heads with dead
+
+  $ hg heads --dead
+  changeset:   3:827d1b641f05
+  tag:         tip
+  parent:      0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     3
+  
+  changeset:   2:3b4379e05029
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     dead
+  
+
+test log without dead
+
+  $ hg log
+  changeset:   3:827d1b641f05
+  tag:         tip
+  parent:      0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     3
+  
+  changeset:   0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     0
+  
+
+test log with showdeads
+
+  $ hg log --show-dead
+  changeset:   3:827d1b641f05
+  tag:         tip
+  parent:      0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     3
+  
+  changeset:   2:3b4379e05029
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     dead
+  
+  changeset:   1:e79561967a43
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+  changeset:   0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     0
+  
+test log with explicit revision without showdeads
+
+  $ hg log -r 2
+  changeset:   2:3b4379e05029
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     dead
+  
+
+test log with explicit revision with showdeads
+
+  $ hg log -r 2 --show-dead
+  changeset:   2:3b4379e05029
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     dead
+  
+test log with dead revset predicate
+  $ hg log -r 'dead()'
+  changeset:   1:e79561967a43
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+  changeset:   2:3b4379e05029
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     dead
+  
+  $ hg log -r 'not dead()'
+  changeset:   0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     0
+  
+  changeset:   3:827d1b641f05
+  tag:         tip
+  parent:      0:62ecad8b70e5
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     3
+  
+  $ hg log -r 'dead(0:1)'
+  changeset:   1:e79561967a43
+  branch:      stable
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
diff --git a/tests/test-debugcomplete.t b/tests/test-debugcomplete.t
--- a/tests/test-debugcomplete.t
+++ b/tests/test-debugcomplete.t
@@ -179,16 +179,16 @@
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
   annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, include, exclude
-  clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd
-  commit: addremove, close-branch, include, exclude, message, logfile, date, user
+  clone: noupdate, updaterev, rev, branch, pull, uncompressed, no-dead, ssh, remotecmd
+  commit: addremove, close-branch, kill, include, exclude, message, logfile, date, user
   diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
   export: output, switch-parent, rev, text, git, nodates
   forget: include, exclude
   init: ssh, remotecmd
-  log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
+  log: follow, follow-first, date, copies, keyword, rev, removed, show-dead, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
   merge: force, tool, rev, preview
-  pull: update, force, rev, branch, ssh, remotecmd
-  push: force, rev, branch, new-branch, ssh, remotecmd
+  pull: update, force, rev, branch, dead, ssh, remotecmd
+  push: force, rev, branch, new-branch, dead, ssh, remotecmd
   remove: after, force, include, exclude
   serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
@@ -199,7 +199,7 @@
   backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, command, noupdate
   branch: force, clean
-  branches: active, closed
+  branches: active, closed, dead
   bundle: force, rev, branch, base, all, type, ssh, remotecmd
   cat: output, rev, decode, include, exclude
   copy: after, force, include, exclude, dry-run
@@ -224,14 +224,14 @@
   debugsub: rev
   debugwalk: include, exclude
   grep: print0, all, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
-  heads: rev, topo, active, closed, style, template
+  heads: rev, topo, active, closed, dead, style, template
   help: 
   identify: rev, num, id, branch, tags
   import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
-  incoming: force, newest-first, bundle, rev, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
+  incoming: force, newest-first, bundle, rev, branch, dead, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
   locate: rev, print0, fullpath, include, exclude
   manifest: rev
-  outgoing: force, rev, newest-first, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
+  outgoing: force, rev, newest-first, branch, dead, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
   parents: rev, style, template
   paths: 
   recover: 
diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -901,7 +901,7 @@
   $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey unbundle=HG10GZ,HG10BZ,HG10UN
+  lookup changegroupsubset branchmap pushkey dead unbundle=HG10GZ,HG10BZ,HG10UN
 
 heads
 
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -102,15 +102,19 @@
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadheads&dead=True HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadchangegroup&roots=0000000000000000000000000000000000000000&dead=True HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadheads&dead=True HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadchangegroup&roots=0000000000000000000000000000000000000000&dead=True HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadheads&dead=True HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadchangegroup&roots=0000000000000000000000000000000000000000&dead=True HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadheads&dead=True HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=deadchangegroup&roots=0000000000000000000000000000000000000000&dead=True HTTP/1.1" - - (glob)
 
diff --git a/tests/test-revlog-group-emptyiter.t b/tests/test-revlog-group-emptyiter.t
--- a/tests/test-revlog-group-emptyiter.t
+++ b/tests/test-revlog-group-emptyiter.t
@@ -30,5 +30,5 @@
   adding changesets
   adding manifests
   adding file changes
-  added 1 changesets with 0 changes to 0 files (+1 heads)
+  added 1 changesets with 0 changes to 1 files (+1 heads)
 
diff --git a/tests/test-schemes.t b/tests/test-schemes.t
--- a/tests/test-schemes.t
+++ b/tests/test-schemes.t
@@ -27,7 +27,10 @@
   using http://localhost:$HGPORT/
   sending between command
   comparing with parts://localhost
-  sending heads command
+  sending capabilities command
+  capabilities: changegroupsubset stream dead lookup pushkey unbundle=HG10GZ,HG10BZ,HG10UN branchmap
+  sending deadheads command
+  sending makesdead command
   searching for changes
   no changes found
   [1]


More information about the Mercurial-devel mailing list