When I met Mercurial, I learned all my knowledge from Spolsky’s articles (
Habré translation ), which describe in detail the basic principles of Mercurial’s work and daily work with it. For a long time I used Mercurial to the extent that did not exceed the volume of these articles. Probably for a single developer this is almost enough. Nearly. But Mercurial today is much broader and has the capabilities to allow editing of the history of changes, the presence of which, in general, is not obvious, although these possibilities are quite valuable. And from the comments to different articles on version control systems it is clear that many developers do not know about these features. Below, I want to review a number of Mercurial features related to history changes.
What will be discussed:
- phases
- hg commit –amend
- hg strip
- hg rebase
Local change history
Mercurial's daily work is to create new change sets (they are revisions). Any set, as soon as it is created, is forever written in the repository. It does not matter if this separate set is useful, or if it is a part of the dead-end branch of development that nobody wants, it is saved and copied to all repositories. At least, this is what Mercurial looks at first. On the other hand, sometimes it is really necessary to delete or replace changesets. Therefore, Mercurial has a number of tools that can change history. Changing revisions that are known to other repositories is considered dangerous, since these revisions can be used in other repositories, say, being parents of new branches. Therefore, deleting revisions never affects external repositories. Delete locally. When synchronizing, all revisions that you have removed from yourself will be re-tightened and create confusion. Therefore, the replacement of history in Mercurial is an action of a local character, which is carried out only within the limits of changes that no one but you can see. Mercurial attempts to automatically track the publicity or privacy of change sets using a phase mechanism.
')
Phases
Each change set belongs to one of three phases:
- if the change set has already been sent somewhere, or received from an external repository, then its public (public) phase cannot be changed.
- if the set is created locally, and has not yet been sent anywhere, then its draft phase can be changed so far, but as soon as possible (push or external pull) the set will be published and become public.
- if we do not want the set to become public, then we can manually assign it to the secret phase. Such a set will be published only if you manually return its phase to draft or public. You can easily change the set.
So, when we create a new change set with hg commit, this set refers to the draft phase. Only we have this set, however, as soon as possible, all changes will be sent to the common repository. The phase will change to public. If we want to work locally for the time being so that the changes are not published, we can clearly attribute the changes to the secret phase. Then the changes will remain in the local repository until we explicitly change the phase to draft or public.
The phase is checked and changed with the hg phase command. For example, take the repository, in which there is only one set of changes:
hg log changeset: 0:adfd3246d8b4 tag: tip user: date: Sat Nov 07 11:12:43 2015 +0300 summary: initial commit
Check the phase of changeset 0 now:
hg phase -r 0 0: draft
To change the phase, the –draft, --public or –secret parameter is added to the command (they are also –d, -p, -s). Change the phase to the secret:
hg phase --secret –-force -r 0 hg phase -r 0 0: secret
Please note that in order to increase the phase (in the direction from public to secret), you need to use the --force key. Reverse phase reduction is always safe. Most often, you only need to mark the change sets as secret, or return them to the draft phase. The phase mechanism is conceived in such a way as not to require special attention from the user. I remind you that changing sets of changes is possible only if they do not belong to the public phase.
same thing in tortoisehgThe phase is visible from the main TortoiseHg window. You can change it using the context menu.
Commit --amend
Probably, it happened to everybody a moment after the commit to detect an error in the message or to face the fact that the newly created commit broke the build. Just for these cases, the hg commit command has an amendment option. When this option is used instead of creating a separate set of changes, a correction is made to the last of the sets (more precisely, the current set). Everything is so simple that there is nothing to tell. Create a commit with an error in the text description:
hg commit -m " "
We notice an oversight and immediately correct it:
hg commit -m " " --amend saved backup bundle to D:\work\Habr\hg1\.hg\strip-backup/54b061ad6202-amend-backup.hg
Result:
hg log changeset: 0:88779cfe79c1 tag: tip user: date: Sat Nov 07 11:12:43 2015 +0300 summary:
It is not only the description that changes. You can make edits to the sources, and they will be added to the new set of changes. From the output of the command, you can see that Mercurial made a bakap, in case we did some stupid things. You can restore the backup using the hg unbundle command. And add: commit --amend only works with changesets that have no children.
same thing in tortoisehgFirst you need TortoiseHg to switch the commit mode. After that, the “Fix” button will be called “Cancel” (Translation is confusing. The meaning should be “Re-fix”). When it is pressed, commit --amend with all the pluses will be launched - you can change the message, you can fix the errors in the files.
Enable extensions
Commit --amend is always available, but the hg rebase and hg strip commands are standard extensions that are disabled by default. To enable extensions, add the following lines to the Mercurial.ini file (or to the .hg / hgrc file to include extensions in a separate repository only):
[extensions] rebase = strip =
Strip
The strip command removes a revision and all its descendants from the repository:
hg strip 8 saved backup bundle to D:\work\Habr\hg0\.hg\strip-backup/92f6544e0370-backup.hg
It's funny that the strip command deletes, but does not replace the history, so it is allowed to use it with sets of changes of any phase. However, if you delete sets that appear in other repositories, then when you next tighten the changes, all deletions will be restored.
Rebase
Understanding the Rebase command is the easiest of examples. The main task of rebase is to turn two different branches into a linear history.
While we were working on the X, Y, Z branch, revisions D, E appeared in the external repository.
Since we cannot touch other people's changes, we can relocate our own. The result should be:
Local X, Y, and Z change sets are deleted (saved to .hg / strip-backup, and similar change sets X2, Y2, Z2 are inserted instead).
Rebase is done with the following command:
hg rebase --source 4 --dest 8 saved backup bundle to D:\work\Habr\hg0\.hg\strip-backup/1ab1a1cc3d8d-backup.hg
There are other options for using the command. Abbreviated spelling command:
hg rebase -s 4 -d 8
Use --base instead of --source:
hg rebase --base 6 --dest 8
When the --base option is applied, Mercurial descends from the specified revision to a common ancestor, except for the most common ancestor. In our case, --base 6 means the same as --source 4.
If you specify only one of the options: base, source, or dest, then the current revision is used instead of the missing parameter.
Omit the option --dest, use the current revision as dest.
hg up 8 hg rebase --source 4
Omit the options --source and --base, use the current revision as the base.
hg up 4 hg rebase --dest 8
Examples to fix
Multiple usage scenarios from the Mercurial documentation.
Turn two branches into one
Simple branch movement:
hg rebase --dest E --base C.
We shift the beginning of the branch
The destination does not have to be the final revision:
hg rebase --dest D --base C.
Getting rid of the merger
A bit more complex structure:
hg rebase --dest C --source D.
The set of merge changes F ceases to exist as unnecessary.
Even more interesting case
hg rebase --dest I --source D
Revision H is deleted, this is a merge revision, and as a result of rebase, all the sets already contain all the necessary changes.
Full linearization of history
hg rebase --dest I --source B
Removed merge revisions D and H.
Transferring another branch
hg rebase --dest B --source C
Transfer part of another branch
hg rebase --dest I --source G
Collapse
Sometimes all changes need to fit into one revision. For this, the rebase command has an option - ollapse.
hg rebase --dest B --base E –collapse
Pull --rebase
A little automation that automatically starts a rebase every time you drag out changes. The result is to strive to ensure that all local changes grow to the branch pulled out from the outside. This does not always work, but if it works, then save the extra merger.
Restrictions
- A linear history is usually preferable to a complex graph containing many mergers. However, sometimes during a merge, complex manual work is done to resolve conflicts from two sets. After the rebase has been applied, the result of this work is saved, but the revision corresponding to the merge disappears and, accordingly, the errors made as a result of manual merging cannot be checked or corrected.
- Both rebase and pull --rebase give an error if there are uncommitted changes in the repository. Before using extensions, you need to do something from the list:
Other history editing features
- MQ extension . Allows you to edit the history, however, it is considered obsolete and is not recommended for use, since the rebase, strip, histedit, graft, commit –amend commands were created to replace the MQ.
- HistEdit extension . Allows you to edit the history in manual mode, making separate operations with separate sets of changes.
- RebaseIf extension . Does the same thing as Rebase, but seeks to preserve nontrivial merges. Not included in the standard Mercurial distribution.
- Evolve extension . An experimental extension that adds even more story editing commands. For example: uncommit (undo commit), fold (merge changesets), prune (delete changesets from history). The operation of these commands is ensured by assigning an obsolescence marker to each change set. Thanks to this marker, the actual deletion of the sets does not occur, the sets are only marked as obsolete. This means that history editing operations can even work with sets in the public phase. The extension is experimental and not included in the standard Mercurial distribution.
- Hg graft command . Generally speaking, does not change history, but does something similar. hg graft copies changes from one branch to another, and changes in the old branch are not deleted. After the command, several duplicate sets appear.
Sources