Title: hg hook to detect unmerged changesets
Type: enhancement Stage: patch review
Components: Versions:
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: christian.heimes, ezio.melotti, georg.brandl, jcea, loewis, merwok, orsenthil, pitrou, r.david.murray, terry.reedy
Priority: normal Keywords:

Created on 2012-09-11 05:13 by ezio.melotti, last changed 2013-03-22 17:41 by loewis.

File name Uploaded Description Edit ezio.melotti, 2012-09-11 05:13 ezio.melotti, 2012-09-11 14:39 Shell script to set up the test environment
Messages (11)
msg170263 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2012-09-11 05:13
The attached Mercurial hook checks that all the changesets in a 3.x branch are merged with either another cset in the same branch or a cset in the following 3.x branch.

This is to detect and prevent situations where people commit something on e.g. 3.2 and push it without merging it with default.
msg170289 - (view) Author: Jesús Cea Avión (jcea) * (Python committer) Date: 2012-09-11 11:06
We need something like this.

I can not review, I am not familiar with mercurial hooks internals.
msg170292 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2012-09-11 11:59
+1 for the feature
msg170309 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2012-09-11 14:39
Attached a set up script to reproduce a test environment for the hook.

Create an empty dir and run ``sh`` in it.  This will:
  1) create a 'c1' subdir which is a cpython-like repo with the branches 2.7, 3.1, 3.2, default;
  2) download and set up the hook for this repo; 
  3) create a 'c2' clone of 'c1';

Once the clones are created, cd in 'c2', try to commit something, and push it.

Use `hg up <branchname>` to switch between branches.

If you `hg up 3.1`, change something, commit, and push, the hook will tell you that you have to merge with 3.2, if you do the same on 3.2 it will say you have to merge with default.

You can try unusual combinations (e.g. null merges, rollbacks, multiple commits per branch, etc.) to see how well the hook works.
msg170315 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-09-11 15:32
I might be wrong, but the logic in your hook looks a bit complicated. Wouldn't it be simpler to find all topological heads in the new csets (a topological head is a cset without any child), and check that none of them is on a 3.* branch?

Finding topological heads is very easy, so
msg170319 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2012-09-11 16:02
> I might be wrong, but the logic in your hook looks a bit complicated.

This might be true.  The logic I followed is that once a cset is merged, it becomes a parent of another cset (either in the same branch or in the "next" one).  So, if the cset is in 3.x and is not a parent, it should be merged.

> Wouldn't it be simpler to find all topological heads in the new csets
> (a topological head is a cset without any child), and check that none
> of them is on a 3.* branch?

If it's not a parent, it also means that it doesn't have any child, so we are looking at the same thing from two different points of view.

If I'm reading the code you linked correctly, it's adding all the incoming changesets in a set, and for each changeset added to the set, all its parent are removed, so that eventually only the childless changesets (the topological heads) are left.  This should also be equivalent to "set(allcsets) - set(allparents)".

On the other hand my code checks for specific branches, so if you commit on 3.1 and merge on default, the cset in 3.1 is not a topological head so it's not detected by the version you linked, whereas my script will complain saying that it should be merged with 3.2 and then with default (even if maybe it should complain because you merged it in the wrong branch).
msg184139 - (view) Author: Senthil Kumaran (orsenthil) * (Python committer) Date: 2013-03-14 07:26
Reviewed the patch - the logic looks okay to me - namely verifying that the changeset the merged with the next +0.1, 3.x branch or default.

I tested.

2.7 -> push -> success.
3.1 -> push -> fail -> merge to 3.2 -> fail -> merge to default -> success.

Looks like a good server-side hgrc hook to have.
msg184361 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-03-16 23:53
Would it be easy to check that no 2.7 commit is merged with anything in another branch? (I have not seen anyone do such a wrong merge, but I expect it will happen someday.)
msg184362 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2013-03-16 23:57
It happened once already.  It shouldn't be too difficult to add, but it might make the code more complicated and more likely to fail in some situation.
msg184980 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2013-03-22 16:07
> Wouldn't it be simpler to find all topological heads in the new csets
> (a topological head is a cset without any child), and check that none
> of them is on a 3.* branch?

Indeed -- I was looking at this again and it occurred to me that checking that the only two topological heads are 2.7 and default would be simpler, which is basically the same thing you were suggesting.

I couldn't find a way to get the list of topological heads on active and non-closed heads using the `hg *` commands, but it shouldn't be difficult to do it from the API.
FWIW, `hg heads --topo` also includes closed heads that have never been merged with the other branches (e.g. 2.0, 2.1, etc.).  `hg branches` lists all the non-closed branches, and, among them, the one that are not "inactive" should be the ones with a topological head.  Therefore this should boil down to either a set intersection between open branches and topological heads, or a set difference between open branches and inactive branches.

Once the hook detects an extra head, it could try to figure out what's wrong and suggest a solution.
msg184993 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2013-03-22 17:41
hg log -r 'head()-parents(merge())-closed()' --template '{branch}\n'

works for me. Alternatively,

hg log -r 'head()-parents(merge())-closed()-branch(2.7)-branch(default)'

should come out empty.
Date User Action Args
2013-03-22 17:41:30loewissetmessages: + msg184993
2013-03-22 16:07:19ezio.melottisetmessages: + msg184980
2013-03-16 23:57:25ezio.melottisetmessages: + msg184362
2013-03-16 23:53:51terry.reedysetnosy: + terry.reedy
messages: + msg184361
2013-03-14 07:26:27orsenthilsetnosy: + orsenthil
messages: + msg184139
2012-09-11 17:18:26r.david.murraysetnosy: + r.david.murray
2012-09-11 16:02:17ezio.melottisetnosy: + merwok
messages: + msg170319
2012-09-11 15:32:47pitrousetmessages: + msg170315
2012-09-11 14:39:26ezio.melottisetfiles: +

messages: + msg170309
2012-09-11 11:59:58christian.heimessetnosy: + christian.heimes
messages: + msg170292
2012-09-11 11:06:22jceasetmessages: + msg170289
2012-09-11 11:04:36jceasetnosy: + jcea
2012-09-11 05:13:11ezio.melotticreate