Mercurial - Automatically collapsing commits based on message? - mercurial

If I submit two or more commits into Mercurial with the same message, is there a way to collapse these into one change-set automatically? I assume this would have to be done in a changeset hook but I have not been able to find an automated method for this. Most examples require knowing the changeset revision numbers to collapse it manually and I'd want a way to automatically compare the previous change with the one being submitted and "merge" the two into one changeset.
In other words:
hg commit -m "some change"
hg commit -m "some change"
| * 20 <- "some change"
| * 19 <- "some change"
| * ..
Could this automatically become one changeset and fold it with the last one with the same commit message?
| * 19 <- "some change"
| * ..
This would only collapse changes if the commit message is exactly the same and sequential.
Because I know someone will try to point to these again... I'm not looking for these extensions, but an automatic method that does not require knowing the IDs or revisions.
ConcatenatingChangesets
CollapseExtension
HisteditExtension
Would this be possible if I 'tag'ged the previous commit? Could you combine your next commit into a tagged commit?

Git calls this "amend" and it is a feature of the regular 'git commit' tool. It doesn't do it based on commit message, you have to pass a '--amend' option to commit and it will combine the previous commit with the one you're creating. I haven't tried this with mercurial, but the GitConcepts page on the mercurial wiki says you could run hg rollback; hg commit -m "message" to have the same affect.
I should warn you that rollback will rollback the most recent commit in the repository (tip), so if you have pulled changes or merged things since your last regular commit, rollback will most likely not rollback the changeset you want it to. Additionally if you have pushed changes (or others have pulled from you) you will be breaking compatibility with those repositories because you're essentially editing history.
In theory you could write a hook which will see if the tip of the repository is on the same branch that you're on and if its commit message matches the one you're entering, and if so do a rollback and a commit in one go.
I'm curious to know what your goal is as that will help in recommending a solution for that goal. There may be better and more elegant ways to produce the result you desire than combining changesets. I know that you're not interested in extensions for some reason, but if you're trying to achieve a clean and logical changeset history, you should look in to the
MqExtension.

Related

How to view diff of several changsets?

Someone worked on a feature for a few days during which he made several commits. Other people have committed changes too while he was working so his commits are not consecutive.
How can I code review his commits as a whole, without viewing intermediary changes? And I don't want to view changes made by other people.
I can use TortoiseHG's search feature and type the issue number in the search box to locate his 5 changesets. Ideally I'd multi-select the changesets and then right-click and 'show diff' but that does not work.
I don't mind if the solution implies cloning the repository again and rewriting history in order to collapse the changesets (I'd discard that repo after).
Note: we don't want to change our workflow to ensure that features are pushed as a single commit.
Attempt 1:
update to a rev before his work
multi-select his changesets then 'Copy selected as patch'
apply the patch to my working directory
result: that created rejects so that solution is not ideal
Attempt 2:
use 'import to MQ' on the changesets with the intent of qfolding the them
result: 'import to MQ' fails: abort: revision 19630 is the root of more than one branch.
we don't want to change our workflow to ensure that features are pushed as a single commit.
Okay, you can use any workflow, I will just note: "Branch per Task"
will not require "feature in single commit"
eliminates a lot of (future) headache
In current state without cloning|rewriting history you can
forget about GUI
Get (some way) list of all changesets, related to be-reviewed feature
In console use hg diff -c CSET -c CSET2 ... -c CSETN <other options> (where every CSETX is changeset, related to feature

How to fix and commit one change in Mercurial before commiting other one?

I have a project, which is using Mercurial, I work alone on it and often I find myself in the same situation over and over: during working on something, I realize, that other changes should be fixed and committed before I finish my current work. So, I tried to create an "Anonymous branch"
hg update --check PREVIOUS_REVISION
but unfortunately, it doesn't work with the uncommitted changes (and I really don't want to commit unfinished work). So, every time, I copy directory with sources, revert to the previous revision, fix, commit, switch back to my working copy, pull the change and continue my work... But it takes too much time, so maybe there are better ways to do it and just with one copy of sources? Thank you.
P.S. Probably, it's the same question as How do I put a bunch of uncommitted changes aside while working on something else but my idea is "Is it possible just to leave unfinished work in the default branch in 'as is' state and then work on it later, without loading external patch?" (anyway, feel free to close it if it's a duplicate)
Three ways to solve task of "intermitted work"
Using shelve extension: Save all current changes hg shelve --all, make needed independent changes in clean WC, commit, restore intermediate results of work, saved on step 1 hg unshelve
Using anonymous branch: commit "as is" your WC, hg up <REV> to previous commit, make "must be before" changes, commit, return to older head, merge heads, continue work
Using MQ extension: use MQ Tutorial as starting point, chapter One: "Mq for the impatient" (between qrefresh and qfinish will be pure commit in your case)
I'll prefer (and always use for different tasks) MQ
Addition:
For fans and admirers of "clean history" one possible change in anonymous branching workflow (avoiding merge)
hg commit -m "Unfinished work" (rev M)
hg up -r "tip^1"
...
hg commit -m "Base changes" (rev N)
hg rebase -r M -d N (linearize history)
hg up
...
hg commit --amend -m "Full dependent change"
PS: I can't see anything bad in reversed set of changeset, i.e write a = something(data) in CSET, and function something (int subject) {...} in CSET+1
Having commits that aren't finished isn't a bad thing. It helps you survive things like your hard drive crashing. Why not commit your in-progress code with a commit message like "this is in progress", do whatever else you need to, then continue working on it?
Old commits are, by definition, unfinished. That's why you committed more things after them. Don't worry about the state of your old repository.
Of course, you should make cure you're working in feature branches, so you can push your code to a branch that isn't default.
If the 'must' changes are different hunks of codes, why not hg record, and select the 'must' hunks and commit before the others.

