[PATCH] Deleting a named branch.

Govind Salinas govind at sophiasuchtig.com
Mon Jan 21 20:13:47 CST 2008


# HG changeset patch
# User Govind Salinas <blix at sophiasuchtig.com>
# Date 1200967247 21600
# Node ID 7a01fea81b8d0f80fcd214104f538412bf2258eb
# Parent  d39af2eabb8c8656c200ecc23d07faacf95be68c
Delete a branch and take any nodes not reachable from another head.

This is a patch for comment.  Please take a look and let me know what
needs to be changed.  I am neither very familiar with hg nor is python
my native language so I am sure there are instances where things could
be done better and more efficiently.  I have a test which I have
passing which I will submit with the real patch.  I also understand
that the code should be moved somewhere else, I am not sure if it
should go in localrepo or in repair.

Basic algorithm:

If you are on the branch you want to delete, bail.  That leaves you in
a weird situation.

If the branch you want to delete doesn't exist, bail.

Get an orderd list of all the reachable commits for the branch to
delete (children before parents).

Accumulate a set of all the commits reachable from other branchheads.
The intersection of the two are places where a parent of the branch to
delete is reachable from another branch.  These are the common
ancestors.

Go down the ordered list of commits to the branch to delete until you
find a common ancestor.  The commit previous to this is the last
commit you can delete.

use repair to strip the commits from the limit onwards.

diff -r d39af2eabb8c -r 7a01fea81b8d mercurial/cmdutil.py
--- a/mercurial/cmdutil.py    Fri Jan 18 10:48:25 2008 -0600
+++ b/mercurial/cmdutil.py    Mon Jan 21 20:00:47 2008 -0600
@@ -761,7 +761,7 @@ class changeset_templater(changeset_prin
             branch = changes[5].get("branch")
             if branch != 'default':
                 branch = util.tolocal(branch)
-                return showlist('branch', [branch], plural='branches', **args)
+            return showlist('branch', [branch], plural='branches', **args)

         def showparents(**args):
             parents = [[('rev', p), ('node', hex(log.node(p)))]
diff -r d39af2eabb8c -r 7a01fea81b8d mercurial/commands.py
--- a/mercurial/commands.py    Fri Jan 18 10:48:25 2008 -0600
+++ b/mercurial/commands.py    Mon Jan 21 20:00:47 2008 -0600
@@ -11,7 +11,7 @@ import hg, util, revlog, bundlerepo, ext
 import hg, util, revlog, bundlerepo, extensions
 import difflib, patch, time, help, mdiff, tempfile
 import errno, version, socket
-import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
+import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect, repair

 # Commands start here, listed alphabetically

@@ -342,6 +342,51 @@ def branch(ui, repo, label=None, **opts)
     """

     if label:
+        if opts.get('delete'):
+            if repo.dirstate.branch() == label:
+                raise util.Abort(_('cannot delete current branch\n'))
+            if label not in repo.branchtags():
+                raise util.Abort(_('%s is not a valid branch name\n') % label)
+
+
+            changelog = repo.changelog
+            branchnodes = repo.branchnodes()
+
+            branchtodelete = repo.lookup(label)
+            branchancestors = [branchtodelete]
+
+            idx = 0
+            while (idx < len(branchancestors)):
+                commit = branchancestors[idx]
+                for p in changelog.parents(commit):
+                    if p != nullid and p not in branchancestors:
+                        branchancestors.insert(idx + 1, p)
+                idx += 1
+
+            others = set()
+
+            for branch in branchnodes:
+                if branch != branchtodelete:
+                    others = others.union(changelog.reachable(branch))
+
+            commonancestors = others.intersection(branchancestors)
+
+            prunelimit = None
+            for commit in branchancestors:
+                if commit in commonancestors:
+                    break
+                prunelimit = commit
+            else:
+                prunelimit = None
+
+            if prunelimit:
+                repair.strip(ui, repo, prunelimit, None)
+            repo.invalidate()
+            if label not in repo.branchtags():
+                ui.status(_('branch %s deleted\n') % label)
+            else:
+                util.Abort(_('unable to delete %s') % label)
+            return
         if not opts.get('force') and label in repo.branchtags():
             if label not in [p.branch() for p in repo.workingctx().parents()]:
                 raise util.Abort(_('a branch of the same name already exists'
@@ -2754,7 +2799,9 @@ table = {
     "branch":
         (branch,
          [('f', 'force', None,
-           _('set branch name even if it shadows an existing branch'))],
+           _('set branch name even if it shadows an existing branch')),
+           ('d', 'delete', None,
+           _('remove branch and unreachable history'))],
          _('hg branch [-f] [NAME]')),
     "branches":
         (branches,
diff -r d39af2eabb8c -r 7a01fea81b8d mercurial/localrepo.py
--- a/mercurial/localrepo.py    Fri Jan 18 10:48:25 2008 -0600
+++ b/mercurial/localrepo.py    Mon Jan 21 20:00:47 2008 -0600
@@ -290,6 +290,13 @@ class localrepository(repo.repository):
         self.tags()

         return self._tagstypecache.get(tagname)
+
+    def branchnodes(self):
+        branches = self.branchtags()
+        nodes = set()
+        for b in branches:
+            nodes.add(self.changectx(b).node())
+        return nodes

     def _hgtagsnodes(self):
         heads = self.heads()
@@ -579,6 +586,7 @@ class localrepository(repo.repository):
         self.tagscache = None
         self._tagstypecache = None
         self.nodetagscache = None
+        self.branchcache = None

     def _lock(self, lockname, wait, releasefn, acquirefn, desc):
         try:


More information about the Mercurial-devel mailing list