I'm looking for a counter-part of git commit --amend in Mercurial, i.e. a way to modify the commit which my working copy is linked to. I'm only interested in the last commit, not an arbitrary earlier commit.
The requirements for this amend-procedure are:
if possible, it should not require any extensions. It must not require non-default extensions, i.e. extensions which do not come with an official Mercurial installation.
if the commit to amend is one head of my current branch, no new head should be created. If the commit is not head, a new head may be created.
the procedure should be safe in a way that if for whatever reasons the amending fails, I want to have the same working copy and repository state restored as before the amending. With other words, if the amending itself can fail, there should be a fail-safe procedure to restore the working copy and repository state. I'm referring to "failures" which lie in the nature of the amend-procedure (like e.g. conflicts), not to file-system-related problems (like access restrictions, not being able to lock a file for writing, ...)
Update (1):
the procedure must be automatable, so it can be performed by a GUI client without any user interaction required.
Update (2):
files in the working directory must not be touched (there may be file system locks on certain modified files). This especially means, that a possible approach may at no point require a clean working directory.
With the release of Mercurial 2.2, you can use the --amend option with hg commit to update the last commit with the current working directory
From the command line reference:
The --amend flag can be used to amend the parent of the working directory with a new commit that contains the changes in the parent in addition to those currently reported by hg status, if there are any. The old commit is stored in a backup bundle in .hg/strip-backup (see hg help bundle and hg help unbundle on how to restore it).
Message, user and date are taken from the amended commit unless specified. When a message isn't specified on the command line, the editor will open with the message of the amended commit.
The great thing is that this mechanism is "safe", because it relies on the relatively new "Phases" feature to prevent updates that would change history that's already been made available outside of the local repository.
You have 3 options to edit commits in Mercurial:
hg strip --keep --rev -1 undo the last (1) commit(s), so you can do it again (see this answer for more information).
Using the MQ extension, which is shipped with Mercurial
Even if it isn't shipped with Mercurial, the Histedit extension is worth mentioning
You can also have a look on the Editing History page of the Mercurial wiki.
In short, editing history is really hard and discouraged. And if you've already pushed your changes, there's barely nothing you can do, except if you have total control of all the other clones.
I'm not really familiar with the git commit --amend command, but AFAIK, Histedit is what seems to be the closest approach, but sadly it isn't shipped with Mercurial. MQ is really complicated to use, but you can do nearly anything with it.
GUI equivalent for hg commit --amend:
This also works from TortoiseHG's GUI (I'm using v2.5):
Swich to the 'Commit' view or, in the workbench view, select the 'working directory' entry.
The 'Commit' button has an option named 'Amend current revision' (click the button's drop-down arrow to find it).
||
||
\/
Caveat emptor:
This extra option will only be enabled if the mercurial version is at least
2.2.0, and if the current revision is not public, is not a patch and has no
children. [...]
Clicking the button will call
'commit --amend' to 'amend' the revision.
More info about this on the THG dev channel
I'm tuning into what krtek has written. More specifically solution 1:
Assumptions:
you've committed one (!) changeset but have not pushed it yet
you want to modify this changeset (e.g. add, remove or change files and/or the commit message)
Solution:
use hg rollback to undo the last commit
commit again with the new changes in place
The rollback really undoes the last operation. Its way of working is quite simple: normal operations in HG will only append to files; this includes a commit. Mercurial keeps track of the file lengths of the last transaction and can therefore completely undo one step by truncating the files back to their old lengths.
Assuming that you have not yet propagated your changes, here is what you can do.
Add to your .hgrc:
[extensions]
mq =
In your repository:
hg qimport -r0:tip
hg qpop -a
Of course you need not start with revision zero or pop all patches, for the last just one pop (hg qpop) suffices (see below).
remove the last entry in the .hg/patches/series file, or the patches you do not like. Reordering is possible too.
hg qpush -a; hg qfinish -a
remove the .diff files (unapplied patches) still in .hg/patches (should be one in your case).
If you don't want to take back all of your patch, you can edit it by using hg qimport -r0:tip (or similar), then edit stuff and use hg qrefresh to merge the changes into the topmost patch on your stack. Read hg help qrefresh.
By editing .hg/patches/series, you can even remove several patches, or reorder some. If your last revision is 99, you may just use hg qimport -r98:tip; hg qpop; [edit series file]; hg qpush -a; hg qfinish -a.
Of course, this procedure is highly discouraged and risky. Make a backup of everything before you do this!
As a sidenote, I've done it zillions of times on private-only repositories.
Recent versions of Mercurial include the evolve extension which provides the hg amend command. This allows amending a commit without losing the pre-amend history in your version control.
hg amend [OPTION]... [FILE]...
aliases: refresh
combine a changeset with updates and replace it with a new one
Commits a new changeset incorporating both the changes to the given files
and all the changes from the current parent changeset into the repository.
See 'hg commit' for details about committing changes.
If you don't specify -m, the parent's message will be reused.
Behind the scenes, Mercurial first commits the update as a regular child
of the current parent. Then it creates a new commit on the parent's
parents with the updated contents. Then it changes the working copy parent
to this new combined changeset. Finally, the old changeset and its update
are hidden from 'hg log' (unless you use --hidden with log).
See https://www.mercurial-scm.org/doc/evolution/user-guide.html#example-3-amend-a-changeset-with-evolve for a complete description of the evolve extension.
Might not solve all the problems in the original question, but since this seems to be the de facto post on how mercurial can amend to previous commit, I'll add my 2 cents worth of information.
If you are like me, and only wish to modify the previous commit message (fix a typo etc) without adding any files, this will work
hg commit -X 'glob:**' --amend
Without any include or exclude patterns hg commit will by default include all files in working directory. Applying pattern -X 'glob:**' will exclude all possible files, allowing only to modify the commit message.
Functionally it is same as git commit --amend when there are no files in index/stage.
Another solution could be use the uncommit command to exclude specific file from current commit.
hg uncommit [file/directory]
This is very helpful when you want to keep current commit and deselect some files from commit (especially helpful for files/directories have been deleted).
Related
I have seen Remove file from a commit in Mercurial - but I have very little experience with Mercurial, so I'd like to ask and make sure if this is doable, and how.
There is a software that is developed in Mercurial, and I wanted to try and add a feature. So when I cloned the repo, first I did was added my own branch, and started hacking there. Already in first or second commit of this branch, I added multiple files hg add test1.bmp, hg add test2.bmp and so on, and committed.
Then I kept on hacking other files (haven't touched these test1.bmp, test2.bmp etc since), and made about 3 or 4 commits after the commit where the bmps are added. Nothing has been pushed anywhere - my local copy of the Mercurial repo and my branch in it is the only place where these files are referenced. And just now I realize I shouldn't have added those files at all.
Is it possible to remove these files from all of the commits where they feature, since the first commit where they were added?
Or maybe I should formulate the question in singular - is it possible to remove a file from all of the commits where it features, since the first commit where it was added? (if it is possible for one file, then I can just repeat the process for any additional file I'd like to remove)
Yes this is possible.
However, this is a more advanced use of mercurial and will require you enabling at lease one extension.
As a new user, my recommendations would be to simply use mercurial basic management and remove the file(s) of concern:
hg remove <file>
You can then commit the change.
While this will not remove the file(s) from all changesets, it will remove it
going forward. This honestly is the recommended way of managing working content.
It should be noted that this is likely your only option if you have pushed changes to the parent repository. If you have pushed your changes to the parent repository, you will also have to edit it which makes things much more complicated and the potential for a serious mistake more likely.
If you truly want to remove it from previous changesets, you will need enable the histedit extension (included with mercurial).
[extensions]
histedit =
Since you are a relatively new user to mercurial, I strongly recommend that you backup your repository and experiment there before attempting this on you working copy.
The process I recommend for this is as follows:
Note: this only works as described for changesets that have not been pushed and have phase = draft (or secret)
1) Identify all changes where file(s) are added or modified
You will need to know each changeset where the file(s) where modified or added. You will need to modify each of these changesets in the reverse order that they where added.
You can use:
hg log file
to list changesets where the file was modified or added.
2) Slowly edit each affected changeset (working your way backwards)
Use the histedit to display all show all changesets that can be modified/edited in your default editor.
hg histedit
Find the first changeset you identified and change it from pick to edit and save the change.
This will drop you into histedit edit mode. This is the state just before the changeset was committed. This means you can make changes such as modifying, unedifying. Adding or removing content. In this case we want to undo any changes to the file(s) in question.
hg revert file
or
hg remove file (if this is the changeset where the file as added)
3) recommit changeset
Once you have reversed the file changes/additions, you need to inform histedit
that you are done.
hg histedit --continue
This will cause the changeset to be recommitted with the modifications you made including editing the commit message.
Repeat this for each changeset until completed.
Note: While you can select multiple changes to edit in histedit, I recommend doing one at a time to help reduce complexity. Remember to do the in the reverse order that they where committed to help reduce / eliminate any merge conflicts.
Again a recommend practicing on a repository copy until you have the process firmly understood. Since you are modifying repository history, this can have potential negative effects of a mistake is made.
I highly recommend reading this: https://book.mercurial-scm.org/read/changing-history.html.
Now my there's an extra few hundred lines of code in my project, scattered over half a dozen classes, and I'm not looking forward to tracking them down one by one.
No, but this is an avoidable problem
Instead of doing hg shelve, do hg commit --secret. This will create a "real" commit in the repository, which can be rebased or stripped (hg strip) when you no longer need it. It can also be folded into another changeset using hg histedit or hg fold (the latter requires the Evolve extension, which is experimental at the time of writing, while the former is interactive and may be hard to script).
The --secret flag ensures the changeset will not be accidentally pushed until you manually run hg phase -d REV on the changeset.
Yes, with a few steps. The help for unshelve specifies "If a shelved change is applied successfully, the bundle that contains the shelved changes is moved to a backup location (.hg/shelve-backup)." So hg update --clean will return you to the pre-unshelve state, unless you added changes in the mean-time, in which case you need to figure out how to recover the world prior to the unwanted patches. Then you can re-apply the patches stored in the backup individually if you wish to essentially re-do the unshelve under whatever conditions you prefer. In any event, the back-up contains the data you thought was missing.
If I have a bunch of uncommitted changes and want to set it aside while working on something else instead, and then later (f.i. after several days) come back to it and proceed working. What would be the easiest workflow to accomplish this? (So far I have only experience with Mercurial's basic functionality). My usual method was to create a new branch using clone, but there might be better ways.
You have a handful options:
Shelve the items. This saves the changes and removes them from the working directory so the branch can continue. It doesn't create a change-set.
hg shelve --all --name "UnfinishedChanges"
hg unshelve --name "UnfinishedChanges"
Update/Edit: Newer versions of mercurial may need to use
hg shelve -n "UnfinishedChanges"
hg unshelve "UnfinishedChanges"
You can still use --name as an alternative to -n, but mercurial doesn't seem to like --name anymore. Additionally, the --all is no longer required and mercurial will in fact freak out over it.
Patch queue the items using mq. This isn't too dissimilar to shelve in some respects, but behaves differently. The end result is the same, changes are removed and can be optionally re-applied later. When pushed, the patches are logical change-sets, when popped they are saved elsewhere and are not part of change-set history.
hg qnew "UnfinishedWork"
hg qrefresh
hg qpop
hg qpush "UnfinishedWork"
Commit them locally, update to the previous change-set and continue working and make use of anonymous branches (or multiple heads). If you then want the changes, you can merge heads. If you don't want the changes, you can strip the change-set.
hg commit -m"Commiting unfinished work in-line."
hg update -r<previous revision>
hg strip -r<revision of temporary commit>
Commit them to a named branch. The workflow then becomes the same as option 3 - merge or strip when you are ready.
hg branch "NewBranch"
hg commit -m"Commiting unfinished work to temporary named branch."
hg update <previous branch name>
Personally I use option 3 or 4 as I don't mind stripping change-sets or checking-in partial code (so long as that doesn't eventually get pushed). This can be used in conjunction with the new Phase stuff to hide your local change-sets from other users if need-be.
I also use the rebase command to move change-sets around to avoid merges where a merge wouldn't add anything to the history of the code. Merges I tend to save for activity between important branches (such as release branches), or activity from a longer-lived feature branch. There is also the histedit command I use for compressing change-sets where the "chattiness" of them reduces the value.
Patch queues are also a common mechanism for doing this, but they have stack semantics. You push and pop patches, but a patch that is "underneath" another patch in the stack requires that the one on top of it be pushed also.
Warning, as with all these options, if the files have more changes since the temporary changes that you've shelved / queued / branched, there will be merge resolution required when un-shelving / pushing / merging.
Personally, I don't like any of the answers posted so far:
I don't like clone branching because I like each project to have only one directory. Working on different directories at the same time completly messes the history of recent files of my editors. I always end up changing the wrong file. So I don't do that anymore.
I use shelve for quick fixes (just to move my uncommited changes to another branch, if I realize I'm at the wrong one). You are talking about days, no way I'd shelve something for days.
I think mq is too complicated for such an ordinary sittuation
I think the best way is to simply commit your changes, than you go back to the changeset before you start these changes and work from there. There are some minor issues, let me illustrate:
Let's say you have the changeset A. Than you start your changes. At this point you want set it aside for a while. First of all, commit your work:
hg ci -m "Working on new stuff"
If you want, you can add a bookmark to make it easier to come back later. I always create bookmarks to my anonymous branches.
hg bookmark new-stuff
Go back to the changeset before these modifications
hg update A
From here, you work and generate the changeset C. Now you have 2 heads (B and C), you'll be warned when you try to push. You can push only one branch by specifying the head of that branch:
hg push -r C
Or you can change the phase of the new-stuff branch to secret. Secret changesets won't be pushed.
hg phase -r new-stuff --secret --force
To keep local uncommited changes, easiest way for me is just to save them as a patch file.
hg diff > /tmp/`hg id -i`.patch
and when you need to return to previous state:
hg up <REV_WHERE_SAVED>
hg patch --no-commit /tmp/<REV_WHERE_SAVED>.patch
You can just clone your repo multiple times. I tend to have a root clone, then multiple childs from there. Example:
MyProject.Root
MyProject.BugFix1
MyProject.BugFix2
MyProject.FeatureChange1
MyProject.FeatureChange2
The 4 childs are all cloned from the root and push/pull to/from the root. The root then push/pulls from the master repo on the network/internet somewhere. The root acts as your sort of personal staging area.
So in your case, you'd just clone up a new repo and start working. Leave your 'shelved' work alone in the other repo. It's that simple.
The only downside is disk space usage, but if that were a concern you'd not be using DVCS at all anyway ;) Oh and it does kind of pollute your Visual Studio "recent projects" list, but what the hey.
[Edit following comments] :-
To conclude then... what you're doing is completely fine and normal. I would argue it is the best possible way to work when the following are true: 1) it is short-lived 2) you don't need to collaborate with other developers 3) the changes don't need to leave your PC until commit/push time.
How can I undo my last accidentally commited (not pushed) change in Mercurial?
If possible, a way to do so with TortoiseHg would be prefered.
Update
In my concrete case I commited a changeset (not pushed). Then I pulled and updated from the server. With these new updates I decided, that my last commit is obsolete and I don't want to sync it. So it seems, that hg rollback is not exactly what I'm searching for, because it would rollback the pull instead of my commit.
One way would be hg rollback (deprecated as of Hg2.7, August 2013)
Please use hg commit --amend instead of rollback to correct mistakes in the last commit.
Roll back the last transaction in a repository.
When committing or merging, Mercurial adds the changeset entry last.
Mercurial keeps a transaction log of the name of each file touched and its length prior to the transaction. On abort, it truncates each file to its prior length. This simplicity is one benefit of making revlogs append-only. The transaction journal also allows an undo operation.
See TortoiseHg Recovery section:
This thread also details the difference between hg rollback and hg strip:
(written by Martin Geisler who also contributes on SO)
'hg rollback' will remove the last transaction. Transactions are a concept often found in databases. In Mercurial we start a transaction when certain operations are run, such as commit, push, pull...
When the operation finishes succesfully, the transaction is marked as complete. If an error occurs, the transaction is "rolled back" and the repository is left in the same state as before.
You can manually trigger a rollback with 'hg rollback'. This will undo the last transactional command. If a pull command brought 10 new changesets into the repository on different branches, then 'hg rollback' will remove them all.
Please note: there is no backup when you rollback a transaction!
'hg strip' will remove a changeset and all its descendants. The
changesets are saved as a bundle, which you can apply again if you
need them back.
ForeverWintr suggests in the comments (in 2016, 5 years later)
You can 'un-commit' files by first hg forgetting them, e.g.: hg forget filea; hg commit --amend, but that seems unintuitive.
hg strip --keep is probably a better solution for modern hg.
hg strip will completely remove a revision (and any descendants) from the repository.
To use strip you'll need to install MqExtension by adding the following lines to your .hgrc (or mercurial.ini):
[extensions]
mq =
In TortoiseHg the strip command is available in the workbench. Right click on a revision and choose 'Modify history' -> 'Strip'.
Since strip changes the the repository's history you should only use it on revisions which haven't been shared with anyone yet. If you are using mercurial 2.1+ you can uses phases to track this information. If a commit is still in the draft phase it hasn't been shared with other repositories so you can safely strip it. (Thanks to Zasurus for pointing this out).
Since you can't rollback you should merge that commit into the new head you got when you pulled. If you don't want any of the work you did in it you can easily do that using this tip.
So if you've pulled and updated to their head you can do this:
hg --config ui.merge=internal:local merge
keeps all the changes in the currently checked out revision, and none of the changes in the not-checked-out revision (the one you wrote that you no longer want).
This is a great way to do it because it keeps your history accurate and complete. If 2 years from now someone finds a bug in what you pulled down you can look in your (unused but saved) implementation of the same thing and go, "oh, I did it right". :)
hg rollback is what you want.
In TortoiseHg, the hg rollback is accomplished in the commit dialog. Open the commit dialog and select "Undo".
In the current version of TortoiseHg Workbench 4.4.1 (07.2018) you can use Repository - Rollback/undo...:
Its workaround.
If you not push to server, you will clone into new folder else washout(delete all files) from your repository folder and clone new.
I believe the more modern and simpler way to do this now is hg uncommit. Note this leaves behind an empty commit which can be useful if you want to reuse the commit message later. If you don't, use hg uncommit --no-keep to not leave the empty commit.
hg uncommit [OPTION]... [FILE]...
uncommit part or all of a local changeset
This command undoes the effect of a local commit, returning the affected
files to their uncommitted state. This means that files modified or
deleted in the changeset will be left unchanged, and so will remain
modified in the working directory.
If no files are specified, the commit will be left empty, unless --no-keep
Sorry, I am not sure what the equivalent is TortoiseHg.
after you have pulled and updated your workspace do a thg and right click on the change set you want to get rid of and then click modify history -> strip, it will remove the change set and you will point to default tip.
I ran into this issue recently, although my situation was slightly different - I had already pushed the commit I wanted to undo. I solved my problem with hg backout <revision-code>. It seems to work in a similar way to git revert. From this answer:
backout: create a new commit that is the inverse of a given commit.
Net effect is an undo, but the change remains in your history.
In TortiseHg this can be done by right clicking the commit you wish to undo and selecting Backout.... TortiseHg doc here.
I think there's an argument for creating a new undo commit instead of removing the previous commit outright. From this article about git revert:
Instead of removing the commit from the project history, it figures
out how to invert the changes introduced by the commit and appends a
new commit with the resulting inverse content. This prevents Git from
losing history, which is important for the integrity of your revision
history and for reliable collaboration.
If I do 'hg status' and see the following:
R flash/AC_OETags.js
it seems to mean that there is no file there, but there has been one at some point that has been removed.
How do I 'commit' this change so it stops showing up when I do 'hg status'?
==UPDATE==
The answer seems to be to commit the file. Actually, there are ~100 files with status R because I removed an entire directory tree. Anyone know how to commit all files in a directory tree in one go?
I don't want to do just hg commit, because there are other changes too.
The “R” means “Removed” so the next time we commit in Mercurial this file will be removed. (The history of the file will remain in the repository, so of course we can always get it back).
therefore run your hg commit command and all will be well
Thanks to hginit.com for that titbit - its my Mercurial bible
You can commit just that file:
hg commit flash/AC_OETags.js
however having "masses of other uncommitted files" is terrible process. You need to come up with a workflow that lets you commit frequently.
You can use the repository explorer from TortoiseHg to easily manage the files you want to include in a commit.
Also, removing a directory probably warrants a changeset in itself. You should get into the habit of committing more often (one concept, one commit... and it's local anyway). Furthermore, as long as you haven't pushed your changes to anyone (or anyone pulled from you) you could still use hg rebase --collapse to regroup some changesets if you think you have separated too much (this is a more advanced feature that I suggest you try on a test repository first as you could break things if you're not careful)