Show More
@@ -42,6 +42,7 b' from . import (' | |||||
42 | scmutil, |
|
42 | scmutil, | |
43 | smartset, |
|
43 | smartset, | |
44 | subrepoutil, |
|
44 | subrepoutil, | |
|
45 | templatekw, | |||
45 | templater, |
|
46 | templater, | |
46 | util, |
|
47 | util, | |
47 | vfs as vfsmod, |
|
48 | vfs as vfsmod, | |
@@ -891,46 +892,98 b' def getcommiteditor(edit=False, finishde' | |||||
891 | else: |
|
892 | else: | |
892 | return commiteditor |
|
893 | return commiteditor | |
893 |
|
894 | |||
894 | def makefilename(ctx, pat, |
|
895 | def rendertemplate(ctx, tmpl, props=None): | |
895 | total=None, seqno=None, revwidth=None, pathname=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 | expander = { |
|
934 | expander = { | |
897 |
'H': |
|
935 | b'H': b'{node}', | |
898 |
'R': |
|
936 | b'R': b'{rev}', | |
899 |
'h': |
|
937 | b'h': b'{node|short}', | |
900 | 'm': lambda: re.sub('[^\w]', '_', |
|
938 | b'm': br'{sub(r"[^\w]", "_", desc|firstline)}', | |
901 | ctx.description().strip().splitlines()[0]), |
|
939 | b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}', | |
902 | 'r': lambda: ('%d' % ctx.rev()).zfill(revwidth or 0), |
|
940 | b'%': b'%', | |
903 | '%': lambda: '%', |
|
941 | b'b': b'{reporoot|basename}', | |
904 | 'b': lambda: os.path.basename(ctx.repo().root), |
|
942 | } | |
905 | } |
|
|||
906 | if total is not None: |
|
943 | if total is not None: | |
907 |
expander['N'] = |
|
944 | expander[b'N'] = b'{total}' | |
908 | if seqno is not None: |
|
945 | if seqno is not None: | |
909 |
expander['n'] = |
|
946 | expander[b'n'] = b'{seqno}' | |
910 | if total is not None and seqno is not None: |
|
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 | if pathname is not None: |
|
949 | if pathname is not None: | |
913 |
expander['s'] = |
|
950 | expander[b's'] = b'{pathname|basename}' | |
914 | expander['d'] = lambda: os.path.dirname(pathname) or '.' |
|
951 | expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}' | |
915 |
expander['p'] = |
|
952 | expander[b'p'] = b'{pathname}' | |
916 |
|
953 | |||
917 | newname = [] |
|
954 | newname = [] | |
918 | patlen = len(pat) |
|
955 | for typ, start, end in templater.scantemplate(pat, raw=True): | |
919 | i = 0 |
|
956 | if typ != b'string': | |
920 | while i < patlen: |
|
957 | newname.append(pat[start:end]) | |
921 | c = pat[i:i + 1] |
|
958 | continue | |
922 |
i |
|
959 | i = start | |
923 |
|
|
960 | while i < end: | |
924 |
|
|
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 | try: |
|
971 | try: | |
926 |
|
|
972 | newname.append(expander[c]) | |
927 | except KeyError: |
|
973 | except KeyError: | |
928 | raise error.Abort(_("invalid format spec '%%%s' in output " |
|
974 | raise error.Abort(_("invalid format spec '%%%s' in output " | |
929 | "filename") % c) |
|
975 | "filename") % c) | |
930 | newname.append(c) |
|
|||
931 | i += 1 |
|
|||
932 | return ''.join(newname) |
|
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 | def isstdiofilename(pat): |
|
987 | def isstdiofilename(pat): | |
935 | """True if the given pat looks like a filename denoting stdin/stdout""" |
|
988 | """True if the given pat looks like a filename denoting stdin/stdout""" | |
936 | return not pat or pat == '-' |
|
989 | return not pat or pat == '-' |
@@ -1285,7 +1285,9 b' def cat(ui, repo, file1, *pats, **opts):' | |||||
1285 | no revision is given, the parent of the working directory is used. |
|
1285 | no revision is given, the parent of the working directory is used. | |
1286 |
|
1286 | |||
1287 | Output may be to a file, in which case the name of the file is |
|
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 | :``%%``: literal "%" character |
|
1292 | :``%%``: literal "%" character | |
1291 | :``%s``: basename of file being printed |
|
1293 | :``%s``: basename of file being printed | |
@@ -1296,6 +1298,7 b' def cat(ui, repo, file1, *pats, **opts):' | |||||
1296 | :``%h``: short-form changeset hash (12 hexadecimal digits) |
|
1298 | :``%h``: short-form changeset hash (12 hexadecimal digits) | |
1297 | :``%r``: zero-padded changeset revision number |
|
1299 | :``%r``: zero-padded changeset revision number | |
1298 | :``%b``: basename of the exporting repository |
|
1300 | :``%b``: basename of the exporting repository | |
|
1301 | :``\\``: literal "\\" character | |||
1299 |
|
1302 | |||
1300 | Returns 0 on success. |
|
1303 | Returns 0 on success. | |
1301 | """ |
|
1304 | """ | |
@@ -1901,7 +1904,9 b' def export(ui, repo, *changesets, **opts' | |||||
1901 | first parent only. |
|
1904 | first parent only. | |
1902 |
|
1905 | |||
1903 | Output may be to a file, in which case the name of the file is |
|
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 | :``%%``: literal "%" character |
|
1911 | :``%%``: literal "%" character | |
1907 | :``%H``: changeset hash (40 hexadecimal digits) |
|
1912 | :``%H``: changeset hash (40 hexadecimal digits) | |
@@ -1912,6 +1917,7 b' def export(ui, repo, *changesets, **opts' | |||||
1912 | :``%m``: first line of the commit message (only alphanumeric characters) |
|
1917 | :``%m``: first line of the commit message (only alphanumeric characters) | |
1913 | :``%n``: zero-padded sequence number, starting at 1 |
|
1918 | :``%n``: zero-padded sequence number, starting at 1 | |
1914 | :``%r``: zero-padded changeset revision number |
|
1919 | :``%r``: zero-padded changeset revision number | |
|
1920 | :``\\``: literal "\\" character | |||
1915 |
|
1921 | |||
1916 | Without the -a/--text option, export will avoid generating diffs |
|
1922 | Without the -a/--text option, export will avoid generating diffs | |
1917 | of files it detects as binary. With -a, export will generate a |
|
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 | testmod('mercurial.changegroup') |
|
43 | testmod('mercurial.changegroup') | |
44 | testmod('mercurial.changelog') |
|
44 | testmod('mercurial.changelog') | |
|
45 | testmod('mercurial.cmdutil') | |||
45 | testmod('mercurial.color') |
|
46 | testmod('mercurial.color') | |
46 | testmod('mercurial.config') |
|
47 | testmod('mercurial.config') | |
47 | testmod('mercurial.context') |
|
48 | testmod('mercurial.context') |
@@ -186,11 +186,45 b' Checking if only alphanumeric characters' | |||||
186 | exporting patch: |
|
186 | exporting patch: | |
187 | ___________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch |
|
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 | Invalid pattern in file name: | |
190 |
|
209 | |||
191 |
|
|
210 | $ hg export -o '%x.patch' tip | |
192 |
abort: |
|
211 | abort: invalid format spec '%x' in output filename | |
193 | [255] |
|
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 | Catch exporting unknown revisions (especially empty revsets, see issue3353) |
|
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