I use TortoiseHg 1.1 and in our team we tend to create named branches for each case we work on then merge with default when we're done. Unfortunately most of the named branches don't get closed. This isn't a huge problem but it's annoying when you try and filter by the branch you are currently working on and there is a massive list of old branches to search through.
So is there an easy way in TortoiseHg or from the command line to close multiple branches quickly without having to manually commit and close on each branch?
Unfortunately no.
You need to close each branch separately with its own commit on that branch.
The best way to do that is to use the command line, you could even create a simple batch file to close a bunch of them:
for %%f in (branch1 branch2 branch4) do (
hg up "%%f"
if exitcode 1 goto quit
hg commit --close-branch -m "closing branch %%f"
if exitcode 1 goto quit
)
:quit
The workflow I use is to use the bookmarks extension to keep only local bookmarks for my lines of development, this way I use normal unnamed branches in Mercurial, but can still easily tell them apart locally. The commit messages are used to separate them later.
This way I don't have a bunch of named branches cluttering up my history, I don't have to remember to close them, and I only have to keep track of the branches I use locally.
See the following for all the possible options:
https://www.mercurial-scm.org/wiki/PruningDeadBranches
of which the closing branch option can be used.
hg up -C badbranch
hg commit --close-branch -m 'close badbranch, this approach never worked'
hg up -C default # switch back to "good" branch
Also, my understanding has been that it is preferable to clone for most work and use named branches only for few possible long lived trains of development.
Here is a powershell script that will close all active branches except the default.
Add your own filtering logic to exclude branches you don't want to close.
$branches = hg branches -a | sort
$exclude = "default"
foreach ($item in $branches)
{
$branch = $item.Split([string[]] " ", [StringSplitOptions]::RemoveEmptyEntries)[0]
if(!$exclude.Contains($branch))
{
hg update $branch
hg commit --close-branch -m "closing old branch"
hg status
Write-Host $branch
}
}
hg push
Okay so this is way late, but I set out to do this via bash and after a lot of pain I got a working solution. Take note that this does not do an hg push at the end, because I wanted to look over the results in tortoise before pushing.
#Protected branches to not close
develop='develop'
default='default'
IFS=$'\n'
#Get all branches
allBranches=$( hg branches | sort )
#Loop over and close old branches
for item in $allBranches
do
#Trim off everything but the branch name
branch=$( echo $item | sed -r 's/\s*[0-9]+:[0-9a-f]+(\s\(inactive\))?$//' )
if [ $branch != $develop ] && [ $branch != $default ]
then
hg update $branch
hg commit --close-branch -m "Closing old branch"
fi
done
After running the script I asked the team which branches they were actively working on and stripped those closure commits.
Related
As a followup for Mercurial: enforce "hg pull -u" before "hg commit"
I have started to use a hook
[hooks]
pretxnchangegroup.forbid_2heads = /usr/local/bin/forbid_2head.sh
where forbid_2head.sh looks like this
#!/bin/bash
BRANCH=`hg branch`
COUNT=`hg heads --template '{branch}|{rev}\n' | grep ^${BRANCH} | wc -l`
if [ "$COUNT" -ne "1" ] ; then
echo "=========================================================="
echo "Trying to push more than one head, which is not allowed"
echo "You seem to try to add changes to an old changeset!"
echo "=========================================================="
exit 1
fi
exit 0
It is derivative of the script found at http://tutorials.davidherron.com/2008/10/forbidding-multiple-heads-in-shared.html
where I do allow multiple named branches.
The problem I have now is that
it stops hg push -f which is what I wanted
it also stops hg pull in case there are incoming changeset and I have commits outgoing. This is indeed bad
Can I in any way reuse the same script but change the hook setup and stop "hg push -f"?
Or can I in forbid_2head.sh know whether this is a push or pull command running?
First, the script isn't completely correct: it just counts the number of heads in the branch currently checked out on the server (the one hg branch) reports. You could improve it by using
hg heads tip
to get the heads of the branch of tip. But someone might push changesets on more than one branch at a time, so what you really want is
hg heads --template '{branch}\n' $HG_NODE:tip
to find branch heads for the branches touched by $HG_NODE:tip (the changesets pushed in the not-yet-committed transaction). You can then compare that with
hg log --template '{branch}\n' -r $HG_NODE:tip | sort -u
which are the branches touched by the changegroup.
If you don't want to allow existing multiple heads, then you can simplify the above to just
$(hg heads --template 'x' | wc -c) -eq $(hg branches | wc -l)
which just tests that the number of branch heads is equal to the number of branches — i.e., that there is exactly one branch head per named branch.
With that out of the way, let me mention $HG_SOURCE. That environment variable is set by Mercurial when it runs the hook: it has the value push if the changegroup is being pushed into the repository using direct filesystem access, and the value serve if the changegroup is coming in over SSH or HTTP. See the Mercurial book.
So, to conclude, I believe this is a good "forbid multiple heads" script:
#!/bin/sh
HEADS=$(hg heads --template 'x' | wc -c)
BRANCHES=$(hg branches | wc -l)
test $HG_SOURCE = 'serve' -a $HEADS -ne $BRANCHES
That may look paradoxical, I know that secret changesets are meant to be private, but what if I want to backup those secret changesets?
I work with some branches in parallel and sometimes I want to push one, but not the others. To achieve that, I work in separate clones but I hate that.
So now mercurial has phases, I can make secret branches and have everything in the same repository. The problem is that between the beginning of the secret branch and its publication, I want to backup those secret changesets (I have a clone in another machine just to hold my backups in case something happens with my local repo or my machine).
Is there a way of doing that or my workflow is completly wrong?
No need to mark anything secret. If you only want to push one branch, use:
hg push -r REV
This will push REV and its ancestors only.
Secret is good for Mercurial patch queue revisions, since they can't be pushed anyway and it prevents a local clone from copying them.
Draft is good for tracking unpushed changes. If you still want to back them up, pushing them will flip them to Public, but you can reset them back to draft (as compared to another repository) with:
hg phase -fd 'outgoing(URL)'
(URL can be blank for the default push repo).
It seems like phases are still relatively new and some workflows, such as this, don't seem to be included, yet. As of 2013-03-19, I believe the only way you can do this is manually changing the phases from secret to public.
You can use these commands from the command line:
for /f "delims=" %a in ('hg log --template "{rev} " -r "secret()"') do #set secret=%a
hg phase -d %secret%
hg push -f
hg phase -sf %secret%
This doesn't change the commits to secret on the repository you are pushing to, I tried to change the push to do this (but was unsuccessful):
hg push -f --remotecmd hg phase -sf %secret%
The commits would have to match exactly for the remote hg command to work, but I couldn't get it to change on the remote repository anyway.
============================================================
If you want to use a GUI like TortoiseHG Workbench you will have to do this all manually (change the phases in the GUI on any repositories you want to) at the moment. Sorry, and hopefully, we can find a better solution, soon!
The best approach is a combination of #mischab1's answer, #mark-tolonen's answer and aliases.
By following mischab1's answer, you make sure that pushing to your backup location will not change the phase to "public".
Second step would be to add the backup location to your repository's hgrc/paths:
[paths]
default = ...
backup = backup_location
The next step is to define a backup command via alias in the global hgrc, e.g. "bubr" (for backup current branch) or "burev" (backup current rev).
[alias]
bubr = push -b . backup
burev = push -r . backup
hg bubr or hg burev will then push the current branch/revision to the location defined as "backup" path.
Edit This still has the drawback that you could accidentally push all changes with "hg push" so defining also an hg pubr command to push the current branch and not using "hg push" per default might be helpful.
This is the best I have come up with so far. I think its essentially equivalent to what you want push/pull to be able to do.
Mark all the secret changesets you want to transfer as draft
In the source repo run hg bundle -r last_draft_rev bundlefile.hg path\to\backup\repo
In the destination repo run hg unbundle bundlefile.hg
The changesets will come into the backup as DRAFT
Mark the first draft changeset as secret, and all its descendants will be marked thusly
I couldn't get #2 to work if the changesets were still marked secret.
#echo off
rem hgfullpull_naive.cmd
setlocal
set SRC_REPO=%~f1
set DST_REPO=%~f2
set TMP_DIR=%TEMP%\%~n0.tmp
set NODES_LIST=%TMP_DIR%\%~n0.%RANDOM%.tmp
if "%SRC_REPO%"=="" exit /b 1
if "%DST_REPO%"=="" exit /b 1
if "%SRC_REPO%"=="%DST_REPO%" exit /b 1
call :ALL
del /Q "%NODES_LIST%"
endlocal
goto :eof
:ALL
md "%TMP_DIR%"
hg log --rev "secret()" --template "{node}\n" --repository "%SRC_REPO%" >"%NODES_LIST%" || exit /b 1
call :CHANGE_PHASE "%SRC_REPO%" --draft
hg pull --repository "%DST_REPO%" "%SRC_REPO%"
call :CHANGE_PHASE "%SRC_REPO%" --secret
call :CHANGE_PHASE "%DST_REPO%" --secret
goto :eof
:CHANGE_PHASE
setlocal
set REPO=%~1
set PHASE=%~2
for /F "eol=; delims= usebackq" %%i IN ("%NODES_LIST%") DO (hg phase %PHASE% --force --rev %%i --repository "%REPO%")
endlocal
goto :eof
The easiest thing to do now-days, is to mark your backup repository as non-publishing by adding the following to its hgrc config file.
[phases]
publish = False
See Mercurial's Wiki for more info.
I have two heads, let's call them "A" (the good head) and "B" (the bad head). I want to merge them by taking everything from A and nothing from B. Basically, my merge of A and B is A.
When I try hg merge, it starts asking me about this file and that, and inevitably I get into trouble. I don't want any of that! How can I tell it to merge them and end up with A, preferably without any intermediate steps?
From the Mercurial tips at section 22. Keep "My" or "Their" files when doing a merge.
https://www.mercurial-scm.org/wiki/TipsAndTricks
Occasionally you want to merge two heads, but you want to throw away all changes from one of the heads, a so-called dummy merge. You can override the merge by using the ui.merge configuration entry:
$ hg --config ui.merge=internal:local merge #keep my files
$ hg --config ui.merge=internal:other merge #keep their files
Here local means parent of working directory, other is the head you want to merge with. This will leave out updates from the other head.
To merge X into the current revision without letting any of the changes from X come through, do:
hg --config ui.merge=internal:fail merge X
hg revert --all --rev .
The other approach is mentioned in : https://www.mercurial-scm.org/wiki/PruningDeadBranches
$ hg update -C tip # jump to one head
$ hg merge otherhead # merge in the other head
$ hg revert -a -r tip # undo all the changes from the merge
$ hg commit -m "eliminate other head" # create new tip identical to the old
One thing I came across and started using recently on some personal repos was just using the close-branch switch with commit. e.g.
$ hg update B
$ hg commit --close-branch -m "Abandoning branch"
In my reasoning, if you're blowing away one branch in favor of the other entirely, it's simply not a merge and it's silly to call it that. I'm relatively new to hg myself, and I seem to recall that --close-branch has not been around since the beginning and maybe that's why it doesn't have as much traction as the merging gyrations I usually see.
I discovered today that I can switch back to a branch even after I closed it. Why?
hg init abc
cd abc
echo 'readme1' > README1.txt
hg ci -Am "1st commit"
hg branch other-branch
echo 'readme2' > README2.txt
hg ci -Am "2nd commit"
hg update default
hg merge other-branch
hg ci -m "Merged other-branch into default"
hg update other-branch
hg ci -m "Closing other branch" --close-branch
hg update default
now I think I'm not supposed to do this
hg update other-branch
but it works ok
It confuses me and makes me feel somewhat uneasy.
UPDATE: sorry forgot to indicate that I'm using HG v.1.6
Like Amber said, when you close a branch, it just records that it is closed. As a consequence when you do "hg branches" you will just see "default", and not "other-branch".
However, as soon as you switch to this branch and commit something new, it automatically reopen it (and thus appears again in the list of "hg branches"). You can also re-close it when you are done.
I find this feature actually desirable: Imagine you have created a "stable" branch in order to stabilize some code for a delivery, only allowing bug fixes on this branch. Now after the delivery, you can close the stable branch and develop new features again on default, switching to the next iteration and preparing the next delivery (assuming you are using scrum for instance). Now when three days after, your delivery customer finds a problem and demand to receive a fixed delivery, not wanting to wait for the next one, then you can easily switch to the stable branch, reproduce the problem, fix it (reopening the branch), redeploy, and finally close the branch again. This seems like a plausible scenario and a good Mercurial behavior to me.
Just my 0.02€ :-)
Cheers,
Christophe.
= The illiterate of the 21st century will not be those who cannot read or write; they will be those who cannot learn, unlearn, and relearn. --Alvin Toffler =
Closing a branch mostly just makes it not show up in certain lists:
https://www.mercurial-scm.org/wiki/PruningDeadBranches#Closing_branches
When using hg branch FeatureBranchName and publishing it to a central repo that is shared amongst developers, is there a way to eventually close the FeatureBranchName when its development has officially been merged with the default branch?
It would also be helpful if the FeatureBranchName was not visible when performing a hg branches command.
hg commit --close-branch
should be enough to mark a branch close. (see hg commit)
--close-branch
mark a branch as closed, hiding it from the branch list.
See also this thread:
My expectation is that I close a branch because this line of development has come to a dead end, and I don't want to be bothered with it any more.
Therefore, when a branch has been closed I shouldn't see it (in branches, heads, log, for instance) unless I explicitly ask to see closed branches.
I should note that I expect a closed branch to remain in the repository;
it may be useful in the future, and the commit --close-branch message
should at least explain why the branch was closed.
Pruning branches is another thing altogether.
Note: that "closing branch" business is one aspect seen as missing in Git, when compared to Mercurial:
Branches in git are, we’re always told, ephemeral things to be used and thrown away, and so far as I know git doesn’t have a way to indicate to your colleagues that you’re done with a branch;
the only way to do this is to delete it, or to hope they see the final merge commit and understand that the branch is closed to further development.
[In Mercurial] When you’re done with a branch, however, you cannot delete it from the repository; instead, you issue a commit which closes the branch, and Mercurial notes that the branch is closed. It’ll remain a permanent part of your repository history.
I wrote a simple script that completes the branch close, commands found at PruningDeadBranches.
## Script ##
#!/bin/bash
#script to close the not required branch in mercurial
hg up -C $1
if [ $? -eq 0 ]; then
echo "$1 is up"
else
echo "branch not found, please recheck your argument"
exit 1
fi
# if we are here then the branch is up, so we do the following
hg commit --close-branch -m 'this branch no longer required'
echo "$1 is closed"
hg up -C default
echo "default is up"
How to
Move to the local copy of the repository, and run this script by giving an argument. For example:
$./the_script_above.sh bad_branch_name_to_close
What does it do
This does the following:
If the branch exists, it updates to the given branch or else exists with
an error message.
It closes the branch.
Updates to the default branch.
Stops.