The following test script shows a problem where we can make an empty commit because Mercurial forgot to update th .hgsubstate file. Create three changesets with outer and inner repos moving in lock-step $ hg init $ hg init sub $ echo 'sub = sub' > .hgsub $ hg add .hgsub $ echo x0 > x $ echo y0 > sub/y $ hg add -S adding x adding sub/y $ hg commit -m r0 committing subrepository sub $ echo x1 > x $ echo y1 > sub/y $ hg commit -m r1 committing subrepository sub $ echo x2 > x $ echo y2 > sub/y $ hg commit -m r2 committing subrepository sub $ cat x sub/y .hgsubstate x2 y2 97dc77eda23d16886b839deb0e8ff655b431b9f7 sub $ hg -R sub log --template '{rev}: {node|short}\n' 2: 97dc77eda23d 1: 8514e0ba703c 0: a2eda9ba59e6 Now update to an earlier version: $ hg update 1 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat x sub/y .hgsubstate x1 y1 8514e0ba703cdb0dcc5c92beb6c534ec8e7cb34b sub $ hg -R sub id 8514e0ba703c Update the subrepository to an even earlier version: $ hg -R sub update 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat x sub/y .hgsubstate x1 y0 8514e0ba703cdb0dcc5c92beb6c534ec8e7cb34b sub $ hg -R sub id a2eda9ba59e6 What happens when we update the outer repository? $ hg update 2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat x sub/y .hgsubstate x2 y2 8514e0ba703cdb0dcc5c92beb6c534ec8e7cb34b sub $ hg -R sub id 97dc77eda23d tip The .hgsubstate file still records the old revision, but the subrepo was updated correctly. $ hg summary parent: 2:0fc3cdfccbc8 tip r2 branch: default commit: 1 modified, 1 subrepos update: (current) $ hg commit -m 'r3 - empty substate commit!' committing subrepository sub $ cat x sub/y .hgsubstate x2 y2 97dc77eda23d16886b839deb0e8ff655b431b9f7 sub $ hg -R sub id 97dc77eda23d tip $ hg log changeset: 3:9313ee30238c tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: r3 - empty substate commit! changeset: 2:0fc3cdfccbc8 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: r2 changeset: 1:5d202505eb64 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: r1 changeset: 0:56b51e4cf504 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: r0 $ hg export tip # HG changeset patch # User test # Date 0 0 # Node ID 9313ee30238c88d124ef38e8f2f3a7daa83618b5 # Parent 0fc3cdfccbc805c2bb4f2a09a354ff037583bdab r3 - empty substate commit!
I tried to track down a similar case some months ago. (Discussed on IRC July 22-23 2010.) That did however involve a commit that failed after .hgsubstate had been updated: [mk@dev-mk subtest]$ hg up -R sub -r tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved [mk@dev-mk subtest]$ hg commit --config hooks.precommit=true -m hey committing subrepository sub [mk@dev-mk subtest]$ hg up -R sub -r null 0 files updated, 0 files merged, 1 files removed, 0 files unresolved [mk@dev-mk subtest]$ hg commit --config hooks.precommit=false -m hey committing subrepository sub abort: precommit hook exited with status 1 [mk@dev-mk subtest]$ hg diff diff --git a/.hgsubstate b/.hgsubstate --- a/.hgsubstate +++ b/.hgsubstate @@ -1,1 +1,1 @@ -2a1db703cc4c9e7441592191e4ff2367708120e4 sub +0000000000000000000000000000000000000000 sub [mk@dev-mk subtest]$ hg up -R sub -r tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved [mk@dev-mk subtest]$ hg commit --config hooks.precommit=true -m hey committing subrepository sub [mk@dev-mk subtest]$ hg log -p -v -r tip changeset: 9:ccdb16ca32c0 tag: tip user: x date: Thu Jul 22 16:54:44 2010 +0200 description: hey I think these shows a general problem with the invisible substate. It is almost invisible and works fine in most cases, but the hardcoded magic makes it hard to debug and workaround issues. I tried a patch like --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -779,6 +779,10 @@ changelist.append(fname) return flog.add(text, meta, tr, linkrev, fparent1, fparent2) + # any reason to commit this file? did something go wrong? + if fname == '.hgsubstate' and fparent2o == nullid and manifest1.flags(fname) == fctx.flags(): + raise util.Abort(_('no change to %s') % fname) + # are just the flags changed during merge? if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags(): changelist.append(fname) - but the problem is that we do allow empty patches - especially for mq. I also tried to catch and abort in localrepo commitctx when we get as far as trying to commit an unmodified file. That also didn't work out - and nothing more happened. Now I guess the best/simplest way to solve this is to add a ugly hack for special handling of commits that only makes a noop-change to .hgsubstate.
Regarding .hgsubstate handling see also issue1771
Hi Mads, so by setting precommit=false you can trick Mercurial into updating the .hgsubstate file thereby making it dirty, and when you make a commit we don't realize that nothing is dirty after all since the subrepo is at its correct revision... I've attached an easier way to provoke this by simply overwriting the .hgsubstate file.
Matt, do you have any good ideas for where we should handle this?
Probably in localrepo.commit? Perhaps we can simply ignore the status on .hgsubstate earlier on.
Have looked at it. One solution could be to just 'revert' whatever a user have done with the .hgsubstate file when starting a commit.
Fixed by http://selenic.com/repo/hg/rev/b254f827b7a6 Matt Mackall <mpm@selenic.com> subrepo: rewrite handling of subrepo state at commit (issue2403) (please test the fix)
--- Bug imported by bugzilla@serpentine.com 2012-05-12 09:12 EDT --- This bug was previously known as _bug_ 2403 at http://mercurial.selenic.com/bts/issue2403 Imported an attachment (id=1459)