📜 ⬆️ ⬇️

Mercurial: changing history

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:



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:


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 tortoisehg
The 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 tortoisehg



First 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 = 

same thing in tortoisehg



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 

same thing in tortoisehg


Operation result



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


  1. 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.
  2. 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:

    • commit changes
    • cancel changes
    • postpone changes with the shelve / unshelve command (you must first enable the shelve extension)

       hg shelve --name shelve_name_1 ... hg unshelve shelve_name_1 

    • postpone changes using a group of commands

       hg diff > somefile hg revert –a ... hg import –no-commit somefile 

    • postpone changes using TortoiseHg





Other history editing features


  1. 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.
  2. HistEdit extension . Allows you to edit the history in manual mode, making separate operations with separate sets of changes.
  3. RebaseIf extension . Does the same thing as Rebase, but seeks to preserve nontrivial merges. Not included in the standard Mercurial distribution.
  4. 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.
  5. 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


Source: https://habr.com/ru/post/274877/


All Articles