What to do instead of squashing commits in Mercurial

I've got my IDE set to commit locally every time I save anything. I'd ideally like to keep an uncensored record of my idiot fumblings for the rare occasions they may be useful. But most of the time it makes my history way to detailed.
I'd like to know a good strategy to keep that history but be able to ignore it most of the time. My IDE is running my own script every time I save, so I have control over that.
I'm pretty new to Mercurial, so a basic answer might be all I need here. But what are all the steps I should do when committing, merging, and reporting to be able to mostly ignore these automatic commits, but without actually squashing them? Or am I better off giving up and just squashing?
Related question about how to squash with highly rated comment suggesting it might be better to keep that history
Edit - My point here is that if Mercurial wants to keep all your history (which I agree with), it should let you filter that history to avoid seeing the stuff you might be tempted to squash. I would prefer not to squash, I'm just asking for help in a strategy to (in regular usage, though not quite always) make it look as much as possible like I did squash my history.
You want to keep a detailed history in your repo, but you want to have (and be able to export) an idealized history that only contains "reasonable" revsets, right? I can sympathize.
Solution 1: Use tags to mark interesting points in the history, and learn to ignore all the messy bits between them.
Solution 2: Use two branches and merge. Do your development in branch default, and keep a parallel branch release. (You could call it clean, but in effect you are managing releases). Whenever default is in a stable state that you want to checkpoint, switch to branch release and merge into it the current state of default-- in batches, if you wish. If you never commit anything directly to release, there will never be a merge conflict.
(original branch) --o--o--o--o--o--o--o (default)
\ \ \
r ... ... --r--------r (release)
Result: You can update to any revision of release and expect a functioning state. You can run hg log -r release and you will only see the chosen checkpoints. You can examine the full log to see how everything happened. Drawbacks: Because the release branch depends on default, you can't push it to another repo without bringing default with it. Also hg glog -r release will look weird because of the repeated merges.
Solution 3: Use named branches as above, but use the rebase extension instead of merging. It has an option to copy, rather than move outright, the rebased changesets; and it has an option --collapse that will convert a set of revisions into a single one. Whenever you have a set of revisions r1:tip you want to finalize, copy them from default to release as follows:
hg rebase --source r1 --dest release --keep --collapse
This pushes ONE revision at the head of release that is equivalent to the entire changeset from r1 to the head of default. The --keep option makes it a copy, not a destructive rewrite. The advantage is that the release branch looks just as you wanted: nice and clean, and you can push it without dragging the default branch with it. The disadvantage is that you cannot relate its stages to the revisions in default, so I'd recommend method 2 unless you really have to hide the intermediate revisions. (Also: it's not as easy to squash your history in multiple batches, since rebase will move/copy all descendants of the "source" revision.)
All of these require you to do some extra work. This is inevitable, since mercurial has no way of knowing which revsets you'd like to squash.
it should let you filter that history to avoid seeing the stuff you might be tempted to squash
Mercurial has the tools for this. If you just don't want see (in hg log, I suppose) - filter these changesets with revsets:
hg log -r "not desc('autosave')"
Or if you use TortoiseHg, just go View -> Filter Toolbar, and type in "not desc('autosave')" in the toolbar. Voila, your autosave entries are hidden from the main list.
If you actually do want to keep all the tiny changes from every Ctrl-S in the repo history and only have log show the subset of the important ones, you could always tag the "important" changesets and then alias log to log -r tagged(). Or you could use the same principle with some other revset descriptor, such as including the text 'autosave' in the auto-committed messages and using log -r keyword(autosave), which would show you all non-autosaved commits.
To accomplish your goal, at least as I'd approach it, I'd use the mq extension and auto-commit the patch queue repository on every save. Then when you've finished your "idiot fumblings" you can hg qfinish the patch as a single changeset that can be pushed. You should (as always!) keep the changes centered around a single concept or step (e.g. "fixing the save button"), but this will capture all the little steps it took to get you there.
You'd need to
hg qinit --mq once to initialze the patch queue repo (fyi: stored at \.hg\patches\)
hg qnew fixing-the-save-btn creates a patch
then every time you save in your IDE
hg qrefresh to update the patch
hg commit --mq to make the small changeset in the patch queue repo
and when you are done
hg qfinish fixing-the-save-btn converts the patch into a changeset to be pushed
This keeps your fumblings local to your repo complete with what was changed every time you saved, but only pushes a changeset when it is complete. You could also qpop or qpush to change which item you were working on.
If you were to try the squash method, you'd lose the fumbling history when you squashed the changesets down. Either that or you'd be stuck trying to migrate work to/from the 'real' repository, which, I can tell you from experience, you don't want to do. :)
I would suggest you to use branches. When you start a new feature, you create a new branch. You can commit as many and often as you like within that branch. When you are done, you merge the feature branch into your trunk. In this way, you basically separate the history into two categories: one in fine-grain (history in feature branches), and the other in coarse-grain (history in the trunk). You can easily look at either one of them using the command: hg log --branch <branch-name>.

Mercurial: how to amend the last commit?

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).

Mercurial undo last commit

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.