Mercurial > hg
view doc/gendoc.py @ 32979:66117dae87f9
patch: rewrite reversehunks (issue5337)
The old reversehunks code accesses "crecord.uihunk._hunk", which is the raw
recordhunk without crecord selection information, therefore "revert -i"
cannot revert individual lines, aka. issue5337.
The patch rewrites related logic to return the right reverse hunk for
revert. Namely,
1. "fromline" and "toline" are correctly swapped [1]
2. crecord.uihunk generates a correct reverse hunk [2]
Besides, reversehunks(hunks) will no longer modify its input "hunks", which
is more expected.
[1]: To explain why "fromline" and "toline" need to be swapped, take the
following example:
$ cat > a <<EOF
> 1
> 2
> 3
> 4
> EOF
$ cat > b <<EOF
> 2
> 3
> 5
> EOF
$ diff a b
1d0 <---- "1" is "fromline" and "0" is "toline"
< 1 and they are swapped if diff from the reversed direction
4c3 |
< 4 |
--- |
> 5 |
|
$ diff b a |
0a1 <---------+
> 1
3c4 <---- also "4c3" gets swapped to "3c4"
< 5
---
> 4
[2]: This is a bit tricky.
For example, given a file which is empty in working parent but has 3 lines
in working copy, and the user selection:
select hunk to discard
[x] +1
[ ] +2
[x] +3
The user intent is to drop "1" and "3" in working copy but keep "2", so the
reverse patch would be something like:
-1
2 (2 is a "context line")
-3
We cannot just take all selected lines and swap "-" and "+", which will be:
-1
-3
That patch won't apply because of "2". So the correct way is to insert "2"
as a "context line" by inserting it first then deleting it:
-2
+2
Therefore, the correct revert patch is:
-1
-2
+2
-3
It could be reordered to look more like a common diff hunk:
-1
-2
-3
+2
Note: It's possible to return multiple hunks so there won't be lines like
"-2", "+2". But the current implementation is much simpler.
For deletions, like the working parent has "1\n2\n3\n" and it was changed to
empty in working copy:
select hunk to discard
[x] -1
[ ] -2
[x] -3
The user intent is to drop the deletion of 1 and 3 (in other words, keep
those lines), but still delete "2".
The reverse patch is meant to be applied to working copy which is empty.
So the patch would be:
+1
+3
That is to say, there is no need to special handle the unselected "2" like
the above insertion case.
author | Jun Wu <quark@fb.com> |
---|---|
date | Tue, 20 Jun 2017 23:22:38 -0700 |
parents | ff874d34c856 |
children | 170926caf44c |
line wrap: on
line source
#!/usr/bin/env python """usage: %s DOC ... where DOC is the name of a document """ from __future__ import absolute_import import os import sys import textwrap # This script is executed during installs and may not have C extensions # available. Relax C module requirements. os.environ['HGMODULEPOLICY'] = 'allow' # import from the live mercurial repo sys.path.insert(0, "..") from mercurial import demandimport; demandimport.enable() # Load util so that the locale path is set by i18n.setdatapath() before # calling _(). from mercurial import util util.datapath from mercurial import ( commands, extensions, help, minirst, ui as uimod, ) from mercurial.i18n import ( gettext, _, ) table = commands.table globalopts = commands.globalopts helptable = help.helptable loaddoc = help.loaddoc def get_desc(docstr): if not docstr: return "", "" # sanitize docstr = docstr.strip("\n") docstr = docstr.rstrip() shortdesc = docstr.splitlines()[0].strip() i = docstr.find("\n") if i != -1: desc = docstr[i + 2:] else: desc = shortdesc desc = textwrap.dedent(desc) return (shortdesc, desc) def get_opts(opts): for opt in opts: if len(opt) == 5: shortopt, longopt, default, desc, optlabel = opt else: shortopt, longopt, default, desc = opt optlabel = _("VALUE") allopts = [] if shortopt: allopts.append("-%s" % shortopt) if longopt: allopts.append("--%s" % longopt) if isinstance(default, list): allopts[-1] += " <%s[+]>" % optlabel elif (default is not None) and not isinstance(default, bool): allopts[-1] += " <%s>" % optlabel if '\n' in desc: # only remove line breaks and indentation desc = ' '.join(l.lstrip() for l in desc.split('\n')) desc += default and _(" (default: %s)") % default or "" yield (", ".join(allopts), desc) def get_cmd(cmd, cmdtable): d = {} attr = cmdtable[cmd] cmds = cmd.lstrip("^").split("|") d['cmd'] = cmds[0] d['aliases'] = cmd.split("|")[1:] d['desc'] = get_desc(gettext(attr[0].__doc__)) d['opts'] = list(get_opts(attr[1])) s = 'hg ' + cmds[0] if len(attr) > 2: if not attr[2].startswith('hg'): s += ' ' + attr[2] else: s = attr[2] d['synopsis'] = s.strip() return d def showdoc(ui): # print options ui.write(minirst.section(_("Options"))) multioccur = False for optstr, desc in get_opts(globalopts): ui.write("%s\n %s\n\n" % (optstr, desc)) if optstr.endswith("[+]>"): multioccur = True if multioccur: ui.write(_("\n[+] marked option can be specified multiple times\n")) ui.write("\n") # print cmds ui.write(minirst.section(_("Commands"))) commandprinter(ui, table, minirst.subsection) # print help topics # The config help topic is included in the hgrc.5 man page. helpprinter(ui, helptable, minirst.section, exclude=['config']) ui.write(minirst.section(_("Extensions"))) ui.write(_("This section contains help for extensions that are " "distributed together with Mercurial. Help for other " "extensions is available in the help system.")) ui.write(("\n\n" ".. contents::\n" " :class: htmlonly\n" " :local:\n" " :depth: 1\n\n")) for extensionname in sorted(allextensionnames()): mod = extensions.load(ui, extensionname, None) ui.write(minirst.subsection(extensionname)) ui.write("%s\n\n" % gettext(mod.__doc__)) cmdtable = getattr(mod, 'cmdtable', None) if cmdtable: ui.write(minirst.subsubsection(_('Commands'))) commandprinter(ui, cmdtable, minirst.subsubsubsection) def showtopic(ui, topic): extrahelptable = [ (["common"], '', loaddoc('common')), (["hg.1"], '', loaddoc('hg.1')), (["hg-ssh.8"], '', loaddoc('hg-ssh.8')), (["hgignore.5"], '', loaddoc('hgignore.5')), (["hgrc.5"], '', loaddoc('hgrc.5')), (["hgignore.5.gendoc"], '', loaddoc('hgignore')), (["hgrc.5.gendoc"], '', loaddoc('config')), ] helpprinter(ui, helptable + extrahelptable, None, include=[topic]) def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]): for names, sec, doc in helptable: if exclude and names[0] in exclude: continue if include and names[0] not in include: continue for name in names: ui.write(".. _%s:\n" % name) ui.write("\n") if sectionfunc: ui.write(sectionfunc(sec)) if callable(doc): doc = doc(ui) ui.write(doc) ui.write("\n") def commandprinter(ui, cmdtable, sectionfunc): h = {} for c, attr in cmdtable.items(): f = c.split("|")[0] f = f.lstrip("^") h[f] = c cmds = h.keys() cmds.sort() for f in cmds: if f.startswith("debug"): continue d = get_cmd(h[f], cmdtable) ui.write(sectionfunc(d['cmd'])) # short description ui.write(d['desc'][0]) # synopsis ui.write("::\n\n") synopsislines = d['synopsis'].splitlines() for line in synopsislines: # some commands (such as rebase) have a multi-line # synopsis ui.write(" %s\n" % line) ui.write('\n') # description ui.write("%s\n\n" % d['desc'][1]) # options opt_output = list(d['opts']) if opt_output: opts_len = max([len(line[0]) for line in opt_output]) ui.write(_("Options:\n\n")) multioccur = False for optstr, desc in opt_output: if desc: s = "%-*s %s" % (opts_len, optstr, desc) else: s = optstr ui.write("%s\n" % s) if optstr.endswith("[+]>"): multioccur = True if multioccur: ui.write(_("\n[+] marked option can be specified" " multiple times\n")) ui.write("\n") # aliases if d['aliases']: ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases'])) def allextensionnames(): return extensions.enabled().keys() + extensions.disabled().keys() if __name__ == "__main__": doc = 'hg.1.gendoc' if len(sys.argv) > 1: doc = sys.argv[1] ui = uimod.ui.load() if doc == 'hg.1.gendoc': showdoc(ui) else: showtopic(ui, sys.argv[1])