74 from mercurial import obsolete |
74 from mercurial import obsolete |
75 from mercurial.localrepo import storecache |
75 from mercurial.localrepo import storecache |
76 obsolete._enabled = True |
76 obsolete._enabled = True |
77 |
77 |
78 |
78 |
|
79 ### Extension helper |
|
80 ############################# |
|
81 |
|
82 ### setup code |
|
83 |
|
84 class exthelper(object): |
|
85 """Helper for modular extension setup |
|
86 |
|
87 A single helper should be intanciated for each extension. Helper method are |
|
88 then used as decorator for various purpose. |
|
89 |
|
90 All decorator returns the original function and may be chained. |
|
91 """ |
|
92 |
|
93 def __init__(self): |
|
94 self._uicallables = [] |
|
95 self._extcallables = [] |
|
96 self._repocallables = [] |
|
97 self._revsetsymbols = [] |
|
98 self._commandwrappers = [] |
|
99 self._extcommandwrappers = [] |
|
100 self._functionwrappers = [] |
|
101 self._duckpunchers = [] |
|
102 |
|
103 def final_uisetup(self, ui): |
|
104 """Method to be used as a the extension uisetup |
|
105 |
|
106 The following operations belong here: |
|
107 |
|
108 - Changes to ui.__class__ . The ui object that will be used to run the |
|
109 command has not yet been created. Changes made here will affect ui |
|
110 objects created after this, and in particular the ui that will be |
|
111 passed to runcommand |
|
112 - Command wraps (extensions.wrapcommand) |
|
113 - Changes that need to be visible by other extensions: because |
|
114 initialization occurs in phases (all extensions run uisetup, then all |
|
115 run extsetup), a change made here will be visible by other extensions |
|
116 during extsetup |
|
117 - Monkeypatches or function wraps (extensions.wrapfunction) of dispatch |
|
118 module members |
|
119 - Setup of pre-* and post-* hooks |
|
120 - pushkey setup |
|
121 """ |
|
122 for cont, funcname, func in self._duckpunchers: |
|
123 setattr(cont, funcname, func) |
|
124 for command, wrapper in self._commandwrappers: |
|
125 extensions.wrapcommand(commands.table, command, wrapper) |
|
126 for cont, funcname, wrapper in self._functionwrappers: |
|
127 extensions.wrapfunction(cont, funcname, wrapper) |
|
128 for c in self._uicallables: |
|
129 c(ui) |
|
130 |
|
131 def final_extsetup(self, ui): |
|
132 """Method to be used as a the extension extsetup |
|
133 |
|
134 The following operations belong here: |
|
135 |
|
136 - Changes depending on the status of other extensions. (if extensions.find('mq')) |
|
137 - Add a global option to all commands |
|
138 - Extend revsets |
|
139 """ |
|
140 knownexts = {} |
|
141 for name, symbol in self._revsetsymbols: |
|
142 revset.symbols[name] = symbol |
|
143 for ext, command, wrapper in self._extcommandwrappers: |
|
144 if ext not in knownexts: |
|
145 e = extensions.find('rebase') |
|
146 if e is None: |
|
147 raise util.Abort('extension %s not found' %e) |
|
148 knownexts[ext] = e.cmdtable |
|
149 extensions.wrapcommand(knownexts[ext], commands, wrapper) |
|
150 for c in self._extcallables: |
|
151 c(ui) |
|
152 |
|
153 def final_reposetup(self, ui, repo): |
|
154 """Method to be used as a the extension reposetup |
|
155 |
|
156 The following operations belong here: |
|
157 |
|
158 - All hooks but pre-* and post-* |
|
159 - Modify configuration variables |
|
160 - Changes to repo.__class__, repo.dirstate.__class__ |
|
161 """ |
|
162 for c in self._repocallables: |
|
163 c(ui, repo) |
|
164 |
|
165 def uisetup(self, call): |
|
166 """Decorated function will be executed during uisetup |
|
167 |
|
168 example:: |
|
169 |
|
170 @eh.uisetup |
|
171 def setupbabar(ui): |
|
172 print 'this is uisetup!' |
|
173 """ |
|
174 self._uicallables.append(call) |
|
175 return call |
|
176 |
|
177 def extsetup(self, call): |
|
178 """Decorated function will be executed during extsetup |
|
179 |
|
180 example:: |
|
181 |
|
182 @eh.extsetup |
|
183 def setupcelestine(ui): |
|
184 print 'this is extsetup!' |
|
185 """ |
|
186 self._uicallables.append(call) |
|
187 return call |
|
188 |
|
189 def reposetup(self, call): |
|
190 """Decorated function will be executed during reposetup |
|
191 |
|
192 example:: |
|
193 |
|
194 @eh.reposetup |
|
195 def setupzephir(ui, repo): |
|
196 print 'this is reposetup!' |
|
197 """ |
|
198 self._repocallables.append(call) |
|
199 return call |
|
200 |
|
201 def revset(self, symbolname): |
|
202 """Decorated function is a revset symbol |
|
203 |
|
204 The name of the symbol must be given as the decorator argument. |
|
205 The symbol is added during `extsetup`. |
|
206 |
|
207 example:: |
|
208 |
|
209 @eh.revset('hidden') |
|
210 def revsetbabar(repo, subset, x): |
|
211 args = revset.getargs(x, 0, 0, 'babar accept no argument') |
|
212 return [r for r in subset if 'babar' in repo[r].description()] |
|
213 """ |
|
214 def dec(symbol): |
|
215 self._revsetsymbols.append((symbolname, symbol)) |
|
216 return symbol |
|
217 return dec |
|
218 |
|
219 def wrapcommand(self, command, extension=None): |
|
220 """Decorated function is a command wrapper |
|
221 |
|
222 The name of the command must be given as the decorator argument. |
|
223 The wrapping is installed during `uisetup`. |
|
224 |
|
225 If the second option `extension` argument is provided, the wrapping |
|
226 will be applied in the extension commandtable. This argument must be a |
|
227 string that will be searched using `extension.find` if not found and |
|
228 Abort error is raised. If the wrapping apply to an extension, it is |
|
229 installed during `extsetup` |
|
230 |
|
231 example:: |
|
232 |
|
233 @eh.wrapcommand('summary') |
|
234 def wrapsummary(orig, ui, repo, *args, **kwargs): |
|
235 ui.note('Barry!') |
|
236 return orig(ui, repo, *args, **kwargs) |
|
237 |
|
238 """ |
|
239 def dec(wrapper): |
|
240 if extension is None: |
|
241 self._commandwrappers.append((command, wrapper)) |
|
242 else: |
|
243 self._extcommandwrappers.append((extension, command, wrapper)) |
|
244 return wrapper |
|
245 return dec |
|
246 |
|
247 def wrapfunction(self, container, funcname): |
|
248 """Decorated function is a function wrapper |
|
249 |
|
250 This function take two argument, the container and the name of the |
|
251 function to wrap. The wrapping is performed during `uisetup`. |
|
252 (there is don't support extension) |
|
253 |
|
254 example:: |
|
255 |
|
256 @eh.function(discovery, 'checkheads') |
|
257 def wrapfunction(orig, *args, **kwargs): |
|
258 ui.note('His head smashed in and his heart cut out') |
|
259 return orig(*args, **kwargs) |
|
260 """ |
|
261 def dec(wrapper): |
|
262 self._functionwrappers.append((container, funcname, wrapper)) |
|
263 return wrapper |
|
264 return dec |
|
265 |
|
266 def addattr(self, container, funcname): |
|
267 """Decorated function is to be added to the container |
|
268 |
|
269 This function take two argument, the container and the name of the |
|
270 function to wrap. The wrapping is performed during `uisetup`. |
|
271 |
|
272 example:: |
|
273 |
|
274 @eh.function(context.changectx, 'babar') |
|
275 def babar(ctx): |
|
276 return 'babar' in ctx.description |
|
277 """ |
|
278 def dec(func): |
|
279 self._duckpunchers.append((container, funcname, func)) |
|
280 return func |
|
281 return dec |
|
282 |
|
283 eh = exthelper() |
|
284 uisetup = eh.final_uisetup |
|
285 extsetup = eh.final_extsetup |
|
286 reposetup = eh.final_reposetup |
|
287 |
79 |
288 |
80 ### Patch changectx |
289 ### Patch changectx |
81 ############################# |
290 ############################# |
82 |
291 |
|
292 @eh.addattr(context.changectx, 'unstable') |
83 def unstable(ctx): |
293 def unstable(ctx): |
84 """is the changeset unstable (have obsolete ancestor)""" |
294 """is the changeset unstable (have obsolete ancestor)""" |
85 if ctx.node() is None: |
295 if ctx.node() is None: |
86 return False |
296 return False |
87 return ctx.rev() in ctx._repo._unstableset |
297 return ctx.rev() in ctx._repo._unstableset |
88 |
298 |
89 context.changectx.unstable = unstable |
299 |
90 |
300 @eh.addattr(context.changectx, 'extinct') |
91 def extinct(ctx): |
301 def extinct(ctx): |
92 """is the changeset extinct by other""" |
302 """is the changeset extinct by other""" |
93 if ctx.node() is None: |
303 if ctx.node() is None: |
94 return False |
304 return False |
95 return ctx.rev() in ctx._repo._extinctset |
305 return ctx.rev() in ctx._repo._extinctset |
96 |
306 |
97 context.changectx.extinct = extinct |
307 @eh.addattr(context.changectx, 'latecomer') |
98 |
|
99 def latecomer(ctx): |
308 def latecomer(ctx): |
100 """is the changeset latecomer (Try to succeed to public change)""" |
309 """is the changeset latecomer (Try to succeed to public change)""" |
101 if ctx.node() is None: |
310 if ctx.node() is None: |
102 return False |
311 return False |
103 return ctx.rev() in ctx._repo._latecomerset |
312 return ctx.rev() in ctx._repo._latecomerset |
104 |
313 |
105 context.changectx.latecomer = latecomer |
314 @eh.addattr(context.changectx, 'conflicting') |
106 |
|
107 def conflicting(ctx): |
315 def conflicting(ctx): |
108 """is the changeset conflicting (Try to succeed to public change)""" |
316 """is the changeset conflicting (Try to succeed to public change)""" |
109 if ctx.node() is None: |
317 if ctx.node() is None: |
110 return False |
318 return False |
111 return ctx.rev() in ctx._repo._conflictingset |
319 return ctx.rev() in ctx._repo._conflictingset |
112 |
320 |
113 context.changectx.conflicting = conflicting |
|
114 |
|
115 |
321 |
116 ### revset |
322 ### revset |
117 ############################# |
323 ############################# |
118 |
324 |
|
325 @eh.revset('hidden') |
119 def revsethidden(repo, subset, x): |
326 def revsethidden(repo, subset, x): |
120 """``hidden()`` |
327 """``hidden()`` |
121 Changeset is hidden. |
328 Changeset is hidden. |
122 """ |
329 """ |
123 args = revset.getargs(x, 0, 0, 'hidden takes no argument') |
330 args = revset.getargs(x, 0, 0, 'hidden takes no argument') |
124 return [r for r in subset if r in repo.hiddenrevs] |
331 return [r for r in subset if r in repo.hiddenrevs] |
125 |
332 |
|
333 @eh.revset('obsolete') |
126 def revsetobsolete(repo, subset, x): |
334 def revsetobsolete(repo, subset, x): |
127 """``obsolete()`` |
335 """``obsolete()`` |
128 Changeset is obsolete. |
336 Changeset is obsolete. |
129 """ |
337 """ |
130 args = revset.getargs(x, 0, 0, 'obsolete takes no argument') |
338 args = revset.getargs(x, 0, 0, 'obsolete takes no argument') |
131 return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0] |
339 return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0] |
132 |
340 |
|
341 @eh.revset('unstable') |
133 def revsetunstable(repo, subset, x): |
342 def revsetunstable(repo, subset, x): |
134 """``unstable()`` |
343 """``unstable()`` |
135 Unstable changesets are non-obsolete with obsolete ancestors. |
344 Unstable changesets are non-obsolete with obsolete ancestors. |
136 """ |
345 """ |
137 args = revset.getargs(x, 0, 0, 'unstable takes no arguments') |
346 args = revset.getargs(x, 0, 0, 'unstable takes no arguments') |
138 return [r for r in subset if r in repo._unstableset] |
347 return [r for r in subset if r in repo._unstableset] |
139 |
348 |
|
349 @eh.revset('suspended') |
140 def revsetsuspended(repo, subset, x): |
350 def revsetsuspended(repo, subset, x): |
141 """``suspended()`` |
351 """``suspended()`` |
142 Obsolete changesets with non-obsolete descendants. |
352 Obsolete changesets with non-obsolete descendants. |
143 """ |
353 """ |
144 args = revset.getargs(x, 0, 0, 'suspended takes no arguments') |
354 args = revset.getargs(x, 0, 0, 'suspended takes no arguments') |
145 return [r for r in subset if r in repo._suspendedset] |
355 return [r for r in subset if r in repo._suspendedset] |
146 |
356 |
|
357 @eh.revset('extinct') |
147 def revsetextinct(repo, subset, x): |
358 def revsetextinct(repo, subset, x): |
148 """``extinct()`` |
359 """``extinct()`` |
149 Obsolete changesets with obsolete descendants only. |
360 Obsolete changesets with obsolete descendants only. |
150 """ |
361 """ |
151 args = revset.getargs(x, 0, 0, 'extinct takes no arguments') |
362 args = revset.getargs(x, 0, 0, 'extinct takes no arguments') |
152 return [r for r in subset if r in repo._extinctset] |
363 return [r for r in subset if r in repo._extinctset] |
153 |
364 |
|
365 @eh.revset('latecomer') |
154 def revsetlatecomer(repo, subset, x): |
366 def revsetlatecomer(repo, subset, x): |
155 """``latecomer()`` |
367 """``latecomer()`` |
156 Changesets marked as successors of public changesets. |
368 Changesets marked as successors of public changesets. |
157 """ |
369 """ |
158 args = revset.getargs(x, 0, 0, 'latecomer takes no arguments') |
370 args = revset.getargs(x, 0, 0, 'latecomer takes no arguments') |
159 return [r for r in subset if r in repo._latecomerset] |
371 return [r for r in subset if r in repo._latecomerset] |
160 |
372 |
|
373 @eh.revset('conflicting') |
161 def revsetconflicting(repo, subset, x): |
374 def revsetconflicting(repo, subset, x): |
162 """``conflicting()`` |
375 """``conflicting()`` |
163 Changesets marked as successors of a same changeset. |
376 Changesets marked as successors of a same changeset. |
164 """ |
377 """ |
165 args = revset.getargs(x, 0, 0, 'conflicting takes no arguments') |
378 args = revset.getargs(x, 0, 0, 'conflicting takes no arguments') |
429 ############################# |
636 ############################# |
430 |
637 |
431 cmdtable = {} |
638 cmdtable = {} |
432 command = cmdutil.command(cmdtable) |
639 command = cmdutil.command(cmdtable) |
433 |
640 |
|
641 |
|
642 |
|
643 @command('debugsuccessors', [], '') |
|
644 def cmddebugsuccessors(ui, repo): |
|
645 """dump obsolete changesets and their successors |
|
646 |
|
647 Each line matches an existing marker, the first identifier is the |
|
648 obsolete changeset identifier, followed by it successors. |
|
649 """ |
|
650 lock = repo.lock() |
|
651 try: |
|
652 allsuccessors = repo.obsstore.precursors |
|
653 for old in sorted(allsuccessors): |
|
654 successors = [sorted(m[1]) for m in allsuccessors[old]] |
|
655 for i, group in enumerate(sorted(successors)): |
|
656 ui.write('%s' % short(old)) |
|
657 for new in group: |
|
658 ui.write(' %s' % short(new)) |
|
659 ui.write('\n') |
|
660 finally: |
|
661 lock.release() |
|
662 |
|
663 ### Altering existing command |
|
664 ############################# |
|
665 |
|
666 @eh.wrapcommand("update") |
|
667 @eh.wrapcommand("pull") |
|
668 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): |
|
669 res = origfn(ui, repo, *args, **opts) |
|
670 if repo['.'].obsolete(): |
|
671 ui.warn(_('Working directory parent is obsolete\n')) |
|
672 return res |
|
673 |
|
674 def warnobserrors(orig, ui, repo, *args, **kwargs): |
|
675 """display warning is the command resulted in more instable changeset""" |
|
676 priorunstables = len(repo.revs('unstable()')) |
|
677 priorlatecomers = len(repo.revs('latecomer()')) |
|
678 priorconflictings = len(repo.revs('conflicting()')) |
|
679 #print orig, priorunstables |
|
680 #print len(repo.revs('secret() - obsolete()')) |
|
681 try: |
|
682 return orig(ui, repo, *args, **kwargs) |
|
683 finally: |
|
684 newunstables = len(repo.revs('unstable()')) - priorunstables |
|
685 newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers |
|
686 newconflictings = len(repo.revs('conflicting()')) - priorconflictings |
|
687 #print orig, newunstables |
|
688 #print len(repo.revs('secret() - obsolete()')) |
|
689 if newunstables > 0: |
|
690 ui.warn(_('%i new unstables changesets\n') % newunstables) |
|
691 if newlatecomers > 0: |
|
692 ui.warn(_('%i new latecomers changesets\n') % newlatecomers) |
|
693 if newconflictings > 0: |
|
694 ui.warn(_('%i new conflictings changesets\n') % newconflictings) |
|
695 |
|
696 @eh.extsetup |
|
697 def _coreobserrorwrapping(ui): |
|
698 # warning about more obsolete |
|
699 for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']: |
|
700 entry = extensions.wrapcommand(commands.table, cmd, warnobserrors) |
|
701 |
|
702 @eh.wrapfunction(cmdutil, 'amend') |
|
703 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): |
|
704 oldnode = old.node() |
|
705 new = orig(ui, repo, commitfunc, old, *args, **kwargs) |
|
706 if new != oldnode: |
|
707 lock = repo.lock() |
|
708 try: |
|
709 tr = repo.transaction('post-amend-obst') |
|
710 try: |
|
711 meta = { |
|
712 'date': '%i %i' % util.makedate(), |
|
713 'user': ui.username(), |
|
714 } |
|
715 repo.obsstore.create(tr, oldnode, [new], 0, meta) |
|
716 tr.close() |
|
717 repo._clearobsoletecache() |
|
718 finally: |
|
719 tr.release() |
|
720 finally: |
|
721 lock.release() |
|
722 return new |
|
723 |
|
724 |
|
725 ### diagnostique tools |
|
726 ############################# |
|
727 |
|
728 def unstables(repo): |
|
729 """Return all unstable changeset""" |
|
730 return scmutil.revrange(repo, ['obsolete():: and (not obsolete())']) |
|
731 |
|
732 def newerversion(repo, obs): |
|
733 """Return the newer version of an obsolete changeset""" |
|
734 toproceed = set([(obs,)]) |
|
735 # XXX known optimization available |
|
736 newer = set() |
|
737 objectrels = repo.obsstore.precursors |
|
738 while toproceed: |
|
739 current = toproceed.pop() |
|
740 assert len(current) <= 1, 'splitting not handled yet. %r' % current |
|
741 current = [n for n in current if n != nullid] |
|
742 if current: |
|
743 n, = current |
|
744 if n in objectrels: |
|
745 markers = objectrels[n] |
|
746 for mark in markers: |
|
747 toproceed.add(tuple(mark[1])) |
|
748 else: |
|
749 newer.add(tuple(current)) |
|
750 else: |
|
751 newer.add(()) |
|
752 return sorted(newer) |
|
753 |
|
754 ### repo subclassing |
|
755 ############################# |
|
756 |
|
757 @eh.reposetup |
|
758 def _reposetup(ui, repo): |
|
759 if not repo.local(): |
|
760 return |
|
761 |
|
762 if not util.safehasattr(repo.opener, 'tryread'): |
|
763 raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)') |
|
764 opush = repo.push |
|
765 o_updatebranchcache = repo.updatebranchcache |
|
766 |
|
767 # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\ |
|
768 if util.safehasattr(repo, '_journalfiles'): # Hg 2.2 |
|
769 o_journalfiles = repo._journalfiles |
|
770 o_writejournal = repo._writejournal |
|
771 o_hook = repo.hook |
|
772 |
|
773 |
|
774 class obsoletingrepo(repo.__class__): |
|
775 |
|
776 # workaround |
|
777 def hook(self, name, throw=False, **args): |
|
778 if 'pushkey' in name: |
|
779 args.pop('new') |
|
780 args.pop('old') |
|
781 return o_hook(name, throw=False, **args) |
|
782 |
|
783 ### Public method |
|
784 # XXX Kill me |
|
785 def obsoletedby(self, node): |
|
786 """return the set of node that make <node> obsolete (obj)""" |
|
787 others = set() |
|
788 for marker in self.obsstore.precursors.get(node, []): |
|
789 others.update(marker[1]) |
|
790 return others |
|
791 |
|
792 # XXX Kill me |
|
793 def obsolete(self, node): |
|
794 """return the set of node that <node> make obsolete (sub)""" |
|
795 return set(marker[0] for marker in self.obsstore.successors.get(node, [])) |
|
796 |
|
797 # XXX move me on obssotre |
|
798 @util.propertycache |
|
799 def _obsoleteset(self): |
|
800 """the set of obsolete revision""" |
|
801 obs = set() |
|
802 nm = self.changelog.nodemap |
|
803 for prec in self.obsstore.precursors: |
|
804 rev = nm.get(prec) |
|
805 if rev is not None: |
|
806 obs.add(rev) |
|
807 return obs |
|
808 |
|
809 # XXX move me on obssotre |
|
810 @util.propertycache |
|
811 def _unstableset(self): |
|
812 """the set of non obsolete revision with obsolete parent""" |
|
813 return set(self.revs('(obsolete()::) - obsolete()')) |
|
814 |
|
815 # XXX move me on obssotre |
|
816 @util.propertycache |
|
817 def _suspendedset(self): |
|
818 """the set of obsolete parent with non obsolete descendant""" |
|
819 return set(self.revs('obsolete() and obsolete()::unstable()')) |
|
820 |
|
821 # XXX move me on obssotre |
|
822 @util.propertycache |
|
823 def _extinctset(self): |
|
824 """the set of obsolete parent without non obsolete descendant""" |
|
825 return set(self.revs('obsolete() - obsolete()::unstable()')) |
|
826 |
|
827 # XXX move me on obssotre |
|
828 @util.propertycache |
|
829 def _latecomerset(self): |
|
830 """the set of rev trying to obsolete public revision""" |
|
831 query = 'allsuccessors(public()) - obsolete() - public()' |
|
832 return set(self.revs(query)) |
|
833 |
|
834 # XXX move me on obssotre |
|
835 @util.propertycache |
|
836 def _conflictingset(self): |
|
837 """the set of rev trying to obsolete public revision""" |
|
838 conflicting = set() |
|
839 obsstore = self.obsstore |
|
840 newermap = {} |
|
841 for ctx in self.set('(not public()) - obsolete()'): |
|
842 prec = obsstore.successors.get(ctx.node(), ()) |
|
843 toprocess = set(prec) |
|
844 while toprocess: |
|
845 prec = toprocess.pop()[0] |
|
846 if prec not in newermap: |
|
847 newermap[prec] = newerversion(self, prec) |
|
848 newer = [n for n in newermap[prec] if n] # filter kill |
|
849 if len(newer) > 1: |
|
850 conflicting.add(ctx.rev()) |
|
851 break |
|
852 toprocess.update(obsstore.successors.get(prec, ())) |
|
853 return conflicting |
|
854 |
|
855 def _clearobsoletecache(self): |
|
856 if '_obsoleteset' in vars(self): |
|
857 del self._obsoleteset |
|
858 self._clearunstablecache() |
|
859 |
|
860 def updatebranchcache(self): |
|
861 o_updatebranchcache() |
|
862 self._clearunstablecache() |
|
863 |
|
864 def _clearunstablecache(self): |
|
865 if '_unstableset' in vars(self): |
|
866 del self._unstableset |
|
867 if '_suspendedset' in vars(self): |
|
868 del self._suspendedset |
|
869 if '_extinctset' in vars(self): |
|
870 del self._extinctset |
|
871 if '_latecomerset' in vars(self): |
|
872 del self._latecomerset |
|
873 if '_conflictingset' in vars(self): |
|
874 del self._conflictingset |
|
875 |
|
876 # XXX kill me |
|
877 def addobsolete(self, sub, obj): |
|
878 """Add a relation marking that node <sub> is a new version of <obj>""" |
|
879 assert sub != obj |
|
880 if not repo[obj].phase(): |
|
881 if sub is None: |
|
882 self.ui.warn( |
|
883 _("trying to kill immutable changeset %(obj)s\n") |
|
884 % {'obj': short(obj)}) |
|
885 if sub is not None: |
|
886 self.ui.warn( |
|
887 _("%(sub)s try to obsolete immutable changeset %(obj)s\n") |
|
888 % {'sub': short(sub), 'obj': short(obj)}) |
|
889 lock = self.lock() |
|
890 try: |
|
891 tr = self.transaction('add-obsolete') |
|
892 try: |
|
893 meta = { |
|
894 'date': '%i %i' % util.makedate(), |
|
895 'user': ui.username(), |
|
896 } |
|
897 subs = (sub == nullid) and [] or [sub] |
|
898 mid = self.obsstore.create(tr, obj, subs, 0, meta) |
|
899 tr.close() |
|
900 self._clearobsoletecache() |
|
901 return mid |
|
902 finally: |
|
903 tr.release() |
|
904 finally: |
|
905 lock.release() |
|
906 |
|
907 # XXX kill me |
|
908 def addcollapsedobsolete(self, oldnodes, newnode): |
|
909 """Mark oldnodes as collapsed into newnode.""" |
|
910 # Assume oldnodes are all descendants of a single rev |
|
911 rootrevs = self.revs('roots(%ln)', oldnodes) |
|
912 assert len(rootrevs) == 1, rootrevs |
|
913 #rootnode = self[rootrevs[0]].node() |
|
914 for n in oldnodes: |
|
915 self.addobsolete(newnode, n) |
|
916 |
|
917 ### pull // push support |
|
918 |
|
919 def push(self, remote, *args, **opts): |
|
920 """wrapper around pull that pull obsolete relation""" |
|
921 try: |
|
922 result = opush(remote, *args, **opts) |
|
923 except util.Abort, ex: |
|
924 hint = _("use 'hg stabilize' to get a stable history " |
|
925 "or --force to ignore warnings") |
|
926 if (len(ex.args) >= 1 |
|
927 and ex.args[0].startswith('push includes ') |
|
928 and ex.hint is None): |
|
929 ex.hint = hint |
|
930 raise |
|
931 return result |
|
932 |
|
933 |
|
934 repo.__class__ = obsoletingrepo |
|
935 |
|
936 @eh.reposetup |
|
937 def _checkoldobsolete(ui, repo): |
|
938 if not repo.local(): |
|
939 return |
|
940 for arg in sys.argv: |
|
941 if 'debugc' in arg: |
|
942 break |
|
943 else: |
|
944 data = repo.opener.tryread('obsolete-relations') |
|
945 if not data: |
|
946 data = repo.sopener.tryread('obsoletemarkers') |
|
947 if data: |
|
948 raise util.Abort('old format of obsolete marker detected!\n' |
|
949 'run `hg debugconvertobsolete` once.') |
|
950 |
|
951 ### serialisation |
|
952 ############################# |
|
953 |
|
954 def _obsdeserialise(flike): |
|
955 """read a file like object serialised with _obsserialise |
|
956 |
|
957 this desierialize into a {subject -> objects} mapping""" |
|
958 rels = {} |
|
959 for line in flike: |
|
960 subhex, objhex = line.split() |
|
961 subnode = bin(subhex) |
|
962 if subnode == nullid: |
|
963 subnode = None |
|
964 rels.setdefault( subnode, set()).add(bin(objhex)) |
|
965 return rels |
434 |
966 |
435 @command('debugconvertobsolete', [], '') |
967 @command('debugconvertobsolete', [], '') |
436 def cmddebugconvertobsolete(ui, repo): |
968 def cmddebugconvertobsolete(ui, repo): |
437 """import markers from an .hg/obsolete-relations file""" |
969 """import markers from an .hg/obsolete-relations file""" |
438 cnt = 0 |
970 cnt = 0 |
517 if not some: |
1049 if not some: |
518 ui.warn('nothing to do\n') |
1050 ui.warn('nothing to do\n') |
519 ui.status('%i obsolete marker converted\n' % cnt) |
1051 ui.status('%i obsolete marker converted\n' % cnt) |
520 if err: |
1052 if err: |
521 ui.write_err('%i conversion failed. check you graph!\n' % err) |
1053 ui.write_err('%i conversion failed. check you graph!\n' % err) |
522 |
|
523 @command('debugsuccessors', [], '') |
|
524 def cmddebugsuccessors(ui, repo): |
|
525 """dump obsolete changesets and their successors |
|
526 |
|
527 Each line matches an existing marker, the first identifier is the |
|
528 obsolete changeset identifier, followed by it successors. |
|
529 """ |
|
530 lock = repo.lock() |
|
531 try: |
|
532 allsuccessors = repo.obsstore.precursors |
|
533 for old in sorted(allsuccessors): |
|
534 successors = [sorted(m[1]) for m in allsuccessors[old]] |
|
535 for i, group in enumerate(sorted(successors)): |
|
536 ui.write('%s' % short(old)) |
|
537 for new in group: |
|
538 ui.write(' %s' % short(new)) |
|
539 ui.write('\n') |
|
540 finally: |
|
541 lock.release() |
|
542 |
|
543 ### Altering existing command |
|
544 ############################# |
|
545 |
|
546 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): |
|
547 res = origfn(ui, repo, *args, **opts) |
|
548 if repo['.'].obsolete(): |
|
549 ui.warn(_('Working directory parent is obsolete\n')) |
|
550 return res |
|
551 |
|
552 def warnobserrors(orig, ui, repo, *args, **kwargs): |
|
553 """display warning is the command resulted in more instable changeset""" |
|
554 priorunstables = len(repo.revs('unstable()')) |
|
555 priorlatecomers = len(repo.revs('latecomer()')) |
|
556 priorconflictings = len(repo.revs('conflicting()')) |
|
557 #print orig, priorunstables |
|
558 #print len(repo.revs('secret() - obsolete()')) |
|
559 try: |
|
560 return orig(ui, repo, *args, **kwargs) |
|
561 finally: |
|
562 newunstables = len(repo.revs('unstable()')) - priorunstables |
|
563 newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers |
|
564 newconflictings = len(repo.revs('conflicting()')) - priorconflictings |
|
565 #print orig, newunstables |
|
566 #print len(repo.revs('secret() - obsolete()')) |
|
567 if newunstables > 0: |
|
568 ui.warn(_('%i new unstables changesets\n') % newunstables) |
|
569 if newlatecomers > 0: |
|
570 ui.warn(_('%i new latecomers changesets\n') % newlatecomers) |
|
571 if newconflictings > 0: |
|
572 ui.warn(_('%i new conflictings changesets\n') % newconflictings) |
|
573 |
|
574 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): |
|
575 oldnode = old.node() |
|
576 new = orig(ui, repo, commitfunc, old, *args, **kwargs) |
|
577 if new != oldnode: |
|
578 lock = repo.lock() |
|
579 try: |
|
580 tr = repo.transaction('post-amend-obst') |
|
581 try: |
|
582 meta = { |
|
583 'date': '%i %i' % util.makedate(), |
|
584 'user': ui.username(), |
|
585 } |
|
586 repo.obsstore.create(tr, oldnode, [new], 0, meta) |
|
587 tr.close() |
|
588 repo._clearobsoletecache() |
|
589 finally: |
|
590 tr.release() |
|
591 finally: |
|
592 lock.release() |
|
593 return new |
|
594 |
|
595 def uisetup(ui): |
|
596 extensions.wrapcommand(commands.table, "update", wrapmayobsoletewc) |
|
597 extensions.wrapcommand(commands.table, "pull", wrapmayobsoletewc) |
|
598 extensions.wrapfunction(cmdutil, 'amend', wrapcmdutilamend) |
|
599 extensions.wrapfunction(discovery, 'checkheads', wrapcheckheads) |
|
600 extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache) |
|
601 |
|
602 ### serialisation |
|
603 ############################# |
|
604 |
|
605 def _obsserialise(obssubrels, flike): |
|
606 """serialise an obsolete relation mapping in a plain text one |
|
607 |
|
608 this is for subject -> [objects] mapping |
|
609 |
|
610 format is:: |
|
611 |
|
612 <subject-full-hex> <object-full-hex>\n""" |
|
613 for sub, objs in obssubrels.iteritems(): |
|
614 for obj in objs: |
|
615 if sub is None: |
|
616 sub = nullid |
|
617 flike.write('%s %s\n' % (hex(sub), hex(obj))) |
|
618 |
|
619 def _obsdeserialise(flike): |
|
620 """read a file like object serialised with _obsserialise |
|
621 |
|
622 this desierialize into a {subject -> objects} mapping""" |
|
623 rels = {} |
|
624 for line in flike: |
|
625 subhex, objhex = line.split() |
|
626 subnode = bin(subhex) |
|
627 if subnode == nullid: |
|
628 subnode = None |
|
629 rels.setdefault( subnode, set()).add(bin(objhex)) |
|
630 return rels |
|
631 |
|
632 ### diagnostique tools |
|
633 ############################# |
|
634 |
|
635 def unstables(repo): |
|
636 """Return all unstable changeset""" |
|
637 return scmutil.revrange(repo, ['obsolete():: and (not obsolete())']) |
|
638 |
|
639 def newerversion(repo, obs): |
|
640 """Return the newer version of an obsolete changeset""" |
|
641 toproceed = set([(obs,)]) |
|
642 # XXX known optimization available |
|
643 newer = set() |
|
644 objectrels = repo.obsstore.precursors |
|
645 while toproceed: |
|
646 current = toproceed.pop() |
|
647 assert len(current) <= 1, 'splitting not handled yet. %r' % current |
|
648 current = [n for n in current if n != nullid] |
|
649 if current: |
|
650 n, = current |
|
651 if n in objectrels: |
|
652 markers = objectrels[n] |
|
653 for mark in markers: |
|
654 toproceed.add(tuple(mark[1])) |
|
655 else: |
|
656 newer.add(tuple(current)) |
|
657 else: |
|
658 newer.add(()) |
|
659 return sorted(newer) |
|
660 |
|
661 ### repo subclassing |
|
662 ############################# |
|
663 |
|
664 def reposetup(ui, repo): |
|
665 if not repo.local(): |
|
666 return |
|
667 |
|
668 if not util.safehasattr(repo.opener, 'tryread'): |
|
669 raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)') |
|
670 opush = repo.push |
|
671 o_updatebranchcache = repo.updatebranchcache |
|
672 |
|
673 # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\ |
|
674 if util.safehasattr(repo, '_journalfiles'): # Hg 2.2 |
|
675 o_journalfiles = repo._journalfiles |
|
676 o_writejournal = repo._writejournal |
|
677 o_hook = repo.hook |
|
678 |
|
679 |
|
680 class obsoletingrepo(repo.__class__): |
|
681 |
|
682 # workaround |
|
683 def hook(self, name, throw=False, **args): |
|
684 if 'pushkey' in name: |
|
685 args.pop('new') |
|
686 args.pop('old') |
|
687 return o_hook(name, throw=False, **args) |
|
688 |
|
689 ### Public method |
|
690 def obsoletedby(self, node): |
|
691 """return the set of node that make <node> obsolete (obj)""" |
|
692 others = set() |
|
693 for marker in self.obsstore.precursors.get(node, []): |
|
694 others.update(marker[1]) |
|
695 return others |
|
696 |
|
697 def obsolete(self, node): |
|
698 """return the set of node that <node> make obsolete (sub)""" |
|
699 return set(marker[0] for marker in self.obsstore.successors.get(node, [])) |
|
700 |
|
701 @util.propertycache |
|
702 def _obsoleteset(self): |
|
703 """the set of obsolete revision""" |
|
704 obs = set() |
|
705 nm = self.changelog.nodemap |
|
706 for prec in self.obsstore.precursors: |
|
707 rev = nm.get(prec) |
|
708 if rev is not None: |
|
709 obs.add(rev) |
|
710 return obs |
|
711 |
|
712 @util.propertycache |
|
713 def _unstableset(self): |
|
714 """the set of non obsolete revision with obsolete parent""" |
|
715 return set(self.revs('(obsolete()::) - obsolete()')) |
|
716 |
|
717 @util.propertycache |
|
718 def _suspendedset(self): |
|
719 """the set of obsolete parent with non obsolete descendant""" |
|
720 return set(self.revs('obsolete() and obsolete()::unstable()')) |
|
721 |
|
722 @util.propertycache |
|
723 def _extinctset(self): |
|
724 """the set of obsolete parent without non obsolete descendant""" |
|
725 return set(self.revs('obsolete() - obsolete()::unstable()')) |
|
726 |
|
727 @util.propertycache |
|
728 def _latecomerset(self): |
|
729 """the set of rev trying to obsolete public revision""" |
|
730 query = 'allsuccessors(public()) - obsolete() - public()' |
|
731 return set(self.revs(query)) |
|
732 |
|
733 @util.propertycache |
|
734 def _conflictingset(self): |
|
735 """the set of rev trying to obsolete public revision""" |
|
736 conflicting = set() |
|
737 obsstore = self.obsstore |
|
738 newermap = {} |
|
739 for ctx in self.set('(not public()) - obsolete()'): |
|
740 prec = obsstore.successors.get(ctx.node(), ()) |
|
741 toprocess = set(prec) |
|
742 while toprocess: |
|
743 prec = toprocess.pop()[0] |
|
744 if prec not in newermap: |
|
745 newermap[prec] = newerversion(self, prec) |
|
746 newer = [n for n in newermap[prec] if n] # filter kill |
|
747 if len(newer) > 1: |
|
748 conflicting.add(ctx.rev()) |
|
749 break |
|
750 toprocess.update(obsstore.successors.get(prec, ())) |
|
751 return conflicting |
|
752 |
|
753 def _clearobsoletecache(self): |
|
754 if '_obsoleteset' in vars(self): |
|
755 del self._obsoleteset |
|
756 self._clearunstablecache() |
|
757 |
|
758 def updatebranchcache(self): |
|
759 o_updatebranchcache() |
|
760 self._clearunstablecache() |
|
761 |
|
762 def _clearunstablecache(self): |
|
763 if '_unstableset' in vars(self): |
|
764 del self._unstableset |
|
765 if '_suspendedset' in vars(self): |
|
766 del self._suspendedset |
|
767 if '_extinctset' in vars(self): |
|
768 del self._extinctset |
|
769 if '_latecomerset' in vars(self): |
|
770 del self._latecomerset |
|
771 if '_conflictingset' in vars(self): |
|
772 del self._conflictingset |
|
773 |
|
774 def addobsolete(self, sub, obj): |
|
775 """Add a relation marking that node <sub> is a new version of <obj>""" |
|
776 assert sub != obj |
|
777 if not repo[obj].phase(): |
|
778 if sub is None: |
|
779 self.ui.warn( |
|
780 _("trying to kill immutable changeset %(obj)s\n") |
|
781 % {'obj': short(obj)}) |
|
782 if sub is not None: |
|
783 self.ui.warn( |
|
784 _("%(sub)s try to obsolete immutable changeset %(obj)s\n") |
|
785 % {'sub': short(sub), 'obj': short(obj)}) |
|
786 lock = self.lock() |
|
787 try: |
|
788 tr = self.transaction('add-obsolete') |
|
789 try: |
|
790 meta = { |
|
791 'date': '%i %i' % util.makedate(), |
|
792 'user': ui.username(), |
|
793 } |
|
794 subs = (sub == nullid) and [] or [sub] |
|
795 mid = self.obsstore.create(tr, obj, subs, 0, meta) |
|
796 tr.close() |
|
797 self._clearobsoletecache() |
|
798 return mid |
|
799 finally: |
|
800 tr.release() |
|
801 finally: |
|
802 lock.release() |
|
803 |
|
804 def addcollapsedobsolete(self, oldnodes, newnode): |
|
805 """Mark oldnodes as collapsed into newnode.""" |
|
806 # Assume oldnodes are all descendants of a single rev |
|
807 rootrevs = self.revs('roots(%ln)', oldnodes) |
|
808 assert len(rootrevs) == 1, rootrevs |
|
809 #rootnode = self[rootrevs[0]].node() |
|
810 for n in oldnodes: |
|
811 self.addobsolete(newnode, n) |
|
812 |
|
813 ### pull // push support |
|
814 |
|
815 def push(self, remote, *args, **opts): |
|
816 """wrapper around pull that pull obsolete relation""" |
|
817 try: |
|
818 result = opush(remote, *args, **opts) |
|
819 except util.Abort, ex: |
|
820 hint = _("use 'hg stabilize' to get a stable history " |
|
821 "or --force to ignore warnings") |
|
822 if (len(ex.args) >= 1 |
|
823 and ex.args[0].startswith('push includes ') |
|
824 and ex.hint is None): |
|
825 ex.hint = hint |
|
826 raise |
|
827 return result |
|
828 |
|
829 |
|
830 repo.__class__ = obsoletingrepo |
|
831 for arg in sys.argv: |
|
832 if 'debugc' in arg: |
|
833 break |
|
834 else: |
|
835 data = repo.opener.tryread('obsolete-relations') |
|
836 if not data: |
|
837 data = repo.sopener.tryread('obsoletemarkers') |
|
838 if data: |
|
839 raise util.Abort('old format of obsolete marker detected!\n' |
|
840 'run `hg debugconvertobsolete` once.') |
|