Mercurial [1] is a distributed source control management tool.
With it you can save snapshots of your work on documents and go back to these at all times.
Also you can easily collaborate with other people and use Mercurial to merge your work.
Someone changes something in text file you also worked on? No problem. If you didn't work on the same line, you can simply let Mercurial do an automatic merge and your work will be joined. (If you worked on the same line you'll need to select how you want to merge these two changes).
It doesn't need a network connection for normal operation, except when you want to push your changes over the internet or pull changes of others from the web, so its commands are fast. The time to do a commit is barely noticeable which makes atomic commits easy to do.
And if you already know subversion, the switch to Mercurial will be mostly painless.
But its most important strength is not its speed. It is that Mercurial just works. No hassle with complicated setup. No arcane commands. Almost everything I ever wanted to do with it just worked out of the box, and that's a rare and precious feature today.
And to answer a common question:
“Once you have learned git well, what use is hg?” — Ross Bartlett in Why Mercurial? [5]
I wish you much fun with Mercurial!
New version: draketo.de/software/mercurial-branching-strategy [12]
This is a complete collaboration model for Mercurial [13]. It shows you all the actions you may need to take, except for the basics already found in other tutorials like
Adaptions [16] optimize the model for special needs like maintaining multiple releases [17]1, grafting micro-releases [18] and an explicit code review stage [19].
Any model to be used by people should consist of simple, consistent rules. Programming is complex enough without having to worry about elaborate branching directives. Therefore this model boils down to 3 simple rules:
(1) you do all the work on
default
2 - except for hotfixes.(2) on
stable
you only do hotfixes, merges for release3 and tagging for release. Only maintainers4 touch stable.(3) you can use arbitrary feature-branches5, as long as you don’t call them
default
orstable
. They always start at default (since you do all the work on default).
To visualize the structure, here’s a 3-tiered diagram. To the left are the actions of programmers (commits and feature branches) and in the center the tasks for maintainers (release and hotfix). The users to the right just use the stable branch.6
[20]
An overview of the branching strategy. Click the image to get the emacs [21] org-mode [22] ditaa [23]-source.
Now we can look at all the actions you will ever need to do in this model:7
Regular development
commit changes: (edit); hg ci -m "message"
continue development after a release: hg update; (edit); hg ci -m "message"
Feature Branches
start a larger feature: hg branch feature-x; (edit); hg ci -m "message"
continue with the feature: hg update feature-x; (edit); hg ci -m "message"
merge the feature: hg update default; hg merge feature-x; hg ci -m "merged feature x into default"
close and merge the feature when you are done: hg update feature-x; hg ci --close-branch -m "finished feature x"; hg update default; hg merge feature-x; hg ci -m "merged finished feature x into default"
Tasks for Maintainers
create the repo: hg init reponame; cd reponame
first commit: (edit); hg ci -m "message"
create the stable branch and do the first release: hg branch stable; hg tag tagname; hg up default; hg merge stable; hg ci -m "merge stable into default: ready for more development"
apply a hotfix8: hg up stable; (edit); hg ci -m "message"; hg up default; hg merge stable; hg ci -m "merge stable into default: ready for more development"
do a release9: hg up stable; hg merge default; hg ci -m "(description of the main changes since the last release)" ; hg tag tagname; hg up default ; hg merge stable ; hg ci -m "merged stable into default: ready for more development"
That’s it. All that follows are a detailed example which goes through all actions one-by-one, adaptions to this workflow and the final summary.
This is the output of a complete example run 10 of the branching model, including all complications you should ever hit.
We start with the full history. In the following sections, we will take it apart to see what the commands do. So just take a glance, take in the basic structure and then move on for the details.
hg log -G
@ changeset: 15:855a230f416f
|\ tag: tip
| | parent: 13:e7f11bbc756c
| | parent: 14:79b616e34057
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:49 2013 +0100
| | summary: merged stable into default: ready for more development
| |
| o changeset: 14:79b616e34057
|/| branch: stable
| | parent: 7:e8b509ebeaa9
| | parent: 13:e7f11bbc756c
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:48 2013 +0100
| | summary: merged default into stable for release
| |
o | changeset: 13:e7f11bbc756c
|\ \ parent: 11:e77a94df3bfe
| | | parent: 12:aefc8b3a1df2
| | | user: Arne Babenhauserheide <bab@draketo.de>
| | | date: Sat Jan 26 15:39:47 2013 +0100
| | | summary: merged finished feature x into default
| | |
| o | changeset: 12:aefc8b3a1df2
| | | branch: feature-x
| | | parent: 9:1dd6209b2a71
| | | user: Arne Babenhauserheide <bab@draketo.de>
| | | date: Sat Jan 26 15:39:46 2013 +0100
| | | summary: finished feature x
| | |
o | | changeset: 11:e77a94df3bfe
|\| | parent: 10:8c423bc00eb6
| | | parent: 9:1dd6209b2a71
| | | user: Arne Babenhauserheide <bab@draketo.de>
| | | date: Sat Jan 26 15:39:45 2013 +0100
| | | summary: merged feature x into default
| | |
o | | changeset: 10:8c423bc00eb6
| | | parent: 8:dc61c2731eda
| | | user: Arne Babenhauserheide <bab@draketo.de>
| | | date: Sat Jan 26 15:39:44 2013 +0100
| | | summary: 3
| | |
| o | changeset: 9:1dd6209b2a71
|/ / branch: feature-x
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:43 2013 +0100
| | summary: x
| |
o | changeset: 8:dc61c2731eda
|\| parent: 5:4c57fdadfa26
| | parent: 7:e8b509ebeaa9
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:43 2013 +0100
| | summary: merged stable into default: ready for more development
| |
| o changeset: 7:e8b509ebeaa9
| | branch: stable
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:42 2013 +0100
| | summary: Added tag v2 for changeset 089fb0af2801
| |
| o changeset: 6:089fb0af2801
|/| branch: stable
| | tag: v2
| | parent: 4:d987ce9fc7c6
| | parent: 5:4c57fdadfa26
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:41 2013 +0100
| | summary: merge default into stable for release
| |
o | changeset: 5:4c57fdadfa26
|\| parent: 3:bc625b0bf090
| | parent: 4:d987ce9fc7c6
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:40 2013 +0100
| | summary: merge stable into default: ready for more development
| |
| o changeset: 4:d987ce9fc7c6
| | branch: stable
| | parent: 1:a8b7e0472c5b
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:39 2013 +0100
| | summary: hotfix
| |
o | changeset: 3:bc625b0bf090
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:38 2013 +0100
| | summary: 2
| |
o | changeset: 2:3e8df435bcb0
|\| parent: 0:f97ea6e468a1
| | parent: 1:a8b7e0472c5b
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:38 2013 +0100
| | summary: merged stable into default: ready for more development
| |
| o changeset: 1:a8b7e0472c5b
|/ branch: stable
| user: Arne Babenhauserheide <bab@draketo.de>
| date: Sat Jan 26 15:39:36 2013 +0100
| summary: Added tag v1 for changeset f97ea6e468a1
|
o changeset: 0:f97ea6e468a1
tag: v1
user: Arne Babenhauserheide <bab@draketo.de>
date: Sat Jan 26 15:39:36 2013 +0100
summary: 1
Let’s take the log apart to show the actions contributors will do.
Initializing and doing the first commit creates the first changeset:
o changeset: 0:f97ea6e468a1
tag: v1
user: Arne Babenhauserheide <bab@draketo.de>
date: Sat Jan 26 15:39:36 2013 +0100
summary: 1
Nothing much to see here.
Commands:
hg init test-branch; cd test-branch
(edit); hg ci -m "message"
We add the first tagging commit on the stable branch as release and merge back into default:
o changeset: 2:3e8df435bcb0
|\ parent: 0:f97ea6e468a1
| | parent: 1:a8b7e0472c5b
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:38 2013 +0100
| | summary: merged stable into default: ready for more development
| |
| o changeset: 1:a8b7e0472c5b
|/ branch: stable
| user: Arne Babenhauserheide <bab@draketo.de>
| date: Sat Jan 26 15:39:36 2013 +0100
| summary: Added tag v1 for changeset f97ea6e468a1
|
o changeset: 0:f97ea6e468a1
tag: v1
user: Arne Babenhauserheide <bab@draketo.de>
date: Sat Jan 26 15:39:36 2013 +0100
summary: 1
Mind the tag field which is now shown in changeset 0 and the branchname for changeset 1. This is the only release which will ever be on the default branch (because the stable branch only starts to exist after the first commit on it: The commit which adds the tag).
Commands:
hg branch stable
hg tag tagname
hg up default
hg merge stable
hg ci -m "merged stable into default: ready for more development"`
Now we just chuck along. The one commit shown here could be an arbitrary number of commits.
o changeset: 3:bc625b0bf090
| user: Arne Babenhauserheide <bab@draketo.de>
| date: Sat Jan 26 15:39:38 2013 +0100
| summary: 2
|
o changeset: 2:3e8df435bcb0
|\ parent: 0:f97ea6e468a1
| | parent: 1:a8b7e0472c5b
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:38 2013 +0100
| | summary: merged stable into default: ready for more development
Commands:
(edit)
hg ci -m "message"
If a hotfix has to be applied to the release out of order, we just update to the stable branch, apply the hotfix and then merge the stable branch into default11. This gives us changesets 4 for the hotfix and 5 for the merge (2 and 3 are shown as reference).
o changeset: 5:4c57fdadfa26
|\ parent: 3:bc625b0bf090
| | parent: 4:d987ce9fc7c6
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:40 2013 +0100
| | summary: merge stable into default: ready for more development
| |
| o changeset: 4:d987ce9fc7c6
| | branch: stable
| | parent: 1:a8b7e0472c5b
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:39 2013 +0100
| | summary: hotfix
| |
o | changeset: 3:bc625b0bf090
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:38 2013 +0100
| | summary: 2
| |
o | changeset: 2:3e8df435bcb0
|\| parent: 0:f97ea6e468a1
| | parent: 1:a8b7e0472c5b
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:38 2013 +0100
| | summary: merged stable into default: ready for more development
Commands:
hg up stable
(edit)
hg ci -m "message"
hg up default
hg merge stable
hg ci -m "merge stable into default: ready for more development"
To do a regular release, we just merge the default branch into the stable branch and tag the merge. Then we merge stable back into default. This gives us changesets 6 to 812. The commit-message you use for the merge to stable will become the description for your tag, so you should choose a good description instead of “merge default into stable for release”. Userfriendly, simplified release notes would be a good choice.
o changeset: 8:dc61c2731eda
|\ parent: 5:4c57fdadfa26
| | parent: 7:e8b509ebeaa9
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:43 2013 +0100
| | summary: merged stable into default: ready for more development
| |
| o changeset: 7:e8b509ebeaa9
| | branch: stable
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:42 2013 +0100
| | summary: Added tag v2 for changeset 089fb0af2801
| |
| o changeset: 6:089fb0af2801
|/| branch: stable
| | tag: v2
| | parent: 4:d987ce9fc7c6
| | parent: 5:4c57fdadfa26
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:41 2013 +0100
| | summary: merge default into stable for release
| |
o | changeset: 5:4c57fdadfa26
|\| parent: 3:bc625b0bf090
| | parent: 4:d987ce9fc7c6
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:40 2013 +0100
| | summary: merge stable into default: ready for more development
Commands:
hg up stable
hg merge default
hg ci -m "merge default into stable for release"
hg tag tagname
hg up default
hg merge stable
hg ci -m "merged stable into default: ready for more development"
Now we want to do some larger development, so we use a feature branch. The one feature-commit shown here (x) could be an arbitrary number of commits, and as long as you stay in your branch, the development of your colleagues will not disturb your own work. Once the feature is finished, we merge it into default. The feature branch gives us changesets 9 to 13 (with 10 being an example for an unrelated intermediate commit on default).
o changeset: 13:e7f11bbc756c
|\ parent: 11:e77a94df3bfe
| | parent: 12:aefc8b3a1df2
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:47 2013 +0100
| | summary: merged finished feature x into default
| |
| o changeset: 12:aefc8b3a1df2
| | branch: feature-x
| | parent: 9:1dd6209b2a71
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:46 2013 +0100
| | summary: finished feature x
| |
o | changeset: 11:e77a94df3bfe
|\| parent: 10:8c423bc00eb6
| | parent: 9:1dd6209b2a71
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:45 2013 +0100
| | summary: merged feature x into default
| |
o | changeset: 10:8c423bc00eb6
| | parent: 8:dc61c2731eda
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:44 2013 +0100
| | summary: 3
| |
| o changeset: 9:1dd6209b2a71
|/ branch: feature-x
| user: Arne Babenhauserheide <bab@draketo.de>
| date: Sat Jan 26 15:39:43 2013 +0100
| summary: x
|
o changeset: 8:dc61c2731eda
|\ parent: 5:4c57fdadfa26
| | parent: 7:e8b509ebeaa9
| | user: Arne Babenhauserheide <bab@draketo.de>
| | date: Sat Jan 26 15:39:43 2013 +0100
| | summary: merged stable into default: ready for more development
Commands:
Start the feature
hg branch feature-x
(edit)
hg ci -m "message"
Do an intermediate commit on default
hg update default
(edit)
hg ci -m "message"
Continue working on the feature
hg update feature-x
(edit)
hg ci -m "message"
Merge the feature
hg update default
hg merge feature-x
hg ci -m "merged feature x into default"`
Close and merge a finished feature
hg update feature-x
hg ci --close-branch -m "finished feature x"
hg update default; hg merge feature-x
hg ci -m "merged finished feature x into default"
Note: Closing the feature branch hides that branch in the output of hg branches
(except when using --closed
) to make the repository state lean and simple while still keeping the feature branch information in history. It shows your collegues, that they no longer have to keep the feature in mind as soon as they merge the most recent changes from the default branch into their own feature branches.
Note: To make the final merge of your feature into default easier, you can regularly merge the default branch into the feature branch.
Note: We use feature branches to ensure that new clones start at a revision which other developers can directly use. With bookmarks you could get trapped on a feature-head which might not be merged to default
for quite some time. For more reasons, see the bookmarks footnote.
The final action is to have a maintainer do a regular merge of default
into stable
to reach a state from which we could safely do a release. Since we already showed how to do that, we are finished here.
This realizes the successful Git branching model [24]13 with Mercurial [13] while maintaining one release at any given time.
If you have special needs, this model can easily be extended to fullfill your requirements. Useful extensions include:
All these extensions are orthogonal, so you can use them together without getting side-effects.
To use the branching model with multiple simultaneously maintained releases, you only need to change the hotfix procedure: When applying a hotfix, you go back to the old release with hg update tagname
, fix there, add a new tag for the fixed release and then update to the next release. There you merge the new fix-release and do the same for all other releases. If the most recent release is not the head of the stable branch, you also merge into stable. Then you merge the stable branch into default, as for a normal hotfix.14
With this merge-chain you don’t need special branches for releases, but all changesets are still clearly recorded. This simplification over git is a direct result of having real anonymous branching in Mercurial.
hg update release-1.0
(edit)
hg ci -m "message"
hg tag release-1.1
hg update release-2.0
hg merge release-1.1
hg ci -m "merged changes from release 1.1"
hg tag release-2.1
… and so on
In the Diagram this just adds a merge path from the hotfix to the still maintained releases. Note that nothing changed in the workflow of programmers.
[20]
An overview of the branching strategy with maintained releases. Click the image to get the emacs [21] org-mode [22] ditaa [23]-source.
If you need to test parts of the current development in small chunks, you can graft micro releases. In that case, just update to stable and merge the first revision from default, whose child you do not want, and graft later changes15.
Example for the first time you use micro-releases16:
You have changes 1, 2, 3, 4 and 5 on default. First you want to create a release which contains 1 and 4, but not 2, 3 or 5.
hg update 1 hg branch stable hg graft 4
As usual tag the release and merge stable back into default:
hg tag rel-14 hg update default hg merge stable hg commit -m "merge stable into default. ready for more development"
Example for the second and subsequent releases:
Now you want to release the change 2 and 5, but you’re still not ready to release 3. So you merge 2 and graft 5.
hg update stable hg merge 2 hg commit -m "merge all changes until 2 from default" hg graft 5
As usual tag the release and finally merge stable back into default:
hg tag rel-1245 hg update default hg merge stable hg commit -m "merge stable into default. ready for more development"
The history now looks like this17:
@ merge stable into default. ready for more development (default)
|\
| o Added tag rel-1245 for changeset 4e889731c6ca (stable)
| |
| o 5 (stable)
| |
| o merge all changes until 2 from default (stable)
| |\
o---+ merge stable into default. ready for more development (default)
| | |
| | o Added tag rel-14 for changeset cc2c95dd3f27 (stable)
| | |
| | o 4 (stable)
| | |
o | | 5 (default)
| | |
o | | 4 (default)
| | |
o | | 3 (default)
|/ /
o / 2 (default)
|/
o 1 (default)
|
o 0 (default)
In the Diagram this just adds graft commits to stable:
[20]
An overview of the branching strategy with grafted micro-releases. Click the image to get the emacs [21] org-mode [22] ditaa [23]-source.
Grafted micro-releases add another layer between development and releases. They can be necessary in cases where testing requires actually deploying a release, as for example in Freenet [25].
If you want to add a separate review stage, you can use a review branch1819 into which you only merge or graft reviewed changes. The review branch then acts as a staging area for all changes which might go into a release.
To use this extension of the branching model, just create a branch on default called review
in which you merge or graft reviewed changes. The first time you do that, you update to the first commit whose children you do not want to include. Then create the review branch with hg branch review
and use hg graft REV
to pull in all changes you want to include.
On subsequent reviews, you just update to review with hg update nextrelease, merge the first revision which has a child you do not want with hg merge REV
and graft additional later changes with hg graft REV
as you would do it for micro-releases..
In both cases you create the release by merging the review
branch into stable.
A special condition when using a review branch is that you always have to merge hotfixes into the review branch, too, because the review branch does not automatically contain all changes from the default branch.
In the Diagram this just adds the review
branch between default
and stable
instead of the release merge. Also it adds the hotfix merge to the review
branch.
[20]
An overview of the branching strategy with areview
branch. Click the image to get the emacs [21] org-mode [22] ditaa [23]-source.
In the default flow when the users directly use the stable branch you do QA on the default branch before merging to stable. QA is a part of the maintainers job, there.
If your users want external QA, that QA is done for revisions on the stable branch. It is restricted to signing good revisions. Any changes have to be done on the default branch - except for hotfixes for previously signed releases. It is only a hotfix, if your users could already be running a broken version.
There is also an extension with an explicit review branch. There QA is done on the review branch.
This realizes the successful Git branching model [24] with Mercurial [13].
We now have nice graphs, examples, potential extensions and so on. But since this strategy uses Mercurial instead of git, we don’t actually need all the graphics, descriptions and branch categories in the git version - or in this post.
Instead we can boil all of this down to 3 simple rules:
(1) you do all the work on
default
- except for hotfixes.(2) on
stable
you only do hotfixes, merges for release and tagging for release. Only maintainers touch stable.(3) you can use arbitrary feature-branches, as long as you don’t call them
default
orstable
. They always start at default (since you do all the work on default).
They are the rules you already know from the starting summary. Keep them in mind and you’re good to go. And when you’re doing regular development, there is only one rule to remember:
You do all the work on default.
That’s it. Happy hacking!
if you need to maintain multiple very different releases simultanously, see ⁰ or 20 for adaptions ↩
default
is the default branch. That’s the named branch you use when you don’t explicitely set a branch. Its alias is the empty string, so if no branch is shown in the log (hg log
), you’re on the default branch. Thanks to John for asking! ↩
If you want to release the changes from default
in smaller chunks, you can also graft specific changes into a release preparation branch and merge that instead of directly merging default into stable. This can be useful to get real-life testing of the distinct parts. For details see the extension Graft changes into micro-releases. ↩
Maintainers are those who do releases, while they do a release. At any other time, they follow the same patterns as everyone else. If the release tasks seem a bit long, keep in mind that you only need them when you do the release. Their goal is to make regular development as easy as possible, so you can tell your non-releasing colleagues “just work on default and everything will be fine”. ↩
This model does not use bookmarks, because they don’t offer benefits which outweight the cost of introducing another concept: If you use bookmarks for differenciating lines of development, you have to define the canonical revision to clone by setting the @
bookmark. For local work and small features, bookmarks can be used quite well, though, and since this model does not define their use, it also does not limit it.
Additionally bookmarks could be useful for feature branches, if you use many of them (in that case reusing names is a real danger and not just a rare annoyance) or if you use release branches:
“What are people working on right now?” → hg bookmarks
“Which lines of development do we have in the project?” → hg branches
↩
Those users who want external verification can restrict themselves to the tagged releases - potentially GPG signed [26] by trusted 3rd-party reviewers. GPG signatures are treated like hotfixes: reviewers sign on stable (via hg sign
without options) and merge into default. Signing directly on stable reduces the possibility of signing the wrong revision. ↩
hg pull
and hg push
to transfer changes and hg merge
when you have multiple heads on one branch are implied in the actions: you can use any kind of repository structure and synchronization scheme. The practical actions only assume that you synchronize your repositories with the other contributors at some point. ↩
Here a hotfix is defined as a fix which must be applied quickly out-of-order, for example to fix a security hole. It prompts a bugfix-release which only contains already stable and tested changes plus the hotfix. ↩
If your project needs a certain release preparation phase (like translations), then you can simply assign a task branch. Instead of merging to stable, you merge to the task branch, and once the task is done, you merge the task branch to stable. An Example: Assume that you need to update translations before you release anything. (next part: init: you only need this once) When you want to do the first release which needs to be translated, you update to the revision from which you want to make the release and create the “translation” branch: hg update default; hg branch translation; hg commit -m "prepared the translation branch"
. All translators now update to the translation branch and do the translations. Then you merge it into stable: hg update stable; hg merge translation; hg ci -m "merged translated source for release"
. After the release you merge stable back into default as usual. (regular releases) If you want to start translating the next time, you just merge the revision to release into the translation branch: hg update translation; hg merge default; hg commit -m "prepared translation branch"
. Afterwards you merge “translation” into stable and proceed as usual. ↩
To run the example and check the output yourself, just copy-paste the following your shell: LC_ALL=C sh -c 'hg init test-branch; cd test-branch; echo 1 > 1; hg ci -Am 1; hg branch stable; hg tag v1 ; hg up default; hg merge stable; hg ci -m "merged stable into default: ready for more development"; echo 2 > 2; hg ci -Am 2; hg up stable; echo 1.1 > 1; hg ci -Am hotfix; hg up default; hg merge stable; hg ci -m "merge stable into default: ready for more development"; hg up stable; hg merge default; hg ci -m "merge default into stable for release" ; hg tag v2; hg up default ; hg merge stable ; hg ci -m "merged stable into default: ready for more development" ; hg branch feature-x; echo x > x ; hg ci -Am x; hg up default; echo 3 > 3; hg ci -Am 3; hg merge feature-x; hg ci -m "merged feature x into default"; hg update feature-x; hg ci --close-branch -m "finished feature x"; hg update default; hg merge feature-x; hg ci -m "merged finished feature x into default"; hg up stable ; hg merge default; hg ci -m "merged default into stable for release"; hg up default; hg merge stable ; hg ci -m "merged stable into default: ready for more development"; hg log -G'
↩
We merge the hotfix into default to define the relevance of the fix for general development. If the hotfix also affects the current line of development, we keep its changes in the merge. If the current line of development does not need the hotfix, we discard its changes in the merge. We do this to ensure that it is clear in future how to treat the hotfix when merging new changes: let the merge record the decision. ↩
We can also merge to stable regularly as soon as some set of changes is considered stable, but without making an actual release (==tagging). That way we always have a stable branch which people can test without having to create releases right away. The releases are those changesets on the stable branch which carry a tag. ↩
If you look at the Git branching model [24] which inspired this Mercurial branching model, you’ll note that its diagram [27] is a lot more complex than the diagram of this Mercurial version [28].
The reason for that is the more expressive history model of Mercurial. In short: The git version has 5 types of branches: feature, develop, release, hotfix and master (for tagging). With Mercurial you can reduce them to 3: default, stable and feature branches:
stable
followed by a merge to default
, so we also need no branch for them (down to 3 branch-types). And if we only maintain one release at a time, we only need one branch for them: stable (down from branch-type to single branch). So we get down from 5 mandatory branches (2 of them are categories containing multiple branches) to 2 simple branches without losing functionality.
And new developers only need to know two things about our branching model to contribute:
“If you use feature branches, don’t call them
default
orstable
. And don’t touchstable
”.
Merging old releases into new ones sounds like a lot of work. If you get that feeling, then have a look how many releases you really maintain right now. In my Gentoo tree most programs actually have only one single release, so using actual release branches would incur an additional burden without adding real value. You can also look at the rule of thumb whether to choose feature branches instead ↩
If you want to make sure that every changeset on stable
is production-ready, you can also start a new release-branch on stable, then merge the first revision, whose child you do not want, into that branch and graft additional changes. Then close the branch and merge it into stable. You can achieve the same with much lower overhead (unneeded complexity) by changing the requirement to “every tagged revision on stable
is production-ready”. To only see tagged revisions on stable, just use hg log -r "branch(stable) and tag()"
. This also works for incoming and outgoing, so you can use it for triggering a build system. ↩
To test this workflow yourself, just create the test repository with hg init 12345; cd 12345; for i in {0..5}; do echo $i > $i; hg ci -Am $i; done
. ↩
The short graphlog for the grafted micro-releases was created via hg glog --template "{desc} ({branch})"
. ↩
The review branch is a special preparation-branch, because it can get discontinous changes, if maintainers decide to graft some changes which have ancestors they did not review yet. ↩
We use one single review branch which gets reused at every review to ensure that there are no changes in stable which we did not have in the review. As alternative, you could use one branch per review. In that case, ensure that you start the review-* branches from stable
and not from default
. Then merge and graft the changes from default which you want to review for inclusion in your next release. ↩
If you want to adapt the model to multiple very distinct releases, simply add multiple release-branches (i.e. release-x
). Then hg graft
the changes you want to use from default or stable into the releases and merge the releases into stable to ensure that the relationship of their changes to current changes is clear, recorded and will be applied automatically by Mercurial in future merges21. If you use multiple tagged releases, you need to merge the releases into each other in order - starting from the oldest and finishing by merging the most recent one into stable - to record the same information as with release branches. Additionally it is considered impolite to other developers to keep multiple heads in one branch, because with multiple heads other developers do not know the canonical tip of the branch which they should use to make their changes - or in case of stable, which head they should merge to for preparing the next release. That’s why you are likely better off creating a branch per release, if you want to maintain many very different releases for a long time. If you only use tags on stable for releases, you need one merge per maintained release to create a bugfix version of one old release. By adding release branches, you reduce that overhead to one single merge to stable per affected release by stating clearly, that changes to old versions should never affect new versions, except if those changes are explicitely merged into the new versions. If the bugfix affects all releases, release branches require two times as many actions as tagged releases, though: You need to graft the bugfix into every release and merge the release into stable.22 ↩
If for example you want to ignore that change to an old release for new releases, you simply merge the old release into stable and use hg revert --all -r stable
before committing the merge. ↩
A rule of thumb for deciding between tagged releases and release branches is: If you only have a few releases you maintain at the same time, use tagged releases. If you expect that most bugfixes will apply to all releases, starting with some old release, just use tagged releases. If bugfixes will only apply to one release and the current development, use tagged releases and merge hotfixes only to stable. If most bugfixes will only apply to one release and not to the current development, use release branches. ↩
Anhang | Größe |
---|---|
hgbranchingoverview.png [28] | 28.75 KB |
hgbranchinggraft.png [29] | 29.36 KB |
hgbranchingreview.png [30] | 35.6 KB |
2012-09-03-Mo-hg-branching-diagrams.org [31] | 12.43 KB |
hgbranchingmaintain.png [32] | 45.08 KB |
2012-09-03-Mo-hg-branching-diagrams.org [20] | 10.74 KB |
Note: This tutorial is for the old TortoiseHG (with gtk interface). The new one works a bit differently (and uses Qt). See the official quick start guide [33]. The right-click menus should still work similar to the ones described here, though.
After installing TortoiseHG [34], you can download a repository to your computer by right-clicking in a folder and selecting the menu "TortoiseHG" and then "Clone" in there (currently you still need Windows for that - all other dialogs can be evoked in GNU/Linux on the commandline via "hgtk").
Right-Click menu, Windows:
Create Clone, GNU/Linux:
In the dialog you just enter the url of the repository, for example:
http://www.bitbucket.org/ArneBab/md-esw-2009 [35]
(that's also the address of the repository in the internet - just try clicking the link.
When you log in to bitbucket.org [3] you will find a clone-address directly on the site. You can also use that clone address to upload changes (it contains your login-name, and I can give you "push" access on that site).
This gives you two basic abilities:
(I assume that part of what I say is redundant, but I'd rather write a bit too much than omit a crucial bit)
To save changes, you can simlply select "HG Commit" in the right-click-menu. If some of your files aren't known to HG yet (the box before the file isn't ticked), you have to add them (tick the box) to be able to commit them.
To go back to earlier changes, you can use "Checkout Revision" in the "TortoiseHG" menu. In that dialog you can then select the revision you want to see and use the icon on the upper left to get all files to that revision.
You can synchronize by right-clicking in the folder and selecting "Synchronize" in the "TortoiseHG" menu (inside the right-click menu). In the opening dialog you can "push" (upload changes - arrow up with the bar above it), "pull" (download changes to your computer - arrow down with bar below), and check what you would pull or push (arrows iwthout bars). I thing that using dialog will soon became second nature for you, too :)
Have fun with TortoiseHG [34]! :) - Arne
PS: There's also a longer intro to TortoiseHG [36] and an overview to DVCS [37].
PPS: md-esw-2009 is a repository in which Baddok and I planned a dual-gm roleplaying session Mechanical Dream [38].
PPPS: There's also a german version [39] of this article on my german pages [40].
If you came here searching for a way to set the username in Mercurial: just run
hg config --edit
and add
[ui]
username = YOURNAME <EMAIL>
to the file which gets opened. If you have a very old version of Mercurial (<3.0), open$HOME/.hgrc
manually.Update (2015-02-05 [41]): For the Git breakage there is now a partial solution in Git v2.3.0: You can push into a checked out branch when you prepare the target repo via
git config receive.denyCurrentBranch updateInstead
, but only if nothing was changed there. This does not fully address the workflow breakage (the success of the operation is still state-dependent), but at least it makes it work. With Git providing a partial solution for the breakage I reported and Mercurial providing a full solution since 2014-05-01, I call this blog post a success. Thank you Git and Mercurial devs!Update (2014-05-01 [42]): The Mercurial breakage is fixed in Mercurial 3.0: When you commit without username it now says “Abort: no username supplied (use "hg config --edit" to set your username)”. The editor shows a template with a commented-out field for the username. Just put your name and email after the pre-filled
username =
and save the file. The Git breakage still exists.Update (2013-04-18): In #mercurial @ irc.freenode.net [43] there were discussions yesterday for improving the help output if you do not have your username setup, yet.
I recently tried contributing to a new project again, and I was quite surprised which hurdles can be in your way, when you did not setup your environment, yet.
So I decided to put together a small test for the basic workflow: Cloning a project, doing and testing a change and pushing it back.
I did that for Git and Mercurial, because both break at different points.
I’ll express the basic usecase in Subversion:
You can also replace the request for commit rights with creating a patch and sending it to a mailing list. But let’s take the easiest case of a new contributor who is directly welcomed into the project as trusted committer.
A slightly more advanced workflow adds testing in a clean tree. In Subversion it looks almost like the simple commit:
Let’s start with Linus’ DVCS. And since we’re using a DVCS, let’s also try it out in real life
LC_ALL=C LANG=C PS1="$" rm -rf /tmp/gitflow > /dev/null mkdir -p /tmp/gitflow > /dev/null cd /tmp/gitflow > /dev/null # init the repo git init orig > /dev/null cd orig > /dev/null echo 1 > 1 # add a commit git add 1 > /dev/null git config user.name upstream > /dev/null git config user.email up@stream > /dev/null git commit -m 1 > /dev/null # checkout another branch but master. YES, YOU SHOULD DO THAT on the shared repo. We’ll see later, why. git checkout -b never-pull-this-temporary-useless-branch master 2> /dev/null cd .. > /dev/null echo # purely cosmetic and implementation detail: this adds a new line to the output ls
wolf, n.: A man who knows all the ankles. arne@fluss ~/.emacs.d/private/journal $ arne@fluss ~/.emacs.d/private/journal $ $$$$$$$$$$$$$$$$ orig
git --version
git version 1.8.1.5
First I get the repo
git clone orig mine
echo $ ls
ls
Cloning into 'mine'... done. $ ls mine orig
cd mine echo 2 > 1 git commit -m "hack"
$# On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: 1 no changes added to commit (use "git add" and/or "git commit -a")
ARGL… but let’s paste the commands into the shell. I do not use –global, since I do not want to shoot my test environment here.
git config user.name "contributor" git config user.email "con@tribut.or"
and try again
git commit -m "hack"
On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: 1 no changes added to commit (use "git add" and/or "git commit -a")
ARGL… well, paste it in again…
git add 1
git commit -m "hack"
[master aba911a] hack 1 file changed, 1 insertion(+), 1 deletion(-)
Finally I managed to commit my file. Now, let’s push it back.
git push
warning: push.default is unset; its implicit value is changing in Git 2.0 from 'matching' to 'simple'. To squelch this message and maintain the current behavior after the default changes, use: git config --global push.default matching To squelch this message and adopt the new behavior now, use: git config --global push.default simple See 'git help config' and search for 'push.default' for further information. (the 'simple' mode was introduced in Git 1.7.11. Use the similar mode 'current' instead of 'simple' if you sometimes use older versions of Git) Counting objects: 5, done. (1/3) Writing objects: 66% (2/3) Writing objects: 100% (3/3) Writing objects: 100% (3/3), 222 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To /tmp/gitflow/orig master
HA! It’s in.
In short the required commands look like this:
compare Subversion:
Now let’s see what that initial setup with setting a non-master branch was about…
I want to test a change and ensure, that it works with a fresh clone. So I just clone my local repo and commit there.
cd .. git clone mine test cd test # setup the user locally again. Normally you do not need that again, since you’d use --global. git config user.email "contributor" git config user.name "con@tribut.or" # hack and commit echo test > 1 git add 1 echo # cosmetic git commit -m "change to test" >/dev/null # (run the tests)
git push
warning: push.default is unset; its implicit value is changing in Git 2.0 from 'matching' to 'simple'. To squelch this message and maintain the current behavior after the default changes, use: git config --global push.default matching To squelch this message and adopt the new behavior now, use: git config --global push.default simple See 'git help config' and search for 'push.default' for further information. (the 'simple' mode was introduced in Git 1.7.11. Use the similar mode 'current' instead of 'simple' if you sometimes use older versions of Git) Counting objects: 5, done. (1/3) Writing objects: 66% (2/3) Writing objects: 100% (3/3) Writing objects: 100% (3/3), 234 bytes, done. Total 3 (delta 0), reused 0 (delta 0) remote: error: refusing to update checked out branch: refs/heads/master remote: error: By default, updating the current branch in a non-bare repository remote: error: is denied, because it will make the index and work tree inconsistent remote: error: with what you pushed, and will require 'git reset --hard' to match remote: error: the work tree to HEAD. remote: error: remote: error: You can set 'receive.denyCurrentBranch' configuration variable to remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into remote: error: its current branch; however, this is not recommended unless you remote: error: arranged to update its work tree to match what you pushed in some remote: error: other way. remote: error: remote: error: To squelch this message and still keep the default behaviour, set remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'. To /tmp/gitflow/mine master (branch is currently checked out) error: failed to push some refs to '/tmp/gitflow/mine'
Uh… what? If I were a real first time user, at this point I would just send a patch…
The simple local test clone does not work: You actually have to also checkout a different branch if you want to be able to push back (needless duplication of information - and effort). And it actually breaks this simple workflow.
(experienced git users will now tell me that you should always checkout a work branch. But that would mean that I would have to add the additional branching step to the simplest case without testing repo, too, raising the bar for contribution even higher)
git checkout -b testing master git push ../mine testing
Switched to a new branch 'testing' Counting objects: 5, done. (1/3)Writing objects: 66% (2/3) Writing objects: 100% (3/3) Writing objects: 100% (3/3), 234 bytes, done. : Total 3 (delta 0), reused 0 (delta 0) : To ../mine : testing
Since I only pushed to mine, I now have to go there, merge and push.
cd ../mine
git merge testing
git push
Updating aba911a..820dea8 Fast-forward 1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) warning: push.default is unset; its implicit value is changing in Git 2.0 from 'matching' to 'simple'. To squelch this message and maintain the current behavior after the default changes, use: git config --global push.default matching To squelch this message and adopt the new behavior now, use: git config --global push.default simple See 'git help config' and search for 'push.default' for further information. (the 'simple' mode was introduced in Git 1.7.11. Use the similar mode 'current' instead of 'simple' if you sometimes use older versions of Git) Counting objects: 5, done. (1/3) Writing objects: 66% (2/3) Writing objects: 100% (3/3) Writing objects: 100% (3/3), 234 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To /tmp/gitflow/orig master
In short the required commands for testing look like this:
Compare to Subversion
The git workflows broke at several places:
Simplest:
Testing clone (only additional breakages):
Now let’s try the same
LC_ALL=C LANG=C PS1="$" rm -rf /tmp/hgflow > /dev/null mkdir -p /tmp/hgflow > /dev/null cd /tmp/hgflow > /dev/null # init the repo hg init orig > /dev/null cd orig > /dev/null echo 1 > 1 > /dev/null # add a commit hg add 1 > /dev/null hg commit -u upstream -m 1 > /dev/null cd .. >/dev/null echo # purely cosmetic and implementation detail: this adds a new line to the output ls
The most happy marriage I can imagine to myself would be the union of a deaf man to a blind woman. -- Samuel Taylor Coleridge arne@fluss ~/.emacs.d/private/journal $ arne@fluss ~/.emacs.d/private/journal $ $$$$$$$$$$$$ orig
hg --version
Mercurial Distributed SCM (version 2.5.2) (see http://https://mercurial-scm.org for more information) Copyright (C) 2005-2012 Matt Mackall and others This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
hg clone orig mine
echo $ ls
ls
updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ ls mine orig
cd mine echo 2 > 1 echo # I disable the username to show the problem hg --config ui.username= commit -m "hack"
$ $abort: no username supplied (see "hg help config")
ARGL, what??? Mind the update at the top of this article: This is fixed in Mercurial 3.0
Well, let’s do what it says (but only see the first 30 lines to avoid blowing up this example):
hg help config | head -n 30 | grep -B 3 -A 1 per-repository
These files do not exist by default and you will have to create the appropriate configuration files yourself: global configuration like the USERPROFILE%\mercurial.ini" or HOME/.hgrc" and local configuration is put into the per-repository /.hg/hgrc" file.
Are you serious??? I have to actually read a guide just to commit my change??? As normal user this would tip my frustration with the tool over the edge and likely get me to just send a patch… Mind the update at the top of this article: This is fixed in Mercurial 3.0
But I am no normal user, since I want to write this guide. So I assume a really patient user, who does the following (after reading for 3 minutes):
echo '[ui] username = "contributor"' >> .hg/hgrc
and tries again:
hg commit -m "hack"
Now it worked. But this is MAJOR BREAKAGE. Mind the update at the top of this article: This is fixed in Mercurial 3.0
hg push
pushing to /tmp/hgflow/orig searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files
Done. This was easy, and I did not get yelled at (different from the experience with git :) ).
In short the required commands look like this:
username = "contributor"' >> .hg/hgrc (are you serious?)
Compare to Subversion
and to git
cd .. hg clone mine test cd test # setup the user locally again. Normally you do not need that again, since you’d use --global. echo '[ui] username = "contributor"' >> .hg/hgrc # hack and commit echo test > 1 echo # cosmetic hg commit -m "change to test" # (run the tests)
updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $$> $$$
hg push
pushing to /tmp/hgflow/mine searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files
It’s in mine now, but I still need to push it from there.
cd ../mine
hg push
pushing to /tmp/hgflow/orig searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files
Done.
If I had worked on mine in the meantime, I would have to merge there, too - just as with git with the exception that I would not have to give a branch name. But since we’re in the simplest case, we don’t need to do that.
In short the required commands for testing look like this:
Compare to Subversion
and to git
The Mercurial workflow broke only ONCE, but there it broke HARD: To commit you actually have to READ THE HELP PAGE on config to find out how to set your username.
So, to wrap it up: ARE YOU SERIOUS? Mind the update at the top of this article: This is fixed in Mercurial 3.0
That’s a really nice workflow, disturbed by a devastating user experience for just one of the commands.
This is a place where hg should learn from git: The initial setup must be possible from the commandline, without reading a help page and without changing to an editor and then back into the commandline.
Also the workflows for a user who gets permission to push always required some additional steps compared to Subversion.
One of the additional steps cannot be avoided without losing offline-commits (which are a major strength of DVCS), because those make it necessary to split svn commit into commit and push: That separates storing changes from sharing them.
But git actually requires additional steps which are only necessary due to implementation details of its storage layer: Pushing to a repo with the same branch checked out is not allowed, so you have to create an additional branch in your local clone and merge it in the other repo, even if all your changes are siblings of the changes in the other repository, and it requires either a flag to every commit command or explicit adding of changes. That does not amount to the one unavoidable additional command, but actually further three commands, so the number of commands to get code, hack on it and share it increases from 5 to 9. And if you work in a team where people trust you to write good code, that does not actually reduce the required effort to share your changes.
On the other hand, both Mercurial and Git allow you to work offline, and you can do as many testing steps in between as you like, without needing to get the changes from the server every time (because you can simply clone a local repo for that).
Date: 2013-04-17T20:39+0200
Org [22] version 7.9.2 with Emacs [44] version 24
Validate XHTML 1.0 [45]Anhang | Größe |
---|---|
dvcs-basic-svn.png [46] | 2.53 KB |
dvcs-basic-svn-testing.png [47] | 2.68 KB |
dvcs-basic-hg.png [48] | 2.72 KB |
dvcs-basic-hg-testing.png [49] | 3.08 KB |
dvcs-basic-git.png [50] | 2.89 KB |
dvcs-basic-git-testing.png [51] | 3.95 KB |
2013-04-17-Mi-basic-usecase-dvcs.org [52] | 13.02 KB |
2013-04-17-Mi-basic-usecase-dvcs.pdf [53] | 274.67 KB |
In the mercurial list Stanimir Stamenkov asked how to get rid of intermediate merges in the log to simplify reading the history (and to not care about missing some of the details).
Update: Since Mercurial 2.4 [54] you can simply use
hg log -Gr "branchpoint()"
I did some tests for that and I think the nicest representation I found is this:
hg log -Gr "(all() - merge()) or head()"
This article shows examples for this. To find more revset options, run hg help revsets
.
It showed that in the end the revisions converged again - and it shows the actual states of the development.
$ hg log -Gr "(all() - merge()) or head()"
@ Änderung: 7:52fe4a8ec3cc
|\ Marke: tip
| | Vorgänger: 6:7d3026216270
| | Vorgänger: 5:848c390645ac
| | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | Datum: Tue Aug 14 15:09:54 2012 +0200
| | Zusammenfassung: merge
| |
| \
| |\
| | o Änderung: 3:55ba56aa8299
| | | Vorgänger: 0:385d95ab1fea
| | | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | | Datum: Tue Aug 14 15:09:40 2012 +0200
| | | Zusammenfassung: 4
| | |
| o | Änderung: 2:b500d0a90d40
| |/ Vorgänger: 0:385d95ab1fea
| | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | Datum: Tue Aug 14 15:09:39 2012 +0200
| | Zusammenfassung: 3
| |
o | Änderung: 1:8cc66166edc9
|/ Nutzer: Arne Babenhauserheide <bab@draketo.de>
| Datum: Tue Aug 14 15:09:38 2012 +0200
| Zusammenfassung: 2
|
o Änderung: 0:385d95ab1fea
Nutzer: Arne Babenhauserheide <bab@draketo.de>
Datum: Tue Aug 14 15:09:38 2012 +0200
Zusammenfassung: 1
The shortest representation is without the heads, though. It does not represent the current state of development if the last commit was a merge or if some branches were not merged. Otherwise it is equivalent.
$ hg log -Gr "(all() - merge())"
o Änderung: 3:55ba56aa8299
| Vorgänger: 0:385d95ab1fea
| Nutzer: Arne Babenhauserheide <bab@draketo.de>
| Datum: Tue Aug 14 15:09:40 2012 +0200
| Zusammenfassung: 4
|
| o Änderung: 2:b500d0a90d40
|/ Vorgänger: 0:385d95ab1fea
| Nutzer: Arne Babenhauserheide <bab@draketo.de>
| Datum: Tue Aug 14 15:09:39 2012 +0200
| Zusammenfassung: 3
|
| o Änderung: 1:8cc66166edc9
|/ Nutzer: Arne Babenhauserheide <bab@draketo.de>
| Datum: Tue Aug 14 15:09:38 2012 +0200
| Zusammenfassung: 2
|
o Änderung: 0:385d95ab1fea
Nutzer: Arne Babenhauserheide <bab@draketo.de>
Datum: Tue Aug 14 15:09:38 2012 +0200
Zusammenfassung: 1
The vanilla-log looks like this:
$ hg log -G
@ Änderung: 7:52fe4a8ec3cc
|\ Marke: tip
| | Vorgänger: 6:7d3026216270
| | Vorgänger: 5:848c390645ac
| | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | Datum: Tue Aug 14 15:09:54 2012 +0200
| | Zusammenfassung: merge
| |
| o Änderung: 6:7d3026216270
| |\ Vorgänger: 2:b500d0a90d40
| | | Vorgänger: 4:8dbc55213c9f
| | | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | | Datum: Tue Aug 14 15:09:45 2012 +0200
| | | Zusammenfassung: merged 4
| | |
o | | Änderung: 5:848c390645ac
|\| | Vorgänger: 3:55ba56aa8299
| | | Vorgänger: 2:b500d0a90d40
| | | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | | Datum: Tue Aug 14 15:09:43 2012 +0200
| | | Zusammenfassung: merged 2
| | |
+---o Änderung: 4:8dbc55213c9f
| | | Vorgänger: 3:55ba56aa8299
| | | Vorgänger: 1:8cc66166edc9
| | | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | | Datum: Tue Aug 14 15:09:41 2012 +0200
| | | Zusammenfassung: merged 1
| | |
o | | Änderung: 3:55ba56aa8299
| | | Vorgänger: 0:385d95ab1fea
| | | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | | Datum: Tue Aug 14 15:09:40 2012 +0200
| | | Zusammenfassung: 4
| | |
| o | Änderung: 2:b500d0a90d40
|/ / Vorgänger: 0:385d95ab1fea
| | Nutzer: Arne Babenhauserheide <bab@draketo.de>
| | Datum: Tue Aug 14 15:09:39 2012 +0200
| | Zusammenfassung: 3
| |
| o Änderung: 1:8cc66166edc9
|/ Nutzer: Arne Babenhauserheide <bab@draketo.de>
| Datum: Tue Aug 14 15:09:38 2012 +0200
| Zusammenfassung: 2
|
o Änderung: 0:385d95ab1fea
Nutzer: Arne Babenhauserheide <bab@draketo.de>
Datum: Tue Aug 14 15:09:38 2012 +0200
Zusammenfassung: 1
To create the test repo, I just used a few short loops in the shell:
hg init test ; cd test
for i in 1 2 3 4; do echo $i > $i ; hg ci -Am "$i"; hg up -r -$i; done
for i in 1 2 3 4; do echo $i > $i ; hg ci -Am "$i"; hg up -r -$i; hg merge $i ; hg ci -m "merged $i"; done
for i in $(hg heads --template "{node} ") ; do hg merge $i ; hg ci -m "merge"; done
Do you have better representations for viewing convoluted history?
PS: Yes, you can rewrite history, but that’s a really bad idea if you have many people who closely interact and publish early and often.
Update 2016: Instead of fixing the article, the Atlassian web workers removed the comments which point out the misinformation in the article. *sigh*
Summary:
In the Atlassian Blog, a Git proponent spread blatant misinformation which the Atlassian folks are leaving uncommented even though the falseness has been shown by multiple people and even in examples in the article itself.
The claims and corrections:
bookmark@path
, when there could be confusion. This is equivalent to git’s use of path/branch
, but only used where it is needed, while git forces the user to always make that distinction.mq
) and the record extension provides a staging area like the git index — for those who want it. 2 years ago, Atlassian developer Charles O’Farrell published the article Git vs. Mercurial: Why Git? [57] in which he claimed to show "the winning side of Git”. This article was part of the Dev Tools series at Atlassian and written as a reply to the article Why Mercurial? [5]. It was spiced with so much misinformation about Mercurial (statements which were factually wrong) that the comments exploded right away. But the article was never corrected. Just now I was referred to the text again, and I decided to do what I should have done 2 years ago: Write an answer which debunks the myths.
“I also think that git isn’t the most beginner-friendly program. That’s why I’m only using its elementary features” — “I hear that from many git-users …” — part of the discussion which got me to write this article
Charles starts off by contradicting himself: He claims that git is safer, because it “actually never lets you change anything” - and goes on to explain, that all unreferenced data can be garbage collected after 30 days. Since nowadays the git garbage collector runs automatically, all unreferenced changes are lost after approximately 30 days.
This obviously means that git does allow you to change something. That this change only becomes irreversible after 30 days is an implementation detail which you have to keep in mind if you want to be safe.1
He then goes on to say how this allows for easy history rewriting with the interactive rebase and correctly includes, that the histedit extension of Mercurial allows you to do the same. (He also mentions the Mercurial Queues Extension (mq [58]), just to admit that it is not the equivalent of git rebase -i
but instead provides a staging area for future commits).
Then he starts the FUD2: Since histedit stores its backup in an external file, he asks rhetorically what new commands he would have to learn to restore it.
Dear reader, what new command might be required to pull data out of a backup? Something like git ref? Something like git reflog to find it and then something else?
Turns out, this is as easy and consistent as most things in Mercurial: Backup bundles can be treated just like repositories: To restore the changes, simply use
hg pull backup.bundle
So, all FUD removed, his take on safer history and rewriting history is reduced to “in hg it’s different, and potentially confusing features are shipped as extensions. Recovering changes from backups is consistent with your day-to-day usage of hg”.
(note that the flexibility of hg also enables the creation of extensions like mutable hg [59] which avoids all the potential race conditions with git rebase - even for code you share between repositories (which is a total no-go in git), with a safety net which warns you if you try to change published history; thanks to the core feature phases [60])
On branching Charles goes deep into misinformation: He wrote his article in the year 2012, when Mercurial had already provided named branches as well as anonymous branching for 6 years, and one year after bookmarks became a core feature in hg 1.8 [61], and he kept talking about how Mercurial advised to keep one clone per branch by referencing to a blog post [62] which incorrectly assumed that the hg developers were using that workflow (obviously he did not bother to check that claim). Also he went on clamoring, that bookmarks initially could not be pushed between repositories, and how they were added “due to popular demand”. The reality is, that at some point a developer simply said “I’ll write that”. And within a few months, he implemented the equivalent of git branches. Before that, no hg developer saw enough need for them to excert that effort and today most still simply use named branches.
But obviously Charles could not imagine named branches to work, so he kept talking about how bookmarks do not have namespaces while git branches have them, and that this would create confusion. He showed the following example for git and Mercurial (shortened here):
* 9e4b1b8 (origin/master, origin/test) Remove unused variable | * 565ad9c (HEAD, master) Added Hello example |/ * 46f0ac9 Initial commit
and
o changeset: 2:67deb4acba33 | bookmark: master@default | summary: Third commit | | @ changeset: 1:2d479c025719 |/ bookmark: master | summary: Second commit | o changeset: 0:e0e024ff06ad summary: First commit
Then he asked: “would the real master branch please stand up?”
Let’s try to answer that:
Git: there is a commit marked as (origin/master, origin/test)
, and one marked as (HEAD, master)
. If you know that origin
is the canonical remote repository in git, then you can guess, that the names prefixed with origin/
come from the remote repository.
Mercurial: There is a commit with the bookmark master@default
and one with the bookmark master
. When you know that default
is the canonical remote repository in Mercurial, then you can guess, that the bookmark postfixed with @default
comes from the remote repository.
But Charles concludes his example with the sentence: “Because there is no notion of namespaces, we have no way of knowing which bookmarks are local and which ones are remote, and depending on what we call them, we might start running into conflicts.”
And this is not only FUD, it is factually wrong and disproven in his own example. After this, I cannot understand how anyone could take his text seriously.
But he goes on.
His final misinformation is about the git index - a staging area for uncommitted changes. He correctly identifies the index as “one of the things that people either love or hate about Git”. As Mercurial cares a lot about giving newcomers a safe environment to work in, it ships this controversial feature as extension and not as core command.
Charles now claims that the equivalent of the git index is the record [63] extension - and then complains that it does not imitate the index exactly, because it does not give a staging area but rather allows committing partial changes. Instead of now turning towards the Mercurial Queues Extension which he mentioned earlier as staging area for commits, he asserts that record cannot provide the same feature as git.
Not very surprisingly, when you have an extension to provide partial commits (record [63]) and one to provide a staging area (mq [58]), if you want both, you simply activate both extensions. When you do that, Mercurial offers the qrecord
command which stores partial changes in the current staging area.
Not mentioning this is simply a matter of not having done proper research for his article - and not updating the post means that he intentionally continues to spread misinformation.
The only thing he got right is that git blame
is able to reconstruct copies of code from one file to another.
Mercurial provides this for renamed files, but not for directly copy-pasted lines. Analysis of the commits would naturally allow doing the same, and all the information for that is available, but this is not implemented yet. If people ask for it loud enough, it will only be a matter of time, though. As bookmarks showed, the Mercurial code base is clean enough that it suffices to have a single developer who steps up and creates an extension for this. If enough people use it, the extension can become a core feature later on.
“There is a reason why hg users tend to talk less about hg: There is no need to talk about it that much.” — Arne Babenhauserheide as answer to Why Mercurial? [5]
Charles concludes with “Git means never having to say, you should have”, and “Mercurial feels like Git lite”. Since he obviously did not do his research on Mercurial while he took the time to acquire in-depth knowledge of git, it’s quite understandable that he thinks this. But it is no base for writing an article - especially not for Atlassian, the most prominent Mercurial hosting provider since their acquisition of Bitbucket, which grew big as pure Mercurial hoster and added git after being acquired by Atlassian.
He then manages to finish his article with one more unfounded smoke bomb: The repository format drives what is possible with our DVCS tools, now and in the future.
While this statement actually is true, in the context of git-vs-mercurial it is a horrible misfit: The hg-git extension [64] shows since 2009 [65], 3 years before Charles wrote his article, that it is possible to convert transparently from git to Mercurial and back. So the repository format of Mercurial has all capabilities of the repository format of git - and since git cannot natively store named branches, represent branches with multiple heads or push changes into a checked out branch [66], the capabilities of the repository format of Mercurial are actually a superset of the capabilities of the storage format of Git.
But what he also states is that “there are more important things than having a cuddly command line”. And this is the final misleading statement to debunk: While the command line does not determine what is theoretically possible with the tool, it does determine what regular users can do with it. The horrible command line of git likely contributes to the many git users who never use anything but commit -a
, push
and pull
- and to the proliferation of git gurus whom the normal users call when git shot them into their foot again.
It’s sad when someone uses his writing skills to wrap FUD and misinformation into pretty packaging to get people to take his side. Even more sad is, that this often works for quite some time and that few people read the comments section.3
And now that I finished debunking the article, there is one final thing I want to share. It is a quote from the discussion which prompted me to write this piece:
<…> btw. I also think that git isn’t the most beginner-friendly program.
<…> That’s why I’m only using its elementary features
<ArneBab> I hear that from many git-users…
<…> oh, maybe I should have another look at hg after all
This is a translation of the real quote in German:
<…> ich finde btw auch dass git nicht gerade das anfängerfreundlichste programm ist
<…> darum nutze ich das auch nur recht rudimentär
<ArneBab> das höre ich von vielen git-Nutzern…
<…> oha. nagut, dann sollte ich mir hg vielleicht doch nochmal ansehen
Note: hg is short for Mercurial. It is how Mercurial is called on the command line.
Garbage collection after 30 days means that you have to remember additional information while you work. And that is a problem: You waste resources which would be better spent on the code you write. A DVCS should be about having to remember less, because your DVCS keeps the state for you.
FUD means fear-uncertainty-doubt and is a pretty common technique used to discredit things when one has no real arguments: Instead of giving a clear argument which can be debunked, just make some vague hints that something might be wrong or that there might be some deficiency or danger. Most readers will never check this and so this establishes the notion that something IS wrong.
Lesson learned: If you take the time to debunk something in the comments, be sure to also write an article about it. Otherwise you might find the same misinformation still being spread [67] 2 years later by the same people. When Atlassian bought Bitbucket, that essentially amounted to a hostile takeover of a Mercurial team by git-zealots. And they got away with this, because too few people called them up on it in public.
A comment [68] on largefile support missing in BitBucket, despite being a much-requested feature [69] since 2012.
Note that it’s not Atlassian which got big with Mercurial. It’s Bitbucket which got big with Mercurial, and it was later bought by Atlassian. Also Atlassian is still spreading lies about Mercurial in the Atlassian blog by hosting a guest entry by a git zealot which is filled with factual errors, some even disproven in the examples in the article. Despite being called out on that in public [70], they did not even see the need to add a note to that guest entry about misunderstanding by the author.
I asked their marketing team personally several times to correct this. I know they read it, because people I used to collaborate with work at the BitBucket Mercurial support.
Dear BitBucket, this is where you could be: Virtuos Games uses BitTorrentSync with Mercurial for game development using decentralized large asset storage [71].
I guess they show that there is room for a Mercurial hosting company. Maybe it will be kiln [72].
I‘m sorry for the great Mercurial developers working at Atlassian to improve Mercurial support. I know you’re doing great work and I hope you will prove me wrong on this. But from the outside it seems like you’re being used to hide hostility by the parent company against the core part of their own product. “…we decided to collaborate with GitHub on building a standard for large file support” — seriously? There is already a standard for large file support [73] which has been part of Mercurial core since 2011 [74], and works almost seamlessly. It just needs support from BitBucket to be easier for BitBucket users.
This crazyness is a new spin on never trust a company [75]: never ever trust a zealot with a tool which helps “the other side”: They are prone to even put zeal over business. For everyone at BitBucket: If this isn’t a wakeup call, I don’t know what is.
And if you like Git and are happy for a competitor to get weakened: To you really want your tool to win by spreading intentional misinformation? Wouldn’t you feel more at ease seeing your tool win by merit of better technology, not by buying companies which support other tools and then starving them down and forcing them to badmouth their own technology [70]?
In many discussions on DVCS over the years I have been fair, friendly and technical while receiving vitriol [76] and misinformation and FUD [70]. This strip visualizes the impression which stuck to my mind when speaking with casual git-users.
Update: I found a very calm discussion at a place where I did not expect it: reddit [77]. I’m sorry to you, guys. Thank you for proving that a constructive discussion is possible from both sides! I hope that you are not among the ones offended by this strip.
To Hg-users: There are git users who really understand what they are doing and who stick to arguments and friendly competition. This comic arose from the many frustrating experiences with the many other git users. Please don’t let this strip trick you into going down to non-constructive arguments. Let’s stay friendly. I already feel slightly bad about this short move into competition-like visualization for a topic where I much prefer friendly, constructive discussions. But it sucks to see contributors stumble over git, so I think it was time for this.
»I also think that git isn’t the most beginner-friendly program. That’s why I’m using only its elementary features«
To put the strip in words [79], let’s complete the quote:
»I also think that git isn’t the most beginner-friendly program.
That’s why I’m using only its elementary features«
<ArneBab> I hear that from many git-users…
»oh, maybe I should have another look at hg after all«
Because there are far too many Git-Users who only dare using the most basic commands which makes git at best useless and at worst harmful.
This is not the fault of the users. It is the fault of the tool.
If you are offended by this strip: You knew the title when you came here, right?
And if you are offended enough, that you want to make your own strip and set things right, go grab the source-file [80], fire up krita [81] and go for it! This strip is free.1
If you feel that this strip fits Mercurial and Git perfectly, keep in mind, that this is only one aspect of the situation, and that using Git is still much better than being forced to use centralized or proprietary version tracking (and people who survive the initial phase mostly unscarred can actually do the same with Git as they could with Mercurial).
And Mercurial also has its share of problems - even horrible ones [82] (update 2014: These were fixed in version 3.0 [83]) - but compared to Git it is a wonder of usability.
And in case this strip does not apply to your usage of Git: there are far too many people whose experience it fits - and this should not be the case for the most widespread system for accessing the code of free software projects.
(and should this strip be completely unintelligible to you: curse a world in which the concept of monofilament whips isn’t mainstream ☺ — let’s get more people to play Shadowrun [84])
So if you are one of the people, who mostly use commit, pull and push, and turn to a Git-Guru when things break, then you might want to kiss the Git-Guru goodbye and give Mercurial [56] a try.
By the way: the extensions named in the Final Round are record [85], mutable [86] and infocalypse [87]: Select the changes to commit on a hunk-by-hunk base, change history with automatic conflict resolution (even for rebase) and collaborate anonymously over Freenet [25].
And if you are one of the Git Gurus who claim that squashing attacking Ninjas is only possible with Git, have a look what a Firefox-contributor and former long-term Git-User [11] and a Facebook infrastructure developer [9] have to say about this.
All the graphics in this strip are available under free licenses: creative-commons attribution [88] or GPLv3 or later [89] — you decide which of those you use. If it is cc attribution, call me Arne Babenhauserheide and link to this article. You’ll find all the sources as well as some preliminary works and SVGs in git-vs-hg-offensive.tar_.gz [90] or git-vs-hg-offensive.zip [91] (whichever you prefer)
Anhang | Größe |
---|---|
git-vs-hg-offensive-purevector-retouch2.png [92] | 184.31 KB |
git-vs-hg-offensive.tar_.gz [90] | 22.59 MB |
git-vs-hg-offensive.zip [91] | 22.62 MB |
git-vs-hg-offensive.png [78] | 185.98 KB |
git-vs-hg-offensive-purevector-retouch2.kra [80] | 377.58 KB |
git-vs-hg-offensive-thumb.jpg [93] | 11.3 KB |
git-vs-hg-offensive-thumb-240x240.jpg [94] | 11.78 KB |
We (nelchael and me) just finished a live ebuild [95] for Mercurial [56] which allows to conveniently track the main (mpm) repo [96] of Mercurial in Gentoo [97].
To use the ebuild, just add
=dev-util/mercurial-9999 **
to your package.keywords and emerge mercurial (again).
It took us a while since we had to revise the Mercurial eclass to always build Mercurial live packages from their Mercurial repository and nelchael took the chance to completely overhaul the eclass.
If you're interested in the details, please have a look at the ebuild [98] and the eclass [99] as well as the tracking bug [100].
To use the eclass in an ebuild, just add inherit mercurial at the beginning of the ebuild and set EHG_REPO_URI to the correct repository URI. If you need to share a single repository between several ebuilds, set EHG_PROJECT to the project name in all of them.
Have fun with Mercurial!
The official workflow guide for Mercurial, mirrored from mercurial-scm.org/guide [14]. License: GPLv2 or later [101].
It delves into nonlinear history and merging right from the beginning and uses only features you get without activating extensions. Due to this it offers efficient and safe workflows without danger of losing already committed work.
With Mercurial you can use a multitude of different workflows. This page shows some of them, including their use cases. It is intended to make it easy for beginners of version tracking to get going instantly and learn completely incrementally. It doesn't explain the concepts used, because there are already many other great resources doing that, for example the wiki [102] and the hgbook [103].
If you want a more exhaustive tutorial with the basics, please have a look at the Tutorial in the Mercurial Wiki [104]. For a really detailed and very nice to read description of Mercurial, please have a look at Mercurial: The Definitive Guide [105].
Note:
This guide doesn't require any prior knowledge of version control systems (though subversion users will likely feel at home quite quickly). Basic command line abilities are helpful, because we'll use the command line client.
We go from simple to more complex workflows. Those further down build on previous workflows.
The first workflow is also the easiest one: You want to use Mercurial to be able to look back when you did which changes.
This workflow only requires an installed Mercurial and write access to some file storage (you almost definitely have that :) ). It shows the basic techniques for more complex workflows.
As first step, you should teach Mercurial your name. For that you open the file ~/.hgrc (or mercurial.ini in your home directory for Windows) with a text-editor and add the ui section (user interaction) with your username:
[ui] username = Mr. Johnson <johnson@smith.com>
Now you add a new folder in which you want to work:
$ hg init project
$ cd project $ (add files) $ hg add $ hg commit (enter the commit message)
Note:
You can also go into an existing directory with files and init the repository there.
$ cd project $ hg init
Alternatively you can add only specific files instead of all files in the directory. Mercurial will then track only these files and won't know about the others. The following tells mercurial to track all files whose names begin with "file0" as well as file10, file11 and file12.
$ hg add file0* file10 file11 file12
$ (do some changes)
see which files changed, which have been added or removed, and which aren't tracked yet
$ hg status
see the exact changes
$ hg diff
commit the changes.
$ hg commit
now an editor pops up and asks you for a commit message. Upon saving and closing the editor, your changes have been stored by Mercurial.
Note:
You can also supply the commit message directly via hg commit -m 'MESSAGE'.
When you copy or move files, you should tell Mercurial to do the copy or move for you, so it can track the relationship between the files.
Remember to commit after moving or copying. From the basic commands only commit creates a new revision
$ hg cp original copy $ hg commit (enter the commit message) $ hg mv original target $ hg commit (enter the commit message)
Now you have two files, "copy" and "target", and Mercurial knows how they are related.
Note:
Should you forget to do the explicit copy or move, you can still tell Mercurial to detect the changes via hg addremove --similarity 100. Just use hg help addremove for details.
$ hg log
This prints a list of changesets along with their date, the user who committed them (you) and their commit message.
To see a certain revision, you can use the -r switch (--revision). To also see the diff of the displayed revisions, there's the -p switch (--patch)
$ hg log -p -r 3
The second workflow is still very easy: You're a lone developer and you want to use Mercurial to keep track of your own changes.
It works just like the log keeping workflow, with the difference that you go back to earlier changes at times.
To start a new project, you initialize a repository, add your files and commit whenever you finished a part of your work.
Also you check your history from time to time, so see how you progressed.
Init your project, add files, see changes and commit them.
$ hg init project $ cd project $ (add files) $ hg add # tell Mercurial to track all files $ (do some changes) $ hg diff # see changes $ hg commit # save changes $ hg cp # copy files or folders $ hg mv # move files or folders $ hg log # see history
Different from the log keeping workflow, you'll want to go back in history at times and do some changes directly there, for example because an earlier change introduced a bug and you want to fix it where it occurred.
To look at a previous version of your code, you can use update. Let's assume that you want to see revision 3.
$ hg update 3
Now your code is back at revision 3, the fourth commit (Mercurial starts counting at 0).
To check if you're really at that revision, you can use identify -n.
$ hg identify -n
Note:
identify without options gives you the short form of a unique revision ID. That ID is what Mercurial uses internally. If you tell someone about the version you updated to, you should use that ID, since the numbers can be different for other people. If you want to know the reasons behind that, please read up Mercurials [basic concepts [106]]. When you're at the most recent revision, hg identify -n will return "-1".
To update to the most recent revision, you can use "tip" as revision name.
$ hg update tip
Note:
If at any place any command complains, your best bet is to read what it tells you and follow that advice.
Note:
Instead of hg update you can also use the shorthand hg up. Similarly you can abbreviate hg commit to hg ci.
Note:
To get a revision devoid of files, just update to "null" via hg update null. That's the revision before any files were added.
When you find a bug in some earlier revision you have two options: either you can fix it in the current code, or you can go back in history and fix the code exactly where you did it, which creates a cleaner history.
To do it the cleaner way, you first update to the old revision, fix the bug and commit it. Afterwards you merge this revision and commit the merge. Don't worry, though: Merging in mercurial is fast and painless, as you'll see in an instant.
Let's assume the bug was introduced in revision 3.
$ hg update 3 $ (fix the bug) $ hg commit
Now the fix is already stored in history. We just need to merge it with the current version of your code.
$ hg merge
If there are conflicts use hg resolve - that's also what merge tells you to do in case of conflicts.
First list the files with conflicts
$ hg resolve --list
Then resolve them one by one. resolve attempts the merge again
$ hg resolve conflicting_file (fix it by hand, if necessary)
Mark the fixed file as resolved
$ hg resolve --mark conflicting_file
Commit the merge, as soon as you resolved all conflicts. This step is also necessary when there were no conflicts!
$ hg commit
At this point, your fix is merged with all your other work, and you can just go on coding. Additionally the history shows clearly where you fixed the bug, so you'll always be able to check where the bug was.
Note:
Most merges will just work. You only need resolve, when merge complains.
So now you can initialize repositories, save changes, update to previous changes and develop in a nonlinear history by committing in earlier changesets and merging the changes into the current code.
Note:
If you fix a bug in an earlier revision, and some later revision copied or moved that file, the fix will be propagated to the target file(s) when you merge. This is the main reason why you should always use hg cp and hg mv.
At times you'll be working on several features in parallel. If you want to avoid mixing incomplete code versions, you can create clones of your local repository and work on each feature in its own code directory.
After finishing your feature you then pull it back into your main directory and merge the changes.
First create the feature clone and do some changes
$ hg clone project feature1 $ cd feature1 $ (do some changes and commits)
Now check what will come in when you pull from feature1, just like you can use diff before committing. The respective command for pulling is incoming
$ cd ../project $ hg incoming ../feature1
Note:
If you want to see the diffs, you can use hg incoming --patch just as you can do with hg log --patch for the changes in the repository.
If you like the changes, you pull them into the project
$ hg pull ../feature1
Now you have the history of feature1 inside your project, but the changes aren't yet visible. Instead they are only stored inside a ".hg" directory of the project (more information on the store [103]).
Note:
From now on we'll use the name "repository" for a directory which has a .hg directory with Mercurial history.
If you didn't do any changes in the project, while you were working on feature1, you can just update to tip (hg update tip), but it is more likely that you'll have done some other changes in between changes. In that case, it's time for merging.
Merge feature1 into the project code
$ hg merge
If there are conflicts use hg resolve - that's also what merge tells you to do in case of conflicts. After you merge, you have to commit explicitly to make your merge final
$ hg commit (enter commit message, for example "merged feature1")
You can create an arbitrary number of clones and also carry them around on USB sticks. Also you can use them to synchronize your files at home and at work, or between your desktop and your laptop.
Note:
You also have to commit after a merge when there are no conflicts, because merging creates new history and you might want to attach a specific message to the merge (like "merge feature1").
Now you can work on different features in parallel, but from time to time a bad commit might sneak in. Naturally you could then just go back one revision and merge the stray error, keeping all mistakes out of the merged revision. However, there's an easier way, if you realize your error before you do another commit or pull: rollback.
Rolling back means undoing the last operation which added something to your history.
Imagine you just realized that you did a bad commit - for example you didn't see a spelling error in a label. To fix it you would use
hg rollback
And then redo the commit
hg commit -m "message"
If you can use the command history of your shell and you added the previous message via commit -m "message", that following commit just means two clicks on the arrow-key "up" and one click on "enter".
Though it changes your history, rolling back doesn't change your files. It only undoes the last addition to your history.
But beware, that a rollback itself can't be undone. If you rollback and then forget to commit, you can't just say "give me my old commit back". You have to create a new commit.
Note:
Rollback is possible, because Mercurial uses transactions when recording changes, and you can use the transaction record to undo the last transaction. This means that you can also use rollback to undo your last pull, if you didn't yet commit anything new.
Now we go one step further: You are no longer alone, and you want to share your changes with others and include their changes.
The basic requirement for that is that you have to be able to see the changes of others.
Mercurial allows you to do that very easily by including a simple webserver from which you can pull changes just as you can pull changes from local clones.
Note:
There are a few other ways to share changes, though. Instead of using the builtin webserver, you can also send the changes by email or setup a shared repository, to where you push changes instead of pulling them. We'll cover one of those later.
This is the easiest way to quickly share changes.
First the one who wants to share his changes creates the webserver
$ hg serve
Now all others can point their browsers to his IP address (for example 192.168.178.100) at port 8000. They will then see all his history there and can decide if they want to pull his changes.
$ firefox http://192.168.178.100:8000
If they decide to include the changes, they just pull from the same URL
$ hg pull http://192.168.178.100:8000
At this point you all can work as if you had pulled from a local repository. All the data is now in your individual repositories and you can merge the changes and work with them without needing any connection to the served repository.
Often you won't have direct access to the repository of someone else, be it because he's behind a restrictive firewall, or because you live in different timezones. You might also want to keep your changes confidential and prefer internal email (if you want additional protection, you can also encrypt the emails, for example with GnuPG [107]).
In that case, you can easily export your changes as patches and send them by email.
Another reason to send them by email can be that your policy requires manual review of the changes when the other developers are used to reading diffs in emails. I'm sure you can think of more reasons.
Sending the changes via email is pretty straightforward with Mercurial. You just export your changes and attach (or copy paste) it in your email. Your colleagues can then just import them.
First check which changes you want to export
$ cd project $ hg log
We assume that you want to export changeset 3 and 4
$ hg export 3 > change3.diff $ hg export 4 > change4.diff
Now attach them to an email and your colleagues can just run import on both diffs to get your full changes, including your user information.
To be careful, they first clone their repository to have an integration directory as sandbox
$ hg clone project integration $ cd integration $ hg import change3.diff $ hg import change4.diff
That's it. They can now test your changes in feature clones. If they accept them, they pull the changes into the main repository
$ cd ../project $ hg pull ../integration
Note:
The patchbomb extension automates the email-sending, but you don't need it for this workflow.
Note:
You can also send around bundles, which are snippets of your actual history. Just create them via
$ hg bundle --base FIRST_REVISION_TO_BUNDLE changes.bundle
Others can then get your changes by simply pulling them, as if your bundle were an actual repository
$ hg pull path/to/changes.bundle
Sending changes by email might be the easiest way to reach people when you aren't yet part of the regular development team, but it creates additional workload: You have to bundle the changes, send mails and then import the bundles manually. Luckily there's an easier way which works quite well: The shared push repository.
Till now we transferred all changes either via email or via pull. Now we use another option: pushing. As the name suggests it's just the opposite of pulling: You push your changes into another repository.
But to make use of it, we first need something we can push to.
By default hg serve doesn't allow pushing, since that would be a major security hole. You can allow pushing in the server, but that's no solution when you live in different timezones, so we'll go with another approach here: Using a shared repository, either on an existing shared server or on a service like BitBucket [3]. Doing so has a bit higher starting cost and takes a bit longer to explain, but it's well worth the effort spent.
If you want to use an existing shared server, you can use serve there and allow pushing [108]. Also there are some other nice ways to allow pushing to a Mercurial repository [109], including simple access via SSH [110].
Otherwise you first need to setup a BitBucket Account. Just signup at BitBucket [3]. After signing up (and login) hover your mouse over "Repositories". There click the item at the bottom of the opening dialog which say "Create new".
Give it a name and a description. If you want to keep it hidden from the public, select "private"
$ firefox http://bitbucket.org
Now your repository is created and you see instructions for pushing to it. For that you'll use a command similar to the following (just with a different URL)
$ hg push https://bitbucket.org/ArneBab/hello/
(Replace the URL with the URL of your created repository. If your username is "Foo" and your repository is named "bar", the URL will be https://bitbucket.org/Foo/bar/ [111])
Mercurial will ask for your BitBucket name and password, then push your code.
Voilà, your code is online.
Note:
You can also use SSH for pushing to BitBucket [112].
Now it's time to tell all your colleagues to sign up at BitBucket, too.
After that you can click the "Admin" tab of your created repository and add the usernames of your colleagues on the right side under "Permission: Writers". Now they are allowed to push code to the repository.
(If you chose to make the repository private, you'll need to add them to "Permission: Readers", too)
If one of you now wants to publish changes, he'll simply push them to the repository, and all others get them by pulling.
Publish your changes
$ hg push https://bitbucket.org/ArneBab/hello/
Pull others changes into your local repository
$ hg pull https://bitbucket.org/ArneBab/hello/
People who join you in development can also just clone this repository, as if one of you were using hg serve
$ hg clone https://bitbucket.org/ArneBab/hello/ [113] hello
That local repository will automatically be configured to pull/push from/to the online repository, so new contributors can just use hg push and hg pull without an URL.
Note:
To make this workflow more scalable, each one of you can have his own BitBucket repository and you can simply pull from the others repositories. That way you can easily establish workflows in which certain people act as integrators and finally push checked code to a shared pull repository from which all others pull.
Note:
You can also use this workflow with a shared server instead of BitBucket, either via SSH or via a shared directory. An example for an SSH URL with Mercurial is be ssh://user@example.com/path/to/repo. When using a shared directory you just push as if the repository in the shared directory were on your local drive.
Now let's take a step back and look where we are.
With the commands you already know, a bit reading of hg help <command> and some evil script-fu you can already do almost everything you'll ever need to do when working with source code history. So from now on almost everything is convenience, and that's a good thing.
First this is good, because it means, that you can now use most of the concepts which are utilized in more complex workflows.
Second it aids you, because convenience lets you focus on your task instead of focusing on your tool. It helps you concentrate on the coding itself. Still you can always go back to the basics, if you want to.
A short summary of what you can do which can also act as a short check, if you still remember the meaning of the commands.
$ hg init project $ cd project $ (add some files) $ hg add $ hg commit (enter the commit message)
$ (do some changes) $ hg commit (enter the commit message) $ hg update 0 $ (do some changes) $ hg commit (enter the commit message) $ hg merge $ (optionally hg resolve) $ hg commit (enter the commit message)
$ cd .. $ hg clone project feature1 $ cd feature1 $ (do some changes) $ hg commit (enter the commit message) $ cd ../project $ hg pull ../feature1
$ hg serve & $ cd .. $ hg clone http://127.0.0.1:8000 [114] project-clone
$ cd project-clone $ (do some changes) $ hg commit (enter the commit message) $ hg export tip > ../changes.diff
$ cd ../project $ hg import ../changes.diff
$ cd ../feature1 $ hg pull http://127.0.0.1:8000
$ (setup bitbucket repo) $ hg push https://bitbucket.org/USER/REPO [115] (enter name and password in the prompt) $ hg pull https://bitbucket.org/USER/REPO
Let's move on towards useful features and a bit more advanced workflows.
When you routinely pull code from others, it can happen that you overlook some bad change. As soon as others pull that change from you, you have little chance to get completely rid of it.
To resolve that problem, Mercurial offers you the backout command. Backing out a change means, that you tell Mercurial to create a commit which reverses the bad change. That way you don't get rid of the bad code in history, but you can remove it from new revisions.
Note:
The basic commands don't directly rewrite history. If you want to do that, you need to activate some of the extensions which are shipped with mercurial. We'll come to that later on.
Let's assume the bad change was revision 3, and you already have one more revision in your
repository. To remove the bad code, you can just backout of it. This creates a new
change which reverses the bad change. After backing out, you can then merge that new change
into the current code.
$ hg backout 3 $ hg merge (potentially resolve conflicts) $ hg commit (enter commit message. For example: "merged backout")
That's it. You reversed the bad change. It's still recorded that it was once there (following the principle "don't rewrite history, if it's not really necessary"), but it doesn't affect future code anymore.
Now that you can share changes and reverse them if necessary, you can go one step further: Using Mercurial to help in coordinating the coding.
The first part is an easy way to develop features together, without requiring every developer to keep track of several feature clones.
When you want to split your development into several features, you need to keep track of who works on which feature and where to get which changes.
Mercurial makes this easy for you by providing named branches. They are a part of the main repository, so they are available to everyone involved. At the same time, changes committed on a certain branch don't get mixed with the changes in the default branch, so features are kept separate, until they get merged into the default branch.
Note:
Cloning a repository always puts you onto the default branch at first.
When someone in your group wants to start coding on a feature without disturbing the others, he can create a named branch and commit there. When someone else wants to join in, he just updates to the branch and commits away. As soon as the feature is finished, someone merges the named branch into the default branch.
Create the branch
$ hg branch feature1 (do some changes) $ hg commit (write commit message)
Update into the branch and work in it
$ hg update feature1 (do some changes) $ hg commit (write commit message)
Now you can commit, pull, push and merge (and anything else) as if you were working in a separate repository. If the history of the named branch is linear and you call "hg merge", Mercurial asks you to specify an explicit revision, since the branch in which you work doesn't have anything to merge.
When you finished the feature, you merge the branch back into the default branch.
$ hg update default $ hg merge feature1 $ hg commit (write commit message)
And that's it. Now you can easily keep features separate without unnecessary bookkeeping.
Note:
Named branches stay in history as permanent record after you finished your work. If you don't like having that record in your history, please have a look at some of the advanced workflows [116].
Since you can now code separate features more easily, you might want to mark certain revisions as fit for consumption (or similar). For example you might want to mark releases, or just mark off revisions as reviewed.
For this Mercurial offers tags. Tags add a name to a revision and are part of the history. You can tag a change years after it was committed. The tag includes the information when it was added, and tags can be pulled, pushed and merged just like any other committed change.
Note:
A tag must not contain the char ":", since that char is used for specifying multiple revisions - see "hg help revisions".
Note:
To securely mark a revision, you can use the gpg extension [117] to sign the tag.
Let's assume you want to give revision 3 the name "v0.1".
Add the tag
$ hg tag -r 3 v0.1
See all tags
$ hg tags
When you look at the log you'll now see a line in changeset 3 which marks the Tag. If someone wants to update to the tagged revision, he can just use the name of your tag
$ hg update v0.1
Now he'll be at the tagged revision and can work from there.
At times you will have changes in your repository, which you really don't want in it.
There are many advanced options for removing these, and most of them use great extensions (Mercurial Queues [118] is the most often used one), but in this basic guide, we'll solve the problem with just the commands we already learned. But we'll use an option to clone which we didn't yet use.
This workflow becomes inconvenient when you need to remove changes, which are buried below many new changes. If you spot the bad changes early enough, you can get rid of them without too much effort, though.
Let's assume you want to get rid of revision 2 and the highest revision is 3.
The first step is to use the "--rev" option to clone: Create a clone which only contains the changes up to the specified revision. Since you want to keep revision 1, you only clone up to that
$ hg clone -r 1 project stripped
Now you can export the change 3 from the original repository (project) and import it into the stripped one
$ cd project $ hg export 3 > ../changes.diff $ cd ../stripped $ hg import ../changes.diff
If a part of the changes couldn't be applied, you'll see that part in *.rej files. If you have *.rej files, you'll have to include or discard changes by hand
$ cat *.rej (apply changes by hand) $ hg commit (write commit message)
That's it. hg export also includes the commit message, date, committer and similar metadata, so you are already done.
Note:
removing history will change the revision IDs of revisions after the removed one, and if you pull from someone else who still has the revision you removed, you will pull the removed parts again. That's why rewriting history should most times only be done for changes which you didn't yet publicise.
So now you can work with Mercurial in private, and also share your changes in a multitude of ways.
Additionally you can remove bad changes, either by creating a change in the repository which reverses the original change, or by really rewriting history, so it looks like the change never occurred.
And you can separate the work on features in a single repository by using named branches and add tags to revisions which are visible markers for others and can be used to update to the tagged revisions.
With this we can conclude our practical guide.
If you now want to check some more complex workflows, please have a look at the general workflows wikipage [119].
To deepen your understanding, you should also check the basic concept overview [106].
Have fun with Mercurial!
Learning Mercurial in Workflows - A practical guide to version tracking / source code management with Mercurial
Copyright © 2011 Arne Babenhauserheide (main author), David Soria Parra, Augie Fackler, Benoit Boissinot, Adrian Buehlmann, Nicolas Dumazet and Steve Losh.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License [101] along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Also published on Mercurials Workflows wikipage [120]. Originally written for PyHurd [121]: Python bindings for the GNU Hurd [122].
If you
then this workflow might be right for you.
Note: If you have a huge number of small features (2000 and upwards), the number of persistent named branches can create some performance problems for listing the branches [123] (only for the listing!) (as different example, pushing is unaffected: Linear history is just as fast as 2000 branches [124]). For features which need no collaboration or need only a few commits, this workflow also has much unnecessary overhead. It is best used for features which will be developed side by side with default for some time (and many commits), so tracking the default branch against the feature is relevant. To mark single-commit features as belonging to a feature, just use the commit message.
Note: The difference between Mercurial named branches and git branches is that git branches don’t stay in history. They don’t allow you to find out later in which branch a certain commit was added. If you want git-style branching, just use bookmarks.
Note: If you avoid using stable
as branch name, you can always upgrade this workflow to the complete branching model [125] later on.
Just vanilla Mercurial.
The workflow is 6-stepped:
Let’s see the steps in detail.
First start a new branch with the name of the feature (starting from default).
hg branch feature-x \# do some changes hg commit -m "Started implemented feature-x"
Then commit away and push whenever you finish something which might be of interest to others, regardless how marginal.
You can push to a shared repository, or to your own clone or even send the changes via email to other contributors (for example via the mailbomb extension).
Merge changes in the default branch into your feature as often as possible to reduce the work necessary when you want to merge the feature later on.
hg update feature-x hg merge default hg commit -m "merged default into feature-x"
When your feature is stable, merge it into default.
hg update default hg merge feature-x hg commit -m "merged feature-x"
And when the feature needs no more work, close the branch.
\# start from default, automatic when using a fresh clone hg update default hg branch feature-x \# do some changes hg commit -m "started feature X" hg push
\# commit and push as you like
hg update default hg merge feature-x hg ci -m "merged feature X into default" hg commit --close-branch -m "finished feature X"
This hides the branch from the output of hg branches
, so you don’t clutter your history.
To improve a feature after it was officially closed, first merge default into the feature branch (to get it up to date), then work just as if you had started it.
hg up feature-x hg merge default hg ci -m "merged default into feature X" \# commit, push, repeat, finish
Generally merge default into your feature as often as possible.
If this workflow helps you, I’d be glad to hear from you [126]!
For a more extensive project-workflow, have a look at the Complete Mercurial Branching Strategy [125]. It extends the feature branches workflow to account for release cycles.
Written in the Mercurial mailing list [127]
Hi Bernard,
Am Dienstag 03 Februar 2009 20:19:14 schrieb ... ...:
> Most of the docs I can find seem to assume the reader is familiar with
> existing software developemnt tools and methodologies.
>
> This is not the case for me.
It wasn't for me either, and I can assure you that using Mercurial becomes
natural quite quickly.
> Now, I need to coordinate with a second (also SCM clueless) programmer.
...
> I envision us both working the main trunk for many small day-to-day
> changes, and our own isolated repo for larger additions that we will each
> be working on.
I don't know about a HOWTO, but I can give you a short description about basic
usage and the workflow I'd use:
Basic usage
Workflow
(ADDRESS can be either a host or an IP).
That's your repository for the small day to day changes.
In that clone you simply work, pull and commit as usual, but you only push after you finished the feature.
Once you finished the feature, you push the changes from the feature clone via "hg push" in feature1 (which gets them into your main working clone) and then push then onward into the shared repository.
That's it - or rather that's what I'd do. It might be right for you, too, and
if it isn't, don't be shy of experimenting. As long as you have a backup clone
lying around (for example cloned to a USB stick via "hg clone project
path/to/stick/project"), you can't do too much damage :)
I hope I could provide a bit of help :)
Short version (rename from $OLD to $NEW):
ROOT="$(hg id -qr 'first(roots(branch('$OLD')))')" MSG="$(hg log -r $ROOT -T '{desc}')" hg update $ROOT hg branch $NEW hg commit --amend -m "$MSG" hg evolve --all
Mercurial records in which named branch a commit was created. This can be inconvenient when you choose temporary branch names like "foo" or "justworkdamnit".
The evolve extension [128] enables safe, collaborative history editing which removes this inconvenience while preserving the reliability guarantees of Mercurial [129].
Here I show in a quick example how to rename a branch in Mercurial using the evolve extension.
You can use this method for all changes which you did not transfer elsewhere yet (they must be in draft or secret phase [130]).
Note (2016): The evolve extension is still in testing. Do not use it for production yet. If you want to help stabilizing it, please join evolve-testers [131]. I’ve been using it for more than a year, but I know how to fix things when I hit a bug in the evolve extension.
hg clone https://www.mercurial-scm.org/repo/evolve/ ~/.local/share/hgext-evolve
echo "[extensions]
evolve = ~/.local/share/hgext-evolve/hgext/evolve.py" >> ~/.hgrc
hg init foo cd foo echo 1 > 1 hg ci -Am 1 echo stable > 1 hg branch stapling hg ci -m stable # add a second commit to the branch # to make this non-trivial echo stable2 > 1 hg ci -m stable2
# amend the first revision in the branch hg up -r "first(branch(stapling))" hg branch stable hg ci --amend -m stable # (notes that there is an unstable changeset) # evolve the history hg evolve
hg log -G
That’s it.
@ changeset: 5:1822f3b02b72
| branch: stable
| tag: tip
| user: Freenet
| date: Fri Nov 18 00:56:57 2016 +0100
| summary: stable2
|
o changeset: 4:d47764612e1a
| branch: stable
| parent: 0:d2b5bb69b11b
| user: Freenet
| date: Fri Nov 18 00:56:56 2016 +0100
| summary: stable
|
o changeset: 0:d2b5bb69b11b
user: Freenet
date: Fri Nov 18 00:56:55 2016 +0100
summary: 1
☺ Yay! ☺
Happy Hacking!
PS: If you want to share this: Short on GNU social [132]
PPS: If you want to tweet this:
hg branch name O→N
— ((λ()'ArneBab)) (@ArneBab) November 18, 2016 [135]
I="$(hg id -qr 'first(branch('$O'))')"
M="$(hg log -r $I [133] -T' {desc}')"
hg up $I [133]
hg branch $N [134]
hg ci --amend -m "$M"
hg ev
PPPS: For efficient collaboration via Mercurial see the complete branching strategy [136].
Currently I rework my code extensively before I push it into upstream SVN. Some of that is inconvenient and it would be nicer to have easy to use refactoring tools.
hg evolve [140] might offer that.
This test uses the mutable-hg extension in revision c70a1091e0d8 (24 changesets after 2.1.0). It will likely be obsolete, soon, since mutable-hg is currently moved into Mercurial core by Pierre-Yves David, its main developer. I hope it will be useful for you, to assess the future possibilities of Mercurial today. This is not (only) a pun on “obsolete”, the functionality at the core of evolve which allows safe, collaborative history rewriting ☺
# Tests for refactoring history with the evolve extension export LANG=C # to get rid of localized strings export PS1="$ " rm -r testmy testother testpublic
Initialize the repos I need for the test.
We have one public repo and 2 nonpublishing repos.
# Initialize the test repo hg init testpublic # a public repo hg init testmy # my repo hg init testother # other repo # make the two private repos nonpublishing for i in my other do echo "[ui] username = $i [phases] publish = False" > test${i}/.hg/hgrc done
note: it would be nice if we could just specify nonpublishing with the init command.
Prepare the content of the repos.
cd testmy echo "Hello World" > hello.txt hg ci -Am "Hello World" hg log -G cd ..
adding hello.txt @ changeset: 0:c19ed5b17f4f tag: tip user: my date: Sat Jan 12 00:17:40 2013 +0100 summary: Hello World
Add a bad change and amend it.
cd testmy sed -i s/World/Evoluton/ hello.txt hg ci -m "Hello Evolution" echo hg log -G cat hello.txt # FIX this up sed -i s/Evoluton/Evolution/ hello.txt hg amend -m "Hello Evolution" # pass the message explicitely again to avoid having the editor pop up echo hg log -G cd ..
@ changeset: 1:83a5e89adea6 | tag: tip | user: my | date: Sat Jan 12 00:17:41 2013 +0100 | summary: Hello Evolution | o changeset: 0:c19ed5b17f4f user: my date: Sat Jan 12 00:17:40 2013 +0100 summary: Hello World Hello Evoluton @ changeset: 3:129d59901401 | tag: tip | parent: 0:c19ed5b17f4f | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution | o changeset: 0:c19ed5b17f4f user: my date: Sat Jan 12 00:17:40 2013 +0100 summary: Hello World
Add a bad change. Followed by a good change. Pull both into another repo and amend it. Do a good change in the other repo. Then amend the bad change in the original repo, pull it into the other and evolve.
Now we change the format to planning a roleplaying session to have a more complex task. We want to present this as coherent story on how to plan a story, so we want clean history.
First I do my own change.
cd testmy # Now we add the bad change echo "Wishes: - The Solek wants Action - The Judicator wants Action " >> plan.txt hg ci -Am "What the players want" # show what we did echo hg log -G -r tip # and the good change echo "Places: - The village - The researchers cave " >> plan.txt hg ci -m "The places" echo hg log -G -r 1: cd ..
adding plan.txt @ changeset: 4:b170dda0a4a7 | tag: tip | user: my | date: Sat Jan 12 00:17:44 2013 +0100 | summary: What the players want | @ changeset: 5:2a37053027cc | tag: tip | user: my | date: Sat Jan 12 00:17:45 2013 +0100 | summary: The places | o changeset: 4:b170dda0a4a7 | user: my | date: Sat Jan 12 00:17:44 2013 +0100 | summary: What the players want | o changeset: 3:129d59901401 | parent: 0:c19ed5b17f4f | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution |
Now my file contains the wishes of the players as well as the places.
We pull the changes into the repo of another gamemaster with whom we plan this game.
hg -R testother pull -u testmy hg -R testother log -G -r 1:
pulling from testmy requesting all changes adding changesets adding manifests adding file changes added 4 changesets with 4 changes to 2 files 2 files updated, 0 files merged, 0 files removed, 0 files unresolved @ changeset: 3:2a37053027cc | tag: tip | user: my | date: Sat Jan 12 00:17:45 2013 +0100 | summary: The places | o changeset: 2:b170dda0a4a7 | user: my | date: Sat Jan 12 00:17:44 2013 +0100 | summary: What the players want | o changeset: 1:129d59901401 | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution |
note: the revisions numbers are different because the other repo only gets those obsolete revisions which are ancestors to non-obsolete revisions. That way evolve slowly cleans out obsolete revisions from the history without breaking repositories which already have them (but giving them a clear and easy path for evolution).
He then adds the important people:
cd testother echo "People: - The Lost - The Specter " >> plan.txt hg ci -m "The people" echo hg log -G -r 1: cd ..
@ changeset: 4:65cc97fc774a | tag: tip | user: other | date: Sat Jan 12 00:17:48 2013 +0100 | summary: The people | o changeset: 3:2a37053027cc | user: my | date: Sat Jan 12 00:17:45 2013 +0100 | summary: The places | o changeset: 2:b170dda0a4a7 | user: my | date: Sat Jan 12 00:17:44 2013 +0100 | summary: What the players want | o changeset: 1:129d59901401 | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution |
And I realize too late, that my estimate of the wishes of the players was wrong. So I simply amend it.
cd testmy hg up -r -2 sed -i "s/The Solek wants Action/The Solek wants emotionally intense situations/" plan.txt hg amend -m "The wishes of the players" hg log -G -r 1: cd ..
1 files updated, 0 files merged, 0 files removed, 0 files unresolved 1 new unstable changesets @ changeset: 7:86e7a5305c9e | tag: tip | parent: 3:129d59901401 | user: my | date: Sat Jan 12 00:17:50 2013 +0100 | summary: The wishes of the players | | o changeset: 5:2a37053027cc | | user: my | | date: Sat Jan 12 00:17:45 2013 +0100 | | summary: The places | | | x changeset: 4:b170dda0a4a7 |/ user: my | date: Sat Jan 12 00:17:44 2013 +0100 | summary: What the players want | o changeset: 3:129d59901401 | parent: 0:c19ed5b17f4f | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution |
Now I amended my commit, but my history does not look good, yet. Actually it looks evil, since I have 2 heads, which is not so nice. The changeset under which we just pulled away the bad change has become unstable, because its ancestor has been obsoleted, so it has no stable foothold anymore. In other DVCSs, this means that we as users have to find out what was changed and fix it ourselves.
Changeset evolution allows us to evolve our repository to get rid of dependencies on obsolete changes.
cd testmy hg evolve hg log -G -r 1: cd ..
move:[5] The places atop:[7] The wishes of the players merging plan.txt @ changeset: 8:0980732d20e0 | tag: tip | user: my | date: Sat Jan 12 00:17:45 2013 +0100 | summary: The places | o changeset: 7:86e7a5305c9e | parent: 3:129d59901401 | user: my | date: Sat Jan 12 00:17:50 2013 +0100 | summary: The wishes of the players | o changeset: 3:129d59901401 | parent: 0:c19ed5b17f4f | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution |
Now I have nice looking history without any hassle - and without having to resort to low-level commands.
But I rewrote history. What happens if my collegue pulls this?
hg -R testother pull testmy hg -R testother log -G
pulling from testmy searching for changes adding changesets adding manifests adding file changes added 2 changesets with 2 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) 1 new unstable changesets o changeset: 6:0980732d20e0 | tag: tip | user: my | date: Sat Jan 12 00:17:45 2013 +0100 | summary: The places | o changeset: 5:86e7a5305c9e | parent: 1:129d59901401 | user: my | date: Sat Jan 12 00:17:50 2013 +0100 | summary: The wishes of the players | | @ changeset: 4:65cc97fc774a | | user: other | | date: Sat Jan 12 00:17:48 2013 +0100 | | summary: The people | | | x changeset: 3:2a37053027cc | | user: my | | date: Sat Jan 12 00:17:45 2013 +0100 | | summary: The places | | | x changeset: 2:b170dda0a4a7 |/ user: my | date: Sat Jan 12 00:17:44 2013 +0100 | summary: What the players want | o changeset: 1:129d59901401 | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution | o changeset: 0:c19ed5b17f4f user: my date: Sat Jan 12 00:17:40 2013 +0100 summary: Hello World
As you can see, he is told that his changes became unstable, since they depend on obsolete history. No need to panic: He can just evolve his repo to be state of the art again.
But the unstable change is the current working directory, so evolve does not change it. Instead it tells us, that we might want to call it with `–any`. And as it is the case with most hints in hg, that is actually the case.
hg -R testother evolve
nothing to evolve here (1 troubled changesets, do you want --any ?)
note: that message might be a candidate for cleanup.
hg -R testother evolve --any hg -R testother log -G -r 1:
move:[4] The people atop:[6] The places merging plan.txt @ changeset: 7:058175606243 | tag: tip | user: other | date: Sat Jan 12 00:17:48 2013 +0100 | summary: The people | o changeset: 6:0980732d20e0 | user: my | date: Sat Jan 12 00:17:45 2013 +0100 | summary: The places | o changeset: 5:86e7a5305c9e | parent: 1:129d59901401 | user: my | date: Sat Jan 12 00:17:50 2013 +0100 | summary: The wishes of the players | o changeset: 1:129d59901401 | user: my | date: Sat Jan 12 00:17:42 2013 +0100 | summary: Hello Evolution |
And as you can see, everything looks nice again.
Publishing the changes into a public repo makes them immutable.
Now imagine, that my co-gamemaster publishes his work. Mercurial will then store that his changes were published and warn us, if we try to change them.
cd testother hg up > /dev/null echo "current phase" hg phase . hg push ../testpublic echo "phase after publishing" hg phase . cd ..
current phase 7: draft pushing to ../testpublic searching for changes adding changesets adding manifests adding file changes added 5 changesets with 5 changes to 2 files phase after publishing 7: public
Now trying to amend history will fail (except if we first change the phase to draft with `hg phase –force –draft .`).
cd testother hg amend -m "change published history" # change to draft hg phase -fd . hg phase . # now we could amend, but that would defeat the point of this section, so we go to public again. hg phase -p . cd ..
abort: can not rewrite immutable changeset 058175606243 7: draft
Once I pull from that repo, the revisions which are in there will also switch phase to public in my repo.
So by pushing the changes into a publishing repo, you can get the Mercurial of all contributors to track which revisions are safe to change - and which are not. An alternative is using `hg phase -p REV`.
Do multiple commits to create a patch, then fold them into one commit.
Now I go into a bit of a planning spree.
cd testmy echo "Scenes:" >> plan.txt hg ci -m "we need scenes" echo "- Lost appears" >> plan.txt hg ci -m "scene" echo "- People vanish" >> plan.txt hg ci -m "scene" echo "- Portals during dreamtime" >> plan.txt hg ci -m "scene" echo hg log -G -r 9: cd ..
@ changeset: 12:fbcce7ad7369 | tag: tip | user: my | date: Sat Jan 12 00:18:06 2013 +0100 | summary: scene | o changeset: 11:189c0362a80f | user: my | date: Sat Jan 12 00:18:05 2013 +0100 | summary: scene | o changeset: 10:715a31ac9dee | user: my | date: Sat Jan 12 00:18:05 2013 +0100 | summary: scene | o changeset: 9:dfa4c226150b | user: my | date: Sat Jan 12 00:18:05 2013 +0100 | summary: we need scenes |
Yes, I tend to do that…
But we actually only need one change, so make it one by folding the last 4 changes changes into a single commit.
Since fold needs an interactive editor (it does not take -m, yet), we will leave that out. The commented commands allow you to fold the changesets.
cd testmy # hg fold -r "-1:-4" # hg log -G -r 9: cd ..
Do one big commit, then split it into two atomic commits.
Now I apply the scenes to wishes, places and people. Which is not useful: First I should apply them to the wishes and check if all wishes are fullfilled. But while writing I forgot that, and anxious to show my co-gamemaster, I just did one big commit.
cd testmy sed -i "s/The Judicator wants Action/The Judicator wants Action - portals/" plan.txt sed -i "s/The village/The village - lost, vanish, portals/" plan.txt hg ci -m "Apply Scenes to people and places." echo hg log -G -r 12: cd ..
@ changeset: 13:5c83a3540c37 | tag: tip | user: my | date: Sat Jan 12 00:18:10 2013 +0100 | summary: Apply Scenes to people and places. | o changeset: 12:fbcce7ad7369 | user: my | date: Sat Jan 12 00:18:06 2013 +0100 | summary: scene |
Let’s fix that: uncommit it and commit it as separate changes. Normally I would just use `hg record` to interactively select changes to record. Since this is a non-interactive test, I manually undo and redo changes instead.
cd testmy hg uncommit --all # to undo all changes, not just those for specified files hg diff sed -i "s/The village - lost, vanish, portals/The village/" plan.txt hg amend -m "Apply scenes to wishes" sed -i "s/The village/The village - lost, vanish, portals/" plan.txt hg commit -m "Apply scenes to places" echo hg log -G -r 12: cd ..
new changeset is empty (use "hg kill ." to remove it) diff --git a/plan.txt b/plan.txt --- a/plan.txt +++ b/plan.txt @@ -1,10 +1,10 @@ Wishes: - The Solek wants emotionally intense situations -- The Judicator wants Action +- The Judicator wants Action - portals Places: -- The village +- The village - lost, vanish, portals - The researchers cave Scenes: @ changeset: 17:f8cc86f681ac | tag: tip | user: my | date: Sat Jan 12 00:18:13 2013 +0100 | summary: Apply scenes to places | o changeset: 16:6c8918a352e2 | parent: 12:fbcce7ad7369 | user: my | date: Sat Jan 12 00:18:12 2013 +0100 | summary: Apply scenes to wishes | o changeset: 12:fbcce7ad7369 | user: my | date: Sat Jan 12 00:18:06 2013 +0100 | summary: scene |
Do one big commit, add an atomic commit. Then split the big commit.
Let’s get the changes from our co-gamemaster and apply people to wishes, places and scenes. Then add a scene we need to fullfill the wishes and clean the commits afterwards.
First get the changes:
cd testmy hg pull ../testother hg merge --tool internal:merge tip # the new head from our co-gamemaster # fix the conflicts sed -i "s/<<<.*local//" plan.txt sed -i "s/====.*/\n/" plan.txt sed -i "s/>>>.*other//" plan.txt # mark them as solved. hg resolve -m hg commit -m "merge people" echo hg log -G -r 12: cd ..
pulling from ../testother searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads .' to see heads, 'hg merge' to merge) merging plan.txt warning: conflicts during merge. merging plan.txt incomplete! (edit conflicts, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon @ changeset: 19:8bf8d55739fa |\ tag: tip | | parent: 17:f8cc86f681ac | | parent: 18:058175606243 | | user: my | | date: Sat Jan 12 00:18:16 2013 +0100 | | summary: merge people | | | o changeset: 18:058175606243 | | parent: 8:0980732d20e0 | | user: other | | date: Sat Jan 12 00:17:48 2013 +0100 | | summary: The people | | o | changeset: 17:f8cc86f681ac | | user: my | | date: Sat Jan 12 00:18:13 2013 +0100 | | summary: Apply scenes to places | | o | changeset: 16:6c8918a352e2 | | parent: 12:fbcce7ad7369 | | user: my | | date: Sat Jan 12 00:18:12 2013 +0100 | | summary: Apply scenes to wishes | | o | changeset: 12:fbcce7ad7369 | | user: my | | date: Sat Jan 12 00:18:06 2013 +0100 | | summary: scene | |
Now we have all changes in our repo. We begin to apply people to wishes, places and scenes.
cd testmy sed -i "s/The Solek wants emotionally intense situations/The Solek wants emotionally intense situations | specter, Lost/" plan.txt sed -i "s/Lost appears/Lost appears | Lost/" plan.txt sed -i "s/People vanish/People vanish | Specter/" plan.txt hg commit -m "apply people to wishes, places and scenes" echo hg log -G -r 19: cat plan.txt cd ..
@ changeset: 20:c00aa6f24c3f | tag: tip | user: my | date: Sat Jan 12 00:18:18 2013 +0100 | summary: apply people to wishes, places and scenes | o changeset: 19:8bf8d55739fa |\ parent: 17:f8cc86f681ac | | parent: 18:058175606243 | | user: my | | date: Sat Jan 12 00:18:16 2013 +0100 | | summary: merge people | | Wishes: - The Solek wants emotionally intense situations | specter, Lost - The Judicator wants Action - portals Places: - The village - lost, vanish, portals - The researchers cave Scenes: - Lost appears | Lost - People vanish | Specter - Portals during dreamtime People: - The Lost - The Specter
As you can see, the specter only applies to the wishes, and we miss a person for the action.
Let’s fix that.
cd testmy sed -i "s/- The Specter/- The Specter\n- Wild Memories/" plan.txt sed -i "s/- Portals during dreamtime/- Portals during dreamtime\n- Unconnected Memories/" plan.txt hg ci -m "Added wild memories to fullfill the wish for action" echo hg log -G -r 19: cd ..
@ changeset: 21:5393327d2d3f | tag: tip | user: my | date: Sat Jan 12 00:18:20 2013 +0100 | summary: Added wild memories to fullfill the wish for action | o changeset: 20:c00aa6f24c3f | user: my | date: Sat Jan 12 00:18:18 2013 +0100 | summary: apply people to wishes, places and scenes | o changeset: 19:8bf8d55739fa |\ parent: 17:f8cc86f681ac | | parent: 18:058175606243 | | user: my | | date: Sat Jan 12 00:18:16 2013 +0100 | | summary: merge people | |
Now split the big change into applying people first to wishes, then to places and scenes.
cd testmy # go back to the big change hg up -r -2 # uncommit it hg uncommit --all # Now rework it into two commits sed -i "s/- Lost appears | Lost/- Lost appears/" plan.txt sed -i "s/- People vanish | Specter/- People vanish/" plan.txt hg amend -m "Apply people to wishes" sed -i "s/- Lost appears/- Lost appears | Lost/" plan.txt sed -i "s/- People vanish/- People vanish | Specter/" plan.txt hg commit -m "Apply people to scenes" # let’s mark this for later use hg book splitchanges # and evolve to get rid of the obsoletes echo hg evolve --any hg log -G -r 19: cd ..
1 files updated, 0 files merged, 0 files removed, 0 files unresolved new changeset is empty (use "hg kill ." to remove it) 1 new unstable changesets move:[21] Added wild memories to fullfill the wish for action atop:[24] Apply people to wishes merging plan.txt @ changeset: 26:ab48ecaceb01 | tag: tip | parent: 24:909bb640d4fc | user: my | date: Sat Jan 12 00:18:20 2013 +0100 | summary: Added wild memories to fullfill the wish for action | | o changeset: 25:76083662b263 |/ bookmark: splitchanges | user: my | date: Sat Jan 12 00:18:23 2013 +0100 | summary: Apply people to scenes | o changeset: 24:909bb640d4fc | parent: 19:8bf8d55739fa | user: my | date: Sat Jan 12 00:18:23 2013 +0100 | summary: Apply people to wishes | o changeset: 19:8bf8d55739fa |\ parent: 17:f8cc86f681ac | | parent: 18:058175606243 | | user: my | | date: Sat Jan 12 00:18:16 2013 +0100 | | summary: merge people | |
You can see the additional commit sticking out. We want to get the history easy to follow, so we just graft the last last change atop the split changes.
note: We seem to have the workdir on the new changeset instead of on the one we did before the evolve. I assume that’s a bug to fix.
cd testmy hg up splitchanges hg graft -O tip hg log -G -r 19: cd ..
1 files updated, 0 files merged, 0 files removed, 0 files unresolved grafting revision 26 merging plan.txt @ changeset: 27:4d3a40c254b4 | bookmark: splitchanges | tag: tip | parent: 25:76083662b263 | user: my | date: Sat Jan 12 00:18:20 2013 +0100 | summary: Added wild memories to fullfill the wish for action | o changeset: 25:76083662b263 | user: my | date: Sat Jan 12 00:18:23 2013 +0100 | summary: Apply people to scenes | o changeset: 24:909bb640d4fc | parent: 19:8bf8d55739fa | user: my | date: Sat Jan 12 00:18:23 2013 +0100 | summary: Apply people to wishes | o changeset: 19:8bf8d55739fa |\ parent: 17:f8cc86f681ac | | parent: 18:058175606243 | | user: my | | date: Sat Jan 12 00:18:16 2013 +0100 | | summary: merge people | |
note: We use graft here, because using a second amend would just change the changeset in between but not add another change. If there had been more changes after the single followup commit, we would simply have called evolve to fix them, because graft -O left an obsolete marker on the grafted changeset, so evolve would have seen how to change all its children.
That’s it. All that’s left is finishing plan.txt, but I’ll rather do that outside this guide :)
Evolve does a pretty good job at making it convenient and safe to rework history. If you’re an early adopter, I can advise testing it yourself. Otherwise, it might be better to wait until more early adopters tested it and polished its rough edges.
note: hg amend was subsumed into hg commit –amend, so the dedicated command will likely disappear.
PS: In case you’re interested: The roleplaying session worked out wonderfully and a good deal of our planning actually survived the contact with the players - enough that we could wing the rest with short coordination meetings in which we two game masters enthusiastically told each other what happened in the respective group, planned the next steps and enjoyed the evil gamemasters giggle ☺.note: This guide was created by Arne Babenhauserheide [141] with emacs [142] org-mode [22] and turned to html via M-x org-export-as-html - including results of the evaluation of the code snippets.
Date: 2013-01-12T00:18+0100
Org [22] version 7.9.2 with Emacs [44] version 24
Validate XHTML 1.0 [45]Anhang | Größe |
---|---|
hg-evolve-2013-01-12.pdf [137] | 254.54 KB |
hg-evolve-2013-01-12.org [138] | 13.19 KB |
If you want to publish your scientific scripts, as Nick Barnes advises in Nature [143], you can very easily do so with Mercurial.
All my stuff (not just code), excempting only huge datasets, is in a Mercurial source repository.1
Whenever I change something and it does anything new, I commit the files with a simple commit (even if it’s only “it compiles!”).
With that I can always check “which were the last things I did” (look into the log) or “when did I change this line, and why?” (annotate the file). Also I can easily share my scripts folder with others and Mercurial can merge my work and theirs, so if they fix a line and I fix another line, both fixes get integrated without having to manually copy-paste them around.
For all that it doesn’t need much additional expertise: The basics can be learned in just 15 minutes — and you’ll likely never need more than these for your work.2
Update 2013: Nowadays I include the revision
of scripts I use in the name of their output files or folders, so I always know which version of my scripts I used to create some result.
Mercurial is free software for versiontracking: http://mercurial-scm.org [56] ↩
You can use Mercurial in three main ways:
Just use the commandline client (GNU/Linux, Windows, Mac OSX, …). 15 minutes for the basics: http://mercurial-scm.org/guide [14]
Use the graphical interface integrated into the Windows explorer, also callable via hgtk in Unixoid systems: https://tortoisehg.readthedocs.io/en/latest/quick.html [144]
Use a program which integrates Mercurial: http://mercurial-scm.org/wiki/OtherTools [145]
Written in the discussion about a pull request for Freenet [146].
When I look up a commit, I’m not searching for prose. I’m searching for short snippets of information I need. If they are long-winded explanations, I am unlikely to even read them.
To understand this, please imagine coming back home, getting off the bike and taking 15 minutes to look at the most recent pull-request. You know that you’ll need to start making dinner at 19:00, so there is no time to waste.
With long winded commit messages that plays out like this:
You look into the pull-request and the explanations are longer than the code changes. You can either read half the explanations or just look at the code. So you try to understand what the code does and what it intends to do from the code alone. After 15 minutes you post a partial review and start cooking. Next slot for code review is tomorrow evening, or maybe next friday. The pull-request lies open for several weeks while more changes pile up.
Contrast that with short commit messages:
You look into the pull-request. The commit message gives you the intention of the change (“sounds good”), maybe with a short note on non-obvious side-effects of the implementation, and you skim the code to see whether it realizes the intention. If it does and you don’t see problems which the writer might have overlooked: Great, code review finished. You write the review and go make dinner. The pull-request is merged the same week.
That’s why I’d suggest to just write short messages and put detailed explanations into a blog. If you like writing those explanations. That’s what you have a blog for, and you can search that later if you need these notes. If they are essential to understand effects of later changes, you might want to document them in a text file like HACKING or docs/devnotes.txt
.
The Linux kernel has nice examples of concise commit messages:
Note that the merge commit already almost looks like an entry into a NEWS file [149] using the Perl Changes Format [150]. (If NEWS files cause you merging pain, consider setting a union merge rule [151].)
A workflow where the repository gets updated only from repositories whose heads got signed by at least a certain percentage or a certain number of trusted committers.
Mercurial [56], two hooks for checking and three special files in the repo.
The hooks do all the work - apart from them, the repo is just a normal Mercurial repository. After cloning it, you only need to setup the hooks to activate the workflow.
Extensions: gpg
Hooks: prechangegroup and pretxnchangegroup
Files: .hgtrustedkeys , .hgbackuprepos , .hgtrustminimum
prechangegroup: Copy the local versions of the files for access in the pretxnchangegroup hook (might be unnecessary by letting the pretxnchangegroup hook use the rollback-info).
pretxnchangegroup:
.hgtrustedkeys contains a list of public GnuPG keys.
.hgbackuprepos contains a list of (pull) links to backup repositories.
.hgtrustminimum contains the percentage or number of keys from which a signature is needed for a head to be accepted.
With this workflow you can even do automatic updates from the repository. It should be ideal for release repositories of distributed projects.
If you want to work on the project, a very worthwhile goal might be implementing it in infocalypse [152]: anonymous code collaboration via Freenet and Mercurial, built to survive the informational apocalypse (and any kind of censorship).
Links:
[1] http://selenic.com/mercurial
[2] http://mercurial.selenic.com.org
[3] http://bitbucket.org
[4] http://hginit.com/
[5] http://blogs.atlassian.com/2012/02/mercurial-vs-git-why-mercurial/
[6] https://www.mercurial-scm.org/wiki/CategoryDeveloper
[7] https://bitbucket.org/durin42/hg-git
[8] http://mercurial.selenic.com/wiki/UsingExtensions
[9] https://code.facebook.com/posts/218678814984400/scaling-mercurial-at-facebook/
[10] http://gregoryszorc.com/blog/2015/10/22/cloning-improvements-in-mercurial-3.6/
[11] http://gregoryszorc.com/blog/2013/05/12/thoughts-on-mercurial-(and-git)/
[12] https://www.draketo.de/software/mercurial-branching-strategy
[13] http://mercurial.selenic.com
[14] http://mercurial-scm.org/guide
[15] https://bitbucket.org/ArneBab/hg-init-science/downloads/hg-init-science-0.2.5.pdf
[16] https://www.draketo.de/light/english/mercurial/complete-branching-strategy#extensions
[17] https://www.draketo.de/branching-strategy#multiple-releases
[18] https://www.draketo.de/branching-strategy#graft-releases
[19] https://www.draketo.de/branching-strategy#review-branch
[20] https://www.draketo.de/files/2012-09-03-Mo-hg-branching-diagrams_13.org
[21] https://www.draketo.de/light/english/emacs
[22] http://orgmode.org
[23] http://ditaa.sourceforge.net/
[24] http://nvie.com/posts/a-successful-git-branching-model/
[25] http://freenetproject.org
[26] http://mercurial.selenic.com/wiki/GpgExtension
[27] https://nvie.com/img/git-model@2x.png
[28] https://www.draketo.de/files/hgbranchingoverview_2.png
[29] https://www.draketo.de/files/hgbranchinggraft_3.png
[30] https://www.draketo.de/files/hgbranchingreview.png
[31] https://www.draketo.de/files/2012-09-03-Mo-hg-branching-diagrams_12.org
[32] https://www.draketo.de/files/hgbranchingmaintain_3.png
[33] http://tortoisehg.bitbucket.org/manual/2.9/quick.html
[34] http://tortoisehg.sf.net
[35] http://www.bitbucket.org/ArneBab/md-esw-2009
[36] http://bitbucket.org/tortoisehg/stable/wiki/intro
[37] http://betterexplained.com/articles/intro-to-distributed-version-control-illustrated/
[38] http://steamlogic.com
[39] http://draketo.de/licht/freie-software/mercurial/kurze-einf-hrung-mercurial-und-tortoisehg
[40] https://www.draketo.de/
[41] https://raw.githubusercontent.com/git/git/master/Documentation/RelNotes/2.3.0.txt
[42] https://mercurial-scm.org/wiki/WhatsNew#Mercurial_3.0_.282014-05-01.29
[43] http://webchat.freenode.net?randomnick=1&channels=%23mercurial
[44] http://www.gnu.org/software/emacs/
[45] http://validator.w3.org/check?uri=referer
[46] https://www.draketo.de/files/dvcs-basic-svn.png
[47] https://www.draketo.de/files/dvcs-basic-svn-testing.png
[48] https://www.draketo.de/files/dvcs-basic-hg.png
[49] https://www.draketo.de/files/dvcs-basic-hg-testing.png
[50] https://www.draketo.de/files/dvcs-basic-git.png
[51] https://www.draketo.de/files/dvcs-basic-git-testing.png
[52] https://www.draketo.de/files/2013-04-17-Mi-basic-usecase-dvcs.org
[53] https://www.draketo.de/files/2013-04-17-Mi-basic-usecase-dvcs_0.pdf
[54] http://mercurial.selenic.com/wiki/WhatsNew#Mercurial_2.4_.282012-11-1.29
[55] http://hg-git.github.io/
[56] http://mercurial-scm.org
[57] https://blogs.atlassian.com/2012/03/git-vs-mercurial-why-git/
[58] http://mercurial.selenic.com/wiki/MqExtension
[59] http://hg-lab.logilab.org/doc/mutable-history/html/
[60] http://www.selenic.com/hg/help/phases
[61] http://mercurial.selenic.com/wiki/WhatsNew/Archive#Mercurial_1.8_.282011-03-01.29
[62] https://web.archive.org/web/20130331015536/http://ghostinthecode.posterous.com/choosing-how-to-branch-in-mercurial
[63] http://mercurial.selenic.com/wiki/RecordExtension
[64] http://mercurial.selenic.com/wiki/HgGit
[65] https://bitbucket.org/durin42/hg-git/commits/06366111af3c6a2ffa06333ed60d3ed3b9ec0763
[66] http://draketo.de/light/english/dvcs-workflow-failures-git-hg#sec-2-3-2
[67] https://blogs.atlassian.com/2013/11/dont-move-to-git/
[68] https://bitbucket.org/site/master/issues/3843/largefiles-support-bb-3903#comment-25745558
[69] https://bitbucket.org/site/master/issues/3843/largefiles-support-bb-3903
[70] https://www.draketo.de/light/english/mercurial/factual-errors-why-git-atlassian
[71] https://blog.getsync.com/2016/02/03/virtuos-games-uses-sync-to-move-huge-datasets-when-porting-titles/
[72] http://help.fogcreek.com/8168/how-to-use-the-mercurial-largefiles-extension
[73] https://www.mercurial-scm.org/wiki/LargefilesExtension
[74] https://www.mercurial-scm.org/wiki/WhatsNew/Archive#Mercurial_2.0_.282011-11-01.29
[75] https://www.draketo.de/english/comments/light/never-trust-a-company
[76] http://felipec.wordpress.com/2011/01/16/mercurial-vs-git-its-all-in-the-branches/
[77] http://www.reddit.com/r/programming/comments/20r8vu/factual_errors_in_git_vs_mercurial_why_git_from/
[78] https://www.draketo.de/files/git-vs-hg-offensive.png
[79] https://www.draketo.de/light/english/mercurial/factual-errors-why-git-atlassian#friendly
[80] https://www.draketo.de/files/git-vs-hg-offensive-purevector-retouch2.kra
[81] http://krita.org/
[82] https://www.draketo.de/light/english/dvcs-workflow-failures-git-hg
[83] https://www.draketo.de/light/english/dvcs-workflow-failures-git-hg#update-2014-05-01
[84] http://www.shadowrun.com/
[85] http://mercurial-scm.org/wiki/RecordExtension
[86] http://mercurial-scm.org/wiki/EvolveExtension
[87] http://mercurial-scm.org/wiki/Infocalypse
[88] http://creativecommons.org/licenses/by/4.0/
[89] http://gnu.org/l/gpl
[90] https://www.draketo.de/files/git-vs-hg-offensive.tar_.gz
[91] https://www.draketo.de/files/git-vs-hg-offensive.zip
[92] https://www.draketo.de/files/git-vs-hg-offensive-purevector-retouch2.png
[93] https://www.draketo.de/files/git-vs-hg-offensive-thumb.jpg
[94] https://www.draketo.de/files/git-vs-hg-offensive-thumb-240x240.jpg
[95] http://packages.gentoo.org/package/dev-util/mercurial
[96] http://selenic.com/repo/hg
[97] http://gentoo.org
[98] http://sources.gentoo.org/viewcvs.py/gentoo-x86/dev-util/mercurial/mercurial-9999.ebuild?view=markup
[99] http://sources.gentoo.org/viewcvs.py/gentoo-x86/eclass/mercurial.eclass?view=markup
[100] http://bugs.gentoo.org/251163
[101] http://www.gnu.org/licenses/gpl-2.0.html
[102] http://www.selenic.com/mercurial/wiki/UnderstandingMercurial
[103] http://hgbook.red-bean.com/read/behind-the-scenes.html
[104] http://www.selenic.com/mercurial/wiki/Tutorial
[105] http://hgbook.red-bean.com/
[106] http://mercurial-scm.org/wiki/UnderstandingMercurial
[107] http://gnupg.org
[108] http://www.selenic.com/mercurial/wiki/HgWebDirStepByStep#head-746ca383e3a62df34279ec2fca888113497da022
[109] http://www.selenic.com/mercurial/wiki/MultipleCommitters
[110] http://www.selenic.com/mercurial/wiki/SharedSSH
[111] https://bitbucket.org/Foo/bar/
[112] http://bitbucket.org/help/UsingSSH
[113] https://bitbucket.org/ArneBab/hello/
[114] http://127.0.0.1:8000
[115] https://bitbucket.org/USER/REPO
[116] http://www.selenic.com/mercurial/wiki/Workflows
[117] http://www.selenic.com/mercurial/wiki/GpgExtension
[118] http://www.selenic.com/mercurial/wiki/MqExtension
[119] http://selenic.com/mercurial/wiki/Workflows
[120] http://mercurial.selenic.com/wiki/Workflows#Feature_seperation_through_named_branches
[121] http://savannah.nongnu.org/projects/pyhurd/
[122] http://hurd.gnu.org
[123] http://www.selenic.com/pipermail/mercurial/2011-June/038718.html
[124] http://www.selenic.com/pipermail/mercurial/2011-June/038715.html
[125] https://www.draketo.de/light/english/mercurial/complete-branching-strategy
[126] https://www.draketo.de/contact
[127] http://selenic.com/pipermail/mercurial/2009-February/023883.html
[128] https://www.mercurial-scm.org/wiki/EvolveExtension
[129] https://www.mercurial-scm.org
[130] https://www.mercurial-scm.org/wiki/Phases
[131] https://www.mercurial-scm.org/mailman/listinfo/evolve-testers
[132] http://sn.1w6.org/conversation/163665#notice-191445
[133] https://twitter.com/search?q=%24I&src=ctag
[134] https://twitter.com/search?q=%24N&src=ctag
[135] https://twitter.com/ArneBab/status/799607154739126272
[136] http://www.draketo.de/branching-strategy
[137] https://www.draketo.de/files/hg-evolve-2013-01-12.pdf
[138] https://www.draketo.de/files/hg-evolve-2013-01-12.org
[139] http://draketo.de/proj/hg-evolve-test
[140] http://hg-lab.logilab.org/doc/mutable-history/html/index.html
[141] http://draketo.de
[142] http://gnu.org/software/emacs
[143] http://www.nature.com/news/2010/101013/full/467753a.html
[144] https://tortoisehg.readthedocs.io/en/latest/quick.html
[145] http://mercurial-scm.org/wiki/OtherTools
[146] https://github.com/freenet/fred/pull/341
[147] https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7a6cb0abe1aa63334f3ded6d2b6c8eca80e72302
[148] https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=0a68c6bc7ce9d4855f3130f9eff8ff774b597531
[149] https://www.gnu.org/prep/standards/html_node/NEWS-File.html
[150] https://metacpan.org/pod/distribution/CPAN-Changes/lib/CPAN/Changes/Spec.pod
[151] https://www.draketo.de/english/free-software/merge-news
[152] https://www.draketo.de/english/mercurial/anonymous-code-collaboration-freenethg