Is there a way to "close" a branch?

Asked by Bartek Kania

Hi!

I'm wondering if there is a way to mark a branch as closed or locked so that devs
cannot commit to it anymore.

Basically, what I want to do is to branch from "trunk", do some development in the new branch and when it is finished merge it back to trunk.
After this I would like to close/lock the branch so no further commits may be done to it.

All branches in the example above live in a shared repository and are accessed using the smart server over https.

Thanks!
/B

Question information

Language:
English Edit question
Status:
Solved
For:
Bazaar Edit question
Assignee:
Andrew Bennetts Edit question
Solved by:
Andrew Bennetts
Solved:
Last query:
Last reply:
Revision history for this message
Vincent Ladeuil (vila) said :
#1

>>>>> Bartek Kania <email address hidden> writes:

    > New question #80723 on Bazaar:
    > https://answers.launchpad.net/bzr/+question/80723

    > Hi!

    > I'm wondering if there is a way to mark a branch as closed or
    > locked so that devs
    > cannot commit to it anymore.

    > Basically, what I want to do is to branch from "trunk", do some
    > development in the new branch and when it is finished merge it
    > back to trunk.
    > After this I would like to close/lock the branch so no further
    > commits may be done to it.

Once your branch is merged back to trunk, it will remain
accessible from there forever.

Simply looking at your trunk history will not only reveal when
your branch was merged but also all commits that were done in
that branch until the merge.

If anybody do further commits in that branch now, they will not
be merged automatically into trunk.

So, really, if your point is to ensure that your branch is not
modified once it is merged, then just use the merged branch !

Issuing:

 bzr log --include-merges -c <revno-where-the-branch-were-merged>

Will tell you what was the last revno in your branch. You can then do:

  bzr branch -r<last-revno-in-my-branch> trunk a-new-place-for-my-branch

and restore your branch whenever and wherever you want.

Revision history for this message
Bartek Kania (egnarf) said :
#2

What I want to prevent is people forgetting to change branch and committing to a branch that is no longer used.
And then forgetting all about it and wondering where their code disapeared.
The solution you propose won't help with this unfortunately.

Ideally I'd like some way to mark this branch as closed and bzr to inform the forgetful that this branch is no longer used.

So my point is not really to ensure that it is not modified, but rather to avoid the possibility of a commit ending up in the "closed" branch and being forgotten.

/B

Revision history for this message
Vincent Ladeuil (vila) said :
#3

>>>>> Bartek Kania <email address hidden> writes:

    > Question #80723 on Bazaar changed:
    > https://answers.launchpad.net/bzr/+question/80723

    > Status: Answered => Open

    > Bartek Kania is still having a problem:

    > What I want to prevent is people forgetting to change
    > branch and committing to a branch that is no longer used.

Could you better define what you mean by 'no longer used' ?

Used by who ? Under which kind of setup ?

    > And then forgetting all about it and wondering where their
    > code disapeared.

Huh ? Disappeared ? Again, can you tell us what kind of setup
you're using here ?

Commits made in a branch don't disappear, especially in a shared
repository, where even if the *branch* disappear, the tip of that
branch is still in the repository waiting to be used to create a
branch from it.

    > The solution you propose won't help with this
    > unfortunately.

    > Ideally I'd like some way to mark this branch as closed and
    > bzr to inform the forgetful that this branch is no longer
    > used.

Now, if you're using a setup where all devs use checkouts or
bound branches to a central server, you can try to make the
'.bzr' directory and all its contained files read only and the
subsequent commits will fail, that's a dirty way to achieve what
you want. Renaming the branch directory by appending '-closed'
will be a bit less dirty while provoking the same kind of
heads up for the developer though.

But I'd really like a better description of your workflow here as
I don't think that's the right way to achieve what you want.

    > So my point is not really to ensure that it is not
    > modified, but rather to avoid the possibility of a commit
    > ending up in the "closed" branch and being forgotten.

I don't understand what you mean by 'forgotten'. Surely the dev
doing the commit knows about it, can look at his branch ('bzr
info' will also display all branches related to that one: the
parent branch, the push branch, the submit branch, etc).

So even if he don't realize for some time *where* his code is
committed, he can find it back quite quickly.

Revision history for this message
Martin Pool (mbp) said :
#4

2009/8/24 Vincent Ladeuil <email address hidden>:
>    > What I want to prevent is people forgetting to change
>    > branch and committing to a branch that is no longer used.
>
> Could you better define what you mean by 'no longer used' ?
>
> Used by who ? Under which kind of setup ?
>
>    > And then forgetting all about it and wondering where their
>    > code disapeared.
>
> Huh ? Disappeared ? Again, can you tell us what kind of setup
> you're using here ?

I think I can see where Bartek is going: for instance you might have a
feature branch that's been abandoned, or a release branch from which
you no longer plan to make releases. I think it's reasonably common
in more centralized systems to close them by essentially making them
readonly and giving a message to explain why.

In a distributed system where there are typically more branches the
need may seem greater.

But, of course it's good to understand the real case here.

We could support it for a particular centralized branch and that might
be good, but is that enough protection against people's code being
left behind on their own machines?

Is it perhaps better to let people push to it, but make those changes
obvious and not lost?

Could this be done through a server-side hook?

--
Martin <http://launchpad.net/~mbp/>

Revision history for this message
Bartek Kania (egnarf) said :
#5

Martin,
You are indeed correct in your understanding of my problem.
It is precisely this problem I have with feature branches being merged into trunk, and then abandoned.
Sometimes someone has some testserver where they forgot they were using a feature branch and therefore commits a patch to the wrong branch.

I'm running a totally centralized setup. Actually I'm preparing for a switch from svn to bzr. None of the devs have much experience with good VCS systems, so currently code won't be forgotten on local branches since noone will know they can do it =)

