##// END OF EJS Templates
merge with stable
Matt Mackall -
r20012:a1d88278 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,528 +1,547 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import re, glob, os, sys
10 import re, glob, os, sys
11 import keyword
11 import keyword
12 import optparse
12 import optparse
13 try:
13 try:
14 import re2
14 import re2
15 except ImportError:
15 except ImportError:
16 re2 = None
16 re2 = None
17
17
18 def compilere(pat, multiline=False):
18 def compilere(pat, multiline=False):
19 if multiline:
19 if multiline:
20 pat = '(?m)' + pat
20 pat = '(?m)' + pat
21 if re2:
21 if re2:
22 try:
22 try:
23 return re2.compile(pat)
23 return re2.compile(pat)
24 except re2.error:
24 except re2.error:
25 pass
25 pass
26 return re.compile(pat)
26 return re.compile(pat)
27
27
28 def repquote(m):
28 def repquote(m):
29 t = re.sub(r"\w", "x", m.group('text'))
29 fromc = '.:'
30 t = re.sub(r"[^\s\nx]", "o", t)
30 tochr = 'pq'
31 def encodechr(i):
32 if i > 255:
33 return 'u'
34 c = chr(i)
35 if c in ' \n':
36 return c
37 if c.isalpha():
38 return 'x'
39 if c.isdigit():
40 return 'n'
41 try:
42 return tochr[fromc.find(c)]
43 except (ValueError, IndexError):
44 return 'o'
45 t = m.group('text')
46 tt = ''.join(encodechr(i) for i in xrange(256))
47 t = t.translate(tt)
31 return m.group('quote') + t + m.group('quote')
48 return m.group('quote') + t + m.group('quote')
32
49
33 def reppython(m):
50 def reppython(m):
34 comment = m.group('comment')
51 comment = m.group('comment')
35 if comment:
52 if comment:
36 l = len(comment.rstrip())
53 l = len(comment.rstrip())
37 return "#" * l + comment[l:]
54 return "#" * l + comment[l:]
38 return repquote(m)
55 return repquote(m)
39
56
40 def repcomment(m):
57 def repcomment(m):
41 return m.group(1) + "#" * len(m.group(2))
58 return m.group(1) + "#" * len(m.group(2))
42
59
43 def repccomment(m):
60 def repccomment(m):
44 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
61 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
45 return m.group(1) + t + "*/"
62 return m.group(1) + t + "*/"
46
63
47 def repcallspaces(m):
64 def repcallspaces(m):
48 t = re.sub(r"\n\s+", "\n", m.group(2))
65 t = re.sub(r"\n\s+", "\n", m.group(2))
49 return m.group(1) + t
66 return m.group(1) + t
50
67
51 def repinclude(m):
68 def repinclude(m):
52 return m.group(1) + "<foo>"
69 return m.group(1) + "<foo>"
53
70
54 def rephere(m):
71 def rephere(m):
55 t = re.sub(r"\S", "x", m.group(2))
72 t = re.sub(r"\S", "x", m.group(2))
56 return m.group(1) + t
73 return m.group(1) + t
57
74
58
75
59 testpats = [
76 testpats = [
60 [
77 [
61 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
78 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
62 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
79 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
63 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
80 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
64 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
81 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
65 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
82 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
66 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
83 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
67 (r'echo -n', "don't use 'echo -n', use printf"),
84 (r'echo -n', "don't use 'echo -n', use printf"),
68 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
85 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
69 (r'head -c', "don't use 'head -c', use 'dd'"),
86 (r'head -c', "don't use 'head -c', use 'dd'"),
70 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
87 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
71 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
88 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
72 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
89 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
73 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
90 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
74 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
91 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
75 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
92 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
76 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
93 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
77 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
94 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
78 "use egrep for extended grep syntax"),
95 "use egrep for extended grep syntax"),
79 (r'/bin/', "don't use explicit paths for tools"),
96 (r'/bin/', "don't use explicit paths for tools"),
80 (r'[^\n]\Z', "no trailing newline"),
97 (r'[^\n]\Z', "no trailing newline"),
81 (r'export.*=', "don't export and assign at once"),
98 (r'export.*=', "don't export and assign at once"),
82 (r'^source\b', "don't use 'source', use '.'"),
99 (r'^source\b', "don't use 'source', use '.'"),
83 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
100 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
84 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
101 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
85 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
102 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
86 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
103 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
87 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
104 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
88 (r'^alias\b.*=', "don't use alias, use a function"),
105 (r'^alias\b.*=', "don't use alias, use a function"),
89 (r'if\s*!', "don't use '!' to negate exit status"),
106 (r'if\s*!', "don't use '!' to negate exit status"),
90 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
107 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
91 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
108 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
92 (r'^( *)\t', "don't use tabs to indent"),
109 (r'^( *)\t', "don't use tabs to indent"),
93 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
110 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
94 "put a backslash-escaped newline after sed 'i' command"),
111 "put a backslash-escaped newline after sed 'i' command"),
95 ],
112 ],
96 # warnings
113 # warnings
97 [
114 [
98 (r'^function', "don't use 'function', use old style"),
115 (r'^function', "don't use 'function', use old style"),
99 (r'^diff.*-\w*N', "don't use 'diff -N'"),
116 (r'^diff.*-\w*N', "don't use 'diff -N'"),
100 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
117 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
101 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
118 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
102 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
119 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
103 ]
120 ]
104 ]
121 ]
105
122
106 testfilters = [
123 testfilters = [
107 (r"( *)(#([^\n]*\S)?)", repcomment),
124 (r"( *)(#([^\n]*\S)?)", repcomment),
108 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
125 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
109 ]
126 ]
110
127
111 winglobmsg = "use (glob) to match Windows paths too"
128 winglobmsg = "use (glob) to match Windows paths too"
112 uprefix = r"^ \$ "
129 uprefix = r"^ \$ "
113 utestpats = [
130 utestpats = [
114 [
131 [
115 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
132 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
116 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
133 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
117 "use regex test output patterns instead of sed"),
134 "use regex test output patterns instead of sed"),
118 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
135 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
119 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
136 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
120 (uprefix + r'.*\|\| echo.*(fail|error)',
137 (uprefix + r'.*\|\| echo.*(fail|error)',
121 "explicit exit code checks unnecessary"),
138 "explicit exit code checks unnecessary"),
122 (uprefix + r'set -e', "don't use set -e"),
139 (uprefix + r'set -e', "don't use set -e"),
123 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
140 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
124 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
141 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
125 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
142 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
126 winglobmsg),
143 winglobmsg),
127 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
144 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
128 (r'^ reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
145 (r'^ reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
129 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
146 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
130 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
147 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
131 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
148 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
132 '\$TESTTMP/unix-repo$'),
149 '\$TESTTMP/unix-repo$'),
133 (r'^ moving \S+/.*[^)]$', winglobmsg),
150 (r'^ moving \S+/.*[^)]$', winglobmsg),
134 (r'^ no changes made to subrepo since.*/.*[^)]$',
151 (r'^ no changes made to subrepo since.*/.*[^)]$',
135 winglobmsg, '\$TESTTMP/unix-repo$'),
152 winglobmsg, '\$TESTTMP/unix-repo$'),
136 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$',
153 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$',
137 winglobmsg, '\$TESTTMP/unix-repo$'),
154 winglobmsg, '\$TESTTMP/unix-repo$'),
138 ],
155 ],
139 # warnings
156 # warnings
140 [
157 [
141 (r'^ [^*?/\n]* \(glob\)$',
158 (r'^ [^*?/\n]* \(glob\)$',
142 "glob match with no glob character (?*/)"),
159 "glob match with no glob character (?*/)"),
143 ]
160 ]
144 ]
161 ]
145
162
146 for i in [0, 1]:
163 for i in [0, 1]:
147 for p, m in testpats[i]:
164 for p, m in testpats[i]:
148 if p.startswith(r'^'):
165 if p.startswith(r'^'):
149 p = r"^ [$>] (%s)" % p[1:]
166 p = r"^ [$>] (%s)" % p[1:]
150 else:
167 else:
151 p = r"^ [$>] .*(%s)" % p
168 p = r"^ [$>] .*(%s)" % p
152 utestpats[i].append((p, m))
169 utestpats[i].append((p, m))
153
170
154 utestfilters = [
171 utestfilters = [
155 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
172 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
156 (r"( *)(#([^\n]*\S)?)", repcomment),
173 (r"( *)(#([^\n]*\S)?)", repcomment),
157 ]
174 ]
158
175
159 pypats = [
176 pypats = [
160 [
177 [
161 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
178 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
162 "tuple parameter unpacking not available in Python 3+"),
179 "tuple parameter unpacking not available in Python 3+"),
163 (r'lambda\s*\(.*,.*\)',
180 (r'lambda\s*\(.*,.*\)',
164 "tuple parameter unpacking not available in Python 3+"),
181 "tuple parameter unpacking not available in Python 3+"),
165 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
182 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
166 '2to3 can\'t always rewrite "import qux, foo.bar", '
183 '2to3 can\'t always rewrite "import qux, foo.bar", '
167 'use "import foo.bar" on its own line instead.'),
184 'use "import foo.bar" on its own line instead.'),
168 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
185 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
169 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
186 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
170 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
187 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
171 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
188 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
172 (r'^\s*\t', "don't use tabs"),
189 (r'^\s*\t', "don't use tabs"),
173 (r'\S;\s*\n', "semicolon"),
190 (r'\S;\s*\n', "semicolon"),
174 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
191 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
175 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
192 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
176 (r'(\w|\)),\w', "missing whitespace after ,"),
193 (r'(\w|\)),\w', "missing whitespace after ,"),
177 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
194 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
178 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
195 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
179 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
196 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
180 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
197 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
181 (r'(?<!def)(\s+|^|\()next\(.+\)',
198 (r'(?<!def)(\s+|^|\()next\(.+\)',
182 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
199 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
183 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
200 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
184 r'((?:\n|\1\s.*\n)+?)\1finally:',
201 r'((?:\n|\1\s.*\n)+?)\1finally:',
185 'no yield inside try/finally in Python 2.4'),
202 'no yield inside try/finally in Python 2.4'),
186 (r'.{81}', "line too long"),
203 (r'.{81}', "line too long"),
187 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
204 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
188 (r'[^\n]\Z', "no trailing newline"),
205 (r'[^\n]\Z', "no trailing newline"),
189 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
206 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
190 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
207 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
191 # "don't use underbars in identifiers"),
208 # "don't use underbars in identifiers"),
192 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
209 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
193 "don't use camelcase in identifiers"),
210 "don't use camelcase in identifiers"),
194 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
211 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
195 "linebreak after :"),
212 "linebreak after :"),
196 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
213 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
197 (r'class\s[^( \n]+\(\):',
214 (r'class\s[^( \n]+\(\):',
198 "class foo() not available in Python 2.4, use class foo(object)"),
215 "class foo() not available in Python 2.4, use class foo(object)"),
199 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
216 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
200 "Python keyword is not a function"),
217 "Python keyword is not a function"),
201 (r',]', "unneeded trailing ',' in list"),
218 (r',]', "unneeded trailing ',' in list"),
202 # (r'class\s[A-Z][^\(]*\((?!Exception)',
219 # (r'class\s[A-Z][^\(]*\((?!Exception)',
203 # "don't capitalize non-exception classes"),
220 # "don't capitalize non-exception classes"),
204 # (r'in range\(', "use xrange"),
221 # (r'in range\(', "use xrange"),
205 # (r'^\s*print\s+', "avoid using print in core and extensions"),
222 # (r'^\s*print\s+', "avoid using print in core and extensions"),
206 (r'[\x80-\xff]', "non-ASCII character literal"),
223 (r'[\x80-\xff]', "non-ASCII character literal"),
207 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
224 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
208 (r'^\s*with\s+', "with not available in Python 2.4"),
225 (r'^\s*with\s+', "with not available in Python 2.4"),
209 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
226 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
210 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
227 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
211 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
228 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
212 (r'(?<!def)\s+(any|all|format)\(',
229 (r'(?<!def)\s+(any|all|format)\(',
213 "any/all/format not available in Python 2.4"),
230 "any/all/format not available in Python 2.4"),
214 (r'(?<!def)\s+(callable)\(',
231 (r'(?<!def)\s+(callable)\(',
215 "callable not available in Python 3, use getattr(f, '__call__', None)"),
232 "callable not available in Python 3, use getattr(f, '__call__', None)"),
216 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
233 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
217 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
234 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
218 "gratuitous whitespace after Python keyword"),
235 "gratuitous whitespace after Python keyword"),
219 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
236 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
220 # (r'\s\s=', "gratuitous whitespace before ="),
237 # (r'\s\s=', "gratuitous whitespace before ="),
221 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
238 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
222 "missing whitespace around operator"),
239 "missing whitespace around operator"),
223 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
240 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
224 "missing whitespace around operator"),
241 "missing whitespace around operator"),
225 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
242 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
226 "missing whitespace around operator"),
243 "missing whitespace around operator"),
227 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
244 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
228 "wrong whitespace around ="),
245 "wrong whitespace around ="),
229 (r'\([^()]*( =[^=]|[^<>!=]= )',
246 (r'\([^()]*( =[^=]|[^<>!=]= )',
230 "no whitespace around = for named parameters"),
247 "no whitespace around = for named parameters"),
231 (r'raise Exception', "don't raise generic exceptions"),
248 (r'raise Exception', "don't raise generic exceptions"),
232 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
249 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
233 "don't use old-style two-argument raise, use Exception(message)"),
250 "don't use old-style two-argument raise, use Exception(message)"),
234 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
251 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
235 (r' [=!]=\s+(True|False|None)',
252 (r' [=!]=\s+(True|False|None)',
236 "comparison with singleton, use 'is' or 'is not' instead"),
253 "comparison with singleton, use 'is' or 'is not' instead"),
237 (r'^\s*(while|if) [01]:',
254 (r'^\s*(while|if) [01]:',
238 "use True/False for constant Boolean expression"),
255 "use True/False for constant Boolean expression"),
239 (r'(?:(?<!def)\s+|\()hasattr',
256 (r'(?:(?<!def)\s+|\()hasattr',
240 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
257 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
241 (r'opener\([^)]*\).read\(',
258 (r'opener\([^)]*\).read\(',
242 "use opener.read() instead"),
259 "use opener.read() instead"),
243 (r'BaseException', 'not in Python 2.4, use Exception'),
260 (r'BaseException', 'not in Python 2.4, use Exception'),
244 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
261 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
245 (r'opener\([^)]*\).write\(',
262 (r'opener\([^)]*\).write\(',
246 "use opener.write() instead"),
263 "use opener.write() instead"),
247 (r'[\s\(](open|file)\([^)]*\)\.read\(',
264 (r'[\s\(](open|file)\([^)]*\)\.read\(',
248 "use util.readfile() instead"),
265 "use util.readfile() instead"),
249 (r'[\s\(](open|file)\([^)]*\)\.write\(',
266 (r'[\s\(](open|file)\([^)]*\)\.write\(',
250 "use util.writefile() instead"),
267 "use util.writefile() instead"),
251 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
268 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
252 "always assign an opened file to a variable, and close it afterwards"),
269 "always assign an opened file to a variable, and close it afterwards"),
253 (r'[\s\(](open|file)\([^)]*\)\.',
270 (r'[\s\(](open|file)\([^)]*\)\.',
254 "always assign an opened file to a variable, and close it afterwards"),
271 "always assign an opened file to a variable, and close it afterwards"),
255 (r'(?i)descendent', "the proper spelling is descendAnt"),
272 (r'(?i)descendent', "the proper spelling is descendAnt"),
256 (r'\.debug\(\_', "don't mark debug messages for translation"),
273 (r'\.debug\(\_', "don't mark debug messages for translation"),
257 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
274 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
258 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
275 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
259 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
276 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
260 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
277 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
261 "missing _() in ui message (use () to hide false-positives)"),
278 "missing _() in ui message (use () to hide false-positives)"),
262 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
279 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
263 ],
280 ],
264 # warnings
281 # warnings
265 [
282 [
283 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
266 ]
284 ]
267 ]
285 ]
268
286
269 pyfilters = [
287 pyfilters = [
270 (r"""(?msx)(?P<comment>\#.*?$)|
288 (r"""(?msx)(?P<comment>\#.*?$)|
271 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
289 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
272 (?P<text>(([^\\]|\\.)*?))
290 (?P<text>(([^\\]|\\.)*?))
273 (?P=quote))""", reppython),
291 (?P=quote))""", reppython),
274 ]
292 ]
275
293
276 txtfilters = []
294 txtfilters = []
277
295
278 txtpats = [
296 txtpats = [
279 [
297 [
280 ('\s$', 'trailing whitespace'),
298 ('\s$', 'trailing whitespace'),
281 ],
299 ],
282 []
300 []
283 ]
301 ]
284
302
285 cpats = [
303 cpats = [
286 [
304 [
287 (r'//', "don't use //-style comments"),
305 (r'//', "don't use //-style comments"),
288 (r'^ ', "don't use spaces to indent"),
306 (r'^ ', "don't use spaces to indent"),
289 (r'\S\t', "don't use tabs except for indent"),
307 (r'\S\t', "don't use tabs except for indent"),
290 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
308 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
291 (r'.{81}', "line too long"),
309 (r'.{81}', "line too long"),
292 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
310 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
293 (r'return\(', "return is not a function"),
311 (r'return\(', "return is not a function"),
294 (r' ;', "no space before ;"),
312 (r' ;', "no space before ;"),
295 (r'[)][{]', "space between ) and {"),
313 (r'[)][{]', "space between ) and {"),
296 (r'\w+\* \w+', "use int *foo, not int* foo"),
314 (r'\w+\* \w+', "use int *foo, not int* foo"),
297 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
315 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
298 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
316 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
299 (r'\w,\w', "missing whitespace after ,"),
317 (r'\w,\w', "missing whitespace after ,"),
300 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
318 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
301 (r'^#\s+\w', "use #foo, not # foo"),
319 (r'^#\s+\w', "use #foo, not # foo"),
302 (r'[^\n]\Z', "no trailing newline"),
320 (r'[^\n]\Z', "no trailing newline"),
303 (r'^\s*#import\b', "use only #include in standard C code"),
321 (r'^\s*#import\b', "use only #include in standard C code"),
304 ],
322 ],
305 # warnings
323 # warnings
306 []
324 []
307 ]
325 ]
308
326
309 cfilters = [
327 cfilters = [
310 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
328 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
311 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
329 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
312 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
330 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
313 (r'(\()([^)]+\))', repcallspaces),
331 (r'(\()([^)]+\))', repcallspaces),
314 ]
332 ]
315
333
316 inutilpats = [
334 inutilpats = [
317 [
335 [
318 (r'\bui\.', "don't use ui in util"),
336 (r'\bui\.', "don't use ui in util"),
319 ],
337 ],
320 # warnings
338 # warnings
321 []
339 []
322 ]
340 ]
323
341
324 inrevlogpats = [
342 inrevlogpats = [
325 [
343 [
326 (r'\brepo\.', "don't use repo in revlog"),
344 (r'\brepo\.', "don't use repo in revlog"),
327 ],
345 ],
328 # warnings
346 # warnings
329 []
347 []
330 ]
348 ]
331
349
332 checks = [
350 checks = [
333 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
351 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
334 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
352 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
335 ('c', r'.*\.[ch]$', cfilters, cpats),
353 ('c', r'.*\.[ch]$', cfilters, cpats),
336 ('unified test', r'.*\.t$', utestfilters, utestpats),
354 ('unified test', r'.*\.t$', utestfilters, utestpats),
337 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
355 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
338 inrevlogpats),
356 inrevlogpats),
339 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
357 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
340 inutilpats),
358 inutilpats),
341 ('txt', r'.*\.txt$', txtfilters, txtpats),
359 ('txt', r'.*\.txt$', txtfilters, txtpats),
342 ]
360 ]
343
361
344 def _preparepats():
362 def _preparepats():
345 for c in checks:
363 for c in checks:
346 failandwarn = c[-1]
364 failandwarn = c[-1]
347 for pats in failandwarn:
365 for pats in failandwarn:
348 for i, pseq in enumerate(pats):
366 for i, pseq in enumerate(pats):
349 # fix-up regexes for multi-line searches
367 # fix-up regexes for multi-line searches
350 p = pseq[0]
368 p = pseq[0]
351 # \s doesn't match \n
369 # \s doesn't match \n
352 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
370 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
353 # [^...] doesn't match newline
371 # [^...] doesn't match newline
354 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
372 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
355
373
356 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
374 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
357 filters = c[2]
375 filters = c[2]
358 for i, flt in enumerate(filters):
376 for i, flt in enumerate(filters):
359 filters[i] = re.compile(flt[0]), flt[1]
377 filters[i] = re.compile(flt[0]), flt[1]
360 _preparepats()
378 _preparepats()
361
379
362 class norepeatlogger(object):
380 class norepeatlogger(object):
363 def __init__(self):
381 def __init__(self):
364 self._lastseen = None
382 self._lastseen = None
365
383
366 def log(self, fname, lineno, line, msg, blame):
384 def log(self, fname, lineno, line, msg, blame):
367 """print error related a to given line of a given file.
385 """print error related a to given line of a given file.
368
386
369 The faulty line will also be printed but only once in the case
387 The faulty line will also be printed but only once in the case
370 of multiple errors.
388 of multiple errors.
371
389
372 :fname: filename
390 :fname: filename
373 :lineno: line number
391 :lineno: line number
374 :line: actual content of the line
392 :line: actual content of the line
375 :msg: error message
393 :msg: error message
376 """
394 """
377 msgid = fname, lineno, line
395 msgid = fname, lineno, line
378 if msgid != self._lastseen:
396 if msgid != self._lastseen:
379 if blame:
397 if blame:
380 print "%s:%d (%s):" % (fname, lineno, blame)
398 print "%s:%d (%s):" % (fname, lineno, blame)
381 else:
399 else:
382 print "%s:%d:" % (fname, lineno)
400 print "%s:%d:" % (fname, lineno)
383 print " > %s" % line
401 print " > %s" % line
384 self._lastseen = msgid
402 self._lastseen = msgid
385 print " " + msg
403 print " " + msg
386
404
387 _defaultlogger = norepeatlogger()
405 _defaultlogger = norepeatlogger()
388
406
389 def getblame(f):
407 def getblame(f):
390 lines = []
408 lines = []
391 for l in os.popen('hg annotate -un %s' % f):
409 for l in os.popen('hg annotate -un %s' % f):
392 start, line = l.split(':', 1)
410 start, line = l.split(':', 1)
393 user, rev = start.split()
411 user, rev = start.split()
394 lines.append((line[1:-1], user, rev))
412 lines.append((line[1:-1], user, rev))
395 return lines
413 return lines
396
414
397 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
415 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
398 blame=False, debug=False, lineno=True):
416 blame=False, debug=False, lineno=True):
399 """checks style and portability of a given file
417 """checks style and portability of a given file
400
418
401 :f: filepath
419 :f: filepath
402 :logfunc: function used to report error
420 :logfunc: function used to report error
403 logfunc(filename, linenumber, linecontent, errormessage)
421 logfunc(filename, linenumber, linecontent, errormessage)
404 :maxerr: number of error to display before aborting.
422 :maxerr: number of error to display before aborting.
405 Set to false (default) to report all errors
423 Set to false (default) to report all errors
406
424
407 return True if no error is found, False otherwise.
425 return True if no error is found, False otherwise.
408 """
426 """
409 blamecache = None
427 blamecache = None
410 result = True
428 result = True
411 for name, match, filters, pats in checks:
429 for name, match, filters, pats in checks:
412 if debug:
430 if debug:
413 print name, f
431 print name, f
414 fc = 0
432 fc = 0
415 if not re.match(match, f):
433 if not re.match(match, f):
416 if debug:
434 if debug:
417 print "Skipping %s for %s it doesn't match %s" % (
435 print "Skipping %s for %s it doesn't match %s" % (
418 name, match, f)
436 name, match, f)
419 continue
437 continue
420 try:
438 try:
421 fp = open(f)
439 fp = open(f)
422 except IOError, e:
440 except IOError, e:
423 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
441 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
424 continue
442 continue
425 pre = post = fp.read()
443 pre = post = fp.read()
426 fp.close()
444 fp.close()
427 if "no-" "check-code" in pre:
445 if "no-" "check-code" in pre:
428 if debug:
446 if debug:
429 print "Skipping %s for %s it has no-" "check-code" % (
447 print "Skipping %s for %s it has no-" "check-code" % (
430 name, f)
448 name, f)
431 break
449 break
432 for p, r in filters:
450 for p, r in filters:
433 post = re.sub(p, r, post)
451 post = re.sub(p, r, post)
434 nerrs = len(pats[0]) # nerr elements are errors
452 nerrs = len(pats[0]) # nerr elements are errors
435 if warnings:
453 if warnings:
436 pats = pats[0] + pats[1]
454 pats = pats[0] + pats[1]
437 else:
455 else:
438 pats = pats[0]
456 pats = pats[0]
439 # print post # uncomment to show filtered version
457 # print post # uncomment to show filtered version
440
458
441 if debug:
459 if debug:
442 print "Checking %s for %s" % (name, f)
460 print "Checking %s for %s" % (name, f)
443
461
444 prelines = None
462 prelines = None
445 errors = []
463 errors = []
446 for i, pat in enumerate(pats):
464 for i, pat in enumerate(pats):
447 if len(pat) == 3:
465 if len(pat) == 3:
448 p, msg, ignore = pat
466 p, msg, ignore = pat
449 else:
467 else:
450 p, msg = pat
468 p, msg = pat
451 ignore = None
469 ignore = None
470 if i >= nerrs:
471 msg = "warning: " + msg
452
472
453 pos = 0
473 pos = 0
454 n = 0
474 n = 0
455 for m in p.finditer(post):
475 for m in p.finditer(post):
456 if prelines is None:
476 if prelines is None:
457 prelines = pre.splitlines()
477 prelines = pre.splitlines()
458 postlines = post.splitlines(True)
478 postlines = post.splitlines(True)
459 if i >= nerrs:
460 msg = "warning: " + msg
461
479
462 start = m.start()
480 start = m.start()
463 while n < len(postlines):
481 while n < len(postlines):
464 step = len(postlines[n])
482 step = len(postlines[n])
465 if pos + step > start:
483 if pos + step > start:
466 break
484 break
467 pos += step
485 pos += step
468 n += 1
486 n += 1
469 l = prelines[n]
487 l = prelines[n]
470
488
471 if "check-code" "-ignore" in l:
489 if "check-code" "-ignore" in l:
472 if debug:
490 if debug:
473 print "Skipping %s for %s:%s (check-code" "-ignore)" % (
491 print "Skipping %s for %s:%s (check-code" "-ignore)" % (
474 name, f, n)
492 name, f, n)
475 continue
493 continue
476 elif ignore and re.search(ignore, l, re.MULTILINE):
494 elif ignore and re.search(ignore, l, re.MULTILINE):
477 continue
495 continue
478 bd = ""
496 bd = ""
479 if blame:
497 if blame:
480 bd = 'working directory'
498 bd = 'working directory'
481 if not blamecache:
499 if not blamecache:
482 blamecache = getblame(f)
500 blamecache = getblame(f)
483 if n < len(blamecache):
501 if n < len(blamecache):
484 bl, bu, br = blamecache[n]
502 bl, bu, br = blamecache[n]
485 if bl == l:
503 if bl == l:
486 bd = '%s@%s' % (bu, br)
504 bd = '%s@%s' % (bu, br)
505
487 errors.append((f, lineno and n + 1, l, msg, bd))
506 errors.append((f, lineno and n + 1, l, msg, bd))
488 result = False
507 result = False
489
508
490 errors.sort()
509 errors.sort()
491 for e in errors:
510 for e in errors:
492 logfunc(*e)
511 logfunc(*e)
493 fc += 1
512 fc += 1
494 if maxerr and fc >= maxerr:
513 if maxerr and fc >= maxerr:
495 print " (too many errors, giving up)"
514 print " (too many errors, giving up)"
496 break
515 break
497
516
498 return result
517 return result
499
518
500 if __name__ == "__main__":
519 if __name__ == "__main__":
501 parser = optparse.OptionParser("%prog [options] [files]")
520 parser = optparse.OptionParser("%prog [options] [files]")
502 parser.add_option("-w", "--warnings", action="store_true",
521 parser.add_option("-w", "--warnings", action="store_true",
503 help="include warning-level checks")
522 help="include warning-level checks")
504 parser.add_option("-p", "--per-file", type="int",
523 parser.add_option("-p", "--per-file", type="int",
505 help="max warnings per file")
524 help="max warnings per file")
506 parser.add_option("-b", "--blame", action="store_true",
525 parser.add_option("-b", "--blame", action="store_true",
507 help="use annotate to generate blame info")
526 help="use annotate to generate blame info")
508 parser.add_option("", "--debug", action="store_true",
527 parser.add_option("", "--debug", action="store_true",
509 help="show debug information")
528 help="show debug information")
510 parser.add_option("", "--nolineno", action="store_false",
529 parser.add_option("", "--nolineno", action="store_false",
511 dest='lineno', help="don't show line numbers")
530 dest='lineno', help="don't show line numbers")
512
531
513 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
532 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
514 lineno=True)
533 lineno=True)
515 (options, args) = parser.parse_args()
534 (options, args) = parser.parse_args()
516
535
517 if len(args) == 0:
536 if len(args) == 0:
518 check = glob.glob("*")
537 check = glob.glob("*")
519 else:
538 else:
520 check = args
539 check = args
521
540
522 ret = 0
541 ret = 0
523 for f in check:
542 for f in check:
524 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
543 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
525 blame=options.blame, debug=options.debug,
544 blame=options.blame, debug=options.debug,
526 lineno=options.lineno):
545 lineno=options.lineno):
527 ret = 1
546 ret = 1
528 sys.exit(ret)
547 sys.exit(ret)
@@ -1,349 +1,350 b''
1 """automatically manage newlines in repository files
1 """automatically manage newlines in repository files
2
2
3 This extension allows you to manage the type of line endings (CRLF or
3 This extension allows you to manage the type of line endings (CRLF or
4 LF) that are used in the repository and in the local working
4 LF) that are used in the repository and in the local working
5 directory. That way you can get CRLF line endings on Windows and LF on
5 directory. That way you can get CRLF line endings on Windows and LF on
6 Unix/Mac, thereby letting everybody use their OS native line endings.
6 Unix/Mac, thereby letting everybody use their OS native line endings.
7
7
8 The extension reads its configuration from a versioned ``.hgeol``
8 The extension reads its configuration from a versioned ``.hgeol``
9 configuration file found in the root of the working copy. The
9 configuration file found in the root of the working copy. The
10 ``.hgeol`` file use the same syntax as all other Mercurial
10 ``.hgeol`` file use the same syntax as all other Mercurial
11 configuration files. It uses two sections, ``[patterns]`` and
11 configuration files. It uses two sections, ``[patterns]`` and
12 ``[repository]``.
12 ``[repository]``.
13
13
14 The ``[patterns]`` section specifies how line endings should be
14 The ``[patterns]`` section specifies how line endings should be
15 converted between the working copy and the repository. The format is
15 converted between the working copy and the repository. The format is
16 specified by a file pattern. The first match is used, so put more
16 specified by a file pattern. The first match is used, so put more
17 specific patterns first. The available line endings are ``LF``,
17 specific patterns first. The available line endings are ``LF``,
18 ``CRLF``, and ``BIN``.
18 ``CRLF``, and ``BIN``.
19
19
20 Files with the declared format of ``CRLF`` or ``LF`` are always
20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 checked out and stored in the repository in that format and files
21 checked out and stored in the repository in that format and files
22 declared to be binary (``BIN``) are left unchanged. Additionally,
22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 ``native`` is an alias for checking out in the platform's default line
23 ``native`` is an alias for checking out in the platform's default line
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 default behaviour; it is only needed if you need to override a later,
26 default behaviour; it is only needed if you need to override a later,
27 more general pattern.
27 more general pattern.
28
28
29 The optional ``[repository]`` section specifies the line endings to
29 The optional ``[repository]`` section specifies the line endings to
30 use for files stored in the repository. It has a single setting,
30 use for files stored in the repository. It has a single setting,
31 ``native``, which determines the storage line endings for files
31 ``native``, which determines the storage line endings for files
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 will be converted to ``LF`` when stored in the repository. Files
35 will be converted to ``LF`` when stored in the repository. Files
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 are always stored as-is in the repository.
37 are always stored as-is in the repository.
38
38
39 Example versioned ``.hgeol`` file::
39 Example versioned ``.hgeol`` file::
40
40
41 [patterns]
41 [patterns]
42 **.py = native
42 **.py = native
43 **.vcproj = CRLF
43 **.vcproj = CRLF
44 **.txt = native
44 **.txt = native
45 Makefile = LF
45 Makefile = LF
46 **.jpg = BIN
46 **.jpg = BIN
47
47
48 [repository]
48 [repository]
49 native = LF
49 native = LF
50
50
51 .. note::
51 .. note::
52
52 The rules will first apply when files are touched in the working
53 The rules will first apply when files are touched in the working
53 copy, e.g. by updating to null and back to tip to touch all files.
54 copy, e.g. by updating to null and back to tip to touch all files.
54
55
55 The extension uses an optional ``[eol]`` section read from both the
56 The extension uses an optional ``[eol]`` section read from both the
56 normal Mercurial configuration files and the ``.hgeol`` file, with the
57 normal Mercurial configuration files and the ``.hgeol`` file, with the
57 latter overriding the former. You can use that section to control the
58 latter overriding the former. You can use that section to control the
58 overall behavior. There are three settings:
59 overall behavior. There are three settings:
59
60
60 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
61 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
61 ``CRLF`` to override the default interpretation of ``native`` for
62 ``CRLF`` to override the default interpretation of ``native`` for
62 checkout. This can be used with :hg:`archive` on Unix, say, to
63 checkout. This can be used with :hg:`archive` on Unix, say, to
63 generate an archive where files have line endings for Windows.
64 generate an archive where files have line endings for Windows.
64
65
65 - ``eol.only-consistent`` (default True) can be set to False to make
66 - ``eol.only-consistent`` (default True) can be set to False to make
66 the extension convert files with inconsistent EOLs. Inconsistent
67 the extension convert files with inconsistent EOLs. Inconsistent
67 means that there is both ``CRLF`` and ``LF`` present in the file.
68 means that there is both ``CRLF`` and ``LF`` present in the file.
68 Such files are normally not touched under the assumption that they
69 Such files are normally not touched under the assumption that they
69 have mixed EOLs on purpose.
70 have mixed EOLs on purpose.
70
71
71 - ``eol.fix-trailing-newline`` (default False) can be set to True to
72 - ``eol.fix-trailing-newline`` (default False) can be set to True to
72 ensure that converted files end with a EOL character (either ``\\n``
73 ensure that converted files end with a EOL character (either ``\\n``
73 or ``\\r\\n`` as per the configured patterns).
74 or ``\\r\\n`` as per the configured patterns).
74
75
75 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
76 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
76 like the deprecated win32text extension does. This means that you can
77 like the deprecated win32text extension does. This means that you can
77 disable win32text and enable eol and your filters will still work. You
78 disable win32text and enable eol and your filters will still work. You
78 only need to these filters until you have prepared a ``.hgeol`` file.
79 only need to these filters until you have prepared a ``.hgeol`` file.
79
80
80 The ``win32text.forbid*`` hooks provided by the win32text extension
81 The ``win32text.forbid*`` hooks provided by the win32text extension
81 have been unified into a single hook named ``eol.checkheadshook``. The
82 have been unified into a single hook named ``eol.checkheadshook``. The
82 hook will lookup the expected line endings from the ``.hgeol`` file,
83 hook will lookup the expected line endings from the ``.hgeol`` file,
83 which means you must migrate to a ``.hgeol`` file first before using
84 which means you must migrate to a ``.hgeol`` file first before using
84 the hook. ``eol.checkheadshook`` only checks heads, intermediate
85 the hook. ``eol.checkheadshook`` only checks heads, intermediate
85 invalid revisions will be pushed. To forbid them completely, use the
86 invalid revisions will be pushed. To forbid them completely, use the
86 ``eol.checkallhook`` hook. These hooks are best used as
87 ``eol.checkallhook`` hook. These hooks are best used as
87 ``pretxnchangegroup`` hooks.
88 ``pretxnchangegroup`` hooks.
88
89
89 See :hg:`help patterns` for more information about the glob patterns
90 See :hg:`help patterns` for more information about the glob patterns
90 used.
91 used.
91 """
92 """
92
93
93 from mercurial.i18n import _
94 from mercurial.i18n import _
94 from mercurial import util, config, extensions, match, error
95 from mercurial import util, config, extensions, match, error
95 import re, os
96 import re, os
96
97
97 testedwith = 'internal'
98 testedwith = 'internal'
98
99
99 # Matches a lone LF, i.e., one that is not part of CRLF.
100 # Matches a lone LF, i.e., one that is not part of CRLF.
100 singlelf = re.compile('(^|[^\r])\n')
101 singlelf = re.compile('(^|[^\r])\n')
101 # Matches a single EOL which can either be a CRLF where repeated CR
102 # Matches a single EOL which can either be a CRLF where repeated CR
102 # are removed or a LF. We do not care about old Macintosh files, so a
103 # are removed or a LF. We do not care about old Macintosh files, so a
103 # stray CR is an error.
104 # stray CR is an error.
104 eolre = re.compile('\r*\n')
105 eolre = re.compile('\r*\n')
105
106
106
107
107 def inconsistenteol(data):
108 def inconsistenteol(data):
108 return '\r\n' in data and singlelf.search(data)
109 return '\r\n' in data and singlelf.search(data)
109
110
110 def tolf(s, params, ui, **kwargs):
111 def tolf(s, params, ui, **kwargs):
111 """Filter to convert to LF EOLs."""
112 """Filter to convert to LF EOLs."""
112 if util.binary(s):
113 if util.binary(s):
113 return s
114 return s
114 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
115 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
115 return s
116 return s
116 if (ui.configbool('eol', 'fix-trailing-newline', False)
117 if (ui.configbool('eol', 'fix-trailing-newline', False)
117 and s and s[-1] != '\n'):
118 and s and s[-1] != '\n'):
118 s = s + '\n'
119 s = s + '\n'
119 return eolre.sub('\n', s)
120 return eolre.sub('\n', s)
120
121
121 def tocrlf(s, params, ui, **kwargs):
122 def tocrlf(s, params, ui, **kwargs):
122 """Filter to convert to CRLF EOLs."""
123 """Filter to convert to CRLF EOLs."""
123 if util.binary(s):
124 if util.binary(s):
124 return s
125 return s
125 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
126 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
126 return s
127 return s
127 if (ui.configbool('eol', 'fix-trailing-newline', False)
128 if (ui.configbool('eol', 'fix-trailing-newline', False)
128 and s and s[-1] != '\n'):
129 and s and s[-1] != '\n'):
129 s = s + '\n'
130 s = s + '\n'
130 return eolre.sub('\r\n', s)
131 return eolre.sub('\r\n', s)
131
132
132 def isbinary(s, params):
133 def isbinary(s, params):
133 """Filter to do nothing with the file."""
134 """Filter to do nothing with the file."""
134 return s
135 return s
135
136
136 filters = {
137 filters = {
137 'to-lf': tolf,
138 'to-lf': tolf,
138 'to-crlf': tocrlf,
139 'to-crlf': tocrlf,
139 'is-binary': isbinary,
140 'is-binary': isbinary,
140 # The following provide backwards compatibility with win32text
141 # The following provide backwards compatibility with win32text
141 'cleverencode:': tolf,
142 'cleverencode:': tolf,
142 'cleverdecode:': tocrlf
143 'cleverdecode:': tocrlf
143 }
144 }
144
145
145 class eolfile(object):
146 class eolfile(object):
146 def __init__(self, ui, root, data):
147 def __init__(self, ui, root, data):
147 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
148 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
148 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
149 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
149
150
150 self.cfg = config.config()
151 self.cfg = config.config()
151 # Our files should not be touched. The pattern must be
152 # Our files should not be touched. The pattern must be
152 # inserted first override a '** = native' pattern.
153 # inserted first override a '** = native' pattern.
153 self.cfg.set('patterns', '.hg*', 'BIN')
154 self.cfg.set('patterns', '.hg*', 'BIN')
154 # We can then parse the user's patterns.
155 # We can then parse the user's patterns.
155 self.cfg.parse('.hgeol', data)
156 self.cfg.parse('.hgeol', data)
156
157
157 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
158 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
158 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
159 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
159 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
160 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
160 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
161 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
161
162
162 include = []
163 include = []
163 exclude = []
164 exclude = []
164 for pattern, style in self.cfg.items('patterns'):
165 for pattern, style in self.cfg.items('patterns'):
165 key = style.upper()
166 key = style.upper()
166 if key == 'BIN':
167 if key == 'BIN':
167 exclude.append(pattern)
168 exclude.append(pattern)
168 else:
169 else:
169 include.append(pattern)
170 include.append(pattern)
170 # This will match the files for which we need to care
171 # This will match the files for which we need to care
171 # about inconsistent newlines.
172 # about inconsistent newlines.
172 self.match = match.match(root, '', [], include, exclude)
173 self.match = match.match(root, '', [], include, exclude)
173
174
174 def copytoui(self, ui):
175 def copytoui(self, ui):
175 for pattern, style in self.cfg.items('patterns'):
176 for pattern, style in self.cfg.items('patterns'):
176 key = style.upper()
177 key = style.upper()
177 try:
178 try:
178 ui.setconfig('decode', pattern, self._decode[key])
179 ui.setconfig('decode', pattern, self._decode[key])
179 ui.setconfig('encode', pattern, self._encode[key])
180 ui.setconfig('encode', pattern, self._encode[key])
180 except KeyError:
181 except KeyError:
181 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
182 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
182 % (style, self.cfg.source('patterns', pattern)))
183 % (style, self.cfg.source('patterns', pattern)))
183 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
184 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
184 for k, v in self.cfg.items('eol'):
185 for k, v in self.cfg.items('eol'):
185 ui.setconfig('eol', k, v)
186 ui.setconfig('eol', k, v)
186
187
187 def checkrev(self, repo, ctx, files):
188 def checkrev(self, repo, ctx, files):
188 failed = []
189 failed = []
189 for f in (files or ctx.files()):
190 for f in (files or ctx.files()):
190 if f not in ctx:
191 if f not in ctx:
191 continue
192 continue
192 for pattern, style in self.cfg.items('patterns'):
193 for pattern, style in self.cfg.items('patterns'):
193 if not match.match(repo.root, '', [pattern])(f):
194 if not match.match(repo.root, '', [pattern])(f):
194 continue
195 continue
195 target = self._encode[style.upper()]
196 target = self._encode[style.upper()]
196 data = ctx[f].data()
197 data = ctx[f].data()
197 if (target == "to-lf" and "\r\n" in data
198 if (target == "to-lf" and "\r\n" in data
198 or target == "to-crlf" and singlelf.search(data)):
199 or target == "to-crlf" and singlelf.search(data)):
199 failed.append((str(ctx), target, f))
200 failed.append((str(ctx), target, f))
200 break
201 break
201 return failed
202 return failed
202
203
203 def parseeol(ui, repo, nodes):
204 def parseeol(ui, repo, nodes):
204 try:
205 try:
205 for node in nodes:
206 for node in nodes:
206 try:
207 try:
207 if node is None:
208 if node is None:
208 # Cannot use workingctx.data() since it would load
209 # Cannot use workingctx.data() since it would load
209 # and cache the filters before we configure them.
210 # and cache the filters before we configure them.
210 data = repo.wfile('.hgeol').read()
211 data = repo.wfile('.hgeol').read()
211 else:
212 else:
212 data = repo[node]['.hgeol'].data()
213 data = repo[node]['.hgeol'].data()
213 return eolfile(ui, repo.root, data)
214 return eolfile(ui, repo.root, data)
214 except (IOError, LookupError):
215 except (IOError, LookupError):
215 pass
216 pass
216 except error.ParseError, inst:
217 except error.ParseError, inst:
217 ui.warn(_("warning: ignoring .hgeol file due to parse error "
218 ui.warn(_("warning: ignoring .hgeol file due to parse error "
218 "at %s: %s\n") % (inst.args[1], inst.args[0]))
219 "at %s: %s\n") % (inst.args[1], inst.args[0]))
219 return None
220 return None
220
221
221 def _checkhook(ui, repo, node, headsonly):
222 def _checkhook(ui, repo, node, headsonly):
222 # Get revisions to check and touched files at the same time
223 # Get revisions to check and touched files at the same time
223 files = set()
224 files = set()
224 revs = set()
225 revs = set()
225 for rev in xrange(repo[node].rev(), len(repo)):
226 for rev in xrange(repo[node].rev(), len(repo)):
226 revs.add(rev)
227 revs.add(rev)
227 if headsonly:
228 if headsonly:
228 ctx = repo[rev]
229 ctx = repo[rev]
229 files.update(ctx.files())
230 files.update(ctx.files())
230 for pctx in ctx.parents():
231 for pctx in ctx.parents():
231 revs.discard(pctx.rev())
232 revs.discard(pctx.rev())
232 failed = []
233 failed = []
233 for rev in revs:
234 for rev in revs:
234 ctx = repo[rev]
235 ctx = repo[rev]
235 eol = parseeol(ui, repo, [ctx.node()])
236 eol = parseeol(ui, repo, [ctx.node()])
236 if eol:
237 if eol:
237 failed.extend(eol.checkrev(repo, ctx, files))
238 failed.extend(eol.checkrev(repo, ctx, files))
238
239
239 if failed:
240 if failed:
240 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
241 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
241 msgs = []
242 msgs = []
242 for node, target, f in failed:
243 for node, target, f in failed:
243 msgs.append(_(" %s in %s should not have %s line endings") %
244 msgs.append(_(" %s in %s should not have %s line endings") %
244 (f, node, eols[target]))
245 (f, node, eols[target]))
245 raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
246 raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
246
247
247 def checkallhook(ui, repo, node, hooktype, **kwargs):
248 def checkallhook(ui, repo, node, hooktype, **kwargs):
248 """verify that files have expected EOLs"""
249 """verify that files have expected EOLs"""
249 _checkhook(ui, repo, node, False)
250 _checkhook(ui, repo, node, False)
250
251
251 def checkheadshook(ui, repo, node, hooktype, **kwargs):
252 def checkheadshook(ui, repo, node, hooktype, **kwargs):
252 """verify that files have expected EOLs"""
253 """verify that files have expected EOLs"""
253 _checkhook(ui, repo, node, True)
254 _checkhook(ui, repo, node, True)
254
255
255 # "checkheadshook" used to be called "hook"
256 # "checkheadshook" used to be called "hook"
256 hook = checkheadshook
257 hook = checkheadshook
257
258
258 def preupdate(ui, repo, hooktype, parent1, parent2):
259 def preupdate(ui, repo, hooktype, parent1, parent2):
259 repo.loadeol([parent1])
260 repo.loadeol([parent1])
260 return False
261 return False
261
262
262 def uisetup(ui):
263 def uisetup(ui):
263 ui.setconfig('hooks', 'preupdate.eol', preupdate)
264 ui.setconfig('hooks', 'preupdate.eol', preupdate)
264
265
265 def extsetup(ui):
266 def extsetup(ui):
266 try:
267 try:
267 extensions.find('win32text')
268 extensions.find('win32text')
268 ui.warn(_("the eol extension is incompatible with the "
269 ui.warn(_("the eol extension is incompatible with the "
269 "win32text extension\n"))
270 "win32text extension\n"))
270 except KeyError:
271 except KeyError:
271 pass
272 pass
272
273
273
274
274 def reposetup(ui, repo):
275 def reposetup(ui, repo):
275 uisetup(repo.ui)
276 uisetup(repo.ui)
276
277
277 if not repo.local():
278 if not repo.local():
278 return
279 return
279 for name, fn in filters.iteritems():
280 for name, fn in filters.iteritems():
280 repo.adddatafilter(name, fn)
281 repo.adddatafilter(name, fn)
281
282
282 ui.setconfig('patch', 'eol', 'auto')
283 ui.setconfig('patch', 'eol', 'auto')
283
284
284 class eolrepo(repo.__class__):
285 class eolrepo(repo.__class__):
285
286
286 def loadeol(self, nodes):
287 def loadeol(self, nodes):
287 eol = parseeol(self.ui, self, nodes)
288 eol = parseeol(self.ui, self, nodes)
288 if eol is None:
289 if eol is None:
289 return None
290 return None
290 eol.copytoui(self.ui)
291 eol.copytoui(self.ui)
291 return eol.match
292 return eol.match
292
293
293 def _hgcleardirstate(self):
294 def _hgcleardirstate(self):
294 self._eolfile = self.loadeol([None, 'tip'])
295 self._eolfile = self.loadeol([None, 'tip'])
295 if not self._eolfile:
296 if not self._eolfile:
296 self._eolfile = util.never
297 self._eolfile = util.never
297 return
298 return
298
299
299 try:
300 try:
300 cachemtime = os.path.getmtime(self.join("eol.cache"))
301 cachemtime = os.path.getmtime(self.join("eol.cache"))
301 except OSError:
302 except OSError:
302 cachemtime = 0
303 cachemtime = 0
303
304
304 try:
305 try:
305 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
306 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
306 except OSError:
307 except OSError:
307 eolmtime = 0
308 eolmtime = 0
308
309
309 if eolmtime > cachemtime:
310 if eolmtime > cachemtime:
310 self.ui.debug("eol: detected change in .hgeol\n")
311 self.ui.debug("eol: detected change in .hgeol\n")
311 wlock = None
312 wlock = None
312 try:
313 try:
313 wlock = self.wlock()
314 wlock = self.wlock()
314 for f in self.dirstate:
315 for f in self.dirstate:
315 if self.dirstate[f] == 'n':
316 if self.dirstate[f] == 'n':
316 # all normal files need to be looked at
317 # all normal files need to be looked at
317 # again since the new .hgeol file might no
318 # again since the new .hgeol file might no
318 # longer match a file it matched before
319 # longer match a file it matched before
319 self.dirstate.normallookup(f)
320 self.dirstate.normallookup(f)
320 # Create or touch the cache to update mtime
321 # Create or touch the cache to update mtime
321 self.opener("eol.cache", "w").close()
322 self.opener("eol.cache", "w").close()
322 wlock.release()
323 wlock.release()
323 except error.LockUnavailable:
324 except error.LockUnavailable:
324 # If we cannot lock the repository and clear the
325 # If we cannot lock the repository and clear the
325 # dirstate, then a commit might not see all files
326 # dirstate, then a commit might not see all files
326 # as modified. But if we cannot lock the
327 # as modified. But if we cannot lock the
327 # repository, then we can also not make a commit,
328 # repository, then we can also not make a commit,
328 # so ignore the error.
329 # so ignore the error.
329 pass
330 pass
330
331
331 def commitctx(self, ctx, error=False):
332 def commitctx(self, ctx, error=False):
332 for f in sorted(ctx.added() + ctx.modified()):
333 for f in sorted(ctx.added() + ctx.modified()):
333 if not self._eolfile(f):
334 if not self._eolfile(f):
334 continue
335 continue
335 try:
336 try:
336 data = ctx[f].data()
337 data = ctx[f].data()
337 except IOError:
338 except IOError:
338 continue
339 continue
339 if util.binary(data):
340 if util.binary(data):
340 # We should not abort here, since the user should
341 # We should not abort here, since the user should
341 # be able to say "** = native" to automatically
342 # be able to say "** = native" to automatically
342 # have all non-binary files taken care of.
343 # have all non-binary files taken care of.
343 continue
344 continue
344 if inconsistenteol(data):
345 if inconsistenteol(data):
345 raise util.Abort(_("inconsistent newline style "
346 raise util.Abort(_("inconsistent newline style "
346 "in %s\n" % f))
347 "in %s\n" % f))
347 return super(eolrepo, self).commitctx(ctx, error)
348 return super(eolrepo, self).commitctx(ctx, error)
348 repo.__class__ = eolrepo
349 repo.__class__ = eolrepo
349 repo._hgcleardirstate()
350 repo._hgcleardirstate()
@@ -1,731 +1,732 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2012 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2012 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a Distributed SCM
10 # Keyword expansion hack against the grain of a Distributed SCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Keywords expand to the changeset data pertaining to the latest change
38 Keywords expand to the changeset data pertaining to the latest change
39 relative to the working directory parent of each file.
39 relative to the working directory parent of each file.
40
40
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 sections of hgrc files.
42 sections of hgrc files.
43
43
44 Example::
44 Example::
45
45
46 [keyword]
46 [keyword]
47 # expand keywords in every python file except those matching "x*"
47 # expand keywords in every python file except those matching "x*"
48 **.py =
48 **.py =
49 x* = ignore
49 x* = ignore
50
50
51 [keywordset]
51 [keywordset]
52 # prefer svn- over cvs-like default keywordmaps
52 # prefer svn- over cvs-like default keywordmaps
53 svn = True
53 svn = True
54
54
55 .. note::
55 .. note::
56
56 The more specific you are in your filename patterns the less you
57 The more specific you are in your filename patterns the less you
57 lose speed in huge repositories.
58 lose speed in huge repositories.
58
59
59 For [keywordmaps] template mapping and expansion demonstration and
60 For [keywordmaps] template mapping and expansion demonstration and
60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 available templates and filters.
62 available templates and filters.
62
63
63 Three additional date template filters are provided:
64 Three additional date template filters are provided:
64
65
65 :``utcdate``: "2006/09/18 15:13:13"
66 :``utcdate``: "2006/09/18 15:13:13"
66 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68
69
69 The default template mappings (view with :hg:`kwdemo -d`) can be
70 The default template mappings (view with :hg:`kwdemo -d`) can be
70 replaced with customized keywords and templates. Again, run
71 replaced with customized keywords and templates. Again, run
71 :hg:`kwdemo` to control the results of your configuration changes.
72 :hg:`kwdemo` to control the results of your configuration changes.
72
73
73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 to avoid storing expanded keywords in the change history.
75 to avoid storing expanded keywords in the change history.
75
76
76 To force expansion after enabling it, or a configuration change, run
77 To force expansion after enabling it, or a configuration change, run
77 :hg:`kwexpand`.
78 :hg:`kwexpand`.
78
79
79 Expansions spanning more than one line and incremental expansions,
80 Expansions spanning more than one line and incremental expansions,
80 like CVS' $Log$, are not supported. A keyword template map "Log =
81 like CVS' $Log$, are not supported. A keyword template map "Log =
81 {desc}" expands to the first line of the changeset description.
82 {desc}" expands to the first line of the changeset description.
82 '''
83 '''
83
84
84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 from mercurial import scmutil
87 from mercurial import scmutil
87 from mercurial.hgweb import webcommands
88 from mercurial.hgweb import webcommands
88 from mercurial.i18n import _
89 from mercurial.i18n import _
89 import os, re, shutil, tempfile
90 import os, re, shutil, tempfile
90
91
91 commands.optionalrepo += ' kwdemo'
92 commands.optionalrepo += ' kwdemo'
92 commands.inferrepo += ' kwexpand kwfiles kwshrink'
93 commands.inferrepo += ' kwexpand kwfiles kwshrink'
93
94
94 cmdtable = {}
95 cmdtable = {}
95 command = cmdutil.command(cmdtable)
96 command = cmdutil.command(cmdtable)
96 testedwith = 'internal'
97 testedwith = 'internal'
97
98
98 # hg commands that do not act on keywords
99 # hg commands that do not act on keywords
99 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
100 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
100 ' outgoing push tip verify convert email glog')
101 ' outgoing push tip verify convert email glog')
101
102
102 # hg commands that trigger expansion only when writing to working dir,
103 # hg commands that trigger expansion only when writing to working dir,
103 # not when reading filelog, and unexpand when reading from working dir
104 # not when reading filelog, and unexpand when reading from working dir
104 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
105 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
105
106
106 # names of extensions using dorecord
107 # names of extensions using dorecord
107 recordextensions = 'record'
108 recordextensions = 'record'
108
109
109 colortable = {
110 colortable = {
110 'kwfiles.enabled': 'green bold',
111 'kwfiles.enabled': 'green bold',
111 'kwfiles.deleted': 'cyan bold underline',
112 'kwfiles.deleted': 'cyan bold underline',
112 'kwfiles.enabledunknown': 'green',
113 'kwfiles.enabledunknown': 'green',
113 'kwfiles.ignored': 'bold',
114 'kwfiles.ignored': 'bold',
114 'kwfiles.ignoredunknown': 'none'
115 'kwfiles.ignoredunknown': 'none'
115 }
116 }
116
117
117 # date like in cvs' $Date
118 # date like in cvs' $Date
118 def utcdate(text):
119 def utcdate(text):
119 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
120 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
120 '''
121 '''
121 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
122 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
122 # date like in svn's $Date
123 # date like in svn's $Date
123 def svnisodate(text):
124 def svnisodate(text):
124 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
125 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
125 +0200 (Tue, 18 Aug 2009)".
126 +0200 (Tue, 18 Aug 2009)".
126 '''
127 '''
127 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
128 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
128 # date like in svn's $Id
129 # date like in svn's $Id
129 def svnutcdate(text):
130 def svnutcdate(text):
130 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
131 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
131 11:00:13Z".
132 11:00:13Z".
132 '''
133 '''
133 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
134 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
134
135
135 templatefilters.filters.update({'utcdate': utcdate,
136 templatefilters.filters.update({'utcdate': utcdate,
136 'svnisodate': svnisodate,
137 'svnisodate': svnisodate,
137 'svnutcdate': svnutcdate})
138 'svnutcdate': svnutcdate})
138
139
139 # make keyword tools accessible
140 # make keyword tools accessible
140 kwtools = {'templater': None, 'hgcmd': ''}
141 kwtools = {'templater': None, 'hgcmd': ''}
141
142
142 def _defaultkwmaps(ui):
143 def _defaultkwmaps(ui):
143 '''Returns default keywordmaps according to keywordset configuration.'''
144 '''Returns default keywordmaps according to keywordset configuration.'''
144 templates = {
145 templates = {
145 'Revision': '{node|short}',
146 'Revision': '{node|short}',
146 'Author': '{author|user}',
147 'Author': '{author|user}',
147 }
148 }
148 kwsets = ({
149 kwsets = ({
149 'Date': '{date|utcdate}',
150 'Date': '{date|utcdate}',
150 'RCSfile': '{file|basename},v',
151 'RCSfile': '{file|basename},v',
151 'RCSFile': '{file|basename},v', # kept for backwards compatibility
152 'RCSFile': '{file|basename},v', # kept for backwards compatibility
152 # with hg-keyword
153 # with hg-keyword
153 'Source': '{root}/{file},v',
154 'Source': '{root}/{file},v',
154 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
155 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
155 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
156 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
156 }, {
157 }, {
157 'Date': '{date|svnisodate}',
158 'Date': '{date|svnisodate}',
158 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
159 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
159 'LastChangedRevision': '{node|short}',
160 'LastChangedRevision': '{node|short}',
160 'LastChangedBy': '{author|user}',
161 'LastChangedBy': '{author|user}',
161 'LastChangedDate': '{date|svnisodate}',
162 'LastChangedDate': '{date|svnisodate}',
162 })
163 })
163 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
164 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
164 return templates
165 return templates
165
166
166 def _shrinktext(text, subfunc):
167 def _shrinktext(text, subfunc):
167 '''Helper for keyword expansion removal in text.
168 '''Helper for keyword expansion removal in text.
168 Depending on subfunc also returns number of substitutions.'''
169 Depending on subfunc also returns number of substitutions.'''
169 return subfunc(r'$\1$', text)
170 return subfunc(r'$\1$', text)
170
171
171 def _preselect(wstatus, changed):
172 def _preselect(wstatus, changed):
172 '''Retrieves modified and added files from a working directory state
173 '''Retrieves modified and added files from a working directory state
173 and returns the subset of each contained in given changed files
174 and returns the subset of each contained in given changed files
174 retrieved from a change context.'''
175 retrieved from a change context.'''
175 modified, added = wstatus[:2]
176 modified, added = wstatus[:2]
176 modified = [f for f in modified if f in changed]
177 modified = [f for f in modified if f in changed]
177 added = [f for f in added if f in changed]
178 added = [f for f in added if f in changed]
178 return modified, added
179 return modified, added
179
180
180
181
181 class kwtemplater(object):
182 class kwtemplater(object):
182 '''
183 '''
183 Sets up keyword templates, corresponding keyword regex, and
184 Sets up keyword templates, corresponding keyword regex, and
184 provides keyword substitution functions.
185 provides keyword substitution functions.
185 '''
186 '''
186
187
187 def __init__(self, ui, repo, inc, exc):
188 def __init__(self, ui, repo, inc, exc):
188 self.ui = ui
189 self.ui = ui
189 self.repo = repo
190 self.repo = repo
190 self.match = match.match(repo.root, '', [], inc, exc)
191 self.match = match.match(repo.root, '', [], inc, exc)
191 self.restrict = kwtools['hgcmd'] in restricted.split()
192 self.restrict = kwtools['hgcmd'] in restricted.split()
192 self.postcommit = False
193 self.postcommit = False
193
194
194 kwmaps = self.ui.configitems('keywordmaps')
195 kwmaps = self.ui.configitems('keywordmaps')
195 if kwmaps: # override default templates
196 if kwmaps: # override default templates
196 self.templates = dict((k, templater.parsestring(v, False))
197 self.templates = dict((k, templater.parsestring(v, False))
197 for k, v in kwmaps)
198 for k, v in kwmaps)
198 else:
199 else:
199 self.templates = _defaultkwmaps(self.ui)
200 self.templates = _defaultkwmaps(self.ui)
200
201
201 @util.propertycache
202 @util.propertycache
202 def escape(self):
203 def escape(self):
203 '''Returns bar-separated and escaped keywords.'''
204 '''Returns bar-separated and escaped keywords.'''
204 return '|'.join(map(re.escape, self.templates.keys()))
205 return '|'.join(map(re.escape, self.templates.keys()))
205
206
206 @util.propertycache
207 @util.propertycache
207 def rekw(self):
208 def rekw(self):
208 '''Returns regex for unexpanded keywords.'''
209 '''Returns regex for unexpanded keywords.'''
209 return re.compile(r'\$(%s)\$' % self.escape)
210 return re.compile(r'\$(%s)\$' % self.escape)
210
211
211 @util.propertycache
212 @util.propertycache
212 def rekwexp(self):
213 def rekwexp(self):
213 '''Returns regex for expanded keywords.'''
214 '''Returns regex for expanded keywords.'''
214 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
215 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
215
216
216 def substitute(self, data, path, ctx, subfunc):
217 def substitute(self, data, path, ctx, subfunc):
217 '''Replaces keywords in data with expanded template.'''
218 '''Replaces keywords in data with expanded template.'''
218 def kwsub(mobj):
219 def kwsub(mobj):
219 kw = mobj.group(1)
220 kw = mobj.group(1)
220 ct = cmdutil.changeset_templater(self.ui, self.repo,
221 ct = cmdutil.changeset_templater(self.ui, self.repo,
221 False, None, '', False)
222 False, None, '', False)
222 ct.use_template(self.templates[kw])
223 ct.use_template(self.templates[kw])
223 self.ui.pushbuffer()
224 self.ui.pushbuffer()
224 ct.show(ctx, root=self.repo.root, file=path)
225 ct.show(ctx, root=self.repo.root, file=path)
225 ekw = templatefilters.firstline(self.ui.popbuffer())
226 ekw = templatefilters.firstline(self.ui.popbuffer())
226 return '$%s: %s $' % (kw, ekw)
227 return '$%s: %s $' % (kw, ekw)
227 return subfunc(kwsub, data)
228 return subfunc(kwsub, data)
228
229
229 def linkctx(self, path, fileid):
230 def linkctx(self, path, fileid):
230 '''Similar to filelog.linkrev, but returns a changectx.'''
231 '''Similar to filelog.linkrev, but returns a changectx.'''
231 return self.repo.filectx(path, fileid=fileid).changectx()
232 return self.repo.filectx(path, fileid=fileid).changectx()
232
233
233 def expand(self, path, node, data):
234 def expand(self, path, node, data):
234 '''Returns data with keywords expanded.'''
235 '''Returns data with keywords expanded.'''
235 if not self.restrict and self.match(path) and not util.binary(data):
236 if not self.restrict and self.match(path) and not util.binary(data):
236 ctx = self.linkctx(path, node)
237 ctx = self.linkctx(path, node)
237 return self.substitute(data, path, ctx, self.rekw.sub)
238 return self.substitute(data, path, ctx, self.rekw.sub)
238 return data
239 return data
239
240
240 def iskwfile(self, cand, ctx):
241 def iskwfile(self, cand, ctx):
241 '''Returns subset of candidates which are configured for keyword
242 '''Returns subset of candidates which are configured for keyword
242 expansion but are not symbolic links.'''
243 expansion but are not symbolic links.'''
243 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
244 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
244
245
245 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
246 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
246 '''Overwrites selected files expanding/shrinking keywords.'''
247 '''Overwrites selected files expanding/shrinking keywords.'''
247 if self.restrict or lookup or self.postcommit: # exclude kw_copy
248 if self.restrict or lookup or self.postcommit: # exclude kw_copy
248 candidates = self.iskwfile(candidates, ctx)
249 candidates = self.iskwfile(candidates, ctx)
249 if not candidates:
250 if not candidates:
250 return
251 return
251 kwcmd = self.restrict and lookup # kwexpand/kwshrink
252 kwcmd = self.restrict and lookup # kwexpand/kwshrink
252 if self.restrict or expand and lookup:
253 if self.restrict or expand and lookup:
253 mf = ctx.manifest()
254 mf = ctx.manifest()
254 if self.restrict or rekw:
255 if self.restrict or rekw:
255 re_kw = self.rekw
256 re_kw = self.rekw
256 else:
257 else:
257 re_kw = self.rekwexp
258 re_kw = self.rekwexp
258 if expand:
259 if expand:
259 msg = _('overwriting %s expanding keywords\n')
260 msg = _('overwriting %s expanding keywords\n')
260 else:
261 else:
261 msg = _('overwriting %s shrinking keywords\n')
262 msg = _('overwriting %s shrinking keywords\n')
262 for f in candidates:
263 for f in candidates:
263 if self.restrict:
264 if self.restrict:
264 data = self.repo.file(f).read(mf[f])
265 data = self.repo.file(f).read(mf[f])
265 else:
266 else:
266 data = self.repo.wread(f)
267 data = self.repo.wread(f)
267 if util.binary(data):
268 if util.binary(data):
268 continue
269 continue
269 if expand:
270 if expand:
270 if lookup:
271 if lookup:
271 ctx = self.linkctx(f, mf[f])
272 ctx = self.linkctx(f, mf[f])
272 data, found = self.substitute(data, f, ctx, re_kw.subn)
273 data, found = self.substitute(data, f, ctx, re_kw.subn)
273 elif self.restrict:
274 elif self.restrict:
274 found = re_kw.search(data)
275 found = re_kw.search(data)
275 else:
276 else:
276 data, found = _shrinktext(data, re_kw.subn)
277 data, found = _shrinktext(data, re_kw.subn)
277 if found:
278 if found:
278 self.ui.note(msg % f)
279 self.ui.note(msg % f)
279 fp = self.repo.wopener(f, "wb", atomictemp=True)
280 fp = self.repo.wopener(f, "wb", atomictemp=True)
280 fp.write(data)
281 fp.write(data)
281 fp.close()
282 fp.close()
282 if kwcmd:
283 if kwcmd:
283 self.repo.dirstate.normal(f)
284 self.repo.dirstate.normal(f)
284 elif self.postcommit:
285 elif self.postcommit:
285 self.repo.dirstate.normallookup(f)
286 self.repo.dirstate.normallookup(f)
286
287
287 def shrink(self, fname, text):
288 def shrink(self, fname, text):
288 '''Returns text with all keyword substitutions removed.'''
289 '''Returns text with all keyword substitutions removed.'''
289 if self.match(fname) and not util.binary(text):
290 if self.match(fname) and not util.binary(text):
290 return _shrinktext(text, self.rekwexp.sub)
291 return _shrinktext(text, self.rekwexp.sub)
291 return text
292 return text
292
293
293 def shrinklines(self, fname, lines):
294 def shrinklines(self, fname, lines):
294 '''Returns lines with keyword substitutions removed.'''
295 '''Returns lines with keyword substitutions removed.'''
295 if self.match(fname):
296 if self.match(fname):
296 text = ''.join(lines)
297 text = ''.join(lines)
297 if not util.binary(text):
298 if not util.binary(text):
298 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
299 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
299 return lines
300 return lines
300
301
301 def wread(self, fname, data):
302 def wread(self, fname, data):
302 '''If in restricted mode returns data read from wdir with
303 '''If in restricted mode returns data read from wdir with
303 keyword substitutions removed.'''
304 keyword substitutions removed.'''
304 if self.restrict:
305 if self.restrict:
305 return self.shrink(fname, data)
306 return self.shrink(fname, data)
306 return data
307 return data
307
308
308 class kwfilelog(filelog.filelog):
309 class kwfilelog(filelog.filelog):
309 '''
310 '''
310 Subclass of filelog to hook into its read, add, cmp methods.
311 Subclass of filelog to hook into its read, add, cmp methods.
311 Keywords are "stored" unexpanded, and processed on reading.
312 Keywords are "stored" unexpanded, and processed on reading.
312 '''
313 '''
313 def __init__(self, opener, kwt, path):
314 def __init__(self, opener, kwt, path):
314 super(kwfilelog, self).__init__(opener, path)
315 super(kwfilelog, self).__init__(opener, path)
315 self.kwt = kwt
316 self.kwt = kwt
316 self.path = path
317 self.path = path
317
318
318 def read(self, node):
319 def read(self, node):
319 '''Expands keywords when reading filelog.'''
320 '''Expands keywords when reading filelog.'''
320 data = super(kwfilelog, self).read(node)
321 data = super(kwfilelog, self).read(node)
321 if self.renamed(node):
322 if self.renamed(node):
322 return data
323 return data
323 return self.kwt.expand(self.path, node, data)
324 return self.kwt.expand(self.path, node, data)
324
325
325 def add(self, text, meta, tr, link, p1=None, p2=None):
326 def add(self, text, meta, tr, link, p1=None, p2=None):
326 '''Removes keyword substitutions when adding to filelog.'''
327 '''Removes keyword substitutions when adding to filelog.'''
327 text = self.kwt.shrink(self.path, text)
328 text = self.kwt.shrink(self.path, text)
328 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
329 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
329
330
330 def cmp(self, node, text):
331 def cmp(self, node, text):
331 '''Removes keyword substitutions for comparison.'''
332 '''Removes keyword substitutions for comparison.'''
332 text = self.kwt.shrink(self.path, text)
333 text = self.kwt.shrink(self.path, text)
333 return super(kwfilelog, self).cmp(node, text)
334 return super(kwfilelog, self).cmp(node, text)
334
335
335 def _status(ui, repo, wctx, kwt, *pats, **opts):
336 def _status(ui, repo, wctx, kwt, *pats, **opts):
336 '''Bails out if [keyword] configuration is not active.
337 '''Bails out if [keyword] configuration is not active.
337 Returns status of working directory.'''
338 Returns status of working directory.'''
338 if kwt:
339 if kwt:
339 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
340 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
340 unknown=opts.get('unknown') or opts.get('all'))
341 unknown=opts.get('unknown') or opts.get('all'))
341 if ui.configitems('keyword'):
342 if ui.configitems('keyword'):
342 raise util.Abort(_('[keyword] patterns cannot match'))
343 raise util.Abort(_('[keyword] patterns cannot match'))
343 raise util.Abort(_('no [keyword] patterns configured'))
344 raise util.Abort(_('no [keyword] patterns configured'))
344
345
345 def _kwfwrite(ui, repo, expand, *pats, **opts):
346 def _kwfwrite(ui, repo, expand, *pats, **opts):
346 '''Selects files and passes them to kwtemplater.overwrite.'''
347 '''Selects files and passes them to kwtemplater.overwrite.'''
347 wctx = repo[None]
348 wctx = repo[None]
348 if len(wctx.parents()) > 1:
349 if len(wctx.parents()) > 1:
349 raise util.Abort(_('outstanding uncommitted merge'))
350 raise util.Abort(_('outstanding uncommitted merge'))
350 kwt = kwtools['templater']
351 kwt = kwtools['templater']
351 wlock = repo.wlock()
352 wlock = repo.wlock()
352 try:
353 try:
353 status = _status(ui, repo, wctx, kwt, *pats, **opts)
354 status = _status(ui, repo, wctx, kwt, *pats, **opts)
354 modified, added, removed, deleted, unknown, ignored, clean = status
355 modified, added, removed, deleted, unknown, ignored, clean = status
355 if modified or added or removed or deleted:
356 if modified or added or removed or deleted:
356 raise util.Abort(_('outstanding uncommitted changes'))
357 raise util.Abort(_('outstanding uncommitted changes'))
357 kwt.overwrite(wctx, clean, True, expand)
358 kwt.overwrite(wctx, clean, True, expand)
358 finally:
359 finally:
359 wlock.release()
360 wlock.release()
360
361
361 @command('kwdemo',
362 @command('kwdemo',
362 [('d', 'default', None, _('show default keyword template maps')),
363 [('d', 'default', None, _('show default keyword template maps')),
363 ('f', 'rcfile', '',
364 ('f', 'rcfile', '',
364 _('read maps from rcfile'), _('FILE'))],
365 _('read maps from rcfile'), _('FILE'))],
365 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
366 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
366 def demo(ui, repo, *args, **opts):
367 def demo(ui, repo, *args, **opts):
367 '''print [keywordmaps] configuration and an expansion example
368 '''print [keywordmaps] configuration and an expansion example
368
369
369 Show current, custom, or default keyword template maps and their
370 Show current, custom, or default keyword template maps and their
370 expansions.
371 expansions.
371
372
372 Extend the current configuration by specifying maps as arguments
373 Extend the current configuration by specifying maps as arguments
373 and using -f/--rcfile to source an external hgrc file.
374 and using -f/--rcfile to source an external hgrc file.
374
375
375 Use -d/--default to disable current configuration.
376 Use -d/--default to disable current configuration.
376
377
377 See :hg:`help templates` for information on templates and filters.
378 See :hg:`help templates` for information on templates and filters.
378 '''
379 '''
379 def demoitems(section, items):
380 def demoitems(section, items):
380 ui.write('[%s]\n' % section)
381 ui.write('[%s]\n' % section)
381 for k, v in sorted(items):
382 for k, v in sorted(items):
382 ui.write('%s = %s\n' % (k, v))
383 ui.write('%s = %s\n' % (k, v))
383
384
384 fn = 'demo.txt'
385 fn = 'demo.txt'
385 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
386 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
386 ui.note(_('creating temporary repository at %s\n') % tmpdir)
387 ui.note(_('creating temporary repository at %s\n') % tmpdir)
387 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
388 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
388 ui.setconfig('keyword', fn, '')
389 ui.setconfig('keyword', fn, '')
389 svn = ui.configbool('keywordset', 'svn')
390 svn = ui.configbool('keywordset', 'svn')
390 # explicitly set keywordset for demo output
391 # explicitly set keywordset for demo output
391 ui.setconfig('keywordset', 'svn', svn)
392 ui.setconfig('keywordset', 'svn', svn)
392
393
393 uikwmaps = ui.configitems('keywordmaps')
394 uikwmaps = ui.configitems('keywordmaps')
394 if args or opts.get('rcfile'):
395 if args or opts.get('rcfile'):
395 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
396 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
396 if uikwmaps:
397 if uikwmaps:
397 ui.status(_('\textending current template maps\n'))
398 ui.status(_('\textending current template maps\n'))
398 if opts.get('default') or not uikwmaps:
399 if opts.get('default') or not uikwmaps:
399 if svn:
400 if svn:
400 ui.status(_('\toverriding default svn keywordset\n'))
401 ui.status(_('\toverriding default svn keywordset\n'))
401 else:
402 else:
402 ui.status(_('\toverriding default cvs keywordset\n'))
403 ui.status(_('\toverriding default cvs keywordset\n'))
403 if opts.get('rcfile'):
404 if opts.get('rcfile'):
404 ui.readconfig(opts.get('rcfile'))
405 ui.readconfig(opts.get('rcfile'))
405 if args:
406 if args:
406 # simulate hgrc parsing
407 # simulate hgrc parsing
407 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
408 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
408 fp = repo.opener('hgrc', 'w')
409 fp = repo.opener('hgrc', 'w')
409 fp.writelines(rcmaps)
410 fp.writelines(rcmaps)
410 fp.close()
411 fp.close()
411 ui.readconfig(repo.join('hgrc'))
412 ui.readconfig(repo.join('hgrc'))
412 kwmaps = dict(ui.configitems('keywordmaps'))
413 kwmaps = dict(ui.configitems('keywordmaps'))
413 elif opts.get('default'):
414 elif opts.get('default'):
414 if svn:
415 if svn:
415 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
416 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
416 else:
417 else:
417 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
418 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
418 kwmaps = _defaultkwmaps(ui)
419 kwmaps = _defaultkwmaps(ui)
419 if uikwmaps:
420 if uikwmaps:
420 ui.status(_('\tdisabling current template maps\n'))
421 ui.status(_('\tdisabling current template maps\n'))
421 for k, v in kwmaps.iteritems():
422 for k, v in kwmaps.iteritems():
422 ui.setconfig('keywordmaps', k, v)
423 ui.setconfig('keywordmaps', k, v)
423 else:
424 else:
424 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
425 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
425 if uikwmaps:
426 if uikwmaps:
426 kwmaps = dict(uikwmaps)
427 kwmaps = dict(uikwmaps)
427 else:
428 else:
428 kwmaps = _defaultkwmaps(ui)
429 kwmaps = _defaultkwmaps(ui)
429
430
430 uisetup(ui)
431 uisetup(ui)
431 reposetup(ui, repo)
432 reposetup(ui, repo)
432 ui.write('[extensions]\nkeyword =\n')
433 ui.write('[extensions]\nkeyword =\n')
433 demoitems('keyword', ui.configitems('keyword'))
434 demoitems('keyword', ui.configitems('keyword'))
434 demoitems('keywordset', ui.configitems('keywordset'))
435 demoitems('keywordset', ui.configitems('keywordset'))
435 demoitems('keywordmaps', kwmaps.iteritems())
436 demoitems('keywordmaps', kwmaps.iteritems())
436 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
437 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
437 repo.wopener.write(fn, keywords)
438 repo.wopener.write(fn, keywords)
438 repo[None].add([fn])
439 repo[None].add([fn])
439 ui.note(_('\nkeywords written to %s:\n') % fn)
440 ui.note(_('\nkeywords written to %s:\n') % fn)
440 ui.note(keywords)
441 ui.note(keywords)
441 repo.dirstate.setbranch('demobranch')
442 repo.dirstate.setbranch('demobranch')
442 for name, cmd in ui.configitems('hooks'):
443 for name, cmd in ui.configitems('hooks'):
443 if name.split('.', 1)[0].find('commit') > -1:
444 if name.split('.', 1)[0].find('commit') > -1:
444 repo.ui.setconfig('hooks', name, '')
445 repo.ui.setconfig('hooks', name, '')
445 msg = _('hg keyword configuration and expansion example')
446 msg = _('hg keyword configuration and expansion example')
446 ui.note("hg ci -m '%s'\n" % msg) # check-code-ignore
447 ui.note("hg ci -m '%s'\n" % msg) # check-code-ignore
447 repo.commit(text=msg)
448 repo.commit(text=msg)
448 ui.status(_('\n\tkeywords expanded\n'))
449 ui.status(_('\n\tkeywords expanded\n'))
449 ui.write(repo.wread(fn))
450 ui.write(repo.wread(fn))
450 shutil.rmtree(tmpdir, ignore_errors=True)
451 shutil.rmtree(tmpdir, ignore_errors=True)
451
452
452 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
453 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
453 def expand(ui, repo, *pats, **opts):
454 def expand(ui, repo, *pats, **opts):
454 '''expand keywords in the working directory
455 '''expand keywords in the working directory
455
456
456 Run after (re)enabling keyword expansion.
457 Run after (re)enabling keyword expansion.
457
458
458 kwexpand refuses to run if given files contain local changes.
459 kwexpand refuses to run if given files contain local changes.
459 '''
460 '''
460 # 3rd argument sets expansion to True
461 # 3rd argument sets expansion to True
461 _kwfwrite(ui, repo, True, *pats, **opts)
462 _kwfwrite(ui, repo, True, *pats, **opts)
462
463
463 @command('kwfiles',
464 @command('kwfiles',
464 [('A', 'all', None, _('show keyword status flags of all files')),
465 [('A', 'all', None, _('show keyword status flags of all files')),
465 ('i', 'ignore', None, _('show files excluded from expansion')),
466 ('i', 'ignore', None, _('show files excluded from expansion')),
466 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
467 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
467 ] + commands.walkopts,
468 ] + commands.walkopts,
468 _('hg kwfiles [OPTION]... [FILE]...'))
469 _('hg kwfiles [OPTION]... [FILE]...'))
469 def files(ui, repo, *pats, **opts):
470 def files(ui, repo, *pats, **opts):
470 '''show files configured for keyword expansion
471 '''show files configured for keyword expansion
471
472
472 List which files in the working directory are matched by the
473 List which files in the working directory are matched by the
473 [keyword] configuration patterns.
474 [keyword] configuration patterns.
474
475
475 Useful to prevent inadvertent keyword expansion and to speed up
476 Useful to prevent inadvertent keyword expansion and to speed up
476 execution by including only files that are actual candidates for
477 execution by including only files that are actual candidates for
477 expansion.
478 expansion.
478
479
479 See :hg:`help keyword` on how to construct patterns both for
480 See :hg:`help keyword` on how to construct patterns both for
480 inclusion and exclusion of files.
481 inclusion and exclusion of files.
481
482
482 With -A/--all and -v/--verbose the codes used to show the status
483 With -A/--all and -v/--verbose the codes used to show the status
483 of files are::
484 of files are::
484
485
485 K = keyword expansion candidate
486 K = keyword expansion candidate
486 k = keyword expansion candidate (not tracked)
487 k = keyword expansion candidate (not tracked)
487 I = ignored
488 I = ignored
488 i = ignored (not tracked)
489 i = ignored (not tracked)
489 '''
490 '''
490 kwt = kwtools['templater']
491 kwt = kwtools['templater']
491 wctx = repo[None]
492 wctx = repo[None]
492 status = _status(ui, repo, wctx, kwt, *pats, **opts)
493 status = _status(ui, repo, wctx, kwt, *pats, **opts)
493 cwd = pats and repo.getcwd() or ''
494 cwd = pats and repo.getcwd() or ''
494 modified, added, removed, deleted, unknown, ignored, clean = status
495 modified, added, removed, deleted, unknown, ignored, clean = status
495 files = []
496 files = []
496 if not opts.get('unknown') or opts.get('all'):
497 if not opts.get('unknown') or opts.get('all'):
497 files = sorted(modified + added + clean)
498 files = sorted(modified + added + clean)
498 kwfiles = kwt.iskwfile(files, wctx)
499 kwfiles = kwt.iskwfile(files, wctx)
499 kwdeleted = kwt.iskwfile(deleted, wctx)
500 kwdeleted = kwt.iskwfile(deleted, wctx)
500 kwunknown = kwt.iskwfile(unknown, wctx)
501 kwunknown = kwt.iskwfile(unknown, wctx)
501 if not opts.get('ignore') or opts.get('all'):
502 if not opts.get('ignore') or opts.get('all'):
502 showfiles = kwfiles, kwdeleted, kwunknown
503 showfiles = kwfiles, kwdeleted, kwunknown
503 else:
504 else:
504 showfiles = [], [], []
505 showfiles = [], [], []
505 if opts.get('all') or opts.get('ignore'):
506 if opts.get('all') or opts.get('ignore'):
506 showfiles += ([f for f in files if f not in kwfiles],
507 showfiles += ([f for f in files if f not in kwfiles],
507 [f for f in unknown if f not in kwunknown])
508 [f for f in unknown if f not in kwunknown])
508 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
509 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
509 kwstates = zip(kwlabels, 'K!kIi', showfiles)
510 kwstates = zip(kwlabels, 'K!kIi', showfiles)
510 fm = ui.formatter('kwfiles', opts)
511 fm = ui.formatter('kwfiles', opts)
511 fmt = '%.0s%s\n'
512 fmt = '%.0s%s\n'
512 if opts.get('all') or ui.verbose:
513 if opts.get('all') or ui.verbose:
513 fmt = '%s %s\n'
514 fmt = '%s %s\n'
514 for kwstate, char, filenames in kwstates:
515 for kwstate, char, filenames in kwstates:
515 label = 'kwfiles.' + kwstate
516 label = 'kwfiles.' + kwstate
516 for f in filenames:
517 for f in filenames:
517 fm.startitem()
518 fm.startitem()
518 fm.write('kwstatus path', fmt, char,
519 fm.write('kwstatus path', fmt, char,
519 repo.pathto(f, cwd), label=label)
520 repo.pathto(f, cwd), label=label)
520 fm.end()
521 fm.end()
521
522
522 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
523 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
523 def shrink(ui, repo, *pats, **opts):
524 def shrink(ui, repo, *pats, **opts):
524 '''revert expanded keywords in the working directory
525 '''revert expanded keywords in the working directory
525
526
526 Must be run before changing/disabling active keywords.
527 Must be run before changing/disabling active keywords.
527
528
528 kwshrink refuses to run if given files contain local changes.
529 kwshrink refuses to run if given files contain local changes.
529 '''
530 '''
530 # 3rd argument sets expansion to False
531 # 3rd argument sets expansion to False
531 _kwfwrite(ui, repo, False, *pats, **opts)
532 _kwfwrite(ui, repo, False, *pats, **opts)
532
533
533
534
534 def uisetup(ui):
535 def uisetup(ui):
535 ''' Monkeypatches dispatch._parse to retrieve user command.'''
536 ''' Monkeypatches dispatch._parse to retrieve user command.'''
536
537
537 def kwdispatch_parse(orig, ui, args):
538 def kwdispatch_parse(orig, ui, args):
538 '''Monkeypatch dispatch._parse to obtain running hg command.'''
539 '''Monkeypatch dispatch._parse to obtain running hg command.'''
539 cmd, func, args, options, cmdoptions = orig(ui, args)
540 cmd, func, args, options, cmdoptions = orig(ui, args)
540 kwtools['hgcmd'] = cmd
541 kwtools['hgcmd'] = cmd
541 return cmd, func, args, options, cmdoptions
542 return cmd, func, args, options, cmdoptions
542
543
543 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
544 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
544
545
545 def reposetup(ui, repo):
546 def reposetup(ui, repo):
546 '''Sets up repo as kwrepo for keyword substitution.
547 '''Sets up repo as kwrepo for keyword substitution.
547 Overrides file method to return kwfilelog instead of filelog
548 Overrides file method to return kwfilelog instead of filelog
548 if file matches user configuration.
549 if file matches user configuration.
549 Wraps commit to overwrite configured files with updated
550 Wraps commit to overwrite configured files with updated
550 keyword substitutions.
551 keyword substitutions.
551 Monkeypatches patch and webcommands.'''
552 Monkeypatches patch and webcommands.'''
552
553
553 try:
554 try:
554 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
555 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
555 or '.hg' in util.splitpath(repo.root)
556 or '.hg' in util.splitpath(repo.root)
556 or repo._url.startswith('bundle:')):
557 or repo._url.startswith('bundle:')):
557 return
558 return
558 except AttributeError:
559 except AttributeError:
559 pass
560 pass
560
561
561 inc, exc = [], ['.hg*']
562 inc, exc = [], ['.hg*']
562 for pat, opt in ui.configitems('keyword'):
563 for pat, opt in ui.configitems('keyword'):
563 if opt != 'ignore':
564 if opt != 'ignore':
564 inc.append(pat)
565 inc.append(pat)
565 else:
566 else:
566 exc.append(pat)
567 exc.append(pat)
567 if not inc:
568 if not inc:
568 return
569 return
569
570
570 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
571 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
571
572
572 class kwrepo(repo.__class__):
573 class kwrepo(repo.__class__):
573 def file(self, f):
574 def file(self, f):
574 if f[0] == '/':
575 if f[0] == '/':
575 f = f[1:]
576 f = f[1:]
576 return kwfilelog(self.sopener, kwt, f)
577 return kwfilelog(self.sopener, kwt, f)
577
578
578 def wread(self, filename):
579 def wread(self, filename):
579 data = super(kwrepo, self).wread(filename)
580 data = super(kwrepo, self).wread(filename)
580 return kwt.wread(filename, data)
581 return kwt.wread(filename, data)
581
582
582 def commit(self, *args, **opts):
583 def commit(self, *args, **opts):
583 # use custom commitctx for user commands
584 # use custom commitctx for user commands
584 # other extensions can still wrap repo.commitctx directly
585 # other extensions can still wrap repo.commitctx directly
585 self.commitctx = self.kwcommitctx
586 self.commitctx = self.kwcommitctx
586 try:
587 try:
587 return super(kwrepo, self).commit(*args, **opts)
588 return super(kwrepo, self).commit(*args, **opts)
588 finally:
589 finally:
589 del self.commitctx
590 del self.commitctx
590
591
591 def kwcommitctx(self, ctx, error=False):
592 def kwcommitctx(self, ctx, error=False):
592 n = super(kwrepo, self).commitctx(ctx, error)
593 n = super(kwrepo, self).commitctx(ctx, error)
593 # no lock needed, only called from repo.commit() which already locks
594 # no lock needed, only called from repo.commit() which already locks
594 if not kwt.postcommit:
595 if not kwt.postcommit:
595 restrict = kwt.restrict
596 restrict = kwt.restrict
596 kwt.restrict = True
597 kwt.restrict = True
597 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
598 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
598 False, True)
599 False, True)
599 kwt.restrict = restrict
600 kwt.restrict = restrict
600 return n
601 return n
601
602
602 def rollback(self, dryrun=False, force=False):
603 def rollback(self, dryrun=False, force=False):
603 wlock = self.wlock()
604 wlock = self.wlock()
604 try:
605 try:
605 if not dryrun:
606 if not dryrun:
606 changed = self['.'].files()
607 changed = self['.'].files()
607 ret = super(kwrepo, self).rollback(dryrun, force)
608 ret = super(kwrepo, self).rollback(dryrun, force)
608 if not dryrun:
609 if not dryrun:
609 ctx = self['.']
610 ctx = self['.']
610 modified, added = _preselect(self[None].status(), changed)
611 modified, added = _preselect(self[None].status(), changed)
611 kwt.overwrite(ctx, modified, True, True)
612 kwt.overwrite(ctx, modified, True, True)
612 kwt.overwrite(ctx, added, True, False)
613 kwt.overwrite(ctx, added, True, False)
613 return ret
614 return ret
614 finally:
615 finally:
615 wlock.release()
616 wlock.release()
616
617
617 # monkeypatches
618 # monkeypatches
618 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
619 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
619 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
620 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
620 rejects or conflicts due to expanded keywords in working dir.'''
621 rejects or conflicts due to expanded keywords in working dir.'''
621 orig(self, ui, gp, backend, store, eolmode)
622 orig(self, ui, gp, backend, store, eolmode)
622 # shrink keywords read from working dir
623 # shrink keywords read from working dir
623 self.lines = kwt.shrinklines(self.fname, self.lines)
624 self.lines = kwt.shrinklines(self.fname, self.lines)
624
625
625 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
626 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
626 opts=None, prefix=''):
627 opts=None, prefix=''):
627 '''Monkeypatch patch.diff to avoid expansion.'''
628 '''Monkeypatch patch.diff to avoid expansion.'''
628 kwt.restrict = True
629 kwt.restrict = True
629 return orig(repo, node1, node2, match, changes, opts, prefix)
630 return orig(repo, node1, node2, match, changes, opts, prefix)
630
631
631 def kwweb_skip(orig, web, req, tmpl):
632 def kwweb_skip(orig, web, req, tmpl):
632 '''Wraps webcommands.x turning off keyword expansion.'''
633 '''Wraps webcommands.x turning off keyword expansion.'''
633 kwt.match = util.never
634 kwt.match = util.never
634 return orig(web, req, tmpl)
635 return orig(web, req, tmpl)
635
636
636 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
637 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
637 '''Wraps cmdutil.amend expanding keywords after amend.'''
638 '''Wraps cmdutil.amend expanding keywords after amend.'''
638 wlock = repo.wlock()
639 wlock = repo.wlock()
639 try:
640 try:
640 kwt.postcommit = True
641 kwt.postcommit = True
641 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
642 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
642 if newid != old.node():
643 if newid != old.node():
643 ctx = repo[newid]
644 ctx = repo[newid]
644 kwt.restrict = True
645 kwt.restrict = True
645 kwt.overwrite(ctx, ctx.files(), False, True)
646 kwt.overwrite(ctx, ctx.files(), False, True)
646 kwt.restrict = False
647 kwt.restrict = False
647 return newid
648 return newid
648 finally:
649 finally:
649 wlock.release()
650 wlock.release()
650
651
651 def kw_copy(orig, ui, repo, pats, opts, rename=False):
652 def kw_copy(orig, ui, repo, pats, opts, rename=False):
652 '''Wraps cmdutil.copy so that copy/rename destinations do not
653 '''Wraps cmdutil.copy so that copy/rename destinations do not
653 contain expanded keywords.
654 contain expanded keywords.
654 Note that the source of a regular file destination may also be a
655 Note that the source of a regular file destination may also be a
655 symlink:
656 symlink:
656 hg cp sym x -> x is symlink
657 hg cp sym x -> x is symlink
657 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
658 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
658 For the latter we have to follow the symlink to find out whether its
659 For the latter we have to follow the symlink to find out whether its
659 target is configured for expansion and we therefore must unexpand the
660 target is configured for expansion and we therefore must unexpand the
660 keywords in the destination.'''
661 keywords in the destination.'''
661 wlock = repo.wlock()
662 wlock = repo.wlock()
662 try:
663 try:
663 orig(ui, repo, pats, opts, rename)
664 orig(ui, repo, pats, opts, rename)
664 if opts.get('dry_run'):
665 if opts.get('dry_run'):
665 return
666 return
666 wctx = repo[None]
667 wctx = repo[None]
667 cwd = repo.getcwd()
668 cwd = repo.getcwd()
668
669
669 def haskwsource(dest):
670 def haskwsource(dest):
670 '''Returns true if dest is a regular file and configured for
671 '''Returns true if dest is a regular file and configured for
671 expansion or a symlink which points to a file configured for
672 expansion or a symlink which points to a file configured for
672 expansion. '''
673 expansion. '''
673 source = repo.dirstate.copied(dest)
674 source = repo.dirstate.copied(dest)
674 if 'l' in wctx.flags(source):
675 if 'l' in wctx.flags(source):
675 source = scmutil.canonpath(repo.root, cwd,
676 source = scmutil.canonpath(repo.root, cwd,
676 os.path.realpath(source))
677 os.path.realpath(source))
677 return kwt.match(source)
678 return kwt.match(source)
678
679
679 candidates = [f for f in repo.dirstate.copies() if
680 candidates = [f for f in repo.dirstate.copies() if
680 'l' not in wctx.flags(f) and haskwsource(f)]
681 'l' not in wctx.flags(f) and haskwsource(f)]
681 kwt.overwrite(wctx, candidates, False, False)
682 kwt.overwrite(wctx, candidates, False, False)
682 finally:
683 finally:
683 wlock.release()
684 wlock.release()
684
685
685 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
686 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
686 '''Wraps record.dorecord expanding keywords after recording.'''
687 '''Wraps record.dorecord expanding keywords after recording.'''
687 wlock = repo.wlock()
688 wlock = repo.wlock()
688 try:
689 try:
689 # record returns 0 even when nothing has changed
690 # record returns 0 even when nothing has changed
690 # therefore compare nodes before and after
691 # therefore compare nodes before and after
691 kwt.postcommit = True
692 kwt.postcommit = True
692 ctx = repo['.']
693 ctx = repo['.']
693 wstatus = repo[None].status()
694 wstatus = repo[None].status()
694 ret = orig(ui, repo, commitfunc, *pats, **opts)
695 ret = orig(ui, repo, commitfunc, *pats, **opts)
695 recctx = repo['.']
696 recctx = repo['.']
696 if ctx != recctx:
697 if ctx != recctx:
697 modified, added = _preselect(wstatus, recctx.files())
698 modified, added = _preselect(wstatus, recctx.files())
698 kwt.restrict = False
699 kwt.restrict = False
699 kwt.overwrite(recctx, modified, False, True)
700 kwt.overwrite(recctx, modified, False, True)
700 kwt.overwrite(recctx, added, False, True, True)
701 kwt.overwrite(recctx, added, False, True, True)
701 kwt.restrict = True
702 kwt.restrict = True
702 return ret
703 return ret
703 finally:
704 finally:
704 wlock.release()
705 wlock.release()
705
706
706 def kwfilectx_cmp(orig, self, fctx):
707 def kwfilectx_cmp(orig, self, fctx):
707 # keyword affects data size, comparing wdir and filelog size does
708 # keyword affects data size, comparing wdir and filelog size does
708 # not make sense
709 # not make sense
709 if (fctx._filerev is None and
710 if (fctx._filerev is None and
710 (self._repo._encodefilterpats or
711 (self._repo._encodefilterpats or
711 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
712 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
712 self.size() - 4 == fctx.size()) or
713 self.size() - 4 == fctx.size()) or
713 self.size() == fctx.size()):
714 self.size() == fctx.size()):
714 return self._filelog.cmp(self._filenode, fctx.data())
715 return self._filelog.cmp(self._filenode, fctx.data())
715 return True
716 return True
716
717
717 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
718 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
718 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
719 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
719 extensions.wrapfunction(patch, 'diff', kw_diff)
720 extensions.wrapfunction(patch, 'diff', kw_diff)
720 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
721 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
721 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
722 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
722 for c in 'annotate changeset rev filediff diff'.split():
723 for c in 'annotate changeset rev filediff diff'.split():
723 extensions.wrapfunction(webcommands, c, kwweb_skip)
724 extensions.wrapfunction(webcommands, c, kwweb_skip)
724 for name in recordextensions.split():
725 for name in recordextensions.split():
725 try:
726 try:
726 record = extensions.find(name)
727 record = extensions.find(name)
727 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
728 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
728 except KeyError:
729 except KeyError:
729 pass
730 pass
730
731
731 repo.__class__ = kwrepo
732 repo.__class__ = kwrepo
@@ -1,3447 +1,3448 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60
60
61 This extension used to provide a strip command. This command now lives
61 This extension used to provide a strip command. This command now lives
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65 from mercurial.i18n import _
65 from mercurial.i18n import _
66 from mercurial.node import bin, hex, short, nullid, nullrev
66 from mercurial.node import bin, hex, short, nullid, nullrev
67 from mercurial.lock import release
67 from mercurial.lock import release
68 from mercurial import commands, cmdutil, hg, scmutil, util, revset
68 from mercurial import commands, cmdutil, hg, scmutil, util, revset
69 from mercurial import extensions, error, phases
69 from mercurial import extensions, error, phases
70 from mercurial import patch as patchmod
70 from mercurial import patch as patchmod
71 from mercurial import localrepo
71 from mercurial import localrepo
72 from mercurial import subrepo
72 from mercurial import subrepo
73 import os, re, errno, shutil
73 import os, re, errno, shutil
74
74
75 commands.norepo += " qclone"
75 commands.norepo += " qclone"
76
76
77 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
77 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
78
78
79 cmdtable = {}
79 cmdtable = {}
80 command = cmdutil.command(cmdtable)
80 command = cmdutil.command(cmdtable)
81 testedwith = 'internal'
81 testedwith = 'internal'
82
82
83 # force load strip extension formerly included in mq and import some utility
83 # force load strip extension formerly included in mq and import some utility
84 try:
84 try:
85 stripext = extensions.find('strip')
85 stripext = extensions.find('strip')
86 except KeyError:
86 except KeyError:
87 # note: load is lazy so we could avoid the try-except,
87 # note: load is lazy so we could avoid the try-except,
88 # but I (marmoute) prefer this explicit code.
88 # but I (marmoute) prefer this explicit code.
89 class dummyui(object):
89 class dummyui(object):
90 def debug(self, msg):
90 def debug(self, msg):
91 pass
91 pass
92 stripext = extensions.load(dummyui(), 'strip', '')
92 stripext = extensions.load(dummyui(), 'strip', '')
93
93
94 strip = stripext.strip
94 strip = stripext.strip
95 checksubstate = stripext.checksubstate
95 checksubstate = stripext.checksubstate
96 checklocalchanges = stripext.checklocalchanges
96 checklocalchanges = stripext.checklocalchanges
97
97
98
98
99 # Patch names looks like unix-file names.
99 # Patch names looks like unix-file names.
100 # They must be joinable with queue directory and result in the patch path.
100 # They must be joinable with queue directory and result in the patch path.
101 normname = util.normpath
101 normname = util.normpath
102
102
103 class statusentry(object):
103 class statusentry(object):
104 def __init__(self, node, name):
104 def __init__(self, node, name):
105 self.node, self.name = node, name
105 self.node, self.name = node, name
106 def __repr__(self):
106 def __repr__(self):
107 return hex(self.node) + ':' + self.name
107 return hex(self.node) + ':' + self.name
108
108
109 class patchheader(object):
109 class patchheader(object):
110 def __init__(self, pf, plainmode=False):
110 def __init__(self, pf, plainmode=False):
111 def eatdiff(lines):
111 def eatdiff(lines):
112 while lines:
112 while lines:
113 l = lines[-1]
113 l = lines[-1]
114 if (l.startswith("diff -") or
114 if (l.startswith("diff -") or
115 l.startswith("Index:") or
115 l.startswith("Index:") or
116 l.startswith("===========")):
116 l.startswith("===========")):
117 del lines[-1]
117 del lines[-1]
118 else:
118 else:
119 break
119 break
120 def eatempty(lines):
120 def eatempty(lines):
121 while lines:
121 while lines:
122 if not lines[-1].strip():
122 if not lines[-1].strip():
123 del lines[-1]
123 del lines[-1]
124 else:
124 else:
125 break
125 break
126
126
127 message = []
127 message = []
128 comments = []
128 comments = []
129 user = None
129 user = None
130 date = None
130 date = None
131 parent = None
131 parent = None
132 format = None
132 format = None
133 subject = None
133 subject = None
134 branch = None
134 branch = None
135 nodeid = None
135 nodeid = None
136 diffstart = 0
136 diffstart = 0
137
137
138 for line in file(pf):
138 for line in file(pf):
139 line = line.rstrip()
139 line = line.rstrip()
140 if (line.startswith('diff --git')
140 if (line.startswith('diff --git')
141 or (diffstart and line.startswith('+++ '))):
141 or (diffstart and line.startswith('+++ '))):
142 diffstart = 2
142 diffstart = 2
143 break
143 break
144 diffstart = 0 # reset
144 diffstart = 0 # reset
145 if line.startswith("--- "):
145 if line.startswith("--- "):
146 diffstart = 1
146 diffstart = 1
147 continue
147 continue
148 elif format == "hgpatch":
148 elif format == "hgpatch":
149 # parse values when importing the result of an hg export
149 # parse values when importing the result of an hg export
150 if line.startswith("# User "):
150 if line.startswith("# User "):
151 user = line[7:]
151 user = line[7:]
152 elif line.startswith("# Date "):
152 elif line.startswith("# Date "):
153 date = line[7:]
153 date = line[7:]
154 elif line.startswith("# Parent "):
154 elif line.startswith("# Parent "):
155 parent = line[9:].lstrip()
155 parent = line[9:].lstrip()
156 elif line.startswith("# Branch "):
156 elif line.startswith("# Branch "):
157 branch = line[9:]
157 branch = line[9:]
158 elif line.startswith("# Node ID "):
158 elif line.startswith("# Node ID "):
159 nodeid = line[10:]
159 nodeid = line[10:]
160 elif not line.startswith("# ") and line:
160 elif not line.startswith("# ") and line:
161 message.append(line)
161 message.append(line)
162 format = None
162 format = None
163 elif line == '# HG changeset patch':
163 elif line == '# HG changeset patch':
164 message = []
164 message = []
165 format = "hgpatch"
165 format = "hgpatch"
166 elif (format != "tagdone" and (line.startswith("Subject: ") or
166 elif (format != "tagdone" and (line.startswith("Subject: ") or
167 line.startswith("subject: "))):
167 line.startswith("subject: "))):
168 subject = line[9:]
168 subject = line[9:]
169 format = "tag"
169 format = "tag"
170 elif (format != "tagdone" and (line.startswith("From: ") or
170 elif (format != "tagdone" and (line.startswith("From: ") or
171 line.startswith("from: "))):
171 line.startswith("from: "))):
172 user = line[6:]
172 user = line[6:]
173 format = "tag"
173 format = "tag"
174 elif (format != "tagdone" and (line.startswith("Date: ") or
174 elif (format != "tagdone" and (line.startswith("Date: ") or
175 line.startswith("date: "))):
175 line.startswith("date: "))):
176 date = line[6:]
176 date = line[6:]
177 format = "tag"
177 format = "tag"
178 elif format == "tag" and line == "":
178 elif format == "tag" and line == "":
179 # when looking for tags (subject: from: etc) they
179 # when looking for tags (subject: from: etc) they
180 # end once you find a blank line in the source
180 # end once you find a blank line in the source
181 format = "tagdone"
181 format = "tagdone"
182 elif message or line:
182 elif message or line:
183 message.append(line)
183 message.append(line)
184 comments.append(line)
184 comments.append(line)
185
185
186 eatdiff(message)
186 eatdiff(message)
187 eatdiff(comments)
187 eatdiff(comments)
188 # Remember the exact starting line of the patch diffs before consuming
188 # Remember the exact starting line of the patch diffs before consuming
189 # empty lines, for external use by TortoiseHg and others
189 # empty lines, for external use by TortoiseHg and others
190 self.diffstartline = len(comments)
190 self.diffstartline = len(comments)
191 eatempty(message)
191 eatempty(message)
192 eatempty(comments)
192 eatempty(comments)
193
193
194 # make sure message isn't empty
194 # make sure message isn't empty
195 if format and format.startswith("tag") and subject:
195 if format and format.startswith("tag") and subject:
196 message.insert(0, "")
196 message.insert(0, "")
197 message.insert(0, subject)
197 message.insert(0, subject)
198
198
199 self.message = message
199 self.message = message
200 self.comments = comments
200 self.comments = comments
201 self.user = user
201 self.user = user
202 self.date = date
202 self.date = date
203 self.parent = parent
203 self.parent = parent
204 # nodeid and branch are for external use by TortoiseHg and others
204 # nodeid and branch are for external use by TortoiseHg and others
205 self.nodeid = nodeid
205 self.nodeid = nodeid
206 self.branch = branch
206 self.branch = branch
207 self.haspatch = diffstart > 1
207 self.haspatch = diffstart > 1
208 self.plainmode = plainmode
208 self.plainmode = plainmode
209
209
210 def setuser(self, user):
210 def setuser(self, user):
211 if not self.updateheader(['From: ', '# User '], user):
211 if not self.updateheader(['From: ', '# User '], user):
212 try:
212 try:
213 patchheaderat = self.comments.index('# HG changeset patch')
213 patchheaderat = self.comments.index('# HG changeset patch')
214 self.comments.insert(patchheaderat + 1, '# User ' + user)
214 self.comments.insert(patchheaderat + 1, '# User ' + user)
215 except ValueError:
215 except ValueError:
216 if self.plainmode or self._hasheader(['Date: ']):
216 if self.plainmode or self._hasheader(['Date: ']):
217 self.comments = ['From: ' + user] + self.comments
217 self.comments = ['From: ' + user] + self.comments
218 else:
218 else:
219 tmp = ['# HG changeset patch', '# User ' + user, '']
219 tmp = ['# HG changeset patch', '# User ' + user, '']
220 self.comments = tmp + self.comments
220 self.comments = tmp + self.comments
221 self.user = user
221 self.user = user
222
222
223 def setdate(self, date):
223 def setdate(self, date):
224 if not self.updateheader(['Date: ', '# Date '], date):
224 if not self.updateheader(['Date: ', '# Date '], date):
225 try:
225 try:
226 patchheaderat = self.comments.index('# HG changeset patch')
226 patchheaderat = self.comments.index('# HG changeset patch')
227 self.comments.insert(patchheaderat + 1, '# Date ' + date)
227 self.comments.insert(patchheaderat + 1, '# Date ' + date)
228 except ValueError:
228 except ValueError:
229 if self.plainmode or self._hasheader(['From: ']):
229 if self.plainmode or self._hasheader(['From: ']):
230 self.comments = ['Date: ' + date] + self.comments
230 self.comments = ['Date: ' + date] + self.comments
231 else:
231 else:
232 tmp = ['# HG changeset patch', '# Date ' + date, '']
232 tmp = ['# HG changeset patch', '# Date ' + date, '']
233 self.comments = tmp + self.comments
233 self.comments = tmp + self.comments
234 self.date = date
234 self.date = date
235
235
236 def setparent(self, parent):
236 def setparent(self, parent):
237 if not self.updateheader(['# Parent '], parent):
237 if not self.updateheader(['# Parent '], parent):
238 try:
238 try:
239 patchheaderat = self.comments.index('# HG changeset patch')
239 patchheaderat = self.comments.index('# HG changeset patch')
240 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
240 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
241 except ValueError:
241 except ValueError:
242 pass
242 pass
243 self.parent = parent
243 self.parent = parent
244
244
245 def setmessage(self, message):
245 def setmessage(self, message):
246 if self.comments:
246 if self.comments:
247 self._delmsg()
247 self._delmsg()
248 self.message = [message]
248 self.message = [message]
249 self.comments += self.message
249 self.comments += self.message
250
250
251 def updateheader(self, prefixes, new):
251 def updateheader(self, prefixes, new):
252 '''Update all references to a field in the patch header.
252 '''Update all references to a field in the patch header.
253 Return whether the field is present.'''
253 Return whether the field is present.'''
254 res = False
254 res = False
255 for prefix in prefixes:
255 for prefix in prefixes:
256 for i in xrange(len(self.comments)):
256 for i in xrange(len(self.comments)):
257 if self.comments[i].startswith(prefix):
257 if self.comments[i].startswith(prefix):
258 self.comments[i] = prefix + new
258 self.comments[i] = prefix + new
259 res = True
259 res = True
260 break
260 break
261 return res
261 return res
262
262
263 def _hasheader(self, prefixes):
263 def _hasheader(self, prefixes):
264 '''Check if a header starts with any of the given prefixes.'''
264 '''Check if a header starts with any of the given prefixes.'''
265 for prefix in prefixes:
265 for prefix in prefixes:
266 for comment in self.comments:
266 for comment in self.comments:
267 if comment.startswith(prefix):
267 if comment.startswith(prefix):
268 return True
268 return True
269 return False
269 return False
270
270
271 def __str__(self):
271 def __str__(self):
272 if not self.comments:
272 if not self.comments:
273 return ''
273 return ''
274 return '\n'.join(self.comments) + '\n\n'
274 return '\n'.join(self.comments) + '\n\n'
275
275
276 def _delmsg(self):
276 def _delmsg(self):
277 '''Remove existing message, keeping the rest of the comments fields.
277 '''Remove existing message, keeping the rest of the comments fields.
278 If comments contains 'subject: ', message will prepend
278 If comments contains 'subject: ', message will prepend
279 the field and a blank line.'''
279 the field and a blank line.'''
280 if self.message:
280 if self.message:
281 subj = 'subject: ' + self.message[0].lower()
281 subj = 'subject: ' + self.message[0].lower()
282 for i in xrange(len(self.comments)):
282 for i in xrange(len(self.comments)):
283 if subj == self.comments[i].lower():
283 if subj == self.comments[i].lower():
284 del self.comments[i]
284 del self.comments[i]
285 self.message = self.message[2:]
285 self.message = self.message[2:]
286 break
286 break
287 ci = 0
287 ci = 0
288 for mi in self.message:
288 for mi in self.message:
289 while mi != self.comments[ci]:
289 while mi != self.comments[ci]:
290 ci += 1
290 ci += 1
291 del self.comments[ci]
291 del self.comments[ci]
292
292
293 def newcommit(repo, phase, *args, **kwargs):
293 def newcommit(repo, phase, *args, **kwargs):
294 """helper dedicated to ensure a commit respect mq.secret setting
294 """helper dedicated to ensure a commit respect mq.secret setting
295
295
296 It should be used instead of repo.commit inside the mq source for operation
296 It should be used instead of repo.commit inside the mq source for operation
297 creating new changeset.
297 creating new changeset.
298 """
298 """
299 repo = repo.unfiltered()
299 repo = repo.unfiltered()
300 if phase is None:
300 if phase is None:
301 if repo.ui.configbool('mq', 'secret', False):
301 if repo.ui.configbool('mq', 'secret', False):
302 phase = phases.secret
302 phase = phases.secret
303 if phase is not None:
303 if phase is not None:
304 backup = repo.ui.backupconfig('phases', 'new-commit')
304 backup = repo.ui.backupconfig('phases', 'new-commit')
305 try:
305 try:
306 if phase is not None:
306 if phase is not None:
307 repo.ui.setconfig('phases', 'new-commit', phase)
307 repo.ui.setconfig('phases', 'new-commit', phase)
308 return repo.commit(*args, **kwargs)
308 return repo.commit(*args, **kwargs)
309 finally:
309 finally:
310 if phase is not None:
310 if phase is not None:
311 repo.ui.restoreconfig(backup)
311 repo.ui.restoreconfig(backup)
312
312
313 class AbortNoCleanup(error.Abort):
313 class AbortNoCleanup(error.Abort):
314 pass
314 pass
315
315
316 class queue(object):
316 class queue(object):
317 def __init__(self, ui, baseui, path, patchdir=None):
317 def __init__(self, ui, baseui, path, patchdir=None):
318 self.basepath = path
318 self.basepath = path
319 try:
319 try:
320 fh = open(os.path.join(path, 'patches.queue'))
320 fh = open(os.path.join(path, 'patches.queue'))
321 cur = fh.read().rstrip()
321 cur = fh.read().rstrip()
322 fh.close()
322 fh.close()
323 if not cur:
323 if not cur:
324 curpath = os.path.join(path, 'patches')
324 curpath = os.path.join(path, 'patches')
325 else:
325 else:
326 curpath = os.path.join(path, 'patches-' + cur)
326 curpath = os.path.join(path, 'patches-' + cur)
327 except IOError:
327 except IOError:
328 curpath = os.path.join(path, 'patches')
328 curpath = os.path.join(path, 'patches')
329 self.path = patchdir or curpath
329 self.path = patchdir or curpath
330 self.opener = scmutil.opener(self.path)
330 self.opener = scmutil.opener(self.path)
331 self.ui = ui
331 self.ui = ui
332 self.baseui = baseui
332 self.baseui = baseui
333 self.applieddirty = False
333 self.applieddirty = False
334 self.seriesdirty = False
334 self.seriesdirty = False
335 self.added = []
335 self.added = []
336 self.seriespath = "series"
336 self.seriespath = "series"
337 self.statuspath = "status"
337 self.statuspath = "status"
338 self.guardspath = "guards"
338 self.guardspath = "guards"
339 self.activeguards = None
339 self.activeguards = None
340 self.guardsdirty = False
340 self.guardsdirty = False
341 # Handle mq.git as a bool with extended values
341 # Handle mq.git as a bool with extended values
342 try:
342 try:
343 gitmode = ui.configbool('mq', 'git', None)
343 gitmode = ui.configbool('mq', 'git', None)
344 if gitmode is None:
344 if gitmode is None:
345 raise error.ConfigError
345 raise error.ConfigError
346 self.gitmode = gitmode and 'yes' or 'no'
346 self.gitmode = gitmode and 'yes' or 'no'
347 except error.ConfigError:
347 except error.ConfigError:
348 self.gitmode = ui.config('mq', 'git', 'auto').lower()
348 self.gitmode = ui.config('mq', 'git', 'auto').lower()
349 self.plainmode = ui.configbool('mq', 'plain', False)
349 self.plainmode = ui.configbool('mq', 'plain', False)
350 self.checkapplied = True
350 self.checkapplied = True
351
351
352 @util.propertycache
352 @util.propertycache
353 def applied(self):
353 def applied(self):
354 def parselines(lines):
354 def parselines(lines):
355 for l in lines:
355 for l in lines:
356 entry = l.split(':', 1)
356 entry = l.split(':', 1)
357 if len(entry) > 1:
357 if len(entry) > 1:
358 n, name = entry
358 n, name = entry
359 yield statusentry(bin(n), name)
359 yield statusentry(bin(n), name)
360 elif l.strip():
360 elif l.strip():
361 self.ui.warn(_('malformated mq status line: %s\n') % entry)
361 self.ui.warn(_('malformated mq status line: %s\n') % entry)
362 # else we ignore empty lines
362 # else we ignore empty lines
363 try:
363 try:
364 lines = self.opener.read(self.statuspath).splitlines()
364 lines = self.opener.read(self.statuspath).splitlines()
365 return list(parselines(lines))
365 return list(parselines(lines))
366 except IOError, e:
366 except IOError, e:
367 if e.errno == errno.ENOENT:
367 if e.errno == errno.ENOENT:
368 return []
368 return []
369 raise
369 raise
370
370
371 @util.propertycache
371 @util.propertycache
372 def fullseries(self):
372 def fullseries(self):
373 try:
373 try:
374 return self.opener.read(self.seriespath).splitlines()
374 return self.opener.read(self.seriespath).splitlines()
375 except IOError, e:
375 except IOError, e:
376 if e.errno == errno.ENOENT:
376 if e.errno == errno.ENOENT:
377 return []
377 return []
378 raise
378 raise
379
379
380 @util.propertycache
380 @util.propertycache
381 def series(self):
381 def series(self):
382 self.parseseries()
382 self.parseseries()
383 return self.series
383 return self.series
384
384
385 @util.propertycache
385 @util.propertycache
386 def seriesguards(self):
386 def seriesguards(self):
387 self.parseseries()
387 self.parseseries()
388 return self.seriesguards
388 return self.seriesguards
389
389
390 def invalidate(self):
390 def invalidate(self):
391 for a in 'applied fullseries series seriesguards'.split():
391 for a in 'applied fullseries series seriesguards'.split():
392 if a in self.__dict__:
392 if a in self.__dict__:
393 delattr(self, a)
393 delattr(self, a)
394 self.applieddirty = False
394 self.applieddirty = False
395 self.seriesdirty = False
395 self.seriesdirty = False
396 self.guardsdirty = False
396 self.guardsdirty = False
397 self.activeguards = None
397 self.activeguards = None
398
398
399 def diffopts(self, opts={}, patchfn=None):
399 def diffopts(self, opts={}, patchfn=None):
400 diffopts = patchmod.diffopts(self.ui, opts)
400 diffopts = patchmod.diffopts(self.ui, opts)
401 if self.gitmode == 'auto':
401 if self.gitmode == 'auto':
402 diffopts.upgrade = True
402 diffopts.upgrade = True
403 elif self.gitmode == 'keep':
403 elif self.gitmode == 'keep':
404 pass
404 pass
405 elif self.gitmode in ('yes', 'no'):
405 elif self.gitmode in ('yes', 'no'):
406 diffopts.git = self.gitmode == 'yes'
406 diffopts.git = self.gitmode == 'yes'
407 else:
407 else:
408 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
408 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
409 ' got %s') % self.gitmode)
409 ' got %s') % self.gitmode)
410 if patchfn:
410 if patchfn:
411 diffopts = self.patchopts(diffopts, patchfn)
411 diffopts = self.patchopts(diffopts, patchfn)
412 return diffopts
412 return diffopts
413
413
414 def patchopts(self, diffopts, *patches):
414 def patchopts(self, diffopts, *patches):
415 """Return a copy of input diff options with git set to true if
415 """Return a copy of input diff options with git set to true if
416 referenced patch is a git patch and should be preserved as such.
416 referenced patch is a git patch and should be preserved as such.
417 """
417 """
418 diffopts = diffopts.copy()
418 diffopts = diffopts.copy()
419 if not diffopts.git and self.gitmode == 'keep':
419 if not diffopts.git and self.gitmode == 'keep':
420 for patchfn in patches:
420 for patchfn in patches:
421 patchf = self.opener(patchfn, 'r')
421 patchf = self.opener(patchfn, 'r')
422 # if the patch was a git patch, refresh it as a git patch
422 # if the patch was a git patch, refresh it as a git patch
423 for line in patchf:
423 for line in patchf:
424 if line.startswith('diff --git'):
424 if line.startswith('diff --git'):
425 diffopts.git = True
425 diffopts.git = True
426 break
426 break
427 patchf.close()
427 patchf.close()
428 return diffopts
428 return diffopts
429
429
430 def join(self, *p):
430 def join(self, *p):
431 return os.path.join(self.path, *p)
431 return os.path.join(self.path, *p)
432
432
433 def findseries(self, patch):
433 def findseries(self, patch):
434 def matchpatch(l):
434 def matchpatch(l):
435 l = l.split('#', 1)[0]
435 l = l.split('#', 1)[0]
436 return l.strip() == patch
436 return l.strip() == patch
437 for index, l in enumerate(self.fullseries):
437 for index, l in enumerate(self.fullseries):
438 if matchpatch(l):
438 if matchpatch(l):
439 return index
439 return index
440 return None
440 return None
441
441
442 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
442 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
443
443
444 def parseseries(self):
444 def parseseries(self):
445 self.series = []
445 self.series = []
446 self.seriesguards = []
446 self.seriesguards = []
447 for l in self.fullseries:
447 for l in self.fullseries:
448 h = l.find('#')
448 h = l.find('#')
449 if h == -1:
449 if h == -1:
450 patch = l
450 patch = l
451 comment = ''
451 comment = ''
452 elif h == 0:
452 elif h == 0:
453 continue
453 continue
454 else:
454 else:
455 patch = l[:h]
455 patch = l[:h]
456 comment = l[h:]
456 comment = l[h:]
457 patch = patch.strip()
457 patch = patch.strip()
458 if patch:
458 if patch:
459 if patch in self.series:
459 if patch in self.series:
460 raise util.Abort(_('%s appears more than once in %s') %
460 raise util.Abort(_('%s appears more than once in %s') %
461 (patch, self.join(self.seriespath)))
461 (patch, self.join(self.seriespath)))
462 self.series.append(patch)
462 self.series.append(patch)
463 self.seriesguards.append(self.guard_re.findall(comment))
463 self.seriesguards.append(self.guard_re.findall(comment))
464
464
465 def checkguard(self, guard):
465 def checkguard(self, guard):
466 if not guard:
466 if not guard:
467 return _('guard cannot be an empty string')
467 return _('guard cannot be an empty string')
468 bad_chars = '# \t\r\n\f'
468 bad_chars = '# \t\r\n\f'
469 first = guard[0]
469 first = guard[0]
470 if first in '-+':
470 if first in '-+':
471 return (_('guard %r starts with invalid character: %r') %
471 return (_('guard %r starts with invalid character: %r') %
472 (guard, first))
472 (guard, first))
473 for c in bad_chars:
473 for c in bad_chars:
474 if c in guard:
474 if c in guard:
475 return _('invalid character in guard %r: %r') % (guard, c)
475 return _('invalid character in guard %r: %r') % (guard, c)
476
476
477 def setactive(self, guards):
477 def setactive(self, guards):
478 for guard in guards:
478 for guard in guards:
479 bad = self.checkguard(guard)
479 bad = self.checkguard(guard)
480 if bad:
480 if bad:
481 raise util.Abort(bad)
481 raise util.Abort(bad)
482 guards = sorted(set(guards))
482 guards = sorted(set(guards))
483 self.ui.debug('active guards: %s\n' % ' '.join(guards))
483 self.ui.debug('active guards: %s\n' % ' '.join(guards))
484 self.activeguards = guards
484 self.activeguards = guards
485 self.guardsdirty = True
485 self.guardsdirty = True
486
486
487 def active(self):
487 def active(self):
488 if self.activeguards is None:
488 if self.activeguards is None:
489 self.activeguards = []
489 self.activeguards = []
490 try:
490 try:
491 guards = self.opener.read(self.guardspath).split()
491 guards = self.opener.read(self.guardspath).split()
492 except IOError, err:
492 except IOError, err:
493 if err.errno != errno.ENOENT:
493 if err.errno != errno.ENOENT:
494 raise
494 raise
495 guards = []
495 guards = []
496 for i, guard in enumerate(guards):
496 for i, guard in enumerate(guards):
497 bad = self.checkguard(guard)
497 bad = self.checkguard(guard)
498 if bad:
498 if bad:
499 self.ui.warn('%s:%d: %s\n' %
499 self.ui.warn('%s:%d: %s\n' %
500 (self.join(self.guardspath), i + 1, bad))
500 (self.join(self.guardspath), i + 1, bad))
501 else:
501 else:
502 self.activeguards.append(guard)
502 self.activeguards.append(guard)
503 return self.activeguards
503 return self.activeguards
504
504
505 def setguards(self, idx, guards):
505 def setguards(self, idx, guards):
506 for g in guards:
506 for g in guards:
507 if len(g) < 2:
507 if len(g) < 2:
508 raise util.Abort(_('guard %r too short') % g)
508 raise util.Abort(_('guard %r too short') % g)
509 if g[0] not in '-+':
509 if g[0] not in '-+':
510 raise util.Abort(_('guard %r starts with invalid char') % g)
510 raise util.Abort(_('guard %r starts with invalid char') % g)
511 bad = self.checkguard(g[1:])
511 bad = self.checkguard(g[1:])
512 if bad:
512 if bad:
513 raise util.Abort(bad)
513 raise util.Abort(bad)
514 drop = self.guard_re.sub('', self.fullseries[idx])
514 drop = self.guard_re.sub('', self.fullseries[idx])
515 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
515 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
516 self.parseseries()
516 self.parseseries()
517 self.seriesdirty = True
517 self.seriesdirty = True
518
518
519 def pushable(self, idx):
519 def pushable(self, idx):
520 if isinstance(idx, str):
520 if isinstance(idx, str):
521 idx = self.series.index(idx)
521 idx = self.series.index(idx)
522 patchguards = self.seriesguards[idx]
522 patchguards = self.seriesguards[idx]
523 if not patchguards:
523 if not patchguards:
524 return True, None
524 return True, None
525 guards = self.active()
525 guards = self.active()
526 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
526 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
527 if exactneg:
527 if exactneg:
528 return False, repr(exactneg[0])
528 return False, repr(exactneg[0])
529 pos = [g for g in patchguards if g[0] == '+']
529 pos = [g for g in patchguards if g[0] == '+']
530 exactpos = [g for g in pos if g[1:] in guards]
530 exactpos = [g for g in pos if g[1:] in guards]
531 if pos:
531 if pos:
532 if exactpos:
532 if exactpos:
533 return True, repr(exactpos[0])
533 return True, repr(exactpos[0])
534 return False, ' '.join(map(repr, pos))
534 return False, ' '.join(map(repr, pos))
535 return True, ''
535 return True, ''
536
536
537 def explainpushable(self, idx, all_patches=False):
537 def explainpushable(self, idx, all_patches=False):
538 write = all_patches and self.ui.write or self.ui.warn
538 write = all_patches and self.ui.write or self.ui.warn
539 if all_patches or self.ui.verbose:
539 if all_patches or self.ui.verbose:
540 if isinstance(idx, str):
540 if isinstance(idx, str):
541 idx = self.series.index(idx)
541 idx = self.series.index(idx)
542 pushable, why = self.pushable(idx)
542 pushable, why = self.pushable(idx)
543 if all_patches and pushable:
543 if all_patches and pushable:
544 if why is None:
544 if why is None:
545 write(_('allowing %s - no guards in effect\n') %
545 write(_('allowing %s - no guards in effect\n') %
546 self.series[idx])
546 self.series[idx])
547 else:
547 else:
548 if not why:
548 if not why:
549 write(_('allowing %s - no matching negative guards\n') %
549 write(_('allowing %s - no matching negative guards\n') %
550 self.series[idx])
550 self.series[idx])
551 else:
551 else:
552 write(_('allowing %s - guarded by %s\n') %
552 write(_('allowing %s - guarded by %s\n') %
553 (self.series[idx], why))
553 (self.series[idx], why))
554 if not pushable:
554 if not pushable:
555 if why:
555 if why:
556 write(_('skipping %s - guarded by %s\n') %
556 write(_('skipping %s - guarded by %s\n') %
557 (self.series[idx], why))
557 (self.series[idx], why))
558 else:
558 else:
559 write(_('skipping %s - no matching guards\n') %
559 write(_('skipping %s - no matching guards\n') %
560 self.series[idx])
560 self.series[idx])
561
561
562 def savedirty(self):
562 def savedirty(self):
563 def writelist(items, path):
563 def writelist(items, path):
564 fp = self.opener(path, 'w')
564 fp = self.opener(path, 'w')
565 for i in items:
565 for i in items:
566 fp.write("%s\n" % i)
566 fp.write("%s\n" % i)
567 fp.close()
567 fp.close()
568 if self.applieddirty:
568 if self.applieddirty:
569 writelist(map(str, self.applied), self.statuspath)
569 writelist(map(str, self.applied), self.statuspath)
570 self.applieddirty = False
570 self.applieddirty = False
571 if self.seriesdirty:
571 if self.seriesdirty:
572 writelist(self.fullseries, self.seriespath)
572 writelist(self.fullseries, self.seriespath)
573 self.seriesdirty = False
573 self.seriesdirty = False
574 if self.guardsdirty:
574 if self.guardsdirty:
575 writelist(self.activeguards, self.guardspath)
575 writelist(self.activeguards, self.guardspath)
576 self.guardsdirty = False
576 self.guardsdirty = False
577 if self.added:
577 if self.added:
578 qrepo = self.qrepo()
578 qrepo = self.qrepo()
579 if qrepo:
579 if qrepo:
580 qrepo[None].add(f for f in self.added if f not in qrepo[None])
580 qrepo[None].add(f for f in self.added if f not in qrepo[None])
581 self.added = []
581 self.added = []
582
582
583 def removeundo(self, repo):
583 def removeundo(self, repo):
584 undo = repo.sjoin('undo')
584 undo = repo.sjoin('undo')
585 if not os.path.exists(undo):
585 if not os.path.exists(undo):
586 return
586 return
587 try:
587 try:
588 os.unlink(undo)
588 os.unlink(undo)
589 except OSError, inst:
589 except OSError, inst:
590 self.ui.warn(_('error removing undo: %s\n') % str(inst))
590 self.ui.warn(_('error removing undo: %s\n') % str(inst))
591
591
592 def backup(self, repo, files, copy=False):
592 def backup(self, repo, files, copy=False):
593 # backup local changes in --force case
593 # backup local changes in --force case
594 for f in sorted(files):
594 for f in sorted(files):
595 absf = repo.wjoin(f)
595 absf = repo.wjoin(f)
596 if os.path.lexists(absf):
596 if os.path.lexists(absf):
597 self.ui.note(_('saving current version of %s as %s\n') %
597 self.ui.note(_('saving current version of %s as %s\n') %
598 (f, f + '.orig'))
598 (f, f + '.orig'))
599 if copy:
599 if copy:
600 util.copyfile(absf, absf + '.orig')
600 util.copyfile(absf, absf + '.orig')
601 else:
601 else:
602 util.rename(absf, absf + '.orig')
602 util.rename(absf, absf + '.orig')
603
603
604 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
604 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
605 fp=None, changes=None, opts={}):
605 fp=None, changes=None, opts={}):
606 stat = opts.get('stat')
606 stat = opts.get('stat')
607 m = scmutil.match(repo[node1], files, opts)
607 m = scmutil.match(repo[node1], files, opts)
608 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
608 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
609 changes, stat, fp)
609 changes, stat, fp)
610
610
611 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
611 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
612 # first try just applying the patch
612 # first try just applying the patch
613 (err, n) = self.apply(repo, [patch], update_status=False,
613 (err, n) = self.apply(repo, [patch], update_status=False,
614 strict=True, merge=rev)
614 strict=True, merge=rev)
615
615
616 if err == 0:
616 if err == 0:
617 return (err, n)
617 return (err, n)
618
618
619 if n is None:
619 if n is None:
620 raise util.Abort(_("apply failed for patch %s") % patch)
620 raise util.Abort(_("apply failed for patch %s") % patch)
621
621
622 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
622 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
623
623
624 # apply failed, strip away that rev and merge.
624 # apply failed, strip away that rev and merge.
625 hg.clean(repo, head)
625 hg.clean(repo, head)
626 strip(self.ui, repo, [n], update=False, backup='strip')
626 strip(self.ui, repo, [n], update=False, backup='strip')
627
627
628 ctx = repo[rev]
628 ctx = repo[rev]
629 ret = hg.merge(repo, rev)
629 ret = hg.merge(repo, rev)
630 if ret:
630 if ret:
631 raise util.Abort(_("update returned %d") % ret)
631 raise util.Abort(_("update returned %d") % ret)
632 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
632 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
633 if n is None:
633 if n is None:
634 raise util.Abort(_("repo commit failed"))
634 raise util.Abort(_("repo commit failed"))
635 try:
635 try:
636 ph = patchheader(mergeq.join(patch), self.plainmode)
636 ph = patchheader(mergeq.join(patch), self.plainmode)
637 except Exception:
637 except Exception:
638 raise util.Abort(_("unable to read %s") % patch)
638 raise util.Abort(_("unable to read %s") % patch)
639
639
640 diffopts = self.patchopts(diffopts, patch)
640 diffopts = self.patchopts(diffopts, patch)
641 patchf = self.opener(patch, "w")
641 patchf = self.opener(patch, "w")
642 comments = str(ph)
642 comments = str(ph)
643 if comments:
643 if comments:
644 patchf.write(comments)
644 patchf.write(comments)
645 self.printdiff(repo, diffopts, head, n, fp=patchf)
645 self.printdiff(repo, diffopts, head, n, fp=patchf)
646 patchf.close()
646 patchf.close()
647 self.removeundo(repo)
647 self.removeundo(repo)
648 return (0, n)
648 return (0, n)
649
649
650 def qparents(self, repo, rev=None):
650 def qparents(self, repo, rev=None):
651 """return the mq handled parent or p1
651 """return the mq handled parent or p1
652
652
653 In some case where mq get himself in being the parent of a merge the
653 In some case where mq get himself in being the parent of a merge the
654 appropriate parent may be p2.
654 appropriate parent may be p2.
655 (eg: an in progress merge started with mq disabled)
655 (eg: an in progress merge started with mq disabled)
656
656
657 If no parent are managed by mq, p1 is returned.
657 If no parent are managed by mq, p1 is returned.
658 """
658 """
659 if rev is None:
659 if rev is None:
660 (p1, p2) = repo.dirstate.parents()
660 (p1, p2) = repo.dirstate.parents()
661 if p2 == nullid:
661 if p2 == nullid:
662 return p1
662 return p1
663 if not self.applied:
663 if not self.applied:
664 return None
664 return None
665 return self.applied[-1].node
665 return self.applied[-1].node
666 p1, p2 = repo.changelog.parents(rev)
666 p1, p2 = repo.changelog.parents(rev)
667 if p2 != nullid and p2 in [x.node for x in self.applied]:
667 if p2 != nullid and p2 in [x.node for x in self.applied]:
668 return p2
668 return p2
669 return p1
669 return p1
670
670
671 def mergepatch(self, repo, mergeq, series, diffopts):
671 def mergepatch(self, repo, mergeq, series, diffopts):
672 if not self.applied:
672 if not self.applied:
673 # each of the patches merged in will have two parents. This
673 # each of the patches merged in will have two parents. This
674 # can confuse the qrefresh, qdiff, and strip code because it
674 # can confuse the qrefresh, qdiff, and strip code because it
675 # needs to know which parent is actually in the patch queue.
675 # needs to know which parent is actually in the patch queue.
676 # so, we insert a merge marker with only one parent. This way
676 # so, we insert a merge marker with only one parent. This way
677 # the first patch in the queue is never a merge patch
677 # the first patch in the queue is never a merge patch
678 #
678 #
679 pname = ".hg.patches.merge.marker"
679 pname = ".hg.patches.merge.marker"
680 n = newcommit(repo, None, '[mq]: merge marker', force=True)
680 n = newcommit(repo, None, '[mq]: merge marker', force=True)
681 self.removeundo(repo)
681 self.removeundo(repo)
682 self.applied.append(statusentry(n, pname))
682 self.applied.append(statusentry(n, pname))
683 self.applieddirty = True
683 self.applieddirty = True
684
684
685 head = self.qparents(repo)
685 head = self.qparents(repo)
686
686
687 for patch in series:
687 for patch in series:
688 patch = mergeq.lookup(patch, strict=True)
688 patch = mergeq.lookup(patch, strict=True)
689 if not patch:
689 if not patch:
690 self.ui.warn(_("patch %s does not exist\n") % patch)
690 self.ui.warn(_("patch %s does not exist\n") % patch)
691 return (1, None)
691 return (1, None)
692 pushable, reason = self.pushable(patch)
692 pushable, reason = self.pushable(patch)
693 if not pushable:
693 if not pushable:
694 self.explainpushable(patch, all_patches=True)
694 self.explainpushable(patch, all_patches=True)
695 continue
695 continue
696 info = mergeq.isapplied(patch)
696 info = mergeq.isapplied(patch)
697 if not info:
697 if not info:
698 self.ui.warn(_("patch %s is not applied\n") % patch)
698 self.ui.warn(_("patch %s is not applied\n") % patch)
699 return (1, None)
699 return (1, None)
700 rev = info[1]
700 rev = info[1]
701 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
701 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
702 if head:
702 if head:
703 self.applied.append(statusentry(head, patch))
703 self.applied.append(statusentry(head, patch))
704 self.applieddirty = True
704 self.applieddirty = True
705 if err:
705 if err:
706 return (err, head)
706 return (err, head)
707 self.savedirty()
707 self.savedirty()
708 return (0, head)
708 return (0, head)
709
709
710 def patch(self, repo, patchfile):
710 def patch(self, repo, patchfile):
711 '''Apply patchfile to the working directory.
711 '''Apply patchfile to the working directory.
712 patchfile: name of patch file'''
712 patchfile: name of patch file'''
713 files = set()
713 files = set()
714 try:
714 try:
715 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
715 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
716 files=files, eolmode=None)
716 files=files, eolmode=None)
717 return (True, list(files), fuzz)
717 return (True, list(files), fuzz)
718 except Exception, inst:
718 except Exception, inst:
719 self.ui.note(str(inst) + '\n')
719 self.ui.note(str(inst) + '\n')
720 if not self.ui.verbose:
720 if not self.ui.verbose:
721 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
721 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
722 self.ui.traceback()
722 self.ui.traceback()
723 return (False, list(files), False)
723 return (False, list(files), False)
724
724
725 def apply(self, repo, series, list=False, update_status=True,
725 def apply(self, repo, series, list=False, update_status=True,
726 strict=False, patchdir=None, merge=None, all_files=None,
726 strict=False, patchdir=None, merge=None, all_files=None,
727 tobackup=None, keepchanges=False):
727 tobackup=None, keepchanges=False):
728 wlock = lock = tr = None
728 wlock = lock = tr = None
729 try:
729 try:
730 wlock = repo.wlock()
730 wlock = repo.wlock()
731 lock = repo.lock()
731 lock = repo.lock()
732 tr = repo.transaction("qpush")
732 tr = repo.transaction("qpush")
733 try:
733 try:
734 ret = self._apply(repo, series, list, update_status,
734 ret = self._apply(repo, series, list, update_status,
735 strict, patchdir, merge, all_files=all_files,
735 strict, patchdir, merge, all_files=all_files,
736 tobackup=tobackup, keepchanges=keepchanges)
736 tobackup=tobackup, keepchanges=keepchanges)
737 tr.close()
737 tr.close()
738 self.savedirty()
738 self.savedirty()
739 return ret
739 return ret
740 except AbortNoCleanup:
740 except AbortNoCleanup:
741 tr.close()
741 tr.close()
742 self.savedirty()
742 self.savedirty()
743 return 2, repo.dirstate.p1()
743 return 2, repo.dirstate.p1()
744 except: # re-raises
744 except: # re-raises
745 try:
745 try:
746 tr.abort()
746 tr.abort()
747 finally:
747 finally:
748 repo.invalidate()
748 repo.invalidate()
749 repo.dirstate.invalidate()
749 repo.dirstate.invalidate()
750 self.invalidate()
750 self.invalidate()
751 raise
751 raise
752 finally:
752 finally:
753 release(tr, lock, wlock)
753 release(tr, lock, wlock)
754 self.removeundo(repo)
754 self.removeundo(repo)
755
755
756 def _apply(self, repo, series, list=False, update_status=True,
756 def _apply(self, repo, series, list=False, update_status=True,
757 strict=False, patchdir=None, merge=None, all_files=None,
757 strict=False, patchdir=None, merge=None, all_files=None,
758 tobackup=None, keepchanges=False):
758 tobackup=None, keepchanges=False):
759 """returns (error, hash)
759 """returns (error, hash)
760
760
761 error = 1 for unable to read, 2 for patch failed, 3 for patch
761 error = 1 for unable to read, 2 for patch failed, 3 for patch
762 fuzz. tobackup is None or a set of files to backup before they
762 fuzz. tobackup is None or a set of files to backup before they
763 are modified by a patch.
763 are modified by a patch.
764 """
764 """
765 # TODO unify with commands.py
765 # TODO unify with commands.py
766 if not patchdir:
766 if not patchdir:
767 patchdir = self.path
767 patchdir = self.path
768 err = 0
768 err = 0
769 n = None
769 n = None
770 for patchname in series:
770 for patchname in series:
771 pushable, reason = self.pushable(patchname)
771 pushable, reason = self.pushable(patchname)
772 if not pushable:
772 if not pushable:
773 self.explainpushable(patchname, all_patches=True)
773 self.explainpushable(patchname, all_patches=True)
774 continue
774 continue
775 self.ui.status(_("applying %s\n") % patchname)
775 self.ui.status(_("applying %s\n") % patchname)
776 pf = os.path.join(patchdir, patchname)
776 pf = os.path.join(patchdir, patchname)
777
777
778 try:
778 try:
779 ph = patchheader(self.join(patchname), self.plainmode)
779 ph = patchheader(self.join(patchname), self.plainmode)
780 except IOError:
780 except IOError:
781 self.ui.warn(_("unable to read %s\n") % patchname)
781 self.ui.warn(_("unable to read %s\n") % patchname)
782 err = 1
782 err = 1
783 break
783 break
784
784
785 message = ph.message
785 message = ph.message
786 if not message:
786 if not message:
787 # The commit message should not be translated
787 # The commit message should not be translated
788 message = "imported patch %s\n" % patchname
788 message = "imported patch %s\n" % patchname
789 else:
789 else:
790 if list:
790 if list:
791 # The commit message should not be translated
791 # The commit message should not be translated
792 message.append("\nimported patch %s" % patchname)
792 message.append("\nimported patch %s" % patchname)
793 message = '\n'.join(message)
793 message = '\n'.join(message)
794
794
795 if ph.haspatch:
795 if ph.haspatch:
796 if tobackup:
796 if tobackup:
797 touched = patchmod.changedfiles(self.ui, repo, pf)
797 touched = patchmod.changedfiles(self.ui, repo, pf)
798 touched = set(touched) & tobackup
798 touched = set(touched) & tobackup
799 if touched and keepchanges:
799 if touched and keepchanges:
800 raise AbortNoCleanup(
800 raise AbortNoCleanup(
801 _("local changes found, refresh first"))
801 _("local changes found, refresh first"))
802 self.backup(repo, touched, copy=True)
802 self.backup(repo, touched, copy=True)
803 tobackup = tobackup - touched
803 tobackup = tobackup - touched
804 (patcherr, files, fuzz) = self.patch(repo, pf)
804 (patcherr, files, fuzz) = self.patch(repo, pf)
805 if all_files is not None:
805 if all_files is not None:
806 all_files.update(files)
806 all_files.update(files)
807 patcherr = not patcherr
807 patcherr = not patcherr
808 else:
808 else:
809 self.ui.warn(_("patch %s is empty\n") % patchname)
809 self.ui.warn(_("patch %s is empty\n") % patchname)
810 patcherr, files, fuzz = 0, [], 0
810 patcherr, files, fuzz = 0, [], 0
811
811
812 if merge and files:
812 if merge and files:
813 # Mark as removed/merged and update dirstate parent info
813 # Mark as removed/merged and update dirstate parent info
814 removed = []
814 removed = []
815 merged = []
815 merged = []
816 for f in files:
816 for f in files:
817 if os.path.lexists(repo.wjoin(f)):
817 if os.path.lexists(repo.wjoin(f)):
818 merged.append(f)
818 merged.append(f)
819 else:
819 else:
820 removed.append(f)
820 removed.append(f)
821 for f in removed:
821 for f in removed:
822 repo.dirstate.remove(f)
822 repo.dirstate.remove(f)
823 for f in merged:
823 for f in merged:
824 repo.dirstate.merge(f)
824 repo.dirstate.merge(f)
825 p1, p2 = repo.dirstate.parents()
825 p1, p2 = repo.dirstate.parents()
826 repo.setparents(p1, merge)
826 repo.setparents(p1, merge)
827
827
828 if all_files and '.hgsubstate' in all_files:
828 if all_files and '.hgsubstate' in all_files:
829 wctx = repo['.']
829 wctx = repo['.']
830 mctx = actx = repo[None]
830 mctx = actx = repo[None]
831 overwrite = False
831 overwrite = False
832 mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx,
832 mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx,
833 overwrite)
833 overwrite)
834 files += mergedsubstate.keys()
834 files += mergedsubstate.keys()
835
835
836 match = scmutil.matchfiles(repo, files or [])
836 match = scmutil.matchfiles(repo, files or [])
837 oldtip = repo['tip']
837 oldtip = repo['tip']
838 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
838 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
839 force=True)
839 force=True)
840 if repo['tip'] == oldtip:
840 if repo['tip'] == oldtip:
841 raise util.Abort(_("qpush exactly duplicates child changeset"))
841 raise util.Abort(_("qpush exactly duplicates child changeset"))
842 if n is None:
842 if n is None:
843 raise util.Abort(_("repository commit failed"))
843 raise util.Abort(_("repository commit failed"))
844
844
845 if update_status:
845 if update_status:
846 self.applied.append(statusentry(n, patchname))
846 self.applied.append(statusentry(n, patchname))
847
847
848 if patcherr:
848 if patcherr:
849 self.ui.warn(_("patch failed, rejects left in working dir\n"))
849 self.ui.warn(_("patch failed, rejects left in working dir\n"))
850 err = 2
850 err = 2
851 break
851 break
852
852
853 if fuzz and strict:
853 if fuzz and strict:
854 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
854 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
855 err = 3
855 err = 3
856 break
856 break
857 return (err, n)
857 return (err, n)
858
858
859 def _cleanup(self, patches, numrevs, keep=False):
859 def _cleanup(self, patches, numrevs, keep=False):
860 if not keep:
860 if not keep:
861 r = self.qrepo()
861 r = self.qrepo()
862 if r:
862 if r:
863 r[None].forget(patches)
863 r[None].forget(patches)
864 for p in patches:
864 for p in patches:
865 try:
865 try:
866 os.unlink(self.join(p))
866 os.unlink(self.join(p))
867 except OSError, inst:
867 except OSError, inst:
868 if inst.errno != errno.ENOENT:
868 if inst.errno != errno.ENOENT:
869 raise
869 raise
870
870
871 qfinished = []
871 qfinished = []
872 if numrevs:
872 if numrevs:
873 qfinished = self.applied[:numrevs]
873 qfinished = self.applied[:numrevs]
874 del self.applied[:numrevs]
874 del self.applied[:numrevs]
875 self.applieddirty = True
875 self.applieddirty = True
876
876
877 unknown = []
877 unknown = []
878
878
879 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
879 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
880 reverse=True):
880 reverse=True):
881 if i is not None:
881 if i is not None:
882 del self.fullseries[i]
882 del self.fullseries[i]
883 else:
883 else:
884 unknown.append(p)
884 unknown.append(p)
885
885
886 if unknown:
886 if unknown:
887 if numrevs:
887 if numrevs:
888 rev = dict((entry.name, entry.node) for entry in qfinished)
888 rev = dict((entry.name, entry.node) for entry in qfinished)
889 for p in unknown:
889 for p in unknown:
890 msg = _('revision %s refers to unknown patches: %s\n')
890 msg = _('revision %s refers to unknown patches: %s\n')
891 self.ui.warn(msg % (short(rev[p]), p))
891 self.ui.warn(msg % (short(rev[p]), p))
892 else:
892 else:
893 msg = _('unknown patches: %s\n')
893 msg = _('unknown patches: %s\n')
894 raise util.Abort(''.join(msg % p for p in unknown))
894 raise util.Abort(''.join(msg % p for p in unknown))
895
895
896 self.parseseries()
896 self.parseseries()
897 self.seriesdirty = True
897 self.seriesdirty = True
898 return [entry.node for entry in qfinished]
898 return [entry.node for entry in qfinished]
899
899
900 def _revpatches(self, repo, revs):
900 def _revpatches(self, repo, revs):
901 firstrev = repo[self.applied[0].node].rev()
901 firstrev = repo[self.applied[0].node].rev()
902 patches = []
902 patches = []
903 for i, rev in enumerate(revs):
903 for i, rev in enumerate(revs):
904
904
905 if rev < firstrev:
905 if rev < firstrev:
906 raise util.Abort(_('revision %d is not managed') % rev)
906 raise util.Abort(_('revision %d is not managed') % rev)
907
907
908 ctx = repo[rev]
908 ctx = repo[rev]
909 base = self.applied[i].node
909 base = self.applied[i].node
910 if ctx.node() != base:
910 if ctx.node() != base:
911 msg = _('cannot delete revision %d above applied patches')
911 msg = _('cannot delete revision %d above applied patches')
912 raise util.Abort(msg % rev)
912 raise util.Abort(msg % rev)
913
913
914 patch = self.applied[i].name
914 patch = self.applied[i].name
915 for fmt in ('[mq]: %s', 'imported patch %s'):
915 for fmt in ('[mq]: %s', 'imported patch %s'):
916 if ctx.description() == fmt % patch:
916 if ctx.description() == fmt % patch:
917 msg = _('patch %s finalized without changeset message\n')
917 msg = _('patch %s finalized without changeset message\n')
918 repo.ui.status(msg % patch)
918 repo.ui.status(msg % patch)
919 break
919 break
920
920
921 patches.append(patch)
921 patches.append(patch)
922 return patches
922 return patches
923
923
924 def finish(self, repo, revs):
924 def finish(self, repo, revs):
925 # Manually trigger phase computation to ensure phasedefaults is
925 # Manually trigger phase computation to ensure phasedefaults is
926 # executed before we remove the patches.
926 # executed before we remove the patches.
927 repo._phasecache
927 repo._phasecache
928 patches = self._revpatches(repo, sorted(revs))
928 patches = self._revpatches(repo, sorted(revs))
929 qfinished = self._cleanup(patches, len(patches))
929 qfinished = self._cleanup(patches, len(patches))
930 if qfinished and repo.ui.configbool('mq', 'secret', False):
930 if qfinished and repo.ui.configbool('mq', 'secret', False):
931 # only use this logic when the secret option is added
931 # only use this logic when the secret option is added
932 oldqbase = repo[qfinished[0]]
932 oldqbase = repo[qfinished[0]]
933 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
933 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
934 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
934 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
935 phases.advanceboundary(repo, tphase, qfinished)
935 phases.advanceboundary(repo, tphase, qfinished)
936
936
937 def delete(self, repo, patches, opts):
937 def delete(self, repo, patches, opts):
938 if not patches and not opts.get('rev'):
938 if not patches and not opts.get('rev'):
939 raise util.Abort(_('qdelete requires at least one revision or '
939 raise util.Abort(_('qdelete requires at least one revision or '
940 'patch name'))
940 'patch name'))
941
941
942 realpatches = []
942 realpatches = []
943 for patch in patches:
943 for patch in patches:
944 patch = self.lookup(patch, strict=True)
944 patch = self.lookup(patch, strict=True)
945 info = self.isapplied(patch)
945 info = self.isapplied(patch)
946 if info:
946 if info:
947 raise util.Abort(_("cannot delete applied patch %s") % patch)
947 raise util.Abort(_("cannot delete applied patch %s") % patch)
948 if patch not in self.series:
948 if patch not in self.series:
949 raise util.Abort(_("patch %s not in series file") % patch)
949 raise util.Abort(_("patch %s not in series file") % patch)
950 if patch not in realpatches:
950 if patch not in realpatches:
951 realpatches.append(patch)
951 realpatches.append(patch)
952
952
953 numrevs = 0
953 numrevs = 0
954 if opts.get('rev'):
954 if opts.get('rev'):
955 if not self.applied:
955 if not self.applied:
956 raise util.Abort(_('no patches applied'))
956 raise util.Abort(_('no patches applied'))
957 revs = scmutil.revrange(repo, opts.get('rev'))
957 revs = scmutil.revrange(repo, opts.get('rev'))
958 if len(revs) > 1 and revs[0] > revs[1]:
958 if len(revs) > 1 and revs[0] > revs[1]:
959 revs.reverse()
959 revs.reverse()
960 revpatches = self._revpatches(repo, revs)
960 revpatches = self._revpatches(repo, revs)
961 realpatches += revpatches
961 realpatches += revpatches
962 numrevs = len(revpatches)
962 numrevs = len(revpatches)
963
963
964 self._cleanup(realpatches, numrevs, opts.get('keep'))
964 self._cleanup(realpatches, numrevs, opts.get('keep'))
965
965
966 def checktoppatch(self, repo):
966 def checktoppatch(self, repo):
967 '''check that working directory is at qtip'''
967 '''check that working directory is at qtip'''
968 if self.applied:
968 if self.applied:
969 top = self.applied[-1].node
969 top = self.applied[-1].node
970 patch = self.applied[-1].name
970 patch = self.applied[-1].name
971 if repo.dirstate.p1() != top:
971 if repo.dirstate.p1() != top:
972 raise util.Abort(_("working directory revision is not qtip"))
972 raise util.Abort(_("working directory revision is not qtip"))
973 return top, patch
973 return top, patch
974 return None, None
974 return None, None
975
975
976 def putsubstate2changes(self, substatestate, changes):
976 def putsubstate2changes(self, substatestate, changes):
977 for files in changes[:3]:
977 for files in changes[:3]:
978 if '.hgsubstate' in files:
978 if '.hgsubstate' in files:
979 return # already listed up
979 return # already listed up
980 # not yet listed up
980 # not yet listed up
981 if substatestate in 'a?':
981 if substatestate in 'a?':
982 changes[1].append('.hgsubstate')
982 changes[1].append('.hgsubstate')
983 elif substatestate in 'r':
983 elif substatestate in 'r':
984 changes[2].append('.hgsubstate')
984 changes[2].append('.hgsubstate')
985 else: # modified
985 else: # modified
986 changes[0].append('.hgsubstate')
986 changes[0].append('.hgsubstate')
987
987
988 def checklocalchanges(self, repo, force=False, refresh=True):
988 def checklocalchanges(self, repo, force=False, refresh=True):
989 excsuffix = ''
989 excsuffix = ''
990 if refresh:
990 if refresh:
991 excsuffix = ', refresh first'
991 excsuffix = ', refresh first'
992 # plain versions for i18n tool to detect them
992 # plain versions for i18n tool to detect them
993 _("local changes found, refresh first")
993 _("local changes found, refresh first")
994 _("local changed subrepos found, refresh first")
994 _("local changed subrepos found, refresh first")
995 return checklocalchanges(repo, force, excsuffix)
995 return checklocalchanges(repo, force, excsuffix)
996
996
997 _reserved = ('series', 'status', 'guards', '.', '..')
997 _reserved = ('series', 'status', 'guards', '.', '..')
998 def checkreservedname(self, name):
998 def checkreservedname(self, name):
999 if name in self._reserved:
999 if name in self._reserved:
1000 raise util.Abort(_('"%s" cannot be used as the name of a patch')
1000 raise util.Abort(_('"%s" cannot be used as the name of a patch')
1001 % name)
1001 % name)
1002 for prefix in ('.hg', '.mq'):
1002 for prefix in ('.hg', '.mq'):
1003 if name.startswith(prefix):
1003 if name.startswith(prefix):
1004 raise util.Abort(_('patch name cannot begin with "%s"')
1004 raise util.Abort(_('patch name cannot begin with "%s"')
1005 % prefix)
1005 % prefix)
1006 for c in ('#', ':'):
1006 for c in ('#', ':'):
1007 if c in name:
1007 if c in name:
1008 raise util.Abort(_('"%s" cannot be used in the name of a patch')
1008 raise util.Abort(_('"%s" cannot be used in the name of a patch')
1009 % c)
1009 % c)
1010
1010
1011 def checkpatchname(self, name, force=False):
1011 def checkpatchname(self, name, force=False):
1012 self.checkreservedname(name)
1012 self.checkreservedname(name)
1013 if not force and os.path.exists(self.join(name)):
1013 if not force and os.path.exists(self.join(name)):
1014 if os.path.isdir(self.join(name)):
1014 if os.path.isdir(self.join(name)):
1015 raise util.Abort(_('"%s" already exists as a directory')
1015 raise util.Abort(_('"%s" already exists as a directory')
1016 % name)
1016 % name)
1017 else:
1017 else:
1018 raise util.Abort(_('patch "%s" already exists') % name)
1018 raise util.Abort(_('patch "%s" already exists') % name)
1019
1019
1020 def checkkeepchanges(self, keepchanges, force):
1020 def checkkeepchanges(self, keepchanges, force):
1021 if force and keepchanges:
1021 if force and keepchanges:
1022 raise util.Abort(_('cannot use both --force and --keep-changes'))
1022 raise util.Abort(_('cannot use both --force and --keep-changes'))
1023
1023
1024 def new(self, repo, patchfn, *pats, **opts):
1024 def new(self, repo, patchfn, *pats, **opts):
1025 """options:
1025 """options:
1026 msg: a string or a no-argument function returning a string
1026 msg: a string or a no-argument function returning a string
1027 """
1027 """
1028 msg = opts.get('msg')
1028 msg = opts.get('msg')
1029 user = opts.get('user')
1029 user = opts.get('user')
1030 date = opts.get('date')
1030 date = opts.get('date')
1031 if date:
1031 if date:
1032 date = util.parsedate(date)
1032 date = util.parsedate(date)
1033 diffopts = self.diffopts({'git': opts.get('git')})
1033 diffopts = self.diffopts({'git': opts.get('git')})
1034 if opts.get('checkname', True):
1034 if opts.get('checkname', True):
1035 self.checkpatchname(patchfn)
1035 self.checkpatchname(patchfn)
1036 inclsubs = checksubstate(repo)
1036 inclsubs = checksubstate(repo)
1037 if inclsubs:
1037 if inclsubs:
1038 inclsubs.append('.hgsubstate')
1038 inclsubs.append('.hgsubstate')
1039 substatestate = repo.dirstate['.hgsubstate']
1039 substatestate = repo.dirstate['.hgsubstate']
1040 if opts.get('include') or opts.get('exclude') or pats:
1040 if opts.get('include') or opts.get('exclude') or pats:
1041 if inclsubs:
1041 if inclsubs:
1042 pats = list(pats or []) + inclsubs
1042 pats = list(pats or []) + inclsubs
1043 match = scmutil.match(repo[None], pats, opts)
1043 match = scmutil.match(repo[None], pats, opts)
1044 # detect missing files in pats
1044 # detect missing files in pats
1045 def badfn(f, msg):
1045 def badfn(f, msg):
1046 if f != '.hgsubstate': # .hgsubstate is auto-created
1046 if f != '.hgsubstate': # .hgsubstate is auto-created
1047 raise util.Abort('%s: %s' % (f, msg))
1047 raise util.Abort('%s: %s' % (f, msg))
1048 match.bad = badfn
1048 match.bad = badfn
1049 changes = repo.status(match=match)
1049 changes = repo.status(match=match)
1050 m, a, r, d = changes[:4]
1050 m, a, r, d = changes[:4]
1051 else:
1051 else:
1052 changes = self.checklocalchanges(repo, force=True)
1052 changes = self.checklocalchanges(repo, force=True)
1053 m, a, r, d = changes
1053 m, a, r, d = changes
1054 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1054 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1055 if len(repo[None].parents()) > 1:
1055 if len(repo[None].parents()) > 1:
1056 raise util.Abort(_('cannot manage merge changesets'))
1056 raise util.Abort(_('cannot manage merge changesets'))
1057 commitfiles = m + a + r
1057 commitfiles = m + a + r
1058 self.checktoppatch(repo)
1058 self.checktoppatch(repo)
1059 insert = self.fullseriesend()
1059 insert = self.fullseriesend()
1060 wlock = repo.wlock()
1060 wlock = repo.wlock()
1061 try:
1061 try:
1062 try:
1062 try:
1063 # if patch file write fails, abort early
1063 # if patch file write fails, abort early
1064 p = self.opener(patchfn, "w")
1064 p = self.opener(patchfn, "w")
1065 except IOError, e:
1065 except IOError, e:
1066 raise util.Abort(_('cannot write patch "%s": %s')
1066 raise util.Abort(_('cannot write patch "%s": %s')
1067 % (patchfn, e.strerror))
1067 % (patchfn, e.strerror))
1068 try:
1068 try:
1069 if self.plainmode:
1069 if self.plainmode:
1070 if user:
1070 if user:
1071 p.write("From: " + user + "\n")
1071 p.write("From: " + user + "\n")
1072 if not date:
1072 if not date:
1073 p.write("\n")
1073 p.write("\n")
1074 if date:
1074 if date:
1075 p.write("Date: %d %d\n\n" % date)
1075 p.write("Date: %d %d\n\n" % date)
1076 else:
1076 else:
1077 p.write("# HG changeset patch\n")
1077 p.write("# HG changeset patch\n")
1078 p.write("# Parent "
1078 p.write("# Parent "
1079 + hex(repo[None].p1().node()) + "\n")
1079 + hex(repo[None].p1().node()) + "\n")
1080 if user:
1080 if user:
1081 p.write("# User " + user + "\n")
1081 p.write("# User " + user + "\n")
1082 if date:
1082 if date:
1083 p.write("# Date %s %s\n\n" % date)
1083 p.write("# Date %s %s\n\n" % date)
1084 if util.safehasattr(msg, '__call__'):
1084 if util.safehasattr(msg, '__call__'):
1085 msg = msg()
1085 msg = msg()
1086 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1086 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1087 n = newcommit(repo, None, commitmsg, user, date, match=match,
1087 n = newcommit(repo, None, commitmsg, user, date, match=match,
1088 force=True)
1088 force=True)
1089 if n is None:
1089 if n is None:
1090 raise util.Abort(_("repo commit failed"))
1090 raise util.Abort(_("repo commit failed"))
1091 try:
1091 try:
1092 self.fullseries[insert:insert] = [patchfn]
1092 self.fullseries[insert:insert] = [patchfn]
1093 self.applied.append(statusentry(n, patchfn))
1093 self.applied.append(statusentry(n, patchfn))
1094 self.parseseries()
1094 self.parseseries()
1095 self.seriesdirty = True
1095 self.seriesdirty = True
1096 self.applieddirty = True
1096 self.applieddirty = True
1097 if msg:
1097 if msg:
1098 msg = msg + "\n\n"
1098 msg = msg + "\n\n"
1099 p.write(msg)
1099 p.write(msg)
1100 if commitfiles:
1100 if commitfiles:
1101 parent = self.qparents(repo, n)
1101 parent = self.qparents(repo, n)
1102 if inclsubs:
1102 if inclsubs:
1103 self.putsubstate2changes(substatestate, changes)
1103 self.putsubstate2changes(substatestate, changes)
1104 chunks = patchmod.diff(repo, node1=parent, node2=n,
1104 chunks = patchmod.diff(repo, node1=parent, node2=n,
1105 changes=changes, opts=diffopts)
1105 changes=changes, opts=diffopts)
1106 for chunk in chunks:
1106 for chunk in chunks:
1107 p.write(chunk)
1107 p.write(chunk)
1108 p.close()
1108 p.close()
1109 r = self.qrepo()
1109 r = self.qrepo()
1110 if r:
1110 if r:
1111 r[None].add([patchfn])
1111 r[None].add([patchfn])
1112 except: # re-raises
1112 except: # re-raises
1113 repo.rollback()
1113 repo.rollback()
1114 raise
1114 raise
1115 except Exception:
1115 except Exception:
1116 patchpath = self.join(patchfn)
1116 patchpath = self.join(patchfn)
1117 try:
1117 try:
1118 os.unlink(patchpath)
1118 os.unlink(patchpath)
1119 except OSError:
1119 except OSError:
1120 self.ui.warn(_('error unlinking %s\n') % patchpath)
1120 self.ui.warn(_('error unlinking %s\n') % patchpath)
1121 raise
1121 raise
1122 self.removeundo(repo)
1122 self.removeundo(repo)
1123 finally:
1123 finally:
1124 release(wlock)
1124 release(wlock)
1125
1125
1126 def isapplied(self, patch):
1126 def isapplied(self, patch):
1127 """returns (index, rev, patch)"""
1127 """returns (index, rev, patch)"""
1128 for i, a in enumerate(self.applied):
1128 for i, a in enumerate(self.applied):
1129 if a.name == patch:
1129 if a.name == patch:
1130 return (i, a.node, a.name)
1130 return (i, a.node, a.name)
1131 return None
1131 return None
1132
1132
1133 # if the exact patch name does not exist, we try a few
1133 # if the exact patch name does not exist, we try a few
1134 # variations. If strict is passed, we try only #1
1134 # variations. If strict is passed, we try only #1
1135 #
1135 #
1136 # 1) a number (as string) to indicate an offset in the series file
1136 # 1) a number (as string) to indicate an offset in the series file
1137 # 2) a unique substring of the patch name was given
1137 # 2) a unique substring of the patch name was given
1138 # 3) patchname[-+]num to indicate an offset in the series file
1138 # 3) patchname[-+]num to indicate an offset in the series file
1139 def lookup(self, patch, strict=False):
1139 def lookup(self, patch, strict=False):
1140 def partialname(s):
1140 def partialname(s):
1141 if s in self.series:
1141 if s in self.series:
1142 return s
1142 return s
1143 matches = [x for x in self.series if s in x]
1143 matches = [x for x in self.series if s in x]
1144 if len(matches) > 1:
1144 if len(matches) > 1:
1145 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1145 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1146 for m in matches:
1146 for m in matches:
1147 self.ui.warn(' %s\n' % m)
1147 self.ui.warn(' %s\n' % m)
1148 return None
1148 return None
1149 if matches:
1149 if matches:
1150 return matches[0]
1150 return matches[0]
1151 if self.series and self.applied:
1151 if self.series and self.applied:
1152 if s == 'qtip':
1152 if s == 'qtip':
1153 return self.series[self.seriesend(True) - 1]
1153 return self.series[self.seriesend(True) - 1]
1154 if s == 'qbase':
1154 if s == 'qbase':
1155 return self.series[0]
1155 return self.series[0]
1156 return None
1156 return None
1157
1157
1158 if patch in self.series:
1158 if patch in self.series:
1159 return patch
1159 return patch
1160
1160
1161 if not os.path.isfile(self.join(patch)):
1161 if not os.path.isfile(self.join(patch)):
1162 try:
1162 try:
1163 sno = int(patch)
1163 sno = int(patch)
1164 except (ValueError, OverflowError):
1164 except (ValueError, OverflowError):
1165 pass
1165 pass
1166 else:
1166 else:
1167 if -len(self.series) <= sno < len(self.series):
1167 if -len(self.series) <= sno < len(self.series):
1168 return self.series[sno]
1168 return self.series[sno]
1169
1169
1170 if not strict:
1170 if not strict:
1171 res = partialname(patch)
1171 res = partialname(patch)
1172 if res:
1172 if res:
1173 return res
1173 return res
1174 minus = patch.rfind('-')
1174 minus = patch.rfind('-')
1175 if minus >= 0:
1175 if minus >= 0:
1176 res = partialname(patch[:minus])
1176 res = partialname(patch[:minus])
1177 if res:
1177 if res:
1178 i = self.series.index(res)
1178 i = self.series.index(res)
1179 try:
1179 try:
1180 off = int(patch[minus + 1:] or 1)
1180 off = int(patch[minus + 1:] or 1)
1181 except (ValueError, OverflowError):
1181 except (ValueError, OverflowError):
1182 pass
1182 pass
1183 else:
1183 else:
1184 if i - off >= 0:
1184 if i - off >= 0:
1185 return self.series[i - off]
1185 return self.series[i - off]
1186 plus = patch.rfind('+')
1186 plus = patch.rfind('+')
1187 if plus >= 0:
1187 if plus >= 0:
1188 res = partialname(patch[:plus])
1188 res = partialname(patch[:plus])
1189 if res:
1189 if res:
1190 i = self.series.index(res)
1190 i = self.series.index(res)
1191 try:
1191 try:
1192 off = int(patch[plus + 1:] or 1)
1192 off = int(patch[plus + 1:] or 1)
1193 except (ValueError, OverflowError):
1193 except (ValueError, OverflowError):
1194 pass
1194 pass
1195 else:
1195 else:
1196 if i + off < len(self.series):
1196 if i + off < len(self.series):
1197 return self.series[i + off]
1197 return self.series[i + off]
1198 raise util.Abort(_("patch %s not in series") % patch)
1198 raise util.Abort(_("patch %s not in series") % patch)
1199
1199
1200 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1200 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1201 all=False, move=False, exact=False, nobackup=False,
1201 all=False, move=False, exact=False, nobackup=False,
1202 keepchanges=False):
1202 keepchanges=False):
1203 self.checkkeepchanges(keepchanges, force)
1203 self.checkkeepchanges(keepchanges, force)
1204 diffopts = self.diffopts()
1204 diffopts = self.diffopts()
1205 wlock = repo.wlock()
1205 wlock = repo.wlock()
1206 try:
1206 try:
1207 heads = [h for hs in repo.branchmap().itervalues() for h in hs]
1207 heads = [h for hs in repo.branchmap().itervalues() for h in hs]
1208 if not heads:
1208 if not heads:
1209 heads = [nullid]
1209 heads = [nullid]
1210 if repo.dirstate.p1() not in heads and not exact:
1210 if repo.dirstate.p1() not in heads and not exact:
1211 self.ui.status(_("(working directory not at a head)\n"))
1211 self.ui.status(_("(working directory not at a head)\n"))
1212
1212
1213 if not self.series:
1213 if not self.series:
1214 self.ui.warn(_('no patches in series\n'))
1214 self.ui.warn(_('no patches in series\n'))
1215 return 0
1215 return 0
1216
1216
1217 # Suppose our series file is: A B C and the current 'top'
1217 # Suppose our series file is: A B C and the current 'top'
1218 # patch is B. qpush C should be performed (moving forward)
1218 # patch is B. qpush C should be performed (moving forward)
1219 # qpush B is a NOP (no change) qpush A is an error (can't
1219 # qpush B is a NOP (no change) qpush A is an error (can't
1220 # go backwards with qpush)
1220 # go backwards with qpush)
1221 if patch:
1221 if patch:
1222 patch = self.lookup(patch)
1222 patch = self.lookup(patch)
1223 info = self.isapplied(patch)
1223 info = self.isapplied(patch)
1224 if info and info[0] >= len(self.applied) - 1:
1224 if info and info[0] >= len(self.applied) - 1:
1225 self.ui.warn(
1225 self.ui.warn(
1226 _('qpush: %s is already at the top\n') % patch)
1226 _('qpush: %s is already at the top\n') % patch)
1227 return 0
1227 return 0
1228
1228
1229 pushable, reason = self.pushable(patch)
1229 pushable, reason = self.pushable(patch)
1230 if pushable:
1230 if pushable:
1231 if self.series.index(patch) < self.seriesend():
1231 if self.series.index(patch) < self.seriesend():
1232 raise util.Abort(
1232 raise util.Abort(
1233 _("cannot push to a previous patch: %s") % patch)
1233 _("cannot push to a previous patch: %s") % patch)
1234 else:
1234 else:
1235 if reason:
1235 if reason:
1236 reason = _('guarded by %s') % reason
1236 reason = _('guarded by %s') % reason
1237 else:
1237 else:
1238 reason = _('no matching guards')
1238 reason = _('no matching guards')
1239 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1239 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1240 return 1
1240 return 1
1241 elif all:
1241 elif all:
1242 patch = self.series[-1]
1242 patch = self.series[-1]
1243 if self.isapplied(patch):
1243 if self.isapplied(patch):
1244 self.ui.warn(_('all patches are currently applied\n'))
1244 self.ui.warn(_('all patches are currently applied\n'))
1245 return 0
1245 return 0
1246
1246
1247 # Following the above example, starting at 'top' of B:
1247 # Following the above example, starting at 'top' of B:
1248 # qpush should be performed (pushes C), but a subsequent
1248 # qpush should be performed (pushes C), but a subsequent
1249 # qpush without an argument is an error (nothing to
1249 # qpush without an argument is an error (nothing to
1250 # apply). This allows a loop of "...while hg qpush..." to
1250 # apply). This allows a loop of "...while hg qpush..." to
1251 # work as it detects an error when done
1251 # work as it detects an error when done
1252 start = self.seriesend()
1252 start = self.seriesend()
1253 if start == len(self.series):
1253 if start == len(self.series):
1254 self.ui.warn(_('patch series already fully applied\n'))
1254 self.ui.warn(_('patch series already fully applied\n'))
1255 return 1
1255 return 1
1256 if not force and not keepchanges:
1256 if not force and not keepchanges:
1257 self.checklocalchanges(repo, refresh=self.applied)
1257 self.checklocalchanges(repo, refresh=self.applied)
1258
1258
1259 if exact:
1259 if exact:
1260 if keepchanges:
1260 if keepchanges:
1261 raise util.Abort(
1261 raise util.Abort(
1262 _("cannot use --exact and --keep-changes together"))
1262 _("cannot use --exact and --keep-changes together"))
1263 if move:
1263 if move:
1264 raise util.Abort(_('cannot use --exact and --move '
1264 raise util.Abort(_('cannot use --exact and --move '
1265 'together'))
1265 'together'))
1266 if self.applied:
1266 if self.applied:
1267 raise util.Abort(_('cannot push --exact with applied '
1267 raise util.Abort(_('cannot push --exact with applied '
1268 'patches'))
1268 'patches'))
1269 root = self.series[start]
1269 root = self.series[start]
1270 target = patchheader(self.join(root), self.plainmode).parent
1270 target = patchheader(self.join(root), self.plainmode).parent
1271 if not target:
1271 if not target:
1272 raise util.Abort(
1272 raise util.Abort(
1273 _("%s does not have a parent recorded") % root)
1273 _("%s does not have a parent recorded") % root)
1274 if not repo[target] == repo['.']:
1274 if not repo[target] == repo['.']:
1275 hg.update(repo, target)
1275 hg.update(repo, target)
1276
1276
1277 if move:
1277 if move:
1278 if not patch:
1278 if not patch:
1279 raise util.Abort(_("please specify the patch to move"))
1279 raise util.Abort(_("please specify the patch to move"))
1280 for fullstart, rpn in enumerate(self.fullseries):
1280 for fullstart, rpn in enumerate(self.fullseries):
1281 # strip markers for patch guards
1281 # strip markers for patch guards
1282 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1282 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1283 break
1283 break
1284 for i, rpn in enumerate(self.fullseries[fullstart:]):
1284 for i, rpn in enumerate(self.fullseries[fullstart:]):
1285 # strip markers for patch guards
1285 # strip markers for patch guards
1286 if self.guard_re.split(rpn, 1)[0] == patch:
1286 if self.guard_re.split(rpn, 1)[0] == patch:
1287 break
1287 break
1288 index = fullstart + i
1288 index = fullstart + i
1289 assert index < len(self.fullseries)
1289 assert index < len(self.fullseries)
1290 fullpatch = self.fullseries[index]
1290 fullpatch = self.fullseries[index]
1291 del self.fullseries[index]
1291 del self.fullseries[index]
1292 self.fullseries.insert(fullstart, fullpatch)
1292 self.fullseries.insert(fullstart, fullpatch)
1293 self.parseseries()
1293 self.parseseries()
1294 self.seriesdirty = True
1294 self.seriesdirty = True
1295
1295
1296 self.applieddirty = True
1296 self.applieddirty = True
1297 if start > 0:
1297 if start > 0:
1298 self.checktoppatch(repo)
1298 self.checktoppatch(repo)
1299 if not patch:
1299 if not patch:
1300 patch = self.series[start]
1300 patch = self.series[start]
1301 end = start + 1
1301 end = start + 1
1302 else:
1302 else:
1303 end = self.series.index(patch, start) + 1
1303 end = self.series.index(patch, start) + 1
1304
1304
1305 tobackup = set()
1305 tobackup = set()
1306 if (not nobackup and force) or keepchanges:
1306 if (not nobackup and force) or keepchanges:
1307 m, a, r, d = self.checklocalchanges(repo, force=True)
1307 m, a, r, d = self.checklocalchanges(repo, force=True)
1308 if keepchanges:
1308 if keepchanges:
1309 tobackup.update(m + a + r + d)
1309 tobackup.update(m + a + r + d)
1310 else:
1310 else:
1311 tobackup.update(m + a)
1311 tobackup.update(m + a)
1312
1312
1313 s = self.series[start:end]
1313 s = self.series[start:end]
1314 all_files = set()
1314 all_files = set()
1315 try:
1315 try:
1316 if mergeq:
1316 if mergeq:
1317 ret = self.mergepatch(repo, mergeq, s, diffopts)
1317 ret = self.mergepatch(repo, mergeq, s, diffopts)
1318 else:
1318 else:
1319 ret = self.apply(repo, s, list, all_files=all_files,
1319 ret = self.apply(repo, s, list, all_files=all_files,
1320 tobackup=tobackup, keepchanges=keepchanges)
1320 tobackup=tobackup, keepchanges=keepchanges)
1321 except: # re-raises
1321 except: # re-raises
1322 self.ui.warn(_('cleaning up working directory...'))
1322 self.ui.warn(_('cleaning up working directory...'))
1323 node = repo.dirstate.p1()
1323 node = repo.dirstate.p1()
1324 hg.revert(repo, node, None)
1324 hg.revert(repo, node, None)
1325 # only remove unknown files that we know we touched or
1325 # only remove unknown files that we know we touched or
1326 # created while patching
1326 # created while patching
1327 for f in all_files:
1327 for f in all_files:
1328 if f not in repo.dirstate:
1328 if f not in repo.dirstate:
1329 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1329 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1330 self.ui.warn(_('done\n'))
1330 self.ui.warn(_('done\n'))
1331 raise
1331 raise
1332
1332
1333 if not self.applied:
1333 if not self.applied:
1334 return ret[0]
1334 return ret[0]
1335 top = self.applied[-1].name
1335 top = self.applied[-1].name
1336 if ret[0] and ret[0] > 1:
1336 if ret[0] and ret[0] > 1:
1337 msg = _("errors during apply, please fix and refresh %s\n")
1337 msg = _("errors during apply, please fix and refresh %s\n")
1338 self.ui.write(msg % top)
1338 self.ui.write(msg % top)
1339 else:
1339 else:
1340 self.ui.write(_("now at: %s\n") % top)
1340 self.ui.write(_("now at: %s\n") % top)
1341 return ret[0]
1341 return ret[0]
1342
1342
1343 finally:
1343 finally:
1344 wlock.release()
1344 wlock.release()
1345
1345
1346 def pop(self, repo, patch=None, force=False, update=True, all=False,
1346 def pop(self, repo, patch=None, force=False, update=True, all=False,
1347 nobackup=False, keepchanges=False):
1347 nobackup=False, keepchanges=False):
1348 self.checkkeepchanges(keepchanges, force)
1348 self.checkkeepchanges(keepchanges, force)
1349 wlock = repo.wlock()
1349 wlock = repo.wlock()
1350 try:
1350 try:
1351 if patch:
1351 if patch:
1352 # index, rev, patch
1352 # index, rev, patch
1353 info = self.isapplied(patch)
1353 info = self.isapplied(patch)
1354 if not info:
1354 if not info:
1355 patch = self.lookup(patch)
1355 patch = self.lookup(patch)
1356 info = self.isapplied(patch)
1356 info = self.isapplied(patch)
1357 if not info:
1357 if not info:
1358 raise util.Abort(_("patch %s is not applied") % patch)
1358 raise util.Abort(_("patch %s is not applied") % patch)
1359
1359
1360 if not self.applied:
1360 if not self.applied:
1361 # Allow qpop -a to work repeatedly,
1361 # Allow qpop -a to work repeatedly,
1362 # but not qpop without an argument
1362 # but not qpop without an argument
1363 self.ui.warn(_("no patches applied\n"))
1363 self.ui.warn(_("no patches applied\n"))
1364 return not all
1364 return not all
1365
1365
1366 if all:
1366 if all:
1367 start = 0
1367 start = 0
1368 elif patch:
1368 elif patch:
1369 start = info[0] + 1
1369 start = info[0] + 1
1370 else:
1370 else:
1371 start = len(self.applied) - 1
1371 start = len(self.applied) - 1
1372
1372
1373 if start >= len(self.applied):
1373 if start >= len(self.applied):
1374 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1374 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1375 return
1375 return
1376
1376
1377 if not update:
1377 if not update:
1378 parents = repo.dirstate.parents()
1378 parents = repo.dirstate.parents()
1379 rr = [x.node for x in self.applied]
1379 rr = [x.node for x in self.applied]
1380 for p in parents:
1380 for p in parents:
1381 if p in rr:
1381 if p in rr:
1382 self.ui.warn(_("qpop: forcing dirstate update\n"))
1382 self.ui.warn(_("qpop: forcing dirstate update\n"))
1383 update = True
1383 update = True
1384 else:
1384 else:
1385 parents = [p.node() for p in repo[None].parents()]
1385 parents = [p.node() for p in repo[None].parents()]
1386 needupdate = False
1386 needupdate = False
1387 for entry in self.applied[start:]:
1387 for entry in self.applied[start:]:
1388 if entry.node in parents:
1388 if entry.node in parents:
1389 needupdate = True
1389 needupdate = True
1390 break
1390 break
1391 update = needupdate
1391 update = needupdate
1392
1392
1393 tobackup = set()
1393 tobackup = set()
1394 if update:
1394 if update:
1395 m, a, r, d = self.checklocalchanges(
1395 m, a, r, d = self.checklocalchanges(
1396 repo, force=force or keepchanges)
1396 repo, force=force or keepchanges)
1397 if force:
1397 if force:
1398 if not nobackup:
1398 if not nobackup:
1399 tobackup.update(m + a)
1399 tobackup.update(m + a)
1400 elif keepchanges:
1400 elif keepchanges:
1401 tobackup.update(m + a + r + d)
1401 tobackup.update(m + a + r + d)
1402
1402
1403 self.applieddirty = True
1403 self.applieddirty = True
1404 end = len(self.applied)
1404 end = len(self.applied)
1405 rev = self.applied[start].node
1405 rev = self.applied[start].node
1406
1406
1407 try:
1407 try:
1408 heads = repo.changelog.heads(rev)
1408 heads = repo.changelog.heads(rev)
1409 except error.LookupError:
1409 except error.LookupError:
1410 node = short(rev)
1410 node = short(rev)
1411 raise util.Abort(_('trying to pop unknown node %s') % node)
1411 raise util.Abort(_('trying to pop unknown node %s') % node)
1412
1412
1413 if heads != [self.applied[-1].node]:
1413 if heads != [self.applied[-1].node]:
1414 raise util.Abort(_("popping would remove a revision not "
1414 raise util.Abort(_("popping would remove a revision not "
1415 "managed by this patch queue"))
1415 "managed by this patch queue"))
1416 if not repo[self.applied[-1].node].mutable():
1416 if not repo[self.applied[-1].node].mutable():
1417 raise util.Abort(
1417 raise util.Abort(
1418 _("popping would remove an immutable revision"),
1418 _("popping would remove an immutable revision"),
1419 hint=_('see "hg help phases" for details'))
1419 hint=_('see "hg help phases" for details'))
1420
1420
1421 # we know there are no local changes, so we can make a simplified
1421 # we know there are no local changes, so we can make a simplified
1422 # form of hg.update.
1422 # form of hg.update.
1423 if update:
1423 if update:
1424 qp = self.qparents(repo, rev)
1424 qp = self.qparents(repo, rev)
1425 ctx = repo[qp]
1425 ctx = repo[qp]
1426 m, a, r, d = repo.status(qp, '.')[:4]
1426 m, a, r, d = repo.status(qp, '.')[:4]
1427 if d:
1427 if d:
1428 raise util.Abort(_("deletions found between repo revs"))
1428 raise util.Abort(_("deletions found between repo revs"))
1429
1429
1430 tobackup = set(a + m + r) & tobackup
1430 tobackup = set(a + m + r) & tobackup
1431 if keepchanges and tobackup:
1431 if keepchanges and tobackup:
1432 raise util.Abort(_("local changes found, refresh first"))
1432 raise util.Abort(_("local changes found, refresh first"))
1433 self.backup(repo, tobackup)
1433 self.backup(repo, tobackup)
1434
1434
1435 for f in a:
1435 for f in a:
1436 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1436 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1437 repo.dirstate.drop(f)
1437 repo.dirstate.drop(f)
1438 for f in m + r:
1438 for f in m + r:
1439 fctx = ctx[f]
1439 fctx = ctx[f]
1440 repo.wwrite(f, fctx.data(), fctx.flags())
1440 repo.wwrite(f, fctx.data(), fctx.flags())
1441 repo.dirstate.normal(f)
1441 repo.dirstate.normal(f)
1442 repo.setparents(qp, nullid)
1442 repo.setparents(qp, nullid)
1443 for patch in reversed(self.applied[start:end]):
1443 for patch in reversed(self.applied[start:end]):
1444 self.ui.status(_("popping %s\n") % patch.name)
1444 self.ui.status(_("popping %s\n") % patch.name)
1445 del self.applied[start:end]
1445 del self.applied[start:end]
1446 strip(self.ui, repo, [rev], update=False, backup='strip')
1446 strip(self.ui, repo, [rev], update=False, backup='strip')
1447 for s, state in repo['.'].substate.items():
1447 for s, state in repo['.'].substate.items():
1448 repo['.'].sub(s).get(state)
1448 repo['.'].sub(s).get(state)
1449 if self.applied:
1449 if self.applied:
1450 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1450 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1451 else:
1451 else:
1452 self.ui.write(_("patch queue now empty\n"))
1452 self.ui.write(_("patch queue now empty\n"))
1453 finally:
1453 finally:
1454 wlock.release()
1454 wlock.release()
1455
1455
1456 def diff(self, repo, pats, opts):
1456 def diff(self, repo, pats, opts):
1457 top, patch = self.checktoppatch(repo)
1457 top, patch = self.checktoppatch(repo)
1458 if not top:
1458 if not top:
1459 self.ui.write(_("no patches applied\n"))
1459 self.ui.write(_("no patches applied\n"))
1460 return
1460 return
1461 qp = self.qparents(repo, top)
1461 qp = self.qparents(repo, top)
1462 if opts.get('reverse'):
1462 if opts.get('reverse'):
1463 node1, node2 = None, qp
1463 node1, node2 = None, qp
1464 else:
1464 else:
1465 node1, node2 = qp, None
1465 node1, node2 = qp, None
1466 diffopts = self.diffopts(opts, patch)
1466 diffopts = self.diffopts(opts, patch)
1467 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1467 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1468
1468
1469 def refresh(self, repo, pats=None, **opts):
1469 def refresh(self, repo, pats=None, **opts):
1470 if not self.applied:
1470 if not self.applied:
1471 self.ui.write(_("no patches applied\n"))
1471 self.ui.write(_("no patches applied\n"))
1472 return 1
1472 return 1
1473 msg = opts.get('msg', '').rstrip()
1473 msg = opts.get('msg', '').rstrip()
1474 newuser = opts.get('user')
1474 newuser = opts.get('user')
1475 newdate = opts.get('date')
1475 newdate = opts.get('date')
1476 if newdate:
1476 if newdate:
1477 newdate = '%d %d' % util.parsedate(newdate)
1477 newdate = '%d %d' % util.parsedate(newdate)
1478 wlock = repo.wlock()
1478 wlock = repo.wlock()
1479
1479
1480 try:
1480 try:
1481 self.checktoppatch(repo)
1481 self.checktoppatch(repo)
1482 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1482 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1483 if repo.changelog.heads(top) != [top]:
1483 if repo.changelog.heads(top) != [top]:
1484 raise util.Abort(_("cannot refresh a revision with children"))
1484 raise util.Abort(_("cannot refresh a revision with children"))
1485 if not repo[top].mutable():
1485 if not repo[top].mutable():
1486 raise util.Abort(_("cannot refresh immutable revision"),
1486 raise util.Abort(_("cannot refresh immutable revision"),
1487 hint=_('see "hg help phases" for details'))
1487 hint=_('see "hg help phases" for details'))
1488
1488
1489 cparents = repo.changelog.parents(top)
1489 cparents = repo.changelog.parents(top)
1490 patchparent = self.qparents(repo, top)
1490 patchparent = self.qparents(repo, top)
1491
1491
1492 inclsubs = checksubstate(repo, hex(patchparent))
1492 inclsubs = checksubstate(repo, hex(patchparent))
1493 if inclsubs:
1493 if inclsubs:
1494 inclsubs.append('.hgsubstate')
1494 inclsubs.append('.hgsubstate')
1495 substatestate = repo.dirstate['.hgsubstate']
1495 substatestate = repo.dirstate['.hgsubstate']
1496
1496
1497 ph = patchheader(self.join(patchfn), self.plainmode)
1497 ph = patchheader(self.join(patchfn), self.plainmode)
1498 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1498 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1499 if msg:
1499 if msg:
1500 ph.setmessage(msg)
1500 ph.setmessage(msg)
1501 if newuser:
1501 if newuser:
1502 ph.setuser(newuser)
1502 ph.setuser(newuser)
1503 if newdate:
1503 if newdate:
1504 ph.setdate(newdate)
1504 ph.setdate(newdate)
1505 ph.setparent(hex(patchparent))
1505 ph.setparent(hex(patchparent))
1506
1506
1507 # only commit new patch when write is complete
1507 # only commit new patch when write is complete
1508 patchf = self.opener(patchfn, 'w', atomictemp=True)
1508 patchf = self.opener(patchfn, 'w', atomictemp=True)
1509
1509
1510 comments = str(ph)
1510 comments = str(ph)
1511 if comments:
1511 if comments:
1512 patchf.write(comments)
1512 patchf.write(comments)
1513
1513
1514 # update the dirstate in place, strip off the qtip commit
1514 # update the dirstate in place, strip off the qtip commit
1515 # and then commit.
1515 # and then commit.
1516 #
1516 #
1517 # this should really read:
1517 # this should really read:
1518 # mm, dd, aa = repo.status(top, patchparent)[:3]
1518 # mm, dd, aa = repo.status(top, patchparent)[:3]
1519 # but we do it backwards to take advantage of manifest/changelog
1519 # but we do it backwards to take advantage of manifest/changelog
1520 # caching against the next repo.status call
1520 # caching against the next repo.status call
1521 mm, aa, dd = repo.status(patchparent, top)[:3]
1521 mm, aa, dd = repo.status(patchparent, top)[:3]
1522 changes = repo.changelog.read(top)
1522 changes = repo.changelog.read(top)
1523 man = repo.manifest.read(changes[0])
1523 man = repo.manifest.read(changes[0])
1524 aaa = aa[:]
1524 aaa = aa[:]
1525 matchfn = scmutil.match(repo[None], pats, opts)
1525 matchfn = scmutil.match(repo[None], pats, opts)
1526 # in short mode, we only diff the files included in the
1526 # in short mode, we only diff the files included in the
1527 # patch already plus specified files
1527 # patch already plus specified files
1528 if opts.get('short'):
1528 if opts.get('short'):
1529 # if amending a patch, we start with existing
1529 # if amending a patch, we start with existing
1530 # files plus specified files - unfiltered
1530 # files plus specified files - unfiltered
1531 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1531 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1532 # filter with include/exclude options
1532 # filter with include/exclude options
1533 matchfn = scmutil.match(repo[None], opts=opts)
1533 matchfn = scmutil.match(repo[None], opts=opts)
1534 else:
1534 else:
1535 match = scmutil.matchall(repo)
1535 match = scmutil.matchall(repo)
1536 m, a, r, d = repo.status(match=match)[:4]
1536 m, a, r, d = repo.status(match=match)[:4]
1537 mm = set(mm)
1537 mm = set(mm)
1538 aa = set(aa)
1538 aa = set(aa)
1539 dd = set(dd)
1539 dd = set(dd)
1540
1540
1541 # we might end up with files that were added between
1541 # we might end up with files that were added between
1542 # qtip and the dirstate parent, but then changed in the
1542 # qtip and the dirstate parent, but then changed in the
1543 # local dirstate. in this case, we want them to only
1543 # local dirstate. in this case, we want them to only
1544 # show up in the added section
1544 # show up in the added section
1545 for x in m:
1545 for x in m:
1546 if x not in aa:
1546 if x not in aa:
1547 mm.add(x)
1547 mm.add(x)
1548 # we might end up with files added by the local dirstate that
1548 # we might end up with files added by the local dirstate that
1549 # were deleted by the patch. In this case, they should only
1549 # were deleted by the patch. In this case, they should only
1550 # show up in the changed section.
1550 # show up in the changed section.
1551 for x in a:
1551 for x in a:
1552 if x in dd:
1552 if x in dd:
1553 dd.remove(x)
1553 dd.remove(x)
1554 mm.add(x)
1554 mm.add(x)
1555 else:
1555 else:
1556 aa.add(x)
1556 aa.add(x)
1557 # make sure any files deleted in the local dirstate
1557 # make sure any files deleted in the local dirstate
1558 # are not in the add or change column of the patch
1558 # are not in the add or change column of the patch
1559 forget = []
1559 forget = []
1560 for x in d + r:
1560 for x in d + r:
1561 if x in aa:
1561 if x in aa:
1562 aa.remove(x)
1562 aa.remove(x)
1563 forget.append(x)
1563 forget.append(x)
1564 continue
1564 continue
1565 else:
1565 else:
1566 mm.discard(x)
1566 mm.discard(x)
1567 dd.add(x)
1567 dd.add(x)
1568
1568
1569 m = list(mm)
1569 m = list(mm)
1570 r = list(dd)
1570 r = list(dd)
1571 a = list(aa)
1571 a = list(aa)
1572
1572
1573 # create 'match' that includes the files to be recommitted.
1573 # create 'match' that includes the files to be recommitted.
1574 # apply matchfn via repo.status to ensure correct case handling.
1574 # apply matchfn via repo.status to ensure correct case handling.
1575 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1575 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1576 allmatches = set(cm + ca + cr + cd)
1576 allmatches = set(cm + ca + cr + cd)
1577 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1577 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1578
1578
1579 files = set(inclsubs)
1579 files = set(inclsubs)
1580 for x in refreshchanges:
1580 for x in refreshchanges:
1581 files.update(x)
1581 files.update(x)
1582 match = scmutil.matchfiles(repo, files)
1582 match = scmutil.matchfiles(repo, files)
1583
1583
1584 bmlist = repo[top].bookmarks()
1584 bmlist = repo[top].bookmarks()
1585
1585
1586 try:
1586 try:
1587 if diffopts.git or diffopts.upgrade:
1587 if diffopts.git or diffopts.upgrade:
1588 copies = {}
1588 copies = {}
1589 for dst in a:
1589 for dst in a:
1590 src = repo.dirstate.copied(dst)
1590 src = repo.dirstate.copied(dst)
1591 # during qfold, the source file for copies may
1591 # during qfold, the source file for copies may
1592 # be removed. Treat this as a simple add.
1592 # be removed. Treat this as a simple add.
1593 if src is not None and src in repo.dirstate:
1593 if src is not None and src in repo.dirstate:
1594 copies.setdefault(src, []).append(dst)
1594 copies.setdefault(src, []).append(dst)
1595 repo.dirstate.add(dst)
1595 repo.dirstate.add(dst)
1596 # remember the copies between patchparent and qtip
1596 # remember the copies between patchparent and qtip
1597 for dst in aaa:
1597 for dst in aaa:
1598 f = repo.file(dst)
1598 f = repo.file(dst)
1599 src = f.renamed(man[dst])
1599 src = f.renamed(man[dst])
1600 if src:
1600 if src:
1601 copies.setdefault(src[0], []).extend(
1601 copies.setdefault(src[0], []).extend(
1602 copies.get(dst, []))
1602 copies.get(dst, []))
1603 if dst in a:
1603 if dst in a:
1604 copies[src[0]].append(dst)
1604 copies[src[0]].append(dst)
1605 # we can't copy a file created by the patch itself
1605 # we can't copy a file created by the patch itself
1606 if dst in copies:
1606 if dst in copies:
1607 del copies[dst]
1607 del copies[dst]
1608 for src, dsts in copies.iteritems():
1608 for src, dsts in copies.iteritems():
1609 for dst in dsts:
1609 for dst in dsts:
1610 repo.dirstate.copy(src, dst)
1610 repo.dirstate.copy(src, dst)
1611 else:
1611 else:
1612 for dst in a:
1612 for dst in a:
1613 repo.dirstate.add(dst)
1613 repo.dirstate.add(dst)
1614 # Drop useless copy information
1614 # Drop useless copy information
1615 for f in list(repo.dirstate.copies()):
1615 for f in list(repo.dirstate.copies()):
1616 repo.dirstate.copy(None, f)
1616 repo.dirstate.copy(None, f)
1617 for f in r:
1617 for f in r:
1618 repo.dirstate.remove(f)
1618 repo.dirstate.remove(f)
1619 # if the patch excludes a modified file, mark that
1619 # if the patch excludes a modified file, mark that
1620 # file with mtime=0 so status can see it.
1620 # file with mtime=0 so status can see it.
1621 mm = []
1621 mm = []
1622 for i in xrange(len(m) - 1, -1, -1):
1622 for i in xrange(len(m) - 1, -1, -1):
1623 if not matchfn(m[i]):
1623 if not matchfn(m[i]):
1624 mm.append(m[i])
1624 mm.append(m[i])
1625 del m[i]
1625 del m[i]
1626 for f in m:
1626 for f in m:
1627 repo.dirstate.normal(f)
1627 repo.dirstate.normal(f)
1628 for f in mm:
1628 for f in mm:
1629 repo.dirstate.normallookup(f)
1629 repo.dirstate.normallookup(f)
1630 for f in forget:
1630 for f in forget:
1631 repo.dirstate.drop(f)
1631 repo.dirstate.drop(f)
1632
1632
1633 if not msg:
1633 if not msg:
1634 if not ph.message:
1634 if not ph.message:
1635 message = "[mq]: %s\n" % patchfn
1635 message = "[mq]: %s\n" % patchfn
1636 else:
1636 else:
1637 message = "\n".join(ph.message)
1637 message = "\n".join(ph.message)
1638 else:
1638 else:
1639 message = msg
1639 message = msg
1640
1640
1641 user = ph.user or changes[1]
1641 user = ph.user or changes[1]
1642
1642
1643 oldphase = repo[top].phase()
1643 oldphase = repo[top].phase()
1644
1644
1645 # assumes strip can roll itself back if interrupted
1645 # assumes strip can roll itself back if interrupted
1646 repo.setparents(*cparents)
1646 repo.setparents(*cparents)
1647 self.applied.pop()
1647 self.applied.pop()
1648 self.applieddirty = True
1648 self.applieddirty = True
1649 strip(self.ui, repo, [top], update=False, backup='strip')
1649 strip(self.ui, repo, [top], update=False, backup='strip')
1650 except: # re-raises
1650 except: # re-raises
1651 repo.dirstate.invalidate()
1651 repo.dirstate.invalidate()
1652 raise
1652 raise
1653
1653
1654 try:
1654 try:
1655 # might be nice to attempt to roll back strip after this
1655 # might be nice to attempt to roll back strip after this
1656
1656
1657 # Ensure we create a new changeset in the same phase than
1657 # Ensure we create a new changeset in the same phase than
1658 # the old one.
1658 # the old one.
1659 n = newcommit(repo, oldphase, message, user, ph.date,
1659 n = newcommit(repo, oldphase, message, user, ph.date,
1660 match=match, force=True)
1660 match=match, force=True)
1661 # only write patch after a successful commit
1661 # only write patch after a successful commit
1662 c = [list(x) for x in refreshchanges]
1662 c = [list(x) for x in refreshchanges]
1663 if inclsubs:
1663 if inclsubs:
1664 self.putsubstate2changes(substatestate, c)
1664 self.putsubstate2changes(substatestate, c)
1665 chunks = patchmod.diff(repo, patchparent,
1665 chunks = patchmod.diff(repo, patchparent,
1666 changes=c, opts=diffopts)
1666 changes=c, opts=diffopts)
1667 for chunk in chunks:
1667 for chunk in chunks:
1668 patchf.write(chunk)
1668 patchf.write(chunk)
1669 patchf.close()
1669 patchf.close()
1670
1670
1671 marks = repo._bookmarks
1671 marks = repo._bookmarks
1672 for bm in bmlist:
1672 for bm in bmlist:
1673 marks[bm] = n
1673 marks[bm] = n
1674 marks.write()
1674 marks.write()
1675
1675
1676 self.applied.append(statusentry(n, patchfn))
1676 self.applied.append(statusentry(n, patchfn))
1677 except: # re-raises
1677 except: # re-raises
1678 ctx = repo[cparents[0]]
1678 ctx = repo[cparents[0]]
1679 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1679 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1680 self.savedirty()
1680 self.savedirty()
1681 self.ui.warn(_('refresh interrupted while patch was popped! '
1681 self.ui.warn(_('refresh interrupted while patch was popped! '
1682 '(revert --all, qpush to recover)\n'))
1682 '(revert --all, qpush to recover)\n'))
1683 raise
1683 raise
1684 finally:
1684 finally:
1685 wlock.release()
1685 wlock.release()
1686 self.removeundo(repo)
1686 self.removeundo(repo)
1687
1687
1688 def init(self, repo, create=False):
1688 def init(self, repo, create=False):
1689 if not create and os.path.isdir(self.path):
1689 if not create and os.path.isdir(self.path):
1690 raise util.Abort(_("patch queue directory already exists"))
1690 raise util.Abort(_("patch queue directory already exists"))
1691 try:
1691 try:
1692 os.mkdir(self.path)
1692 os.mkdir(self.path)
1693 except OSError, inst:
1693 except OSError, inst:
1694 if inst.errno != errno.EEXIST or not create:
1694 if inst.errno != errno.EEXIST or not create:
1695 raise
1695 raise
1696 if create:
1696 if create:
1697 return self.qrepo(create=True)
1697 return self.qrepo(create=True)
1698
1698
1699 def unapplied(self, repo, patch=None):
1699 def unapplied(self, repo, patch=None):
1700 if patch and patch not in self.series:
1700 if patch and patch not in self.series:
1701 raise util.Abort(_("patch %s is not in series file") % patch)
1701 raise util.Abort(_("patch %s is not in series file") % patch)
1702 if not patch:
1702 if not patch:
1703 start = self.seriesend()
1703 start = self.seriesend()
1704 else:
1704 else:
1705 start = self.series.index(patch) + 1
1705 start = self.series.index(patch) + 1
1706 unapplied = []
1706 unapplied = []
1707 for i in xrange(start, len(self.series)):
1707 for i in xrange(start, len(self.series)):
1708 pushable, reason = self.pushable(i)
1708 pushable, reason = self.pushable(i)
1709 if pushable:
1709 if pushable:
1710 unapplied.append((i, self.series[i]))
1710 unapplied.append((i, self.series[i]))
1711 self.explainpushable(i)
1711 self.explainpushable(i)
1712 return unapplied
1712 return unapplied
1713
1713
1714 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1714 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1715 summary=False):
1715 summary=False):
1716 def displayname(pfx, patchname, state):
1716 def displayname(pfx, patchname, state):
1717 if pfx:
1717 if pfx:
1718 self.ui.write(pfx)
1718 self.ui.write(pfx)
1719 if summary:
1719 if summary:
1720 ph = patchheader(self.join(patchname), self.plainmode)
1720 ph = patchheader(self.join(patchname), self.plainmode)
1721 msg = ph.message and ph.message[0] or ''
1721 msg = ph.message and ph.message[0] or ''
1722 if self.ui.formatted():
1722 if self.ui.formatted():
1723 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1723 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1724 if width > 0:
1724 if width > 0:
1725 msg = util.ellipsis(msg, width)
1725 msg = util.ellipsis(msg, width)
1726 else:
1726 else:
1727 msg = ''
1727 msg = ''
1728 self.ui.write(patchname, label='qseries.' + state)
1728 self.ui.write(patchname, label='qseries.' + state)
1729 self.ui.write(': ')
1729 self.ui.write(': ')
1730 self.ui.write(msg, label='qseries.message.' + state)
1730 self.ui.write(msg, label='qseries.message.' + state)
1731 else:
1731 else:
1732 self.ui.write(patchname, label='qseries.' + state)
1732 self.ui.write(patchname, label='qseries.' + state)
1733 self.ui.write('\n')
1733 self.ui.write('\n')
1734
1734
1735 applied = set([p.name for p in self.applied])
1735 applied = set([p.name for p in self.applied])
1736 if length is None:
1736 if length is None:
1737 length = len(self.series) - start
1737 length = len(self.series) - start
1738 if not missing:
1738 if not missing:
1739 if self.ui.verbose:
1739 if self.ui.verbose:
1740 idxwidth = len(str(start + length - 1))
1740 idxwidth = len(str(start + length - 1))
1741 for i in xrange(start, start + length):
1741 for i in xrange(start, start + length):
1742 patch = self.series[i]
1742 patch = self.series[i]
1743 if patch in applied:
1743 if patch in applied:
1744 char, state = 'A', 'applied'
1744 char, state = 'A', 'applied'
1745 elif self.pushable(i)[0]:
1745 elif self.pushable(i)[0]:
1746 char, state = 'U', 'unapplied'
1746 char, state = 'U', 'unapplied'
1747 else:
1747 else:
1748 char, state = 'G', 'guarded'
1748 char, state = 'G', 'guarded'
1749 pfx = ''
1749 pfx = ''
1750 if self.ui.verbose:
1750 if self.ui.verbose:
1751 pfx = '%*d %s ' % (idxwidth, i, char)
1751 pfx = '%*d %s ' % (idxwidth, i, char)
1752 elif status and status != char:
1752 elif status and status != char:
1753 continue
1753 continue
1754 displayname(pfx, patch, state)
1754 displayname(pfx, patch, state)
1755 else:
1755 else:
1756 msng_list = []
1756 msng_list = []
1757 for root, dirs, files in os.walk(self.path):
1757 for root, dirs, files in os.walk(self.path):
1758 d = root[len(self.path) + 1:]
1758 d = root[len(self.path) + 1:]
1759 for f in files:
1759 for f in files:
1760 fl = os.path.join(d, f)
1760 fl = os.path.join(d, f)
1761 if (fl not in self.series and
1761 if (fl not in self.series and
1762 fl not in (self.statuspath, self.seriespath,
1762 fl not in (self.statuspath, self.seriespath,
1763 self.guardspath)
1763 self.guardspath)
1764 and not fl.startswith('.')):
1764 and not fl.startswith('.')):
1765 msng_list.append(fl)
1765 msng_list.append(fl)
1766 for x in sorted(msng_list):
1766 for x in sorted(msng_list):
1767 pfx = self.ui.verbose and ('D ') or ''
1767 pfx = self.ui.verbose and ('D ') or ''
1768 displayname(pfx, x, 'missing')
1768 displayname(pfx, x, 'missing')
1769
1769
1770 def issaveline(self, l):
1770 def issaveline(self, l):
1771 if l.name == '.hg.patches.save.line':
1771 if l.name == '.hg.patches.save.line':
1772 return True
1772 return True
1773
1773
1774 def qrepo(self, create=False):
1774 def qrepo(self, create=False):
1775 ui = self.baseui.copy()
1775 ui = self.baseui.copy()
1776 if create or os.path.isdir(self.join(".hg")):
1776 if create or os.path.isdir(self.join(".hg")):
1777 return hg.repository(ui, path=self.path, create=create)
1777 return hg.repository(ui, path=self.path, create=create)
1778
1778
1779 def restore(self, repo, rev, delete=None, qupdate=None):
1779 def restore(self, repo, rev, delete=None, qupdate=None):
1780 desc = repo[rev].description().strip()
1780 desc = repo[rev].description().strip()
1781 lines = desc.splitlines()
1781 lines = desc.splitlines()
1782 i = 0
1782 i = 0
1783 datastart = None
1783 datastart = None
1784 series = []
1784 series = []
1785 applied = []
1785 applied = []
1786 qpp = None
1786 qpp = None
1787 for i, line in enumerate(lines):
1787 for i, line in enumerate(lines):
1788 if line == 'Patch Data:':
1788 if line == 'Patch Data:':
1789 datastart = i + 1
1789 datastart = i + 1
1790 elif line.startswith('Dirstate:'):
1790 elif line.startswith('Dirstate:'):
1791 l = line.rstrip()
1791 l = line.rstrip()
1792 l = l[10:].split(' ')
1792 l = l[10:].split(' ')
1793 qpp = [bin(x) for x in l]
1793 qpp = [bin(x) for x in l]
1794 elif datastart is not None:
1794 elif datastart is not None:
1795 l = line.rstrip()
1795 l = line.rstrip()
1796 n, name = l.split(':', 1)
1796 n, name = l.split(':', 1)
1797 if n:
1797 if n:
1798 applied.append(statusentry(bin(n), name))
1798 applied.append(statusentry(bin(n), name))
1799 else:
1799 else:
1800 series.append(l)
1800 series.append(l)
1801 if datastart is None:
1801 if datastart is None:
1802 self.ui.warn(_("no saved patch data found\n"))
1802 self.ui.warn(_("no saved patch data found\n"))
1803 return 1
1803 return 1
1804 self.ui.warn(_("restoring status: %s\n") % lines[0])
1804 self.ui.warn(_("restoring status: %s\n") % lines[0])
1805 self.fullseries = series
1805 self.fullseries = series
1806 self.applied = applied
1806 self.applied = applied
1807 self.parseseries()
1807 self.parseseries()
1808 self.seriesdirty = True
1808 self.seriesdirty = True
1809 self.applieddirty = True
1809 self.applieddirty = True
1810 heads = repo.changelog.heads()
1810 heads = repo.changelog.heads()
1811 if delete:
1811 if delete:
1812 if rev not in heads:
1812 if rev not in heads:
1813 self.ui.warn(_("save entry has children, leaving it alone\n"))
1813 self.ui.warn(_("save entry has children, leaving it alone\n"))
1814 else:
1814 else:
1815 self.ui.warn(_("removing save entry %s\n") % short(rev))
1815 self.ui.warn(_("removing save entry %s\n") % short(rev))
1816 pp = repo.dirstate.parents()
1816 pp = repo.dirstate.parents()
1817 if rev in pp:
1817 if rev in pp:
1818 update = True
1818 update = True
1819 else:
1819 else:
1820 update = False
1820 update = False
1821 strip(self.ui, repo, [rev], update=update, backup='strip')
1821 strip(self.ui, repo, [rev], update=update, backup='strip')
1822 if qpp:
1822 if qpp:
1823 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1823 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1824 (short(qpp[0]), short(qpp[1])))
1824 (short(qpp[0]), short(qpp[1])))
1825 if qupdate:
1825 if qupdate:
1826 self.ui.status(_("updating queue directory\n"))
1826 self.ui.status(_("updating queue directory\n"))
1827 r = self.qrepo()
1827 r = self.qrepo()
1828 if not r:
1828 if not r:
1829 self.ui.warn(_("unable to load queue repository\n"))
1829 self.ui.warn(_("unable to load queue repository\n"))
1830 return 1
1830 return 1
1831 hg.clean(r, qpp[0])
1831 hg.clean(r, qpp[0])
1832
1832
1833 def save(self, repo, msg=None):
1833 def save(self, repo, msg=None):
1834 if not self.applied:
1834 if not self.applied:
1835 self.ui.warn(_("save: no patches applied, exiting\n"))
1835 self.ui.warn(_("save: no patches applied, exiting\n"))
1836 return 1
1836 return 1
1837 if self.issaveline(self.applied[-1]):
1837 if self.issaveline(self.applied[-1]):
1838 self.ui.warn(_("status is already saved\n"))
1838 self.ui.warn(_("status is already saved\n"))
1839 return 1
1839 return 1
1840
1840
1841 if not msg:
1841 if not msg:
1842 msg = _("hg patches saved state")
1842 msg = _("hg patches saved state")
1843 else:
1843 else:
1844 msg = "hg patches: " + msg.rstrip('\r\n')
1844 msg = "hg patches: " + msg.rstrip('\r\n')
1845 r = self.qrepo()
1845 r = self.qrepo()
1846 if r:
1846 if r:
1847 pp = r.dirstate.parents()
1847 pp = r.dirstate.parents()
1848 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1848 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1849 msg += "\n\nPatch Data:\n"
1849 msg += "\n\nPatch Data:\n"
1850 msg += ''.join('%s\n' % x for x in self.applied)
1850 msg += ''.join('%s\n' % x for x in self.applied)
1851 msg += ''.join(':%s\n' % x for x in self.fullseries)
1851 msg += ''.join(':%s\n' % x for x in self.fullseries)
1852 n = repo.commit(msg, force=True)
1852 n = repo.commit(msg, force=True)
1853 if not n:
1853 if not n:
1854 self.ui.warn(_("repo commit failed\n"))
1854 self.ui.warn(_("repo commit failed\n"))
1855 return 1
1855 return 1
1856 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1856 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1857 self.applieddirty = True
1857 self.applieddirty = True
1858 self.removeundo(repo)
1858 self.removeundo(repo)
1859
1859
1860 def fullseriesend(self):
1860 def fullseriesend(self):
1861 if self.applied:
1861 if self.applied:
1862 p = self.applied[-1].name
1862 p = self.applied[-1].name
1863 end = self.findseries(p)
1863 end = self.findseries(p)
1864 if end is None:
1864 if end is None:
1865 return len(self.fullseries)
1865 return len(self.fullseries)
1866 return end + 1
1866 return end + 1
1867 return 0
1867 return 0
1868
1868
1869 def seriesend(self, all_patches=False):
1869 def seriesend(self, all_patches=False):
1870 """If all_patches is False, return the index of the next pushable patch
1870 """If all_patches is False, return the index of the next pushable patch
1871 in the series, or the series length. If all_patches is True, return the
1871 in the series, or the series length. If all_patches is True, return the
1872 index of the first patch past the last applied one.
1872 index of the first patch past the last applied one.
1873 """
1873 """
1874 end = 0
1874 end = 0
1875 def nextpatch(start):
1875 def nextpatch(start):
1876 if all_patches or start >= len(self.series):
1876 if all_patches or start >= len(self.series):
1877 return start
1877 return start
1878 for i in xrange(start, len(self.series)):
1878 for i in xrange(start, len(self.series)):
1879 p, reason = self.pushable(i)
1879 p, reason = self.pushable(i)
1880 if p:
1880 if p:
1881 return i
1881 return i
1882 self.explainpushable(i)
1882 self.explainpushable(i)
1883 return len(self.series)
1883 return len(self.series)
1884 if self.applied:
1884 if self.applied:
1885 p = self.applied[-1].name
1885 p = self.applied[-1].name
1886 try:
1886 try:
1887 end = self.series.index(p)
1887 end = self.series.index(p)
1888 except ValueError:
1888 except ValueError:
1889 return 0
1889 return 0
1890 return nextpatch(end + 1)
1890 return nextpatch(end + 1)
1891 return nextpatch(end)
1891 return nextpatch(end)
1892
1892
1893 def appliedname(self, index):
1893 def appliedname(self, index):
1894 pname = self.applied[index].name
1894 pname = self.applied[index].name
1895 if not self.ui.verbose:
1895 if not self.ui.verbose:
1896 p = pname
1896 p = pname
1897 else:
1897 else:
1898 p = str(self.series.index(pname)) + " " + pname
1898 p = str(self.series.index(pname)) + " " + pname
1899 return p
1899 return p
1900
1900
1901 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1901 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1902 force=None, git=False):
1902 force=None, git=False):
1903 def checkseries(patchname):
1903 def checkseries(patchname):
1904 if patchname in self.series:
1904 if patchname in self.series:
1905 raise util.Abort(_('patch %s is already in the series file')
1905 raise util.Abort(_('patch %s is already in the series file')
1906 % patchname)
1906 % patchname)
1907
1907
1908 if rev:
1908 if rev:
1909 if files:
1909 if files:
1910 raise util.Abort(_('option "-r" not valid when importing '
1910 raise util.Abort(_('option "-r" not valid when importing '
1911 'files'))
1911 'files'))
1912 rev = scmutil.revrange(repo, rev)
1912 rev = scmutil.revrange(repo, rev)
1913 rev.sort(reverse=True)
1913 rev.sort(reverse=True)
1914 elif not files:
1914 elif not files:
1915 raise util.Abort(_('no files or revisions specified'))
1915 raise util.Abort(_('no files or revisions specified'))
1916 if (len(files) > 1 or len(rev) > 1) and patchname:
1916 if (len(files) > 1 or len(rev) > 1) and patchname:
1917 raise util.Abort(_('option "-n" not valid when importing multiple '
1917 raise util.Abort(_('option "-n" not valid when importing multiple '
1918 'patches'))
1918 'patches'))
1919 imported = []
1919 imported = []
1920 if rev:
1920 if rev:
1921 # If mq patches are applied, we can only import revisions
1921 # If mq patches are applied, we can only import revisions
1922 # that form a linear path to qbase.
1922 # that form a linear path to qbase.
1923 # Otherwise, they should form a linear path to a head.
1923 # Otherwise, they should form a linear path to a head.
1924 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1924 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1925 if len(heads) > 1:
1925 if len(heads) > 1:
1926 raise util.Abort(_('revision %d is the root of more than one '
1926 raise util.Abort(_('revision %d is the root of more than one '
1927 'branch') % rev[-1])
1927 'branch') % rev[-1])
1928 if self.applied:
1928 if self.applied:
1929 base = repo.changelog.node(rev[0])
1929 base = repo.changelog.node(rev[0])
1930 if base in [n.node for n in self.applied]:
1930 if base in [n.node for n in self.applied]:
1931 raise util.Abort(_('revision %d is already managed')
1931 raise util.Abort(_('revision %d is already managed')
1932 % rev[0])
1932 % rev[0])
1933 if heads != [self.applied[-1].node]:
1933 if heads != [self.applied[-1].node]:
1934 raise util.Abort(_('revision %d is not the parent of '
1934 raise util.Abort(_('revision %d is not the parent of '
1935 'the queue') % rev[0])
1935 'the queue') % rev[0])
1936 base = repo.changelog.rev(self.applied[0].node)
1936 base = repo.changelog.rev(self.applied[0].node)
1937 lastparent = repo.changelog.parentrevs(base)[0]
1937 lastparent = repo.changelog.parentrevs(base)[0]
1938 else:
1938 else:
1939 if heads != [repo.changelog.node(rev[0])]:
1939 if heads != [repo.changelog.node(rev[0])]:
1940 raise util.Abort(_('revision %d has unmanaged children')
1940 raise util.Abort(_('revision %d has unmanaged children')
1941 % rev[0])
1941 % rev[0])
1942 lastparent = None
1942 lastparent = None
1943
1943
1944 diffopts = self.diffopts({'git': git})
1944 diffopts = self.diffopts({'git': git})
1945 for r in rev:
1945 for r in rev:
1946 if not repo[r].mutable():
1946 if not repo[r].mutable():
1947 raise util.Abort(_('revision %d is not mutable') % r,
1947 raise util.Abort(_('revision %d is not mutable') % r,
1948 hint=_('see "hg help phases" for details'))
1948 hint=_('see "hg help phases" for details'))
1949 p1, p2 = repo.changelog.parentrevs(r)
1949 p1, p2 = repo.changelog.parentrevs(r)
1950 n = repo.changelog.node(r)
1950 n = repo.changelog.node(r)
1951 if p2 != nullrev:
1951 if p2 != nullrev:
1952 raise util.Abort(_('cannot import merge revision %d') % r)
1952 raise util.Abort(_('cannot import merge revision %d') % r)
1953 if lastparent and lastparent != r:
1953 if lastparent and lastparent != r:
1954 raise util.Abort(_('revision %d is not the parent of %d')
1954 raise util.Abort(_('revision %d is not the parent of %d')
1955 % (r, lastparent))
1955 % (r, lastparent))
1956 lastparent = p1
1956 lastparent = p1
1957
1957
1958 if not patchname:
1958 if not patchname:
1959 patchname = normname('%d.diff' % r)
1959 patchname = normname('%d.diff' % r)
1960 checkseries(patchname)
1960 checkseries(patchname)
1961 self.checkpatchname(patchname, force)
1961 self.checkpatchname(patchname, force)
1962 self.fullseries.insert(0, patchname)
1962 self.fullseries.insert(0, patchname)
1963
1963
1964 patchf = self.opener(patchname, "w")
1964 patchf = self.opener(patchname, "w")
1965 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1965 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1966 patchf.close()
1966 patchf.close()
1967
1967
1968 se = statusentry(n, patchname)
1968 se = statusentry(n, patchname)
1969 self.applied.insert(0, se)
1969 self.applied.insert(0, se)
1970
1970
1971 self.added.append(patchname)
1971 self.added.append(patchname)
1972 imported.append(patchname)
1972 imported.append(patchname)
1973 patchname = None
1973 patchname = None
1974 if rev and repo.ui.configbool('mq', 'secret', False):
1974 if rev and repo.ui.configbool('mq', 'secret', False):
1975 # if we added anything with --rev, we must move the secret root
1975 # if we added anything with --rev, we must move the secret root
1976 phases.retractboundary(repo, phases.secret, [n])
1976 phases.retractboundary(repo, phases.secret, [n])
1977 self.parseseries()
1977 self.parseseries()
1978 self.applieddirty = True
1978 self.applieddirty = True
1979 self.seriesdirty = True
1979 self.seriesdirty = True
1980
1980
1981 for i, filename in enumerate(files):
1981 for i, filename in enumerate(files):
1982 if existing:
1982 if existing:
1983 if filename == '-':
1983 if filename == '-':
1984 raise util.Abort(_('-e is incompatible with import from -'))
1984 raise util.Abort(_('-e is incompatible with import from -'))
1985 filename = normname(filename)
1985 filename = normname(filename)
1986 self.checkreservedname(filename)
1986 self.checkreservedname(filename)
1987 originpath = self.join(filename)
1987 originpath = self.join(filename)
1988 if not os.path.isfile(originpath):
1988 if not os.path.isfile(originpath):
1989 raise util.Abort(_("patch %s does not exist") % filename)
1989 raise util.Abort(_("patch %s does not exist") % filename)
1990
1990
1991 if patchname:
1991 if patchname:
1992 self.checkpatchname(patchname, force)
1992 self.checkpatchname(patchname, force)
1993
1993
1994 self.ui.write(_('renaming %s to %s\n')
1994 self.ui.write(_('renaming %s to %s\n')
1995 % (filename, patchname))
1995 % (filename, patchname))
1996 util.rename(originpath, self.join(patchname))
1996 util.rename(originpath, self.join(patchname))
1997 else:
1997 else:
1998 patchname = filename
1998 patchname = filename
1999
1999
2000 else:
2000 else:
2001 if filename == '-' and not patchname:
2001 if filename == '-' and not patchname:
2002 raise util.Abort(_('need --name to import a patch from -'))
2002 raise util.Abort(_('need --name to import a patch from -'))
2003 elif not patchname:
2003 elif not patchname:
2004 patchname = normname(os.path.basename(filename.rstrip('/')))
2004 patchname = normname(os.path.basename(filename.rstrip('/')))
2005 self.checkpatchname(patchname, force)
2005 self.checkpatchname(patchname, force)
2006 try:
2006 try:
2007 if filename == '-':
2007 if filename == '-':
2008 text = self.ui.fin.read()
2008 text = self.ui.fin.read()
2009 else:
2009 else:
2010 fp = hg.openpath(self.ui, filename)
2010 fp = hg.openpath(self.ui, filename)
2011 text = fp.read()
2011 text = fp.read()
2012 fp.close()
2012 fp.close()
2013 except (OSError, IOError):
2013 except (OSError, IOError):
2014 raise util.Abort(_("unable to read file %s") % filename)
2014 raise util.Abort(_("unable to read file %s") % filename)
2015 patchf = self.opener(patchname, "w")
2015 patchf = self.opener(patchname, "w")
2016 patchf.write(text)
2016 patchf.write(text)
2017 patchf.close()
2017 patchf.close()
2018 if not force:
2018 if not force:
2019 checkseries(patchname)
2019 checkseries(patchname)
2020 if patchname not in self.series:
2020 if patchname not in self.series:
2021 index = self.fullseriesend() + i
2021 index = self.fullseriesend() + i
2022 self.fullseries[index:index] = [patchname]
2022 self.fullseries[index:index] = [patchname]
2023 self.parseseries()
2023 self.parseseries()
2024 self.seriesdirty = True
2024 self.seriesdirty = True
2025 self.ui.warn(_("adding %s to series file\n") % patchname)
2025 self.ui.warn(_("adding %s to series file\n") % patchname)
2026 self.added.append(patchname)
2026 self.added.append(patchname)
2027 imported.append(patchname)
2027 imported.append(patchname)
2028 patchname = None
2028 patchname = None
2029
2029
2030 self.removeundo(repo)
2030 self.removeundo(repo)
2031 return imported
2031 return imported
2032
2032
2033 def fixkeepchangesopts(ui, opts):
2033 def fixkeepchangesopts(ui, opts):
2034 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2034 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2035 or opts.get('exact')):
2035 or opts.get('exact')):
2036 return opts
2036 return opts
2037 opts = dict(opts)
2037 opts = dict(opts)
2038 opts['keep_changes'] = True
2038 opts['keep_changes'] = True
2039 return opts
2039 return opts
2040
2040
2041 @command("qdelete|qremove|qrm",
2041 @command("qdelete|qremove|qrm",
2042 [('k', 'keep', None, _('keep patch file')),
2042 [('k', 'keep', None, _('keep patch file')),
2043 ('r', 'rev', [],
2043 ('r', 'rev', [],
2044 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2044 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2045 _('hg qdelete [-k] [PATCH]...'))
2045 _('hg qdelete [-k] [PATCH]...'))
2046 def delete(ui, repo, *patches, **opts):
2046 def delete(ui, repo, *patches, **opts):
2047 """remove patches from queue
2047 """remove patches from queue
2048
2048
2049 The patches must not be applied, and at least one patch is required. Exact
2049 The patches must not be applied, and at least one patch is required. Exact
2050 patch identifiers must be given. With -k/--keep, the patch files are
2050 patch identifiers must be given. With -k/--keep, the patch files are
2051 preserved in the patch directory.
2051 preserved in the patch directory.
2052
2052
2053 To stop managing a patch and move it into permanent history,
2053 To stop managing a patch and move it into permanent history,
2054 use the :hg:`qfinish` command."""
2054 use the :hg:`qfinish` command."""
2055 q = repo.mq
2055 q = repo.mq
2056 q.delete(repo, patches, opts)
2056 q.delete(repo, patches, opts)
2057 q.savedirty()
2057 q.savedirty()
2058 return 0
2058 return 0
2059
2059
2060 @command("qapplied",
2060 @command("qapplied",
2061 [('1', 'last', None, _('show only the preceding applied patch'))
2061 [('1', 'last', None, _('show only the preceding applied patch'))
2062 ] + seriesopts,
2062 ] + seriesopts,
2063 _('hg qapplied [-1] [-s] [PATCH]'))
2063 _('hg qapplied [-1] [-s] [PATCH]'))
2064 def applied(ui, repo, patch=None, **opts):
2064 def applied(ui, repo, patch=None, **opts):
2065 """print the patches already applied
2065 """print the patches already applied
2066
2066
2067 Returns 0 on success."""
2067 Returns 0 on success."""
2068
2068
2069 q = repo.mq
2069 q = repo.mq
2070
2070
2071 if patch:
2071 if patch:
2072 if patch not in q.series:
2072 if patch not in q.series:
2073 raise util.Abort(_("patch %s is not in series file") % patch)
2073 raise util.Abort(_("patch %s is not in series file") % patch)
2074 end = q.series.index(patch) + 1
2074 end = q.series.index(patch) + 1
2075 else:
2075 else:
2076 end = q.seriesend(True)
2076 end = q.seriesend(True)
2077
2077
2078 if opts.get('last') and not end:
2078 if opts.get('last') and not end:
2079 ui.write(_("no patches applied\n"))
2079 ui.write(_("no patches applied\n"))
2080 return 1
2080 return 1
2081 elif opts.get('last') and end == 1:
2081 elif opts.get('last') and end == 1:
2082 ui.write(_("only one patch applied\n"))
2082 ui.write(_("only one patch applied\n"))
2083 return 1
2083 return 1
2084 elif opts.get('last'):
2084 elif opts.get('last'):
2085 start = end - 2
2085 start = end - 2
2086 end = 1
2086 end = 1
2087 else:
2087 else:
2088 start = 0
2088 start = 0
2089
2089
2090 q.qseries(repo, length=end, start=start, status='A',
2090 q.qseries(repo, length=end, start=start, status='A',
2091 summary=opts.get('summary'))
2091 summary=opts.get('summary'))
2092
2092
2093
2093
2094 @command("qunapplied",
2094 @command("qunapplied",
2095 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2095 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2096 _('hg qunapplied [-1] [-s] [PATCH]'))
2096 _('hg qunapplied [-1] [-s] [PATCH]'))
2097 def unapplied(ui, repo, patch=None, **opts):
2097 def unapplied(ui, repo, patch=None, **opts):
2098 """print the patches not yet applied
2098 """print the patches not yet applied
2099
2099
2100 Returns 0 on success."""
2100 Returns 0 on success."""
2101
2101
2102 q = repo.mq
2102 q = repo.mq
2103 if patch:
2103 if patch:
2104 if patch not in q.series:
2104 if patch not in q.series:
2105 raise util.Abort(_("patch %s is not in series file") % patch)
2105 raise util.Abort(_("patch %s is not in series file") % patch)
2106 start = q.series.index(patch) + 1
2106 start = q.series.index(patch) + 1
2107 else:
2107 else:
2108 start = q.seriesend(True)
2108 start = q.seriesend(True)
2109
2109
2110 if start == len(q.series) and opts.get('first'):
2110 if start == len(q.series) and opts.get('first'):
2111 ui.write(_("all patches applied\n"))
2111 ui.write(_("all patches applied\n"))
2112 return 1
2112 return 1
2113
2113
2114 length = opts.get('first') and 1 or None
2114 length = opts.get('first') and 1 or None
2115 q.qseries(repo, start=start, length=length, status='U',
2115 q.qseries(repo, start=start, length=length, status='U',
2116 summary=opts.get('summary'))
2116 summary=opts.get('summary'))
2117
2117
2118 @command("qimport",
2118 @command("qimport",
2119 [('e', 'existing', None, _('import file in patch directory')),
2119 [('e', 'existing', None, _('import file in patch directory')),
2120 ('n', 'name', '',
2120 ('n', 'name', '',
2121 _('name of patch file'), _('NAME')),
2121 _('name of patch file'), _('NAME')),
2122 ('f', 'force', None, _('overwrite existing files')),
2122 ('f', 'force', None, _('overwrite existing files')),
2123 ('r', 'rev', [],
2123 ('r', 'rev', [],
2124 _('place existing revisions under mq control'), _('REV')),
2124 _('place existing revisions under mq control'), _('REV')),
2125 ('g', 'git', None, _('use git extended diff format')),
2125 ('g', 'git', None, _('use git extended diff format')),
2126 ('P', 'push', None, _('qpush after importing'))],
2126 ('P', 'push', None, _('qpush after importing'))],
2127 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2127 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2128 def qimport(ui, repo, *filename, **opts):
2128 def qimport(ui, repo, *filename, **opts):
2129 """import a patch or existing changeset
2129 """import a patch or existing changeset
2130
2130
2131 The patch is inserted into the series after the last applied
2131 The patch is inserted into the series after the last applied
2132 patch. If no patches have been applied, qimport prepends the patch
2132 patch. If no patches have been applied, qimport prepends the patch
2133 to the series.
2133 to the series.
2134
2134
2135 The patch will have the same name as its source file unless you
2135 The patch will have the same name as its source file unless you
2136 give it a new one with -n/--name.
2136 give it a new one with -n/--name.
2137
2137
2138 You can register an existing patch inside the patch directory with
2138 You can register an existing patch inside the patch directory with
2139 the -e/--existing flag.
2139 the -e/--existing flag.
2140
2140
2141 With -f/--force, an existing patch of the same name will be
2141 With -f/--force, an existing patch of the same name will be
2142 overwritten.
2142 overwritten.
2143
2143
2144 An existing changeset may be placed under mq control with -r/--rev
2144 An existing changeset may be placed under mq control with -r/--rev
2145 (e.g. qimport --rev . -n patch will place the current revision
2145 (e.g. qimport --rev . -n patch will place the current revision
2146 under mq control). With -g/--git, patches imported with --rev will
2146 under mq control). With -g/--git, patches imported with --rev will
2147 use the git diff format. See the diffs help topic for information
2147 use the git diff format. See the diffs help topic for information
2148 on why this is important for preserving rename/copy information
2148 on why this is important for preserving rename/copy information
2149 and permission changes. Use :hg:`qfinish` to remove changesets
2149 and permission changes. Use :hg:`qfinish` to remove changesets
2150 from mq control.
2150 from mq control.
2151
2151
2152 To import a patch from standard input, pass - as the patch file.
2152 To import a patch from standard input, pass - as the patch file.
2153 When importing from standard input, a patch name must be specified
2153 When importing from standard input, a patch name must be specified
2154 using the --name flag.
2154 using the --name flag.
2155
2155
2156 To import an existing patch while renaming it::
2156 To import an existing patch while renaming it::
2157
2157
2158 hg qimport -e existing-patch -n new-name
2158 hg qimport -e existing-patch -n new-name
2159
2159
2160 Returns 0 if import succeeded.
2160 Returns 0 if import succeeded.
2161 """
2161 """
2162 lock = repo.lock() # cause this may move phase
2162 lock = repo.lock() # cause this may move phase
2163 try:
2163 try:
2164 q = repo.mq
2164 q = repo.mq
2165 try:
2165 try:
2166 imported = q.qimport(
2166 imported = q.qimport(
2167 repo, filename, patchname=opts.get('name'),
2167 repo, filename, patchname=opts.get('name'),
2168 existing=opts.get('existing'), force=opts.get('force'),
2168 existing=opts.get('existing'), force=opts.get('force'),
2169 rev=opts.get('rev'), git=opts.get('git'))
2169 rev=opts.get('rev'), git=opts.get('git'))
2170 finally:
2170 finally:
2171 q.savedirty()
2171 q.savedirty()
2172 finally:
2172 finally:
2173 lock.release()
2173 lock.release()
2174
2174
2175 if imported and opts.get('push') and not opts.get('rev'):
2175 if imported and opts.get('push') and not opts.get('rev'):
2176 return q.push(repo, imported[-1])
2176 return q.push(repo, imported[-1])
2177 return 0
2177 return 0
2178
2178
2179 def qinit(ui, repo, create):
2179 def qinit(ui, repo, create):
2180 """initialize a new queue repository
2180 """initialize a new queue repository
2181
2181
2182 This command also creates a series file for ordering patches, and
2182 This command also creates a series file for ordering patches, and
2183 an mq-specific .hgignore file in the queue repository, to exclude
2183 an mq-specific .hgignore file in the queue repository, to exclude
2184 the status and guards files (these contain mostly transient state).
2184 the status and guards files (these contain mostly transient state).
2185
2185
2186 Returns 0 if initialization succeeded."""
2186 Returns 0 if initialization succeeded."""
2187 q = repo.mq
2187 q = repo.mq
2188 r = q.init(repo, create)
2188 r = q.init(repo, create)
2189 q.savedirty()
2189 q.savedirty()
2190 if r:
2190 if r:
2191 if not os.path.exists(r.wjoin('.hgignore')):
2191 if not os.path.exists(r.wjoin('.hgignore')):
2192 fp = r.wopener('.hgignore', 'w')
2192 fp = r.wopener('.hgignore', 'w')
2193 fp.write('^\\.hg\n')
2193 fp.write('^\\.hg\n')
2194 fp.write('^\\.mq\n')
2194 fp.write('^\\.mq\n')
2195 fp.write('syntax: glob\n')
2195 fp.write('syntax: glob\n')
2196 fp.write('status\n')
2196 fp.write('status\n')
2197 fp.write('guards\n')
2197 fp.write('guards\n')
2198 fp.close()
2198 fp.close()
2199 if not os.path.exists(r.wjoin('series')):
2199 if not os.path.exists(r.wjoin('series')):
2200 r.wopener('series', 'w').close()
2200 r.wopener('series', 'w').close()
2201 r[None].add(['.hgignore', 'series'])
2201 r[None].add(['.hgignore', 'series'])
2202 commands.add(ui, r)
2202 commands.add(ui, r)
2203 return 0
2203 return 0
2204
2204
2205 @command("^qinit",
2205 @command("^qinit",
2206 [('c', 'create-repo', None, _('create queue repository'))],
2206 [('c', 'create-repo', None, _('create queue repository'))],
2207 _('hg qinit [-c]'))
2207 _('hg qinit [-c]'))
2208 def init(ui, repo, **opts):
2208 def init(ui, repo, **opts):
2209 """init a new queue repository (DEPRECATED)
2209 """init a new queue repository (DEPRECATED)
2210
2210
2211 The queue repository is unversioned by default. If
2211 The queue repository is unversioned by default. If
2212 -c/--create-repo is specified, qinit will create a separate nested
2212 -c/--create-repo is specified, qinit will create a separate nested
2213 repository for patches (qinit -c may also be run later to convert
2213 repository for patches (qinit -c may also be run later to convert
2214 an unversioned patch repository into a versioned one). You can use
2214 an unversioned patch repository into a versioned one). You can use
2215 qcommit to commit changes to this queue repository.
2215 qcommit to commit changes to this queue repository.
2216
2216
2217 This command is deprecated. Without -c, it's implied by other relevant
2217 This command is deprecated. Without -c, it's implied by other relevant
2218 commands. With -c, use :hg:`init --mq` instead."""
2218 commands. With -c, use :hg:`init --mq` instead."""
2219 return qinit(ui, repo, create=opts.get('create_repo'))
2219 return qinit(ui, repo, create=opts.get('create_repo'))
2220
2220
2221 @command("qclone",
2221 @command("qclone",
2222 [('', 'pull', None, _('use pull protocol to copy metadata')),
2222 [('', 'pull', None, _('use pull protocol to copy metadata')),
2223 ('U', 'noupdate', None,
2223 ('U', 'noupdate', None,
2224 _('do not update the new working directories')),
2224 _('do not update the new working directories')),
2225 ('', 'uncompressed', None,
2225 ('', 'uncompressed', None,
2226 _('use uncompressed transfer (fast over LAN)')),
2226 _('use uncompressed transfer (fast over LAN)')),
2227 ('p', 'patches', '',
2227 ('p', 'patches', '',
2228 _('location of source patch repository'), _('REPO')),
2228 _('location of source patch repository'), _('REPO')),
2229 ] + commands.remoteopts,
2229 ] + commands.remoteopts,
2230 _('hg qclone [OPTION]... SOURCE [DEST]'))
2230 _('hg qclone [OPTION]... SOURCE [DEST]'))
2231 def clone(ui, source, dest=None, **opts):
2231 def clone(ui, source, dest=None, **opts):
2232 '''clone main and patch repository at same time
2232 '''clone main and patch repository at same time
2233
2233
2234 If source is local, destination will have no patches applied. If
2234 If source is local, destination will have no patches applied. If
2235 source is remote, this command can not check if patches are
2235 source is remote, this command can not check if patches are
2236 applied in source, so cannot guarantee that patches are not
2236 applied in source, so cannot guarantee that patches are not
2237 applied in destination. If you clone remote repository, be sure
2237 applied in destination. If you clone remote repository, be sure
2238 before that it has no patches applied.
2238 before that it has no patches applied.
2239
2239
2240 Source patch repository is looked for in <src>/.hg/patches by
2240 Source patch repository is looked for in <src>/.hg/patches by
2241 default. Use -p <url> to change.
2241 default. Use -p <url> to change.
2242
2242
2243 The patch directory must be a nested Mercurial repository, as
2243 The patch directory must be a nested Mercurial repository, as
2244 would be created by :hg:`init --mq`.
2244 would be created by :hg:`init --mq`.
2245
2245
2246 Return 0 on success.
2246 Return 0 on success.
2247 '''
2247 '''
2248 def patchdir(repo):
2248 def patchdir(repo):
2249 """compute a patch repo url from a repo object"""
2249 """compute a patch repo url from a repo object"""
2250 url = repo.url()
2250 url = repo.url()
2251 if url.endswith('/'):
2251 if url.endswith('/'):
2252 url = url[:-1]
2252 url = url[:-1]
2253 return url + '/.hg/patches'
2253 return url + '/.hg/patches'
2254
2254
2255 # main repo (destination and sources)
2255 # main repo (destination and sources)
2256 if dest is None:
2256 if dest is None:
2257 dest = hg.defaultdest(source)
2257 dest = hg.defaultdest(source)
2258 sr = hg.peer(ui, opts, ui.expandpath(source))
2258 sr = hg.peer(ui, opts, ui.expandpath(source))
2259
2259
2260 # patches repo (source only)
2260 # patches repo (source only)
2261 if opts.get('patches'):
2261 if opts.get('patches'):
2262 patchespath = ui.expandpath(opts.get('patches'))
2262 patchespath = ui.expandpath(opts.get('patches'))
2263 else:
2263 else:
2264 patchespath = patchdir(sr)
2264 patchespath = patchdir(sr)
2265 try:
2265 try:
2266 hg.peer(ui, opts, patchespath)
2266 hg.peer(ui, opts, patchespath)
2267 except error.RepoError:
2267 except error.RepoError:
2268 raise util.Abort(_('versioned patch repository not found'
2268 raise util.Abort(_('versioned patch repository not found'
2269 ' (see init --mq)'))
2269 ' (see init --mq)'))
2270 qbase, destrev = None, None
2270 qbase, destrev = None, None
2271 if sr.local():
2271 if sr.local():
2272 repo = sr.local()
2272 repo = sr.local()
2273 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2273 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2274 qbase = repo.mq.applied[0].node
2274 qbase = repo.mq.applied[0].node
2275 if not hg.islocal(dest):
2275 if not hg.islocal(dest):
2276 heads = set(repo.heads())
2276 heads = set(repo.heads())
2277 destrev = list(heads.difference(repo.heads(qbase)))
2277 destrev = list(heads.difference(repo.heads(qbase)))
2278 destrev.append(repo.changelog.parents(qbase)[0])
2278 destrev.append(repo.changelog.parents(qbase)[0])
2279 elif sr.capable('lookup'):
2279 elif sr.capable('lookup'):
2280 try:
2280 try:
2281 qbase = sr.lookup('qbase')
2281 qbase = sr.lookup('qbase')
2282 except error.RepoError:
2282 except error.RepoError:
2283 pass
2283 pass
2284
2284
2285 ui.note(_('cloning main repository\n'))
2285 ui.note(_('cloning main repository\n'))
2286 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2286 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2287 pull=opts.get('pull'),
2287 pull=opts.get('pull'),
2288 rev=destrev,
2288 rev=destrev,
2289 update=False,
2289 update=False,
2290 stream=opts.get('uncompressed'))
2290 stream=opts.get('uncompressed'))
2291
2291
2292 ui.note(_('cloning patch repository\n'))
2292 ui.note(_('cloning patch repository\n'))
2293 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2293 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2294 pull=opts.get('pull'), update=not opts.get('noupdate'),
2294 pull=opts.get('pull'), update=not opts.get('noupdate'),
2295 stream=opts.get('uncompressed'))
2295 stream=opts.get('uncompressed'))
2296
2296
2297 if dr.local():
2297 if dr.local():
2298 repo = dr.local()
2298 repo = dr.local()
2299 if qbase:
2299 if qbase:
2300 ui.note(_('stripping applied patches from destination '
2300 ui.note(_('stripping applied patches from destination '
2301 'repository\n'))
2301 'repository\n'))
2302 strip(ui, repo, [qbase], update=False, backup=None)
2302 strip(ui, repo, [qbase], update=False, backup=None)
2303 if not opts.get('noupdate'):
2303 if not opts.get('noupdate'):
2304 ui.note(_('updating destination repository\n'))
2304 ui.note(_('updating destination repository\n'))
2305 hg.update(repo, repo.changelog.tip())
2305 hg.update(repo, repo.changelog.tip())
2306
2306
2307 @command("qcommit|qci",
2307 @command("qcommit|qci",
2308 commands.table["^commit|ci"][1],
2308 commands.table["^commit|ci"][1],
2309 _('hg qcommit [OPTION]... [FILE]...'))
2309 _('hg qcommit [OPTION]... [FILE]...'))
2310 def commit(ui, repo, *pats, **opts):
2310 def commit(ui, repo, *pats, **opts):
2311 """commit changes in the queue repository (DEPRECATED)
2311 """commit changes in the queue repository (DEPRECATED)
2312
2312
2313 This command is deprecated; use :hg:`commit --mq` instead."""
2313 This command is deprecated; use :hg:`commit --mq` instead."""
2314 q = repo.mq
2314 q = repo.mq
2315 r = q.qrepo()
2315 r = q.qrepo()
2316 if not r:
2316 if not r:
2317 raise util.Abort('no queue repository')
2317 raise util.Abort('no queue repository')
2318 commands.commit(r.ui, r, *pats, **opts)
2318 commands.commit(r.ui, r, *pats, **opts)
2319
2319
2320 @command("qseries",
2320 @command("qseries",
2321 [('m', 'missing', None, _('print patches not in series')),
2321 [('m', 'missing', None, _('print patches not in series')),
2322 ] + seriesopts,
2322 ] + seriesopts,
2323 _('hg qseries [-ms]'))
2323 _('hg qseries [-ms]'))
2324 def series(ui, repo, **opts):
2324 def series(ui, repo, **opts):
2325 """print the entire series file
2325 """print the entire series file
2326
2326
2327 Returns 0 on success."""
2327 Returns 0 on success."""
2328 repo.mq.qseries(repo, missing=opts.get('missing'),
2328 repo.mq.qseries(repo, missing=opts.get('missing'),
2329 summary=opts.get('summary'))
2329 summary=opts.get('summary'))
2330 return 0
2330 return 0
2331
2331
2332 @command("qtop", seriesopts, _('hg qtop [-s]'))
2332 @command("qtop", seriesopts, _('hg qtop [-s]'))
2333 def top(ui, repo, **opts):
2333 def top(ui, repo, **opts):
2334 """print the name of the current patch
2334 """print the name of the current patch
2335
2335
2336 Returns 0 on success."""
2336 Returns 0 on success."""
2337 q = repo.mq
2337 q = repo.mq
2338 t = q.applied and q.seriesend(True) or 0
2338 t = q.applied and q.seriesend(True) or 0
2339 if t:
2339 if t:
2340 q.qseries(repo, start=t - 1, length=1, status='A',
2340 q.qseries(repo, start=t - 1, length=1, status='A',
2341 summary=opts.get('summary'))
2341 summary=opts.get('summary'))
2342 else:
2342 else:
2343 ui.write(_("no patches applied\n"))
2343 ui.write(_("no patches applied\n"))
2344 return 1
2344 return 1
2345
2345
2346 @command("qnext", seriesopts, _('hg qnext [-s]'))
2346 @command("qnext", seriesopts, _('hg qnext [-s]'))
2347 def next(ui, repo, **opts):
2347 def next(ui, repo, **opts):
2348 """print the name of the next pushable patch
2348 """print the name of the next pushable patch
2349
2349
2350 Returns 0 on success."""
2350 Returns 0 on success."""
2351 q = repo.mq
2351 q = repo.mq
2352 end = q.seriesend()
2352 end = q.seriesend()
2353 if end == len(q.series):
2353 if end == len(q.series):
2354 ui.write(_("all patches applied\n"))
2354 ui.write(_("all patches applied\n"))
2355 return 1
2355 return 1
2356 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2356 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2357
2357
2358 @command("qprev", seriesopts, _('hg qprev [-s]'))
2358 @command("qprev", seriesopts, _('hg qprev [-s]'))
2359 def prev(ui, repo, **opts):
2359 def prev(ui, repo, **opts):
2360 """print the name of the preceding applied patch
2360 """print the name of the preceding applied patch
2361
2361
2362 Returns 0 on success."""
2362 Returns 0 on success."""
2363 q = repo.mq
2363 q = repo.mq
2364 l = len(q.applied)
2364 l = len(q.applied)
2365 if l == 1:
2365 if l == 1:
2366 ui.write(_("only one patch applied\n"))
2366 ui.write(_("only one patch applied\n"))
2367 return 1
2367 return 1
2368 if not l:
2368 if not l:
2369 ui.write(_("no patches applied\n"))
2369 ui.write(_("no patches applied\n"))
2370 return 1
2370 return 1
2371 idx = q.series.index(q.applied[-2].name)
2371 idx = q.series.index(q.applied[-2].name)
2372 q.qseries(repo, start=idx, length=1, status='A',
2372 q.qseries(repo, start=idx, length=1, status='A',
2373 summary=opts.get('summary'))
2373 summary=opts.get('summary'))
2374
2374
2375 def setupheaderopts(ui, opts):
2375 def setupheaderopts(ui, opts):
2376 if not opts.get('user') and opts.get('currentuser'):
2376 if not opts.get('user') and opts.get('currentuser'):
2377 opts['user'] = ui.username()
2377 opts['user'] = ui.username()
2378 if not opts.get('date') and opts.get('currentdate'):
2378 if not opts.get('date') and opts.get('currentdate'):
2379 opts['date'] = "%d %d" % util.makedate()
2379 opts['date'] = "%d %d" % util.makedate()
2380
2380
2381 @command("^qnew",
2381 @command("^qnew",
2382 [('e', 'edit', None, _('edit commit message')),
2382 [('e', 'edit', None, _('edit commit message')),
2383 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2383 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2384 ('g', 'git', None, _('use git extended diff format')),
2384 ('g', 'git', None, _('use git extended diff format')),
2385 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2385 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2386 ('u', 'user', '',
2386 ('u', 'user', '',
2387 _('add "From: <USER>" to patch'), _('USER')),
2387 _('add "From: <USER>" to patch'), _('USER')),
2388 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2388 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2389 ('d', 'date', '',
2389 ('d', 'date', '',
2390 _('add "Date: <DATE>" to patch'), _('DATE'))
2390 _('add "Date: <DATE>" to patch'), _('DATE'))
2391 ] + commands.walkopts + commands.commitopts,
2391 ] + commands.walkopts + commands.commitopts,
2392 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2392 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2393 def new(ui, repo, patch, *args, **opts):
2393 def new(ui, repo, patch, *args, **opts):
2394 """create a new patch
2394 """create a new patch
2395
2395
2396 qnew creates a new patch on top of the currently-applied patch (if
2396 qnew creates a new patch on top of the currently-applied patch (if
2397 any). The patch will be initialized with any outstanding changes
2397 any). The patch will be initialized with any outstanding changes
2398 in the working directory. You may also use -I/--include,
2398 in the working directory. You may also use -I/--include,
2399 -X/--exclude, and/or a list of files after the patch name to add
2399 -X/--exclude, and/or a list of files after the patch name to add
2400 only changes to matching files to the new patch, leaving the rest
2400 only changes to matching files to the new patch, leaving the rest
2401 as uncommitted modifications.
2401 as uncommitted modifications.
2402
2402
2403 -u/--user and -d/--date can be used to set the (given) user and
2403 -u/--user and -d/--date can be used to set the (given) user and
2404 date, respectively. -U/--currentuser and -D/--currentdate set user
2404 date, respectively. -U/--currentuser and -D/--currentdate set user
2405 to current user and date to current date.
2405 to current user and date to current date.
2406
2406
2407 -e/--edit, -m/--message or -l/--logfile set the patch header as
2407 -e/--edit, -m/--message or -l/--logfile set the patch header as
2408 well as the commit message. If none is specified, the header is
2408 well as the commit message. If none is specified, the header is
2409 empty and the commit message is '[mq]: PATCH'.
2409 empty and the commit message is '[mq]: PATCH'.
2410
2410
2411 Use the -g/--git option to keep the patch in the git extended diff
2411 Use the -g/--git option to keep the patch in the git extended diff
2412 format. Read the diffs help topic for more information on why this
2412 format. Read the diffs help topic for more information on why this
2413 is important for preserving permission changes and copy/rename
2413 is important for preserving permission changes and copy/rename
2414 information.
2414 information.
2415
2415
2416 Returns 0 on successful creation of a new patch.
2416 Returns 0 on successful creation of a new patch.
2417 """
2417 """
2418 msg = cmdutil.logmessage(ui, opts)
2418 msg = cmdutil.logmessage(ui, opts)
2419 def getmsg():
2419 def getmsg():
2420 return ui.edit(msg, opts.get('user') or ui.username())
2420 return ui.edit(msg, opts.get('user') or ui.username())
2421 q = repo.mq
2421 q = repo.mq
2422 opts['msg'] = msg
2422 opts['msg'] = msg
2423 if opts.get('edit'):
2423 if opts.get('edit'):
2424 opts['msg'] = getmsg
2424 opts['msg'] = getmsg
2425 else:
2425 else:
2426 opts['msg'] = msg
2426 opts['msg'] = msg
2427 setupheaderopts(ui, opts)
2427 setupheaderopts(ui, opts)
2428 q.new(repo, patch, *args, **opts)
2428 q.new(repo, patch, *args, **opts)
2429 q.savedirty()
2429 q.savedirty()
2430 return 0
2430 return 0
2431
2431
2432 @command("^qrefresh",
2432 @command("^qrefresh",
2433 [('e', 'edit', None, _('edit commit message')),
2433 [('e', 'edit', None, _('edit commit message')),
2434 ('g', 'git', None, _('use git extended diff format')),
2434 ('g', 'git', None, _('use git extended diff format')),
2435 ('s', 'short', None,
2435 ('s', 'short', None,
2436 _('refresh only files already in the patch and specified files')),
2436 _('refresh only files already in the patch and specified files')),
2437 ('U', 'currentuser', None,
2437 ('U', 'currentuser', None,
2438 _('add/update author field in patch with current user')),
2438 _('add/update author field in patch with current user')),
2439 ('u', 'user', '',
2439 ('u', 'user', '',
2440 _('add/update author field in patch with given user'), _('USER')),
2440 _('add/update author field in patch with given user'), _('USER')),
2441 ('D', 'currentdate', None,
2441 ('D', 'currentdate', None,
2442 _('add/update date field in patch with current date')),
2442 _('add/update date field in patch with current date')),
2443 ('d', 'date', '',
2443 ('d', 'date', '',
2444 _('add/update date field in patch with given date'), _('DATE'))
2444 _('add/update date field in patch with given date'), _('DATE'))
2445 ] + commands.walkopts + commands.commitopts,
2445 ] + commands.walkopts + commands.commitopts,
2446 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2446 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2447 def refresh(ui, repo, *pats, **opts):
2447 def refresh(ui, repo, *pats, **opts):
2448 """update the current patch
2448 """update the current patch
2449
2449
2450 If any file patterns are provided, the refreshed patch will
2450 If any file patterns are provided, the refreshed patch will
2451 contain only the modifications that match those patterns; the
2451 contain only the modifications that match those patterns; the
2452 remaining modifications will remain in the working directory.
2452 remaining modifications will remain in the working directory.
2453
2453
2454 If -s/--short is specified, files currently included in the patch
2454 If -s/--short is specified, files currently included in the patch
2455 will be refreshed just like matched files and remain in the patch.
2455 will be refreshed just like matched files and remain in the patch.
2456
2456
2457 If -e/--edit is specified, Mercurial will start your configured editor for
2457 If -e/--edit is specified, Mercurial will start your configured editor for
2458 you to enter a message. In case qrefresh fails, you will find a backup of
2458 you to enter a message. In case qrefresh fails, you will find a backup of
2459 your message in ``.hg/last-message.txt``.
2459 your message in ``.hg/last-message.txt``.
2460
2460
2461 hg add/remove/copy/rename work as usual, though you might want to
2461 hg add/remove/copy/rename work as usual, though you might want to
2462 use git-style patches (-g/--git or [diff] git=1) to track copies
2462 use git-style patches (-g/--git or [diff] git=1) to track copies
2463 and renames. See the diffs help topic for more information on the
2463 and renames. See the diffs help topic for more information on the
2464 git diff format.
2464 git diff format.
2465
2465
2466 Returns 0 on success.
2466 Returns 0 on success.
2467 """
2467 """
2468 q = repo.mq
2468 q = repo.mq
2469 message = cmdutil.logmessage(ui, opts)
2469 message = cmdutil.logmessage(ui, opts)
2470 if opts.get('edit'):
2470 if opts.get('edit'):
2471 if not q.applied:
2471 if not q.applied:
2472 ui.write(_("no patches applied\n"))
2472 ui.write(_("no patches applied\n"))
2473 return 1
2473 return 1
2474 if message:
2474 if message:
2475 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2475 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2476 patch = q.applied[-1].name
2476 patch = q.applied[-1].name
2477 ph = patchheader(q.join(patch), q.plainmode)
2477 ph = patchheader(q.join(patch), q.plainmode)
2478 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2478 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2479 # We don't want to lose the patch message if qrefresh fails (issue2062)
2479 # We don't want to lose the patch message if qrefresh fails (issue2062)
2480 repo.savecommitmessage(message)
2480 repo.savecommitmessage(message)
2481 setupheaderopts(ui, opts)
2481 setupheaderopts(ui, opts)
2482 wlock = repo.wlock()
2482 wlock = repo.wlock()
2483 try:
2483 try:
2484 ret = q.refresh(repo, pats, msg=message, **opts)
2484 ret = q.refresh(repo, pats, msg=message, **opts)
2485 q.savedirty()
2485 q.savedirty()
2486 return ret
2486 return ret
2487 finally:
2487 finally:
2488 wlock.release()
2488 wlock.release()
2489
2489
2490 @command("^qdiff",
2490 @command("^qdiff",
2491 commands.diffopts + commands.diffopts2 + commands.walkopts,
2491 commands.diffopts + commands.diffopts2 + commands.walkopts,
2492 _('hg qdiff [OPTION]... [FILE]...'))
2492 _('hg qdiff [OPTION]... [FILE]...'))
2493 def diff(ui, repo, *pats, **opts):
2493 def diff(ui, repo, *pats, **opts):
2494 """diff of the current patch and subsequent modifications
2494 """diff of the current patch and subsequent modifications
2495
2495
2496 Shows a diff which includes the current patch as well as any
2496 Shows a diff which includes the current patch as well as any
2497 changes which have been made in the working directory since the
2497 changes which have been made in the working directory since the
2498 last refresh (thus showing what the current patch would become
2498 last refresh (thus showing what the current patch would become
2499 after a qrefresh).
2499 after a qrefresh).
2500
2500
2501 Use :hg:`diff` if you only want to see the changes made since the
2501 Use :hg:`diff` if you only want to see the changes made since the
2502 last qrefresh, or :hg:`export qtip` if you want to see changes
2502 last qrefresh, or :hg:`export qtip` if you want to see changes
2503 made by the current patch without including changes made since the
2503 made by the current patch without including changes made since the
2504 qrefresh.
2504 qrefresh.
2505
2505
2506 Returns 0 on success.
2506 Returns 0 on success.
2507 """
2507 """
2508 repo.mq.diff(repo, pats, opts)
2508 repo.mq.diff(repo, pats, opts)
2509 return 0
2509 return 0
2510
2510
2511 @command('qfold',
2511 @command('qfold',
2512 [('e', 'edit', None, _('edit patch header')),
2512 [('e', 'edit', None, _('edit patch header')),
2513 ('k', 'keep', None, _('keep folded patch files')),
2513 ('k', 'keep', None, _('keep folded patch files')),
2514 ] + commands.commitopts,
2514 ] + commands.commitopts,
2515 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2515 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2516 def fold(ui, repo, *files, **opts):
2516 def fold(ui, repo, *files, **opts):
2517 """fold the named patches into the current patch
2517 """fold the named patches into the current patch
2518
2518
2519 Patches must not yet be applied. Each patch will be successively
2519 Patches must not yet be applied. Each patch will be successively
2520 applied to the current patch in the order given. If all the
2520 applied to the current patch in the order given. If all the
2521 patches apply successfully, the current patch will be refreshed
2521 patches apply successfully, the current patch will be refreshed
2522 with the new cumulative patch, and the folded patches will be
2522 with the new cumulative patch, and the folded patches will be
2523 deleted. With -k/--keep, the folded patch files will not be
2523 deleted. With -k/--keep, the folded patch files will not be
2524 removed afterwards.
2524 removed afterwards.
2525
2525
2526 The header for each folded patch will be concatenated with the
2526 The header for each folded patch will be concatenated with the
2527 current patch header, separated by a line of ``* * *``.
2527 current patch header, separated by a line of ``* * *``.
2528
2528
2529 Returns 0 on success."""
2529 Returns 0 on success."""
2530 q = repo.mq
2530 q = repo.mq
2531 if not files:
2531 if not files:
2532 raise util.Abort(_('qfold requires at least one patch name'))
2532 raise util.Abort(_('qfold requires at least one patch name'))
2533 if not q.checktoppatch(repo)[0]:
2533 if not q.checktoppatch(repo)[0]:
2534 raise util.Abort(_('no patches applied'))
2534 raise util.Abort(_('no patches applied'))
2535 q.checklocalchanges(repo)
2535 q.checklocalchanges(repo)
2536
2536
2537 message = cmdutil.logmessage(ui, opts)
2537 message = cmdutil.logmessage(ui, opts)
2538 if opts.get('edit'):
2538 if opts.get('edit'):
2539 if message:
2539 if message:
2540 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2540 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2541
2541
2542 parent = q.lookup('qtip')
2542 parent = q.lookup('qtip')
2543 patches = []
2543 patches = []
2544 messages = []
2544 messages = []
2545 for f in files:
2545 for f in files:
2546 p = q.lookup(f)
2546 p = q.lookup(f)
2547 if p in patches or p == parent:
2547 if p in patches or p == parent:
2548 ui.warn(_('skipping already folded patch %s\n') % p)
2548 ui.warn(_('skipping already folded patch %s\n') % p)
2549 if q.isapplied(p):
2549 if q.isapplied(p):
2550 raise util.Abort(_('qfold cannot fold already applied patch %s')
2550 raise util.Abort(_('qfold cannot fold already applied patch %s')
2551 % p)
2551 % p)
2552 patches.append(p)
2552 patches.append(p)
2553
2553
2554 for p in patches:
2554 for p in patches:
2555 if not message:
2555 if not message:
2556 ph = patchheader(q.join(p), q.plainmode)
2556 ph = patchheader(q.join(p), q.plainmode)
2557 if ph.message:
2557 if ph.message:
2558 messages.append(ph.message)
2558 messages.append(ph.message)
2559 pf = q.join(p)
2559 pf = q.join(p)
2560 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2560 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2561 if not patchsuccess:
2561 if not patchsuccess:
2562 raise util.Abort(_('error folding patch %s') % p)
2562 raise util.Abort(_('error folding patch %s') % p)
2563
2563
2564 if not message:
2564 if not message:
2565 ph = patchheader(q.join(parent), q.plainmode)
2565 ph = patchheader(q.join(parent), q.plainmode)
2566 message, user = ph.message, ph.user
2566 message, user = ph.message, ph.user
2567 for msg in messages:
2567 for msg in messages:
2568 message.append('* * *')
2568 message.append('* * *')
2569 message.extend(msg)
2569 message.extend(msg)
2570 message = '\n'.join(message)
2570 message = '\n'.join(message)
2571
2571
2572 if opts.get('edit'):
2572 if opts.get('edit'):
2573 message = ui.edit(message, user or ui.username())
2573 message = ui.edit(message, user or ui.username())
2574
2574
2575 diffopts = q.patchopts(q.diffopts(), *patches)
2575 diffopts = q.patchopts(q.diffopts(), *patches)
2576 wlock = repo.wlock()
2576 wlock = repo.wlock()
2577 try:
2577 try:
2578 q.refresh(repo, msg=message, git=diffopts.git)
2578 q.refresh(repo, msg=message, git=diffopts.git)
2579 q.delete(repo, patches, opts)
2579 q.delete(repo, patches, opts)
2580 q.savedirty()
2580 q.savedirty()
2581 finally:
2581 finally:
2582 wlock.release()
2582 wlock.release()
2583
2583
2584 @command("qgoto",
2584 @command("qgoto",
2585 [('', 'keep-changes', None,
2585 [('', 'keep-changes', None,
2586 _('tolerate non-conflicting local changes')),
2586 _('tolerate non-conflicting local changes')),
2587 ('f', 'force', None, _('overwrite any local changes')),
2587 ('f', 'force', None, _('overwrite any local changes')),
2588 ('', 'no-backup', None, _('do not save backup copies of files'))],
2588 ('', 'no-backup', None, _('do not save backup copies of files'))],
2589 _('hg qgoto [OPTION]... PATCH'))
2589 _('hg qgoto [OPTION]... PATCH'))
2590 def goto(ui, repo, patch, **opts):
2590 def goto(ui, repo, patch, **opts):
2591 '''push or pop patches until named patch is at top of stack
2591 '''push or pop patches until named patch is at top of stack
2592
2592
2593 Returns 0 on success.'''
2593 Returns 0 on success.'''
2594 opts = fixkeepchangesopts(ui, opts)
2594 opts = fixkeepchangesopts(ui, opts)
2595 q = repo.mq
2595 q = repo.mq
2596 patch = q.lookup(patch)
2596 patch = q.lookup(patch)
2597 nobackup = opts.get('no_backup')
2597 nobackup = opts.get('no_backup')
2598 keepchanges = opts.get('keep_changes')
2598 keepchanges = opts.get('keep_changes')
2599 if q.isapplied(patch):
2599 if q.isapplied(patch):
2600 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2600 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2601 keepchanges=keepchanges)
2601 keepchanges=keepchanges)
2602 else:
2602 else:
2603 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2603 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2604 keepchanges=keepchanges)
2604 keepchanges=keepchanges)
2605 q.savedirty()
2605 q.savedirty()
2606 return ret
2606 return ret
2607
2607
2608 @command("qguard",
2608 @command("qguard",
2609 [('l', 'list', None, _('list all patches and guards')),
2609 [('l', 'list', None, _('list all patches and guards')),
2610 ('n', 'none', None, _('drop all guards'))],
2610 ('n', 'none', None, _('drop all guards'))],
2611 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2611 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2612 def guard(ui, repo, *args, **opts):
2612 def guard(ui, repo, *args, **opts):
2613 '''set or print guards for a patch
2613 '''set or print guards for a patch
2614
2614
2615 Guards control whether a patch can be pushed. A patch with no
2615 Guards control whether a patch can be pushed. A patch with no
2616 guards is always pushed. A patch with a positive guard ("+foo") is
2616 guards is always pushed. A patch with a positive guard ("+foo") is
2617 pushed only if the :hg:`qselect` command has activated it. A patch with
2617 pushed only if the :hg:`qselect` command has activated it. A patch with
2618 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2618 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2619 has activated it.
2619 has activated it.
2620
2620
2621 With no arguments, print the currently active guards.
2621 With no arguments, print the currently active guards.
2622 With arguments, set guards for the named patch.
2622 With arguments, set guards for the named patch.
2623
2623
2624 .. note::
2624 .. note::
2625
2625 Specifying negative guards now requires '--'.
2626 Specifying negative guards now requires '--'.
2626
2627
2627 To set guards on another patch::
2628 To set guards on another patch::
2628
2629
2629 hg qguard other.patch -- +2.6.17 -stable
2630 hg qguard other.patch -- +2.6.17 -stable
2630
2631
2631 Returns 0 on success.
2632 Returns 0 on success.
2632 '''
2633 '''
2633 def status(idx):
2634 def status(idx):
2634 guards = q.seriesguards[idx] or ['unguarded']
2635 guards = q.seriesguards[idx] or ['unguarded']
2635 if q.series[idx] in applied:
2636 if q.series[idx] in applied:
2636 state = 'applied'
2637 state = 'applied'
2637 elif q.pushable(idx)[0]:
2638 elif q.pushable(idx)[0]:
2638 state = 'unapplied'
2639 state = 'unapplied'
2639 else:
2640 else:
2640 state = 'guarded'
2641 state = 'guarded'
2641 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2642 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2642 ui.write('%s: ' % ui.label(q.series[idx], label))
2643 ui.write('%s: ' % ui.label(q.series[idx], label))
2643
2644
2644 for i, guard in enumerate(guards):
2645 for i, guard in enumerate(guards):
2645 if guard.startswith('+'):
2646 if guard.startswith('+'):
2646 ui.write(guard, label='qguard.positive')
2647 ui.write(guard, label='qguard.positive')
2647 elif guard.startswith('-'):
2648 elif guard.startswith('-'):
2648 ui.write(guard, label='qguard.negative')
2649 ui.write(guard, label='qguard.negative')
2649 else:
2650 else:
2650 ui.write(guard, label='qguard.unguarded')
2651 ui.write(guard, label='qguard.unguarded')
2651 if i != len(guards) - 1:
2652 if i != len(guards) - 1:
2652 ui.write(' ')
2653 ui.write(' ')
2653 ui.write('\n')
2654 ui.write('\n')
2654 q = repo.mq
2655 q = repo.mq
2655 applied = set(p.name for p in q.applied)
2656 applied = set(p.name for p in q.applied)
2656 patch = None
2657 patch = None
2657 args = list(args)
2658 args = list(args)
2658 if opts.get('list'):
2659 if opts.get('list'):
2659 if args or opts.get('none'):
2660 if args or opts.get('none'):
2660 raise util.Abort(_('cannot mix -l/--list with options or '
2661 raise util.Abort(_('cannot mix -l/--list with options or '
2661 'arguments'))
2662 'arguments'))
2662 for i in xrange(len(q.series)):
2663 for i in xrange(len(q.series)):
2663 status(i)
2664 status(i)
2664 return
2665 return
2665 if not args or args[0][0:1] in '-+':
2666 if not args or args[0][0:1] in '-+':
2666 if not q.applied:
2667 if not q.applied:
2667 raise util.Abort(_('no patches applied'))
2668 raise util.Abort(_('no patches applied'))
2668 patch = q.applied[-1].name
2669 patch = q.applied[-1].name
2669 if patch is None and args[0][0:1] not in '-+':
2670 if patch is None and args[0][0:1] not in '-+':
2670 patch = args.pop(0)
2671 patch = args.pop(0)
2671 if patch is None:
2672 if patch is None:
2672 raise util.Abort(_('no patch to work with'))
2673 raise util.Abort(_('no patch to work with'))
2673 if args or opts.get('none'):
2674 if args or opts.get('none'):
2674 idx = q.findseries(patch)
2675 idx = q.findseries(patch)
2675 if idx is None:
2676 if idx is None:
2676 raise util.Abort(_('no patch named %s') % patch)
2677 raise util.Abort(_('no patch named %s') % patch)
2677 q.setguards(idx, args)
2678 q.setguards(idx, args)
2678 q.savedirty()
2679 q.savedirty()
2679 else:
2680 else:
2680 status(q.series.index(q.lookup(patch)))
2681 status(q.series.index(q.lookup(patch)))
2681
2682
2682 @command("qheader", [], _('hg qheader [PATCH]'))
2683 @command("qheader", [], _('hg qheader [PATCH]'))
2683 def header(ui, repo, patch=None):
2684 def header(ui, repo, patch=None):
2684 """print the header of the topmost or specified patch
2685 """print the header of the topmost or specified patch
2685
2686
2686 Returns 0 on success."""
2687 Returns 0 on success."""
2687 q = repo.mq
2688 q = repo.mq
2688
2689
2689 if patch:
2690 if patch:
2690 patch = q.lookup(patch)
2691 patch = q.lookup(patch)
2691 else:
2692 else:
2692 if not q.applied:
2693 if not q.applied:
2693 ui.write(_('no patches applied\n'))
2694 ui.write(_('no patches applied\n'))
2694 return 1
2695 return 1
2695 patch = q.lookup('qtip')
2696 patch = q.lookup('qtip')
2696 ph = patchheader(q.join(patch), q.plainmode)
2697 ph = patchheader(q.join(patch), q.plainmode)
2697
2698
2698 ui.write('\n'.join(ph.message) + '\n')
2699 ui.write('\n'.join(ph.message) + '\n')
2699
2700
2700 def lastsavename(path):
2701 def lastsavename(path):
2701 (directory, base) = os.path.split(path)
2702 (directory, base) = os.path.split(path)
2702 names = os.listdir(directory)
2703 names = os.listdir(directory)
2703 namere = re.compile("%s.([0-9]+)" % base)
2704 namere = re.compile("%s.([0-9]+)" % base)
2704 maxindex = None
2705 maxindex = None
2705 maxname = None
2706 maxname = None
2706 for f in names:
2707 for f in names:
2707 m = namere.match(f)
2708 m = namere.match(f)
2708 if m:
2709 if m:
2709 index = int(m.group(1))
2710 index = int(m.group(1))
2710 if maxindex is None or index > maxindex:
2711 if maxindex is None or index > maxindex:
2711 maxindex = index
2712 maxindex = index
2712 maxname = f
2713 maxname = f
2713 if maxname:
2714 if maxname:
2714 return (os.path.join(directory, maxname), maxindex)
2715 return (os.path.join(directory, maxname), maxindex)
2715 return (None, None)
2716 return (None, None)
2716
2717
2717 def savename(path):
2718 def savename(path):
2718 (last, index) = lastsavename(path)
2719 (last, index) = lastsavename(path)
2719 if last is None:
2720 if last is None:
2720 index = 0
2721 index = 0
2721 newpath = path + ".%d" % (index + 1)
2722 newpath = path + ".%d" % (index + 1)
2722 return newpath
2723 return newpath
2723
2724
2724 @command("^qpush",
2725 @command("^qpush",
2725 [('', 'keep-changes', None,
2726 [('', 'keep-changes', None,
2726 _('tolerate non-conflicting local changes')),
2727 _('tolerate non-conflicting local changes')),
2727 ('f', 'force', None, _('apply on top of local changes')),
2728 ('f', 'force', None, _('apply on top of local changes')),
2728 ('e', 'exact', None,
2729 ('e', 'exact', None,
2729 _('apply the target patch to its recorded parent')),
2730 _('apply the target patch to its recorded parent')),
2730 ('l', 'list', None, _('list patch name in commit text')),
2731 ('l', 'list', None, _('list patch name in commit text')),
2731 ('a', 'all', None, _('apply all patches')),
2732 ('a', 'all', None, _('apply all patches')),
2732 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2733 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2733 ('n', 'name', '',
2734 ('n', 'name', '',
2734 _('merge queue name (DEPRECATED)'), _('NAME')),
2735 _('merge queue name (DEPRECATED)'), _('NAME')),
2735 ('', 'move', None,
2736 ('', 'move', None,
2736 _('reorder patch series and apply only the patch')),
2737 _('reorder patch series and apply only the patch')),
2737 ('', 'no-backup', None, _('do not save backup copies of files'))],
2738 ('', 'no-backup', None, _('do not save backup copies of files'))],
2738 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2739 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2739 def push(ui, repo, patch=None, **opts):
2740 def push(ui, repo, patch=None, **opts):
2740 """push the next patch onto the stack
2741 """push the next patch onto the stack
2741
2742
2742 By default, abort if the working directory contains uncommitted
2743 By default, abort if the working directory contains uncommitted
2743 changes. With --keep-changes, abort only if the uncommitted files
2744 changes. With --keep-changes, abort only if the uncommitted files
2744 overlap with patched files. With -f/--force, backup and patch over
2745 overlap with patched files. With -f/--force, backup and patch over
2745 uncommitted changes.
2746 uncommitted changes.
2746
2747
2747 Return 0 on success.
2748 Return 0 on success.
2748 """
2749 """
2749 q = repo.mq
2750 q = repo.mq
2750 mergeq = None
2751 mergeq = None
2751
2752
2752 opts = fixkeepchangesopts(ui, opts)
2753 opts = fixkeepchangesopts(ui, opts)
2753 if opts.get('merge'):
2754 if opts.get('merge'):
2754 if opts.get('name'):
2755 if opts.get('name'):
2755 newpath = repo.join(opts.get('name'))
2756 newpath = repo.join(opts.get('name'))
2756 else:
2757 else:
2757 newpath, i = lastsavename(q.path)
2758 newpath, i = lastsavename(q.path)
2758 if not newpath:
2759 if not newpath:
2759 ui.warn(_("no saved queues found, please use -n\n"))
2760 ui.warn(_("no saved queues found, please use -n\n"))
2760 return 1
2761 return 1
2761 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2762 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2762 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2763 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2763 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2764 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2764 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2765 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2765 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2766 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2766 keepchanges=opts.get('keep_changes'))
2767 keepchanges=opts.get('keep_changes'))
2767 return ret
2768 return ret
2768
2769
2769 @command("^qpop",
2770 @command("^qpop",
2770 [('a', 'all', None, _('pop all patches')),
2771 [('a', 'all', None, _('pop all patches')),
2771 ('n', 'name', '',
2772 ('n', 'name', '',
2772 _('queue name to pop (DEPRECATED)'), _('NAME')),
2773 _('queue name to pop (DEPRECATED)'), _('NAME')),
2773 ('', 'keep-changes', None,
2774 ('', 'keep-changes', None,
2774 _('tolerate non-conflicting local changes')),
2775 _('tolerate non-conflicting local changes')),
2775 ('f', 'force', None, _('forget any local changes to patched files')),
2776 ('f', 'force', None, _('forget any local changes to patched files')),
2776 ('', 'no-backup', None, _('do not save backup copies of files'))],
2777 ('', 'no-backup', None, _('do not save backup copies of files'))],
2777 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2778 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2778 def pop(ui, repo, patch=None, **opts):
2779 def pop(ui, repo, patch=None, **opts):
2779 """pop the current patch off the stack
2780 """pop the current patch off the stack
2780
2781
2781 Without argument, pops off the top of the patch stack. If given a
2782 Without argument, pops off the top of the patch stack. If given a
2782 patch name, keeps popping off patches until the named patch is at
2783 patch name, keeps popping off patches until the named patch is at
2783 the top of the stack.
2784 the top of the stack.
2784
2785
2785 By default, abort if the working directory contains uncommitted
2786 By default, abort if the working directory contains uncommitted
2786 changes. With --keep-changes, abort only if the uncommitted files
2787 changes. With --keep-changes, abort only if the uncommitted files
2787 overlap with patched files. With -f/--force, backup and discard
2788 overlap with patched files. With -f/--force, backup and discard
2788 changes made to such files.
2789 changes made to such files.
2789
2790
2790 Return 0 on success.
2791 Return 0 on success.
2791 """
2792 """
2792 opts = fixkeepchangesopts(ui, opts)
2793 opts = fixkeepchangesopts(ui, opts)
2793 localupdate = True
2794 localupdate = True
2794 if opts.get('name'):
2795 if opts.get('name'):
2795 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2796 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2796 ui.warn(_('using patch queue: %s\n') % q.path)
2797 ui.warn(_('using patch queue: %s\n') % q.path)
2797 localupdate = False
2798 localupdate = False
2798 else:
2799 else:
2799 q = repo.mq
2800 q = repo.mq
2800 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2801 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2801 all=opts.get('all'), nobackup=opts.get('no_backup'),
2802 all=opts.get('all'), nobackup=opts.get('no_backup'),
2802 keepchanges=opts.get('keep_changes'))
2803 keepchanges=opts.get('keep_changes'))
2803 q.savedirty()
2804 q.savedirty()
2804 return ret
2805 return ret
2805
2806
2806 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2807 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2807 def rename(ui, repo, patch, name=None, **opts):
2808 def rename(ui, repo, patch, name=None, **opts):
2808 """rename a patch
2809 """rename a patch
2809
2810
2810 With one argument, renames the current patch to PATCH1.
2811 With one argument, renames the current patch to PATCH1.
2811 With two arguments, renames PATCH1 to PATCH2.
2812 With two arguments, renames PATCH1 to PATCH2.
2812
2813
2813 Returns 0 on success."""
2814 Returns 0 on success."""
2814 q = repo.mq
2815 q = repo.mq
2815 if not name:
2816 if not name:
2816 name = patch
2817 name = patch
2817 patch = None
2818 patch = None
2818
2819
2819 if patch:
2820 if patch:
2820 patch = q.lookup(patch)
2821 patch = q.lookup(patch)
2821 else:
2822 else:
2822 if not q.applied:
2823 if not q.applied:
2823 ui.write(_('no patches applied\n'))
2824 ui.write(_('no patches applied\n'))
2824 return
2825 return
2825 patch = q.lookup('qtip')
2826 patch = q.lookup('qtip')
2826 absdest = q.join(name)
2827 absdest = q.join(name)
2827 if os.path.isdir(absdest):
2828 if os.path.isdir(absdest):
2828 name = normname(os.path.join(name, os.path.basename(patch)))
2829 name = normname(os.path.join(name, os.path.basename(patch)))
2829 absdest = q.join(name)
2830 absdest = q.join(name)
2830 q.checkpatchname(name)
2831 q.checkpatchname(name)
2831
2832
2832 ui.note(_('renaming %s to %s\n') % (patch, name))
2833 ui.note(_('renaming %s to %s\n') % (patch, name))
2833 i = q.findseries(patch)
2834 i = q.findseries(patch)
2834 guards = q.guard_re.findall(q.fullseries[i])
2835 guards = q.guard_re.findall(q.fullseries[i])
2835 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2836 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2836 q.parseseries()
2837 q.parseseries()
2837 q.seriesdirty = True
2838 q.seriesdirty = True
2838
2839
2839 info = q.isapplied(patch)
2840 info = q.isapplied(patch)
2840 if info:
2841 if info:
2841 q.applied[info[0]] = statusentry(info[1], name)
2842 q.applied[info[0]] = statusentry(info[1], name)
2842 q.applieddirty = True
2843 q.applieddirty = True
2843
2844
2844 destdir = os.path.dirname(absdest)
2845 destdir = os.path.dirname(absdest)
2845 if not os.path.isdir(destdir):
2846 if not os.path.isdir(destdir):
2846 os.makedirs(destdir)
2847 os.makedirs(destdir)
2847 util.rename(q.join(patch), absdest)
2848 util.rename(q.join(patch), absdest)
2848 r = q.qrepo()
2849 r = q.qrepo()
2849 if r and patch in r.dirstate:
2850 if r and patch in r.dirstate:
2850 wctx = r[None]
2851 wctx = r[None]
2851 wlock = r.wlock()
2852 wlock = r.wlock()
2852 try:
2853 try:
2853 if r.dirstate[patch] == 'a':
2854 if r.dirstate[patch] == 'a':
2854 r.dirstate.drop(patch)
2855 r.dirstate.drop(patch)
2855 r.dirstate.add(name)
2856 r.dirstate.add(name)
2856 else:
2857 else:
2857 wctx.copy(patch, name)
2858 wctx.copy(patch, name)
2858 wctx.forget([patch])
2859 wctx.forget([patch])
2859 finally:
2860 finally:
2860 wlock.release()
2861 wlock.release()
2861
2862
2862 q.savedirty()
2863 q.savedirty()
2863
2864
2864 @command("qrestore",
2865 @command("qrestore",
2865 [('d', 'delete', None, _('delete save entry')),
2866 [('d', 'delete', None, _('delete save entry')),
2866 ('u', 'update', None, _('update queue working directory'))],
2867 ('u', 'update', None, _('update queue working directory'))],
2867 _('hg qrestore [-d] [-u] REV'))
2868 _('hg qrestore [-d] [-u] REV'))
2868 def restore(ui, repo, rev, **opts):
2869 def restore(ui, repo, rev, **opts):
2869 """restore the queue state saved by a revision (DEPRECATED)
2870 """restore the queue state saved by a revision (DEPRECATED)
2870
2871
2871 This command is deprecated, use :hg:`rebase` instead."""
2872 This command is deprecated, use :hg:`rebase` instead."""
2872 rev = repo.lookup(rev)
2873 rev = repo.lookup(rev)
2873 q = repo.mq
2874 q = repo.mq
2874 q.restore(repo, rev, delete=opts.get('delete'),
2875 q.restore(repo, rev, delete=opts.get('delete'),
2875 qupdate=opts.get('update'))
2876 qupdate=opts.get('update'))
2876 q.savedirty()
2877 q.savedirty()
2877 return 0
2878 return 0
2878
2879
2879 @command("qsave",
2880 @command("qsave",
2880 [('c', 'copy', None, _('copy patch directory')),
2881 [('c', 'copy', None, _('copy patch directory')),
2881 ('n', 'name', '',
2882 ('n', 'name', '',
2882 _('copy directory name'), _('NAME')),
2883 _('copy directory name'), _('NAME')),
2883 ('e', 'empty', None, _('clear queue status file')),
2884 ('e', 'empty', None, _('clear queue status file')),
2884 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2885 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2885 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2886 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2886 def save(ui, repo, **opts):
2887 def save(ui, repo, **opts):
2887 """save current queue state (DEPRECATED)
2888 """save current queue state (DEPRECATED)
2888
2889
2889 This command is deprecated, use :hg:`rebase` instead."""
2890 This command is deprecated, use :hg:`rebase` instead."""
2890 q = repo.mq
2891 q = repo.mq
2891 message = cmdutil.logmessage(ui, opts)
2892 message = cmdutil.logmessage(ui, opts)
2892 ret = q.save(repo, msg=message)
2893 ret = q.save(repo, msg=message)
2893 if ret:
2894 if ret:
2894 return ret
2895 return ret
2895 q.savedirty() # save to .hg/patches before copying
2896 q.savedirty() # save to .hg/patches before copying
2896 if opts.get('copy'):
2897 if opts.get('copy'):
2897 path = q.path
2898 path = q.path
2898 if opts.get('name'):
2899 if opts.get('name'):
2899 newpath = os.path.join(q.basepath, opts.get('name'))
2900 newpath = os.path.join(q.basepath, opts.get('name'))
2900 if os.path.exists(newpath):
2901 if os.path.exists(newpath):
2901 if not os.path.isdir(newpath):
2902 if not os.path.isdir(newpath):
2902 raise util.Abort(_('destination %s exists and is not '
2903 raise util.Abort(_('destination %s exists and is not '
2903 'a directory') % newpath)
2904 'a directory') % newpath)
2904 if not opts.get('force'):
2905 if not opts.get('force'):
2905 raise util.Abort(_('destination %s exists, '
2906 raise util.Abort(_('destination %s exists, '
2906 'use -f to force') % newpath)
2907 'use -f to force') % newpath)
2907 else:
2908 else:
2908 newpath = savename(path)
2909 newpath = savename(path)
2909 ui.warn(_("copy %s to %s\n") % (path, newpath))
2910 ui.warn(_("copy %s to %s\n") % (path, newpath))
2910 util.copyfiles(path, newpath)
2911 util.copyfiles(path, newpath)
2911 if opts.get('empty'):
2912 if opts.get('empty'):
2912 del q.applied[:]
2913 del q.applied[:]
2913 q.applieddirty = True
2914 q.applieddirty = True
2914 q.savedirty()
2915 q.savedirty()
2915 return 0
2916 return 0
2916
2917
2917
2918
2918 @command("qselect",
2919 @command("qselect",
2919 [('n', 'none', None, _('disable all guards')),
2920 [('n', 'none', None, _('disable all guards')),
2920 ('s', 'series', None, _('list all guards in series file')),
2921 ('s', 'series', None, _('list all guards in series file')),
2921 ('', 'pop', None, _('pop to before first guarded applied patch')),
2922 ('', 'pop', None, _('pop to before first guarded applied patch')),
2922 ('', 'reapply', None, _('pop, then reapply patches'))],
2923 ('', 'reapply', None, _('pop, then reapply patches'))],
2923 _('hg qselect [OPTION]... [GUARD]...'))
2924 _('hg qselect [OPTION]... [GUARD]...'))
2924 def select(ui, repo, *args, **opts):
2925 def select(ui, repo, *args, **opts):
2925 '''set or print guarded patches to push
2926 '''set or print guarded patches to push
2926
2927
2927 Use the :hg:`qguard` command to set or print guards on patch, then use
2928 Use the :hg:`qguard` command to set or print guards on patch, then use
2928 qselect to tell mq which guards to use. A patch will be pushed if
2929 qselect to tell mq which guards to use. A patch will be pushed if
2929 it has no guards or any positive guards match the currently
2930 it has no guards or any positive guards match the currently
2930 selected guard, but will not be pushed if any negative guards
2931 selected guard, but will not be pushed if any negative guards
2931 match the current guard. For example::
2932 match the current guard. For example::
2932
2933
2933 qguard foo.patch -- -stable (negative guard)
2934 qguard foo.patch -- -stable (negative guard)
2934 qguard bar.patch +stable (positive guard)
2935 qguard bar.patch +stable (positive guard)
2935 qselect stable
2936 qselect stable
2936
2937
2937 This activates the "stable" guard. mq will skip foo.patch (because
2938 This activates the "stable" guard. mq will skip foo.patch (because
2938 it has a negative match) but push bar.patch (because it has a
2939 it has a negative match) but push bar.patch (because it has a
2939 positive match).
2940 positive match).
2940
2941
2941 With no arguments, prints the currently active guards.
2942 With no arguments, prints the currently active guards.
2942 With one argument, sets the active guard.
2943 With one argument, sets the active guard.
2943
2944
2944 Use -n/--none to deactivate guards (no other arguments needed).
2945 Use -n/--none to deactivate guards (no other arguments needed).
2945 When no guards are active, patches with positive guards are
2946 When no guards are active, patches with positive guards are
2946 skipped and patches with negative guards are pushed.
2947 skipped and patches with negative guards are pushed.
2947
2948
2948 qselect can change the guards on applied patches. It does not pop
2949 qselect can change the guards on applied patches. It does not pop
2949 guarded patches by default. Use --pop to pop back to the last
2950 guarded patches by default. Use --pop to pop back to the last
2950 applied patch that is not guarded. Use --reapply (which implies
2951 applied patch that is not guarded. Use --reapply (which implies
2951 --pop) to push back to the current patch afterwards, but skip
2952 --pop) to push back to the current patch afterwards, but skip
2952 guarded patches.
2953 guarded patches.
2953
2954
2954 Use -s/--series to print a list of all guards in the series file
2955 Use -s/--series to print a list of all guards in the series file
2955 (no other arguments needed). Use -v for more information.
2956 (no other arguments needed). Use -v for more information.
2956
2957
2957 Returns 0 on success.'''
2958 Returns 0 on success.'''
2958
2959
2959 q = repo.mq
2960 q = repo.mq
2960 guards = q.active()
2961 guards = q.active()
2961 if args or opts.get('none'):
2962 if args or opts.get('none'):
2962 old_unapplied = q.unapplied(repo)
2963 old_unapplied = q.unapplied(repo)
2963 old_guarded = [i for i in xrange(len(q.applied)) if
2964 old_guarded = [i for i in xrange(len(q.applied)) if
2964 not q.pushable(i)[0]]
2965 not q.pushable(i)[0]]
2965 q.setactive(args)
2966 q.setactive(args)
2966 q.savedirty()
2967 q.savedirty()
2967 if not args:
2968 if not args:
2968 ui.status(_('guards deactivated\n'))
2969 ui.status(_('guards deactivated\n'))
2969 if not opts.get('pop') and not opts.get('reapply'):
2970 if not opts.get('pop') and not opts.get('reapply'):
2970 unapplied = q.unapplied(repo)
2971 unapplied = q.unapplied(repo)
2971 guarded = [i for i in xrange(len(q.applied))
2972 guarded = [i for i in xrange(len(q.applied))
2972 if not q.pushable(i)[0]]
2973 if not q.pushable(i)[0]]
2973 if len(unapplied) != len(old_unapplied):
2974 if len(unapplied) != len(old_unapplied):
2974 ui.status(_('number of unguarded, unapplied patches has '
2975 ui.status(_('number of unguarded, unapplied patches has '
2975 'changed from %d to %d\n') %
2976 'changed from %d to %d\n') %
2976 (len(old_unapplied), len(unapplied)))
2977 (len(old_unapplied), len(unapplied)))
2977 if len(guarded) != len(old_guarded):
2978 if len(guarded) != len(old_guarded):
2978 ui.status(_('number of guarded, applied patches has changed '
2979 ui.status(_('number of guarded, applied patches has changed '
2979 'from %d to %d\n') %
2980 'from %d to %d\n') %
2980 (len(old_guarded), len(guarded)))
2981 (len(old_guarded), len(guarded)))
2981 elif opts.get('series'):
2982 elif opts.get('series'):
2982 guards = {}
2983 guards = {}
2983 noguards = 0
2984 noguards = 0
2984 for gs in q.seriesguards:
2985 for gs in q.seriesguards:
2985 if not gs:
2986 if not gs:
2986 noguards += 1
2987 noguards += 1
2987 for g in gs:
2988 for g in gs:
2988 guards.setdefault(g, 0)
2989 guards.setdefault(g, 0)
2989 guards[g] += 1
2990 guards[g] += 1
2990 if ui.verbose:
2991 if ui.verbose:
2991 guards['NONE'] = noguards
2992 guards['NONE'] = noguards
2992 guards = guards.items()
2993 guards = guards.items()
2993 guards.sort(key=lambda x: x[0][1:])
2994 guards.sort(key=lambda x: x[0][1:])
2994 if guards:
2995 if guards:
2995 ui.note(_('guards in series file:\n'))
2996 ui.note(_('guards in series file:\n'))
2996 for guard, count in guards:
2997 for guard, count in guards:
2997 ui.note('%2d ' % count)
2998 ui.note('%2d ' % count)
2998 ui.write(guard, '\n')
2999 ui.write(guard, '\n')
2999 else:
3000 else:
3000 ui.note(_('no guards in series file\n'))
3001 ui.note(_('no guards in series file\n'))
3001 else:
3002 else:
3002 if guards:
3003 if guards:
3003 ui.note(_('active guards:\n'))
3004 ui.note(_('active guards:\n'))
3004 for g in guards:
3005 for g in guards:
3005 ui.write(g, '\n')
3006 ui.write(g, '\n')
3006 else:
3007 else:
3007 ui.write(_('no active guards\n'))
3008 ui.write(_('no active guards\n'))
3008 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3009 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3009 popped = False
3010 popped = False
3010 if opts.get('pop') or opts.get('reapply'):
3011 if opts.get('pop') or opts.get('reapply'):
3011 for i in xrange(len(q.applied)):
3012 for i in xrange(len(q.applied)):
3012 pushable, reason = q.pushable(i)
3013 pushable, reason = q.pushable(i)
3013 if not pushable:
3014 if not pushable:
3014 ui.status(_('popping guarded patches\n'))
3015 ui.status(_('popping guarded patches\n'))
3015 popped = True
3016 popped = True
3016 if i == 0:
3017 if i == 0:
3017 q.pop(repo, all=True)
3018 q.pop(repo, all=True)
3018 else:
3019 else:
3019 q.pop(repo, str(i - 1))
3020 q.pop(repo, str(i - 1))
3020 break
3021 break
3021 if popped:
3022 if popped:
3022 try:
3023 try:
3023 if reapply:
3024 if reapply:
3024 ui.status(_('reapplying unguarded patches\n'))
3025 ui.status(_('reapplying unguarded patches\n'))
3025 q.push(repo, reapply)
3026 q.push(repo, reapply)
3026 finally:
3027 finally:
3027 q.savedirty()
3028 q.savedirty()
3028
3029
3029 @command("qfinish",
3030 @command("qfinish",
3030 [('a', 'applied', None, _('finish all applied changesets'))],
3031 [('a', 'applied', None, _('finish all applied changesets'))],
3031 _('hg qfinish [-a] [REV]...'))
3032 _('hg qfinish [-a] [REV]...'))
3032 def finish(ui, repo, *revrange, **opts):
3033 def finish(ui, repo, *revrange, **opts):
3033 """move applied patches into repository history
3034 """move applied patches into repository history
3034
3035
3035 Finishes the specified revisions (corresponding to applied
3036 Finishes the specified revisions (corresponding to applied
3036 patches) by moving them out of mq control into regular repository
3037 patches) by moving them out of mq control into regular repository
3037 history.
3038 history.
3038
3039
3039 Accepts a revision range or the -a/--applied option. If --applied
3040 Accepts a revision range or the -a/--applied option. If --applied
3040 is specified, all applied mq revisions are removed from mq
3041 is specified, all applied mq revisions are removed from mq
3041 control. Otherwise, the given revisions must be at the base of the
3042 control. Otherwise, the given revisions must be at the base of the
3042 stack of applied patches.
3043 stack of applied patches.
3043
3044
3044 This can be especially useful if your changes have been applied to
3045 This can be especially useful if your changes have been applied to
3045 an upstream repository, or if you are about to push your changes
3046 an upstream repository, or if you are about to push your changes
3046 to upstream.
3047 to upstream.
3047
3048
3048 Returns 0 on success.
3049 Returns 0 on success.
3049 """
3050 """
3050 if not opts.get('applied') and not revrange:
3051 if not opts.get('applied') and not revrange:
3051 raise util.Abort(_('no revisions specified'))
3052 raise util.Abort(_('no revisions specified'))
3052 elif opts.get('applied'):
3053 elif opts.get('applied'):
3053 revrange = ('qbase::qtip',) + revrange
3054 revrange = ('qbase::qtip',) + revrange
3054
3055
3055 q = repo.mq
3056 q = repo.mq
3056 if not q.applied:
3057 if not q.applied:
3057 ui.status(_('no patches applied\n'))
3058 ui.status(_('no patches applied\n'))
3058 return 0
3059 return 0
3059
3060
3060 revs = scmutil.revrange(repo, revrange)
3061 revs = scmutil.revrange(repo, revrange)
3061 if repo['.'].rev() in revs and repo[None].files():
3062 if repo['.'].rev() in revs and repo[None].files():
3062 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3063 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3063 # queue.finish may changes phases but leave the responsibility to lock the
3064 # queue.finish may changes phases but leave the responsibility to lock the
3064 # repo to the caller to avoid deadlock with wlock. This command code is
3065 # repo to the caller to avoid deadlock with wlock. This command code is
3065 # responsibility for this locking.
3066 # responsibility for this locking.
3066 lock = repo.lock()
3067 lock = repo.lock()
3067 try:
3068 try:
3068 q.finish(repo, revs)
3069 q.finish(repo, revs)
3069 q.savedirty()
3070 q.savedirty()
3070 finally:
3071 finally:
3071 lock.release()
3072 lock.release()
3072 return 0
3073 return 0
3073
3074
3074 @command("qqueue",
3075 @command("qqueue",
3075 [('l', 'list', False, _('list all available queues')),
3076 [('l', 'list', False, _('list all available queues')),
3076 ('', 'active', False, _('print name of active queue')),
3077 ('', 'active', False, _('print name of active queue')),
3077 ('c', 'create', False, _('create new queue')),
3078 ('c', 'create', False, _('create new queue')),
3078 ('', 'rename', False, _('rename active queue')),
3079 ('', 'rename', False, _('rename active queue')),
3079 ('', 'delete', False, _('delete reference to queue')),
3080 ('', 'delete', False, _('delete reference to queue')),
3080 ('', 'purge', False, _('delete queue, and remove patch dir')),
3081 ('', 'purge', False, _('delete queue, and remove patch dir')),
3081 ],
3082 ],
3082 _('[OPTION] [QUEUE]'))
3083 _('[OPTION] [QUEUE]'))
3083 def qqueue(ui, repo, name=None, **opts):
3084 def qqueue(ui, repo, name=None, **opts):
3084 '''manage multiple patch queues
3085 '''manage multiple patch queues
3085
3086
3086 Supports switching between different patch queues, as well as creating
3087 Supports switching between different patch queues, as well as creating
3087 new patch queues and deleting existing ones.
3088 new patch queues and deleting existing ones.
3088
3089
3089 Omitting a queue name or specifying -l/--list will show you the registered
3090 Omitting a queue name or specifying -l/--list will show you the registered
3090 queues - by default the "normal" patches queue is registered. The currently
3091 queues - by default the "normal" patches queue is registered. The currently
3091 active queue will be marked with "(active)". Specifying --active will print
3092 active queue will be marked with "(active)". Specifying --active will print
3092 only the name of the active queue.
3093 only the name of the active queue.
3093
3094
3094 To create a new queue, use -c/--create. The queue is automatically made
3095 To create a new queue, use -c/--create. The queue is automatically made
3095 active, except in the case where there are applied patches from the
3096 active, except in the case where there are applied patches from the
3096 currently active queue in the repository. Then the queue will only be
3097 currently active queue in the repository. Then the queue will only be
3097 created and switching will fail.
3098 created and switching will fail.
3098
3099
3099 To delete an existing queue, use --delete. You cannot delete the currently
3100 To delete an existing queue, use --delete. You cannot delete the currently
3100 active queue.
3101 active queue.
3101
3102
3102 Returns 0 on success.
3103 Returns 0 on success.
3103 '''
3104 '''
3104 q = repo.mq
3105 q = repo.mq
3105 _defaultqueue = 'patches'
3106 _defaultqueue = 'patches'
3106 _allqueues = 'patches.queues'
3107 _allqueues = 'patches.queues'
3107 _activequeue = 'patches.queue'
3108 _activequeue = 'patches.queue'
3108
3109
3109 def _getcurrent():
3110 def _getcurrent():
3110 cur = os.path.basename(q.path)
3111 cur = os.path.basename(q.path)
3111 if cur.startswith('patches-'):
3112 if cur.startswith('patches-'):
3112 cur = cur[8:]
3113 cur = cur[8:]
3113 return cur
3114 return cur
3114
3115
3115 def _noqueues():
3116 def _noqueues():
3116 try:
3117 try:
3117 fh = repo.opener(_allqueues, 'r')
3118 fh = repo.opener(_allqueues, 'r')
3118 fh.close()
3119 fh.close()
3119 except IOError:
3120 except IOError:
3120 return True
3121 return True
3121
3122
3122 return False
3123 return False
3123
3124
3124 def _getqueues():
3125 def _getqueues():
3125 current = _getcurrent()
3126 current = _getcurrent()
3126
3127
3127 try:
3128 try:
3128 fh = repo.opener(_allqueues, 'r')
3129 fh = repo.opener(_allqueues, 'r')
3129 queues = [queue.strip() for queue in fh if queue.strip()]
3130 queues = [queue.strip() for queue in fh if queue.strip()]
3130 fh.close()
3131 fh.close()
3131 if current not in queues:
3132 if current not in queues:
3132 queues.append(current)
3133 queues.append(current)
3133 except IOError:
3134 except IOError:
3134 queues = [_defaultqueue]
3135 queues = [_defaultqueue]
3135
3136
3136 return sorted(queues)
3137 return sorted(queues)
3137
3138
3138 def _setactive(name):
3139 def _setactive(name):
3139 if q.applied:
3140 if q.applied:
3140 raise util.Abort(_('new queue created, but cannot make active '
3141 raise util.Abort(_('new queue created, but cannot make active '
3141 'as patches are applied'))
3142 'as patches are applied'))
3142 _setactivenocheck(name)
3143 _setactivenocheck(name)
3143
3144
3144 def _setactivenocheck(name):
3145 def _setactivenocheck(name):
3145 fh = repo.opener(_activequeue, 'w')
3146 fh = repo.opener(_activequeue, 'w')
3146 if name != 'patches':
3147 if name != 'patches':
3147 fh.write(name)
3148 fh.write(name)
3148 fh.close()
3149 fh.close()
3149
3150
3150 def _addqueue(name):
3151 def _addqueue(name):
3151 fh = repo.opener(_allqueues, 'a')
3152 fh = repo.opener(_allqueues, 'a')
3152 fh.write('%s\n' % (name,))
3153 fh.write('%s\n' % (name,))
3153 fh.close()
3154 fh.close()
3154
3155
3155 def _queuedir(name):
3156 def _queuedir(name):
3156 if name == 'patches':
3157 if name == 'patches':
3157 return repo.join('patches')
3158 return repo.join('patches')
3158 else:
3159 else:
3159 return repo.join('patches-' + name)
3160 return repo.join('patches-' + name)
3160
3161
3161 def _validname(name):
3162 def _validname(name):
3162 for n in name:
3163 for n in name:
3163 if n in ':\\/.':
3164 if n in ':\\/.':
3164 return False
3165 return False
3165 return True
3166 return True
3166
3167
3167 def _delete(name):
3168 def _delete(name):
3168 if name not in existing:
3169 if name not in existing:
3169 raise util.Abort(_('cannot delete queue that does not exist'))
3170 raise util.Abort(_('cannot delete queue that does not exist'))
3170
3171
3171 current = _getcurrent()
3172 current = _getcurrent()
3172
3173
3173 if name == current:
3174 if name == current:
3174 raise util.Abort(_('cannot delete currently active queue'))
3175 raise util.Abort(_('cannot delete currently active queue'))
3175
3176
3176 fh = repo.opener('patches.queues.new', 'w')
3177 fh = repo.opener('patches.queues.new', 'w')
3177 for queue in existing:
3178 for queue in existing:
3178 if queue == name:
3179 if queue == name:
3179 continue
3180 continue
3180 fh.write('%s\n' % (queue,))
3181 fh.write('%s\n' % (queue,))
3181 fh.close()
3182 fh.close()
3182 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3183 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3183
3184
3184 if not name or opts.get('list') or opts.get('active'):
3185 if not name or opts.get('list') or opts.get('active'):
3185 current = _getcurrent()
3186 current = _getcurrent()
3186 if opts.get('active'):
3187 if opts.get('active'):
3187 ui.write('%s\n' % (current,))
3188 ui.write('%s\n' % (current,))
3188 return
3189 return
3189 for queue in _getqueues():
3190 for queue in _getqueues():
3190 ui.write('%s' % (queue,))
3191 ui.write('%s' % (queue,))
3191 if queue == current and not ui.quiet:
3192 if queue == current and not ui.quiet:
3192 ui.write(_(' (active)\n'))
3193 ui.write(_(' (active)\n'))
3193 else:
3194 else:
3194 ui.write('\n')
3195 ui.write('\n')
3195 return
3196 return
3196
3197
3197 if not _validname(name):
3198 if not _validname(name):
3198 raise util.Abort(
3199 raise util.Abort(
3199 _('invalid queue name, may not contain the characters ":\\/."'))
3200 _('invalid queue name, may not contain the characters ":\\/."'))
3200
3201
3201 existing = _getqueues()
3202 existing = _getqueues()
3202
3203
3203 if opts.get('create'):
3204 if opts.get('create'):
3204 if name in existing:
3205 if name in existing:
3205 raise util.Abort(_('queue "%s" already exists') % name)
3206 raise util.Abort(_('queue "%s" already exists') % name)
3206 if _noqueues():
3207 if _noqueues():
3207 _addqueue(_defaultqueue)
3208 _addqueue(_defaultqueue)
3208 _addqueue(name)
3209 _addqueue(name)
3209 _setactive(name)
3210 _setactive(name)
3210 elif opts.get('rename'):
3211 elif opts.get('rename'):
3211 current = _getcurrent()
3212 current = _getcurrent()
3212 if name == current:
3213 if name == current:
3213 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3214 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3214 if name in existing:
3215 if name in existing:
3215 raise util.Abort(_('queue "%s" already exists') % name)
3216 raise util.Abort(_('queue "%s" already exists') % name)
3216
3217
3217 olddir = _queuedir(current)
3218 olddir = _queuedir(current)
3218 newdir = _queuedir(name)
3219 newdir = _queuedir(name)
3219
3220
3220 if os.path.exists(newdir):
3221 if os.path.exists(newdir):
3221 raise util.Abort(_('non-queue directory "%s" already exists') %
3222 raise util.Abort(_('non-queue directory "%s" already exists') %
3222 newdir)
3223 newdir)
3223
3224
3224 fh = repo.opener('patches.queues.new', 'w')
3225 fh = repo.opener('patches.queues.new', 'w')
3225 for queue in existing:
3226 for queue in existing:
3226 if queue == current:
3227 if queue == current:
3227 fh.write('%s\n' % (name,))
3228 fh.write('%s\n' % (name,))
3228 if os.path.exists(olddir):
3229 if os.path.exists(olddir):
3229 util.rename(olddir, newdir)
3230 util.rename(olddir, newdir)
3230 else:
3231 else:
3231 fh.write('%s\n' % (queue,))
3232 fh.write('%s\n' % (queue,))
3232 fh.close()
3233 fh.close()
3233 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3234 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3234 _setactivenocheck(name)
3235 _setactivenocheck(name)
3235 elif opts.get('delete'):
3236 elif opts.get('delete'):
3236 _delete(name)
3237 _delete(name)
3237 elif opts.get('purge'):
3238 elif opts.get('purge'):
3238 if name in existing:
3239 if name in existing:
3239 _delete(name)
3240 _delete(name)
3240 qdir = _queuedir(name)
3241 qdir = _queuedir(name)
3241 if os.path.exists(qdir):
3242 if os.path.exists(qdir):
3242 shutil.rmtree(qdir)
3243 shutil.rmtree(qdir)
3243 else:
3244 else:
3244 if name not in existing:
3245 if name not in existing:
3245 raise util.Abort(_('use --create to create a new queue'))
3246 raise util.Abort(_('use --create to create a new queue'))
3246 _setactive(name)
3247 _setactive(name)
3247
3248
3248 def mqphasedefaults(repo, roots):
3249 def mqphasedefaults(repo, roots):
3249 """callback used to set mq changeset as secret when no phase data exists"""
3250 """callback used to set mq changeset as secret when no phase data exists"""
3250 if repo.mq.applied:
3251 if repo.mq.applied:
3251 if repo.ui.configbool('mq', 'secret', False):
3252 if repo.ui.configbool('mq', 'secret', False):
3252 mqphase = phases.secret
3253 mqphase = phases.secret
3253 else:
3254 else:
3254 mqphase = phases.draft
3255 mqphase = phases.draft
3255 qbase = repo[repo.mq.applied[0].node]
3256 qbase = repo[repo.mq.applied[0].node]
3256 roots[mqphase].add(qbase.node())
3257 roots[mqphase].add(qbase.node())
3257 return roots
3258 return roots
3258
3259
3259 def reposetup(ui, repo):
3260 def reposetup(ui, repo):
3260 class mqrepo(repo.__class__):
3261 class mqrepo(repo.__class__):
3261 @localrepo.unfilteredpropertycache
3262 @localrepo.unfilteredpropertycache
3262 def mq(self):
3263 def mq(self):
3263 return queue(self.ui, self.baseui, self.path)
3264 return queue(self.ui, self.baseui, self.path)
3264
3265
3265 def abortifwdirpatched(self, errmsg, force=False):
3266 def abortifwdirpatched(self, errmsg, force=False):
3266 if self.mq.applied and self.mq.checkapplied and not force:
3267 if self.mq.applied and self.mq.checkapplied and not force:
3267 parents = self.dirstate.parents()
3268 parents = self.dirstate.parents()
3268 patches = [s.node for s in self.mq.applied]
3269 patches = [s.node for s in self.mq.applied]
3269 if parents[0] in patches or parents[1] in patches:
3270 if parents[0] in patches or parents[1] in patches:
3270 raise util.Abort(errmsg)
3271 raise util.Abort(errmsg)
3271
3272
3272 def commit(self, text="", user=None, date=None, match=None,
3273 def commit(self, text="", user=None, date=None, match=None,
3273 force=False, editor=False, extra={}):
3274 force=False, editor=False, extra={}):
3274 self.abortifwdirpatched(
3275 self.abortifwdirpatched(
3275 _('cannot commit over an applied mq patch'),
3276 _('cannot commit over an applied mq patch'),
3276 force)
3277 force)
3277
3278
3278 return super(mqrepo, self).commit(text, user, date, match, force,
3279 return super(mqrepo, self).commit(text, user, date, match, force,
3279 editor, extra)
3280 editor, extra)
3280
3281
3281 def checkpush(self, force, revs):
3282 def checkpush(self, force, revs):
3282 if self.mq.applied and self.mq.checkapplied and not force:
3283 if self.mq.applied and self.mq.checkapplied and not force:
3283 outapplied = [e.node for e in self.mq.applied]
3284 outapplied = [e.node for e in self.mq.applied]
3284 if revs:
3285 if revs:
3285 # Assume applied patches have no non-patch descendants and
3286 # Assume applied patches have no non-patch descendants and
3286 # are not on remote already. Filtering any changeset not
3287 # are not on remote already. Filtering any changeset not
3287 # pushed.
3288 # pushed.
3288 heads = set(revs)
3289 heads = set(revs)
3289 for node in reversed(outapplied):
3290 for node in reversed(outapplied):
3290 if node in heads:
3291 if node in heads:
3291 break
3292 break
3292 else:
3293 else:
3293 outapplied.pop()
3294 outapplied.pop()
3294 # looking for pushed and shared changeset
3295 # looking for pushed and shared changeset
3295 for node in outapplied:
3296 for node in outapplied:
3296 if self[node].phase() < phases.secret:
3297 if self[node].phase() < phases.secret:
3297 raise util.Abort(_('source has mq patches applied'))
3298 raise util.Abort(_('source has mq patches applied'))
3298 # no non-secret patches pushed
3299 # no non-secret patches pushed
3299 super(mqrepo, self).checkpush(force, revs)
3300 super(mqrepo, self).checkpush(force, revs)
3300
3301
3301 def _findtags(self):
3302 def _findtags(self):
3302 '''augment tags from base class with patch tags'''
3303 '''augment tags from base class with patch tags'''
3303 result = super(mqrepo, self)._findtags()
3304 result = super(mqrepo, self)._findtags()
3304
3305
3305 q = self.mq
3306 q = self.mq
3306 if not q.applied:
3307 if not q.applied:
3307 return result
3308 return result
3308
3309
3309 mqtags = [(patch.node, patch.name) for patch in q.applied]
3310 mqtags = [(patch.node, patch.name) for patch in q.applied]
3310
3311
3311 try:
3312 try:
3312 # for now ignore filtering business
3313 # for now ignore filtering business
3313 self.unfiltered().changelog.rev(mqtags[-1][0])
3314 self.unfiltered().changelog.rev(mqtags[-1][0])
3314 except error.LookupError:
3315 except error.LookupError:
3315 self.ui.warn(_('mq status file refers to unknown node %s\n')
3316 self.ui.warn(_('mq status file refers to unknown node %s\n')
3316 % short(mqtags[-1][0]))
3317 % short(mqtags[-1][0]))
3317 return result
3318 return result
3318
3319
3319 # do not add fake tags for filtered revisions
3320 # do not add fake tags for filtered revisions
3320 included = self.changelog.hasnode
3321 included = self.changelog.hasnode
3321 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3322 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3322 if not mqtags:
3323 if not mqtags:
3323 return result
3324 return result
3324
3325
3325 mqtags.append((mqtags[-1][0], 'qtip'))
3326 mqtags.append((mqtags[-1][0], 'qtip'))
3326 mqtags.append((mqtags[0][0], 'qbase'))
3327 mqtags.append((mqtags[0][0], 'qbase'))
3327 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3328 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3328 tags = result[0]
3329 tags = result[0]
3329 for patch in mqtags:
3330 for patch in mqtags:
3330 if patch[1] in tags:
3331 if patch[1] in tags:
3331 self.ui.warn(_('tag %s overrides mq patch of the same '
3332 self.ui.warn(_('tag %s overrides mq patch of the same '
3332 'name\n') % patch[1])
3333 'name\n') % patch[1])
3333 else:
3334 else:
3334 tags[patch[1]] = patch[0]
3335 tags[patch[1]] = patch[0]
3335
3336
3336 return result
3337 return result
3337
3338
3338 if repo.local():
3339 if repo.local():
3339 repo.__class__ = mqrepo
3340 repo.__class__ = mqrepo
3340
3341
3341 repo._phasedefaults.append(mqphasedefaults)
3342 repo._phasedefaults.append(mqphasedefaults)
3342
3343
3343 def mqimport(orig, ui, repo, *args, **kwargs):
3344 def mqimport(orig, ui, repo, *args, **kwargs):
3344 if (util.safehasattr(repo, 'abortifwdirpatched')
3345 if (util.safehasattr(repo, 'abortifwdirpatched')
3345 and not kwargs.get('no_commit', False)):
3346 and not kwargs.get('no_commit', False)):
3346 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3347 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3347 kwargs.get('force'))
3348 kwargs.get('force'))
3348 return orig(ui, repo, *args, **kwargs)
3349 return orig(ui, repo, *args, **kwargs)
3349
3350
3350 def mqinit(orig, ui, *args, **kwargs):
3351 def mqinit(orig, ui, *args, **kwargs):
3351 mq = kwargs.pop('mq', None)
3352 mq = kwargs.pop('mq', None)
3352
3353
3353 if not mq:
3354 if not mq:
3354 return orig(ui, *args, **kwargs)
3355 return orig(ui, *args, **kwargs)
3355
3356
3356 if args:
3357 if args:
3357 repopath = args[0]
3358 repopath = args[0]
3358 if not hg.islocal(repopath):
3359 if not hg.islocal(repopath):
3359 raise util.Abort(_('only a local queue repository '
3360 raise util.Abort(_('only a local queue repository '
3360 'may be initialized'))
3361 'may be initialized'))
3361 else:
3362 else:
3362 repopath = cmdutil.findrepo(os.getcwd())
3363 repopath = cmdutil.findrepo(os.getcwd())
3363 if not repopath:
3364 if not repopath:
3364 raise util.Abort(_('there is no Mercurial repository here '
3365 raise util.Abort(_('there is no Mercurial repository here '
3365 '(.hg not found)'))
3366 '(.hg not found)'))
3366 repo = hg.repository(ui, repopath)
3367 repo = hg.repository(ui, repopath)
3367 return qinit(ui, repo, True)
3368 return qinit(ui, repo, True)
3368
3369
3369 def mqcommand(orig, ui, repo, *args, **kwargs):
3370 def mqcommand(orig, ui, repo, *args, **kwargs):
3370 """Add --mq option to operate on patch repository instead of main"""
3371 """Add --mq option to operate on patch repository instead of main"""
3371
3372
3372 # some commands do not like getting unknown options
3373 # some commands do not like getting unknown options
3373 mq = kwargs.pop('mq', None)
3374 mq = kwargs.pop('mq', None)
3374
3375
3375 if not mq:
3376 if not mq:
3376 return orig(ui, repo, *args, **kwargs)
3377 return orig(ui, repo, *args, **kwargs)
3377
3378
3378 q = repo.mq
3379 q = repo.mq
3379 r = q.qrepo()
3380 r = q.qrepo()
3380 if not r:
3381 if not r:
3381 raise util.Abort(_('no queue repository'))
3382 raise util.Abort(_('no queue repository'))
3382 return orig(r.ui, r, *args, **kwargs)
3383 return orig(r.ui, r, *args, **kwargs)
3383
3384
3384 def summaryhook(ui, repo):
3385 def summaryhook(ui, repo):
3385 q = repo.mq
3386 q = repo.mq
3386 m = []
3387 m = []
3387 a, u = len(q.applied), len(q.unapplied(repo))
3388 a, u = len(q.applied), len(q.unapplied(repo))
3388 if a:
3389 if a:
3389 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3390 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3390 if u:
3391 if u:
3391 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3392 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3392 if m:
3393 if m:
3393 # i18n: column positioning for "hg summary"
3394 # i18n: column positioning for "hg summary"
3394 ui.write(_("mq: %s\n") % ', '.join(m))
3395 ui.write(_("mq: %s\n") % ', '.join(m))
3395 else:
3396 else:
3396 # i18n: column positioning for "hg summary"
3397 # i18n: column positioning for "hg summary"
3397 ui.note(_("mq: (empty queue)\n"))
3398 ui.note(_("mq: (empty queue)\n"))
3398
3399
3399 def revsetmq(repo, subset, x):
3400 def revsetmq(repo, subset, x):
3400 """``mq()``
3401 """``mq()``
3401 Changesets managed by MQ.
3402 Changesets managed by MQ.
3402 """
3403 """
3403 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3404 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3404 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3405 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3405 return [r for r in subset if r in applied]
3406 return [r for r in subset if r in applied]
3406
3407
3407 # tell hggettext to extract docstrings from these functions:
3408 # tell hggettext to extract docstrings from these functions:
3408 i18nfunctions = [revsetmq]
3409 i18nfunctions = [revsetmq]
3409
3410
3410 def extsetup(ui):
3411 def extsetup(ui):
3411 # Ensure mq wrappers are called first, regardless of extension load order by
3412 # Ensure mq wrappers are called first, regardless of extension load order by
3412 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3413 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3413 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3414 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3414
3415
3415 extensions.wrapcommand(commands.table, 'import', mqimport)
3416 extensions.wrapcommand(commands.table, 'import', mqimport)
3416 cmdutil.summaryhooks.add('mq', summaryhook)
3417 cmdutil.summaryhooks.add('mq', summaryhook)
3417
3418
3418 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3419 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3419 entry[1].extend(mqopt)
3420 entry[1].extend(mqopt)
3420
3421
3421 nowrap = set(commands.norepo.split(" "))
3422 nowrap = set(commands.norepo.split(" "))
3422
3423
3423 def dotable(cmdtable):
3424 def dotable(cmdtable):
3424 for cmd in cmdtable.keys():
3425 for cmd in cmdtable.keys():
3425 cmd = cmdutil.parsealiases(cmd)[0]
3426 cmd = cmdutil.parsealiases(cmd)[0]
3426 if cmd in nowrap:
3427 if cmd in nowrap:
3427 continue
3428 continue
3428 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3429 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3429 entry[1].extend(mqopt)
3430 entry[1].extend(mqopt)
3430
3431
3431 dotable(commands.table)
3432 dotable(commands.table)
3432
3433
3433 for extname, extmodule in extensions.extensions():
3434 for extname, extmodule in extensions.extensions():
3434 if extmodule.__file__ != __file__:
3435 if extmodule.__file__ != __file__:
3435 dotable(getattr(extmodule, 'cmdtable', {}))
3436 dotable(getattr(extmodule, 'cmdtable', {}))
3436
3437
3437 revset.symbols['mq'] = revsetmq
3438 revset.symbols['mq'] = revsetmq
3438
3439
3439 colortable = {'qguard.negative': 'red',
3440 colortable = {'qguard.negative': 'red',
3440 'qguard.positive': 'yellow',
3441 'qguard.positive': 'yellow',
3441 'qguard.unguarded': 'green',
3442 'qguard.unguarded': 'green',
3442 'qseries.applied': 'blue bold underline',
3443 'qseries.applied': 'blue bold underline',
3443 'qseries.guarded': 'black bold',
3444 'qseries.guarded': 'black bold',
3444 'qseries.missing': 'red bold',
3445 'qseries.missing': 'red bold',
3445 'qseries.unapplied': 'black bold'}
3446 'qseries.unapplied': 'black bold'}
3446
3447
3447 commands.inferrepo += " qnew qrefresh qdiff qcommit"
3448 commands.inferrepo += " qnew qrefresh qdiff qcommit"
@@ -1,74 +1,75 b''
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''share a common history between several working directories'''
6 '''share a common history between several working directories'''
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial import hg, commands, util
9 from mercurial import hg, commands, util
10
10
11 testedwith = 'internal'
11 testedwith = 'internal'
12
12
13 def share(ui, source, dest=None, noupdate=False):
13 def share(ui, source, dest=None, noupdate=False):
14 """create a new shared repository
14 """create a new shared repository
15
15
16 Initialize a new repository and working directory that shares its
16 Initialize a new repository and working directory that shares its
17 history with another repository.
17 history with another repository.
18
18
19 .. note::
19 .. note::
20
20 using rollback or extensions that destroy/modify history (mq,
21 using rollback or extensions that destroy/modify history (mq,
21 rebase, etc.) can cause considerable confusion with shared
22 rebase, etc.) can cause considerable confusion with shared
22 clones. In particular, if two shared clones are both updated to
23 clones. In particular, if two shared clones are both updated to
23 the same changeset, and one of them destroys that changeset
24 the same changeset, and one of them destroys that changeset
24 with rollback, the other clone will suddenly stop working: all
25 with rollback, the other clone will suddenly stop working: all
25 operations will fail with "abort: working directory has unknown
26 operations will fail with "abort: working directory has unknown
26 parent". The only known workaround is to use debugsetparents on
27 parent". The only known workaround is to use debugsetparents on
27 the broken clone to reset it to a changeset that still exists.
28 the broken clone to reset it to a changeset that still exists.
28 """
29 """
29
30
30 return hg.share(ui, source, dest, not noupdate)
31 return hg.share(ui, source, dest, not noupdate)
31
32
32 def unshare(ui, repo):
33 def unshare(ui, repo):
33 """convert a shared repository to a normal one
34 """convert a shared repository to a normal one
34
35
35 Copy the store data to the repo and remove the sharedpath data.
36 Copy the store data to the repo and remove the sharedpath data.
36 """
37 """
37
38
38 if repo.sharedpath == repo.path:
39 if repo.sharedpath == repo.path:
39 raise util.Abort(_("this is not a shared repo"))
40 raise util.Abort(_("this is not a shared repo"))
40
41
41 destlock = lock = None
42 destlock = lock = None
42 lock = repo.lock()
43 lock = repo.lock()
43 try:
44 try:
44 # we use locks here because if we race with commit, we
45 # we use locks here because if we race with commit, we
45 # can end up with extra data in the cloned revlogs that's
46 # can end up with extra data in the cloned revlogs that's
46 # not pointed to by changesets, thus causing verify to
47 # not pointed to by changesets, thus causing verify to
47 # fail
48 # fail
48
49
49 destlock = hg.copystore(ui, repo, repo.path)
50 destlock = hg.copystore(ui, repo, repo.path)
50
51
51 sharefile = repo.join('sharedpath')
52 sharefile = repo.join('sharedpath')
52 util.rename(sharefile, sharefile + '.old')
53 util.rename(sharefile, sharefile + '.old')
53
54
54 repo.requirements.discard('sharedpath')
55 repo.requirements.discard('sharedpath')
55 repo._writerequirements()
56 repo._writerequirements()
56 finally:
57 finally:
57 destlock and destlock.release()
58 destlock and destlock.release()
58 lock and lock.release()
59 lock and lock.release()
59
60
60 # update store, spath, sopener and sjoin of repo
61 # update store, spath, sopener and sjoin of repo
61 repo.__init__(repo.baseui, repo.root)
62 repo.__init__(repo.baseui, repo.root)
62
63
63 cmdtable = {
64 cmdtable = {
64 "share":
65 "share":
65 (share,
66 (share,
66 [('U', 'noupdate', None, _('do not create a working copy'))],
67 [('U', 'noupdate', None, _('do not create a working copy'))],
67 _('[-U] SOURCE [DEST]')),
68 _('[-U] SOURCE [DEST]')),
68 "unshare":
69 "unshare":
69 (unshare,
70 (unshare,
70 [],
71 [],
71 ''),
72 ''),
72 }
73 }
73
74
74 commands.norepo += " share"
75 commands.norepo += " share"
@@ -1,217 +1,219 b''
1 """strip changesets and their descendents from history
1 """strip changesets and their descendents from history
2
2
3 This extension allows you to strip changesets and all their descendants from the
3 This extension allows you to strip changesets and all their descendants from the
4 repository. See the command help for details.
4 repository. See the command help for details.
5 """
5 """
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7 from mercurial.node import nullid
7 from mercurial.node import nullid
8 from mercurial.lock import release
8 from mercurial.lock import release
9 from mercurial import cmdutil, hg, scmutil, util
9 from mercurial import cmdutil, hg, scmutil, util
10 from mercurial import repair, bookmarks
10 from mercurial import repair, bookmarks
11
11
12 cmdtable = {}
12 cmdtable = {}
13 command = cmdutil.command(cmdtable)
13 command = cmdutil.command(cmdtable)
14 testedwith = 'internal'
14 testedwith = 'internal'
15
15
16 def checksubstate(repo, baserev=None):
16 def checksubstate(repo, baserev=None):
17 '''return list of subrepos at a different revision than substate.
17 '''return list of subrepos at a different revision than substate.
18 Abort if any subrepos have uncommitted changes.'''
18 Abort if any subrepos have uncommitted changes.'''
19 inclsubs = []
19 inclsubs = []
20 wctx = repo[None]
20 wctx = repo[None]
21 if baserev:
21 if baserev:
22 bctx = repo[baserev]
22 bctx = repo[baserev]
23 else:
23 else:
24 bctx = wctx.parents()[0]
24 bctx = wctx.parents()[0]
25 for s in sorted(wctx.substate):
25 for s in sorted(wctx.substate):
26 if wctx.sub(s).dirty(True):
26 if wctx.sub(s).dirty(True):
27 raise util.Abort(
27 raise util.Abort(
28 _("uncommitted changes in subrepository %s") % s)
28 _("uncommitted changes in subrepository %s") % s)
29 elif s not in bctx.substate or bctx.sub(s).dirty():
29 elif s not in bctx.substate or bctx.sub(s).dirty():
30 inclsubs.append(s)
30 inclsubs.append(s)
31 return inclsubs
31 return inclsubs
32
32
33 def checklocalchanges(repo, force=False, excsuffix=''):
33 def checklocalchanges(repo, force=False, excsuffix=''):
34 cmdutil.checkunfinished(repo)
34 cmdutil.checkunfinished(repo)
35 m, a, r, d = repo.status()[:4]
35 m, a, r, d = repo.status()[:4]
36 if not force:
36 if not force:
37 if (m or a or r or d):
37 if (m or a or r or d):
38 _("local changes found") # i18n tool detection
38 _("local changes found") # i18n tool detection
39 raise util.Abort(_("local changes found" + excsuffix))
39 raise util.Abort(_("local changes found" + excsuffix))
40 if checksubstate(repo):
40 if checksubstate(repo):
41 _("local changed subrepos found") # i18n tool detection
41 _("local changed subrepos found") # i18n tool detection
42 raise util.Abort(_("local changed subrepos found" + excsuffix))
42 raise util.Abort(_("local changed subrepos found" + excsuffix))
43 return m, a, r, d
43 return m, a, r, d
44
44
45 def strip(ui, repo, revs, update=True, backup="all", force=None):
45 def strip(ui, repo, revs, update=True, backup="all", force=None):
46 wlock = lock = None
46 wlock = lock = None
47 try:
47 try:
48 wlock = repo.wlock()
48 wlock = repo.wlock()
49 lock = repo.lock()
49 lock = repo.lock()
50
50
51 if update:
51 if update:
52 checklocalchanges(repo, force=force)
52 checklocalchanges(repo, force=force)
53 urev, p2 = repo.changelog.parents(revs[0])
53 urev, p2 = repo.changelog.parents(revs[0])
54 if p2 != nullid and p2 in [x.node for x in repo.mq.applied]:
54 if (util.safehasattr(repo, 'mq') and
55 p2 != nullid
56 and p2 in [x.node for x in repo.mq.applied]):
55 urev = p2
57 urev = p2
56 hg.clean(repo, urev)
58 hg.clean(repo, urev)
57 repo.dirstate.write()
59 repo.dirstate.write()
58
60
59 repair.strip(ui, repo, revs, backup)
61 repair.strip(ui, repo, revs, backup)
60 finally:
62 finally:
61 release(lock, wlock)
63 release(lock, wlock)
62
64
63
65
64 @command("strip",
66 @command("strip",
65 [
67 [
66 ('r', 'rev', [], _('strip specified revision (optional, '
68 ('r', 'rev', [], _('strip specified revision (optional, '
67 'can specify revisions without this '
69 'can specify revisions without this '
68 'option)'), _('REV')),
70 'option)'), _('REV')),
69 ('f', 'force', None, _('force removal of changesets, discard '
71 ('f', 'force', None, _('force removal of changesets, discard '
70 'uncommitted changes (no backup)')),
72 'uncommitted changes (no backup)')),
71 ('b', 'backup', None, _('bundle only changesets with local revision'
73 ('b', 'backup', None, _('bundle only changesets with local revision'
72 ' number greater than REV which are not'
74 ' number greater than REV which are not'
73 ' descendants of REV (DEPRECATED)')),
75 ' descendants of REV (DEPRECATED)')),
74 ('', 'no-backup', None, _('no backups')),
76 ('', 'no-backup', None, _('no backups')),
75 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
77 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
76 ('n', '', None, _('ignored (DEPRECATED)')),
78 ('n', '', None, _('ignored (DEPRECATED)')),
77 ('k', 'keep', None, _("do not modify working copy during strip")),
79 ('k', 'keep', None, _("do not modify working copy during strip")),
78 ('B', 'bookmark', '', _("remove revs only reachable from given"
80 ('B', 'bookmark', '', _("remove revs only reachable from given"
79 " bookmark"))],
81 " bookmark"))],
80 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
82 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
81 def stripcmd(ui, repo, *revs, **opts):
83 def stripcmd(ui, repo, *revs, **opts):
82 """strip changesets and all their descendants from the repository
84 """strip changesets and all their descendants from the repository
83
85
84 The strip command removes the specified changesets and all their
86 The strip command removes the specified changesets and all their
85 descendants. If the working directory has uncommitted changes, the
87 descendants. If the working directory has uncommitted changes, the
86 operation is aborted unless the --force flag is supplied, in which
88 operation is aborted unless the --force flag is supplied, in which
87 case changes will be discarded.
89 case changes will be discarded.
88
90
89 If a parent of the working directory is stripped, then the working
91 If a parent of the working directory is stripped, then the working
90 directory will automatically be updated to the most recent
92 directory will automatically be updated to the most recent
91 available ancestor of the stripped parent after the operation
93 available ancestor of the stripped parent after the operation
92 completes.
94 completes.
93
95
94 Any stripped changesets are stored in ``.hg/strip-backup`` as a
96 Any stripped changesets are stored in ``.hg/strip-backup`` as a
95 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
97 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
96 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
98 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
97 where BUNDLE is the bundle file created by the strip. Note that
99 where BUNDLE is the bundle file created by the strip. Note that
98 the local revision numbers will in general be different after the
100 the local revision numbers will in general be different after the
99 restore.
101 restore.
100
102
101 Use the --no-backup option to discard the backup bundle once the
103 Use the --no-backup option to discard the backup bundle once the
102 operation completes.
104 operation completes.
103
105
104 Strip is not a history-rewriting operation and can be used on
106 Strip is not a history-rewriting operation and can be used on
105 changesets in the public phase. But if the stripped changesets have
107 changesets in the public phase. But if the stripped changesets have
106 been pushed to a remote repository you will likely pull them again.
108 been pushed to a remote repository you will likely pull them again.
107
109
108 Return 0 on success.
110 Return 0 on success.
109 """
111 """
110 backup = 'all'
112 backup = 'all'
111 if opts.get('backup'):
113 if opts.get('backup'):
112 backup = 'strip'
114 backup = 'strip'
113 elif opts.get('no_backup') or opts.get('nobackup'):
115 elif opts.get('no_backup') or opts.get('nobackup'):
114 backup = 'none'
116 backup = 'none'
115
117
116 cl = repo.changelog
118 cl = repo.changelog
117 revs = list(revs) + opts.get('rev')
119 revs = list(revs) + opts.get('rev')
118 revs = set(scmutil.revrange(repo, revs))
120 revs = set(scmutil.revrange(repo, revs))
119
121
120 if opts.get('bookmark'):
122 if opts.get('bookmark'):
121 mark = opts.get('bookmark')
123 mark = opts.get('bookmark')
122 marks = repo._bookmarks
124 marks = repo._bookmarks
123 if mark not in marks:
125 if mark not in marks:
124 raise util.Abort(_("bookmark '%s' not found") % mark)
126 raise util.Abort(_("bookmark '%s' not found") % mark)
125
127
126 # If the requested bookmark is not the only one pointing to a
128 # If the requested bookmark is not the only one pointing to a
127 # a revision we have to only delete the bookmark and not strip
129 # a revision we have to only delete the bookmark and not strip
128 # anything. revsets cannot detect that case.
130 # anything. revsets cannot detect that case.
129 uniquebm = True
131 uniquebm = True
130 for m, n in marks.iteritems():
132 for m, n in marks.iteritems():
131 if m != mark and n == repo[mark].node():
133 if m != mark and n == repo[mark].node():
132 uniquebm = False
134 uniquebm = False
133 break
135 break
134 if uniquebm:
136 if uniquebm:
135 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
137 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
136 "ancestors(head() and not bookmark(%s)) - "
138 "ancestors(head() and not bookmark(%s)) - "
137 "ancestors(bookmark() and not bookmark(%s))",
139 "ancestors(bookmark() and not bookmark(%s))",
138 mark, mark, mark)
140 mark, mark, mark)
139 revs.update(set(rsrevs))
141 revs.update(set(rsrevs))
140 if not revs:
142 if not revs:
141 del marks[mark]
143 del marks[mark]
142 marks.write()
144 marks.write()
143 ui.write(_("bookmark '%s' deleted\n") % mark)
145 ui.write(_("bookmark '%s' deleted\n") % mark)
144
146
145 if not revs:
147 if not revs:
146 raise util.Abort(_('empty revision set'))
148 raise util.Abort(_('empty revision set'))
147
149
148 descendants = set(cl.descendants(revs))
150 descendants = set(cl.descendants(revs))
149 strippedrevs = revs.union(descendants)
151 strippedrevs = revs.union(descendants)
150 roots = revs.difference(descendants)
152 roots = revs.difference(descendants)
151
153
152 update = False
154 update = False
153 # if one of the wdir parent is stripped we'll need
155 # if one of the wdir parent is stripped we'll need
154 # to update away to an earlier revision
156 # to update away to an earlier revision
155 for p in repo.dirstate.parents():
157 for p in repo.dirstate.parents():
156 if p != nullid and cl.rev(p) in strippedrevs:
158 if p != nullid and cl.rev(p) in strippedrevs:
157 update = True
159 update = True
158 break
160 break
159
161
160 rootnodes = set(cl.node(r) for r in roots)
162 rootnodes = set(cl.node(r) for r in roots)
161
163
162 q = getattr(repo, 'mq', None)
164 q = getattr(repo, 'mq', None)
163 if q is not None and q.applied:
165 if q is not None and q.applied:
164 # refresh queue state if we're about to strip
166 # refresh queue state if we're about to strip
165 # applied patches
167 # applied patches
166 if cl.rev(repo.lookup('qtip')) in strippedrevs:
168 if cl.rev(repo.lookup('qtip')) in strippedrevs:
167 q.applieddirty = True
169 q.applieddirty = True
168 start = 0
170 start = 0
169 end = len(q.applied)
171 end = len(q.applied)
170 for i, statusentry in enumerate(q.applied):
172 for i, statusentry in enumerate(q.applied):
171 if statusentry.node in rootnodes:
173 if statusentry.node in rootnodes:
172 # if one of the stripped roots is an applied
174 # if one of the stripped roots is an applied
173 # patch, only part of the queue is stripped
175 # patch, only part of the queue is stripped
174 start = i
176 start = i
175 break
177 break
176 del q.applied[start:end]
178 del q.applied[start:end]
177 q.savedirty()
179 q.savedirty()
178
180
179 revs = sorted(rootnodes)
181 revs = sorted(rootnodes)
180 if update and opts.get('keep'):
182 if update and opts.get('keep'):
181 wlock = repo.wlock()
183 wlock = repo.wlock()
182 try:
184 try:
183 urev, p2 = repo.changelog.parents(revs[0])
185 urev, p2 = repo.changelog.parents(revs[0])
184 if (util.safehasattr(repo, 'mq') and p2 != nullid
186 if (util.safehasattr(repo, 'mq') and p2 != nullid
185 and p2 in [x.node for x in repo.mq.applied]):
187 and p2 in [x.node for x in repo.mq.applied]):
186 urev = p2
188 urev = p2
187 uctx = repo[urev]
189 uctx = repo[urev]
188
190
189 # only reset the dirstate for files that would actually change
191 # only reset the dirstate for files that would actually change
190 # between the working context and uctx
192 # between the working context and uctx
191 descendantrevs = repo.revs("%s::." % uctx.rev())
193 descendantrevs = repo.revs("%s::." % uctx.rev())
192 changedfiles = []
194 changedfiles = []
193 for rev in descendantrevs:
195 for rev in descendantrevs:
194 # blindly reset the files, regardless of what actually changed
196 # blindly reset the files, regardless of what actually changed
195 changedfiles.extend(repo[rev].files())
197 changedfiles.extend(repo[rev].files())
196
198
197 # reset files that only changed in the dirstate too
199 # reset files that only changed in the dirstate too
198 dirstate = repo.dirstate
200 dirstate = repo.dirstate
199 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
201 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
200 changedfiles.extend(dirchanges)
202 changedfiles.extend(dirchanges)
201
203
202 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
204 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
203 repo.dirstate.write()
205 repo.dirstate.write()
204 update = False
206 update = False
205 finally:
207 finally:
206 wlock.release()
208 wlock.release()
207
209
208 if opts.get('bookmark'):
210 if opts.get('bookmark'):
209 if mark == repo._bookmarkcurrent:
211 if mark == repo._bookmarkcurrent:
210 bookmarks.setcurrent(repo, None)
212 bookmarks.setcurrent(repo, None)
211 del marks[mark]
213 del marks[mark]
212 marks.write()
214 marks.write()
213 ui.write(_("bookmark '%s' deleted\n") % mark)
215 ui.write(_("bookmark '%s' deleted\n") % mark)
214
216
215 strip(ui, repo, revs, backup=backup, update=update, force=opts.get('force'))
217 strip(ui, repo, revs, backup=backup, update=update, force=opts.get('force'))
216
218
217 return 0
219 return 0
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now