Show More
@@ -42,6 +42,7 b' from . import (' | |||
|
42 | 42 | scmutil, |
|
43 | 43 | smartset, |
|
44 | 44 | subrepoutil, |
|
45 | templatekw, | |
|
45 | 46 | templater, |
|
46 | 47 | util, |
|
47 | 48 | vfs as vfsmod, |
@@ -891,46 +892,98 b' def getcommiteditor(edit=False, finishde' | |||
|
891 | 892 | else: |
|
892 | 893 | return commiteditor |
|
893 | 894 | |
|
894 | def makefilename(ctx, pat, | |
|
895 | total=None, seqno=None, revwidth=None, pathname=None): | |
|
895 | def rendertemplate(ctx, tmpl, props=None): | |
|
896 | """Expand a literal template 'tmpl' byte-string against one changeset | |
|
897 | ||
|
898 | Each props item must be a stringify-able value or a callable returning | |
|
899 | such value, i.e. no bare list nor dict should be passed. | |
|
900 | """ | |
|
901 | repo = ctx.repo() | |
|
902 | tres = formatter.templateresources(repo.ui, repo) | |
|
903 | t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords, | |
|
904 | resources=tres) | |
|
905 | mapping = {'ctx': ctx, 'revcache': {}} | |
|
906 | if props: | |
|
907 | mapping.update(props) | |
|
908 | return t.render(mapping) | |
|
909 | ||
|
910 | def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): | |
|
911 | r"""Convert old-style filename format string to template string | |
|
912 | ||
|
913 | >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) | |
|
914 | 'foo-{reporoot|basename}-{seqno}.patch' | |
|
915 | >>> _buildfntemplate(b'%R{tags % "{tag}"}%H') | |
|
916 | '{rev}{tags % "{tag}"}{node}' | |
|
917 | ||
|
918 | '\' in outermost strings has to be escaped because it is a directory | |
|
919 | separator on Windows: | |
|
920 | ||
|
921 | >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0) | |
|
922 | 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch' | |
|
923 | >>> _buildfntemplate(b'\\\\foo\\bar.patch') | |
|
924 | '\\\\\\\\foo\\\\bar.patch' | |
|
925 | >>> _buildfntemplate(b'\\{tags % "{tag}"}') | |
|
926 | '\\\\{tags % "{tag}"}' | |
|
927 | ||
|
928 | but inner strings follow the template rules (i.e. '\' is taken as an | |
|
929 | escape character): | |
|
930 | ||
|
931 | >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0) | |
|
932 | '{"c:\\tmp"}' | |
|
933 | """ | |
|
896 | 934 | expander = { |
|
897 |
'H': |
|
|
898 |
'R': |
|
|
899 |
'h': |
|
|
900 | 'm': lambda: re.sub('[^\w]', '_', | |
|
901 | ctx.description().strip().splitlines()[0]), | |
|
902 | 'r': lambda: ('%d' % ctx.rev()).zfill(revwidth or 0), | |
|
903 | '%': lambda: '%', | |
|
904 | 'b': lambda: os.path.basename(ctx.repo().root), | |
|
905 | } | |
|
935 | b'H': b'{node}', | |
|
936 | b'R': b'{rev}', | |
|
937 | b'h': b'{node|short}', | |
|
938 | b'm': br'{sub(r"[^\w]", "_", desc|firstline)}', | |
|
939 | b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}', | |
|
940 | b'%': b'%', | |
|
941 | b'b': b'{reporoot|basename}', | |
|
942 | } | |
|
906 | 943 | if total is not None: |
|
907 |
expander['N'] = |
|
|
944 | expander[b'N'] = b'{total}' | |
|
908 | 945 | if seqno is not None: |
|
909 |
expander['n'] = |
|
|
946 | expander[b'n'] = b'{seqno}' | |
|
910 | 947 | if total is not None and seqno is not None: |
|
911 | expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total))) | |
|
948 | expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}' | |
|
912 | 949 | if pathname is not None: |
|
913 |
expander['s'] = |
|
|
914 | expander['d'] = lambda: os.path.dirname(pathname) or '.' | |
|
915 |
expander['p'] = |
|
|
950 | expander[b's'] = b'{pathname|basename}' | |
|
951 | expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}' | |
|
952 | expander[b'p'] = b'{pathname}' | |
|
916 | 953 | |
|
917 | 954 | newname = [] |
|
918 | patlen = len(pat) | |
|
919 | i = 0 | |
|
920 | while i < patlen: | |
|
921 | c = pat[i:i + 1] | |
|
922 |
i |
|
|
923 |
|
|
|
924 |
|
|
|
955 | for typ, start, end in templater.scantemplate(pat, raw=True): | |
|
956 | if typ != b'string': | |
|
957 | newname.append(pat[start:end]) | |
|
958 | continue | |
|
959 | i = start | |
|
960 | while i < end: | |
|
961 | n = pat.find(b'%', i, end) | |
|
962 | if n < 0: | |
|
963 | newname.append(util.escapestr(pat[i:end])) | |
|
964 | break | |
|
965 | newname.append(util.escapestr(pat[i:n])) | |
|
966 | if n + 2 > end: | |
|
967 | raise error.Abort(_("incomplete format spec in output " | |
|
968 | "filename")) | |
|
969 | c = pat[n + 1:n + 2] | |
|
970 | i = n + 2 | |
|
925 | 971 | try: |
|
926 |
|
|
|
972 | newname.append(expander[c]) | |
|
927 | 973 | except KeyError: |
|
928 | 974 | raise error.Abort(_("invalid format spec '%%%s' in output " |
|
929 | 975 | "filename") % c) |
|
930 | newname.append(c) | |
|
931 | i += 1 | |
|
932 | 976 | return ''.join(newname) |
|
933 | 977 | |
|
978 | def makefilename(ctx, pat, **props): | |
|
979 | if not pat: | |
|
980 | return pat | |
|
981 | tmpl = _buildfntemplate(pat, **props) | |
|
982 | # BUG: alias expansion shouldn't be made against template fragments | |
|
983 | # rewritten from %-format strings, but we have no easy way to partially | |
|
984 | # disable the expansion. | |
|
985 | return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) | |
|
986 | ||
|
934 | 987 | def isstdiofilename(pat): |
|
935 | 988 | """True if the given pat looks like a filename denoting stdin/stdout""" |
|
936 | 989 | return not pat or pat == '-' |
@@ -1285,7 +1285,9 b' def cat(ui, repo, file1, *pats, **opts):' | |||
|
1285 | 1285 | no revision is given, the parent of the working directory is used. |
|
1286 | 1286 | |
|
1287 | 1287 | Output may be to a file, in which case the name of the file is |
|
1288 | given using a format string. The formatting rules as follows: | |
|
1288 | given using a template string. See :hg:`help templates`. In addition | |
|
1289 | to the common template keywords, the following formatting rules are | |
|
1290 | supported: | |
|
1289 | 1291 | |
|
1290 | 1292 | :``%%``: literal "%" character |
|
1291 | 1293 | :``%s``: basename of file being printed |
@@ -1296,6 +1298,7 b' def cat(ui, repo, file1, *pats, **opts):' | |||
|
1296 | 1298 | :``%h``: short-form changeset hash (12 hexadecimal digits) |
|
1297 | 1299 | :``%r``: zero-padded changeset revision number |
|
1298 | 1300 | :``%b``: basename of the exporting repository |
|
1301 | :``\\``: literal "\\" character | |
|
1299 | 1302 | |
|
1300 | 1303 | Returns 0 on success. |
|
1301 | 1304 | """ |
@@ -1901,7 +1904,9 b' def export(ui, repo, *changesets, **opts' | |||
|
1901 | 1904 | first parent only. |
|
1902 | 1905 | |
|
1903 | 1906 | Output may be to a file, in which case the name of the file is |
|
1904 | given using a format string. The formatting rules are as follows: | |
|
1907 | given using a template string. See :hg:`help templates`. In addition | |
|
1908 | to the common template keywords, the following formatting rules are | |
|
1909 | supported: | |
|
1905 | 1910 | |
|
1906 | 1911 | :``%%``: literal "%" character |
|
1907 | 1912 | :``%H``: changeset hash (40 hexadecimal digits) |
@@ -1912,6 +1917,7 b' def export(ui, repo, *changesets, **opts' | |||
|
1912 | 1917 | :``%m``: first line of the commit message (only alphanumeric characters) |
|
1913 | 1918 | :``%n``: zero-padded sequence number, starting at 1 |
|
1914 | 1919 | :``%r``: zero-padded changeset revision number |
|
1920 | :``\\``: literal "\\" character | |
|
1915 | 1921 | |
|
1916 | 1922 | Without the -a/--text option, export will avoid generating diffs |
|
1917 | 1923 | of files it detects as binary. With -a, export will generate a |
@@ -42,6 +42,7 b' def testmod(name, optionflags=0, testtar' | |||
|
42 | 42 | |
|
43 | 43 | testmod('mercurial.changegroup') |
|
44 | 44 | testmod('mercurial.changelog') |
|
45 | testmod('mercurial.cmdutil') | |
|
45 | 46 | testmod('mercurial.color') |
|
46 | 47 | testmod('mercurial.config') |
|
47 | 48 | testmod('mercurial.context') |
@@ -186,11 +186,45 b' Checking if only alphanumeric characters' | |||
|
186 | 186 | exporting patch: |
|
187 | 187 | ___________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch |
|
188 | 188 | |
|
189 | Template fragments in file name: | |
|
190 | ||
|
191 | $ hg export -v -o '{node|shortest}.patch' tip | |
|
192 | exporting patch: | |
|
193 | 197e.patch | |
|
194 | ||
|
195 | Backslash should be preserved because it is a directory separator on Windows: | |
|
196 | ||
|
197 | $ mkdir out | |
|
198 | $ hg export -v -o 'out\{node|shortest}.patch' tip | |
|
199 | exporting patch: | |
|
200 | out\197e.patch | |
|
201 | ||
|
202 | Still backslash is taken as an escape character in inner template strings: | |
|
203 | ||
|
204 | $ hg export -v -o '{"out\{foo}.patch"}' tip | |
|
205 | exporting patch: | |
|
206 | out{foo}.patch | |
|
207 | ||
|
189 | 208 |
|
|
190 | 209 | |
|
191 | 210 |
|
|
192 | 211 |
abort: |
|
193 | 212 | [255] |
|
213 | $ hg export -o '%' tip | |
|
214 | abort: incomplete format spec in output filename | |
|
215 | [255] | |
|
216 | $ hg export -o '%{"foo"}' tip | |
|
217 | abort: incomplete format spec in output filename | |
|
218 | [255] | |
|
219 | $ hg export -o '%m{' tip | |
|
220 | hg: parse error at 3: unterminated template expansion | |
|
221 | [255] | |
|
222 | $ hg export -o '%\' tip | |
|
223 | abort: invalid format spec '%\' in output filename | |
|
224 | [255] | |
|
225 | $ hg export -o '\%' tip | |
|
226 | abort: incomplete format spec in output filename | |
|
227 | [255] | |
|
194 | 228 | |
|
195 | 229 | Catch exporting unknown revisions (especially empty revsets, see issue3353) |
|
196 | 230 |
General Comments 0
You need to be logged in to leave comments.
Login now