comparison mercurial/localrepo.py @ 16073:b254f827b7a6

subrepo: rewrite handling of subrepo state at commit (issue2403) When the contents of .hgsubstate are stale (either because they've manually been tweaked or partial updates have confused it), we get confused about whether it actually needs committing. So instead, we actively consult the parent's substate and compare it the actual current state when deciding whether it needs committing. Side effect: lots of "committing subrepo" messages that didn't correspond with real commits disappear. This change is fairly invasive for a fairly obscure condition, so it's kept on the default branch.
author Matt Mackall <mpm@selenic.com>
date Mon, 06 Feb 2012 15:10:01 -0600
parents 308406677e9d
children 004982e5d782
comparison
equal deleted inserted replaced
16072:bcb973abcc0b 16073:b254f827b7a6
1094 if force: 1094 if force:
1095 changes[0].extend(changes[6]) # mq may commit unchanged files 1095 changes[0].extend(changes[6]) # mq may commit unchanged files
1096 1096
1097 # check subrepos 1097 # check subrepos
1098 subs = [] 1098 subs = []
1099 removedsubs = set() 1099 commitsubs = set()
1100 newstate = wctx.substate.copy()
1101 # only manage subrepos and .hgsubstate if .hgsub is present
1100 if '.hgsub' in wctx: 1102 if '.hgsub' in wctx:
1101 # only manage subrepos and .hgsubstate if .hgsub is present 1103 # we'll decide whether to track this ourselves, thanks
1104 if '.hgsubstate' in changes[0]:
1105 changes[0].remove('.hgsubstate')
1106 if '.hgsubstate' in changes[2]:
1107 changes[2].remove('.hgsubstate')
1108
1109 # compare current state to last committed state
1110 # build new substate based on last committed state
1111 oldstate = wctx.p1().substate
1112 for s in sorted(newstate.keys()):
1113 if not match(s):
1114 # ignore working copy, use old state if present
1115 if s in oldstate:
1116 newstate[s] = oldstate[s]
1117 continue
1118 if not force:
1119 raise util.Abort(
1120 _("commit with new subrepo %s excluded") % s)
1121 if wctx.sub(s).dirty(True):
1122 if not self.ui.configbool('ui', 'commitsubrepos'):
1123 raise util.Abort(
1124 _("uncommitted changes in subrepo %s") % s,
1125 hint=_("use --subrepos for recursive commit"))
1126 subs.append(s)
1127 commitsubs.add(s)
1128 else:
1129 bs = wctx.sub(s).basestate()
1130 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1131 if oldstate.get(s, (None, None, None))[1] != bs:
1132 subs.append(s)
1133
1134 # check for removed subrepos
1102 for p in wctx.parents(): 1135 for p in wctx.parents():
1103 removedsubs.update(s for s in p.substate if match(s)) 1136 r = [s for s in p.substate if s not in newstate]
1104 for s in wctx.substate: 1137 subs += [s for s in r if match(s)]
1105 removedsubs.discard(s) 1138 if subs:
1106 if match(s) and wctx.sub(s).dirty():
1107 subs.append(s)
1108 if (subs or removedsubs):
1109 if (not match('.hgsub') and 1139 if (not match('.hgsub') and
1110 '.hgsub' in (wctx.modified() + wctx.added())): 1140 '.hgsub' in (wctx.modified() + wctx.added())):
1111 raise util.Abort( 1141 raise util.Abort(
1112 _("can't commit subrepos without .hgsub")) 1142 _("can't commit subrepos without .hgsub"))
1113 if '.hgsubstate' not in changes[0]: 1143 changes[0].insert(0, '.hgsubstate')
1114 changes[0].insert(0, '.hgsubstate') 1144
1115 if '.hgsubstate' in changes[2]:
1116 changes[2].remove('.hgsubstate')
1117 elif '.hgsub' in changes[2]: 1145 elif '.hgsub' in changes[2]:
1118 # clean up .hgsubstate when .hgsub is removed 1146 # clean up .hgsubstate when .hgsub is removed
1119 if ('.hgsubstate' in wctx and 1147 if ('.hgsubstate' in wctx and
1120 '.hgsubstate' not in changes[0] + changes[1] + changes[2]): 1148 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1121 changes[2].insert(0, '.hgsubstate') 1149 changes[2].insert(0, '.hgsubstate')
1122
1123 if subs and not self.ui.configbool('ui', 'commitsubrepos', False):
1124 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
1125 if changedsubs:
1126 raise util.Abort(_("uncommitted changes in subrepo %s")
1127 % changedsubs[0],
1128 hint=_("use --subrepos for recursive commit"))
1129 1150
1130 # make sure all explicit patterns are matched 1151 # make sure all explicit patterns are matched
1131 if not force and match.files(): 1152 if not force and match.files():
1132 matched = set(changes[0] + changes[1] + changes[2]) 1153 matched = set(changes[0] + changes[1] + changes[2])
1133 1154
1160 cctx = context.workingctx(self, text, user, date, extra, changes) 1181 cctx = context.workingctx(self, text, user, date, extra, changes)
1161 if editor: 1182 if editor:
1162 cctx._text = editor(self, cctx, subs) 1183 cctx._text = editor(self, cctx, subs)
1163 edited = (text != cctx._text) 1184 edited = (text != cctx._text)
1164 1185
1165 # commit subs 1186 # commit subs and write new state
1166 if subs or removedsubs: 1187 if subs:
1167 state = wctx.substate.copy() 1188 for s in sorted(commitsubs):
1168 for s in sorted(subs):
1169 sub = wctx.sub(s) 1189 sub = wctx.sub(s)
1170 self.ui.status(_('committing subrepository %s\n') % 1190 self.ui.status(_('committing subrepository %s\n') %
1171 subrepo.subrelpath(sub)) 1191 subrepo.subrelpath(sub))
1172 sr = sub.commit(cctx._text, user, date) 1192 sr = sub.commit(cctx._text, user, date)
1173 state[s] = (state[s][0], sr) 1193 newstate[s] = (newstate[s][0], sr)
1174 subrepo.writestate(self, state) 1194 subrepo.writestate(self, newstate)
1175 1195
1176 # Save commit message in case this transaction gets rolled back 1196 # Save commit message in case this transaction gets rolled back
1177 # (e.g. by a pretxncommit hook). Leave the content alone on 1197 # (e.g. by a pretxncommit hook). Leave the content alone on
1178 # the assumption that the user will use the same editor again. 1198 # the assumption that the user will use the same editor again.
1179 msgfn = self.savecommitmessage(cctx._text) 1199 msgfn = self.savecommitmessage(cctx._text)