Mercurial: what are the consequences of changing a phase? - mercurial

In Mercurial, the phase of a revision can be changed arbitrarily. What are the consequences of a phase change for all possible transitions (public, draft, secret) x (public, draft, secret)? Which phase changes are safe? Which may cause troubles and which kind of troubles? Which are more or less no-ops?

The default phase for a commit is draft, that's how they were treated when the phases didn't exist. When pushing a draft changeset, mercurial automatically changes its phase to public. You can use it to know which changesets you have already published or not.
But the real issue is that when the changeset is in public phase, mercurial won't let you change them with history editing extensions (like mq, rebase, etc). That's really important, because the history editing happens only in the local repository, they don't propagate with pull/push operations. So, once a changeset is published, it's out of control, it's dangerous to change it.
You can change from any phase to any other phase. The "normal flow" is to move to a higher phase (secret->draft->public), but Mercurial allows the change to a lower phase with the --force option. A phase change alone is harmless. For example, the only thing that happens when moving from public to draft or secret is that the protection of history editing is dropped, nothing else, pull and push will still work normally, Mercurial could never be confused about the changesets because they have unique identifiers. The history editing action that takes place after a phase change like this is what can cause problems. And that's why Mercurial gives the warning in the phase change and requires the --force option, as a confirmation that that's what you really want.
In general published commits shoudn't be modified, that's what the phases try to ensure. But maybe you do have control over all repositories. Or maybe you pushed something and you know that no one else pulled it yet. Whatever is the reason, you have the option of forcing the phase of the changesets back to draft and edit them. But this edition must be done in every repository that has the changesets.

A public revision can't be edited with history-editing tools (i.e. mq, rebase).
A secret revision can't be pushed to another repository (it will be ignored when you give the push command, or when another repository attempts to pull).
A draft revision allows both, but will automatically change to public if it gets pushed to another repository.
The purpose of the phase system is to prevent you from modifying a revision after you've pushed it to another repository, which is a bad idea unless you're able to delete the old version of the revision from all repositories that it's been pushed to.
All phase changes are more or less no-ops; it's just a marker to indicate what is or isn't safe to do with a revision.

Related

mercurial (hg) modify incoming changeset date in a hook on a push

Using Mercurial.
Does anyone know if there is a good way to modify a changeset's commit date via a hook, such as pretxnchangegroup, during an incoming push ?
Once a change comes in to a repo the changeset dates are always the commit dates. I'd like to changes/override these dates to the 'push' dates instead -- via a hook during a push.
It seems that ctx.date() is a tuple and immutable.
Thank you.
Commit date, author as well as the patch content itself make up the changesetID. Thus changing either will change the changesetID - and as such they cannot be modified on an existing changeset.
Mercurial, like most modern VCS, is a distributed version control system, thus the commit date is recorded as the time received to any arbitrary repository is arbitrary at best while the commit time and date are global as they are recorded once only and don't change on propagation through different repositories.
You should ask yourself why you need that. If you worked on a patch series which you then want to push to an authorative project repo, do the changes before you push by updating (commit --amend) the still-draft changesets on the author's side. You can even integrate a hook server-side to enforce this policy which checks the age of the commits received and deny push when they are older than XX minutes.
However if you really want to realise the bad idea you ask about:
If you operate on a non-publishing repository, and if you receive draft changesets, then you might get away with creating new commits with an updated timestamp from the changesets you just received, obsoleting the old ones. Using the evolve extension might make this work a lot easier.
If you receive public changesets, or if your repository is publishing, then doing these things with rebase and commit --amend would seem very troublesome and I would not enjoy contributing as I would end up with everything I wrote and pushed duplicate in my local repository.

Mercurial: Pull changes from remote repository without public phase (non-publishing server, "abort: can't rebase immutable changeset")