Doing it in a server hook is fine by me, I can even code it up and contribute it if other people want it.
The problem would be to add a command to the bzr client that sends the info to the server. Unfortunately I can't really have much in the way of special client-side plugins because of the management issues that come with that.
I am not yet proficient enough in bzr hooks to solve it entirely on my own.

I've figured out this kinda hackish way to do it by removing the branch and archiving it:
   bzr reconfigure --standalone thebranch
   mv thebranch /some/archive/
but that's a bit too manual and wouldn't allow the devs to do it themselves.
(btw, is there some better way to remove a branch than the one above?)

Ideally I'd like something below that i've seen in some other vcs but can't remember which:
   bzr commit --close -m "Development done here, so closing branch" thebranch

/B

Revision history for this message
Martin Pool (mbp) said :
#6

I'm not sure what to do next. It seems like you need two things:

1- maybe help on writing a hook that locks some branches?
2- a way to write a client-side command to turn this on?

Revision history for this message
Bartek Kania (egnarf) said :
#7

a hook would be a great solution.
My problem with hooks is however that I don't have any method to ensure all devs have the correct plugins installed, so I need to do it server side...

Is there a way for me to do this?

It could even be something so ugly as a special keyword in the commit message triggering it, if that's possible.

I don't see any hook that I can use server side that would allow me to do it.
I tried looking at the tip-change hooks, but those hooks don't seem to be able to access the commit message.

Revision history for this message
Best Andrew Bennetts (spiv) said :
#8

For a server hook, the pre_change_branch_tip and post_change_branch_tip Branch hooks should be what you need.

It sounds like you want two hooks:

 1. a hook to close a branch (a post_change_branch_tip hook, I think)
 2. a hook to refuse further changes to a closed branch. (a pre_change_branch_tip hook)

The pre_change_branch_tip hook is called with an object that has the branch, and the old and proposed new revision ids. So a hook function like this could check the commit message for "[closed]":

REPO_DIR = '/root/of/your/repo'
CLOSED_BRANCH_LIST = '/etc/closed-branches.txt'
def should_close_this_branch(params):
  new_rev = params.branch.repository.get_revision(params.new_revid)
  commit_msg = new_rev.message
  if '[closed]' in commit_msg:
    # add this branch to a list of closed branches, e.g.
    import os
    from bzrlib.osutils import canonical_relpath
    closed_branch_list = open(CLOSED_BRANCH_LIST,'w')
    closed_branch_list.seek(0, os.SEEK_END)
    branches_base_dir = REPO_DIR
    branch_dir = params.branch.bzrdir.root_transport.external_url()
    branch_relpath = canonical_relpath(REPO_DIR, branch_dir)
    closed_branch_list.write(branch_relpath + '\n')
    closed_branch_list.close()

Then a pre_change_branch_tip hook function like this could check if this branch is in that list:

def prevent_changing_closed_branch(params):
    if params.new_revid == params.old_revid:
        # No change. Unusual, but not forbidden.
        return
    branch_dir = params.branch.bzrdir.root_transport.external_url()
    branch_relpath = canonical_relpath(REPO_DIR, branch_dir)
    closed_branch_list = open(CLOSED_BRANCH_LIST,'r')
    for line in closed_branch_list:
        line = line[:-1] # strip newline
        if line == branch_relpath:
            raise bzrlib.errors.TipChangeRejected(
                'Branch %s is closed' % (branch_relpath,))

(I haven't tested this code, and I've omitted the hook registration calls, but hopefully that's enough to give a clear idea of how this would work.)

Possible improvements might be using a revision property instead of a commit message, and storing the closed branches in something more efficient than an unsorted text file, but this is probably good enough.

I'm going to take this example and add it to the documentation, or at least the contrib/ directory, I think. Thanks for the question!

Revision history for this message
Bartek Kania (egnarf) said :
#9

Yep,
that should do it!

Thanks a lot!

Revision history for this message
Bartek Kania (egnarf) said :
#10

Thanks Andrew Bennetts, that solved my question.

Revision history for this message
Bartek Kania (egnarf) said :
#11

Hi!

I would like to contribute my variant of Andrews solution.
In my version a file called '.CLOSE' is created in the branch directory and contains information about the commit closing the branch.
Feel free to use this code in any manner you wish, I'm putting it in the public domain.

"""
Make it possible to close branch by committing a messade containing
[BRANCH CLOSED] within it.
"""

import os
import re
import StringIO
from bzrlib.directory_service import directories
from bzrlib import errors
from bzrlib import branch
from bzrlib.osutils import *

def pre_change_branch_tip(tip):
    if tip.branch.bzrdir.root_transport.has(".CLOSED"):
        f = tip.branch.bzrdir.root_transport.get(".CLOSED")
        msg = "Branch is CLOSED:\n"
        msg += str(f.read())
        f.close()
        raise bzrlib.errors.TipChangeRejected(msg)

def post_change_branch_tip(tip):
    new_rev = tip.branch.repository.get_revision(tip.new_revid)
    commit_msg = new_rev.message
    if '[BRANCH CLOSED]' in commit_msg:
        msg = "Revision: %s\n" % str(tip.new_revno)
        msg += "ID: %s\n" % str(tip.new_revid)
        msg += commit_msg
        sio = StringIO.StringIO(msg)
        tip.branch.bzrdir.root_transport.put_file(".CLOSED", sio)

# Register the hook(s)
branch.Branch.hooks.install_named_hook('pre_change_branch_tip',
                                       pre_change_branch_tip,
                                       'Check if branch is open')
branch.Branch.hooks.install_named_hook('post_change_branch_tip',
                                       post_change_branch_tip,
                                       'Close branch if user requested it')