Mercurial: amend previous commit dates - mercurial

Is there a way of changing the commit dates of multiple draft changesets? In particular, changesets before the last one.
I can change the commit date of the last commit with hg ci --amend -d xxx, but can't do so for any earlier ones.
This is obviously non-trivial, since the changeset date is one of the inputs to the changeset hash (where is this authoritatively documented, by the way?), and so a change here would change the hashes of all of a changeset's descendants. Since these are draft changesets, however, that would be OK.
It doesn't seem possible to do this using histedit.
I can guess that rebase might be able to do this sort of thing, but the associated help-text doesn't give any pointers, which suggests that it's at least exotic.
(The problem I'm trying to deal with is that some code is being edited and committed on a machine which is, deliberately, not network connected and which is frequently rebooted; that means that its notion of the system time can be wrong, and sometimes very wrong – as in 1970! – unless someone remembers to set the system date by hand to a reasonable value. While this doesn't matter to the topology of the commit graph, it would be at least nice, for everyone's sake, if the dates bore some relation to reality. Hence my wish to fix this in a ‘review before push’ step.)

Well, there's one approach which you already lined-out yourself basically, but it requires activation of the evolve extension: Set the date for one commit, and rebase all subsequent ones, and repeat until you have set the date for each. Assuming linear history:
hg update -rOLDEST_UNCHANGED
hg commit --amend --date DATA
hg rebase -b(OLDEST_UNCHANGED+1) -dtip
rinse and repeat with increasing the changesetID by one in each step. If your history is not linear, you will have to pay attention to the revisions you rebase and which you update to.
The second alternative, but not much better, is is making use of the evolve command from the evolve extension. That helps you with this process, making the steps outlined above slightly easier:
Still you have to start with the first of those commits you want to refresh:
hg update --rev OLDEST_UNCHANGED
hg commit --amend --date DATE
hg evolve --all
And repeat this process again with increasing changesetID until each commit has the date you want.
(If there is some way to set/refresh the commit date using evolve for all commits it evolves, I'm happy to learn - it definitely is a feature request otherwise)

For those using TortoiseHG (which doesn't know the evolve extension, and even if it did, there's a simpler way),
Use the mq extension.
Make sure your working directory is clean, (protip: use the shelve extension)
Import all changesets into MQ that you want to change,
Double-click on any changeset that you want to change, so it becomes the top applied one,
Use the Options button to the left of the QRefresh button to set a new date/time,
Hit QRefresh,
Repeat from 3.
Don't forget to uncheck the date change in the options dialog, lest bad things happen.

Related

Is it possible to undo an accidental "hg unshelve"?

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.

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

What is the standard commit process for Hg?

Is it
pull
update
merge
commit
push
? Or can you do the commit first?
I don't like the idea of pulling and merging without having a version of my local code backed up somewhere in case the merge explodes, but presumably you have to do the merge before you can do a push, because you can't have conflicts in the central repo. Not quite understanding this whole process yet; used to my nice simple SVN.
I recommend to always commit before pulling in changes to your working directory, unless you are 100% sure that your changes and the changes to be merged into your working directory will not conflict.
If you do an updating pull (hg pull; hg update, or shorter hg -u pull) and have any outstanding non-committed changes, any changes coming from outside will be combined with your changes. When conflicts happen, it might be difficult to decide how the merge result should look like, because you can't easily distinguish between your changes and the changes merged in.
When you did commit first, it is much easier to decide how the merge result should look like, because you can always look at both parents of the merge.
So, in effect it is:
hg commit
hg pull -u (if no merge necessary, go to 5)
hg merge
hg commit
hg push
Update: As Martin Geisler has pointed out, it is possible to get at the "original" changed version of a file using:
hg resolve --unmark the-file
hg resolve --tool internal:local the-file
or for all files at the same time:
hg resolve --unmark --all
hg resolve --tool internal:local -all
Still, I find the "commit first" system nicer. At the end, it is personal preference...
I don't know as there's a standard per se, but one of the ideas behind Mercurial is that you can commit as often as you like since it goes to your local repository. So you can commit to your heart's content as much as you like before you pull updates.
I tend not to commit very often, saving up for when I'm preparing to push, but that's me. I can see the utility of committing early and often. I do pull updates frequently as I work to cut down on merge fun.
One other thing I do is to keep a parallel clone of my working repo (cloned from the same repository as my working repo, not cloned from my working repo) so that I can check the original state of a file easily, and if need-be check in an out-of-band emergency fix or what-have-you without complicating my current change set.
Do edits
Commit
Goto 1 until satisfied
Pull
Merge & commit
Push if you want to.
Definitely commit before trying to do something complex like a merge. I don't think mercurial will allow you to merge before committing, but even if it did, what if the merge goes wrong. You have no pre-merge revision to go back to.
Commit early, commit often.
If you don't, you are missing out on a huge benefit of a DVCS.
but presumably you have to do the merge before you can do a push, because you can't have conflicts in the central repo
Wrong statement and poor understanding of distributed workflow and parallel development.
You can merge heads before push, but not have or must. Push can put any data to repo, if it needed and intended to be so
By default, push will not allow creation of new heads at the destination,
since multiple heads would make it unclear which head to use. In this
situation, it is recommended to pull and merge before pushing.
(NB: "recommended to pull and merge before" statement)
You can use commit-pull-merge, stash-pull-unstash-merge, perform fetch with modified WC and merge on the fly, don't merge heads at all or sporadically and push --force with +1 heads - there are not common rule for everybody. And any and every such workflow doesn't produce "conflicts in the central repo", but only different DAG.
Each point of divergence, which appear in case of existing your and other changeset from commmon parent in your (or even central) repo is a point of starting anonymous branches in Hg, which (technically) are absolutely legal, applicable and usual way. How they handled is defined by policy and agreement between developers, PM, QA-team and others
I, personally, prefer finish my task (in one or more amount of commits), after it pull and maybe merge, when it approved by development-policy

Condensing Mercurial revision history

We have 2,700+ revisions and it takes a good 30-45 seconds to load Mercurial when doing a merge, push or anything else with TortoiseHg. I'm wondering if there's a way other than straight up creating a new repository to clean up the revision history. Say, cut off files under revision 2,400 or so.
Not an answer to your question, but:
Maybe reducing "log batch size" to 100 (default is 500) in the settings helps.
Our 2300+ rev repo loads in 2-3 secs (off my 15k rpm SAS-disk, but never mind that), so I don't think your problem is many revs, really. There are much bigger repos out there. :)
Note that both Mercurial core and TortoiseHg developers are keen on finding performance bugs, so it might be worthwhile to ask on the mail-lists for assistance.
You can use the histedit extension to compress several changesets into one. Executing the histedit command on a range of revisions will spawn a text document that looks like this (from the histedit documentation):
pick c561b4e977df Add beta
pick 030b686bedc4 Add gamma
pick 7c2fd3b9020c Add delta
Edit history between c561b4e977df and 7c2fd3b9020c
Commands:
p, pick = use commit
e, edit = use commit, but stop for amending
f, fold = use commit, but fold into previous commit
d, drop = remove commit from history
Changing pick to fold for a certain changeset in the list above will fold it into the previous changeset. It will give you an opportunity to resolve failed merges and enter a new commit message as well.
WARNING:
Using histedit will modify the repository history, including hash IDs, which will cause problems unless you re-start each developer with a new repository clone after the changes have been made. Also, you would probably need to limit your histedit-ing to changesets with a single parent (ie: non-merge changesets).

Branch from the past in mercurial

I'm a single developer using Mercurial to create a program. I have so far committed at every step of developing, and I have messed things up and want to work on something else.
How shall I go to a previous state (ie. rollback) and then create a branch, maintaining the newer states?
Does rollback ever delete anything? Should that be all I do? Just rollback untill I'm at the place where I want, edit, then commit? Will I be able to merge the changesets later, or at least look at them? Of course I can just save this repository, rollback, and then create new changesets, then merge the two repositories?
I'm new to SCM and of course DSCM, so be gentle :)
You don't want to use hg rollback, that only erases exactly one change (the most recent). See You Can Only Roll Back Once in the hg book.
To make a branch from an earlier revision, you need to find the revision number you want to branch from and run:
hg clone -r <revnum> <source repository> <destination repository>
You can then do your changes in the newly-cloned repository (which will not contain any of the history that came after the revision number you specified) and then merge those changes back into the original later, if you wish.
The other answer will work but it is excessive. All you need to do is:
hg up <revnum>
and your local working folder will be updated to the state of files at the specified revision. You can just work from there.
As soon as you commit it will create an anonymous branch automatically. Your original commits remain preserved but out of your way.
This is the simplest possible approach (IMHO) which I would recommend starting with.