Background
Mercurial now has Phases which are a great mechanism to keep people from altering history that should not be altered. When a changeset is pushed to remote repository it is made public and can no longer be rebased. This is normally a good thing, if the repository is public and you don't want others altering your changesets.
However, if you have your own temporary repository for sharing draft changesets only with yourself, it can get very annoying. Mercurial has a option to turn this off. Adding this to the .hg/hgrc file of the remote repository.
[phases]
publish = False
This will prevent changesets pushed to the remote repository from moving from draft phase to public phase (both the local changeset and the one just pushed to the remote repository. However, pulling this changeset down still will always pulls it down in the public phase.
Question
I am hoping to pull down changesets in the draft phase. I simply want to push a changeset to my personal server then pull it down at home. After I pull it I will rebase my temporary commit on top of any commits that I have pulled down from our true publishing server.
Any way to avoid the auto-moving of a pulled changeset to public would be great. This remote repository is a complete and total draft server for myself. Being forced to manually move the changesets back to draft after an unsuccessful rebase attempt is really starting to wear on the nerves.
References
https://www.mercurial-scm.org/wiki/Phases
Introduction to Mercurial Phases
How do I tell (locally) mercurial that a server is non-publishing?
After pushing to a review repository, "abort: can't rebase immutable changeset" on rebase
That seems like a bug. What version of Mercurial are you using? Have you tried filing a bug at https://bz.mercurial-scm.org ?
I got this resolved through help on Bugzilla. Here is a snippet of my final post there (thanks for putting me on the right track djc).
It looks like we can chalk it up to User Error/Bad Test Cases (of
course). If there is anyone to blame (besides myself) it could be
TortoiseHg. My test cases consisted of stripping commits and from one
of the two repositories and then changing the phase of that commit in
the other repository and push/pull again. It seems like TortoiseHg
would pull in the wrong phase sometimes (possibly a caching issue). I
can still reproduce this when I switch my server back and forth
between publishing and non-publishing (but not quite as consistently
before).
However, doing a command line pull seems to get it working every time.
I was using command line to check outgoing/incoming, do the phase
changes and such, but possibly never used it for the actual pull.
Sorry to waste everybody's time. I resolved it as invalid, not sure if
someone wants to change it to a better category. Let me know if there
is anything else you want me to check or expand upon.
I'll edit in any updates, let me know if anyone is still having trouble with this.

Pull commits on repo post-rebase

I'm looking for a simple way to pull in additional commits after rebasing or a good reason to tell someone not to rebase.
Essentially we have a project, crons. I make changes to this frequently, and the maintainer of the project pulls in changes when I request it and rebases every time.
This is usually okay, but it can lead to problems in two scenarios:
Releasing from two branches simultaneously
Having to release an additional commit afterwards.
For example, I commit revision 1000. Maintainer pulls and rebases to create revision 1000', but at around the same time I realize a horrible mistake and create revision 1001 (child of 1000). Since 1000 doesn't exist in the target branch, this creates an unusable merge, and the maintainer usually laughs at me and tells me to try again (which requires me getting a fresh checkout of the main branch at 1000' and creating and importing a patch manually from the other checkout). I'm sure you can see how the same problem could occur with me trying to release from two separate branches simultaneously as well.
Anyway, once the main branch has 1000', is there anything that can be done to pull in 1001 without having to merge the same changes again? Or does rebasing ruin this? Regardless is there anything I can say to get Maintainer to stop rebasing? Is he using it incorrectly?
Tell your maintainer to stop being a jacka**.
Rebasing is something that should only be done by you, the one that created the changesets you want to rebase, and not done to changesets that are:
already shared with someone else
gotten from someone else
Your maintainer probably wants a non-distributed version control system, like Subversion, where changesets follows a straight line, instead of the branchy nature of a DVCS. In that respect, the choice of Mercurial is wrong, or the usage of Mercurial is wrong.
Also note that rebasing is one way of changing history, and since Mercurial discourages that (changing history), rebasing is only available as an extension, not available "out of the box" of a vanilla Mercurial configuration.
So to answer your question: No, since your maintainer insists on breaking the nature of a DVCS, the tools will fight against you (and him), and you're going to have a hard time getting the tools to cooperate with you.
Tell your maintainer to embrace how a DVCS really works. Now, he may still insist on not accepting new branches or heads in his repository, and insist on you pulling and merging before pushing back a single head to his repository, but that's OK.
Rebasing shared changesets, however, is not.
If you really want to use rebasing, the correct way to do it is like this:
You pull the latest changes from some source repository
You commit a lot of changesets locally, fixing bugs, adding new features, whatnot
You then try to push, gets told that this will create new heads in the target repository. This tells you that there are new changesets in the target repository that you did not get when you last pulled, because they have been added after that
Instead, you pull, this will add a new head in your local repository. Now you have the head that was created from your new changesets, and the head that was retrieved from the source repository created by others.
You then rebase your changesets on top of the ones you got from the source repository, in essence moving your changesets in the history to appear that you started your work from the latest changeset in the current source repository
You then attempt a new push, succeeding
The end result is that the target repository, and your own repository, will have a more linear changeset history, instead of a branch and then a merge.
However, since multiple branches is perfectly fine in a DVCS, you don't have to go through all of this. You can just merge, and continue working. This is how a DVCS is supposed to work. Rebasing is just an extra tool you can use if you really want to.

Help understanding the benefits of branching in Mercurial

I've struggled to understand how branching is beneficial. I can't push to a repo with 2 heads, or 2 branches... so why would I ever need/use them?
First of all, you can push even with two heads, but since you probably don't want to do that, the default behavior is to prevent you from doing it. You can, however, force the push to go through.
Now, as for branching, let's take a simple scenario in a non-distributed version control system, like Subversion.
Let's assume you have a colleague that is working in the same project as you. The current latest changeset in the Subversion repository is revision 100, you both update to this locally so that now both of you have the same files.
Ok, now your colleague has already been working on his changes for a couple of hours now, and so he commits. This brings the central repository up to revision 101. You're still on revision 100 locally, and you're still working on your changes.
At some point, you complete, and you want to commit, but Subversion won't let you. It says you have to update first, so you start the update process.
The update process wants to take your changes, and pretend you actually started with revision 101 instead of 100. If your changes are not in conflict with whatever it was your colleague committed, all is hunky dory, but if your changes are in conflict, you have a problem.
Now you have to merge your changes with his changes, and things can go haywire. For instance, you might end up merging one file OK, the second file OK, or so you think, and then the third file, and you suddenly discover that you've got some of the details wrong, it would've been better to merge the second file differently.
Unless you made a backup of your changes before updating, and sooner or later you will forget, you have a problem.
Now, the above scenario is actually quite common. Well, perhaps not the merging part, it depends on how many is working in the same area or files at the same time, but the "must update before committing" part is quite common with Subversion.
So how does Mercurial do it?
Well, Mercurial commits locally, it doesn't talk to any remote repository at all, so it won't stop you from committing.
So, let's try the above scenario again, just in Mercurial this time.
The tipmost changeset in the remote repository is revision 100. You both have cloned this down, and you're both starting to work on the changes, from revision 100.
Your colleague completes his changes and commits, locally. He then pushes his changeset up to the central repository, bringing the tip there up to revision 101.
You then complete your changes, and commit, also locally, and then you want to push, but you get the error message you've already discovered, and is asking about.
So how is this different?
Well, your changes are now committed, there is no way, unless you try really hard to accidentally lose them or destroy them.
Here's the 3 repositories in play and their current state:
Colleague ---98---99---100---A
Central ---98---99---100---A
You ---98---99---100---B
If you were to push, and was allowed to do this (or force the push through), the Central repository would look like this:
Central ---98---99---100---A
\
+--B
Two heads. If your colleague now pulled, which one should he continue working from? This question is the reason Mercurial will by default prevent you from causing this.
So instead you pull, and you get the above state in your own repository.
In other words, you can chose to impact your own repository and create multiple heads there, but you are not imposing that problem on anyone else.
You then merge, the same type of operation you had to do in Subversion, except your changeset is safe, it was committed, and you won't accidentally corrupt or destroy it. If, mid-merge, you want to start over, you can, nothing lost, no harm done.
After the merge, your local repository looks like this:
You ---98---99---100---A----M
\ /
+--B--+
This is now safe to push, and if your colleague now pulls, he knows that he has to continue from the M changeset, the one that merged his and your changes.
The above description is what happens due to Mercurials distributed nature.
You can also name branches, to make them more permanent. For instance, you might want to name a branch "stable", to signal that any changesets on that branch have been thoroughly tested and is safe for release to customers or to put into production. Then you would only merge changes onto that branch when said testing has been completed.
The nature, however, is the same as the above description. Whenever more than one person works on a project with Mercurial, you will get branches, and that's a good thing.
Whenever more than one clone of a repo is made and commits are made in those clones, branches happen, whether you name them by using the hg branch command or not. My philosophy is, you might as well give them a name. It makes things less confusing.
A good explanation of mercurial branches: http://stevelosh.com/blog/2009/08/a-guide-to-branching-in-mercurial/

Mercurial subrepositories with MQ

Sometimes when I am working on a code change, I need to make a corresponding change to the shared library code in my repository, which is itself a subrepository. When I want to commit the changes, I do so in the parent repository and Mercurial takes care of doing the coresponding commit in the subrepo.
However, I recently started using MQ to better track my own change history and give myself some more freedom to experiment and do large-scale refactoring work more safely. I have MQ enabled on both the parent and subrepo.
In the scenario describe above, am I correct in assuming that if I 'commit' to a new or existing MQ patch, then the subrepo gets ignored? This is what seems to happen in my testing. Does this mean I need to manually manage the patch queue in the subrepo? This gets unwieldy very quickly.
If this doesn't work, that's fine - I can adjust my workflow and avoid MQ when I have cross-repo work to do - but I wonder if I'm missing something or if others have a solution to this scenario they could share.
Update: Seems like this may be 'unsupported' at this point according to this thread: https://www.mercurial-scm.org/bts/issue2499
I tried the following:
Committed the subrepo change manually.
Updated the .hgsubstate file in the parent repository manually with the hash from the subrepo commit.
Tried to qrefresh the parent repository.
The idea was that I could get the commit from the parent repository to always sync with the right subrepo version, even if I had to do it manually. Unfortunately Mercurial seems to want to protect me here (as it should!) and pits out the following when I try to qrefresh:
warning: not refreshing .hgsubstate
In order to 'fix' things I do an empty commit after the hgfinish on the parent repository to get things synced back up. But... that seems silly and makes the code history harder to grok.
Oh well, guess I'll go back to the stone age and stop using MQ in my workflow when there are sub-repositories involved.