hgext/histedit.py
branchstable
changeset 49366 288de6f5d724
parent 49284 d44e3c45f0e4
child 49703 b8385bbeefdc
child 49891 e90767a71dc0
equal deleted inserted replaced
49364:e8ea403b1c46 49366:288de6f5d724
   188   [histedit]
   188   [histedit]
   189   singletransaction = True
   189   singletransaction = True
   190 
   190 
   191 """
   191 """
   192 
   192 
   193 from __future__ import absolute_import
       
   194 
   193 
   195 # chistedit dependencies that are not available everywhere
   194 # chistedit dependencies that are not available everywhere
   196 try:
   195 try:
   197     import fcntl
   196     import fcntl
   198     import termios
   197     import termios
   199 except ImportError:
   198 except ImportError:
   200     fcntl = None
   199     fcntl = None
   201     termios = None
   200     termios = None
   202 
   201 
       
   202 import binascii
   203 import functools
   203 import functools
   204 import os
   204 import os
       
   205 import pickle
   205 import struct
   206 import struct
   206 
   207 
   207 from mercurial.i18n import _
   208 from mercurial.i18n import _
   208 from mercurial.pycompat import (
   209 from mercurial.pycompat import (
   209     getattr,
   210     getattr,
   243     dateutil,
   244     dateutil,
   244     stringutil,
   245     stringutil,
   245     urlutil,
   246     urlutil,
   246 )
   247 )
   247 
   248 
   248 pickle = util.pickle
       
   249 cmdtable = {}
   249 cmdtable = {}
   250 command = registrar.command(cmdtable)
   250 command = registrar.command(cmdtable)
   251 
   251 
   252 configtable = {}
   252 configtable = {}
   253 configitem = registrar.configitem(configtable)
   253 configitem = registrar.configitem(configtable)
   350     lines = (intro % (first, last)).split(b'\n') + actions + hints
   350     lines = (intro % (first, last)).split(b'\n') + actions + hints
   351 
   351 
   352     return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
   352     return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
   353 
   353 
   354 
   354 
   355 class histeditstate(object):
   355 class histeditstate:
   356     def __init__(self, repo):
   356     def __init__(self, repo):
   357         self.repo = repo
   357         self.repo = repo
   358         self.actions = None
   358         self.actions = None
   359         self.keep = None
   359         self.keep = None
   360         self.topmost = None
   360         self.topmost = None
   453 
   453 
   454         # Rules
   454         # Rules
   455         rules = []
   455         rules = []
   456         rulelen = int(lines[index])
   456         rulelen = int(lines[index])
   457         index += 1
   457         index += 1
   458         for i in pycompat.xrange(rulelen):
   458         for i in range(rulelen):
   459             ruleaction = lines[index]
   459             ruleaction = lines[index]
   460             index += 1
   460             index += 1
   461             rule = lines[index]
   461             rule = lines[index]
   462             index += 1
   462             index += 1
   463             rules.append((ruleaction, rule))
   463             rules.append((ruleaction, rule))
   464 
   464 
   465         # Replacements
   465         # Replacements
   466         replacements = []
   466         replacements = []
   467         replacementlen = int(lines[index])
   467         replacementlen = int(lines[index])
   468         index += 1
   468         index += 1
   469         for i in pycompat.xrange(replacementlen):
   469         for i in range(replacementlen):
   470             replacement = lines[index]
   470             replacement = lines[index]
   471             original = bin(replacement[:40])
   471             original = bin(replacement[:40])
   472             succ = [
   472             succ = [
   473                 bin(replacement[i : i + 40])
   473                 bin(replacement[i : i + 40])
   474                 for i in range(40, len(replacement), 40)
   474                 for i in range(40, len(replacement), 40)
   489 
   489 
   490     def inprogress(self):
   490     def inprogress(self):
   491         return self.repo.vfs.exists(b'histedit-state')
   491         return self.repo.vfs.exists(b'histedit-state')
   492 
   492 
   493 
   493 
   494 class histeditaction(object):
   494 class histeditaction:
   495     def __init__(self, state, node):
   495     def __init__(self, state, node):
   496         self.state = state
   496         self.state = state
   497         self.repo = state.repo
   497         self.repo = state.repo
   498         self.node = node
   498         self.node = node
   499 
   499 
   503         ruleid = rule.strip().split(b' ', 1)[0]
   503         ruleid = rule.strip().split(b' ', 1)[0]
   504         # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
   504         # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
   505         # Check for validation of rule ids and get the rulehash
   505         # Check for validation of rule ids and get the rulehash
   506         try:
   506         try:
   507             rev = bin(ruleid)
   507             rev = bin(ruleid)
   508         except TypeError:
   508         except binascii.Error:
   509             try:
   509             try:
   510                 _ctx = scmutil.revsingle(state.repo, ruleid)
   510                 _ctx = scmutil.revsingle(state.repo, ruleid)
   511                 rulehash = _ctx.hex()
   511                 rulehash = _ctx.hex()
   512                 rev = bin(rulehash)
   512                 rev = bin(rulehash)
   513             except error.RepoLookupError:
   513             except error.RepoLookupError:
   551             {(b'templatealias', b'label(l,x)'): b"x"}, b'histedit'
   551             {(b'templatealias', b'label(l,x)'): b"x"}, b'histedit'
   552         ):
   552         ):
   553             summary = cmdutil.rendertemplate(
   553             summary = cmdutil.rendertemplate(
   554                 ctx, ui.config(b'histedit', b'summary-template')
   554                 ctx, ui.config(b'histedit', b'summary-template')
   555             )
   555             )
   556         # Handle the fact that `''.splitlines() => []`
   556         line = b'%s %s %s' % (self.verb, ctx, stringutil.firstline(summary))
   557         summary = summary.splitlines()[0] if summary else b''
       
   558         line = b'%s %s %s' % (self.verb, ctx, summary)
       
   559         # trim to 75 columns by default so it's not stupidly wide in my editor
   557         # trim to 75 columns by default so it's not stupidly wide in my editor
   560         # (the 5 more are left for verb)
   558         # (the 5 more are left for verb)
   561         maxlen = self.repo.ui.configint(b'histedit', b'linelen')
   559         maxlen = self.repo.ui.configint(b'histedit', b'linelen')
   562         maxlen = max(maxlen, 22)  # avoid truncating hash
   560         maxlen = max(maxlen, 22)  # avoid truncating hash
   563         return stringutil.ellipsis(line, maxlen)
   561         return stringutil.ellipsis(line, maxlen)
  1141 
  1139 
  1142 def screen_size():
  1140 def screen_size():
  1143     return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b'    '))
  1141     return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b'    '))
  1144 
  1142 
  1145 
  1143 
  1146 class histeditrule(object):
  1144 class histeditrule:
  1147     def __init__(self, ui, ctx, pos, action=b'pick'):
  1145     def __init__(self, ui, ctx, pos, action=b'pick'):
  1148         self.ui = ui
  1146         self.ui = ui
  1149         self.ctx = ctx
  1147         self.ctx = ctx
  1150         self.action = action
  1148         self.action = action
  1151         self.origpos = pos
  1149         self.origpos = pos
  1191         if summary:
  1189         if summary:
  1192             return summary
  1190             return summary
  1193         # This is split off from the prefix property so that we can
  1191         # This is split off from the prefix property so that we can
  1194         # separately make the description for 'roll' red (since it
  1192         # separately make the description for 'roll' red (since it
  1195         # will get discarded).
  1193         # will get discarded).
  1196         return self.ctx.description().splitlines()[0].strip()
  1194         return stringutil.firstline(self.ctx.description())
  1197 
  1195 
  1198     def checkconflicts(self, other):
  1196     def checkconflicts(self, other):
  1199         if other.pos > self.pos and other.origpos <= self.origpos:
  1197         if other.pos > self.pos and other.origpos <= self.origpos:
  1200             if set(other.ctx.files()) & set(self.ctx.files()) != set():
  1198             if set(other.ctx.files()) & set(self.ctx.files()) != set():
  1201                 self.conflicts.append(other)
  1199                 self.conflicts.append(other)
  1241     if len(line) <= n:
  1239     if len(line) <= n:
  1242         return line
  1240         return line
  1243     return line[: n - 2] + b' >'
  1241     return line[: n - 2] + b' >'
  1244 
  1242 
  1245 
  1243 
  1246 class _chistedit_state(object):
  1244 class _chistedit_state:
  1247     def __init__(
  1245     def __init__(
  1248         self,
  1246         self,
  1249         repo,
  1247         repo,
  1250         rules,
  1248         rules,
  1251         stdscr,
  1249         stdscr,
  1290 
  1288 
  1291         bms = self.repo.nodebookmarks(ctx.node())
  1289         bms = self.repo.nodebookmarks(ctx.node())
  1292         line = b"bookmark:  %s" % b' '.join(bms)
  1290         line = b"bookmark:  %s" % b' '.join(bms)
  1293         win.addstr(3, 1, line[:length])
  1291         win.addstr(3, 1, line[:length])
  1294 
  1292 
  1295         line = b"summary:   %s" % (ctx.description().splitlines()[0])
  1293         line = b"summary:   %s" % stringutil.firstline(ctx.description())
  1296         win.addstr(4, 1, line[:length])
  1294         win.addstr(4, 1, line[:length])
  1297 
  1295 
  1298         line = b"files:     "
  1296         line = b"files:     "
  1299         win.addstr(5, 1, line)
  1297         win.addstr(5, 1, line)
  1300         fnx = 1 + len(line)
  1298         fnx = 1 + len(line)
  1574         rules[new_rule_pos].pos = new_rule_pos
  1572         rules[new_rule_pos].pos = new_rule_pos
  1575         rules[old_rule_pos].pos = old_rule_pos
  1573         rules[old_rule_pos].pos = old_rule_pos
  1576 
  1574 
  1577         start = min(old_rule_pos, new_rule_pos)
  1575         start = min(old_rule_pos, new_rule_pos)
  1578         end = max(old_rule_pos, new_rule_pos)
  1576         end = max(old_rule_pos, new_rule_pos)
  1579         for r in pycompat.xrange(start, end + 1):
  1577         for r in range(start, end + 1):
  1580             rules[new_rule_pos].checkconflicts(rules[r])
  1578             rules[new_rule_pos].checkconflicts(rules[r])
  1581             rules[old_rule_pos].checkconflicts(rules[r])
  1579             rules[old_rule_pos].checkconflicts(rules[r])
  1582 
  1580 
  1583         if self.selected:
  1581         if self.selected:
  1584             self.make_selection(newpos)
  1582             self.make_selection(newpos)
  2100     """This action runs when histedit is finishing its session"""
  2098     """This action runs when histedit is finishing its session"""
  2101     mergemod.update(repo[state.parentctxnode])
  2099     mergemod.update(repo[state.parentctxnode])
  2102 
  2100 
  2103     mapping, tmpnodes, created, ntm = processreplacement(state)
  2101     mapping, tmpnodes, created, ntm = processreplacement(state)
  2104     if mapping:
  2102     if mapping:
  2105         for prec, succs in pycompat.iteritems(mapping):
  2103         for prec, succs in mapping.items():
  2106             if not succs:
  2104             if not succs:
  2107                 ui.debug(b'histedit: %s is dropped\n' % short(prec))
  2105                 ui.debug(b'histedit: %s is dropped\n' % short(prec))
  2108             else:
  2106             else:
  2109                 ui.debug(
  2107                 ui.debug(
  2110                     b'histedit: %s is replaced by %s\n'
  2108                     b'histedit: %s is replaced by %s\n'
  2138     fl = fm.formatlist
  2136     fl = fm.formatlist
  2139     fd = fm.formatdict
  2137     fd = fm.formatdict
  2140     nodechanges = fd(
  2138     nodechanges = fd(
  2141         {
  2139         {
  2142             hf(oldn): fl([hf(n) for n in newn], name=b'node')
  2140             hf(oldn): fl([hf(n) for n in newn], name=b'node')
  2143             for oldn, newn in pycompat.iteritems(mapping)
  2141             for oldn, newn in mapping.items()
  2144         },
  2142         },
  2145         key=b"oldnode",
  2143         key=b"oldnode",
  2146         value=b"newnodes",
  2144         value=b"newnodes",
  2147     )
  2145     )
  2148     fm.data(nodechanges=nodechanges)
  2146     fm.data(nodechanges=nodechanges)
  2320         )
  2318         )
  2321     state.backupfile = backupfile
  2319     state.backupfile = backupfile
  2322 
  2320 
  2323 
  2321 
  2324 def _getsummary(ctx):
  2322 def _getsummary(ctx):
  2325     # a common pattern is to extract the summary but default to the empty
  2323     return stringutil.firstline(ctx.description())
  2326     # string
       
  2327     summary = ctx.description() or b''
       
  2328     if summary:
       
  2329         summary = summary.splitlines()[0]
       
  2330     return summary
       
  2331 
  2324 
  2332 
  2325 
  2333 def bootstrapcontinue(ui, state, opts):
  2326 def bootstrapcontinue(ui, state, opts):
  2334     repo = state.repo
  2327     repo = state.repo
  2335 
  2328 
  2386                     act.verb = fword
  2379                     act.verb = fword
  2387                     # get the target summary
  2380                     # get the target summary
  2388                     tsum = summary[len(fword) + 1 :].lstrip()
  2381                     tsum = summary[len(fword) + 1 :].lstrip()
  2389                     # safe but slow: reverse iterate over the actions so we
  2382                     # safe but slow: reverse iterate over the actions so we
  2390                     # don't clash on two commits having the same summary
  2383                     # don't clash on two commits having the same summary
  2391                     for na, l in reversed(list(pycompat.iteritems(newact))):
  2384                     for na, l in reversed(list(newact.items())):
  2392                         actx = repo[na.node]
  2385                         actx = repo[na.node]
  2393                         asum = _getsummary(actx)
  2386                         asum = _getsummary(actx)
  2394                         if asum == tsum:
  2387                         if asum == tsum:
  2395                             added = True
  2388                             added = True
  2396                             l.append(act)
  2389                             l.append(act)
  2399             if not added:
  2392             if not added:
  2400                 newact[act] = []
  2393                 newact[act] = []
  2401 
  2394 
  2402         # copy over and flatten the new list
  2395         # copy over and flatten the new list
  2403         actions = []
  2396         actions = []
  2404         for na, l in pycompat.iteritems(newact):
  2397         for na, l in newact.items():
  2405             actions.append(na)
  2398             actions.append(na)
  2406             actions += l
  2399             actions += l
  2407 
  2400 
  2408     rules = b'\n'.join([act.torule() for act in actions])
  2401     rules = b'\n'.join([act.torule() for act in actions])
  2409     rules += b'\n\n'
  2402     rules += b'\n\n'