Mercurial Subrepositories: Prevent accidental recursive commits and pushes
Asked Answered
J

5

17

I work on a team where we have a code in a mercurial repository with several subrepositories:

main/
main/subrepo1/
main/subrepo1/subrepo2/

The default behavior of Mercurial is that when a hg commit is performed in "main", any outstanding changes in the subrepositories "subrepo1" and "subrepo2" will also be committed. Similarly, when "main" is pushed, any outgoing commits in "subrepo1" and "subrepo2" will also be pushed.

We find that people frequently inadvertently commit and push changes in their subrepositories (because they forgot they had made changes, and hg status by default does not show recursive changes). We also find that such global commits / pushes are almost always accidental in our team.

Mercurial 1.7 recently improved the situation with hg status -S and hg outgoing -S, which show changes in subrepositories; but still, this requires people to be paying attention.

Is there a way in Mercurial to make hg commit and hg push abort if there are changes/commits in subrepostories that would otherwise be committed/pushed?

Jacklin answered 31/1, 2011 at 22:31 Comment(1)
Ry4an's solution sounds wise in many circumstances, but the script-based approach of the accepted answer was eventually what I used; so far, so good.Jacklin
J
9

Mercurial 2.0 automatically prevents you from committing subrepositories unless you manually specify the --subrepos (or, alternatively, -S) argument to commit.

For example, you try to perform a commit while there are pending changes in a subrepository, you get the following message:

# hg commit -m 'change main repo'
abort: uncommitted changes in subrepo hello
(use --subrepos for recursive commit)

You can successfully perform the commit, however, by adding --subrepos to the command:

# hg commit --subrepos -m 'commit subrepos'
committing subrepository hello

Some things to still be careful about: If you have changed the revision a subrepository is currently at, but not the contents of the subrepository, Mercurial will happily commit the version change without the --subrepos flag. Further, recursive pushes are still performed without warning.

Jacklin answered 23/11, 2011 at 0:1 Comment(0)
S
11

Since Mercurial 1.8 there is a configuration setting that disables recursive commits. In the parent repositories .hg/hgrc you can add:

[ui]
commitsubrepos = no

If a commit in the parent repository finds uncommitted changes in a subrepository the whole commit is aborted, instead of silently committing the subrepositories.

Subclass answered 4/8, 2011 at 13:31 Comment(1)
The default has changed for this option in Mercurial 2.0, so hg commit will not abort by default if there is a dirty subrepository.Chattel
J
9

Mercurial 2.0 automatically prevents you from committing subrepositories unless you manually specify the --subrepos (or, alternatively, -S) argument to commit.

For example, you try to perform a commit while there are pending changes in a subrepository, you get the following message:

# hg commit -m 'change main repo'
abort: uncommitted changes in subrepo hello
(use --subrepos for recursive commit)

You can successfully perform the commit, however, by adding --subrepos to the command:

# hg commit --subrepos -m 'commit subrepos'
committing subrepository hello

Some things to still be careful about: If you have changed the revision a subrepository is currently at, but not the contents of the subrepository, Mercurial will happily commit the version change without the --subrepos flag. Further, recursive pushes are still performed without warning.

Jacklin answered 23/11, 2011 at 0:1 Comment(0)
X
4

One notion is to use URLs to which you have read-only access in your .hgsub files. Then when you do actually want to push in the subrepo you can just cd into it and do a hg push THE_READ_WRITE_URL.

Xylene answered 31/1, 2011 at 23:0 Comment(2)
Interesting idea. This has the benefit (and problem) of being a change that affects all users. I guess one way of doing this is to serve hg over http for the read-only, and allow standard ssh access for the read/write.Jacklin
That's always been my preference anyway. In a corporate setting I set up a read-only http:// repo so people can web browse or clone w/o bothering with access controls, and then use ssh:// for any selective access write-access requirements.Xylene
J
3

One possible solution, using VonC's "pre-commit" idea.

Setup two scripts; the first check_subrepo_commit.sh:

#!/bin/bash

# If the environment variable "SUBREPO" is set, allow changes.
[ "x$SUBREPO" != "x" ] && exit 0

# Otherwise, ensure that subrepositories have not changed.
LOCAL_CHANGES=`hg status -a -m`
GLOBAL_CHANGES=`hg status -S -a -m`
if [ "x${LOCAL_CHANGES}" != "x$GLOBAL_CHANGES" ]; then
    echo "Subrepository changes exist!"
    exit 1
fi
exit 0

The second, check_subrepo_push.sh:

#!/bin/bash

# If the environment variable "SUBREPO" is set, allow changes.
[ "x$SUBREPO" != "x" ] && exit 0

# Otherwise, ensure that subrepositories have not changed.
LOCAL_CHANGES=`hg outgoing | grep '^changeset:'`
GLOBAL_CHANGES=`hg outgoing -S | grep '^changeset:'`
if [ "x${LOCAL_CHANGES}" != "x$GLOBAL_CHANGES" ]; then
    echo "Global changes exist!"
    exit 1
fi
exit 0

Add the following to your .hgrc:

[hooks]
pre-commit.subrepo = check_subrepo_commit.sh
pre-push.subrepo = check_subrepo_push.sh

By default, hg push and hg commit will abort if there are outstanding changes in subrepositories. Running a command like so:

SUBREPO=1 hg commit

will override the check, allowing you to perform the global commit/push if you really want to.

Jacklin answered 31/1, 2011 at 23:12 Comment(0)
M
2

May be a pre-commit hook (not precommit) could do the hg status -S for you, and block the commit if it detects any changes?

Maryannamaryanne answered 31/1, 2011 at 22:50 Comment(2)
Urgh. The distinction in Mercurial between precommit and pre-commit is terrible. I had tried the first without much luck, but the second looks more promising.Jacklin
Yeah, the naming similarity is unfortunate. There's a pre-X and post-X for every command and then there's an independent precommit hook that runs before the actual committing.Xylene

© 2022 - 2024 — McMap. All rights reserved.