branch | stable |
changeset 1450 | 5f6e78aea094 |
parent 1449 | 9be1cadf7a07 |
child 1451 | 73eb4f33f9dc |
1438:3295353b1363 | 1450:5f6e78aea094 |
---|---|
21 |
21 |
22 __version__ = '5.1.5' |
22 __version__ = '5.1.5' |
23 testedwith = '3.3.3 3.4.1' |
23 testedwith = '3.3.3 3.4.1' |
24 buglink = 'http://bz.selenic.com/' |
24 buglink = 'http://bz.selenic.com/' |
25 |
25 |
26 |
|
27 evolutionhelptext = """ |
|
28 Obsolescence markers make it possible to mark changesets that have been |
|
29 deleted or superset in a new version of the changeset. |
|
30 |
|
31 Unlike the previous way of handling such changes, by stripping the old |
|
32 changesets from the repository, obsolescence markers can be propagated |
|
33 between repositories. This allows for a safe and simple way of exchanging |
|
34 mutable history and altering it after the fact. Changeset phases are |
|
35 respected, such that only draft and secret changesets can be altered (see |
|
36 :hg:`hg phases` for details). |
|
37 |
|
38 Obsolescence is tracked using "obsolete markers", a piece of metadata |
|
39 tracking which changesets have been made obsolete, potential successors for |
|
40 a given changeset, the moment the changeset was marked as obsolete, and the |
|
41 user who performed the rewriting operation. The markers are stored |
|
42 separately from standard changeset data can be exchanged without any of the |
|
43 precursor changesets, preventing unnecessary exchange of obsolescence data. |
|
44 |
|
45 The complete set of obsolescence markers describes a history of changeset |
|
46 modifications that is orthogonal to the repository history of file |
|
47 modifications. This changeset history allows for detection and automatic |
|
48 resolution of edge cases arising from multiple users rewriting the same part |
|
49 of history concurrently. |
|
50 |
|
51 Current feature status |
|
52 ====================== |
|
53 |
|
54 This feature is still in development. If you see this help, you have enable an |
|
55 extension that turned this feature on. |
|
56 |
|
57 Obsolescence markers will be exchanged between repositories that explicitly |
|
58 assert support for the obsolescence feature (this can currently only be done |
|
59 via an extension).""".strip() |
|
60 |
|
61 |
|
26 import sys, os |
62 import sys, os |
27 import random |
63 import random |
28 from StringIO import StringIO |
64 from StringIO import StringIO |
29 import struct |
65 import struct |
30 import re |
66 import re |
67 import collections |
|
31 import socket |
68 import socket |
32 import errno |
69 import errno |
33 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b') |
70 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b') |
34 |
71 |
35 import mercurial |
72 import mercurial |
43 gboptslist = getattr(wireproto, 'gboptslist', None) |
80 gboptslist = getattr(wireproto, 'gboptslist', None) |
44 gboptsmap = getattr(wireproto, 'gboptsmap', None) |
81 gboptsmap = getattr(wireproto, 'gboptsmap', None) |
45 except (ImportError, AttributeError): |
82 except (ImportError, AttributeError): |
46 gboptslist = gboptsmap = None |
83 gboptslist = gboptsmap = None |
47 |
84 |
85 # Flags for enabling optional parts of evolve |
|
86 commandopt = 'allnewcommands' |
|
48 |
87 |
49 from mercurial import bookmarks |
88 from mercurial import bookmarks |
50 from mercurial import cmdutil |
89 from mercurial import cmdutil |
51 from mercurial import commands |
90 from mercurial import commands |
52 from mercurial import context |
91 from mercurial import context |
53 from mercurial import copies |
92 from mercurial import copies |
54 from mercurial import error |
93 from mercurial import error |
55 from mercurial import exchange |
94 from mercurial import exchange |
56 from mercurial import extensions |
95 from mercurial import extensions |
96 from mercurial import help |
|
57 from mercurial import httppeer |
97 from mercurial import httppeer |
58 from mercurial import hg |
98 from mercurial import hg |
59 from mercurial import lock as lockmod |
99 from mercurial import lock as lockmod |
60 from mercurial import merge |
100 from mercurial import merge |
61 from mercurial import node |
101 from mercurial import node |
142 - Setup of pre-* and post-* hooks |
182 - Setup of pre-* and post-* hooks |
143 - pushkey setup |
183 - pushkey setup |
144 """ |
184 """ |
145 for cont, funcname, func in self._duckpunchers: |
185 for cont, funcname, func in self._duckpunchers: |
146 setattr(cont, funcname, func) |
186 setattr(cont, funcname, func) |
147 for command, wrapper in self._commandwrappers: |
187 for command, wrapper, opts in self._commandwrappers: |
148 extensions.wrapcommand(commands.table, command, wrapper) |
188 entry = extensions.wrapcommand(commands.table, command, wrapper) |
189 if opts: |
|
190 for short, long, val, msg in opts: |
|
191 entry[1].append((short, long, val, msg)) |
|
149 for cont, funcname, wrapper in self._functionwrappers: |
192 for cont, funcname, wrapper in self._functionwrappers: |
150 extensions.wrapfunction(cont, funcname, wrapper) |
193 extensions.wrapfunction(cont, funcname, wrapper) |
151 for c in self._uicallables: |
194 for c in self._uicallables: |
152 c(ui) |
195 c(ui) |
153 |
196 |
164 knownexts = {} |
207 knownexts = {} |
165 for name, symbol in self._revsetsymbols: |
208 for name, symbol in self._revsetsymbols: |
166 revset.symbols[name] = symbol |
209 revset.symbols[name] = symbol |
167 for name, kw in self._templatekws: |
210 for name, kw in self._templatekws: |
168 templatekw.keywords[name] = kw |
211 templatekw.keywords[name] = kw |
169 for ext, command, wrapper in self._extcommandwrappers: |
212 for ext, command, wrapper, opts in self._extcommandwrappers: |
170 if ext not in knownexts: |
213 if ext not in knownexts: |
171 e = extensions.find(ext) |
214 try: |
172 if e is None: |
215 e = extensions.find(ext) |
173 raise util.Abort('extension %s not found' % ext) |
216 except KeyError: |
217 # Extension isn't enabled, so don't bother trying to wrap |
|
218 # it. |
|
219 continue |
|
174 knownexts[ext] = e.cmdtable |
220 knownexts[ext] = e.cmdtable |
175 extensions.wrapcommand(knownexts[ext], commands, wrapper) |
221 entry = extensions.wrapcommand(knownexts[ext], command, wrapper) |
222 if opts: |
|
223 for short, long, val, msg in opts: |
|
224 entry[1].append((short, long, val, msg)) |
|
225 |
|
176 for c in self._extcallables: |
226 for c in self._extcallables: |
177 c(ui) |
227 c(ui) |
178 |
228 |
179 def final_reposetup(self, ui, repo): |
229 def final_reposetup(self, ui, repo): |
180 """Method to be used as a the extension reposetup |
230 """Method to be used as a the extension reposetup |
258 def dec(keyword): |
308 def dec(keyword): |
259 self._templatekws.append((keywordname, keyword)) |
309 self._templatekws.append((keywordname, keyword)) |
260 return keyword |
310 return keyword |
261 return dec |
311 return dec |
262 |
312 |
263 def wrapcommand(self, command, extension=None): |
313 def wrapcommand(self, command, extension=None, opts=[]): |
264 """Decorated function is a command wrapper |
314 """Decorated function is a command wrapper |
265 |
315 |
266 The name of the command must be given as the decorator argument. |
316 The name of the command must be given as the decorator argument. |
267 The wrapping is installed during `uisetup`. |
317 The wrapping is installed during `uisetup`. |
268 |
318 |
277 @eh.wrapcommand('summary') |
327 @eh.wrapcommand('summary') |
278 def wrapsummary(orig, ui, repo, *args, **kwargs): |
328 def wrapsummary(orig, ui, repo, *args, **kwargs): |
279 ui.note('Barry!') |
329 ui.note('Barry!') |
280 return orig(ui, repo, *args, **kwargs) |
330 return orig(ui, repo, *args, **kwargs) |
281 |
331 |
332 The `opts` argument allows specifying additional arguments for the |
|
333 command. |
|
334 |
|
282 """ |
335 """ |
283 def dec(wrapper): |
336 def dec(wrapper): |
284 if extension is None: |
337 if extension is None: |
285 self._commandwrappers.append((command, wrapper)) |
338 self._commandwrappers.append((command, wrapper, opts)) |
286 else: |
339 else: |
287 self._extcommandwrappers.append((extension, command, wrapper)) |
340 self._extcommandwrappers.append((extension, command, wrapper, |
341 opts)) |
|
288 return wrapper |
342 return wrapper |
289 return dec |
343 return dec |
290 |
344 |
291 def wrapfunction(self, container, funcname): |
345 def wrapfunction(self, container, funcname): |
292 """Decorated function is a function wrapper |
346 """Decorated function is a function wrapper |
326 |
380 |
327 eh = exthelper() |
381 eh = exthelper() |
328 uisetup = eh.final_uisetup |
382 uisetup = eh.final_uisetup |
329 extsetup = eh.final_extsetup |
383 extsetup = eh.final_extsetup |
330 reposetup = eh.final_reposetup |
384 reposetup = eh.final_reposetup |
385 |
|
386 ##################################################################### |
|
387 ### Option configuration ### |
|
388 ##################################################################### |
|
389 |
|
390 @eh.reposetup # must be the first of its kin. |
|
391 def _configureoptions(ui, repo): |
|
392 # If no capabilities are specified, enable everything. |
|
393 # This is so existing evolve users don't need to change their config. |
|
394 evolveopts = ui.configlist('experimental', 'evolution') |
|
395 if not evolveopts: |
|
396 evolveopts = ['all'] |
|
397 ui.setconfig('experimental', 'evolution', evolveopts) |
|
398 |
|
399 @eh.uisetup |
|
400 def _configurecmdoptions(ui): |
|
401 # Unregister evolve commands if the command capability is not specified. |
|
402 # |
|
403 # This must be in the same function as the option configuration above to |
|
404 # guarantee it happens after the above configuration, but before the |
|
405 # extsetup functions. |
|
406 evolvecommands = ui.configlist('experimental', 'evolutioncommands') |
|
407 evolveopts = ui.configlist('experimental', 'evolution') |
|
408 if evolveopts and (commandopt not in evolveopts and |
|
409 'all' not in evolveopts): |
|
410 # We build whitelist containing the commands we want to enable |
|
411 whitelist = set() |
|
412 for cmd in evolvecommands: |
|
413 matchingevolvecommands = [e for e in cmdtable.keys() if cmd in e] |
|
414 if not matchingevolvecommands: |
|
415 raise error.Abort(_('unknown command: %s') % cmd) |
|
416 elif len(matchingevolvecommands) > 1: |
|
417 raise error.Abort(_('ambiguous command specification: "%s" matches %r') |
|
418 % (cmd, matchingevolvecommands)) |
|
419 else: |
|
420 whitelist.add(matchingevolvecommands[0]) |
|
421 for disabledcmd in set(cmdtable) - whitelist: |
|
422 del cmdtable[disabledcmd] |
|
331 |
423 |
332 ##################################################################### |
424 ##################################################################### |
333 ### experimental behavior ### |
425 ### experimental behavior ### |
334 ##################################################################### |
426 ##################################################################### |
335 |
427 |
588 @eh.wrapcommand("update") |
680 @eh.wrapcommand("update") |
589 @eh.wrapcommand("parents") |
681 @eh.wrapcommand("parents") |
590 @eh.wrapcommand("pull") |
682 @eh.wrapcommand("pull") |
591 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): |
683 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): |
592 """Warn that the working directory parent is an obsolete changeset""" |
684 """Warn that the working directory parent is an obsolete changeset""" |
593 res = origfn(ui, repo, *args, **opts) |
685 def warnobsolete(): |
594 if repo['.'].obsolete(): |
686 if repo['.'].obsolete(): |
595 ui.warn(_('working directory parent is obsolete!\n')) |
687 ui.warn(_('working directory parent is obsolete!\n')) |
688 if (not ui.quiet) and obsolete.isenabled(repo, commandopt): |
|
689 ui.warn(_('(use "hg evolve" to update to its successor)\n')) |
|
690 wlock = None |
|
691 try: |
|
692 wlock = repo.wlock() |
|
693 repo._afterlock(warnobsolete) |
|
694 res = origfn(ui, repo, *args, **opts) |
|
695 finally: |
|
696 lockmod.release(wlock) |
|
596 return res |
697 return res |
597 |
698 |
598 # XXX this could wrap transaction code |
699 # XXX this could wrap transaction code |
599 # XXX (but this is a bit a layer violation) |
700 # XXX (but this is a bit a layer violation) |
600 @eh.wrapcommand("commit") |
701 @eh.wrapcommand("commit") |
960 If a user invokes the command a deprecation warning will be printed and |
1061 If a user invokes the command a deprecation warning will be printed and |
961 the command of the *new* alias will be invoked. |
1062 the command of the *new* alias will be invoked. |
962 |
1063 |
963 This function is loosely based on the extensions.wrapcommand function. |
1064 This function is loosely based on the extensions.wrapcommand function. |
964 ''' |
1065 ''' |
965 aliases, entry = cmdutil.findcmd(newalias, cmdtable) |
1066 try: |
1067 aliases, entry = cmdutil.findcmd(newalias, cmdtable) |
|
1068 except error.UnknownCommand: |
|
1069 # Commands may be disabled |
|
1070 return |
|
966 for alias, e in cmdtable.iteritems(): |
1071 for alias, e in cmdtable.iteritems(): |
967 if e is entry: |
1072 if e is entry: |
968 break |
1073 break |
969 |
1074 |
970 synopsis = '(DEPRECATED)' |
1075 synopsis = '(DEPRECATED)' |
1023 finally: |
1128 finally: |
1024 lockmod.release(tr, lock, wlock) |
1129 lockmod.release(tr, lock, wlock) |
1025 |
1130 |
1026 @command('debugobsstorestat', [], '') |
1131 @command('debugobsstorestat', [], '') |
1027 def cmddebugobsstorestat(ui, repo): |
1132 def cmddebugobsstorestat(ui, repo): |
1133 def _updateclustermap(nodes, mark, clustersmap): |
|
1134 c = (set(nodes), set([mark])) |
|
1135 toproceed = set(nodes) |
|
1136 while toproceed: |
|
1137 n = toproceed.pop() |
|
1138 other = clustersmap.get(n) |
|
1139 if (other is not None |
|
1140 and other is not c): |
|
1141 other[0].update(c[0]) |
|
1142 other[1].update(c[1]) |
|
1143 for on in c[0]: |
|
1144 if on in toproceed: |
|
1145 continue |
|
1146 clustersmap[on] = other |
|
1147 c = other |
|
1148 clustersmap[n] = c |
|
1149 |
|
1028 """print statistic about obsolescence markers in the repo""" |
1150 """print statistic about obsolescence markers in the repo""" |
1029 store = repo.obsstore |
1151 store = repo.obsstore |
1030 unfi = repo.unfiltered() |
1152 unfi = repo.unfiltered() |
1031 nm = unfi.changelog.nodemap |
1153 nm = unfi.changelog.nodemap |
1032 ui.write('markers total: %9i\n' % len(store._all)) |
1154 ui.write('markers total: %9i\n' % len(store._all)) |
1052 parents = [meta.get('p1'), meta.get('p2')] |
1174 parents = [meta.get('p1'), meta.get('p2')] |
1053 parents = [node.bin(p) for p in parents if p is not None] |
1175 parents = [node.bin(p) for p in parents if p is not None] |
1054 if parents: |
1176 if parents: |
1055 parentsdata += 1 |
1177 parentsdata += 1 |
1056 # cluster handling |
1178 # cluster handling |
1057 nodes = set() |
1179 nodes = set(mark[1]) |
1058 nodes.add(mark[0]) |
1180 nodes.add(mark[0]) |
1059 nodes.update(mark[1]) |
1181 _updateclustermap(nodes, mark, clustersmap) |
1060 c = (set(nodes), set([mark])) |
|
1061 |
|
1062 toproceed = set(nodes) |
|
1063 while toproceed: |
|
1064 n = toproceed.pop() |
|
1065 other = clustersmap.get(n) |
|
1066 if (other is not None |
|
1067 and other is not c): |
|
1068 other[0].update(c[0]) |
|
1069 other[1].update(c[1]) |
|
1070 for on in c[0]: |
|
1071 if on in toproceed: |
|
1072 continue |
|
1073 clustersmap[on] = other |
|
1074 c = other |
|
1075 clustersmap[n] = c |
|
1076 # same with parent data |
1182 # same with parent data |
1077 nodes.update(parents) |
1183 nodes.update(parents) |
1078 c = (set(nodes), set([mark])) |
1184 _updateclustermap(nodes, mark, pclustersmap) |
1079 toproceed = set(nodes) |
|
1080 while toproceed: |
|
1081 n = toproceed.pop() |
|
1082 other = pclustersmap.get(n) |
|
1083 if (other is not None |
|
1084 and other is not c): |
|
1085 other[0].update(c[0]) |
|
1086 other[1].update(c[1]) |
|
1087 for on in c[0]: |
|
1088 if on in toproceed: |
|
1089 continue |
|
1090 pclustersmap[on] = other |
|
1091 c = other |
|
1092 pclustersmap[n] = c |
|
1093 |
1185 |
1094 # freezing the result |
1186 # freezing the result |
1095 for c in clustersmap.values(): |
1187 for c in clustersmap.values(): |
1096 fc = (frozenset(c[0]), frozenset(c[1])) |
1188 fc = (frozenset(c[0]), frozenset(c[1])) |
1097 for n in fc[0]: |
1189 for n in fc[0]: |
1141 median = len(allpclusters[nbcluster//2][1]) |
1233 median = len(allpclusters[nbcluster//2][1]) |
1142 ui.write(' median length: %9i\n' % median) |
1234 ui.write(' median length: %9i\n' % median) |
1143 mean = sum(len(x[1]) for x in allpclusters) // nbcluster |
1235 mean = sum(len(x[1]) for x in allpclusters) // nbcluster |
1144 ui.write(' mean length: %9i\n' % mean) |
1236 ui.write(' mean length: %9i\n' % mean) |
1145 |
1237 |
1238 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category): |
|
1239 """Resolve the troubles affecting one revision""" |
|
1240 wlock = lock = tr = None |
|
1241 try: |
|
1242 wlock = repo.wlock() |
|
1243 lock = repo.lock() |
|
1244 tr = repo.transaction("evolve") |
|
1245 if 'unstable' == category: |
|
1246 result = _solveunstable(ui, repo, ctx, dryrun, confirm, progresscb) |
|
1247 elif 'bumped' == category: |
|
1248 result = _solvebumped(ui, repo, ctx, dryrun, confirm, progresscb) |
|
1249 elif 'divergent' == category: |
|
1250 result = _solvedivergent(ui, repo, ctx, dryrun, confirm, |
|
1251 progresscb) |
|
1252 else: |
|
1253 assert False, "unknown trouble category: %s" % (category) |
|
1254 tr.close() |
|
1255 return result |
|
1256 finally: |
|
1257 lockmod.release(tr, lock, wlock) |
|
1258 |
|
1259 def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat): |
|
1260 """Used by the evolve function to display an error message when |
|
1261 no troubles can be resolved""" |
|
1262 troublecategories = ['bumped', 'divergent', 'unstable'] |
|
1263 unselectedcategories = [c for c in troublecategories if c != targetcat] |
|
1264 msg = None |
|
1265 hint = None |
|
1266 |
|
1267 troubled = { |
|
1268 "unstable": repo.revs("unstable()"), |
|
1269 "divergent": repo.revs("divergent()"), |
|
1270 "bumped": repo.revs("bumped()"), |
|
1271 "all": repo.revs("troubled()"), |
|
1272 } |
|
1273 |
|
1274 |
|
1275 hintmap = { |
|
1276 'bumped': _("do you want to use --bumped"), |
|
1277 'bumped+divergent': _("do you want to use --bumped or --divergent"), |
|
1278 'bumped+unstable': _("do you want to use --bumped or --unstable"), |
|
1279 'divergent': _("do you want to use --divergent"), |
|
1280 'divergent+unstable': _("do you want to use --divergent" |
|
1281 " or --unstable"), |
|
1282 'unstable': _("do you want to use --unstable"), |
|
1283 'any+bumped': _("do you want to use --any (or --rev) and --bumped"), |
|
1284 'any+bumped+divergent': _("do you want to use --any (or --rev) and" |
|
1285 " --bumped or --divergent"), |
|
1286 'any+bumped+unstable': _("do you want to use --any (or --rev) and" |
|
1287 "--bumped or --unstable"), |
|
1288 'any+divergent': _("do you want to use --any (or --rev) and" |
|
1289 " --divergent"), |
|
1290 'any+divergent+unstable': _("do you want to use --any (or --rev)" |
|
1291 " and --divergent or --unstable"), |
|
1292 'any+unstable': _("do you want to use --any (or --rev)" |
|
1293 "and --unstable"), |
|
1294 } |
|
1295 |
|
1296 if revopt: |
|
1297 revs = scmutil.revrange(repo, revopt) |
|
1298 if not revs: |
|
1299 msg = _("set of specified revisions is empty") |
|
1300 else: |
|
1301 msg = _("no %s changesets in specified revisions") % targetcat |
|
1302 othertroubles = [] |
|
1303 for cat in unselectedcategories: |
|
1304 if revs & troubled[cat]: |
|
1305 othertroubles.append(cat) |
|
1306 if othertroubles: |
|
1307 hint = hintmap['+'.join(othertroubles)] |
|
1308 |
|
1309 elif anyopt: |
|
1310 msg = _("no %s changesets to evolve") % targetcat |
|
1311 othertroubles = [] |
|
1312 for cat in unselectedcategories: |
|
1313 if troubled[cat]: |
|
1314 othertroubles.append(cat) |
|
1315 if othertroubles: |
|
1316 hint = hintmap['+'.join(othertroubles)] |
|
1317 |
|
1318 else: |
|
1319 # evolve without any option = relative to the current wdir |
|
1320 if targetcat == 'unstable': |
|
1321 msg = _("nothing to evolve on current working copy parent") |
|
1322 else: |
|
1323 msg = _("current working copy parent is not %s") % targetcat |
|
1324 |
|
1325 p1 = repo['.'].rev() |
|
1326 othertroubles = [] |
|
1327 for cat in unselectedcategories: |
|
1328 if p1 in troubled[cat]: |
|
1329 othertroubles.append(cat) |
|
1330 if othertroubles: |
|
1331 hint = hintmap['+'.join(othertroubles)] |
|
1332 else: |
|
1333 l = len(troubled[targetcat]) |
|
1334 if l: |
|
1335 hint = (_("%d other %s in the repository, do you want --any or --rev") |
|
1336 % (l, targetcat)) |
|
1337 else: |
|
1338 othertroubles = [] |
|
1339 for cat in unselectedcategories: |
|
1340 if troubled[cat]: |
|
1341 othertroubles.append(cat) |
|
1342 if othertroubles: |
|
1343 hint = hintmap['any+'+('+'.join(othertroubles))] |
|
1344 else: |
|
1345 msg = _("no troubled changesets") |
|
1346 |
|
1347 assert msg is not None |
|
1348 ui.write_err(msg+"\n") |
|
1349 if hint: |
|
1350 ui.write_err("("+hint+")\n") |
|
1351 return 2 |
|
1352 else: |
|
1353 return 1 |
|
1354 |
|
1355 def _cleanup(ui, repo, startnode, showprogress): |
|
1356 if showprogress: |
|
1357 ui.progress('evolve', None) |
|
1358 if repo['.'] != startnode: |
|
1359 ui.status(_('working directory is now at %s\n') % repo['.']) |
|
1360 |
|
1361 class MultipleSuccessorsError(RuntimeError): |
|
1362 """Exception raised by _singlesuccessor when multiple sucessors sets exists |
|
1363 |
|
1364 The object contains the list of successorssets in its 'successorssets' |
|
1365 attribute to call to easily recover. |
|
1366 """ |
|
1367 |
|
1368 def __init__(self, successorssets): |
|
1369 self.successorssets = successorssets |
|
1370 |
|
1371 def _singlesuccessor(repo, p): |
|
1372 """returns p (as rev) if not obsolete or its unique latest successors |
|
1373 |
|
1374 fail if there are no such successor""" |
|
1375 |
|
1376 if not p.obsolete(): |
|
1377 return p.rev() |
|
1378 obs = repo[p] |
|
1379 ui = repo.ui |
|
1380 newer = obsolete.successorssets(repo, obs.node()) |
|
1381 # search of a parent which is not killed |
|
1382 while not newer: |
|
1383 ui.debug("stabilize target %s is plain dead," |
|
1384 " trying to stabilize on its parent\n" % |
|
1385 obs) |
|
1386 obs = obs.parents()[0] |
|
1387 newer = obsolete.successorssets(repo, obs.node()) |
|
1388 if len(newer) > 1 or len(newer[0]) > 1: |
|
1389 raise MultipleSuccessorsError(newer) |
|
1390 |
|
1391 return repo[newer[0][0]].rev() |
|
1392 |
|
1393 def builddependencies(repo, revs): |
|
1394 """returns dependency graphs giving an order to solve instability of revs |
|
1395 (see _orderrevs for more information on usage)""" |
|
1396 |
|
1397 # For each troubled revision we keep track of what instability if any should |
|
1398 # be resolved in order to resolve it. Example: |
|
1399 # dependencies = {3: [6], 6:[]} |
|
1400 # Means that: 6 has no dependency, 3 depends on 6 to be solved |
|
1401 dependencies = {} |
|
1402 # rdependencies is the inverted dict of dependencies |
|
1403 rdependencies = collections.defaultdict(set) |
|
1404 |
|
1405 for r in revs: |
|
1406 dependencies[r] = set() |
|
1407 for p in repo[r].parents(): |
|
1408 try: |
|
1409 succ = _singlesuccessor(repo, p) |
|
1410 except MultipleSuccessorsError, exc: |
|
1411 dependencies[r] = exc.successorssets |
|
1412 continue |
|
1413 if succ in revs: |
|
1414 dependencies[r].add(succ) |
|
1415 rdependencies[succ].add(r) |
|
1416 return dependencies, rdependencies |
|
1417 |
|
1418 def _selectrevs(repo, allopt, revopt, anyopt, targetcat): |
|
1419 """select troubles in repo matching according to given options""" |
|
1420 revs = set() |
|
1421 if allopt or revopt: |
|
1422 revs = repo.revs(targetcat+'()') |
|
1423 if revopt: |
|
1424 revs = scmutil.revrange(repo, revopt) & revs |
|
1425 elif not anyopt and targetcat == 'unstable': |
|
1426 revs = set(_aspiringdescendant(repo, repo.revs('(.::) - obsolete()::'))) |
|
1427 elif anyopt: |
|
1428 revs = repo.revs('first(%s())' % (targetcat)) |
|
1429 elif targetcat == 'unstable': |
|
1430 revs = set(_aspiringchildren(repo, repo.revs('(.::) - obsolete()::'))) |
|
1431 if 1 < len(revs): |
|
1432 msg = "multiple evolve candidates" |
|
1433 hint = (_("select one of %s with --rev") |
|
1434 % ', '.join([str(repo[r]) for r in sorted(revs)])) |
|
1435 raise error.Abort(msg, hint=hint) |
|
1436 elif targetcat in repo['.'].troubles(): |
|
1437 revs = set([repo['.'].rev()]) |
|
1438 return revs |
|
1439 |
|
1440 |
|
1441 def _orderrevs(repo, revs): |
|
1442 """Compute an ordering to solve instability for the given revs |
|
1443 |
|
1444 - Takes revs a list of instable revisions |
|
1445 |
|
1446 - Returns the same revisions ordered to solve their instability from the |
|
1447 bottom to the top of the stack that the stabilization process will produce |
|
1448 eventually. |
|
1449 |
|
1450 This ensure the minimal number of stabilization as we can stabilize each |
|
1451 revision on its final, stabilized, destination. |
|
1452 """ |
|
1453 # Step 1: Build the dependency graph |
|
1454 dependencies, rdependencies = builddependencies(repo, revs) |
|
1455 # Step 2: Build the ordering |
|
1456 # Remove the revisions with no dependency(A) and add them to the ordering. |
|
1457 # Removing these revisions leads to new revisions with no dependency (the |
|
1458 # one depending on A) that we can remove from the dependency graph and add |
|
1459 # to the ordering. We progress in a similar fashion until the ordering is |
|
1460 # built |
|
1461 solvablerevs = collections.deque([r for r in sorted(dependencies.keys()) |
|
1462 if not dependencies[r]]) |
|
1463 ordering = [] |
|
1464 while solvablerevs: |
|
1465 rev = solvablerevs.popleft() |
|
1466 for dependent in rdependencies[rev]: |
|
1467 dependencies[dependent].remove(rev) |
|
1468 if not dependencies[dependent]: |
|
1469 solvablerevs.append(dependent) |
|
1470 del dependencies[rev] |
|
1471 ordering.append(rev) |
|
1472 |
|
1473 ordering.extend(sorted(dependencies)) |
|
1474 return ordering |
|
1475 |
|
1146 @command('^evolve|stabilize|solve', |
1476 @command('^evolve|stabilize|solve', |
1147 [('n', 'dry-run', False, |
1477 [('n', 'dry-run', False, |
1148 'do not perform actions, just print what would be done'), |
1478 'do not perform actions, just print what would be done'), |
1149 ('', 'confirm', False, |
1479 ('', 'confirm', False, |
1150 'ask for confirmation before performing the action'), |
1480 'ask for confirmation before performing the action'), |
1151 ('A', 'any', False, 'also consider troubled changesets unrelated to current working directory'), |
1481 ('A', 'any', False, 'also consider troubled changesets unrelated to current working directory'), |
1152 ('a', 'all', False, 'evolve all troubled changesets in the repo ' |
1482 ('r', 'rev', [], 'solves troubles of these revisions'), |
1153 '(implies any)'), |
1483 ('', 'bumped', False, 'solves only bumped changesets'), |
1484 ('', 'divergent', False, 'solves only divergent changesets'), |
|
1485 ('', 'unstable', False, 'solves only unstable changesets (default)'), |
|
1486 ('a', 'all', False, 'evolve all troubled changesets related to the current ' |
|
1487 'working directory and its descendants'), |
|
1154 ('c', 'continue', False, 'continue an interrupted evolution'), |
1488 ('c', 'continue', False, 'continue an interrupted evolution'), |
1155 ] + mergetoolopts, |
1489 ] + mergetoolopts, |
1156 _('[OPTIONS]...')) |
1490 _('[OPTIONS]...')) |
1157 def evolve(ui, repo, **opts): |
1491 def evolve(ui, repo, **opts): |
1158 """solve trouble in your repository |
1492 """solve troubles in your repository |
1159 |
1493 |
1160 - rebase unstable changesets to make them stable again, |
1494 - rebase unstable changesets to make them stable again, |
1161 - create proper diffs from bumped changesets, |
1495 - create proper diffs from bumped changesets, |
1162 - merge divergent changesets, |
1496 - fuse divergent changesets back together, |
1163 - update to a successor if the working directory parent is |
1497 - update to a successor if the working directory parent is |
1164 obsolete |
1498 obsolete |
1165 |
1499 |
1166 By default a single changeset is evolved for each invocation and only |
1500 If no argument are passed and the current working copy parent is obsolete, |
1167 troubled changesets that would evolve as a descendant of the current |
1501 :hg:`evolve` will update the working copy to the successors of this working |
1168 working directory will be considered. See --all and --any options to change |
1502 copy parent. If the working copy parent is not obsolete (and still no |
1169 this behavior. |
1503 argument passed) each invocation of :hg:`evolve` will evolve a single |
1170 |
1504 unstable changeset, It will only select a changeset to be evolved if it |
1171 - For unstable, this means taking the first which could be rebased as a |
1505 will result in a new children for the current working copy parent or its |
1172 child of the working directory parent revision or one of its descendants |
1506 descendants. The working copy will be updated on the result |
1173 and rebasing it. |
1507 (this last behavior will most likely to change in the future). |
1174 |
1508 You can evolve all the unstable changesets that will be evolved on the |
1175 - For divergent, this means taking "." if applicable. |
1509 parent of the working copy and all its descendants recursively by using |
1176 |
1510 :hg:`evolve` --all. |
1177 With --any, evolve picks any troubled changeset to repair. |
1511 |
1512 You can decide to evolve other categories of trouble using the --divergent |
|
1513 and --bumped flags. If no other option are specified, this will try to |
|
1514 solve the specified troubles for the working copy parent. |
|
1515 |
|
1516 You can also evolve changesets affected by troubles of the selected |
|
1517 category using the --rev options. You can pick the next one anywhere in the |
|
1518 repo using --any. |
|
1519 |
|
1520 You can evolve all the changesets affected by troubles of the selected |
|
1521 category using --all --any. |
|
1178 |
1522 |
1179 The working directory is updated to the newly created revision. |
1523 The working directory is updated to the newly created revision. |
1180 """ |
1524 """ |
1181 |
1525 |
1526 # Options |
|
1182 contopt = opts['continue'] |
1527 contopt = opts['continue'] |
1183 anyopt = opts['any'] |
1528 anyopt = opts['any'] |
1184 allopt = opts['all'] |
1529 allopt = opts['all'] |
1530 startnode = repo['.'] |
|
1185 dryrunopt = opts['dry_run'] |
1531 dryrunopt = opts['dry_run'] |
1186 confirmopt = opts['confirm'] |
1532 confirmopt = opts['confirm'] |
1533 revopt = opts['rev'] |
|
1534 troublecategories = ['bumped', 'divergent', 'unstable'] |
|
1535 specifiedcategories = [t for t in troublecategories if opts[t]] |
|
1536 targetcat = 'unstable' |
|
1537 if 1 < len(specifiedcategories): |
|
1538 msg = _('cannot specify more than one trouble category to solve (yet)') |
|
1539 raise util.Abort(msg) |
|
1540 elif len(specifiedcategories) == 1: |
|
1541 targetcat = specifiedcategories[0] |
|
1542 elif repo['.'].obsolete(): |
|
1543 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
|
1544 # no args and parent is obsolete, update to successors |
|
1545 try: |
|
1546 ctx = repo[_singlesuccessor(repo, repo['.'])] |
|
1547 except MultipleSuccessorsError, exc: |
|
1548 repo.ui.write_err('parent is obsolete with multiple successors:\n') |
|
1549 for ln in exc.successorssets: |
|
1550 for n in ln: |
|
1551 displayer.show(repo[n]) |
|
1552 return 2 |
|
1553 |
|
1554 |
|
1555 ui.status(_('update:')) |
|
1556 if not ui.quiet: |
|
1557 displayer.show(ctx) |
|
1558 |
|
1559 if dryrunopt: |
|
1560 return 0 |
|
1561 res = hg.update(repo, ctx.rev()) |
|
1562 if ctx != startnode: |
|
1563 ui.status(_('working directory is now at %s\n') % ctx) |
|
1564 return res |
|
1565 |
|
1187 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve') |
1566 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve') |
1188 |
1567 troubled = set(repo.revs('troubled()')) |
1189 startnode = repo['.'] |
1568 |
1190 |
1569 # Progress handling |
1570 seen = 1 |
|
1571 count = allopt and len(troubled) or 1 |
|
1572 showprogress = allopt |
|
1573 |
|
1574 def progresscb(): |
|
1575 if revopt or allopt: |
|
1576 ui.progress('evolve', seen, unit='changesets', total=count) |
|
1577 |
|
1578 # Continuation handling |
|
1191 if contopt: |
1579 if contopt: |
1192 if anyopt: |
1580 if anyopt: |
1193 raise util.Abort('cannot specify both "--any" and "--continue"') |
1581 raise util.Abort('cannot specify both "--any" and "--continue"') |
1194 if allopt: |
1582 if allopt: |
1195 raise util.Abort('cannot specify both "--all" and "--continue"') |
1583 raise util.Abort('cannot specify both "--all" and "--continue"') |
1196 graftcmd = commands.table['graft'][0] |
1584 graftcmd = commands.table['graft'][0] |
1197 return graftcmd(ui, repo, old_obsolete=True, **{'continue': True}) |
1585 return graftcmd(ui, repo, old_obsolete=True, **{'continue': True}) |
1198 |
1586 cmdutil.bailifchanged(repo) |
1199 tro = _picknexttroubled(ui, repo, anyopt or allopt) |
1587 |
1200 if tro is None: |
1588 |
1201 if repo['.'].obsolete(): |
1589 if revopt and allopt: |
1202 displayer = cmdutil.show_changeset( |
1590 raise util.Abort('cannot specify both "--rev" and "--all"') |
1203 ui, repo, {'template': shorttemplate}) |
1591 if revopt and anyopt: |
1204 successors = set() |
1592 raise util.Abort('cannot specify both "--rev" and "--any"') |
1205 |
1593 |
1206 for successorsset in obsolete.successorssets(repo, repo['.'].node()): |
1594 revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat) |
1207 for nodeid in successorsset: |
1595 |
1208 successors.add(repo[nodeid]) |
1596 if not revs: |
1209 |
1597 return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat) |
1210 if not successors: |
1598 |
1211 ui.warn(_('parent is obsolete without successors; ' + |
1599 # For the progress bar to show |
1212 'likely killed\n')) |
1600 count = len(revs) |
1213 return 2 |
1601 # Order the revisions |
1214 |
1602 if targetcat == 'unstable': |
1215 elif len(successors) > 1: |
1603 revs = _orderrevs(repo, revs) |
1216 ui.warn(_('parent is obsolete with multiple successors:\n')) |
1604 for rev in revs: |
1217 |
1605 progresscb() |
1218 for ctx in sorted(successors, key=lambda ctx: ctx.rev()): |
1606 _solveone(ui, repo, repo[rev], dryrunopt, confirmopt, |
1219 displayer.show(ctx) |
1607 progresscb, targetcat) |
1220 |
1608 seen += 1 |
1221 return 2 |
1609 progresscb() |
1222 |
1610 _cleanup(ui, repo, startnode, showprogress) |
1223 else: |
1611 |
1224 ctx = successors.pop() |
1612 def _possibledestination(repo, rev): |
1225 |
1613 """return all changesets that may be a new parent for REV""" |
1226 ui.status(_('update:')) |
1614 tonode = repo.changelog.node |
1227 if not ui.quiet: |
1615 parents = repo.changelog.parentrevs |
1228 displayer.show(ctx) |
1616 torev = repo.changelog.rev |
1229 |
1617 dest = set() |
1230 if dryrunopt: |
1618 tovisit = list(parents(rev)) |
1231 return 0 |
1619 while tovisit: |
1232 else: |
1620 r = tovisit.pop() |
1233 res = hg.update(repo, ctx.rev()) |
1621 succsets = obsolete.successorssets(repo, tonode(r)) |
1234 if ctx != startnode: |
1622 if not succsets: |
1235 ui.status(_('working directory is now at %s\n') % ctx) |
1623 tovisit.extend(parents(r)) |
1236 return res |
|
1237 |
|
1238 troubled = repo.revs('troubled()') |
|
1239 if troubled: |
|
1240 ui.write_err(_('nothing to evolve here\n')) |
|
1241 ui.status(_('(%i troubled changesets, do you want --any ?)\n') |
|
1242 % len(troubled)) |
|
1243 return 2 |
|
1244 else: |
1624 else: |
1245 ui.write_err(_('no troubled changesets\n')) |
1625 # We should probably pick only one destination from split |
1246 return 1 |
1626 # (case where '1 < len(ss)'), This could be the currently tipmost |
1247 |
1627 # but logic is less clear when result of the split are now on |
1248 def progresscb(): |
1628 # multiple branches. |
1249 if allopt: |
1629 for ss in succsets: |
1250 ui.progress('evolve', seen, unit='changesets', total=count) |
1630 for n in ss: |
1251 seen = 1 |
1631 dest.add(torev(n)) |
1252 count = allopt and _counttroubled(ui, repo) or 1 |
1632 return dest |
1253 |
1633 |
1254 while tro is not None: |
1634 def _aspiringchildren(repo, revs): |
1255 progresscb() |
1635 """Return a list of changectx which can be stabilized on top of pctx or |
1256 wlock = lock = tr = None |
1636 one of its descendants. Empty list if none can be found.""" |
1257 try: |
1637 target = set(revs) |
1258 wlock = repo.wlock() |
1638 result = [] |
1259 lock = repo.lock() |
1639 for r in repo.revs('unstable() - %ld', revs): |
1260 tr = repo.transaction("evolve") |
1640 dest = _possibledestination(repo, r) |
1261 result = _evolveany(ui, repo, tro, dryrunopt, confirmopt, |
1641 if target & dest: |
1262 progresscb=progresscb) |
1642 result.append(r) |
1263 tr.close() |
1643 return result |
1264 finally: |
1644 |
1265 lockmod.release(tr, lock, wlock) |
1645 def _aspiringdescendant(repo, revs): |
1266 progresscb() |
1646 """Return a list of changectx which can be stabilized on top of pctx or |
1267 seen += 1 |
1647 one of its descendants recursively. Empty list if none can be found.""" |
1268 if not allopt: |
1648 target = set(revs) |
1269 if repo['.'] != startnode: |
1649 result = set(target) |
1270 ui.status(_('working directory is now at %s\n') % repo['.']) |
1650 paths = collections.defaultdict(set) |
1271 return result |
1651 for r in repo.revs('unstable() - %ld', revs): |
1272 progresscb() |
1652 for d in _possibledestination(repo, r): |
1273 tro = _picknexttroubled(ui, repo, anyopt or allopt) |
1653 paths[d].add(r) |
1274 |
1654 |
1275 if allopt: |
1655 result = set(target) |
1276 ui.progress('evolve', None) |
1656 tovisit = list(revs) |
1277 |
1657 while tovisit: |
1278 if repo['.'] != startnode: |
1658 base = tovisit.pop() |
1279 ui.status(_('working directory is now at %s\n') % repo['.']) |
1659 for unstable in paths[base]: |
1280 |
1660 if unstable not in result: |
1281 |
1661 tovisit.append(unstable) |
1282 def _evolveany(ui, repo, tro, dryrunopt, confirmopt, progresscb): |
1662 result.add(unstable) |
1283 repo = repo.unfiltered() |
1663 return sorted(result - target) |
1284 tro = repo[tro.rev()] |
|
1285 cmdutil.bailifchanged(repo) |
|
1286 troubles = tro.troubles() |
|
1287 if 'unstable' in troubles: |
|
1288 return _solveunstable(ui, repo, tro, dryrunopt, confirmopt, progresscb) |
|
1289 elif 'bumped' in troubles: |
|
1290 return _solvebumped(ui, repo, tro, dryrunopt, confirmopt, progresscb) |
|
1291 elif 'divergent' in troubles: |
|
1292 return _solvedivergent(ui, repo, tro, dryrunopt, confirmopt, |
|
1293 progresscb) |
|
1294 else: |
|
1295 assert False # WHAT? unknown troubles |
|
1296 |
|
1297 def _counttroubled(ui, repo): |
|
1298 """Count the amount of troubled changesets""" |
|
1299 troubled = set() |
|
1300 troubled.update(getrevs(repo, 'unstable')) |
|
1301 troubled.update(getrevs(repo, 'bumped')) |
|
1302 troubled.update(getrevs(repo, 'divergent')) |
|
1303 return len(troubled) |
|
1304 |
|
1305 def _picknexttroubled(ui, repo, pickany=False, progresscb=None): |
|
1306 """Pick a the next trouble changeset to solve""" |
|
1307 if progresscb: progresscb() |
|
1308 tro = _stabilizableunstable(repo, repo['.']) |
|
1309 if tro is None: |
|
1310 wdp = repo['.'] |
|
1311 if 'divergent' in wdp.troubles(): |
|
1312 tro = wdp |
|
1313 if tro is None and pickany: |
|
1314 troubled = list(repo.set('unstable()')) |
|
1315 if not troubled: |
|
1316 troubled = list(repo.set('bumped()')) |
|
1317 if not troubled: |
|
1318 troubled = list(repo.set('divergent()')) |
|
1319 if troubled: |
|
1320 tro = troubled[0] |
|
1321 |
|
1322 return tro |
|
1323 |
|
1324 def _stabilizableunstable(repo, pctx): |
|
1325 """Return a changectx for an unstable changeset which can be |
|
1326 stabilized on top of pctx or one of its descendants. None if none |
|
1327 can be found. |
|
1328 """ |
|
1329 def selfanddescendants(repo, pctx): |
|
1330 yield pctx |
|
1331 for prec in repo.set('allprecursors(%d)', pctx): |
|
1332 yield prec |
|
1333 for ctx in pctx.descendants(): |
|
1334 yield ctx |
|
1335 for prec in repo.set('allprecursors(%d)', ctx): |
|
1336 yield prec |
|
1337 |
|
1338 # Look for an unstable which can be stabilized as a child of |
|
1339 # node. The unstable must be a child of one of node predecessors. |
|
1340 directdesc = set([pctx.rev()]) |
|
1341 for ctx in selfanddescendants(repo, pctx): |
|
1342 for child in ctx.children(): |
|
1343 if ctx.rev() in directdesc and not child.obsolete(): |
|
1344 directdesc.add(child.rev()) |
|
1345 elif child.unstable(): |
|
1346 return child |
|
1347 return None |
|
1348 |
1664 |
1349 def _solveunstable(ui, repo, orig, dryrun=False, confirm=False, |
1665 def _solveunstable(ui, repo, orig, dryrun=False, confirm=False, |
1350 progresscb=None): |
1666 progresscb=None): |
1351 """Stabilize a unstable changeset""" |
1667 """Stabilize a unstable changeset""" |
1352 obs = orig.parents()[0] |
1668 obs = orig.parents()[0] |
1669 if not obs.obsolete() and len(orig.parents()) == 2: |
|
1670 obs = orig.parents()[1] # second parent is obsolete ? |
|
1671 |
|
1353 if not obs.obsolete(): |
1672 if not obs.obsolete(): |
1354 obs = orig.parents()[1] # second parent is obsolete ? |
1673 ui.warn("cannot solve instability of %s, skipping\n" % orig) |
1355 assert obs.obsolete() |
1674 return False |
1356 newer = obsolete.successorssets(repo, obs.node()) |
1675 newer = obsolete.successorssets(repo, obs.node()) |
1357 # search of a parent which is not killed |
1676 # search of a parent which is not killed |
1358 while not newer or newer == [()]: |
1677 while not newer or newer == [()]: |
1359 ui.debug("stabilize target %s is plain dead," |
1678 ui.debug("stabilize target %s is plain dead," |
1360 " trying to stabilize on its parent\n" % |
1679 " trying to stabilize on its parent\n" % |
1361 obs) |
1680 obs) |
1362 obs = obs.parents()[0] |
1681 obs = obs.parents()[0] |
1363 newer = obsolete.successorssets(repo, obs.node()) |
1682 newer = obsolete.successorssets(repo, obs.node()) |
1364 if len(newer) > 1: |
1683 if len(newer) > 1: |
1365 raise util.Abort(_("conflict rewriting. can't choose destination\n")) |
1684 msg = _("skipping %s: divergent rewriting. can't choose destination\n" % obs) |
1685 ui.write_err(msg) |
|
1686 return 2 |
|
1366 targets = newer[0] |
1687 targets = newer[0] |
1367 assert targets |
1688 assert targets |
1368 if len(targets) > 1: |
1689 if len(targets) > 1: |
1369 raise util.Abort(_("does not handle split parents yet\n")) |
1690 msg = _("does not handle split parents yet\n") |
1691 ui.write_err(msg) |
|
1370 return 2 |
1692 return 2 |
1371 target = targets[0] |
1693 target = targets[0] |
1372 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1694 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1373 target = repo[target] |
1695 target = repo[target] |
1374 if not ui.quiet or confirm: |
1696 if not ui.quiet or confirm: |
1397 raise |
1719 raise |
1398 |
1720 |
1399 def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False, |
1721 def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False, |
1400 progresscb=None): |
1722 progresscb=None): |
1401 """Stabilize a bumped changeset""" |
1723 """Stabilize a bumped changeset""" |
1724 repo = repo.unfiltered() |
|
1725 bumped = repo[bumped.rev()] |
|
1402 # For now we deny bumped merge |
1726 # For now we deny bumped merge |
1403 if len(bumped.parents()) > 1: |
1727 if len(bumped.parents()) > 1: |
1404 raise util.Abort('late comer stabilization is confused by bumped' |
1728 msg = _('skipping %s : we do not handle merge yet\n' % bumped) |
1405 ' %s being a merge' % bumped) |
1729 ui.write_err(msg) |
1730 return 2 |
|
1406 prec = repo.set('last(allprecursors(%d) and public())', bumped).next() |
1731 prec = repo.set('last(allprecursors(%d) and public())', bumped).next() |
1407 # For now we deny target merge |
1732 # For now we deny target merge |
1408 if len(prec.parents()) > 1: |
1733 if len(prec.parents()) > 1: |
1409 raise util.Abort('late comer evolution is confused by precursors' |
1734 msg = _('skipping: %s: public version is a merge, this not handled yet\n' % prec) |
1410 ' %s being a merge' % prec) |
1735 ui.write_err(msg) |
1736 return 2 |
|
1411 |
1737 |
1412 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1738 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1413 if not ui.quiet or confirm: |
1739 if not ui.quiet or confirm: |
1414 repo.ui.write(_('recreate:')) |
1740 repo.ui.write(_('recreate:')) |
1415 displayer.show(bumped) |
1741 displayer.show(bumped) |
1449 # Create the new commit context |
1775 # Create the new commit context |
1450 repo.ui.status(_('computing new diff\n')) |
1776 repo.ui.status(_('computing new diff\n')) |
1451 files = set() |
1777 files = set() |
1452 copied = copies.pathcopies(prec, bumped) |
1778 copied = copies.pathcopies(prec, bumped) |
1453 precmanifest = prec.manifest() |
1779 precmanifest = prec.manifest() |
1780 # 3.3.2 needs a list. |
|
1781 # future 3.4 don't detect the size change during iteration |
|
1782 # this is fishy |
|
1454 for key, val in list(bumped.manifest().iteritems()): |
1783 for key, val in list(bumped.manifest().iteritems()): |
1455 precvalue = precmanifest.get(key, None) |
1784 precvalue = precmanifest.get(key, None) |
1456 if precvalue is not None: |
1785 if precvalue is not None: |
1457 del precmanifest[key] |
1786 del precmanifest[key] |
1458 if precvalue != val: |
1787 if precvalue != val: |
1500 repo.dirstate.setparents(newid, node.nullid) |
1829 repo.dirstate.setparents(newid, node.nullid) |
1501 repo.dirstate.endparentchange() |
1830 repo.dirstate.endparentchange() |
1502 |
1831 |
1503 def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False, |
1832 def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False, |
1504 progresscb=None): |
1833 progresscb=None): |
1834 repo = repo.unfiltered() |
|
1835 divergent = repo[divergent.rev()] |
|
1505 base, others = divergentdata(divergent) |
1836 base, others = divergentdata(divergent) |
1506 if len(others) > 1: |
1837 if len(others) > 1: |
1507 othersstr = "[%s]" % (','.join([str(i) for i in others])) |
1838 othersstr = "[%s]" % (','.join([str(i) for i in others])) |
1508 hint = ("changeset %d is divergent with a changeset that got splitted " |
1839 msg = _("skipping %d:divergent with a changeset that got splitted into multiple ones:\n" |
1509 "| into multiple ones:\n[%s]\n" |
1840 "|[%s]\n" |
1510 "| This is not handled by automatic evolution yet\n" |
1841 "| This is not handled by automatic evolution yet\n" |
1511 "| You have to fallback to manual handling with commands " |
1842 "| You have to fallback to manual handling with commands " |
1512 "such as:\n" |
1843 "such as:\n" |
1513 "| - hg touch -D\n" |
1844 "| - hg touch -D\n" |
1514 "| - hg prune\n" |
1845 "| - hg prune\n" |
1515 "| \n" |
1846 "| \n" |
1516 "| You should contact your local evolution Guru for help.\n" |
1847 "| You should contact your local evolution Guru for help.\n" |
1517 % (divergent, othersstr)) |
1848 % (divergent, othersstr)) |
1518 raise util.Abort("we do not handle divergence with split yet", |
1849 ui.write_err(msg) |
1519 hint=hint) |
1850 return 2 |
1520 other = others[0] |
1851 other = others[0] |
1521 if divergent.phase() <= phases.public: |
1852 if divergent.phase() <= phases.public: |
1522 raise util.Abort("we can't resolve this conflict from the public side", |
1853 msg = _("skipping %s: we can't resolve divergence from the public side\n") % divergent |
1523 hint="%s is public, try from %s" % (divergent, other)) |
1854 ui.write_err(msg) |
1855 hint = _("(%s is public, try from %s)\n" % (divergent, other)) |
|
1856 ui.write_err(hint) |
|
1857 return 2 |
|
1524 if len(other.parents()) > 1: |
1858 if len(other.parents()) > 1: |
1525 raise util.Abort("divergent changeset can't be a merge (yet)", |
1859 msg = _("skipping %s: divergent changeset can't be a merge (yet)\n" % divergent) |
1526 hint="You have to fallback to solving this by hand...\n" |
1860 ui.write_err(msg) |
1527 "| This probably means redoing the merge and using " |
1861 hint = _("You have to fallback to solving this by hand...\n" |
1528 "| `hg prune` to kill older version.") |
1862 "| This probably means redoing the merge and using \n" |
1863 "| `hg prune` to kill older version.\n") |
|
1864 ui.write_err(hint) |
|
1865 return 2 |
|
1529 if other.p1() not in divergent.parents(): |
1866 if other.p1() not in divergent.parents(): |
1530 raise util.Abort("parents are not common (not handled yet)", |
1867 msg = _("skipping %s: have a different parent than %s (not handled yet)\n") % (divergent, other) |
1531 hint="| %(d)s, %(o)s are not based on the same changeset.\n" |
1868 hint = _("| %(d)s, %(o)s are not based on the same changeset.\n" |
1532 "| With the current state of its implementation, \n" |
1869 "| With the current state of its implementation, \n" |
1533 "| evolve does not work in that case.\n" |
1870 "| evolve does not work in that case.\n" |
1534 "| rebase one of them next to the other and run \n" |
1871 "| rebase one of them next to the other and run \n" |
1535 "| this command again.\n" |
1872 "| this command again.\n" |
1536 "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n" |
1873 "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n" |
1537 "| - or: hg rebase --dest 'p1(%(o)s)' -r %(d)s" |
1874 "| - or: hg rebase --dest 'p1(%(o)s)' -r %(d)s\n" |
1538 % {'d': divergent, 'o': other}) |
1875 % {'d': divergent, 'o': other}) |
1876 ui.write_err(msg) |
|
1877 ui.write_err(hint) |
|
1878 return 2 |
|
1539 |
1879 |
1540 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1880 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1541 if not ui.quiet or confirm: |
1881 if not ui.quiet or confirm: |
1542 ui.write(_('merge:')) |
1882 ui.write(_('merge:')) |
1543 displayer.show(divergent) |
1883 displayer.show(divergent) |
1623 |
1963 |
1624 shorttemplate = '[{rev}] {desc|firstline}\n' |
1964 shorttemplate = '[{rev}] {desc|firstline}\n' |
1625 |
1965 |
1626 @command('^previous', |
1966 @command('^previous', |
1627 [('B', 'move-bookmark', False, |
1967 [('B', 'move-bookmark', False, |
1628 _('Move active bookmark after update'))], |
1968 _('Move active bookmark after update')), |
1969 ('', 'merge', False, _('bring uncommited change along'))], |
|
1629 '[-B]') |
1970 '[-B]') |
1630 def cmdprevious(ui, repo, **opts): |
1971 def cmdprevious(ui, repo, **opts): |
1631 """update to parent and display summary lines""" |
1972 """update to parent and display summary lines""" |
1632 wkctx = repo[None] |
1973 wkctx = repo[None] |
1633 wparents = wkctx.parents() |
1974 wparents = wkctx.parents() |
1634 if len(wparents) != 1: |
1975 if len(wparents) != 1: |
1635 raise util.Abort('merge in progress') |
1976 raise util.Abort('merge in progress') |
1977 if not opts['merge']: |
|
1978 try: |
|
1979 cmdutil.bailifchanged(repo) |
|
1980 except error.Abort, exc: |
|
1981 exc.hint = _('do you want --merge?') |
|
1982 raise |
|
1636 |
1983 |
1637 parents = wparents[0].parents() |
1984 parents = wparents[0].parents() |
1638 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1985 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1639 if len(parents) == 1: |
1986 if len(parents) == 1: |
1640 p = parents[0] |
1987 p = parents[0] |
1655 ui.warn(_('multiple parents, explicitly update to one\n')) |
2002 ui.warn(_('multiple parents, explicitly update to one\n')) |
1656 return 1 |
2003 return 1 |
1657 |
2004 |
1658 @command('^next', |
2005 @command('^next', |
1659 [('B', 'move-bookmark', False, |
2006 [('B', 'move-bookmark', False, |
1660 _('Move active bookmark after update'))], |
2007 _('Move active bookmark after update')), |
2008 ('', 'merge', False, _('bring uncommited change along')), |
|
2009 ('', 'evolve', False, _('evolve the next changeset if necessary'))], |
|
1661 '[-B]') |
2010 '[-B]') |
1662 def cmdnext(ui, repo, **opts): |
2011 def cmdnext(ui, repo, **opts): |
1663 """update to child and display summary lines""" |
2012 """update to next child |
2013 |
|
2014 You can use the --evolve flag to get unstable children evolved on demand. |
|
2015 |
|
2016 The summary line of the destination is displayed for clarity""" |
|
1664 wkctx = repo[None] |
2017 wkctx = repo[None] |
1665 wparents = wkctx.parents() |
2018 wparents = wkctx.parents() |
1666 if len(wparents) != 1: |
2019 if len(wparents) != 1: |
1667 raise util.Abort('merge in progress') |
2020 raise util.Abort('merge in progress') |
2021 if not opts['merge']: |
|
2022 try: |
|
2023 cmdutil.bailifchanged(repo) |
|
2024 except error.Abort, exc: |
|
2025 exc.hint = _('do you want --merge?') |
|
2026 raise |
|
1668 |
2027 |
1669 children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()] |
2028 children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()] |
1670 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
2029 displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) |
1671 if not children: |
|
1672 ui.warn(_('no non-obsolete children\n')) |
|
1673 return 1 |
|
1674 if len(children) == 1: |
2030 if len(children) == 1: |
1675 c = children[0] |
2031 c = children[0] |
1676 bm = bmactive(repo) |
2032 bm = bmactive(repo) |
1677 shouldmove = opts.get('move_bookmark') and bm is not None |
2033 shouldmove = opts.get('move_bookmark') and bm is not None |
1678 ret = hg.update(repo, c.rev()) |
2034 ret = hg.update(repo, c.rev()) |
1681 repo._bookmarks[bm] = c.node() |
2037 repo._bookmarks[bm] = c.node() |
1682 repo._bookmarks.write() |
2038 repo._bookmarks.write() |
1683 else: |
2039 else: |
1684 bmdeactivate(repo) |
2040 bmdeactivate(repo) |
1685 displayer.show(c) |
2041 displayer.show(c) |
1686 return 0 |
2042 result = 0 |
1687 else: |
2043 elif children: |
2044 ui.warn("ambigious next changeset:\n") |
|
1688 for c in children: |
2045 for c in children: |
1689 displayer.show(c) |
2046 displayer.show(c) |
1690 ui.warn(_('multiple non-obsolete children, ' |
2047 ui.warn(_('explicitly update to one of them\n')) |
1691 'explicitly update to one of them\n')) |
2048 result = 1 |
2049 else: |
|
2050 aspchildren = _aspiringchildren(repo, [repo['.'].rev()]) |
|
2051 if not opts['evolve']: |
|
2052 ui.warn(_('no children\n')) |
|
2053 if aspchildren: |
|
2054 msg = _('(%i unstable changesets to be evolved here, ' |
|
2055 'do you want --evolve?)\n') |
|
2056 ui.warn(msg % len(aspchildren)) |
|
2057 result = 1 |
|
2058 elif 1 < len(aspchildren): |
|
2059 ui.warn("ambigious next (unstable) changeset:\n") |
|
2060 for c in aspchildren: |
|
2061 displayer.show(repo[c]) |
|
2062 ui.warn(_('(run "hg evolve --rev REV" on one of them)\n')) |
|
2063 return 1 |
|
2064 else: |
|
2065 cmdutil.bailifchanged(repo) |
|
2066 result = _solveone(ui, repo, repo[aspchildren[0]], False, |
|
2067 False, lambda:None, category='unstable') |
|
2068 if not result: |
|
2069 ui.status(_('working directory now at %s\n') % repo['.']) |
|
2070 return result |
|
1692 return 1 |
2071 return 1 |
2072 return result |
|
1693 |
2073 |
1694 def _reachablefrombookmark(repo, revs, mark): |
2074 def _reachablefrombookmark(repo, revs, mark): |
1695 """filter revisions and bookmarks reachable from the given bookmark |
2075 """filter revisions and bookmarks reachable from the given bookmark |
1696 yoinked from mq.py |
2076 yoinked from mq.py |
1697 """ |
2077 """ |
1738 |
2118 |
1739 @command('^prune|obsolete|kill', |
2119 @command('^prune|obsolete|kill', |
1740 [('n', 'new', [], _("successor changeset (DEPRECATED)")), |
2120 [('n', 'new', [], _("successor changeset (DEPRECATED)")), |
1741 ('s', 'succ', [], _("successor changeset")), |
2121 ('s', 'succ', [], _("successor changeset")), |
1742 ('r', 'rev', [], _("revisions to prune")), |
2122 ('r', 'rev', [], _("revisions to prune")), |
2123 ('k', 'keep', None, _("does not modify working copy during prune")), |
|
1743 ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")), |
2124 ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")), |
1744 ('B', 'bookmark', '', _("remove revs only reachable from given" |
2125 ('B', 'bookmark', '', _("remove revs only reachable from given" |
1745 " bookmark"))] + metadataopts, |
2126 " bookmark"))] + metadataopts, |
1746 _('[OPTION] [-r] REV...')) |
2127 _('[OPTION] [-r] REV...')) |
1747 # -U --noupdate option to prevent wc update and or bookmarks update ? |
2128 # -U --noupdate option to prevent wc update and or bookmarks update ? |
1777 _deletebookmark(ui, marks, bookmark) |
2158 _deletebookmark(ui, marks, bookmark) |
1778 |
2159 |
1779 if not revs: |
2160 if not revs: |
1780 raise util.Abort(_('nothing to prune')) |
2161 raise util.Abort(_('nothing to prune')) |
1781 |
2162 |
1782 wlock = lock = None |
2163 wlock = lock = tr = None |
1783 try: |
2164 try: |
1784 wlock = repo.wlock() |
2165 wlock = repo.wlock() |
1785 lock = repo.lock() |
2166 lock = repo.lock() |
2167 tr = repo.transaction('prune') |
|
1786 # defines pruned changesets |
2168 # defines pruned changesets |
1787 precs = [] |
2169 precs = [] |
1788 revs.sort() |
2170 revs.sort() |
1789 for p in revs: |
2171 for p in revs: |
1790 cp = repo[p] |
2172 cp = repo[p] |
1794 hint='see "hg help phases" for details') |
2176 hint='see "hg help phases" for details') |
1795 precs.append(cp) |
2177 precs.append(cp) |
1796 if not precs: |
2178 if not precs: |
1797 raise util.Abort('nothing to prune') |
2179 raise util.Abort('nothing to prune') |
1798 |
2180 |
2181 if not obsolete.isenabled(repo, obsolete.allowunstableopt): |
|
2182 if repo.revs("(%ld::) - %ld", revs, revs): |
|
2183 raise util.Abort(_("cannot prune in the middle of a stack")) |
|
2184 |
|
1799 # defines successors changesets |
2185 # defines successors changesets |
1800 sucs = scmutil.revrange(repo, succs) |
2186 sucs = scmutil.revrange(repo, succs) |
1801 sucs.sort() |
2187 sucs.sort() |
1802 sucs = tuple(repo[n] for n in sucs) |
2188 sucs = tuple(repo[n] for n in sucs) |
1803 if not biject and len(sucs) > 1 and len(precs) > 1: |
2189 if not biject and len(sucs) > 1 and len(precs) > 1: |
1811 |
2197 |
1812 relations = [(p, sucs) for p in precs] |
2198 relations = [(p, sucs) for p in precs] |
1813 if biject: |
2199 if biject: |
1814 relations = [(p, (s,)) for p, s in zip(precs, sucs)] |
2200 relations = [(p, (s,)) for p, s in zip(precs, sucs)] |
1815 |
2201 |
1816 # create markers |
|
1817 obsolete.createmarkers(repo, relations, metadata=metadata) |
|
1818 |
|
1819 # informs that changeset have been pruned |
|
1820 ui.status(_('%i changesets pruned\n') % len(precs)) |
|
1821 |
|
1822 wdp = repo['.'] |
2202 wdp = repo['.'] |
1823 |
2203 |
1824 if len(sucs) == 1 and len(precs) == 1 and wdp in precs: |
2204 if len(sucs) == 1 and len(precs) == 1 and wdp in precs: |
1825 # '.' killed, so update to the successor |
2205 # '.' killed, so update to the successor |
1826 newnode = sucs[0] |
2206 newnode = sucs[0] |
1827 else: |
2207 else: |
1828 # update to an unkilled parent |
2208 # update to an unkilled parent |
1829 newnode = wdp |
2209 newnode = wdp |
1830 |
2210 |
1831 while newnode.obsolete(): |
2211 while newnode in precs or newnode.obsolete(): |
1832 newnode = newnode.parents()[0] |
2212 newnode = newnode.parents()[0] |
1833 |
2213 |
2214 |
|
1834 if newnode.node() != wdp.node(): |
2215 if newnode.node() != wdp.node(): |
1835 bookactive = bmactive(repo) |
2216 if opts.get('keep', False): |
1836 # Active bookmark that we don't want to delete (with -B option) |
2217 # This is largely the same as the implementation in |
1837 # we deactivate and move it before the update and reactivate it |
2218 # strip.stripcmd(). We might want to refactor this somewhere |
1838 # after |
2219 # common at some point. |
1839 movebookmark = bookactive and not bookmark |
2220 |
1840 if movebookmark: |
2221 # only reset the dirstate for files that would actually change |
1841 bmdeactivate(repo) |
2222 # between the working context and uctx |
1842 repo._bookmarks[bookactive] = newnode.node() |
2223 descendantrevs = repo.revs("%d::." % newnode.rev()) |
1843 repo._bookmarks.write() |
2224 changedfiles = [] |
1844 commands.update(ui, repo, newnode.rev()) |
2225 for rev in descendantrevs: |
1845 ui.status(_('working directory now at %s\n') % newnode) |
2226 # blindly reset the files, regardless of what actually changed |
1846 if movebookmark: |
2227 changedfiles.extend(repo[rev].files()) |
1847 bmactivate(repo, bookactive) |
2228 |
2229 # reset files that only changed in the dirstate too |
|
2230 dirstate = repo.dirstate |
|
2231 dirchanges = [f for f in dirstate if dirstate[f] != 'n'] |
|
2232 changedfiles.extend(dirchanges) |
|
2233 repo.dirstate.rebuild(newnode.node(), newnode.manifest(), changedfiles) |
|
2234 repo.dirstate.write() |
|
2235 else: |
|
2236 bookactive = bmactive(repo) |
|
2237 # Active bookmark that we don't want to delete (with -B option) |
|
2238 # we deactivate and move it before the update and reactivate it |
|
2239 # after |
|
2240 movebookmark = bookactive and not bookmark |
|
2241 if movebookmark: |
|
2242 bmdeactivate(repo) |
|
2243 repo._bookmarks[bookactive] = newnode.node() |
|
2244 repo._bookmarks.write() |
|
2245 commands.update(ui, repo, newnode.rev()) |
|
2246 ui.status(_('working directory now at %s\n') % newnode) |
|
2247 if movebookmark: |
|
2248 bmactivate(repo, bookactive) |
|
1848 |
2249 |
1849 # update bookmarks |
2250 # update bookmarks |
1850 if bookmark: |
2251 if bookmark: |
1851 _deletebookmark(ui, marks, bookmark) |
2252 _deletebookmark(ui, marks, bookmark) |
2253 |
|
2254 # create markers |
|
2255 obsolete.createmarkers(repo, relations, metadata=metadata) |
|
2256 |
|
2257 # informs that changeset have been pruned |
|
2258 ui.status(_('%i changesets pruned\n') % len(precs)) |
|
2259 |
|
1852 for ctx in repo.unfiltered().set('bookmark() and %ld', precs): |
2260 for ctx in repo.unfiltered().set('bookmark() and %ld', precs): |
1853 # used to be: |
2261 # used to be: |
1854 # |
2262 # |
1855 # ldest = list(repo.set('max((::%d) - obsolete())', ctx)) |
2263 # ldest = list(repo.set('max((::%d) - obsolete())', ctx)) |
1856 # if ldest: |
2264 # if ldest: |
1861 for dest in ctx.ancestors(): |
2269 for dest in ctx.ancestors(): |
1862 if not dest.obsolete(): |
2270 if not dest.obsolete(): |
1863 updatebookmarks = _bookmarksupdater(repo, ctx.node()) |
2271 updatebookmarks = _bookmarksupdater(repo, ctx.node()) |
1864 updatebookmarks(dest.node()) |
2272 updatebookmarks(dest.node()) |
1865 break |
2273 break |
2274 |
|
2275 tr.close() |
|
1866 finally: |
2276 finally: |
1867 lockmod.release(lock, wlock) |
2277 lockmod.release(tr, lock, wlock) |
1868 |
2278 |
1869 @command('amend|refresh', |
2279 @command('amend|refresh', |
1870 [('A', 'addremove', None, |
2280 [('A', 'addremove', None, |
1871 _('mark new/missing files as added/removed before committing')), |
2281 _('mark new/missing files as added/removed before committing')), |
1872 ('e', 'edit', False, _('invoke editor on commit messages')), |
2282 ('e', 'edit', False, _('invoke editor on commit messages')), |
2051 rev = scmutil.revsingle(repo, opts.get('rev')) |
2461 rev = scmutil.revsingle(repo, opts.get('rev')) |
2052 ctx = repo[None] |
2462 ctx = repo[None] |
2053 if ctx.p1() == rev or ctx.p2() == rev: |
2463 if ctx.p1() == rev or ctx.p2() == rev: |
2054 raise util.Abort(_("cannot uncommit to parent changeset")) |
2464 raise util.Abort(_("cannot uncommit to parent changeset")) |
2055 |
2465 |
2466 onahead = old.rev() in repo.changelog.headrevs() |
|
2467 disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt) |
|
2468 if disallowunstable and not onahead: |
|
2469 raise util.Abort(_("cannot uncommit in the middle of a stack")) |
|
2470 |
|
2056 # Recommit the filtered changeset |
2471 # Recommit the filtered changeset |
2057 tr = repo.transaction('uncommit') |
2472 tr = repo.transaction('uncommit') |
2058 newid = None |
2473 newid = None |
2059 if (pats or opts.get('include') or opts.get('exclude') |
2474 includeorexclude = opts.get('include') or opts.get('exclude') |
2060 or opts.get('all')): |
2475 if (pats or includeorexclude or opts.get('all')): |
2061 match = scmutil.match(old, pats, opts) |
2476 match = scmutil.match(old, pats, opts) |
2062 newid = _commitfiltered(repo, old, match, target=rev) |
2477 newid = _commitfiltered(repo, old, match, target=rev) |
2063 if newid is None: |
2478 if newid is None: |
2064 raise util.Abort(_('nothing to uncommit'), |
2479 raise util.Abort(_('nothing to uncommit'), |
2065 hint=_("use --all to uncommit all files")) |
2480 hint=_("use --all to uncommit all files")) |
2104 if oldbookmarks: |
2519 if oldbookmarks: |
2105 repo._bookmarks.write() |
2520 repo._bookmarks.write() |
2106 return result |
2521 return result |
2107 finally: |
2522 finally: |
2108 lockmod.release(lock, wlock) |
2523 lockmod.release(lock, wlock) |
2524 |
|
2525 @eh.wrapcommand('strip', extension='strip', opts=[ |
|
2526 ('', 'bundle', None, _("delete the commit entirely and move it to a " |
|
2527 "backup bundle")), |
|
2528 ]) |
|
2529 def stripwrapper(orig, ui, repo, *revs, **kwargs): |
|
2530 if (not ui.configbool('experimental', 'prunestrip') or |
|
2531 kwargs.get('bundle', False)): |
|
2532 return orig(ui, repo, *revs, **kwargs) |
|
2533 |
|
2534 if kwargs.get('force'): |
|
2535 ui.warn(_("warning: --force has no effect during strip with evolve " |
|
2536 "enabled\n")) |
|
2537 if kwargs.get('no_backup', False): |
|
2538 ui.warn(_("warning: --no-backup has no effect during strips with " |
|
2539 "evolve enabled\n")) |
|
2540 |
|
2541 revs = list(revs) + kwargs.pop('rev', []) |
|
2542 revs = set(scmutil.revrange(repo, revs)) |
|
2543 revs = repo.revs("(%ld)::", revs) |
|
2544 kwargs['rev'] = [] |
|
2545 kwargs['new'] = [] |
|
2546 kwargs['succ'] = [] |
|
2547 kwargs['biject'] = False |
|
2548 return cmdprune(ui, repo, *revs, **kwargs) |
|
2109 |
2549 |
2110 @command('^touch', |
2550 @command('^touch', |
2111 [('r', 'rev', [], 'revision to update'), |
2551 [('r', 'rev', [], 'revision to update'), |
2112 ('D', 'duplicate', False, |
2552 ('D', 'duplicate', False, |
2113 'do not mark the new revision as successor of the old one')], |
2553 'do not mark the new revision as successor of the old one')], |
2231 heads = repo.revs('heads(%ld)', revs) |
2671 heads = repo.revs('heads(%ld)', revs) |
2232 if len(heads) > 1: |
2672 if len(heads) > 1: |
2233 raise util.Abort(_("cannot fold non-linear revisions " |
2673 raise util.Abort(_("cannot fold non-linear revisions " |
2234 "(multiple heads given)")) |
2674 "(multiple heads given)")) |
2235 head = repo[heads.first()] |
2675 head = repo[heads.first()] |
2676 disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt) |
|
2677 if disallowunstable: |
|
2678 if repo.revs("(%ld::) - %ld", revs, revs): |
|
2679 raise util.Abort(_("cannot fold chain not ending with a head "\ |
|
2680 "or with branching")) |
|
2236 wlock = lock = None |
2681 wlock = lock = None |
2237 try: |
2682 try: |
2238 wlock = repo.wlock() |
2683 wlock = repo.wlock() |
2239 lock = repo.lock() |
2684 lock = repo.lock() |
2240 tr = repo.transaction('touch') |
2685 tr = repo.transaction('touch') |
2297 lockmod.release(lock, wlock) |
2742 lockmod.release(lock, wlock) |
2298 |
2743 |
2299 @eh.extsetup |
2744 @eh.extsetup |
2300 def oldevolveextsetup(ui): |
2745 def oldevolveextsetup(ui): |
2301 for cmd in ['kill', 'uncommit', 'touch', 'fold']: |
2746 for cmd in ['kill', 'uncommit', 'touch', 'fold']: |
2302 entry = extensions.wrapcommand(cmdtable, cmd, |
2747 try: |
2303 warnobserrors) |
2748 entry = extensions.wrapcommand(cmdtable, cmd, |
2749 warnobserrors) |
|
2750 except error.UnknownCommand: |
|
2751 # Commands may be disabled |
|
2752 continue |
|
2304 |
2753 |
2305 entry = cmdutil.findcmd('commit', commands.table)[1] |
2754 entry = cmdutil.findcmd('commit', commands.table)[1] |
2306 entry[1].append(('o', 'obsolete', [], |
2755 entry[1].append(('o', 'obsolete', [], |
2307 _("make commit obsolete this revision (DEPRECATED)"))) |
2756 _("make commit obsolete this revision (DEPRECATED)"))) |
2308 entry = cmdutil.findcmd('graft', commands.table)[1] |
2757 entry = cmdutil.findcmd('graft', commands.table)[1] |
2327 topic = 'obsmarkers exchange' |
2776 topic = 'obsmarkers exchange' |
2328 if ui.configbool('experimental', 'verbose-obsolescence-exchange', False): |
2777 if ui.configbool('experimental', 'verbose-obsolescence-exchange', False): |
2329 topic = 'OBSEXC' |
2778 topic = 'OBSEXC' |
2330 ui.progress(topic, *args, **kwargs) |
2779 ui.progress(topic, *args, **kwargs) |
2331 |
2780 |
2332 if getattr(exchange, '_pushdiscoveryobsmarkers', None) is not None: |
2781 @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers') |
2333 @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers') |
2782 def _pushdiscoveryobsmarkers(orig, pushop): |
2334 def _pushdiscoveryobsmarkers(orig, pushop): |
2783 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt) |
2335 if (obsolete._enabled |
2784 and pushop.repo.obsstore |
2336 and pushop.repo.obsstore |
2785 and 'obsolete' in pushop.remote.listkeys('namespaces')): |
2337 and 'obsolete' in pushop.remote.listkeys('namespaces')): |
2786 repo = pushop.repo |
2338 repo = pushop.repo |
2787 obsexcmsg(repo.ui, "computing relevant nodes\n") |
2339 obsexcmsg(repo.ui, "computing relevant nodes\n") |
2788 revs = list(repo.revs('::%ln', pushop.futureheads)) |
2340 revs = list(repo.revs('::%ln', pushop.futureheads)) |
2789 unfi = repo.unfiltered() |
2341 unfi = repo.unfiltered() |
2790 cl = unfi.changelog |
2342 cl = unfi.changelog |
2791 if not pushop.remote.capable('_evoext_obshash_0'): |
2343 if not pushop.remote.capable('_evoext_obshash_0'): |
2792 # do not trust core yet |
2344 # do not trust core yet |
2793 # return orig(pushop) |
2345 # return orig(pushop) |
|
2346 nodes = [cl.node(r) for r in revs] |
|
2347 if nodes: |
|
2348 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" |
|
2349 % len(nodes)) |
|
2350 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) |
|
2351 else: |
|
2352 obsexcmsg(repo.ui, "markers already in sync\n") |
|
2353 pushop.outobsmarkers = [] |
|
2354 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) |
|
2355 return |
|
2356 |
|
2357 common = [] |
|
2358 obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" |
|
2359 % len(revs)) |
|
2360 commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) |
|
2361 common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs) |
|
2362 |
|
2363 revs = list(unfi.revs('%ld - (::%ln)', revs, common)) |
|
2364 nodes = [cl.node(r) for r in revs] |
2794 nodes = [cl.node(r) for r in revs] |
2365 if nodes: |
2795 if nodes: |
2366 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" |
2796 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" |
2367 % len(nodes)) |
2797 % len(nodes)) |
2368 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) |
2798 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) |
2369 else: |
2799 else: |
2370 obsexcmsg(repo.ui, "markers already in sync\n") |
2800 obsexcmsg(repo.ui, "markers already in sync\n") |
2371 pushop.outobsmarkers = [] |
2801 pushop.outobsmarkers = [] |
2802 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) |
|
2803 return |
|
2804 |
|
2805 common = [] |
|
2806 obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" |
|
2807 % len(revs)) |
|
2808 commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) |
|
2809 common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs) |
|
2810 |
|
2811 revs = list(unfi.revs('%ld - (::%ln)', revs, common)) |
|
2812 nodes = [cl.node(r) for r in revs] |
|
2813 if nodes: |
|
2814 obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" |
|
2815 % len(nodes)) |
|
2816 pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) |
|
2817 else: |
|
2818 obsexcmsg(repo.ui, "markers already in sync\n") |
|
2819 pushop.outobsmarkers = [] |
|
2372 |
2820 |
2373 @eh.wrapfunction(wireproto, 'capabilities') |
2821 @eh.wrapfunction(wireproto, 'capabilities') |
2374 def discocapabilities(orig, repo, proto): |
2822 def discocapabilities(orig, repo, proto): |
2375 """wrapper to advertise new capability""" |
2823 """wrapper to advertise new capability""" |
2376 caps = orig(repo, proto) |
2824 caps = orig(repo, proto) |
2377 if obsolete._enabled: |
2825 if obsolete.isenabled(repo, obsolete.exchangeopt): |
2378 caps += ' _evoext_obshash_0' |
2826 caps += ' _evoext_obshash_0' |
2379 return caps |
2827 return caps |
2380 |
2828 |
2381 @eh.extsetup |
2829 @eh.extsetup |
2382 def _installobsmarkersdiscovery(ui): |
2830 def _installobsmarkersdiscovery(ui): |
2532 if cgresult == 0: |
2980 if cgresult == 0: |
2533 return |
2981 return |
2534 pushop.ui.debug('try to push obsolete markers to remote\n') |
2982 pushop.ui.debug('try to push obsolete markers to remote\n') |
2535 repo = pushop.repo |
2983 repo = pushop.repo |
2536 remote = pushop.remote |
2984 remote = pushop.remote |
2537 if (obsolete._enabled and repo.obsstore and |
2985 if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore and |
2538 'obsolete' in remote.listkeys('namespaces')): |
2986 'obsolete' in remote.listkeys('namespaces')): |
2539 markers = pushop.outobsmarkers |
2987 markers = pushop.outobsmarkers |
2540 if not markers: |
2988 if not markers: |
2541 obsexcmsg(repo.ui, "no marker to push\n") |
2989 obsexcmsg(repo.ui, "no marker to push\n") |
2542 elif remote.capable('_evoext_pushobsmarkers_0'): |
2990 elif remote.capable('_evoext_pushobsmarkers_0'): |
2607 def local_pushobsmarker_capabilities(orig, repo, caps): |
3055 def local_pushobsmarker_capabilities(orig, repo, caps): |
2608 caps = orig(repo, caps) |
3056 caps = orig(repo, caps) |
2609 caps.add('_evoext_pushobsmarkers_0') |
3057 caps.add('_evoext_pushobsmarkers_0') |
2610 return caps |
3058 return caps |
2611 |
3059 |
2612 @eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0') |
3060 def _pushobsmarkers(repo, data): |
2613 def local_pushobsmarkers(peer, obsfile): |
|
2614 data = obsfile.read() |
|
2615 tr = lock = None |
|
2616 try: |
|
2617 lock = peer._repo.lock() |
|
2618 tr = peer._repo.transaction('pushkey: obsolete markers') |
|
2619 new = peer._repo.obsstore.mergemarkers(tr, data) |
|
2620 if new is not None: |
|
2621 obsexcmsg(peer._repo.ui, "%i obsolescence markers added\n" % new, True) |
|
2622 tr.close() |
|
2623 finally: |
|
2624 lockmod.release(tr, lock) |
|
2625 peer._repo.hook('evolve_pushobsmarkers') |
|
2626 |
|
2627 def srv_pushobsmarkers(repo, proto): |
|
2628 """wireprotocol command""" |
|
2629 fp = StringIO() |
|
2630 proto.redirect() |
|
2631 proto.getfile(fp) |
|
2632 data = fp.getvalue() |
|
2633 fp.close() |
|
2634 tr = lock = None |
3061 tr = lock = None |
2635 try: |
3062 try: |
2636 lock = repo.lock() |
3063 lock = repo.lock() |
2637 tr = repo.transaction('pushkey: obsolete markers') |
3064 tr = repo.transaction('pushkey: obsolete markers') |
2638 new = repo.obsstore.mergemarkers(tr, data) |
3065 new = repo.obsstore.mergemarkers(tr, data) |
2640 obsexcmsg(repo.ui, "%i obsolescence markers added\n" % new, True) |
3067 obsexcmsg(repo.ui, "%i obsolescence markers added\n" % new, True) |
2641 tr.close() |
3068 tr.close() |
2642 finally: |
3069 finally: |
2643 lockmod.release(tr, lock) |
3070 lockmod.release(tr, lock) |
2644 repo.hook('evolve_pushobsmarkers') |
3071 repo.hook('evolve_pushobsmarkers') |
3072 |
|
3073 @eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0') |
|
3074 def local_pushobsmarkers(peer, obsfile): |
|
3075 data = obsfile.read() |
|
3076 _pushobsmarkers(peer._repo, data) |
|
3077 |
|
3078 def srv_pushobsmarkers(repo, proto): |
|
3079 """wireprotocol command""" |
|
3080 fp = StringIO() |
|
3081 proto.redirect() |
|
3082 proto.getfile(fp) |
|
3083 data = fp.getvalue() |
|
3084 fp.close() |
|
3085 _pushobsmarkers(repo, data) |
|
2645 return wireproto.pushres(0) |
3086 return wireproto.pushres(0) |
2646 |
3087 |
2647 def _buildpullobsmarkersboundaries(pullop): |
3088 def _buildpullobsmarkersboundaries(pullop): |
2648 """small funtion returning the argument for pull markers call |
3089 """small funtion returning the argument for pull markers call |
2649 may to contains 'heads' and 'common'. skip the key for None. |
3090 may to contains 'heads' and 'common'. skip the key for None. |
2672 common = boundaries['common'] |
3113 common = boundaries['common'] |
2673 if common != [nullid]: |
3114 if common != [nullid]: |
2674 kwargs['evo_obscommon'] = common |
3115 kwargs['evo_obscommon'] = common |
2675 return ret |
3116 return ret |
2676 |
3117 |
2677 if getattr(exchange, '_getbundleobsmarkerpart', None) is not None: |
3118 @eh.wrapfunction(exchange, '_getbundleobsmarkerpart') |
2678 @eh.wrapfunction(exchange, '_getbundleobsmarkerpart') |
3119 def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs): |
2679 def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs): |
3120 if 'evo_obscommon' not in kwargs: |
2680 if 'evo_obscommon' not in kwargs: |
3121 return orig(bundler, repo, source, **kwargs) |
2681 return orig(bundler, repo, source, **kwargs) |
3122 |
2682 |
3123 heads = kwargs.get('heads') |
2683 heads = kwargs.get('heads') |
3124 if kwargs.get('obsmarkers', False): |
2684 if kwargs.get('obsmarkers', False): |
3125 if heads is None: |
2685 if heads is None: |
3126 heads = repo.heads() |
2686 heads = repo.heads() |
3127 obscommon = kwargs.get('evo_obscommon', ()) |
2687 obscommon = kwargs.get('evo_obscommon', ()) |
3128 assert obscommon |
2688 assert obscommon |
3129 obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon) |
2689 obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon) |
3130 subset = [c.node() for c in obsset] |
2690 subset = [c.node() for c in obsset] |
3131 markers = repo.obsstore.relevantmarkers(subset) |
2691 markers = repo.obsstore.relevantmarkers(subset) |
3132 exchange.buildobsmarkerspart(bundler, markers) |
2692 exchange.buildobsmarkerspart(bundler, markers) |
3133 |
2693 |
3134 @eh.uisetup |
2694 @eh.uisetup |
3135 def installgetbundlepartgen(ui): |
2695 def installgetbundlepartgen(ui): |
3136 origfunc = exchange.getbundle2partsmapping['obsmarkers'] |
2696 origfunc = exchange.getbundle2partsmapping['obsmarkers'] |
3137 def newfunc(*args, **kwargs): |
2697 def newfunc(*args, **kwargs): |
3138 return _getbundleobsmarkerpart(origfunc, *args, **kwargs) |
2698 return _getbundleobsmarkerpart(origfunc, *args, **kwargs) |
3139 exchange.getbundle2partsmapping['obsmarkers'] = newfunc |
2699 exchange.getbundle2partsmapping['obsmarkers'] = newfunc |
|
2700 |
|
2701 |
3140 |
2702 @eh.wrapfunction(exchange, '_pullobsolete') |
3141 @eh.wrapfunction(exchange, '_pullobsolete') |
2703 def _pullobsolete(orig, pullop): |
3142 def _pullobsolete(orig, pullop): |
2704 if not obsolete._enabled: |
3143 if not obsolete.isenabled(pullop.repo, obsolete.exchangeopt): |
2705 return None |
3144 return None |
2706 if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']): |
3145 if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']): |
2707 return None |
3146 return None |
2708 if 'obsmarkers' in getattr(pullop, 'stepsdone', []): |
3147 if 'obsmarkers' in getattr(pullop, 'stepsdone', []): |
2709 return None |
3148 return None |
2869 ui.status('%s %s\n' % (node.hex(chg), node.hex(obs))) |
3308 ui.status('%s %s\n' % (node.hex(chg), node.hex(obs))) |
2870 |
3309 |
2871 _bestformat = max(obsolete.formats.keys()) |
3310 _bestformat = max(obsolete.formats.keys()) |
2872 |
3311 |
2873 |
3312 |
2874 if getattr(obsolete, '_checkinvalidmarkers', None) is not None: |
3313 @eh.wrapfunction(obsolete, '_checkinvalidmarkers') |
2875 @eh.wrapfunction(obsolete, '_checkinvalidmarkers') |
3314 def _checkinvalidmarkers(orig, markers): |
2876 def _checkinvalidmarkers(orig, markers): |
3315 """search for marker with invalid data and raise error if needed |
2877 """search for marker with invalid data and raise error if needed |
3316 |
2878 |
3317 Exist as a separated function to allow the evolve extension for a more |
2879 Exist as a separated function to allow the evolve extension for a more |
3318 subtle handling. |
2880 subtle handling. |
3319 """ |
2881 """ |
3320 if 'debugobsconvert' in sys.argv: |
2882 if 'debugobsconvert' in sys.argv: |
3321 return |
2883 return |
3322 for mark in markers: |
2884 for mark in markers: |
3323 if node.nullid in mark[1]: |
2885 if node.nullid in mark[1]: |
3324 raise util.Abort(_('bad obsolescence marker detected: ' |
2886 raise util.Abort(_('bad obsolescence marker detected: ' |
3325 'invalid successors nullid'), |
2887 'invalid successors nullid'), |
3326 hint=_('You should run `hg debugobsconvert`')) |
2888 hint=_('You should run `hg debugobsconvert`')) |
|
2889 |
3327 |
2890 @command( |
3328 @command( |
2891 'debugobsconvert', |
3329 'debugobsconvert', |
2892 [('', 'new-format', _bestformat, _('Destination format for markers.'))], |
3330 [('', 'new-format', _bestformat, _('Destination format for markers.'))], |
2893 '') |
3331 '') |
2918 |
3356 |
2919 @eh.wrapfunction(wireproto, 'capabilities') |
3357 @eh.wrapfunction(wireproto, 'capabilities') |
2920 def capabilities(orig, repo, proto): |
3358 def capabilities(orig, repo, proto): |
2921 """wrapper to advertise new capability""" |
3359 """wrapper to advertise new capability""" |
2922 caps = orig(repo, proto) |
3360 caps = orig(repo, proto) |
2923 if obsolete._enabled: |
3361 if obsolete.isenabled(repo, obsolete.exchangeopt): |
2924 caps += ' _evoext_pushobsmarkers_0' |
3362 caps += ' _evoext_pushobsmarkers_0' |
2925 caps += ' _evoext_pullobsmarkers_0' |
3363 caps += ' _evoext_pullobsmarkers_0' |
2926 caps += ' _evoext_obshash_0' |
3364 caps += ' _evoext_obshash_0' |
2927 caps += ' _evoext_obshash_1' |
3365 caps += ' _evoext_obshash_1' |
2928 caps += ' _evoext_getbundle_obscommon' |
3366 caps += ' _evoext_getbundle_obscommon' |
2939 # wrap command content |
3377 # wrap command content |
2940 oldcap, args = wireproto.commands['capabilities'] |
3378 oldcap, args = wireproto.commands['capabilities'] |
2941 def newcap(repo, proto): |
3379 def newcap(repo, proto): |
2942 return capabilities(oldcap, repo, proto) |
3380 return capabilities(oldcap, repo, proto) |
2943 wireproto.commands['capabilities'] = (newcap, args) |
3381 wireproto.commands['capabilities'] = (newcap, args) |
3382 |
|
3383 def _helploader(): |
|
3384 return help.gettext(evolutionhelptext) |
|
3385 |
|
3386 @eh.uisetup |
|
3387 def _setuphelp(ui): |
|
3388 for entry in help.helptable: |
|
3389 if entry[0] == "evolution": |
|
3390 break |
|
3391 else: |
|
3392 help.helptable.append((["evolution"], _("Safely Rewriting History"), |
|
3393 _helploader)) |
|
3394 help.helptable.sort() |