##// END OF EJS Templates
explicitly close files...
Dan Villiom Podlaski Christiansen -
r13400:14f3795a default
parent child Browse files
Show More

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

@@ -1,305 +1,307 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
13
14 def repquote(m):
14 def repquote(m):
15 t = re.sub(r"\w", "x", m.group('text'))
15 t = re.sub(r"\w", "x", m.group('text'))
16 t = re.sub(r"[^\sx]", "o", t)
16 t = re.sub(r"[^\sx]", "o", t)
17 return m.group('quote') + t + m.group('quote')
17 return m.group('quote') + t + m.group('quote')
18
18
19 def reppython(m):
19 def reppython(m):
20 comment = m.group('comment')
20 comment = m.group('comment')
21 if comment:
21 if comment:
22 return "#" * len(comment)
22 return "#" * len(comment)
23 return repquote(m)
23 return repquote(m)
24
24
25 def repcomment(m):
25 def repcomment(m):
26 return m.group(1) + "#" * len(m.group(2))
26 return m.group(1) + "#" * len(m.group(2))
27
27
28 def repccomment(m):
28 def repccomment(m):
29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 return m.group(1) + t + "*/"
30 return m.group(1) + t + "*/"
31
31
32 def repcallspaces(m):
32 def repcallspaces(m):
33 t = re.sub(r"\n\s+", "\n", m.group(2))
33 t = re.sub(r"\n\s+", "\n", m.group(2))
34 return m.group(1) + t
34 return m.group(1) + t
35
35
36 def repinclude(m):
36 def repinclude(m):
37 return m.group(1) + "<foo>"
37 return m.group(1) + "<foo>"
38
38
39 def rephere(m):
39 def rephere(m):
40 t = re.sub(r"\S", "x", m.group(2))
40 t = re.sub(r"\S", "x", m.group(2))
41 return m.group(1) + t
41 return m.group(1) + t
42
42
43
43
44 testpats = [
44 testpats = [
45 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
45 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
46 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
46 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
47 (r'^function', "don't use 'function', use old style"),
47 (r'^function', "don't use 'function', use old style"),
48 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
48 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
49 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
49 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
50 (r'echo -n', "don't use 'echo -n', use printf"),
50 (r'echo -n', "don't use 'echo -n', use printf"),
51 (r'^diff.*-\w*N', "don't use 'diff -N'"),
51 (r'^diff.*-\w*N', "don't use 'diff -N'"),
52 (r'(^| )wc[^|]*$', "filter wc output"),
52 (r'(^| )wc[^|]*$', "filter wc output"),
53 (r'head -c', "don't use 'head -c', use 'dd'"),
53 (r'head -c', "don't use 'head -c', use 'dd'"),
54 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
54 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
55 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
55 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
56 (r'printf.*\\x', "don't use printf \\x, use Python"),
56 (r'printf.*\\x', "don't use printf \\x, use Python"),
57 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
57 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
58 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
58 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
59 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
59 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
60 "use egrep for extended grep syntax"),
60 "use egrep for extended grep syntax"),
61 (r'/bin/', "don't use explicit paths for tools"),
61 (r'/bin/', "don't use explicit paths for tools"),
62 (r'\$PWD', "don't use $PWD, use `pwd`"),
62 (r'\$PWD', "don't use $PWD, use `pwd`"),
63 (r'[^\n]\Z', "no trailing newline"),
63 (r'[^\n]\Z', "no trailing newline"),
64 (r'export.*=', "don't export and assign at once"),
64 (r'export.*=', "don't export and assign at once"),
65 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
65 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
66 (r'^source\b', "don't use 'source', use '.'"),
66 (r'^source\b', "don't use 'source', use '.'"),
67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
68 (r'ls\s+[^|-]+\s+-', "options to 'ls' must come before filenames"),
68 (r'ls\s+[^|-]+\s+-', "options to 'ls' must come before filenames"),
69 ]
69 ]
70
70
71 testfilters = [
71 testfilters = [
72 (r"( *)(#([^\n]*\S)?)", repcomment),
72 (r"( *)(#([^\n]*\S)?)", repcomment),
73 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
73 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
74 ]
74 ]
75
75
76 uprefix = r"^ \$ "
76 uprefix = r"^ \$ "
77 uprefixc = r"^ > "
77 uprefixc = r"^ > "
78 utestpats = [
78 utestpats = [
79 (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"),
79 (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"),
80 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
80 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
81 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
81 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
82 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
82 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
83 (uprefix + r'.*\|\| echo.*(fail|error)',
83 (uprefix + r'.*\|\| echo.*(fail|error)',
84 "explicit exit code checks unnecessary"),
84 "explicit exit code checks unnecessary"),
85 (uprefix + r'set -e', "don't use set -e"),
85 (uprefix + r'set -e', "don't use set -e"),
86 (uprefixc + r'( *)\t', "don't use tabs to indent"),
86 (uprefixc + r'( *)\t', "don't use tabs to indent"),
87 ]
87 ]
88
88
89 for p, m in testpats:
89 for p, m in testpats:
90 if p.startswith('^'):
90 if p.startswith('^'):
91 p = uprefix + p[1:]
91 p = uprefix + p[1:]
92 else:
92 else:
93 p = uprefix + p
93 p = uprefix + p
94 utestpats.append((p, m))
94 utestpats.append((p, m))
95
95
96 utestfilters = [
96 utestfilters = [
97 (r"( *)(#([^\n]*\S)?)", repcomment),
97 (r"( *)(#([^\n]*\S)?)", repcomment),
98 ]
98 ]
99
99
100 pypats = [
100 pypats = [
101 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
101 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
102 "tuple parameter unpacking not available in Python 3+"),
102 "tuple parameter unpacking not available in Python 3+"),
103 (r'lambda\s*\(.*,.*\)',
103 (r'lambda\s*\(.*,.*\)',
104 "tuple parameter unpacking not available in Python 3+"),
104 "tuple parameter unpacking not available in Python 3+"),
105 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
105 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
106 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
106 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
107 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
107 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
108 (r'^\s*\t', "don't use tabs"),
108 (r'^\s*\t', "don't use tabs"),
109 (r'\S;\s*\n', "semicolon"),
109 (r'\S;\s*\n', "semicolon"),
110 (r'\w,\w', "missing whitespace after ,"),
110 (r'\w,\w', "missing whitespace after ,"),
111 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
111 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
112 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
112 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
113 (r'.{85}', "line too long"),
113 (r'.{85}', "line too long"),
114 (r'.{81}', "warning: line over 80 characters"),
114 (r'.{81}', "warning: line over 80 characters"),
115 (r'[^\n]\Z', "no trailing newline"),
115 (r'[^\n]\Z', "no trailing newline"),
116 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
116 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
117 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
117 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
118 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
118 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
119 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
119 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
120 "linebreak after :"),
120 "linebreak after :"),
121 (r'class\s[^(]:', "old-style class, use class foo(object)"),
121 (r'class\s[^(]:', "old-style class, use class foo(object)"),
122 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
122 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
123 "Python keyword is not a function"),
123 "Python keyword is not a function"),
124 (r',]', "unneeded trailing ',' in list"),
124 (r',]', "unneeded trailing ',' in list"),
125 # (r'class\s[A-Z][^\(]*\((?!Exception)',
125 # (r'class\s[A-Z][^\(]*\((?!Exception)',
126 # "don't capitalize non-exception classes"),
126 # "don't capitalize non-exception classes"),
127 # (r'in range\(', "use xrange"),
127 # (r'in range\(', "use xrange"),
128 # (r'^\s*print\s+', "avoid using print in core and extensions"),
128 # (r'^\s*print\s+', "avoid using print in core and extensions"),
129 (r'[\x80-\xff]', "non-ASCII character literal"),
129 (r'[\x80-\xff]', "non-ASCII character literal"),
130 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
130 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
131 (r'^\s*with\s+', "with not available in Python 2.4"),
131 (r'^\s*with\s+', "with not available in Python 2.4"),
132 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
132 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
133 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
133 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
134 (r'(?<!def)\s+(any|all|format)\(',
134 (r'(?<!def)\s+(any|all|format)\(',
135 "any/all/format not available in Python 2.4"),
135 "any/all/format not available in Python 2.4"),
136 (r'(?<!def)\s+(callable)\(',
136 (r'(?<!def)\s+(callable)\(',
137 "callable not available in Python 3, use hasattr(f, '__call__')"),
137 "callable not available in Python 3, use hasattr(f, '__call__')"),
138 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
138 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
139 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
139 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
140 "gratuitous whitespace after Python keyword"),
140 "gratuitous whitespace after Python keyword"),
141 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
141 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
142 # (r'\s\s=', "gratuitous whitespace before ="),
142 # (r'\s\s=', "gratuitous whitespace before ="),
143 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
143 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
144 "missing whitespace around operator"),
144 "missing whitespace around operator"),
145 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
145 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
146 "missing whitespace around operator"),
146 "missing whitespace around operator"),
147 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
147 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
148 "missing whitespace around operator"),
148 "missing whitespace around operator"),
149 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
149 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
150 "wrong whitespace around ="),
150 "wrong whitespace around ="),
151 (r'raise Exception', "don't raise generic exceptions"),
151 (r'raise Exception', "don't raise generic exceptions"),
152 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
152 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
153 "warning: unwrapped ui message"),
153 "warning: unwrapped ui message"),
154 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
154 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
155 (r' [=!]=\s+(True|False|None)',
155 (r' [=!]=\s+(True|False|None)',
156 "comparison with singleton, use 'is' or 'is not' instead"),
156 "comparison with singleton, use 'is' or 'is not' instead"),
157 ]
157 ]
158
158
159 pyfilters = [
159 pyfilters = [
160 (r"""(?msx)(?P<comment>\#.*?$)|
160 (r"""(?msx)(?P<comment>\#.*?$)|
161 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
161 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
162 (?P<text>(([^\\]|\\.)*?))
162 (?P<text>(([^\\]|\\.)*?))
163 (?P=quote))""", reppython),
163 (?P=quote))""", reppython),
164 ]
164 ]
165
165
166 cpats = [
166 cpats = [
167 (r'//', "don't use //-style comments"),
167 (r'//', "don't use //-style comments"),
168 (r'^ ', "don't use spaces to indent"),
168 (r'^ ', "don't use spaces to indent"),
169 (r'\S\t', "don't use tabs except for indent"),
169 (r'\S\t', "don't use tabs except for indent"),
170 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
170 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
171 (r'.{85}', "line too long"),
171 (r'.{85}', "line too long"),
172 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
172 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
173 (r'return\(', "return is not a function"),
173 (r'return\(', "return is not a function"),
174 (r' ;', "no space before ;"),
174 (r' ;', "no space before ;"),
175 (r'\w+\* \w+', "use int *foo, not int* foo"),
175 (r'\w+\* \w+', "use int *foo, not int* foo"),
176 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
176 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
177 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
177 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
178 (r'\w,\w', "missing whitespace after ,"),
178 (r'\w,\w', "missing whitespace after ,"),
179 (r'\w[+/*]\w', "missing whitespace in expression"),
179 (r'\w[+/*]\w', "missing whitespace in expression"),
180 (r'^#\s+\w', "use #foo, not # foo"),
180 (r'^#\s+\w', "use #foo, not # foo"),
181 (r'[^\n]\Z', "no trailing newline"),
181 (r'[^\n]\Z', "no trailing newline"),
182 ]
182 ]
183
183
184 cfilters = [
184 cfilters = [
185 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
185 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
186 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
186 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
187 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
187 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
188 (r'(\()([^)]+\))', repcallspaces),
188 (r'(\()([^)]+\))', repcallspaces),
189 ]
189 ]
190
190
191 checks = [
191 checks = [
192 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
192 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
193 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
193 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
194 ('c', r'.*\.c$', cfilters, cpats),
194 ('c', r'.*\.c$', cfilters, cpats),
195 ('unified test', r'.*\.t$', utestfilters, utestpats),
195 ('unified test', r'.*\.t$', utestfilters, utestpats),
196 ]
196 ]
197
197
198 class norepeatlogger(object):
198 class norepeatlogger(object):
199 def __init__(self):
199 def __init__(self):
200 self._lastseen = None
200 self._lastseen = None
201
201
202 def log(self, fname, lineno, line, msg, blame):
202 def log(self, fname, lineno, line, msg, blame):
203 """print error related a to given line of a given file.
203 """print error related a to given line of a given file.
204
204
205 The faulty line will also be printed but only once in the case
205 The faulty line will also be printed but only once in the case
206 of multiple errors.
206 of multiple errors.
207
207
208 :fname: filename
208 :fname: filename
209 :lineno: line number
209 :lineno: line number
210 :line: actual content of the line
210 :line: actual content of the line
211 :msg: error message
211 :msg: error message
212 """
212 """
213 msgid = fname, lineno, line
213 msgid = fname, lineno, line
214 if msgid != self._lastseen:
214 if msgid != self._lastseen:
215 if blame:
215 if blame:
216 print "%s:%d (%s):" % (fname, lineno, blame)
216 print "%s:%d (%s):" % (fname, lineno, blame)
217 else:
217 else:
218 print "%s:%d:" % (fname, lineno)
218 print "%s:%d:" % (fname, lineno)
219 print " > %s" % line
219 print " > %s" % line
220 self._lastseen = msgid
220 self._lastseen = msgid
221 print " " + msg
221 print " " + msg
222
222
223 _defaultlogger = norepeatlogger()
223 _defaultlogger = norepeatlogger()
224
224
225 def getblame(f):
225 def getblame(f):
226 lines = []
226 lines = []
227 for l in os.popen('hg annotate -un %s' % f):
227 for l in os.popen('hg annotate -un %s' % f):
228 start, line = l.split(':', 1)
228 start, line = l.split(':', 1)
229 user, rev = start.split()
229 user, rev = start.split()
230 lines.append((line[1:-1], user, rev))
230 lines.append((line[1:-1], user, rev))
231 return lines
231 return lines
232
232
233 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
233 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
234 blame=False):
234 blame=False):
235 """checks style and portability of a given file
235 """checks style and portability of a given file
236
236
237 :f: filepath
237 :f: filepath
238 :logfunc: function used to report error
238 :logfunc: function used to report error
239 logfunc(filename, linenumber, linecontent, errormessage)
239 logfunc(filename, linenumber, linecontent, errormessage)
240 :maxerr: number of error to display before arborting.
240 :maxerr: number of error to display before arborting.
241 Set to None (default) to report all errors
241 Set to None (default) to report all errors
242
242
243 return True if no error is found, False otherwise.
243 return True if no error is found, False otherwise.
244 """
244 """
245 blamecache = None
245 blamecache = None
246 result = True
246 result = True
247 for name, match, filters, pats in checks:
247 for name, match, filters, pats in checks:
248 fc = 0
248 fc = 0
249 if not re.match(match, f):
249 if not re.match(match, f):
250 continue
250 continue
251 pre = post = open(f).read()
251 fp = open(f)
252 pre = post = fp.read()
253 fp.close()
252 if "no-" + "check-code" in pre:
254 if "no-" + "check-code" in pre:
253 break
255 break
254 for p, r in filters:
256 for p, r in filters:
255 post = re.sub(p, r, post)
257 post = re.sub(p, r, post)
256 # print post # uncomment to show filtered version
258 # print post # uncomment to show filtered version
257 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
259 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
258 for n, l in z:
260 for n, l in z:
259 if "check-code" + "-ignore" in l[0]:
261 if "check-code" + "-ignore" in l[0]:
260 continue
262 continue
261 for p, msg in pats:
263 for p, msg in pats:
262 if not warnings and msg.startswith("warning"):
264 if not warnings and msg.startswith("warning"):
263 continue
265 continue
264 if re.search(p, l[1]):
266 if re.search(p, l[1]):
265 bd = ""
267 bd = ""
266 if blame:
268 if blame:
267 bd = 'working directory'
269 bd = 'working directory'
268 if not blamecache:
270 if not blamecache:
269 blamecache = getblame(f)
271 blamecache = getblame(f)
270 if n < len(blamecache):
272 if n < len(blamecache):
271 bl, bu, br = blamecache[n]
273 bl, bu, br = blamecache[n]
272 if bl == l[0]:
274 if bl == l[0]:
273 bd = '%s@%s' % (bu, br)
275 bd = '%s@%s' % (bu, br)
274 logfunc(f, n + 1, l[0], msg, bd)
276 logfunc(f, n + 1, l[0], msg, bd)
275 fc += 1
277 fc += 1
276 result = False
278 result = False
277 if maxerr is not None and fc >= maxerr:
279 if maxerr is not None and fc >= maxerr:
278 print " (too many errors, giving up)"
280 print " (too many errors, giving up)"
279 break
281 break
280 break
282 break
281 return result
283 return result
282
284
283 if __name__ == "__main__":
285 if __name__ == "__main__":
284 parser = optparse.OptionParser("%prog [options] [files]")
286 parser = optparse.OptionParser("%prog [options] [files]")
285 parser.add_option("-w", "--warnings", action="store_true",
287 parser.add_option("-w", "--warnings", action="store_true",
286 help="include warning-level checks")
288 help="include warning-level checks")
287 parser.add_option("-p", "--per-file", type="int",
289 parser.add_option("-p", "--per-file", type="int",
288 help="max warnings per file")
290 help="max warnings per file")
289 parser.add_option("-b", "--blame", action="store_true",
291 parser.add_option("-b", "--blame", action="store_true",
290 help="use annotate to generate blame info")
292 help="use annotate to generate blame info")
291
293
292 parser.set_defaults(per_file=15, warnings=False, blame=False)
294 parser.set_defaults(per_file=15, warnings=False, blame=False)
293 (options, args) = parser.parse_args()
295 (options, args) = parser.parse_args()
294
296
295 if len(args) == 0:
297 if len(args) == 0:
296 check = glob.glob("*")
298 check = glob.glob("*")
297 else:
299 else:
298 check = args
300 check = args
299
301
300 for f in check:
302 for f in check:
301 ret = 0
303 ret = 0
302 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
304 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
303 blame=options.blame):
305 blame=options.blame):
304 ret = 1
306 ret = 1
305 sys.exit(ret)
307 sys.exit(ret)
@@ -1,288 +1,290 b''
1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
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 '''commands to sign and verify changesets'''
6 '''commands to sign and verify changesets'''
7
7
8 import os, tempfile, binascii
8 import os, tempfile, binascii
9 from mercurial import util, commands, match
9 from mercurial import util, commands, match
10 from mercurial import node as hgnode
10 from mercurial import node as hgnode
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 class gpg(object):
13 class gpg(object):
14 def __init__(self, path, key=None):
14 def __init__(self, path, key=None):
15 self.path = path
15 self.path = path
16 self.key = (key and " --local-user \"%s\"" % key) or ""
16 self.key = (key and " --local-user \"%s\"" % key) or ""
17
17
18 def sign(self, data):
18 def sign(self, data):
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
20 return util.filter(data, gpgcmd)
20 return util.filter(data, gpgcmd)
21
21
22 def verify(self, data, sig):
22 def verify(self, data, sig):
23 """ returns of the good and bad signatures"""
23 """ returns of the good and bad signatures"""
24 sigfile = datafile = None
24 sigfile = datafile = None
25 try:
25 try:
26 # create temporary files
26 # create temporary files
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
28 fp = os.fdopen(fd, 'wb')
28 fp = os.fdopen(fd, 'wb')
29 fp.write(sig)
29 fp.write(sig)
30 fp.close()
30 fp.close()
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
32 fp = os.fdopen(fd, 'wb')
32 fp = os.fdopen(fd, 'wb')
33 fp.write(data)
33 fp.write(data)
34 fp.close()
34 fp.close()
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
37 ret = util.filter("", gpgcmd)
37 ret = util.filter("", gpgcmd)
38 finally:
38 finally:
39 for f in (sigfile, datafile):
39 for f in (sigfile, datafile):
40 try:
40 try:
41 if f:
41 if f:
42 os.unlink(f)
42 os.unlink(f)
43 except:
43 except:
44 pass
44 pass
45 keys = []
45 keys = []
46 key, fingerprint = None, None
46 key, fingerprint = None, None
47 err = ""
47 err = ""
48 for l in ret.splitlines():
48 for l in ret.splitlines():
49 # see DETAILS in the gnupg documentation
49 # see DETAILS in the gnupg documentation
50 # filter the logger output
50 # filter the logger output
51 if not l.startswith("[GNUPG:]"):
51 if not l.startswith("[GNUPG:]"):
52 continue
52 continue
53 l = l[9:]
53 l = l[9:]
54 if l.startswith("ERRSIG"):
54 if l.startswith("ERRSIG"):
55 err = _("error while verifying signature")
55 err = _("error while verifying signature")
56 break
56 break
57 elif l.startswith("VALIDSIG"):
57 elif l.startswith("VALIDSIG"):
58 # fingerprint of the primary key
58 # fingerprint of the primary key
59 fingerprint = l.split()[10]
59 fingerprint = l.split()[10]
60 elif (l.startswith("GOODSIG") or
60 elif (l.startswith("GOODSIG") or
61 l.startswith("EXPSIG") or
61 l.startswith("EXPSIG") or
62 l.startswith("EXPKEYSIG") or
62 l.startswith("EXPKEYSIG") or
63 l.startswith("BADSIG")):
63 l.startswith("BADSIG")):
64 if key is not None:
64 if key is not None:
65 keys.append(key + [fingerprint])
65 keys.append(key + [fingerprint])
66 key = l.split(" ", 2)
66 key = l.split(" ", 2)
67 fingerprint = None
67 fingerprint = None
68 if err:
68 if err:
69 return err, []
69 return err, []
70 if key is not None:
70 if key is not None:
71 keys.append(key + [fingerprint])
71 keys.append(key + [fingerprint])
72 return err, keys
72 return err, keys
73
73
74 def newgpg(ui, **opts):
74 def newgpg(ui, **opts):
75 """create a new gpg instance"""
75 """create a new gpg instance"""
76 gpgpath = ui.config("gpg", "cmd", "gpg")
76 gpgpath = ui.config("gpg", "cmd", "gpg")
77 gpgkey = opts.get('key')
77 gpgkey = opts.get('key')
78 if not gpgkey:
78 if not gpgkey:
79 gpgkey = ui.config("gpg", "key", None)
79 gpgkey = ui.config("gpg", "key", None)
80 return gpg(gpgpath, gpgkey)
80 return gpg(gpgpath, gpgkey)
81
81
82 def sigwalk(repo):
82 def sigwalk(repo):
83 """
83 """
84 walk over every sigs, yields a couple
84 walk over every sigs, yields a couple
85 ((node, version, sig), (filename, linenumber))
85 ((node, version, sig), (filename, linenumber))
86 """
86 """
87 def parsefile(fileiter, context):
87 def parsefile(fileiter, context):
88 ln = 1
88 ln = 1
89 for l in fileiter:
89 for l in fileiter:
90 if not l:
90 if not l:
91 continue
91 continue
92 yield (l.split(" ", 2), (context, ln))
92 yield (l.split(" ", 2), (context, ln))
93 ln += 1
93 ln += 1
94
94
95 # read the heads
95 # read the heads
96 fl = repo.file(".hgsigs")
96 fl = repo.file(".hgsigs")
97 for r in reversed(fl.heads()):
97 for r in reversed(fl.heads()):
98 fn = ".hgsigs|%s" % hgnode.short(r)
98 fn = ".hgsigs|%s" % hgnode.short(r)
99 for item in parsefile(fl.read(r).splitlines(), fn):
99 for item in parsefile(fl.read(r).splitlines(), fn):
100 yield item
100 yield item
101 try:
101 try:
102 # read local signatures
102 # read local signatures
103 fn = "localsigs"
103 fn = "localsigs"
104 for item in parsefile(repo.opener(fn), fn):
104 for item in parsefile(repo.opener(fn), fn):
105 yield item
105 yield item
106 except IOError:
106 except IOError:
107 pass
107 pass
108
108
109 def getkeys(ui, repo, mygpg, sigdata, context):
109 def getkeys(ui, repo, mygpg, sigdata, context):
110 """get the keys who signed a data"""
110 """get the keys who signed a data"""
111 fn, ln = context
111 fn, ln = context
112 node, version, sig = sigdata
112 node, version, sig = sigdata
113 prefix = "%s:%d" % (fn, ln)
113 prefix = "%s:%d" % (fn, ln)
114 node = hgnode.bin(node)
114 node = hgnode.bin(node)
115
115
116 data = node2txt(repo, node, version)
116 data = node2txt(repo, node, version)
117 sig = binascii.a2b_base64(sig)
117 sig = binascii.a2b_base64(sig)
118 err, keys = mygpg.verify(data, sig)
118 err, keys = mygpg.verify(data, sig)
119 if err:
119 if err:
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
121 return None
121 return None
122
122
123 validkeys = []
123 validkeys = []
124 # warn for expired key and/or sigs
124 # warn for expired key and/or sigs
125 for key in keys:
125 for key in keys:
126 if key[0] == "BADSIG":
126 if key[0] == "BADSIG":
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
128 continue
128 continue
129 if key[0] == "EXPSIG":
129 if key[0] == "EXPSIG":
130 ui.write(_("%s Note: Signature has expired"
130 ui.write(_("%s Note: Signature has expired"
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
132 elif key[0] == "EXPKEYSIG":
132 elif key[0] == "EXPKEYSIG":
133 ui.write(_("%s Note: This key has expired"
133 ui.write(_("%s Note: This key has expired"
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 validkeys.append((key[1], key[2], key[3]))
135 validkeys.append((key[1], key[2], key[3]))
136 return validkeys
136 return validkeys
137
137
138 def sigs(ui, repo):
138 def sigs(ui, repo):
139 """list signed changesets"""
139 """list signed changesets"""
140 mygpg = newgpg(ui)
140 mygpg = newgpg(ui)
141 revs = {}
141 revs = {}
142
142
143 for data, context in sigwalk(repo):
143 for data, context in sigwalk(repo):
144 node, version, sig = data
144 node, version, sig = data
145 fn, ln = context
145 fn, ln = context
146 try:
146 try:
147 n = repo.lookup(node)
147 n = repo.lookup(node)
148 except KeyError:
148 except KeyError:
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
150 continue
150 continue
151 r = repo.changelog.rev(n)
151 r = repo.changelog.rev(n)
152 keys = getkeys(ui, repo, mygpg, data, context)
152 keys = getkeys(ui, repo, mygpg, data, context)
153 if not keys:
153 if not keys:
154 continue
154 continue
155 revs.setdefault(r, [])
155 revs.setdefault(r, [])
156 revs[r].extend(keys)
156 revs[r].extend(keys)
157 for rev in sorted(revs, reverse=True):
157 for rev in sorted(revs, reverse=True):
158 for k in revs[rev]:
158 for k in revs[rev]:
159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
161
161
162 def check(ui, repo, rev):
162 def check(ui, repo, rev):
163 """verify all the signatures there may be for a particular revision"""
163 """verify all the signatures there may be for a particular revision"""
164 mygpg = newgpg(ui)
164 mygpg = newgpg(ui)
165 rev = repo.lookup(rev)
165 rev = repo.lookup(rev)
166 hexrev = hgnode.hex(rev)
166 hexrev = hgnode.hex(rev)
167 keys = []
167 keys = []
168
168
169 for data, context in sigwalk(repo):
169 for data, context in sigwalk(repo):
170 node, version, sig = data
170 node, version, sig = data
171 if node == hexrev:
171 if node == hexrev:
172 k = getkeys(ui, repo, mygpg, data, context)
172 k = getkeys(ui, repo, mygpg, data, context)
173 if k:
173 if k:
174 keys.extend(k)
174 keys.extend(k)
175
175
176 if not keys:
176 if not keys:
177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
178 return
178 return
179
179
180 # print summary
180 # print summary
181 ui.write("%s is signed by:\n" % hgnode.short(rev))
181 ui.write("%s is signed by:\n" % hgnode.short(rev))
182 for key in keys:
182 for key in keys:
183 ui.write(" %s\n" % keystr(ui, key))
183 ui.write(" %s\n" % keystr(ui, key))
184
184
185 def keystr(ui, key):
185 def keystr(ui, key):
186 """associate a string to a key (username, comment)"""
186 """associate a string to a key (username, comment)"""
187 keyid, user, fingerprint = key
187 keyid, user, fingerprint = key
188 comment = ui.config("gpg", fingerprint, None)
188 comment = ui.config("gpg", fingerprint, None)
189 if comment:
189 if comment:
190 return "%s (%s)" % (user, comment)
190 return "%s (%s)" % (user, comment)
191 else:
191 else:
192 return user
192 return user
193
193
194 def sign(ui, repo, *revs, **opts):
194 def sign(ui, repo, *revs, **opts):
195 """add a signature for the current or given revision
195 """add a signature for the current or given revision
196
196
197 If no revision is given, the parent of the working directory is used,
197 If no revision is given, the parent of the working directory is used,
198 or tip if no revision is checked out.
198 or tip if no revision is checked out.
199
199
200 See :hg:`help dates` for a list of formats valid for -d/--date.
200 See :hg:`help dates` for a list of formats valid for -d/--date.
201 """
201 """
202
202
203 mygpg = newgpg(ui, **opts)
203 mygpg = newgpg(ui, **opts)
204 sigver = "0"
204 sigver = "0"
205 sigmessage = ""
205 sigmessage = ""
206
206
207 date = opts.get('date')
207 date = opts.get('date')
208 if date:
208 if date:
209 opts['date'] = util.parsedate(date)
209 opts['date'] = util.parsedate(date)
210
210
211 if revs:
211 if revs:
212 nodes = [repo.lookup(n) for n in revs]
212 nodes = [repo.lookup(n) for n in revs]
213 else:
213 else:
214 nodes = [node for node in repo.dirstate.parents()
214 nodes = [node for node in repo.dirstate.parents()
215 if node != hgnode.nullid]
215 if node != hgnode.nullid]
216 if len(nodes) > 1:
216 if len(nodes) > 1:
217 raise util.Abort(_('uncommitted merge - please provide a '
217 raise util.Abort(_('uncommitted merge - please provide a '
218 'specific revision'))
218 'specific revision'))
219 if not nodes:
219 if not nodes:
220 nodes = [repo.changelog.tip()]
220 nodes = [repo.changelog.tip()]
221
221
222 for n in nodes:
222 for n in nodes:
223 hexnode = hgnode.hex(n)
223 hexnode = hgnode.hex(n)
224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
225 hgnode.short(n)))
225 hgnode.short(n)))
226 # build data
226 # build data
227 data = node2txt(repo, n, sigver)
227 data = node2txt(repo, n, sigver)
228 sig = mygpg.sign(data)
228 sig = mygpg.sign(data)
229 if not sig:
229 if not sig:
230 raise util.Abort(_("error while signing"))
230 raise util.Abort(_("error while signing"))
231 sig = binascii.b2a_base64(sig)
231 sig = binascii.b2a_base64(sig)
232 sig = sig.replace("\n", "")
232 sig = sig.replace("\n", "")
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
234
234
235 # write it
235 # write it
236 if opts['local']:
236 if opts['local']:
237 repo.opener("localsigs", "ab").write(sigmessage)
237 repo.opener("localsigs", "ab").write(sigmessage)
238 return
238 return
239
239
240 msigs = match.exact(repo.root, '', ['.hgsigs'])
240 msigs = match.exact(repo.root, '', ['.hgsigs'])
241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
242 if util.any(s) and not opts["force"]:
242 if util.any(s) and not opts["force"]:
243 raise util.Abort(_("working copy of .hgsigs is changed "
243 raise util.Abort(_("working copy of .hgsigs is changed "
244 "(please commit .hgsigs manually "
244 "(please commit .hgsigs manually "
245 "or use --force)"))
245 "or use --force)"))
246
246
247 repo.wfile(".hgsigs", "ab").write(sigmessage)
247 sigsfile = repo.wfile(".hgsigs", "ab")
248 sigsfile.write(sigmessage)
249 sigsfile.close()
248
250
249 if '.hgsigs' not in repo.dirstate:
251 if '.hgsigs' not in repo.dirstate:
250 repo[None].add([".hgsigs"])
252 repo[None].add([".hgsigs"])
251
253
252 if opts["no_commit"]:
254 if opts["no_commit"]:
253 return
255 return
254
256
255 message = opts['message']
257 message = opts['message']
256 if not message:
258 if not message:
257 # we don't translate commit messages
259 # we don't translate commit messages
258 message = "\n".join(["Added signature for changeset %s"
260 message = "\n".join(["Added signature for changeset %s"
259 % hgnode.short(n)
261 % hgnode.short(n)
260 for n in nodes])
262 for n in nodes])
261 try:
263 try:
262 repo.commit(message, opts['user'], opts['date'], match=msigs)
264 repo.commit(message, opts['user'], opts['date'], match=msigs)
263 except ValueError, inst:
265 except ValueError, inst:
264 raise util.Abort(str(inst))
266 raise util.Abort(str(inst))
265
267
266 def node2txt(repo, node, ver):
268 def node2txt(repo, node, ver):
267 """map a manifest into some text"""
269 """map a manifest into some text"""
268 if ver == "0":
270 if ver == "0":
269 return "%s\n" % hgnode.hex(node)
271 return "%s\n" % hgnode.hex(node)
270 else:
272 else:
271 raise util.Abort(_("unknown signature version"))
273 raise util.Abort(_("unknown signature version"))
272
274
273 cmdtable = {
275 cmdtable = {
274 "sign":
276 "sign":
275 (sign,
277 (sign,
276 [('l', 'local', None, _('make the signature local')),
278 [('l', 'local', None, _('make the signature local')),
277 ('f', 'force', None, _('sign even if the sigfile is modified')),
279 ('f', 'force', None, _('sign even if the sigfile is modified')),
278 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
280 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
279 ('k', 'key', '',
281 ('k', 'key', '',
280 _('the key id to sign with'), _('ID')),
282 _('the key id to sign with'), _('ID')),
281 ('m', 'message', '',
283 ('m', 'message', '',
282 _('commit message'), _('TEXT')),
284 _('commit message'), _('TEXT')),
283 ] + commands.commitopts2,
285 ] + commands.commitopts2,
284 _('hg sign [OPTION]... [REVISION]...')),
286 _('hg sign [OPTION]... [REVISION]...')),
285 "sigcheck": (check, [], _('hg sigcheck REVISION')),
287 "sigcheck": (check, [], _('hg sigcheck REVISION')),
286 "sigs": (sigs, [], _('hg sigs')),
288 "sigs": (sigs, [], _('hg sigs')),
287 }
289 }
288
290
@@ -1,41 +1,44 b''
1 # __init__.py - low-level interfaces to the Linux inotify subsystem
1 # __init__.py - low-level interfaces to the Linux inotify subsystem
2
2
3 # Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 # This library is free software; you can redistribute it and/or modify
5 # This library is free software; you can redistribute it and/or modify
6 # it under the terms of version 2.1 of the GNU Lesser General Public
6 # it under the terms of version 2.1 of the GNU Lesser General Public
7 # License, or any later version.
7 # License, or any later version.
8
8
9 '''Low-level interface to the Linux inotify subsystem.
9 '''Low-level interface to the Linux inotify subsystem.
10
10
11 The inotify subsystem provides an efficient mechanism for file status
11 The inotify subsystem provides an efficient mechanism for file status
12 monitoring and change notification.
12 monitoring and change notification.
13
13
14 This package provides the low-level inotify system call interface and
14 This package provides the low-level inotify system call interface and
15 associated constants and helper functions.
15 associated constants and helper functions.
16
16
17 For a higher-level interface that remains highly efficient, use the
17 For a higher-level interface that remains highly efficient, use the
18 inotify.watcher package.'''
18 inotify.watcher package.'''
19
19
20 __author__ = "Bryan O'Sullivan <bos@serpentine.com>"
20 __author__ = "Bryan O'Sullivan <bos@serpentine.com>"
21
21
22 from _inotify import *
22 from _inotify import *
23
23
24 procfs_path = '/proc/sys/fs/inotify'
24 procfs_path = '/proc/sys/fs/inotify'
25
25
26 def _read_procfs_value(name):
26 def _read_procfs_value(name):
27 def read_value():
27 def read_value():
28 try:
28 try:
29 return int(open(procfs_path + '/' + name).read())
29 fp = open(procfs_path + '/' + name)
30 r = int(fp.read())
31 fp.close()
32 return r
30 except OSError:
33 except OSError:
31 return None
34 return None
32
35
33 read_value.__doc__ = '''Return the value of the %s setting from /proc.
36 read_value.__doc__ = '''Return the value of the %s setting from /proc.
34
37
35 If inotify is not enabled on this system, return None.''' % name
38 If inotify is not enabled on this system, return None.''' % name
36
39
37 return read_value
40 return read_value
38
41
39 max_queued_events = _read_procfs_value('max_queued_events')
42 max_queued_events = _read_procfs_value('max_queued_events')
40 max_user_instances = _read_procfs_value('max_user_instances')
43 max_user_instances = _read_procfs_value('max_user_instances')
41 max_user_watches = _read_procfs_value('max_user_watches')
44 max_user_watches = _read_procfs_value('max_user_watches')
@@ -1,3255 +1,3260 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 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, patch, util
48 from mercurial import commands, cmdutil, hg, patch, util
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 import os, sys, re, errno, shutil
50 import os, sys, re, errno, shutil
51
51
52 commands.norepo += " qclone"
52 commands.norepo += " qclone"
53
53
54 # Patch names looks like unix-file names.
54 # Patch names looks like unix-file names.
55 # They must be joinable with queue directory and result in the patch path.
55 # They must be joinable with queue directory and result in the patch path.
56 normname = util.normpath
56 normname = util.normpath
57
57
58 class statusentry(object):
58 class statusentry(object):
59 def __init__(self, node, name):
59 def __init__(self, node, name):
60 self.node, self.name = node, name
60 self.node, self.name = node, name
61 def __repr__(self):
61 def __repr__(self):
62 return hex(self.node) + ':' + self.name
62 return hex(self.node) + ':' + self.name
63
63
64 class patchheader(object):
64 class patchheader(object):
65 def __init__(self, pf, plainmode=False):
65 def __init__(self, pf, plainmode=False):
66 def eatdiff(lines):
66 def eatdiff(lines):
67 while lines:
67 while lines:
68 l = lines[-1]
68 l = lines[-1]
69 if (l.startswith("diff -") or
69 if (l.startswith("diff -") or
70 l.startswith("Index:") or
70 l.startswith("Index:") or
71 l.startswith("===========")):
71 l.startswith("===========")):
72 del lines[-1]
72 del lines[-1]
73 else:
73 else:
74 break
74 break
75 def eatempty(lines):
75 def eatempty(lines):
76 while lines:
76 while lines:
77 if not lines[-1].strip():
77 if not lines[-1].strip():
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81
81
82 message = []
82 message = []
83 comments = []
83 comments = []
84 user = None
84 user = None
85 date = None
85 date = None
86 parent = None
86 parent = None
87 format = None
87 format = None
88 subject = None
88 subject = None
89 branch = None
89 branch = None
90 nodeid = None
90 nodeid = None
91 diffstart = 0
91 diffstart = 0
92
92
93 for line in file(pf):
93 for line in file(pf):
94 line = line.rstrip()
94 line = line.rstrip()
95 if (line.startswith('diff --git')
95 if (line.startswith('diff --git')
96 or (diffstart and line.startswith('+++ '))):
96 or (diffstart and line.startswith('+++ '))):
97 diffstart = 2
97 diffstart = 2
98 break
98 break
99 diffstart = 0 # reset
99 diffstart = 0 # reset
100 if line.startswith("--- "):
100 if line.startswith("--- "):
101 diffstart = 1
101 diffstart = 1
102 continue
102 continue
103 elif format == "hgpatch":
103 elif format == "hgpatch":
104 # parse values when importing the result of an hg export
104 # parse values when importing the result of an hg export
105 if line.startswith("# User "):
105 if line.startswith("# User "):
106 user = line[7:]
106 user = line[7:]
107 elif line.startswith("# Date "):
107 elif line.startswith("# Date "):
108 date = line[7:]
108 date = line[7:]
109 elif line.startswith("# Parent "):
109 elif line.startswith("# Parent "):
110 parent = line[9:]
110 parent = line[9:]
111 elif line.startswith("# Branch "):
111 elif line.startswith("# Branch "):
112 branch = line[9:]
112 branch = line[9:]
113 elif line.startswith("# Node ID "):
113 elif line.startswith("# Node ID "):
114 nodeid = line[10:]
114 nodeid = line[10:]
115 elif not line.startswith("# ") and line:
115 elif not line.startswith("# ") and line:
116 message.append(line)
116 message.append(line)
117 format = None
117 format = None
118 elif line == '# HG changeset patch':
118 elif line == '# HG changeset patch':
119 message = []
119 message = []
120 format = "hgpatch"
120 format = "hgpatch"
121 elif (format != "tagdone" and (line.startswith("Subject: ") or
121 elif (format != "tagdone" and (line.startswith("Subject: ") or
122 line.startswith("subject: "))):
122 line.startswith("subject: "))):
123 subject = line[9:]
123 subject = line[9:]
124 format = "tag"
124 format = "tag"
125 elif (format != "tagdone" and (line.startswith("From: ") or
125 elif (format != "tagdone" and (line.startswith("From: ") or
126 line.startswith("from: "))):
126 line.startswith("from: "))):
127 user = line[6:]
127 user = line[6:]
128 format = "tag"
128 format = "tag"
129 elif (format != "tagdone" and (line.startswith("Date: ") or
129 elif (format != "tagdone" and (line.startswith("Date: ") or
130 line.startswith("date: "))):
130 line.startswith("date: "))):
131 date = line[6:]
131 date = line[6:]
132 format = "tag"
132 format = "tag"
133 elif format == "tag" and line == "":
133 elif format == "tag" and line == "":
134 # when looking for tags (subject: from: etc) they
134 # when looking for tags (subject: from: etc) they
135 # end once you find a blank line in the source
135 # end once you find a blank line in the source
136 format = "tagdone"
136 format = "tagdone"
137 elif message or line:
137 elif message or line:
138 message.append(line)
138 message.append(line)
139 comments.append(line)
139 comments.append(line)
140
140
141 eatdiff(message)
141 eatdiff(message)
142 eatdiff(comments)
142 eatdiff(comments)
143 # Remember the exact starting line of the patch diffs before consuming
143 # Remember the exact starting line of the patch diffs before consuming
144 # empty lines, for external use by TortoiseHg and others
144 # empty lines, for external use by TortoiseHg and others
145 self.diffstartline = len(comments)
145 self.diffstartline = len(comments)
146 eatempty(message)
146 eatempty(message)
147 eatempty(comments)
147 eatempty(comments)
148
148
149 # make sure message isn't empty
149 # make sure message isn't empty
150 if format and format.startswith("tag") and subject:
150 if format and format.startswith("tag") and subject:
151 message.insert(0, "")
151 message.insert(0, "")
152 message.insert(0, subject)
152 message.insert(0, subject)
153
153
154 self.message = message
154 self.message = message
155 self.comments = comments
155 self.comments = comments
156 self.user = user
156 self.user = user
157 self.date = date
157 self.date = date
158 self.parent = parent
158 self.parent = parent
159 # nodeid and branch are for external use by TortoiseHg and others
159 # nodeid and branch are for external use by TortoiseHg and others
160 self.nodeid = nodeid
160 self.nodeid = nodeid
161 self.branch = branch
161 self.branch = branch
162 self.haspatch = diffstart > 1
162 self.haspatch = diffstart > 1
163 self.plainmode = plainmode
163 self.plainmode = plainmode
164
164
165 def setuser(self, user):
165 def setuser(self, user):
166 if not self.updateheader(['From: ', '# User '], user):
166 if not self.updateheader(['From: ', '# User '], user):
167 try:
167 try:
168 patchheaderat = self.comments.index('# HG changeset patch')
168 patchheaderat = self.comments.index('# HG changeset patch')
169 self.comments.insert(patchheaderat + 1, '# User ' + user)
169 self.comments.insert(patchheaderat + 1, '# User ' + user)
170 except ValueError:
170 except ValueError:
171 if self.plainmode or self._hasheader(['Date: ']):
171 if self.plainmode or self._hasheader(['Date: ']):
172 self.comments = ['From: ' + user] + self.comments
172 self.comments = ['From: ' + user] + self.comments
173 else:
173 else:
174 tmp = ['# HG changeset patch', '# User ' + user, '']
174 tmp = ['# HG changeset patch', '# User ' + user, '']
175 self.comments = tmp + self.comments
175 self.comments = tmp + self.comments
176 self.user = user
176 self.user = user
177
177
178 def setdate(self, date):
178 def setdate(self, date):
179 if not self.updateheader(['Date: ', '# Date '], date):
179 if not self.updateheader(['Date: ', '# Date '], date):
180 try:
180 try:
181 patchheaderat = self.comments.index('# HG changeset patch')
181 patchheaderat = self.comments.index('# HG changeset patch')
182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
183 except ValueError:
183 except ValueError:
184 if self.plainmode or self._hasheader(['From: ']):
184 if self.plainmode or self._hasheader(['From: ']):
185 self.comments = ['Date: ' + date] + self.comments
185 self.comments = ['Date: ' + date] + self.comments
186 else:
186 else:
187 tmp = ['# HG changeset patch', '# Date ' + date, '']
187 tmp = ['# HG changeset patch', '# Date ' + date, '']
188 self.comments = tmp + self.comments
188 self.comments = tmp + self.comments
189 self.date = date
189 self.date = date
190
190
191 def setparent(self, parent):
191 def setparent(self, parent):
192 if not self.updateheader(['# Parent '], parent):
192 if not self.updateheader(['# Parent '], parent):
193 try:
193 try:
194 patchheaderat = self.comments.index('# HG changeset patch')
194 patchheaderat = self.comments.index('# HG changeset patch')
195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
196 except ValueError:
196 except ValueError:
197 pass
197 pass
198 self.parent = parent
198 self.parent = parent
199
199
200 def setmessage(self, message):
200 def setmessage(self, message):
201 if self.comments:
201 if self.comments:
202 self._delmsg()
202 self._delmsg()
203 self.message = [message]
203 self.message = [message]
204 self.comments += self.message
204 self.comments += self.message
205
205
206 def updateheader(self, prefixes, new):
206 def updateheader(self, prefixes, new):
207 '''Update all references to a field in the patch header.
207 '''Update all references to a field in the patch header.
208 Return whether the field is present.'''
208 Return whether the field is present.'''
209 res = False
209 res = False
210 for prefix in prefixes:
210 for prefix in prefixes:
211 for i in xrange(len(self.comments)):
211 for i in xrange(len(self.comments)):
212 if self.comments[i].startswith(prefix):
212 if self.comments[i].startswith(prefix):
213 self.comments[i] = prefix + new
213 self.comments[i] = prefix + new
214 res = True
214 res = True
215 break
215 break
216 return res
216 return res
217
217
218 def _hasheader(self, prefixes):
218 def _hasheader(self, prefixes):
219 '''Check if a header starts with any of the given prefixes.'''
219 '''Check if a header starts with any of the given prefixes.'''
220 for prefix in prefixes:
220 for prefix in prefixes:
221 for comment in self.comments:
221 for comment in self.comments:
222 if comment.startswith(prefix):
222 if comment.startswith(prefix):
223 return True
223 return True
224 return False
224 return False
225
225
226 def __str__(self):
226 def __str__(self):
227 if not self.comments:
227 if not self.comments:
228 return ''
228 return ''
229 return '\n'.join(self.comments) + '\n\n'
229 return '\n'.join(self.comments) + '\n\n'
230
230
231 def _delmsg(self):
231 def _delmsg(self):
232 '''Remove existing message, keeping the rest of the comments fields.
232 '''Remove existing message, keeping the rest of the comments fields.
233 If comments contains 'subject: ', message will prepend
233 If comments contains 'subject: ', message will prepend
234 the field and a blank line.'''
234 the field and a blank line.'''
235 if self.message:
235 if self.message:
236 subj = 'subject: ' + self.message[0].lower()
236 subj = 'subject: ' + self.message[0].lower()
237 for i in xrange(len(self.comments)):
237 for i in xrange(len(self.comments)):
238 if subj == self.comments[i].lower():
238 if subj == self.comments[i].lower():
239 del self.comments[i]
239 del self.comments[i]
240 self.message = self.message[2:]
240 self.message = self.message[2:]
241 break
241 break
242 ci = 0
242 ci = 0
243 for mi in self.message:
243 for mi in self.message:
244 while mi != self.comments[ci]:
244 while mi != self.comments[ci]:
245 ci += 1
245 ci += 1
246 del self.comments[ci]
246 del self.comments[ci]
247
247
248 class queue(object):
248 class queue(object):
249 def __init__(self, ui, path, patchdir=None):
249 def __init__(self, ui, path, patchdir=None):
250 self.basepath = path
250 self.basepath = path
251 try:
251 try:
252 fh = open(os.path.join(path, 'patches.queue'))
252 fh = open(os.path.join(path, 'patches.queue'))
253 cur = fh.read().rstrip()
253 cur = fh.read().rstrip()
254 fh.close()
254 if not cur:
255 if not cur:
255 curpath = os.path.join(path, 'patches')
256 curpath = os.path.join(path, 'patches')
256 else:
257 else:
257 curpath = os.path.join(path, 'patches-' + cur)
258 curpath = os.path.join(path, 'patches-' + cur)
258 except IOError:
259 except IOError:
259 curpath = os.path.join(path, 'patches')
260 curpath = os.path.join(path, 'patches')
260 self.path = patchdir or curpath
261 self.path = patchdir or curpath
261 self.opener = util.opener(self.path)
262 self.opener = util.opener(self.path)
262 self.ui = ui
263 self.ui = ui
263 self.applied_dirty = 0
264 self.applied_dirty = 0
264 self.series_dirty = 0
265 self.series_dirty = 0
265 self.added = []
266 self.added = []
266 self.series_path = "series"
267 self.series_path = "series"
267 self.status_path = "status"
268 self.status_path = "status"
268 self.guards_path = "guards"
269 self.guards_path = "guards"
269 self.active_guards = None
270 self.active_guards = None
270 self.guards_dirty = False
271 self.guards_dirty = False
271 # Handle mq.git as a bool with extended values
272 # Handle mq.git as a bool with extended values
272 try:
273 try:
273 gitmode = ui.configbool('mq', 'git', None)
274 gitmode = ui.configbool('mq', 'git', None)
274 if gitmode is None:
275 if gitmode is None:
275 raise error.ConfigError()
276 raise error.ConfigError()
276 self.gitmode = gitmode and 'yes' or 'no'
277 self.gitmode = gitmode and 'yes' or 'no'
277 except error.ConfigError:
278 except error.ConfigError:
278 self.gitmode = ui.config('mq', 'git', 'auto').lower()
279 self.gitmode = ui.config('mq', 'git', 'auto').lower()
279 self.plainmode = ui.configbool('mq', 'plain', False)
280 self.plainmode = ui.configbool('mq', 'plain', False)
280
281
281 @util.propertycache
282 @util.propertycache
282 def applied(self):
283 def applied(self):
283 if os.path.exists(self.join(self.status_path)):
284 if os.path.exists(self.join(self.status_path)):
284 def parse(l):
285 def parse(l):
285 n, name = l.split(':', 1)
286 n, name = l.split(':', 1)
286 return statusentry(bin(n), name)
287 return statusentry(bin(n), name)
287 lines = self.opener(self.status_path).read().splitlines()
288 lines = self.opener(self.status_path).read().splitlines()
288 return [parse(l) for l in lines]
289 return [parse(l) for l in lines]
289 return []
290 return []
290
291
291 @util.propertycache
292 @util.propertycache
292 def full_series(self):
293 def full_series(self):
293 if os.path.exists(self.join(self.series_path)):
294 if os.path.exists(self.join(self.series_path)):
294 return self.opener(self.series_path).read().splitlines()
295 return self.opener(self.series_path).read().splitlines()
295 return []
296 return []
296
297
297 @util.propertycache
298 @util.propertycache
298 def series(self):
299 def series(self):
299 self.parse_series()
300 self.parse_series()
300 return self.series
301 return self.series
301
302
302 @util.propertycache
303 @util.propertycache
303 def series_guards(self):
304 def series_guards(self):
304 self.parse_series()
305 self.parse_series()
305 return self.series_guards
306 return self.series_guards
306
307
307 def invalidate(self):
308 def invalidate(self):
308 for a in 'applied full_series series series_guards'.split():
309 for a in 'applied full_series series series_guards'.split():
309 if a in self.__dict__:
310 if a in self.__dict__:
310 delattr(self, a)
311 delattr(self, a)
311 self.applied_dirty = 0
312 self.applied_dirty = 0
312 self.series_dirty = 0
313 self.series_dirty = 0
313 self.guards_dirty = False
314 self.guards_dirty = False
314 self.active_guards = None
315 self.active_guards = None
315
316
316 def diffopts(self, opts={}, patchfn=None):
317 def diffopts(self, opts={}, patchfn=None):
317 diffopts = patch.diffopts(self.ui, opts)
318 diffopts = patch.diffopts(self.ui, opts)
318 if self.gitmode == 'auto':
319 if self.gitmode == 'auto':
319 diffopts.upgrade = True
320 diffopts.upgrade = True
320 elif self.gitmode == 'keep':
321 elif self.gitmode == 'keep':
321 pass
322 pass
322 elif self.gitmode in ('yes', 'no'):
323 elif self.gitmode in ('yes', 'no'):
323 diffopts.git = self.gitmode == 'yes'
324 diffopts.git = self.gitmode == 'yes'
324 else:
325 else:
325 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
326 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
326 ' got %s') % self.gitmode)
327 ' got %s') % self.gitmode)
327 if patchfn:
328 if patchfn:
328 diffopts = self.patchopts(diffopts, patchfn)
329 diffopts = self.patchopts(diffopts, patchfn)
329 return diffopts
330 return diffopts
330
331
331 def patchopts(self, diffopts, *patches):
332 def patchopts(self, diffopts, *patches):
332 """Return a copy of input diff options with git set to true if
333 """Return a copy of input diff options with git set to true if
333 referenced patch is a git patch and should be preserved as such.
334 referenced patch is a git patch and should be preserved as such.
334 """
335 """
335 diffopts = diffopts.copy()
336 diffopts = diffopts.copy()
336 if not diffopts.git and self.gitmode == 'keep':
337 if not diffopts.git and self.gitmode == 'keep':
337 for patchfn in patches:
338 for patchfn in patches:
338 patchf = self.opener(patchfn, 'r')
339 patchf = self.opener(patchfn, 'r')
339 # if the patch was a git patch, refresh it as a git patch
340 # if the patch was a git patch, refresh it as a git patch
340 for line in patchf:
341 for line in patchf:
341 if line.startswith('diff --git'):
342 if line.startswith('diff --git'):
342 diffopts.git = True
343 diffopts.git = True
343 break
344 break
344 patchf.close()
345 patchf.close()
345 return diffopts
346 return diffopts
346
347
347 def join(self, *p):
348 def join(self, *p):
348 return os.path.join(self.path, *p)
349 return os.path.join(self.path, *p)
349
350
350 def find_series(self, patch):
351 def find_series(self, patch):
351 def matchpatch(l):
352 def matchpatch(l):
352 l = l.split('#', 1)[0]
353 l = l.split('#', 1)[0]
353 return l.strip() == patch
354 return l.strip() == patch
354 for index, l in enumerate(self.full_series):
355 for index, l in enumerate(self.full_series):
355 if matchpatch(l):
356 if matchpatch(l):
356 return index
357 return index
357 return None
358 return None
358
359
359 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
360 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
360
361
361 def parse_series(self):
362 def parse_series(self):
362 self.series = []
363 self.series = []
363 self.series_guards = []
364 self.series_guards = []
364 for l in self.full_series:
365 for l in self.full_series:
365 h = l.find('#')
366 h = l.find('#')
366 if h == -1:
367 if h == -1:
367 patch = l
368 patch = l
368 comment = ''
369 comment = ''
369 elif h == 0:
370 elif h == 0:
370 continue
371 continue
371 else:
372 else:
372 patch = l[:h]
373 patch = l[:h]
373 comment = l[h:]
374 comment = l[h:]
374 patch = patch.strip()
375 patch = patch.strip()
375 if patch:
376 if patch:
376 if patch in self.series:
377 if patch in self.series:
377 raise util.Abort(_('%s appears more than once in %s') %
378 raise util.Abort(_('%s appears more than once in %s') %
378 (patch, self.join(self.series_path)))
379 (patch, self.join(self.series_path)))
379 self.series.append(patch)
380 self.series.append(patch)
380 self.series_guards.append(self.guard_re.findall(comment))
381 self.series_guards.append(self.guard_re.findall(comment))
381
382
382 def check_guard(self, guard):
383 def check_guard(self, guard):
383 if not guard:
384 if not guard:
384 return _('guard cannot be an empty string')
385 return _('guard cannot be an empty string')
385 bad_chars = '# \t\r\n\f'
386 bad_chars = '# \t\r\n\f'
386 first = guard[0]
387 first = guard[0]
387 if first in '-+':
388 if first in '-+':
388 return (_('guard %r starts with invalid character: %r') %
389 return (_('guard %r starts with invalid character: %r') %
389 (guard, first))
390 (guard, first))
390 for c in bad_chars:
391 for c in bad_chars:
391 if c in guard:
392 if c in guard:
392 return _('invalid character in guard %r: %r') % (guard, c)
393 return _('invalid character in guard %r: %r') % (guard, c)
393
394
394 def set_active(self, guards):
395 def set_active(self, guards):
395 for guard in guards:
396 for guard in guards:
396 bad = self.check_guard(guard)
397 bad = self.check_guard(guard)
397 if bad:
398 if bad:
398 raise util.Abort(bad)
399 raise util.Abort(bad)
399 guards = sorted(set(guards))
400 guards = sorted(set(guards))
400 self.ui.debug('active guards: %s\n' % ' '.join(guards))
401 self.ui.debug('active guards: %s\n' % ' '.join(guards))
401 self.active_guards = guards
402 self.active_guards = guards
402 self.guards_dirty = True
403 self.guards_dirty = True
403
404
404 def active(self):
405 def active(self):
405 if self.active_guards is None:
406 if self.active_guards is None:
406 self.active_guards = []
407 self.active_guards = []
407 try:
408 try:
408 guards = self.opener(self.guards_path).read().split()
409 guards = self.opener(self.guards_path).read().split()
409 except IOError, err:
410 except IOError, err:
410 if err.errno != errno.ENOENT:
411 if err.errno != errno.ENOENT:
411 raise
412 raise
412 guards = []
413 guards = []
413 for i, guard in enumerate(guards):
414 for i, guard in enumerate(guards):
414 bad = self.check_guard(guard)
415 bad = self.check_guard(guard)
415 if bad:
416 if bad:
416 self.ui.warn('%s:%d: %s\n' %
417 self.ui.warn('%s:%d: %s\n' %
417 (self.join(self.guards_path), i + 1, bad))
418 (self.join(self.guards_path), i + 1, bad))
418 else:
419 else:
419 self.active_guards.append(guard)
420 self.active_guards.append(guard)
420 return self.active_guards
421 return self.active_guards
421
422
422 def set_guards(self, idx, guards):
423 def set_guards(self, idx, guards):
423 for g in guards:
424 for g in guards:
424 if len(g) < 2:
425 if len(g) < 2:
425 raise util.Abort(_('guard %r too short') % g)
426 raise util.Abort(_('guard %r too short') % g)
426 if g[0] not in '-+':
427 if g[0] not in '-+':
427 raise util.Abort(_('guard %r starts with invalid char') % g)
428 raise util.Abort(_('guard %r starts with invalid char') % g)
428 bad = self.check_guard(g[1:])
429 bad = self.check_guard(g[1:])
429 if bad:
430 if bad:
430 raise util.Abort(bad)
431 raise util.Abort(bad)
431 drop = self.guard_re.sub('', self.full_series[idx])
432 drop = self.guard_re.sub('', self.full_series[idx])
432 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
433 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
433 self.parse_series()
434 self.parse_series()
434 self.series_dirty = True
435 self.series_dirty = True
435
436
436 def pushable(self, idx):
437 def pushable(self, idx):
437 if isinstance(idx, str):
438 if isinstance(idx, str):
438 idx = self.series.index(idx)
439 idx = self.series.index(idx)
439 patchguards = self.series_guards[idx]
440 patchguards = self.series_guards[idx]
440 if not patchguards:
441 if not patchguards:
441 return True, None
442 return True, None
442 guards = self.active()
443 guards = self.active()
443 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
444 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
444 if exactneg:
445 if exactneg:
445 return False, exactneg[0]
446 return False, exactneg[0]
446 pos = [g for g in patchguards if g[0] == '+']
447 pos = [g for g in patchguards if g[0] == '+']
447 exactpos = [g for g in pos if g[1:] in guards]
448 exactpos = [g for g in pos if g[1:] in guards]
448 if pos:
449 if pos:
449 if exactpos:
450 if exactpos:
450 return True, exactpos[0]
451 return True, exactpos[0]
451 return False, pos
452 return False, pos
452 return True, ''
453 return True, ''
453
454
454 def explain_pushable(self, idx, all_patches=False):
455 def explain_pushable(self, idx, all_patches=False):
455 write = all_patches and self.ui.write or self.ui.warn
456 write = all_patches and self.ui.write or self.ui.warn
456 if all_patches or self.ui.verbose:
457 if all_patches or self.ui.verbose:
457 if isinstance(idx, str):
458 if isinstance(idx, str):
458 idx = self.series.index(idx)
459 idx = self.series.index(idx)
459 pushable, why = self.pushable(idx)
460 pushable, why = self.pushable(idx)
460 if all_patches and pushable:
461 if all_patches and pushable:
461 if why is None:
462 if why is None:
462 write(_('allowing %s - no guards in effect\n') %
463 write(_('allowing %s - no guards in effect\n') %
463 self.series[idx])
464 self.series[idx])
464 else:
465 else:
465 if not why:
466 if not why:
466 write(_('allowing %s - no matching negative guards\n') %
467 write(_('allowing %s - no matching negative guards\n') %
467 self.series[idx])
468 self.series[idx])
468 else:
469 else:
469 write(_('allowing %s - guarded by %r\n') %
470 write(_('allowing %s - guarded by %r\n') %
470 (self.series[idx], why))
471 (self.series[idx], why))
471 if not pushable:
472 if not pushable:
472 if why:
473 if why:
473 write(_('skipping %s - guarded by %r\n') %
474 write(_('skipping %s - guarded by %r\n') %
474 (self.series[idx], why))
475 (self.series[idx], why))
475 else:
476 else:
476 write(_('skipping %s - no matching guards\n') %
477 write(_('skipping %s - no matching guards\n') %
477 self.series[idx])
478 self.series[idx])
478
479
479 def save_dirty(self):
480 def save_dirty(self):
480 def write_list(items, path):
481 def write_list(items, path):
481 fp = self.opener(path, 'w')
482 fp = self.opener(path, 'w')
482 for i in items:
483 for i in items:
483 fp.write("%s\n" % i)
484 fp.write("%s\n" % i)
484 fp.close()
485 fp.close()
485 if self.applied_dirty:
486 if self.applied_dirty:
486 write_list(map(str, self.applied), self.status_path)
487 write_list(map(str, self.applied), self.status_path)
487 if self.series_dirty:
488 if self.series_dirty:
488 write_list(self.full_series, self.series_path)
489 write_list(self.full_series, self.series_path)
489 if self.guards_dirty:
490 if self.guards_dirty:
490 write_list(self.active_guards, self.guards_path)
491 write_list(self.active_guards, self.guards_path)
491 if self.added:
492 if self.added:
492 qrepo = self.qrepo()
493 qrepo = self.qrepo()
493 if qrepo:
494 if qrepo:
494 qrepo[None].add(f for f in self.added if f not in qrepo[None])
495 qrepo[None].add(f for f in self.added if f not in qrepo[None])
495 self.added = []
496 self.added = []
496
497
497 def removeundo(self, repo):
498 def removeundo(self, repo):
498 undo = repo.sjoin('undo')
499 undo = repo.sjoin('undo')
499 if not os.path.exists(undo):
500 if not os.path.exists(undo):
500 return
501 return
501 try:
502 try:
502 os.unlink(undo)
503 os.unlink(undo)
503 except OSError, inst:
504 except OSError, inst:
504 self.ui.warn(_('error removing undo: %s\n') % str(inst))
505 self.ui.warn(_('error removing undo: %s\n') % str(inst))
505
506
506 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
507 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
507 fp=None, changes=None, opts={}):
508 fp=None, changes=None, opts={}):
508 stat = opts.get('stat')
509 stat = opts.get('stat')
509 m = cmdutil.match(repo, files, opts)
510 m = cmdutil.match(repo, files, opts)
510 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
511 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
511 changes, stat, fp)
512 changes, stat, fp)
512
513
513 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
514 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
514 # first try just applying the patch
515 # first try just applying the patch
515 (err, n) = self.apply(repo, [patch], update_status=False,
516 (err, n) = self.apply(repo, [patch], update_status=False,
516 strict=True, merge=rev)
517 strict=True, merge=rev)
517
518
518 if err == 0:
519 if err == 0:
519 return (err, n)
520 return (err, n)
520
521
521 if n is None:
522 if n is None:
522 raise util.Abort(_("apply failed for patch %s") % patch)
523 raise util.Abort(_("apply failed for patch %s") % patch)
523
524
524 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
525 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
525
526
526 # apply failed, strip away that rev and merge.
527 # apply failed, strip away that rev and merge.
527 hg.clean(repo, head)
528 hg.clean(repo, head)
528 self.strip(repo, [n], update=False, backup='strip')
529 self.strip(repo, [n], update=False, backup='strip')
529
530
530 ctx = repo[rev]
531 ctx = repo[rev]
531 ret = hg.merge(repo, rev)
532 ret = hg.merge(repo, rev)
532 if ret:
533 if ret:
533 raise util.Abort(_("update returned %d") % ret)
534 raise util.Abort(_("update returned %d") % ret)
534 n = repo.commit(ctx.description(), ctx.user(), force=True)
535 n = repo.commit(ctx.description(), ctx.user(), force=True)
535 if n is None:
536 if n is None:
536 raise util.Abort(_("repo commit failed"))
537 raise util.Abort(_("repo commit failed"))
537 try:
538 try:
538 ph = patchheader(mergeq.join(patch), self.plainmode)
539 ph = patchheader(mergeq.join(patch), self.plainmode)
539 except:
540 except:
540 raise util.Abort(_("unable to read %s") % patch)
541 raise util.Abort(_("unable to read %s") % patch)
541
542
542 diffopts = self.patchopts(diffopts, patch)
543 diffopts = self.patchopts(diffopts, patch)
543 patchf = self.opener(patch, "w")
544 patchf = self.opener(patch, "w")
544 comments = str(ph)
545 comments = str(ph)
545 if comments:
546 if comments:
546 patchf.write(comments)
547 patchf.write(comments)
547 self.printdiff(repo, diffopts, head, n, fp=patchf)
548 self.printdiff(repo, diffopts, head, n, fp=patchf)
548 patchf.close()
549 patchf.close()
549 self.removeundo(repo)
550 self.removeundo(repo)
550 return (0, n)
551 return (0, n)
551
552
552 def qparents(self, repo, rev=None):
553 def qparents(self, repo, rev=None):
553 if rev is None:
554 if rev is None:
554 (p1, p2) = repo.dirstate.parents()
555 (p1, p2) = repo.dirstate.parents()
555 if p2 == nullid:
556 if p2 == nullid:
556 return p1
557 return p1
557 if not self.applied:
558 if not self.applied:
558 return None
559 return None
559 return self.applied[-1].node
560 return self.applied[-1].node
560 p1, p2 = repo.changelog.parents(rev)
561 p1, p2 = repo.changelog.parents(rev)
561 if p2 != nullid and p2 in [x.node for x in self.applied]:
562 if p2 != nullid and p2 in [x.node for x in self.applied]:
562 return p2
563 return p2
563 return p1
564 return p1
564
565
565 def mergepatch(self, repo, mergeq, series, diffopts):
566 def mergepatch(self, repo, mergeq, series, diffopts):
566 if not self.applied:
567 if not self.applied:
567 # each of the patches merged in will have two parents. This
568 # each of the patches merged in will have two parents. This
568 # can confuse the qrefresh, qdiff, and strip code because it
569 # can confuse the qrefresh, qdiff, and strip code because it
569 # needs to know which parent is actually in the patch queue.
570 # needs to know which parent is actually in the patch queue.
570 # so, we insert a merge marker with only one parent. This way
571 # so, we insert a merge marker with only one parent. This way
571 # the first patch in the queue is never a merge patch
572 # the first patch in the queue is never a merge patch
572 #
573 #
573 pname = ".hg.patches.merge.marker"
574 pname = ".hg.patches.merge.marker"
574 n = repo.commit('[mq]: merge marker', force=True)
575 n = repo.commit('[mq]: merge marker', force=True)
575 self.removeundo(repo)
576 self.removeundo(repo)
576 self.applied.append(statusentry(n, pname))
577 self.applied.append(statusentry(n, pname))
577 self.applied_dirty = 1
578 self.applied_dirty = 1
578
579
579 head = self.qparents(repo)
580 head = self.qparents(repo)
580
581
581 for patch in series:
582 for patch in series:
582 patch = mergeq.lookup(patch, strict=True)
583 patch = mergeq.lookup(patch, strict=True)
583 if not patch:
584 if not patch:
584 self.ui.warn(_("patch %s does not exist\n") % patch)
585 self.ui.warn(_("patch %s does not exist\n") % patch)
585 return (1, None)
586 return (1, None)
586 pushable, reason = self.pushable(patch)
587 pushable, reason = self.pushable(patch)
587 if not pushable:
588 if not pushable:
588 self.explain_pushable(patch, all_patches=True)
589 self.explain_pushable(patch, all_patches=True)
589 continue
590 continue
590 info = mergeq.isapplied(patch)
591 info = mergeq.isapplied(patch)
591 if not info:
592 if not info:
592 self.ui.warn(_("patch %s is not applied\n") % patch)
593 self.ui.warn(_("patch %s is not applied\n") % patch)
593 return (1, None)
594 return (1, None)
594 rev = info[1]
595 rev = info[1]
595 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
596 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
596 if head:
597 if head:
597 self.applied.append(statusentry(head, patch))
598 self.applied.append(statusentry(head, patch))
598 self.applied_dirty = 1
599 self.applied_dirty = 1
599 if err:
600 if err:
600 return (err, head)
601 return (err, head)
601 self.save_dirty()
602 self.save_dirty()
602 return (0, head)
603 return (0, head)
603
604
604 def patch(self, repo, patchfile):
605 def patch(self, repo, patchfile):
605 '''Apply patchfile to the working directory.
606 '''Apply patchfile to the working directory.
606 patchfile: name of patch file'''
607 patchfile: name of patch file'''
607 files = {}
608 files = {}
608 try:
609 try:
609 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
610 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
610 files=files, eolmode=None)
611 files=files, eolmode=None)
611 except Exception, inst:
612 except Exception, inst:
612 self.ui.note(str(inst) + '\n')
613 self.ui.note(str(inst) + '\n')
613 if not self.ui.verbose:
614 if not self.ui.verbose:
614 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
615 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
615 return (False, files, False)
616 return (False, files, False)
616
617
617 return (True, files, fuzz)
618 return (True, files, fuzz)
618
619
619 def apply(self, repo, series, list=False, update_status=True,
620 def apply(self, repo, series, list=False, update_status=True,
620 strict=False, patchdir=None, merge=None, all_files=None):
621 strict=False, patchdir=None, merge=None, all_files=None):
621 wlock = lock = tr = None
622 wlock = lock = tr = None
622 try:
623 try:
623 wlock = repo.wlock()
624 wlock = repo.wlock()
624 lock = repo.lock()
625 lock = repo.lock()
625 tr = repo.transaction("qpush")
626 tr = repo.transaction("qpush")
626 try:
627 try:
627 ret = self._apply(repo, series, list, update_status,
628 ret = self._apply(repo, series, list, update_status,
628 strict, patchdir, merge, all_files=all_files)
629 strict, patchdir, merge, all_files=all_files)
629 tr.close()
630 tr.close()
630 self.save_dirty()
631 self.save_dirty()
631 return ret
632 return ret
632 except:
633 except:
633 try:
634 try:
634 tr.abort()
635 tr.abort()
635 finally:
636 finally:
636 repo.invalidate()
637 repo.invalidate()
637 repo.dirstate.invalidate()
638 repo.dirstate.invalidate()
638 raise
639 raise
639 finally:
640 finally:
640 release(tr, lock, wlock)
641 release(tr, lock, wlock)
641 self.removeundo(repo)
642 self.removeundo(repo)
642
643
643 def _apply(self, repo, series, list=False, update_status=True,
644 def _apply(self, repo, series, list=False, update_status=True,
644 strict=False, patchdir=None, merge=None, all_files=None):
645 strict=False, patchdir=None, merge=None, all_files=None):
645 '''returns (error, hash)
646 '''returns (error, hash)
646 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
647 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
647 # TODO unify with commands.py
648 # TODO unify with commands.py
648 if not patchdir:
649 if not patchdir:
649 patchdir = self.path
650 patchdir = self.path
650 err = 0
651 err = 0
651 n = None
652 n = None
652 for patchname in series:
653 for patchname in series:
653 pushable, reason = self.pushable(patchname)
654 pushable, reason = self.pushable(patchname)
654 if not pushable:
655 if not pushable:
655 self.explain_pushable(patchname, all_patches=True)
656 self.explain_pushable(patchname, all_patches=True)
656 continue
657 continue
657 self.ui.status(_("applying %s\n") % patchname)
658 self.ui.status(_("applying %s\n") % patchname)
658 pf = os.path.join(patchdir, patchname)
659 pf = os.path.join(patchdir, patchname)
659
660
660 try:
661 try:
661 ph = patchheader(self.join(patchname), self.plainmode)
662 ph = patchheader(self.join(patchname), self.plainmode)
662 except:
663 except:
663 self.ui.warn(_("unable to read %s\n") % patchname)
664 self.ui.warn(_("unable to read %s\n") % patchname)
664 err = 1
665 err = 1
665 break
666 break
666
667
667 message = ph.message
668 message = ph.message
668 if not message:
669 if not message:
669 # The commit message should not be translated
670 # The commit message should not be translated
670 message = "imported patch %s\n" % patchname
671 message = "imported patch %s\n" % patchname
671 else:
672 else:
672 if list:
673 if list:
673 # The commit message should not be translated
674 # The commit message should not be translated
674 message.append("\nimported patch %s" % patchname)
675 message.append("\nimported patch %s" % patchname)
675 message = '\n'.join(message)
676 message = '\n'.join(message)
676
677
677 if ph.haspatch:
678 if ph.haspatch:
678 (patcherr, files, fuzz) = self.patch(repo, pf)
679 (patcherr, files, fuzz) = self.patch(repo, pf)
679 if all_files is not None:
680 if all_files is not None:
680 all_files.update(files)
681 all_files.update(files)
681 patcherr = not patcherr
682 patcherr = not patcherr
682 else:
683 else:
683 self.ui.warn(_("patch %s is empty\n") % patchname)
684 self.ui.warn(_("patch %s is empty\n") % patchname)
684 patcherr, files, fuzz = 0, [], 0
685 patcherr, files, fuzz = 0, [], 0
685
686
686 if merge and files:
687 if merge and files:
687 # Mark as removed/merged and update dirstate parent info
688 # Mark as removed/merged and update dirstate parent info
688 removed = []
689 removed = []
689 merged = []
690 merged = []
690 for f in files:
691 for f in files:
691 if os.path.lexists(repo.wjoin(f)):
692 if os.path.lexists(repo.wjoin(f)):
692 merged.append(f)
693 merged.append(f)
693 else:
694 else:
694 removed.append(f)
695 removed.append(f)
695 for f in removed:
696 for f in removed:
696 repo.dirstate.remove(f)
697 repo.dirstate.remove(f)
697 for f in merged:
698 for f in merged:
698 repo.dirstate.merge(f)
699 repo.dirstate.merge(f)
699 p1, p2 = repo.dirstate.parents()
700 p1, p2 = repo.dirstate.parents()
700 repo.dirstate.setparents(p1, merge)
701 repo.dirstate.setparents(p1, merge)
701
702
702 files = cmdutil.updatedir(self.ui, repo, files)
703 files = cmdutil.updatedir(self.ui, repo, files)
703 match = cmdutil.matchfiles(repo, files or [])
704 match = cmdutil.matchfiles(repo, files or [])
704 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
705 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
705
706
706 if n is None:
707 if n is None:
707 raise util.Abort(_("repository commit failed"))
708 raise util.Abort(_("repository commit failed"))
708
709
709 if update_status:
710 if update_status:
710 self.applied.append(statusentry(n, patchname))
711 self.applied.append(statusentry(n, patchname))
711
712
712 if patcherr:
713 if patcherr:
713 self.ui.warn(_("patch failed, rejects left in working dir\n"))
714 self.ui.warn(_("patch failed, rejects left in working dir\n"))
714 err = 2
715 err = 2
715 break
716 break
716
717
717 if fuzz and strict:
718 if fuzz and strict:
718 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
719 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
719 err = 3
720 err = 3
720 break
721 break
721 return (err, n)
722 return (err, n)
722
723
723 def _cleanup(self, patches, numrevs, keep=False):
724 def _cleanup(self, patches, numrevs, keep=False):
724 if not keep:
725 if not keep:
725 r = self.qrepo()
726 r = self.qrepo()
726 if r:
727 if r:
727 r[None].remove(patches, True)
728 r[None].remove(patches, True)
728 else:
729 else:
729 for p in patches:
730 for p in patches:
730 os.unlink(self.join(p))
731 os.unlink(self.join(p))
731
732
732 if numrevs:
733 if numrevs:
733 del self.applied[:numrevs]
734 del self.applied[:numrevs]
734 self.applied_dirty = 1
735 self.applied_dirty = 1
735
736
736 for i in sorted([self.find_series(p) for p in patches], reverse=True):
737 for i in sorted([self.find_series(p) for p in patches], reverse=True):
737 del self.full_series[i]
738 del self.full_series[i]
738 self.parse_series()
739 self.parse_series()
739 self.series_dirty = 1
740 self.series_dirty = 1
740
741
741 def _revpatches(self, repo, revs):
742 def _revpatches(self, repo, revs):
742 firstrev = repo[self.applied[0].node].rev()
743 firstrev = repo[self.applied[0].node].rev()
743 patches = []
744 patches = []
744 for i, rev in enumerate(revs):
745 for i, rev in enumerate(revs):
745
746
746 if rev < firstrev:
747 if rev < firstrev:
747 raise util.Abort(_('revision %d is not managed') % rev)
748 raise util.Abort(_('revision %d is not managed') % rev)
748
749
749 ctx = repo[rev]
750 ctx = repo[rev]
750 base = self.applied[i].node
751 base = self.applied[i].node
751 if ctx.node() != base:
752 if ctx.node() != base:
752 msg = _('cannot delete revision %d above applied patches')
753 msg = _('cannot delete revision %d above applied patches')
753 raise util.Abort(msg % rev)
754 raise util.Abort(msg % rev)
754
755
755 patch = self.applied[i].name
756 patch = self.applied[i].name
756 for fmt in ('[mq]: %s', 'imported patch %s'):
757 for fmt in ('[mq]: %s', 'imported patch %s'):
757 if ctx.description() == fmt % patch:
758 if ctx.description() == fmt % patch:
758 msg = _('patch %s finalized without changeset message\n')
759 msg = _('patch %s finalized without changeset message\n')
759 repo.ui.status(msg % patch)
760 repo.ui.status(msg % patch)
760 break
761 break
761
762
762 patches.append(patch)
763 patches.append(patch)
763 return patches
764 return patches
764
765
765 def finish(self, repo, revs):
766 def finish(self, repo, revs):
766 patches = self._revpatches(repo, sorted(revs))
767 patches = self._revpatches(repo, sorted(revs))
767 self._cleanup(patches, len(patches))
768 self._cleanup(patches, len(patches))
768
769
769 def delete(self, repo, patches, opts):
770 def delete(self, repo, patches, opts):
770 if not patches and not opts.get('rev'):
771 if not patches and not opts.get('rev'):
771 raise util.Abort(_('qdelete requires at least one revision or '
772 raise util.Abort(_('qdelete requires at least one revision or '
772 'patch name'))
773 'patch name'))
773
774
774 realpatches = []
775 realpatches = []
775 for patch in patches:
776 for patch in patches:
776 patch = self.lookup(patch, strict=True)
777 patch = self.lookup(patch, strict=True)
777 info = self.isapplied(patch)
778 info = self.isapplied(patch)
778 if info:
779 if info:
779 raise util.Abort(_("cannot delete applied patch %s") % patch)
780 raise util.Abort(_("cannot delete applied patch %s") % patch)
780 if patch not in self.series:
781 if patch not in self.series:
781 raise util.Abort(_("patch %s not in series file") % patch)
782 raise util.Abort(_("patch %s not in series file") % patch)
782 if patch not in realpatches:
783 if patch not in realpatches:
783 realpatches.append(patch)
784 realpatches.append(patch)
784
785
785 numrevs = 0
786 numrevs = 0
786 if opts.get('rev'):
787 if opts.get('rev'):
787 if not self.applied:
788 if not self.applied:
788 raise util.Abort(_('no patches applied'))
789 raise util.Abort(_('no patches applied'))
789 revs = cmdutil.revrange(repo, opts.get('rev'))
790 revs = cmdutil.revrange(repo, opts.get('rev'))
790 if len(revs) > 1 and revs[0] > revs[1]:
791 if len(revs) > 1 and revs[0] > revs[1]:
791 revs.reverse()
792 revs.reverse()
792 revpatches = self._revpatches(repo, revs)
793 revpatches = self._revpatches(repo, revs)
793 realpatches += revpatches
794 realpatches += revpatches
794 numrevs = len(revpatches)
795 numrevs = len(revpatches)
795
796
796 self._cleanup(realpatches, numrevs, opts.get('keep'))
797 self._cleanup(realpatches, numrevs, opts.get('keep'))
797
798
798 def check_toppatch(self, repo):
799 def check_toppatch(self, repo):
799 if self.applied:
800 if self.applied:
800 top = self.applied[-1].node
801 top = self.applied[-1].node
801 patch = self.applied[-1].name
802 patch = self.applied[-1].name
802 pp = repo.dirstate.parents()
803 pp = repo.dirstate.parents()
803 if top not in pp:
804 if top not in pp:
804 raise util.Abort(_("working directory revision is not qtip"))
805 raise util.Abort(_("working directory revision is not qtip"))
805 return top, patch
806 return top, patch
806 return None, None
807 return None, None
807
808
808 def check_substate(self, repo):
809 def check_substate(self, repo):
809 '''return list of subrepos at a different revision than substate.
810 '''return list of subrepos at a different revision than substate.
810 Abort if any subrepos have uncommitted changes.'''
811 Abort if any subrepos have uncommitted changes.'''
811 inclsubs = []
812 inclsubs = []
812 wctx = repo[None]
813 wctx = repo[None]
813 for s in wctx.substate:
814 for s in wctx.substate:
814 if wctx.sub(s).dirty(True):
815 if wctx.sub(s).dirty(True):
815 raise util.Abort(
816 raise util.Abort(
816 _("uncommitted changes in subrepository %s") % s)
817 _("uncommitted changes in subrepository %s") % s)
817 elif wctx.sub(s).dirty():
818 elif wctx.sub(s).dirty():
818 inclsubs.append(s)
819 inclsubs.append(s)
819 return inclsubs
820 return inclsubs
820
821
821 def check_localchanges(self, repo, force=False, refresh=True):
822 def check_localchanges(self, repo, force=False, refresh=True):
822 m, a, r, d = repo.status()[:4]
823 m, a, r, d = repo.status()[:4]
823 if (m or a or r or d) and not force:
824 if (m or a or r or d) and not force:
824 if refresh:
825 if refresh:
825 raise util.Abort(_("local changes found, refresh first"))
826 raise util.Abort(_("local changes found, refresh first"))
826 else:
827 else:
827 raise util.Abort(_("local changes found"))
828 raise util.Abort(_("local changes found"))
828 return m, a, r, d
829 return m, a, r, d
829
830
830 _reserved = ('series', 'status', 'guards')
831 _reserved = ('series', 'status', 'guards')
831 def check_reserved_name(self, name):
832 def check_reserved_name(self, name):
832 if (name in self._reserved or name.startswith('.hg')
833 if (name in self._reserved or name.startswith('.hg')
833 or name.startswith('.mq') or '#' in name or ':' in name):
834 or name.startswith('.mq') or '#' in name or ':' in name):
834 raise util.Abort(_('"%s" cannot be used as the name of a patch')
835 raise util.Abort(_('"%s" cannot be used as the name of a patch')
835 % name)
836 % name)
836
837
837 def new(self, repo, patchfn, *pats, **opts):
838 def new(self, repo, patchfn, *pats, **opts):
838 """options:
839 """options:
839 msg: a string or a no-argument function returning a string
840 msg: a string or a no-argument function returning a string
840 """
841 """
841 msg = opts.get('msg')
842 msg = opts.get('msg')
842 user = opts.get('user')
843 user = opts.get('user')
843 date = opts.get('date')
844 date = opts.get('date')
844 if date:
845 if date:
845 date = util.parsedate(date)
846 date = util.parsedate(date)
846 diffopts = self.diffopts({'git': opts.get('git')})
847 diffopts = self.diffopts({'git': opts.get('git')})
847 self.check_reserved_name(patchfn)
848 self.check_reserved_name(patchfn)
848 if os.path.exists(self.join(patchfn)):
849 if os.path.exists(self.join(patchfn)):
849 if os.path.isdir(self.join(patchfn)):
850 if os.path.isdir(self.join(patchfn)):
850 raise util.Abort(_('"%s" already exists as a directory')
851 raise util.Abort(_('"%s" already exists as a directory')
851 % patchfn)
852 % patchfn)
852 else:
853 else:
853 raise util.Abort(_('patch "%s" already exists') % patchfn)
854 raise util.Abort(_('patch "%s" already exists') % patchfn)
854
855
855 inclsubs = self.check_substate(repo)
856 inclsubs = self.check_substate(repo)
856 if inclsubs:
857 if inclsubs:
857 inclsubs.append('.hgsubstate')
858 inclsubs.append('.hgsubstate')
858 if opts.get('include') or opts.get('exclude') or pats:
859 if opts.get('include') or opts.get('exclude') or pats:
859 if inclsubs:
860 if inclsubs:
860 pats = list(pats or []) + inclsubs
861 pats = list(pats or []) + inclsubs
861 match = cmdutil.match(repo, pats, opts)
862 match = cmdutil.match(repo, pats, opts)
862 # detect missing files in pats
863 # detect missing files in pats
863 def badfn(f, msg):
864 def badfn(f, msg):
864 if f != '.hgsubstate': # .hgsubstate is auto-created
865 if f != '.hgsubstate': # .hgsubstate is auto-created
865 raise util.Abort('%s: %s' % (f, msg))
866 raise util.Abort('%s: %s' % (f, msg))
866 match.bad = badfn
867 match.bad = badfn
867 m, a, r, d = repo.status(match=match)[:4]
868 m, a, r, d = repo.status(match=match)[:4]
868 else:
869 else:
869 m, a, r, d = self.check_localchanges(repo, force=True)
870 m, a, r, d = self.check_localchanges(repo, force=True)
870 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
871 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
871 if len(repo[None].parents()) > 1:
872 if len(repo[None].parents()) > 1:
872 raise util.Abort(_('cannot manage merge changesets'))
873 raise util.Abort(_('cannot manage merge changesets'))
873 commitfiles = m + a + r
874 commitfiles = m + a + r
874 self.check_toppatch(repo)
875 self.check_toppatch(repo)
875 insert = self.full_series_end()
876 insert = self.full_series_end()
876 wlock = repo.wlock()
877 wlock = repo.wlock()
877 try:
878 try:
878 try:
879 try:
879 # if patch file write fails, abort early
880 # if patch file write fails, abort early
880 p = self.opener(patchfn, "w")
881 p = self.opener(patchfn, "w")
881 except IOError, e:
882 except IOError, e:
882 raise util.Abort(_('cannot write patch "%s": %s')
883 raise util.Abort(_('cannot write patch "%s": %s')
883 % (patchfn, e.strerror))
884 % (patchfn, e.strerror))
884 try:
885 try:
885 if self.plainmode:
886 if self.plainmode:
886 if user:
887 if user:
887 p.write("From: " + user + "\n")
888 p.write("From: " + user + "\n")
888 if not date:
889 if not date:
889 p.write("\n")
890 p.write("\n")
890 if date:
891 if date:
891 p.write("Date: %d %d\n\n" % date)
892 p.write("Date: %d %d\n\n" % date)
892 else:
893 else:
893 p.write("# HG changeset patch\n")
894 p.write("# HG changeset patch\n")
894 p.write("# Parent "
895 p.write("# Parent "
895 + hex(repo[None].parents()[0].node()) + "\n")
896 + hex(repo[None].parents()[0].node()) + "\n")
896 if user:
897 if user:
897 p.write("# User " + user + "\n")
898 p.write("# User " + user + "\n")
898 if date:
899 if date:
899 p.write("# Date %s %s\n\n" % date)
900 p.write("# Date %s %s\n\n" % date)
900 if hasattr(msg, '__call__'):
901 if hasattr(msg, '__call__'):
901 msg = msg()
902 msg = msg()
902 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
903 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
903 n = repo.commit(commitmsg, user, date, match=match, force=True)
904 n = repo.commit(commitmsg, user, date, match=match, force=True)
904 if n is None:
905 if n is None:
905 raise util.Abort(_("repo commit failed"))
906 raise util.Abort(_("repo commit failed"))
906 try:
907 try:
907 self.full_series[insert:insert] = [patchfn]
908 self.full_series[insert:insert] = [patchfn]
908 self.applied.append(statusentry(n, patchfn))
909 self.applied.append(statusentry(n, patchfn))
909 self.parse_series()
910 self.parse_series()
910 self.series_dirty = 1
911 self.series_dirty = 1
911 self.applied_dirty = 1
912 self.applied_dirty = 1
912 if msg:
913 if msg:
913 msg = msg + "\n\n"
914 msg = msg + "\n\n"
914 p.write(msg)
915 p.write(msg)
915 if commitfiles:
916 if commitfiles:
916 parent = self.qparents(repo, n)
917 parent = self.qparents(repo, n)
917 chunks = patch.diff(repo, node1=parent, node2=n,
918 chunks = patch.diff(repo, node1=parent, node2=n,
918 match=match, opts=diffopts)
919 match=match, opts=diffopts)
919 for chunk in chunks:
920 for chunk in chunks:
920 p.write(chunk)
921 p.write(chunk)
921 p.close()
922 p.close()
922 wlock.release()
923 wlock.release()
923 wlock = None
924 wlock = None
924 r = self.qrepo()
925 r = self.qrepo()
925 if r:
926 if r:
926 r[None].add([patchfn])
927 r[None].add([patchfn])
927 except:
928 except:
928 repo.rollback()
929 repo.rollback()
929 raise
930 raise
930 except Exception:
931 except Exception:
931 patchpath = self.join(patchfn)
932 patchpath = self.join(patchfn)
932 try:
933 try:
933 os.unlink(patchpath)
934 os.unlink(patchpath)
934 except:
935 except:
935 self.ui.warn(_('error unlinking %s\n') % patchpath)
936 self.ui.warn(_('error unlinking %s\n') % patchpath)
936 raise
937 raise
937 self.removeundo(repo)
938 self.removeundo(repo)
938 finally:
939 finally:
939 release(wlock)
940 release(wlock)
940
941
941 def strip(self, repo, revs, update=True, backup="all", force=None):
942 def strip(self, repo, revs, update=True, backup="all", force=None):
942 wlock = lock = None
943 wlock = lock = None
943 try:
944 try:
944 wlock = repo.wlock()
945 wlock = repo.wlock()
945 lock = repo.lock()
946 lock = repo.lock()
946
947
947 if update:
948 if update:
948 self.check_localchanges(repo, force=force, refresh=False)
949 self.check_localchanges(repo, force=force, refresh=False)
949 urev = self.qparents(repo, revs[0])
950 urev = self.qparents(repo, revs[0])
950 hg.clean(repo, urev)
951 hg.clean(repo, urev)
951 repo.dirstate.write()
952 repo.dirstate.write()
952
953
953 self.removeundo(repo)
954 self.removeundo(repo)
954 for rev in revs:
955 for rev in revs:
955 repair.strip(self.ui, repo, rev, backup)
956 repair.strip(self.ui, repo, rev, backup)
956 # strip may have unbundled a set of backed up revisions after
957 # strip may have unbundled a set of backed up revisions after
957 # the actual strip
958 # the actual strip
958 self.removeundo(repo)
959 self.removeundo(repo)
959 finally:
960 finally:
960 release(lock, wlock)
961 release(lock, wlock)
961
962
962 def isapplied(self, patch):
963 def isapplied(self, patch):
963 """returns (index, rev, patch)"""
964 """returns (index, rev, patch)"""
964 for i, a in enumerate(self.applied):
965 for i, a in enumerate(self.applied):
965 if a.name == patch:
966 if a.name == patch:
966 return (i, a.node, a.name)
967 return (i, a.node, a.name)
967 return None
968 return None
968
969
969 # if the exact patch name does not exist, we try a few
970 # if the exact patch name does not exist, we try a few
970 # variations. If strict is passed, we try only #1
971 # variations. If strict is passed, we try only #1
971 #
972 #
972 # 1) a number to indicate an offset in the series file
973 # 1) a number to indicate an offset in the series file
973 # 2) a unique substring of the patch name was given
974 # 2) a unique substring of the patch name was given
974 # 3) patchname[-+]num to indicate an offset in the series file
975 # 3) patchname[-+]num to indicate an offset in the series file
975 def lookup(self, patch, strict=False):
976 def lookup(self, patch, strict=False):
976 patch = patch and str(patch)
977 patch = patch and str(patch)
977
978
978 def partial_name(s):
979 def partial_name(s):
979 if s in self.series:
980 if s in self.series:
980 return s
981 return s
981 matches = [x for x in self.series if s in x]
982 matches = [x for x in self.series if s in x]
982 if len(matches) > 1:
983 if len(matches) > 1:
983 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
984 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
984 for m in matches:
985 for m in matches:
985 self.ui.warn(' %s\n' % m)
986 self.ui.warn(' %s\n' % m)
986 return None
987 return None
987 if matches:
988 if matches:
988 return matches[0]
989 return matches[0]
989 if self.series and self.applied:
990 if self.series and self.applied:
990 if s == 'qtip':
991 if s == 'qtip':
991 return self.series[self.series_end(True)-1]
992 return self.series[self.series_end(True)-1]
992 if s == 'qbase':
993 if s == 'qbase':
993 return self.series[0]
994 return self.series[0]
994 return None
995 return None
995
996
996 if patch is None:
997 if patch is None:
997 return None
998 return None
998 if patch in self.series:
999 if patch in self.series:
999 return patch
1000 return patch
1000
1001
1001 if not os.path.isfile(self.join(patch)):
1002 if not os.path.isfile(self.join(patch)):
1002 try:
1003 try:
1003 sno = int(patch)
1004 sno = int(patch)
1004 except (ValueError, OverflowError):
1005 except (ValueError, OverflowError):
1005 pass
1006 pass
1006 else:
1007 else:
1007 if -len(self.series) <= sno < len(self.series):
1008 if -len(self.series) <= sno < len(self.series):
1008 return self.series[sno]
1009 return self.series[sno]
1009
1010
1010 if not strict:
1011 if not strict:
1011 res = partial_name(patch)
1012 res = partial_name(patch)
1012 if res:
1013 if res:
1013 return res
1014 return res
1014 minus = patch.rfind('-')
1015 minus = patch.rfind('-')
1015 if minus >= 0:
1016 if minus >= 0:
1016 res = partial_name(patch[:minus])
1017 res = partial_name(patch[:minus])
1017 if res:
1018 if res:
1018 i = self.series.index(res)
1019 i = self.series.index(res)
1019 try:
1020 try:
1020 off = int(patch[minus + 1:] or 1)
1021 off = int(patch[minus + 1:] or 1)
1021 except (ValueError, OverflowError):
1022 except (ValueError, OverflowError):
1022 pass
1023 pass
1023 else:
1024 else:
1024 if i - off >= 0:
1025 if i - off >= 0:
1025 return self.series[i - off]
1026 return self.series[i - off]
1026 plus = patch.rfind('+')
1027 plus = patch.rfind('+')
1027 if plus >= 0:
1028 if plus >= 0:
1028 res = partial_name(patch[:plus])
1029 res = partial_name(patch[:plus])
1029 if res:
1030 if res:
1030 i = self.series.index(res)
1031 i = self.series.index(res)
1031 try:
1032 try:
1032 off = int(patch[plus + 1:] or 1)
1033 off = int(patch[plus + 1:] or 1)
1033 except (ValueError, OverflowError):
1034 except (ValueError, OverflowError):
1034 pass
1035 pass
1035 else:
1036 else:
1036 if i + off < len(self.series):
1037 if i + off < len(self.series):
1037 return self.series[i + off]
1038 return self.series[i + off]
1038 raise util.Abort(_("patch %s not in series") % patch)
1039 raise util.Abort(_("patch %s not in series") % patch)
1039
1040
1040 def push(self, repo, patch=None, force=False, list=False,
1041 def push(self, repo, patch=None, force=False, list=False,
1041 mergeq=None, all=False, move=False, exact=False):
1042 mergeq=None, all=False, move=False, exact=False):
1042 diffopts = self.diffopts()
1043 diffopts = self.diffopts()
1043 wlock = repo.wlock()
1044 wlock = repo.wlock()
1044 try:
1045 try:
1045 heads = []
1046 heads = []
1046 for b, ls in repo.branchmap().iteritems():
1047 for b, ls in repo.branchmap().iteritems():
1047 heads += ls
1048 heads += ls
1048 if not heads:
1049 if not heads:
1049 heads = [nullid]
1050 heads = [nullid]
1050 if repo.dirstate.parents()[0] not in heads and not exact:
1051 if repo.dirstate.parents()[0] not in heads and not exact:
1051 self.ui.status(_("(working directory not at a head)\n"))
1052 self.ui.status(_("(working directory not at a head)\n"))
1052
1053
1053 if not self.series:
1054 if not self.series:
1054 self.ui.warn(_('no patches in series\n'))
1055 self.ui.warn(_('no patches in series\n'))
1055 return 0
1056 return 0
1056
1057
1057 patch = self.lookup(patch)
1058 patch = self.lookup(patch)
1058 # Suppose our series file is: A B C and the current 'top'
1059 # Suppose our series file is: A B C and the current 'top'
1059 # patch is B. qpush C should be performed (moving forward)
1060 # patch is B. qpush C should be performed (moving forward)
1060 # qpush B is a NOP (no change) qpush A is an error (can't
1061 # qpush B is a NOP (no change) qpush A is an error (can't
1061 # go backwards with qpush)
1062 # go backwards with qpush)
1062 if patch:
1063 if patch:
1063 info = self.isapplied(patch)
1064 info = self.isapplied(patch)
1064 if info and info[0] >= len(self.applied) - 1:
1065 if info and info[0] >= len(self.applied) - 1:
1065 self.ui.warn(
1066 self.ui.warn(
1066 _('qpush: %s is already at the top\n') % patch)
1067 _('qpush: %s is already at the top\n') % patch)
1067 return 0
1068 return 0
1068
1069
1069 pushable, reason = self.pushable(patch)
1070 pushable, reason = self.pushable(patch)
1070 if pushable:
1071 if pushable:
1071 if self.series.index(patch) < self.series_end():
1072 if self.series.index(patch) < self.series_end():
1072 raise util.Abort(
1073 raise util.Abort(
1073 _("cannot push to a previous patch: %s") % patch)
1074 _("cannot push to a previous patch: %s") % patch)
1074 else:
1075 else:
1075 if reason:
1076 if reason:
1076 reason = _('guarded by %r') % reason
1077 reason = _('guarded by %r') % reason
1077 else:
1078 else:
1078 reason = _('no matching guards')
1079 reason = _('no matching guards')
1079 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1080 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1080 return 1
1081 return 1
1081 elif all:
1082 elif all:
1082 patch = self.series[-1]
1083 patch = self.series[-1]
1083 if self.isapplied(patch):
1084 if self.isapplied(patch):
1084 self.ui.warn(_('all patches are currently applied\n'))
1085 self.ui.warn(_('all patches are currently applied\n'))
1085 return 0
1086 return 0
1086
1087
1087 # Following the above example, starting at 'top' of B:
1088 # Following the above example, starting at 'top' of B:
1088 # qpush should be performed (pushes C), but a subsequent
1089 # qpush should be performed (pushes C), but a subsequent
1089 # qpush without an argument is an error (nothing to
1090 # qpush without an argument is an error (nothing to
1090 # apply). This allows a loop of "...while hg qpush..." to
1091 # apply). This allows a loop of "...while hg qpush..." to
1091 # work as it detects an error when done
1092 # work as it detects an error when done
1092 start = self.series_end()
1093 start = self.series_end()
1093 if start == len(self.series):
1094 if start == len(self.series):
1094 self.ui.warn(_('patch series already fully applied\n'))
1095 self.ui.warn(_('patch series already fully applied\n'))
1095 return 1
1096 return 1
1096 if not force:
1097 if not force:
1097 self.check_localchanges(repo)
1098 self.check_localchanges(repo)
1098
1099
1099 if exact:
1100 if exact:
1100 if move:
1101 if move:
1101 raise util.Abort(_("cannot use --exact and --move together"))
1102 raise util.Abort(_("cannot use --exact and --move together"))
1102 if self.applied:
1103 if self.applied:
1103 raise util.Abort(_("cannot push --exact with applied patches"))
1104 raise util.Abort(_("cannot push --exact with applied patches"))
1104 root = self.series[start]
1105 root = self.series[start]
1105 target = patchheader(self.join(root), self.plainmode).parent
1106 target = patchheader(self.join(root), self.plainmode).parent
1106 if not target:
1107 if not target:
1107 raise util.Abort(_("%s does not have a parent recorded" % root))
1108 raise util.Abort(_("%s does not have a parent recorded" % root))
1108 if not repo[target] == repo['.']:
1109 if not repo[target] == repo['.']:
1109 hg.update(repo, target)
1110 hg.update(repo, target)
1110
1111
1111 if move:
1112 if move:
1112 if not patch:
1113 if not patch:
1113 raise util.Abort(_("please specify the patch to move"))
1114 raise util.Abort(_("please specify the patch to move"))
1114 for i, rpn in enumerate(self.full_series[start:]):
1115 for i, rpn in enumerate(self.full_series[start:]):
1115 # strip markers for patch guards
1116 # strip markers for patch guards
1116 if self.guard_re.split(rpn, 1)[0] == patch:
1117 if self.guard_re.split(rpn, 1)[0] == patch:
1117 break
1118 break
1118 index = start + i
1119 index = start + i
1119 assert index < len(self.full_series)
1120 assert index < len(self.full_series)
1120 fullpatch = self.full_series[index]
1121 fullpatch = self.full_series[index]
1121 del self.full_series[index]
1122 del self.full_series[index]
1122 self.full_series.insert(start, fullpatch)
1123 self.full_series.insert(start, fullpatch)
1123 self.parse_series()
1124 self.parse_series()
1124 self.series_dirty = 1
1125 self.series_dirty = 1
1125
1126
1126 self.applied_dirty = 1
1127 self.applied_dirty = 1
1127 if start > 0:
1128 if start > 0:
1128 self.check_toppatch(repo)
1129 self.check_toppatch(repo)
1129 if not patch:
1130 if not patch:
1130 patch = self.series[start]
1131 patch = self.series[start]
1131 end = start + 1
1132 end = start + 1
1132 else:
1133 else:
1133 end = self.series.index(patch, start) + 1
1134 end = self.series.index(patch, start) + 1
1134
1135
1135 s = self.series[start:end]
1136 s = self.series[start:end]
1136 all_files = set()
1137 all_files = set()
1137 try:
1138 try:
1138 if mergeq:
1139 if mergeq:
1139 ret = self.mergepatch(repo, mergeq, s, diffopts)
1140 ret = self.mergepatch(repo, mergeq, s, diffopts)
1140 else:
1141 else:
1141 ret = self.apply(repo, s, list, all_files=all_files)
1142 ret = self.apply(repo, s, list, all_files=all_files)
1142 except:
1143 except:
1143 self.ui.warn(_('cleaning up working directory...'))
1144 self.ui.warn(_('cleaning up working directory...'))
1144 node = repo.dirstate.parents()[0]
1145 node = repo.dirstate.parents()[0]
1145 hg.revert(repo, node, None)
1146 hg.revert(repo, node, None)
1146 # only remove unknown files that we know we touched or
1147 # only remove unknown files that we know we touched or
1147 # created while patching
1148 # created while patching
1148 for f in all_files:
1149 for f in all_files:
1149 if f not in repo.dirstate:
1150 if f not in repo.dirstate:
1150 try:
1151 try:
1151 util.unlinkpath(repo.wjoin(f))
1152 util.unlinkpath(repo.wjoin(f))
1152 except OSError, inst:
1153 except OSError, inst:
1153 if inst.errno != errno.ENOENT:
1154 if inst.errno != errno.ENOENT:
1154 raise
1155 raise
1155 self.ui.warn(_('done\n'))
1156 self.ui.warn(_('done\n'))
1156 raise
1157 raise
1157
1158
1158 if not self.applied:
1159 if not self.applied:
1159 return ret[0]
1160 return ret[0]
1160 top = self.applied[-1].name
1161 top = self.applied[-1].name
1161 if ret[0] and ret[0] > 1:
1162 if ret[0] and ret[0] > 1:
1162 msg = _("errors during apply, please fix and refresh %s\n")
1163 msg = _("errors during apply, please fix and refresh %s\n")
1163 self.ui.write(msg % top)
1164 self.ui.write(msg % top)
1164 else:
1165 else:
1165 self.ui.write(_("now at: %s\n") % top)
1166 self.ui.write(_("now at: %s\n") % top)
1166 return ret[0]
1167 return ret[0]
1167
1168
1168 finally:
1169 finally:
1169 wlock.release()
1170 wlock.release()
1170
1171
1171 def pop(self, repo, patch=None, force=False, update=True, all=False):
1172 def pop(self, repo, patch=None, force=False, update=True, all=False):
1172 wlock = repo.wlock()
1173 wlock = repo.wlock()
1173 try:
1174 try:
1174 if patch:
1175 if patch:
1175 # index, rev, patch
1176 # index, rev, patch
1176 info = self.isapplied(patch)
1177 info = self.isapplied(patch)
1177 if not info:
1178 if not info:
1178 patch = self.lookup(patch)
1179 patch = self.lookup(patch)
1179 info = self.isapplied(patch)
1180 info = self.isapplied(patch)
1180 if not info:
1181 if not info:
1181 raise util.Abort(_("patch %s is not applied") % patch)
1182 raise util.Abort(_("patch %s is not applied") % patch)
1182
1183
1183 if not self.applied:
1184 if not self.applied:
1184 # Allow qpop -a to work repeatedly,
1185 # Allow qpop -a to work repeatedly,
1185 # but not qpop without an argument
1186 # but not qpop without an argument
1186 self.ui.warn(_("no patches applied\n"))
1187 self.ui.warn(_("no patches applied\n"))
1187 return not all
1188 return not all
1188
1189
1189 if all:
1190 if all:
1190 start = 0
1191 start = 0
1191 elif patch:
1192 elif patch:
1192 start = info[0] + 1
1193 start = info[0] + 1
1193 else:
1194 else:
1194 start = len(self.applied) - 1
1195 start = len(self.applied) - 1
1195
1196
1196 if start >= len(self.applied):
1197 if start >= len(self.applied):
1197 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1198 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1198 return
1199 return
1199
1200
1200 if not update:
1201 if not update:
1201 parents = repo.dirstate.parents()
1202 parents = repo.dirstate.parents()
1202 rr = [x.node for x in self.applied]
1203 rr = [x.node for x in self.applied]
1203 for p in parents:
1204 for p in parents:
1204 if p in rr:
1205 if p in rr:
1205 self.ui.warn(_("qpop: forcing dirstate update\n"))
1206 self.ui.warn(_("qpop: forcing dirstate update\n"))
1206 update = True
1207 update = True
1207 else:
1208 else:
1208 parents = [p.node() for p in repo[None].parents()]
1209 parents = [p.node() for p in repo[None].parents()]
1209 needupdate = False
1210 needupdate = False
1210 for entry in self.applied[start:]:
1211 for entry in self.applied[start:]:
1211 if entry.node in parents:
1212 if entry.node in parents:
1212 needupdate = True
1213 needupdate = True
1213 break
1214 break
1214 update = needupdate
1215 update = needupdate
1215
1216
1216 if not force and update:
1217 if not force and update:
1217 self.check_localchanges(repo)
1218 self.check_localchanges(repo)
1218
1219
1219 self.applied_dirty = 1
1220 self.applied_dirty = 1
1220 end = len(self.applied)
1221 end = len(self.applied)
1221 rev = self.applied[start].node
1222 rev = self.applied[start].node
1222 if update:
1223 if update:
1223 top = self.check_toppatch(repo)[0]
1224 top = self.check_toppatch(repo)[0]
1224
1225
1225 try:
1226 try:
1226 heads = repo.changelog.heads(rev)
1227 heads = repo.changelog.heads(rev)
1227 except error.LookupError:
1228 except error.LookupError:
1228 node = short(rev)
1229 node = short(rev)
1229 raise util.Abort(_('trying to pop unknown node %s') % node)
1230 raise util.Abort(_('trying to pop unknown node %s') % node)
1230
1231
1231 if heads != [self.applied[-1].node]:
1232 if heads != [self.applied[-1].node]:
1232 raise util.Abort(_("popping would remove a revision not "
1233 raise util.Abort(_("popping would remove a revision not "
1233 "managed by this patch queue"))
1234 "managed by this patch queue"))
1234
1235
1235 # we know there are no local changes, so we can make a simplified
1236 # we know there are no local changes, so we can make a simplified
1236 # form of hg.update.
1237 # form of hg.update.
1237 if update:
1238 if update:
1238 qp = self.qparents(repo, rev)
1239 qp = self.qparents(repo, rev)
1239 ctx = repo[qp]
1240 ctx = repo[qp]
1240 m, a, r, d = repo.status(qp, top)[:4]
1241 m, a, r, d = repo.status(qp, top)[:4]
1241 if d:
1242 if d:
1242 raise util.Abort(_("deletions found between repo revs"))
1243 raise util.Abort(_("deletions found between repo revs"))
1243 for f in a:
1244 for f in a:
1244 try:
1245 try:
1245 util.unlinkpath(repo.wjoin(f))
1246 util.unlinkpath(repo.wjoin(f))
1246 except OSError, e:
1247 except OSError, e:
1247 if e.errno != errno.ENOENT:
1248 if e.errno != errno.ENOENT:
1248 raise
1249 raise
1249 repo.dirstate.forget(f)
1250 repo.dirstate.forget(f)
1250 for f in m + r:
1251 for f in m + r:
1251 fctx = ctx[f]
1252 fctx = ctx[f]
1252 repo.wwrite(f, fctx.data(), fctx.flags())
1253 repo.wwrite(f, fctx.data(), fctx.flags())
1253 repo.dirstate.normal(f)
1254 repo.dirstate.normal(f)
1254 repo.dirstate.setparents(qp, nullid)
1255 repo.dirstate.setparents(qp, nullid)
1255 for patch in reversed(self.applied[start:end]):
1256 for patch in reversed(self.applied[start:end]):
1256 self.ui.status(_("popping %s\n") % patch.name)
1257 self.ui.status(_("popping %s\n") % patch.name)
1257 del self.applied[start:end]
1258 del self.applied[start:end]
1258 self.strip(repo, [rev], update=False, backup='strip')
1259 self.strip(repo, [rev], update=False, backup='strip')
1259 if self.applied:
1260 if self.applied:
1260 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1261 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1261 else:
1262 else:
1262 self.ui.write(_("patch queue now empty\n"))
1263 self.ui.write(_("patch queue now empty\n"))
1263 finally:
1264 finally:
1264 wlock.release()
1265 wlock.release()
1265
1266
1266 def diff(self, repo, pats, opts):
1267 def diff(self, repo, pats, opts):
1267 top, patch = self.check_toppatch(repo)
1268 top, patch = self.check_toppatch(repo)
1268 if not top:
1269 if not top:
1269 self.ui.write(_("no patches applied\n"))
1270 self.ui.write(_("no patches applied\n"))
1270 return
1271 return
1271 qp = self.qparents(repo, top)
1272 qp = self.qparents(repo, top)
1272 if opts.get('reverse'):
1273 if opts.get('reverse'):
1273 node1, node2 = None, qp
1274 node1, node2 = None, qp
1274 else:
1275 else:
1275 node1, node2 = qp, None
1276 node1, node2 = qp, None
1276 diffopts = self.diffopts(opts, patch)
1277 diffopts = self.diffopts(opts, patch)
1277 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1278 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1278
1279
1279 def refresh(self, repo, pats=None, **opts):
1280 def refresh(self, repo, pats=None, **opts):
1280 if not self.applied:
1281 if not self.applied:
1281 self.ui.write(_("no patches applied\n"))
1282 self.ui.write(_("no patches applied\n"))
1282 return 1
1283 return 1
1283 msg = opts.get('msg', '').rstrip()
1284 msg = opts.get('msg', '').rstrip()
1284 newuser = opts.get('user')
1285 newuser = opts.get('user')
1285 newdate = opts.get('date')
1286 newdate = opts.get('date')
1286 if newdate:
1287 if newdate:
1287 newdate = '%d %d' % util.parsedate(newdate)
1288 newdate = '%d %d' % util.parsedate(newdate)
1288 wlock = repo.wlock()
1289 wlock = repo.wlock()
1289
1290
1290 try:
1291 try:
1291 self.check_toppatch(repo)
1292 self.check_toppatch(repo)
1292 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1293 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1293 if repo.changelog.heads(top) != [top]:
1294 if repo.changelog.heads(top) != [top]:
1294 raise util.Abort(_("cannot refresh a revision with children"))
1295 raise util.Abort(_("cannot refresh a revision with children"))
1295
1296
1296 inclsubs = self.check_substate(repo)
1297 inclsubs = self.check_substate(repo)
1297
1298
1298 cparents = repo.changelog.parents(top)
1299 cparents = repo.changelog.parents(top)
1299 patchparent = self.qparents(repo, top)
1300 patchparent = self.qparents(repo, top)
1300 ph = patchheader(self.join(patchfn), self.plainmode)
1301 ph = patchheader(self.join(patchfn), self.plainmode)
1301 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1302 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1302 if msg:
1303 if msg:
1303 ph.setmessage(msg)
1304 ph.setmessage(msg)
1304 if newuser:
1305 if newuser:
1305 ph.setuser(newuser)
1306 ph.setuser(newuser)
1306 if newdate:
1307 if newdate:
1307 ph.setdate(newdate)
1308 ph.setdate(newdate)
1308 ph.setparent(hex(patchparent))
1309 ph.setparent(hex(patchparent))
1309
1310
1310 # only commit new patch when write is complete
1311 # only commit new patch when write is complete
1311 patchf = self.opener(patchfn, 'w', atomictemp=True)
1312 patchf = self.opener(patchfn, 'w', atomictemp=True)
1312
1313
1313 comments = str(ph)
1314 comments = str(ph)
1314 if comments:
1315 if comments:
1315 patchf.write(comments)
1316 patchf.write(comments)
1316
1317
1317 # update the dirstate in place, strip off the qtip commit
1318 # update the dirstate in place, strip off the qtip commit
1318 # and then commit.
1319 # and then commit.
1319 #
1320 #
1320 # this should really read:
1321 # this should really read:
1321 # mm, dd, aa = repo.status(top, patchparent)[:3]
1322 # mm, dd, aa = repo.status(top, patchparent)[:3]
1322 # but we do it backwards to take advantage of manifest/chlog
1323 # but we do it backwards to take advantage of manifest/chlog
1323 # caching against the next repo.status call
1324 # caching against the next repo.status call
1324 mm, aa, dd = repo.status(patchparent, top)[:3]
1325 mm, aa, dd = repo.status(patchparent, top)[:3]
1325 changes = repo.changelog.read(top)
1326 changes = repo.changelog.read(top)
1326 man = repo.manifest.read(changes[0])
1327 man = repo.manifest.read(changes[0])
1327 aaa = aa[:]
1328 aaa = aa[:]
1328 matchfn = cmdutil.match(repo, pats, opts)
1329 matchfn = cmdutil.match(repo, pats, opts)
1329 # in short mode, we only diff the files included in the
1330 # in short mode, we only diff the files included in the
1330 # patch already plus specified files
1331 # patch already plus specified files
1331 if opts.get('short'):
1332 if opts.get('short'):
1332 # if amending a patch, we start with existing
1333 # if amending a patch, we start with existing
1333 # files plus specified files - unfiltered
1334 # files plus specified files - unfiltered
1334 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1335 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1335 # filter with inc/exl options
1336 # filter with inc/exl options
1336 matchfn = cmdutil.match(repo, opts=opts)
1337 matchfn = cmdutil.match(repo, opts=opts)
1337 else:
1338 else:
1338 match = cmdutil.matchall(repo)
1339 match = cmdutil.matchall(repo)
1339 m, a, r, d = repo.status(match=match)[:4]
1340 m, a, r, d = repo.status(match=match)[:4]
1340 mm = set(mm)
1341 mm = set(mm)
1341 aa = set(aa)
1342 aa = set(aa)
1342 dd = set(dd)
1343 dd = set(dd)
1343
1344
1344 # we might end up with files that were added between
1345 # we might end up with files that were added between
1345 # qtip and the dirstate parent, but then changed in the
1346 # qtip and the dirstate parent, but then changed in the
1346 # local dirstate. in this case, we want them to only
1347 # local dirstate. in this case, we want them to only
1347 # show up in the added section
1348 # show up in the added section
1348 for x in m:
1349 for x in m:
1349 if x not in aa:
1350 if x not in aa:
1350 mm.add(x)
1351 mm.add(x)
1351 # we might end up with files added by the local dirstate that
1352 # we might end up with files added by the local dirstate that
1352 # were deleted by the patch. In this case, they should only
1353 # were deleted by the patch. In this case, they should only
1353 # show up in the changed section.
1354 # show up in the changed section.
1354 for x in a:
1355 for x in a:
1355 if x in dd:
1356 if x in dd:
1356 dd.remove(x)
1357 dd.remove(x)
1357 mm.add(x)
1358 mm.add(x)
1358 else:
1359 else:
1359 aa.add(x)
1360 aa.add(x)
1360 # make sure any files deleted in the local dirstate
1361 # make sure any files deleted in the local dirstate
1361 # are not in the add or change column of the patch
1362 # are not in the add or change column of the patch
1362 forget = []
1363 forget = []
1363 for x in d + r:
1364 for x in d + r:
1364 if x in aa:
1365 if x in aa:
1365 aa.remove(x)
1366 aa.remove(x)
1366 forget.append(x)
1367 forget.append(x)
1367 continue
1368 continue
1368 else:
1369 else:
1369 mm.discard(x)
1370 mm.discard(x)
1370 dd.add(x)
1371 dd.add(x)
1371
1372
1372 m = list(mm)
1373 m = list(mm)
1373 r = list(dd)
1374 r = list(dd)
1374 a = list(aa)
1375 a = list(aa)
1375 c = [filter(matchfn, l) for l in (m, a, r)]
1376 c = [filter(matchfn, l) for l in (m, a, r)]
1376 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1377 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1377 chunks = patch.diff(repo, patchparent, match=match,
1378 chunks = patch.diff(repo, patchparent, match=match,
1378 changes=c, opts=diffopts)
1379 changes=c, opts=diffopts)
1379 for chunk in chunks:
1380 for chunk in chunks:
1380 patchf.write(chunk)
1381 patchf.write(chunk)
1381
1382
1382 try:
1383 try:
1383 if diffopts.git or diffopts.upgrade:
1384 if diffopts.git or diffopts.upgrade:
1384 copies = {}
1385 copies = {}
1385 for dst in a:
1386 for dst in a:
1386 src = repo.dirstate.copied(dst)
1387 src = repo.dirstate.copied(dst)
1387 # during qfold, the source file for copies may
1388 # during qfold, the source file for copies may
1388 # be removed. Treat this as a simple add.
1389 # be removed. Treat this as a simple add.
1389 if src is not None and src in repo.dirstate:
1390 if src is not None and src in repo.dirstate:
1390 copies.setdefault(src, []).append(dst)
1391 copies.setdefault(src, []).append(dst)
1391 repo.dirstate.add(dst)
1392 repo.dirstate.add(dst)
1392 # remember the copies between patchparent and qtip
1393 # remember the copies between patchparent and qtip
1393 for dst in aaa:
1394 for dst in aaa:
1394 f = repo.file(dst)
1395 f = repo.file(dst)
1395 src = f.renamed(man[dst])
1396 src = f.renamed(man[dst])
1396 if src:
1397 if src:
1397 copies.setdefault(src[0], []).extend(
1398 copies.setdefault(src[0], []).extend(
1398 copies.get(dst, []))
1399 copies.get(dst, []))
1399 if dst in a:
1400 if dst in a:
1400 copies[src[0]].append(dst)
1401 copies[src[0]].append(dst)
1401 # we can't copy a file created by the patch itself
1402 # we can't copy a file created by the patch itself
1402 if dst in copies:
1403 if dst in copies:
1403 del copies[dst]
1404 del copies[dst]
1404 for src, dsts in copies.iteritems():
1405 for src, dsts in copies.iteritems():
1405 for dst in dsts:
1406 for dst in dsts:
1406 repo.dirstate.copy(src, dst)
1407 repo.dirstate.copy(src, dst)
1407 else:
1408 else:
1408 for dst in a:
1409 for dst in a:
1409 repo.dirstate.add(dst)
1410 repo.dirstate.add(dst)
1410 # Drop useless copy information
1411 # Drop useless copy information
1411 for f in list(repo.dirstate.copies()):
1412 for f in list(repo.dirstate.copies()):
1412 repo.dirstate.copy(None, f)
1413 repo.dirstate.copy(None, f)
1413 for f in r:
1414 for f in r:
1414 repo.dirstate.remove(f)
1415 repo.dirstate.remove(f)
1415 # if the patch excludes a modified file, mark that
1416 # if the patch excludes a modified file, mark that
1416 # file with mtime=0 so status can see it.
1417 # file with mtime=0 so status can see it.
1417 mm = []
1418 mm = []
1418 for i in xrange(len(m)-1, -1, -1):
1419 for i in xrange(len(m)-1, -1, -1):
1419 if not matchfn(m[i]):
1420 if not matchfn(m[i]):
1420 mm.append(m[i])
1421 mm.append(m[i])
1421 del m[i]
1422 del m[i]
1422 for f in m:
1423 for f in m:
1423 repo.dirstate.normal(f)
1424 repo.dirstate.normal(f)
1424 for f in mm:
1425 for f in mm:
1425 repo.dirstate.normallookup(f)
1426 repo.dirstate.normallookup(f)
1426 for f in forget:
1427 for f in forget:
1427 repo.dirstate.forget(f)
1428 repo.dirstate.forget(f)
1428
1429
1429 if not msg:
1430 if not msg:
1430 if not ph.message:
1431 if not ph.message:
1431 message = "[mq]: %s\n" % patchfn
1432 message = "[mq]: %s\n" % patchfn
1432 else:
1433 else:
1433 message = "\n".join(ph.message)
1434 message = "\n".join(ph.message)
1434 else:
1435 else:
1435 message = msg
1436 message = msg
1436
1437
1437 user = ph.user or changes[1]
1438 user = ph.user or changes[1]
1438
1439
1439 # assumes strip can roll itself back if interrupted
1440 # assumes strip can roll itself back if interrupted
1440 repo.dirstate.setparents(*cparents)
1441 repo.dirstate.setparents(*cparents)
1441 self.applied.pop()
1442 self.applied.pop()
1442 self.applied_dirty = 1
1443 self.applied_dirty = 1
1443 self.strip(repo, [top], update=False,
1444 self.strip(repo, [top], update=False,
1444 backup='strip')
1445 backup='strip')
1445 except:
1446 except:
1446 repo.dirstate.invalidate()
1447 repo.dirstate.invalidate()
1447 raise
1448 raise
1448
1449
1449 try:
1450 try:
1450 # might be nice to attempt to roll back strip after this
1451 # might be nice to attempt to roll back strip after this
1451 patchf.rename()
1452 patchf.rename()
1452 n = repo.commit(message, user, ph.date, match=match,
1453 n = repo.commit(message, user, ph.date, match=match,
1453 force=True)
1454 force=True)
1454 self.applied.append(statusentry(n, patchfn))
1455 self.applied.append(statusentry(n, patchfn))
1455 except:
1456 except:
1456 ctx = repo[cparents[0]]
1457 ctx = repo[cparents[0]]
1457 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1458 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1458 self.save_dirty()
1459 self.save_dirty()
1459 self.ui.warn(_('refresh interrupted while patch was popped! '
1460 self.ui.warn(_('refresh interrupted while patch was popped! '
1460 '(revert --all, qpush to recover)\n'))
1461 '(revert --all, qpush to recover)\n'))
1461 raise
1462 raise
1462 finally:
1463 finally:
1463 wlock.release()
1464 wlock.release()
1464 self.removeundo(repo)
1465 self.removeundo(repo)
1465
1466
1466 def init(self, repo, create=False):
1467 def init(self, repo, create=False):
1467 if not create and os.path.isdir(self.path):
1468 if not create and os.path.isdir(self.path):
1468 raise util.Abort(_("patch queue directory already exists"))
1469 raise util.Abort(_("patch queue directory already exists"))
1469 try:
1470 try:
1470 os.mkdir(self.path)
1471 os.mkdir(self.path)
1471 except OSError, inst:
1472 except OSError, inst:
1472 if inst.errno != errno.EEXIST or not create:
1473 if inst.errno != errno.EEXIST or not create:
1473 raise
1474 raise
1474 if create:
1475 if create:
1475 return self.qrepo(create=True)
1476 return self.qrepo(create=True)
1476
1477
1477 def unapplied(self, repo, patch=None):
1478 def unapplied(self, repo, patch=None):
1478 if patch and patch not in self.series:
1479 if patch and patch not in self.series:
1479 raise util.Abort(_("patch %s is not in series file") % patch)
1480 raise util.Abort(_("patch %s is not in series file") % patch)
1480 if not patch:
1481 if not patch:
1481 start = self.series_end()
1482 start = self.series_end()
1482 else:
1483 else:
1483 start = self.series.index(patch) + 1
1484 start = self.series.index(patch) + 1
1484 unapplied = []
1485 unapplied = []
1485 for i in xrange(start, len(self.series)):
1486 for i in xrange(start, len(self.series)):
1486 pushable, reason = self.pushable(i)
1487 pushable, reason = self.pushable(i)
1487 if pushable:
1488 if pushable:
1488 unapplied.append((i, self.series[i]))
1489 unapplied.append((i, self.series[i]))
1489 self.explain_pushable(i)
1490 self.explain_pushable(i)
1490 return unapplied
1491 return unapplied
1491
1492
1492 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1493 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1493 summary=False):
1494 summary=False):
1494 def displayname(pfx, patchname, state):
1495 def displayname(pfx, patchname, state):
1495 if pfx:
1496 if pfx:
1496 self.ui.write(pfx)
1497 self.ui.write(pfx)
1497 if summary:
1498 if summary:
1498 ph = patchheader(self.join(patchname), self.plainmode)
1499 ph = patchheader(self.join(patchname), self.plainmode)
1499 msg = ph.message and ph.message[0] or ''
1500 msg = ph.message and ph.message[0] or ''
1500 if self.ui.formatted():
1501 if self.ui.formatted():
1501 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1502 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1502 if width > 0:
1503 if width > 0:
1503 msg = util.ellipsis(msg, width)
1504 msg = util.ellipsis(msg, width)
1504 else:
1505 else:
1505 msg = ''
1506 msg = ''
1506 self.ui.write(patchname, label='qseries.' + state)
1507 self.ui.write(patchname, label='qseries.' + state)
1507 self.ui.write(': ')
1508 self.ui.write(': ')
1508 self.ui.write(msg, label='qseries.message.' + state)
1509 self.ui.write(msg, label='qseries.message.' + state)
1509 else:
1510 else:
1510 self.ui.write(patchname, label='qseries.' + state)
1511 self.ui.write(patchname, label='qseries.' + state)
1511 self.ui.write('\n')
1512 self.ui.write('\n')
1512
1513
1513 applied = set([p.name for p in self.applied])
1514 applied = set([p.name for p in self.applied])
1514 if length is None:
1515 if length is None:
1515 length = len(self.series) - start
1516 length = len(self.series) - start
1516 if not missing:
1517 if not missing:
1517 if self.ui.verbose:
1518 if self.ui.verbose:
1518 idxwidth = len(str(start + length - 1))
1519 idxwidth = len(str(start + length - 1))
1519 for i in xrange(start, start + length):
1520 for i in xrange(start, start + length):
1520 patch = self.series[i]
1521 patch = self.series[i]
1521 if patch in applied:
1522 if patch in applied:
1522 char, state = 'A', 'applied'
1523 char, state = 'A', 'applied'
1523 elif self.pushable(i)[0]:
1524 elif self.pushable(i)[0]:
1524 char, state = 'U', 'unapplied'
1525 char, state = 'U', 'unapplied'
1525 else:
1526 else:
1526 char, state = 'G', 'guarded'
1527 char, state = 'G', 'guarded'
1527 pfx = ''
1528 pfx = ''
1528 if self.ui.verbose:
1529 if self.ui.verbose:
1529 pfx = '%*d %s ' % (idxwidth, i, char)
1530 pfx = '%*d %s ' % (idxwidth, i, char)
1530 elif status and status != char:
1531 elif status and status != char:
1531 continue
1532 continue
1532 displayname(pfx, patch, state)
1533 displayname(pfx, patch, state)
1533 else:
1534 else:
1534 msng_list = []
1535 msng_list = []
1535 for root, dirs, files in os.walk(self.path):
1536 for root, dirs, files in os.walk(self.path):
1536 d = root[len(self.path) + 1:]
1537 d = root[len(self.path) + 1:]
1537 for f in files:
1538 for f in files:
1538 fl = os.path.join(d, f)
1539 fl = os.path.join(d, f)
1539 if (fl not in self.series and
1540 if (fl not in self.series and
1540 fl not in (self.status_path, self.series_path,
1541 fl not in (self.status_path, self.series_path,
1541 self.guards_path)
1542 self.guards_path)
1542 and not fl.startswith('.')):
1543 and not fl.startswith('.')):
1543 msng_list.append(fl)
1544 msng_list.append(fl)
1544 for x in sorted(msng_list):
1545 for x in sorted(msng_list):
1545 pfx = self.ui.verbose and ('D ') or ''
1546 pfx = self.ui.verbose and ('D ') or ''
1546 displayname(pfx, x, 'missing')
1547 displayname(pfx, x, 'missing')
1547
1548
1548 def issaveline(self, l):
1549 def issaveline(self, l):
1549 if l.name == '.hg.patches.save.line':
1550 if l.name == '.hg.patches.save.line':
1550 return True
1551 return True
1551
1552
1552 def qrepo(self, create=False):
1553 def qrepo(self, create=False):
1553 ui = self.ui.copy()
1554 ui = self.ui.copy()
1554 ui.setconfig('paths', 'default', '', overlay=False)
1555 ui.setconfig('paths', 'default', '', overlay=False)
1555 ui.setconfig('paths', 'default-push', '', overlay=False)
1556 ui.setconfig('paths', 'default-push', '', overlay=False)
1556 if create or os.path.isdir(self.join(".hg")):
1557 if create or os.path.isdir(self.join(".hg")):
1557 return hg.repository(ui, path=self.path, create=create)
1558 return hg.repository(ui, path=self.path, create=create)
1558
1559
1559 def restore(self, repo, rev, delete=None, qupdate=None):
1560 def restore(self, repo, rev, delete=None, qupdate=None):
1560 desc = repo[rev].description().strip()
1561 desc = repo[rev].description().strip()
1561 lines = desc.splitlines()
1562 lines = desc.splitlines()
1562 i = 0
1563 i = 0
1563 datastart = None
1564 datastart = None
1564 series = []
1565 series = []
1565 applied = []
1566 applied = []
1566 qpp = None
1567 qpp = None
1567 for i, line in enumerate(lines):
1568 for i, line in enumerate(lines):
1568 if line == 'Patch Data:':
1569 if line == 'Patch Data:':
1569 datastart = i + 1
1570 datastart = i + 1
1570 elif line.startswith('Dirstate:'):
1571 elif line.startswith('Dirstate:'):
1571 l = line.rstrip()
1572 l = line.rstrip()
1572 l = l[10:].split(' ')
1573 l = l[10:].split(' ')
1573 qpp = [bin(x) for x in l]
1574 qpp = [bin(x) for x in l]
1574 elif datastart is not None:
1575 elif datastart is not None:
1575 l = line.rstrip()
1576 l = line.rstrip()
1576 n, name = l.split(':', 1)
1577 n, name = l.split(':', 1)
1577 if n:
1578 if n:
1578 applied.append(statusentry(bin(n), name))
1579 applied.append(statusentry(bin(n), name))
1579 else:
1580 else:
1580 series.append(l)
1581 series.append(l)
1581 if datastart is None:
1582 if datastart is None:
1582 self.ui.warn(_("No saved patch data found\n"))
1583 self.ui.warn(_("No saved patch data found\n"))
1583 return 1
1584 return 1
1584 self.ui.warn(_("restoring status: %s\n") % lines[0])
1585 self.ui.warn(_("restoring status: %s\n") % lines[0])
1585 self.full_series = series
1586 self.full_series = series
1586 self.applied = applied
1587 self.applied = applied
1587 self.parse_series()
1588 self.parse_series()
1588 self.series_dirty = 1
1589 self.series_dirty = 1
1589 self.applied_dirty = 1
1590 self.applied_dirty = 1
1590 heads = repo.changelog.heads()
1591 heads = repo.changelog.heads()
1591 if delete:
1592 if delete:
1592 if rev not in heads:
1593 if rev not in heads:
1593 self.ui.warn(_("save entry has children, leaving it alone\n"))
1594 self.ui.warn(_("save entry has children, leaving it alone\n"))
1594 else:
1595 else:
1595 self.ui.warn(_("removing save entry %s\n") % short(rev))
1596 self.ui.warn(_("removing save entry %s\n") % short(rev))
1596 pp = repo.dirstate.parents()
1597 pp = repo.dirstate.parents()
1597 if rev in pp:
1598 if rev in pp:
1598 update = True
1599 update = True
1599 else:
1600 else:
1600 update = False
1601 update = False
1601 self.strip(repo, [rev], update=update, backup='strip')
1602 self.strip(repo, [rev], update=update, backup='strip')
1602 if qpp:
1603 if qpp:
1603 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1604 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1604 (short(qpp[0]), short(qpp[1])))
1605 (short(qpp[0]), short(qpp[1])))
1605 if qupdate:
1606 if qupdate:
1606 self.ui.status(_("updating queue directory\n"))
1607 self.ui.status(_("updating queue directory\n"))
1607 r = self.qrepo()
1608 r = self.qrepo()
1608 if not r:
1609 if not r:
1609 self.ui.warn(_("Unable to load queue repository\n"))
1610 self.ui.warn(_("Unable to load queue repository\n"))
1610 return 1
1611 return 1
1611 hg.clean(r, qpp[0])
1612 hg.clean(r, qpp[0])
1612
1613
1613 def save(self, repo, msg=None):
1614 def save(self, repo, msg=None):
1614 if not self.applied:
1615 if not self.applied:
1615 self.ui.warn(_("save: no patches applied, exiting\n"))
1616 self.ui.warn(_("save: no patches applied, exiting\n"))
1616 return 1
1617 return 1
1617 if self.issaveline(self.applied[-1]):
1618 if self.issaveline(self.applied[-1]):
1618 self.ui.warn(_("status is already saved\n"))
1619 self.ui.warn(_("status is already saved\n"))
1619 return 1
1620 return 1
1620
1621
1621 if not msg:
1622 if not msg:
1622 msg = _("hg patches saved state")
1623 msg = _("hg patches saved state")
1623 else:
1624 else:
1624 msg = "hg patches: " + msg.rstrip('\r\n')
1625 msg = "hg patches: " + msg.rstrip('\r\n')
1625 r = self.qrepo()
1626 r = self.qrepo()
1626 if r:
1627 if r:
1627 pp = r.dirstate.parents()
1628 pp = r.dirstate.parents()
1628 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1629 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1629 msg += "\n\nPatch Data:\n"
1630 msg += "\n\nPatch Data:\n"
1630 msg += ''.join('%s\n' % x for x in self.applied)
1631 msg += ''.join('%s\n' % x for x in self.applied)
1631 msg += ''.join(':%s\n' % x for x in self.full_series)
1632 msg += ''.join(':%s\n' % x for x in self.full_series)
1632 n = repo.commit(msg, force=True)
1633 n = repo.commit(msg, force=True)
1633 if not n:
1634 if not n:
1634 self.ui.warn(_("repo commit failed\n"))
1635 self.ui.warn(_("repo commit failed\n"))
1635 return 1
1636 return 1
1636 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1637 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1637 self.applied_dirty = 1
1638 self.applied_dirty = 1
1638 self.removeundo(repo)
1639 self.removeundo(repo)
1639
1640
1640 def full_series_end(self):
1641 def full_series_end(self):
1641 if self.applied:
1642 if self.applied:
1642 p = self.applied[-1].name
1643 p = self.applied[-1].name
1643 end = self.find_series(p)
1644 end = self.find_series(p)
1644 if end is None:
1645 if end is None:
1645 return len(self.full_series)
1646 return len(self.full_series)
1646 return end + 1
1647 return end + 1
1647 return 0
1648 return 0
1648
1649
1649 def series_end(self, all_patches=False):
1650 def series_end(self, all_patches=False):
1650 """If all_patches is False, return the index of the next pushable patch
1651 """If all_patches is False, return the index of the next pushable patch
1651 in the series, or the series length. If all_patches is True, return the
1652 in the series, or the series length. If all_patches is True, return the
1652 index of the first patch past the last applied one.
1653 index of the first patch past the last applied one.
1653 """
1654 """
1654 end = 0
1655 end = 0
1655 def next(start):
1656 def next(start):
1656 if all_patches or start >= len(self.series):
1657 if all_patches or start >= len(self.series):
1657 return start
1658 return start
1658 for i in xrange(start, len(self.series)):
1659 for i in xrange(start, len(self.series)):
1659 p, reason = self.pushable(i)
1660 p, reason = self.pushable(i)
1660 if p:
1661 if p:
1661 break
1662 break
1662 self.explain_pushable(i)
1663 self.explain_pushable(i)
1663 return i
1664 return i
1664 if self.applied:
1665 if self.applied:
1665 p = self.applied[-1].name
1666 p = self.applied[-1].name
1666 try:
1667 try:
1667 end = self.series.index(p)
1668 end = self.series.index(p)
1668 except ValueError:
1669 except ValueError:
1669 return 0
1670 return 0
1670 return next(end + 1)
1671 return next(end + 1)
1671 return next(end)
1672 return next(end)
1672
1673
1673 def appliedname(self, index):
1674 def appliedname(self, index):
1674 pname = self.applied[index].name
1675 pname = self.applied[index].name
1675 if not self.ui.verbose:
1676 if not self.ui.verbose:
1676 p = pname
1677 p = pname
1677 else:
1678 else:
1678 p = str(self.series.index(pname)) + " " + pname
1679 p = str(self.series.index(pname)) + " " + pname
1679 return p
1680 return p
1680
1681
1681 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1682 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1682 force=None, git=False):
1683 force=None, git=False):
1683 def checkseries(patchname):
1684 def checkseries(patchname):
1684 if patchname in self.series:
1685 if patchname in self.series:
1685 raise util.Abort(_('patch %s is already in the series file')
1686 raise util.Abort(_('patch %s is already in the series file')
1686 % patchname)
1687 % patchname)
1687 def checkfile(patchname):
1688 def checkfile(patchname):
1688 if not force and os.path.exists(self.join(patchname)):
1689 if not force and os.path.exists(self.join(patchname)):
1689 raise util.Abort(_('patch "%s" already exists')
1690 raise util.Abort(_('patch "%s" already exists')
1690 % patchname)
1691 % patchname)
1691
1692
1692 if rev:
1693 if rev:
1693 if files:
1694 if files:
1694 raise util.Abort(_('option "-r" not valid when importing '
1695 raise util.Abort(_('option "-r" not valid when importing '
1695 'files'))
1696 'files'))
1696 rev = cmdutil.revrange(repo, rev)
1697 rev = cmdutil.revrange(repo, rev)
1697 rev.sort(reverse=True)
1698 rev.sort(reverse=True)
1698 if (len(files) > 1 or len(rev) > 1) and patchname:
1699 if (len(files) > 1 or len(rev) > 1) and patchname:
1699 raise util.Abort(_('option "-n" not valid when importing multiple '
1700 raise util.Abort(_('option "-n" not valid when importing multiple '
1700 'patches'))
1701 'patches'))
1701 if rev:
1702 if rev:
1702 # If mq patches are applied, we can only import revisions
1703 # If mq patches are applied, we can only import revisions
1703 # that form a linear path to qbase.
1704 # that form a linear path to qbase.
1704 # Otherwise, they should form a linear path to a head.
1705 # Otherwise, they should form a linear path to a head.
1705 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1706 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1706 if len(heads) > 1:
1707 if len(heads) > 1:
1707 raise util.Abort(_('revision %d is the root of more than one '
1708 raise util.Abort(_('revision %d is the root of more than one '
1708 'branch') % rev[-1])
1709 'branch') % rev[-1])
1709 if self.applied:
1710 if self.applied:
1710 base = repo.changelog.node(rev[0])
1711 base = repo.changelog.node(rev[0])
1711 if base in [n.node for n in self.applied]:
1712 if base in [n.node for n in self.applied]:
1712 raise util.Abort(_('revision %d is already managed')
1713 raise util.Abort(_('revision %d is already managed')
1713 % rev[0])
1714 % rev[0])
1714 if heads != [self.applied[-1].node]:
1715 if heads != [self.applied[-1].node]:
1715 raise util.Abort(_('revision %d is not the parent of '
1716 raise util.Abort(_('revision %d is not the parent of '
1716 'the queue') % rev[0])
1717 'the queue') % rev[0])
1717 base = repo.changelog.rev(self.applied[0].node)
1718 base = repo.changelog.rev(self.applied[0].node)
1718 lastparent = repo.changelog.parentrevs(base)[0]
1719 lastparent = repo.changelog.parentrevs(base)[0]
1719 else:
1720 else:
1720 if heads != [repo.changelog.node(rev[0])]:
1721 if heads != [repo.changelog.node(rev[0])]:
1721 raise util.Abort(_('revision %d has unmanaged children')
1722 raise util.Abort(_('revision %d has unmanaged children')
1722 % rev[0])
1723 % rev[0])
1723 lastparent = None
1724 lastparent = None
1724
1725
1725 diffopts = self.diffopts({'git': git})
1726 diffopts = self.diffopts({'git': git})
1726 for r in rev:
1727 for r in rev:
1727 p1, p2 = repo.changelog.parentrevs(r)
1728 p1, p2 = repo.changelog.parentrevs(r)
1728 n = repo.changelog.node(r)
1729 n = repo.changelog.node(r)
1729 if p2 != nullrev:
1730 if p2 != nullrev:
1730 raise util.Abort(_('cannot import merge revision %d') % r)
1731 raise util.Abort(_('cannot import merge revision %d') % r)
1731 if lastparent and lastparent != r:
1732 if lastparent and lastparent != r:
1732 raise util.Abort(_('revision %d is not the parent of %d')
1733 raise util.Abort(_('revision %d is not the parent of %d')
1733 % (r, lastparent))
1734 % (r, lastparent))
1734 lastparent = p1
1735 lastparent = p1
1735
1736
1736 if not patchname:
1737 if not patchname:
1737 patchname = normname('%d.diff' % r)
1738 patchname = normname('%d.diff' % r)
1738 self.check_reserved_name(patchname)
1739 self.check_reserved_name(patchname)
1739 checkseries(patchname)
1740 checkseries(patchname)
1740 checkfile(patchname)
1741 checkfile(patchname)
1741 self.full_series.insert(0, patchname)
1742 self.full_series.insert(0, patchname)
1742
1743
1743 patchf = self.opener(patchname, "w")
1744 patchf = self.opener(patchname, "w")
1744 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1745 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1745 patchf.close()
1746 patchf.close()
1746
1747
1747 se = statusentry(n, patchname)
1748 se = statusentry(n, patchname)
1748 self.applied.insert(0, se)
1749 self.applied.insert(0, se)
1749
1750
1750 self.added.append(patchname)
1751 self.added.append(patchname)
1751 patchname = None
1752 patchname = None
1752 self.parse_series()
1753 self.parse_series()
1753 self.applied_dirty = 1
1754 self.applied_dirty = 1
1754 self.series_dirty = True
1755 self.series_dirty = True
1755
1756
1756 for i, filename in enumerate(files):
1757 for i, filename in enumerate(files):
1757 if existing:
1758 if existing:
1758 if filename == '-':
1759 if filename == '-':
1759 raise util.Abort(_('-e is incompatible with import from -'))
1760 raise util.Abort(_('-e is incompatible with import from -'))
1760 filename = normname(filename)
1761 filename = normname(filename)
1761 self.check_reserved_name(filename)
1762 self.check_reserved_name(filename)
1762 originpath = self.join(filename)
1763 originpath = self.join(filename)
1763 if not os.path.isfile(originpath):
1764 if not os.path.isfile(originpath):
1764 raise util.Abort(_("patch %s does not exist") % filename)
1765 raise util.Abort(_("patch %s does not exist") % filename)
1765
1766
1766 if patchname:
1767 if patchname:
1767 self.check_reserved_name(patchname)
1768 self.check_reserved_name(patchname)
1768 checkfile(patchname)
1769 checkfile(patchname)
1769
1770
1770 self.ui.write(_('renaming %s to %s\n')
1771 self.ui.write(_('renaming %s to %s\n')
1771 % (filename, patchname))
1772 % (filename, patchname))
1772 util.rename(originpath, self.join(patchname))
1773 util.rename(originpath, self.join(patchname))
1773 else:
1774 else:
1774 patchname = filename
1775 patchname = filename
1775
1776
1776 else:
1777 else:
1777 try:
1778 try:
1778 if filename == '-':
1779 if filename == '-':
1779 if not patchname:
1780 if not patchname:
1780 raise util.Abort(
1781 raise util.Abort(
1781 _('need --name to import a patch from -'))
1782 _('need --name to import a patch from -'))
1782 text = sys.stdin.read()
1783 text = sys.stdin.read()
1783 else:
1784 else:
1784 text = url.open(self.ui, filename).read()
1785 fp = url.open(self.ui, filename)
1786 text = fp.read()
1787 fp.close()
1785 except (OSError, IOError):
1788 except (OSError, IOError):
1786 raise util.Abort(_("unable to read file %s") % filename)
1789 raise util.Abort(_("unable to read file %s") % filename)
1787 if not patchname:
1790 if not patchname:
1788 patchname = normname(os.path.basename(filename))
1791 patchname = normname(os.path.basename(filename))
1789 self.check_reserved_name(patchname)
1792 self.check_reserved_name(patchname)
1790 checkfile(patchname)
1793 checkfile(patchname)
1791 patchf = self.opener(patchname, "w")
1794 patchf = self.opener(patchname, "w")
1792 patchf.write(text)
1795 patchf.write(text)
1796 patchf.close()
1793 if not force:
1797 if not force:
1794 checkseries(patchname)
1798 checkseries(patchname)
1795 if patchname not in self.series:
1799 if patchname not in self.series:
1796 index = self.full_series_end() + i
1800 index = self.full_series_end() + i
1797 self.full_series[index:index] = [patchname]
1801 self.full_series[index:index] = [patchname]
1798 self.parse_series()
1802 self.parse_series()
1799 self.series_dirty = True
1803 self.series_dirty = True
1800 self.ui.warn(_("adding %s to series file\n") % patchname)
1804 self.ui.warn(_("adding %s to series file\n") % patchname)
1801 self.added.append(patchname)
1805 self.added.append(patchname)
1802 patchname = None
1806 patchname = None
1803
1807
1804 def delete(ui, repo, *patches, **opts):
1808 def delete(ui, repo, *patches, **opts):
1805 """remove patches from queue
1809 """remove patches from queue
1806
1810
1807 The patches must not be applied, and at least one patch is required. With
1811 The patches must not be applied, and at least one patch is required. With
1808 -k/--keep, the patch files are preserved in the patch directory.
1812 -k/--keep, the patch files are preserved in the patch directory.
1809
1813
1810 To stop managing a patch and move it into permanent history,
1814 To stop managing a patch and move it into permanent history,
1811 use the :hg:`qfinish` command."""
1815 use the :hg:`qfinish` command."""
1812 q = repo.mq
1816 q = repo.mq
1813 q.delete(repo, patches, opts)
1817 q.delete(repo, patches, opts)
1814 q.save_dirty()
1818 q.save_dirty()
1815 return 0
1819 return 0
1816
1820
1817 def applied(ui, repo, patch=None, **opts):
1821 def applied(ui, repo, patch=None, **opts):
1818 """print the patches already applied
1822 """print the patches already applied
1819
1823
1820 Returns 0 on success."""
1824 Returns 0 on success."""
1821
1825
1822 q = repo.mq
1826 q = repo.mq
1823
1827
1824 if patch:
1828 if patch:
1825 if patch not in q.series:
1829 if patch not in q.series:
1826 raise util.Abort(_("patch %s is not in series file") % patch)
1830 raise util.Abort(_("patch %s is not in series file") % patch)
1827 end = q.series.index(patch) + 1
1831 end = q.series.index(patch) + 1
1828 else:
1832 else:
1829 end = q.series_end(True)
1833 end = q.series_end(True)
1830
1834
1831 if opts.get('last') and not end:
1835 if opts.get('last') and not end:
1832 ui.write(_("no patches applied\n"))
1836 ui.write(_("no patches applied\n"))
1833 return 1
1837 return 1
1834 elif opts.get('last') and end == 1:
1838 elif opts.get('last') and end == 1:
1835 ui.write(_("only one patch applied\n"))
1839 ui.write(_("only one patch applied\n"))
1836 return 1
1840 return 1
1837 elif opts.get('last'):
1841 elif opts.get('last'):
1838 start = end - 2
1842 start = end - 2
1839 end = 1
1843 end = 1
1840 else:
1844 else:
1841 start = 0
1845 start = 0
1842
1846
1843 q.qseries(repo, length=end, start=start, status='A',
1847 q.qseries(repo, length=end, start=start, status='A',
1844 summary=opts.get('summary'))
1848 summary=opts.get('summary'))
1845
1849
1846
1850
1847 def unapplied(ui, repo, patch=None, **opts):
1851 def unapplied(ui, repo, patch=None, **opts):
1848 """print the patches not yet applied
1852 """print the patches not yet applied
1849
1853
1850 Returns 0 on success."""
1854 Returns 0 on success."""
1851
1855
1852 q = repo.mq
1856 q = repo.mq
1853 if patch:
1857 if patch:
1854 if patch not in q.series:
1858 if patch not in q.series:
1855 raise util.Abort(_("patch %s is not in series file") % patch)
1859 raise util.Abort(_("patch %s is not in series file") % patch)
1856 start = q.series.index(patch) + 1
1860 start = q.series.index(patch) + 1
1857 else:
1861 else:
1858 start = q.series_end(True)
1862 start = q.series_end(True)
1859
1863
1860 if start == len(q.series) and opts.get('first'):
1864 if start == len(q.series) and opts.get('first'):
1861 ui.write(_("all patches applied\n"))
1865 ui.write(_("all patches applied\n"))
1862 return 1
1866 return 1
1863
1867
1864 length = opts.get('first') and 1 or None
1868 length = opts.get('first') and 1 or None
1865 q.qseries(repo, start=start, length=length, status='U',
1869 q.qseries(repo, start=start, length=length, status='U',
1866 summary=opts.get('summary'))
1870 summary=opts.get('summary'))
1867
1871
1868 def qimport(ui, repo, *filename, **opts):
1872 def qimport(ui, repo, *filename, **opts):
1869 """import a patch
1873 """import a patch
1870
1874
1871 The patch is inserted into the series after the last applied
1875 The patch is inserted into the series after the last applied
1872 patch. If no patches have been applied, qimport prepends the patch
1876 patch. If no patches have been applied, qimport prepends the patch
1873 to the series.
1877 to the series.
1874
1878
1875 The patch will have the same name as its source file unless you
1879 The patch will have the same name as its source file unless you
1876 give it a new one with -n/--name.
1880 give it a new one with -n/--name.
1877
1881
1878 You can register an existing patch inside the patch directory with
1882 You can register an existing patch inside the patch directory with
1879 the -e/--existing flag.
1883 the -e/--existing flag.
1880
1884
1881 With -f/--force, an existing patch of the same name will be
1885 With -f/--force, an existing patch of the same name will be
1882 overwritten.
1886 overwritten.
1883
1887
1884 An existing changeset may be placed under mq control with -r/--rev
1888 An existing changeset may be placed under mq control with -r/--rev
1885 (e.g. qimport --rev tip -n patch will place tip under mq control).
1889 (e.g. qimport --rev tip -n patch will place tip under mq control).
1886 With -g/--git, patches imported with --rev will use the git diff
1890 With -g/--git, patches imported with --rev will use the git diff
1887 format. See the diffs help topic for information on why this is
1891 format. See the diffs help topic for information on why this is
1888 important for preserving rename/copy information and permission
1892 important for preserving rename/copy information and permission
1889 changes.
1893 changes.
1890
1894
1891 To import a patch from standard input, pass - as the patch file.
1895 To import a patch from standard input, pass - as the patch file.
1892 When importing from standard input, a patch name must be specified
1896 When importing from standard input, a patch name must be specified
1893 using the --name flag.
1897 using the --name flag.
1894
1898
1895 To import an existing patch while renaming it::
1899 To import an existing patch while renaming it::
1896
1900
1897 hg qimport -e existing-patch -n new-name
1901 hg qimport -e existing-patch -n new-name
1898
1902
1899 Returns 0 if import succeeded.
1903 Returns 0 if import succeeded.
1900 """
1904 """
1901 q = repo.mq
1905 q = repo.mq
1902 try:
1906 try:
1903 q.qimport(repo, filename, patchname=opts.get('name'),
1907 q.qimport(repo, filename, patchname=opts.get('name'),
1904 existing=opts.get('existing'), force=opts.get('force'),
1908 existing=opts.get('existing'), force=opts.get('force'),
1905 rev=opts.get('rev'), git=opts.get('git'))
1909 rev=opts.get('rev'), git=opts.get('git'))
1906 finally:
1910 finally:
1907 q.save_dirty()
1911 q.save_dirty()
1908
1912
1909 if opts.get('push') and not opts.get('rev'):
1913 if opts.get('push') and not opts.get('rev'):
1910 return q.push(repo, None)
1914 return q.push(repo, None)
1911 return 0
1915 return 0
1912
1916
1913 def qinit(ui, repo, create):
1917 def qinit(ui, repo, create):
1914 """initialize a new queue repository
1918 """initialize a new queue repository
1915
1919
1916 This command also creates a series file for ordering patches, and
1920 This command also creates a series file for ordering patches, and
1917 an mq-specific .hgignore file in the queue repository, to exclude
1921 an mq-specific .hgignore file in the queue repository, to exclude
1918 the status and guards files (these contain mostly transient state).
1922 the status and guards files (these contain mostly transient state).
1919
1923
1920 Returns 0 if initialization succeeded."""
1924 Returns 0 if initialization succeeded."""
1921 q = repo.mq
1925 q = repo.mq
1922 r = q.init(repo, create)
1926 r = q.init(repo, create)
1923 q.save_dirty()
1927 q.save_dirty()
1924 if r:
1928 if r:
1925 if not os.path.exists(r.wjoin('.hgignore')):
1929 if not os.path.exists(r.wjoin('.hgignore')):
1926 fp = r.wopener('.hgignore', 'w')
1930 fp = r.wopener('.hgignore', 'w')
1927 fp.write('^\\.hg\n')
1931 fp.write('^\\.hg\n')
1928 fp.write('^\\.mq\n')
1932 fp.write('^\\.mq\n')
1929 fp.write('syntax: glob\n')
1933 fp.write('syntax: glob\n')
1930 fp.write('status\n')
1934 fp.write('status\n')
1931 fp.write('guards\n')
1935 fp.write('guards\n')
1932 fp.close()
1936 fp.close()
1933 if not os.path.exists(r.wjoin('series')):
1937 if not os.path.exists(r.wjoin('series')):
1934 r.wopener('series', 'w').close()
1938 r.wopener('series', 'w').close()
1935 r[None].add(['.hgignore', 'series'])
1939 r[None].add(['.hgignore', 'series'])
1936 commands.add(ui, r)
1940 commands.add(ui, r)
1937 return 0
1941 return 0
1938
1942
1939 def init(ui, repo, **opts):
1943 def init(ui, repo, **opts):
1940 """init a new queue repository (DEPRECATED)
1944 """init a new queue repository (DEPRECATED)
1941
1945
1942 The queue repository is unversioned by default. If
1946 The queue repository is unversioned by default. If
1943 -c/--create-repo is specified, qinit will create a separate nested
1947 -c/--create-repo is specified, qinit will create a separate nested
1944 repository for patches (qinit -c may also be run later to convert
1948 repository for patches (qinit -c may also be run later to convert
1945 an unversioned patch repository into a versioned one). You can use
1949 an unversioned patch repository into a versioned one). You can use
1946 qcommit to commit changes to this queue repository.
1950 qcommit to commit changes to this queue repository.
1947
1951
1948 This command is deprecated. Without -c, it's implied by other relevant
1952 This command is deprecated. Without -c, it's implied by other relevant
1949 commands. With -c, use :hg:`init --mq` instead."""
1953 commands. With -c, use :hg:`init --mq` instead."""
1950 return qinit(ui, repo, create=opts.get('create_repo'))
1954 return qinit(ui, repo, create=opts.get('create_repo'))
1951
1955
1952 def clone(ui, source, dest=None, **opts):
1956 def clone(ui, source, dest=None, **opts):
1953 '''clone main and patch repository at same time
1957 '''clone main and patch repository at same time
1954
1958
1955 If source is local, destination will have no patches applied. If
1959 If source is local, destination will have no patches applied. If
1956 source is remote, this command can not check if patches are
1960 source is remote, this command can not check if patches are
1957 applied in source, so cannot guarantee that patches are not
1961 applied in source, so cannot guarantee that patches are not
1958 applied in destination. If you clone remote repository, be sure
1962 applied in destination. If you clone remote repository, be sure
1959 before that it has no patches applied.
1963 before that it has no patches applied.
1960
1964
1961 Source patch repository is looked for in <src>/.hg/patches by
1965 Source patch repository is looked for in <src>/.hg/patches by
1962 default. Use -p <url> to change.
1966 default. Use -p <url> to change.
1963
1967
1964 The patch directory must be a nested Mercurial repository, as
1968 The patch directory must be a nested Mercurial repository, as
1965 would be created by :hg:`init --mq`.
1969 would be created by :hg:`init --mq`.
1966
1970
1967 Return 0 on success.
1971 Return 0 on success.
1968 '''
1972 '''
1969 def patchdir(repo):
1973 def patchdir(repo):
1970 url = repo.url()
1974 url = repo.url()
1971 if url.endswith('/'):
1975 if url.endswith('/'):
1972 url = url[:-1]
1976 url = url[:-1]
1973 return url + '/.hg/patches'
1977 return url + '/.hg/patches'
1974 if dest is None:
1978 if dest is None:
1975 dest = hg.defaultdest(source)
1979 dest = hg.defaultdest(source)
1976 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1980 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1977 if opts.get('patches'):
1981 if opts.get('patches'):
1978 patchespath = ui.expandpath(opts.get('patches'))
1982 patchespath = ui.expandpath(opts.get('patches'))
1979 else:
1983 else:
1980 patchespath = patchdir(sr)
1984 patchespath = patchdir(sr)
1981 try:
1985 try:
1982 hg.repository(ui, patchespath)
1986 hg.repository(ui, patchespath)
1983 except error.RepoError:
1987 except error.RepoError:
1984 raise util.Abort(_('versioned patch repository not found'
1988 raise util.Abort(_('versioned patch repository not found'
1985 ' (see init --mq)'))
1989 ' (see init --mq)'))
1986 qbase, destrev = None, None
1990 qbase, destrev = None, None
1987 if sr.local():
1991 if sr.local():
1988 if sr.mq.applied:
1992 if sr.mq.applied:
1989 qbase = sr.mq.applied[0].node
1993 qbase = sr.mq.applied[0].node
1990 if not hg.islocal(dest):
1994 if not hg.islocal(dest):
1991 heads = set(sr.heads())
1995 heads = set(sr.heads())
1992 destrev = list(heads.difference(sr.heads(qbase)))
1996 destrev = list(heads.difference(sr.heads(qbase)))
1993 destrev.append(sr.changelog.parents(qbase)[0])
1997 destrev.append(sr.changelog.parents(qbase)[0])
1994 elif sr.capable('lookup'):
1998 elif sr.capable('lookup'):
1995 try:
1999 try:
1996 qbase = sr.lookup('qbase')
2000 qbase = sr.lookup('qbase')
1997 except error.RepoError:
2001 except error.RepoError:
1998 pass
2002 pass
1999 ui.note(_('cloning main repository\n'))
2003 ui.note(_('cloning main repository\n'))
2000 sr, dr = hg.clone(ui, sr.url(), dest,
2004 sr, dr = hg.clone(ui, sr.url(), dest,
2001 pull=opts.get('pull'),
2005 pull=opts.get('pull'),
2002 rev=destrev,
2006 rev=destrev,
2003 update=False,
2007 update=False,
2004 stream=opts.get('uncompressed'))
2008 stream=opts.get('uncompressed'))
2005 ui.note(_('cloning patch repository\n'))
2009 ui.note(_('cloning patch repository\n'))
2006 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2010 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2007 pull=opts.get('pull'), update=not opts.get('noupdate'),
2011 pull=opts.get('pull'), update=not opts.get('noupdate'),
2008 stream=opts.get('uncompressed'))
2012 stream=opts.get('uncompressed'))
2009 if dr.local():
2013 if dr.local():
2010 if qbase:
2014 if qbase:
2011 ui.note(_('stripping applied patches from destination '
2015 ui.note(_('stripping applied patches from destination '
2012 'repository\n'))
2016 'repository\n'))
2013 dr.mq.strip(dr, [qbase], update=False, backup=None)
2017 dr.mq.strip(dr, [qbase], update=False, backup=None)
2014 if not opts.get('noupdate'):
2018 if not opts.get('noupdate'):
2015 ui.note(_('updating destination repository\n'))
2019 ui.note(_('updating destination repository\n'))
2016 hg.update(dr, dr.changelog.tip())
2020 hg.update(dr, dr.changelog.tip())
2017
2021
2018 def commit(ui, repo, *pats, **opts):
2022 def commit(ui, repo, *pats, **opts):
2019 """commit changes in the queue repository (DEPRECATED)
2023 """commit changes in the queue repository (DEPRECATED)
2020
2024
2021 This command is deprecated; use :hg:`commit --mq` instead."""
2025 This command is deprecated; use :hg:`commit --mq` instead."""
2022 q = repo.mq
2026 q = repo.mq
2023 r = q.qrepo()
2027 r = q.qrepo()
2024 if not r:
2028 if not r:
2025 raise util.Abort('no queue repository')
2029 raise util.Abort('no queue repository')
2026 commands.commit(r.ui, r, *pats, **opts)
2030 commands.commit(r.ui, r, *pats, **opts)
2027
2031
2028 def series(ui, repo, **opts):
2032 def series(ui, repo, **opts):
2029 """print the entire series file
2033 """print the entire series file
2030
2034
2031 Returns 0 on success."""
2035 Returns 0 on success."""
2032 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2036 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2033 return 0
2037 return 0
2034
2038
2035 def top(ui, repo, **opts):
2039 def top(ui, repo, **opts):
2036 """print the name of the current patch
2040 """print the name of the current patch
2037
2041
2038 Returns 0 on success."""
2042 Returns 0 on success."""
2039 q = repo.mq
2043 q = repo.mq
2040 t = q.applied and q.series_end(True) or 0
2044 t = q.applied and q.series_end(True) or 0
2041 if t:
2045 if t:
2042 q.qseries(repo, start=t - 1, length=1, status='A',
2046 q.qseries(repo, start=t - 1, length=1, status='A',
2043 summary=opts.get('summary'))
2047 summary=opts.get('summary'))
2044 else:
2048 else:
2045 ui.write(_("no patches applied\n"))
2049 ui.write(_("no patches applied\n"))
2046 return 1
2050 return 1
2047
2051
2048 def next(ui, repo, **opts):
2052 def next(ui, repo, **opts):
2049 """print the name of the next patch
2053 """print the name of the next patch
2050
2054
2051 Returns 0 on success."""
2055 Returns 0 on success."""
2052 q = repo.mq
2056 q = repo.mq
2053 end = q.series_end()
2057 end = q.series_end()
2054 if end == len(q.series):
2058 if end == len(q.series):
2055 ui.write(_("all patches applied\n"))
2059 ui.write(_("all patches applied\n"))
2056 return 1
2060 return 1
2057 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2061 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2058
2062
2059 def prev(ui, repo, **opts):
2063 def prev(ui, repo, **opts):
2060 """print the name of the previous patch
2064 """print the name of the previous patch
2061
2065
2062 Returns 0 on success."""
2066 Returns 0 on success."""
2063 q = repo.mq
2067 q = repo.mq
2064 l = len(q.applied)
2068 l = len(q.applied)
2065 if l == 1:
2069 if l == 1:
2066 ui.write(_("only one patch applied\n"))
2070 ui.write(_("only one patch applied\n"))
2067 return 1
2071 return 1
2068 if not l:
2072 if not l:
2069 ui.write(_("no patches applied\n"))
2073 ui.write(_("no patches applied\n"))
2070 return 1
2074 return 1
2071 q.qseries(repo, start=l - 2, length=1, status='A',
2075 q.qseries(repo, start=l - 2, length=1, status='A',
2072 summary=opts.get('summary'))
2076 summary=opts.get('summary'))
2073
2077
2074 def setupheaderopts(ui, opts):
2078 def setupheaderopts(ui, opts):
2075 if not opts.get('user') and opts.get('currentuser'):
2079 if not opts.get('user') and opts.get('currentuser'):
2076 opts['user'] = ui.username()
2080 opts['user'] = ui.username()
2077 if not opts.get('date') and opts.get('currentdate'):
2081 if not opts.get('date') and opts.get('currentdate'):
2078 opts['date'] = "%d %d" % util.makedate()
2082 opts['date'] = "%d %d" % util.makedate()
2079
2083
2080 def new(ui, repo, patch, *args, **opts):
2084 def new(ui, repo, patch, *args, **opts):
2081 """create a new patch
2085 """create a new patch
2082
2086
2083 qnew creates a new patch on top of the currently-applied patch (if
2087 qnew creates a new patch on top of the currently-applied patch (if
2084 any). The patch will be initialized with any outstanding changes
2088 any). The patch will be initialized with any outstanding changes
2085 in the working directory. You may also use -I/--include,
2089 in the working directory. You may also use -I/--include,
2086 -X/--exclude, and/or a list of files after the patch name to add
2090 -X/--exclude, and/or a list of files after the patch name to add
2087 only changes to matching files to the new patch, leaving the rest
2091 only changes to matching files to the new patch, leaving the rest
2088 as uncommitted modifications.
2092 as uncommitted modifications.
2089
2093
2090 -u/--user and -d/--date can be used to set the (given) user and
2094 -u/--user and -d/--date can be used to set the (given) user and
2091 date, respectively. -U/--currentuser and -D/--currentdate set user
2095 date, respectively. -U/--currentuser and -D/--currentdate set user
2092 to current user and date to current date.
2096 to current user and date to current date.
2093
2097
2094 -e/--edit, -m/--message or -l/--logfile set the patch header as
2098 -e/--edit, -m/--message or -l/--logfile set the patch header as
2095 well as the commit message. If none is specified, the header is
2099 well as the commit message. If none is specified, the header is
2096 empty and the commit message is '[mq]: PATCH'.
2100 empty and the commit message is '[mq]: PATCH'.
2097
2101
2098 Use the -g/--git option to keep the patch in the git extended diff
2102 Use the -g/--git option to keep the patch in the git extended diff
2099 format. Read the diffs help topic for more information on why this
2103 format. Read the diffs help topic for more information on why this
2100 is important for preserving permission changes and copy/rename
2104 is important for preserving permission changes and copy/rename
2101 information.
2105 information.
2102
2106
2103 Returns 0 on successful creation of a new patch.
2107 Returns 0 on successful creation of a new patch.
2104 """
2108 """
2105 msg = cmdutil.logmessage(opts)
2109 msg = cmdutil.logmessage(opts)
2106 def getmsg():
2110 def getmsg():
2107 return ui.edit(msg, opts.get('user') or ui.username())
2111 return ui.edit(msg, opts.get('user') or ui.username())
2108 q = repo.mq
2112 q = repo.mq
2109 opts['msg'] = msg
2113 opts['msg'] = msg
2110 if opts.get('edit'):
2114 if opts.get('edit'):
2111 opts['msg'] = getmsg
2115 opts['msg'] = getmsg
2112 else:
2116 else:
2113 opts['msg'] = msg
2117 opts['msg'] = msg
2114 setupheaderopts(ui, opts)
2118 setupheaderopts(ui, opts)
2115 q.new(repo, patch, *args, **opts)
2119 q.new(repo, patch, *args, **opts)
2116 q.save_dirty()
2120 q.save_dirty()
2117 return 0
2121 return 0
2118
2122
2119 def refresh(ui, repo, *pats, **opts):
2123 def refresh(ui, repo, *pats, **opts):
2120 """update the current patch
2124 """update the current patch
2121
2125
2122 If any file patterns are provided, the refreshed patch will
2126 If any file patterns are provided, the refreshed patch will
2123 contain only the modifications that match those patterns; the
2127 contain only the modifications that match those patterns; the
2124 remaining modifications will remain in the working directory.
2128 remaining modifications will remain in the working directory.
2125
2129
2126 If -s/--short is specified, files currently included in the patch
2130 If -s/--short is specified, files currently included in the patch
2127 will be refreshed just like matched files and remain in the patch.
2131 will be refreshed just like matched files and remain in the patch.
2128
2132
2129 If -e/--edit is specified, Mercurial will start your configured editor for
2133 If -e/--edit is specified, Mercurial will start your configured editor for
2130 you to enter a message. In case qrefresh fails, you will find a backup of
2134 you to enter a message. In case qrefresh fails, you will find a backup of
2131 your message in ``.hg/last-message.txt``.
2135 your message in ``.hg/last-message.txt``.
2132
2136
2133 hg add/remove/copy/rename work as usual, though you might want to
2137 hg add/remove/copy/rename work as usual, though you might want to
2134 use git-style patches (-g/--git or [diff] git=1) to track copies
2138 use git-style patches (-g/--git or [diff] git=1) to track copies
2135 and renames. See the diffs help topic for more information on the
2139 and renames. See the diffs help topic for more information on the
2136 git diff format.
2140 git diff format.
2137
2141
2138 Returns 0 on success.
2142 Returns 0 on success.
2139 """
2143 """
2140 q = repo.mq
2144 q = repo.mq
2141 message = cmdutil.logmessage(opts)
2145 message = cmdutil.logmessage(opts)
2142 if opts.get('edit'):
2146 if opts.get('edit'):
2143 if not q.applied:
2147 if not q.applied:
2144 ui.write(_("no patches applied\n"))
2148 ui.write(_("no patches applied\n"))
2145 return 1
2149 return 1
2146 if message:
2150 if message:
2147 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2151 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2148 patch = q.applied[-1].name
2152 patch = q.applied[-1].name
2149 ph = patchheader(q.join(patch), q.plainmode)
2153 ph = patchheader(q.join(patch), q.plainmode)
2150 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2154 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2151 # We don't want to lose the patch message if qrefresh fails (issue2062)
2155 # We don't want to lose the patch message if qrefresh fails (issue2062)
2152 msgfile = repo.opener('last-message.txt', 'wb')
2156 msgfile = repo.opener('last-message.txt', 'wb')
2153 msgfile.write(message)
2157 msgfile.write(message)
2154 msgfile.close()
2158 msgfile.close()
2155 setupheaderopts(ui, opts)
2159 setupheaderopts(ui, opts)
2156 ret = q.refresh(repo, pats, msg=message, **opts)
2160 ret = q.refresh(repo, pats, msg=message, **opts)
2157 q.save_dirty()
2161 q.save_dirty()
2158 return ret
2162 return ret
2159
2163
2160 def diff(ui, repo, *pats, **opts):
2164 def diff(ui, repo, *pats, **opts):
2161 """diff of the current patch and subsequent modifications
2165 """diff of the current patch and subsequent modifications
2162
2166
2163 Shows a diff which includes the current patch as well as any
2167 Shows a diff which includes the current patch as well as any
2164 changes which have been made in the working directory since the
2168 changes which have been made in the working directory since the
2165 last refresh (thus showing what the current patch would become
2169 last refresh (thus showing what the current patch would become
2166 after a qrefresh).
2170 after a qrefresh).
2167
2171
2168 Use :hg:`diff` if you only want to see the changes made since the
2172 Use :hg:`diff` if you only want to see the changes made since the
2169 last qrefresh, or :hg:`export qtip` if you want to see changes
2173 last qrefresh, or :hg:`export qtip` if you want to see changes
2170 made by the current patch without including changes made since the
2174 made by the current patch without including changes made since the
2171 qrefresh.
2175 qrefresh.
2172
2176
2173 Returns 0 on success.
2177 Returns 0 on success.
2174 """
2178 """
2175 repo.mq.diff(repo, pats, opts)
2179 repo.mq.diff(repo, pats, opts)
2176 return 0
2180 return 0
2177
2181
2178 def fold(ui, repo, *files, **opts):
2182 def fold(ui, repo, *files, **opts):
2179 """fold the named patches into the current patch
2183 """fold the named patches into the current patch
2180
2184
2181 Patches must not yet be applied. Each patch will be successively
2185 Patches must not yet be applied. Each patch will be successively
2182 applied to the current patch in the order given. If all the
2186 applied to the current patch in the order given. If all the
2183 patches apply successfully, the current patch will be refreshed
2187 patches apply successfully, the current patch will be refreshed
2184 with the new cumulative patch, and the folded patches will be
2188 with the new cumulative patch, and the folded patches will be
2185 deleted. With -k/--keep, the folded patch files will not be
2189 deleted. With -k/--keep, the folded patch files will not be
2186 removed afterwards.
2190 removed afterwards.
2187
2191
2188 The header for each folded patch will be concatenated with the
2192 The header for each folded patch will be concatenated with the
2189 current patch header, separated by a line of ``* * *``.
2193 current patch header, separated by a line of ``* * *``.
2190
2194
2191 Returns 0 on success."""
2195 Returns 0 on success."""
2192
2196
2193 q = repo.mq
2197 q = repo.mq
2194
2198
2195 if not files:
2199 if not files:
2196 raise util.Abort(_('qfold requires at least one patch name'))
2200 raise util.Abort(_('qfold requires at least one patch name'))
2197 if not q.check_toppatch(repo)[0]:
2201 if not q.check_toppatch(repo)[0]:
2198 raise util.Abort(_('no patches applied'))
2202 raise util.Abort(_('no patches applied'))
2199 q.check_localchanges(repo)
2203 q.check_localchanges(repo)
2200
2204
2201 message = cmdutil.logmessage(opts)
2205 message = cmdutil.logmessage(opts)
2202 if opts.get('edit'):
2206 if opts.get('edit'):
2203 if message:
2207 if message:
2204 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2208 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2205
2209
2206 parent = q.lookup('qtip')
2210 parent = q.lookup('qtip')
2207 patches = []
2211 patches = []
2208 messages = []
2212 messages = []
2209 for f in files:
2213 for f in files:
2210 p = q.lookup(f)
2214 p = q.lookup(f)
2211 if p in patches or p == parent:
2215 if p in patches or p == parent:
2212 ui.warn(_('Skipping already folded patch %s\n') % p)
2216 ui.warn(_('Skipping already folded patch %s\n') % p)
2213 if q.isapplied(p):
2217 if q.isapplied(p):
2214 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2218 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2215 patches.append(p)
2219 patches.append(p)
2216
2220
2217 for p in patches:
2221 for p in patches:
2218 if not message:
2222 if not message:
2219 ph = patchheader(q.join(p), q.plainmode)
2223 ph = patchheader(q.join(p), q.plainmode)
2220 if ph.message:
2224 if ph.message:
2221 messages.append(ph.message)
2225 messages.append(ph.message)
2222 pf = q.join(p)
2226 pf = q.join(p)
2223 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2227 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2224 if not patchsuccess:
2228 if not patchsuccess:
2225 raise util.Abort(_('error folding patch %s') % p)
2229 raise util.Abort(_('error folding patch %s') % p)
2226 cmdutil.updatedir(ui, repo, files)
2230 cmdutil.updatedir(ui, repo, files)
2227
2231
2228 if not message:
2232 if not message:
2229 ph = patchheader(q.join(parent), q.plainmode)
2233 ph = patchheader(q.join(parent), q.plainmode)
2230 message, user = ph.message, ph.user
2234 message, user = ph.message, ph.user
2231 for msg in messages:
2235 for msg in messages:
2232 message.append('* * *')
2236 message.append('* * *')
2233 message.extend(msg)
2237 message.extend(msg)
2234 message = '\n'.join(message)
2238 message = '\n'.join(message)
2235
2239
2236 if opts.get('edit'):
2240 if opts.get('edit'):
2237 message = ui.edit(message, user or ui.username())
2241 message = ui.edit(message, user or ui.username())
2238
2242
2239 diffopts = q.patchopts(q.diffopts(), *patches)
2243 diffopts = q.patchopts(q.diffopts(), *patches)
2240 q.refresh(repo, msg=message, git=diffopts.git)
2244 q.refresh(repo, msg=message, git=diffopts.git)
2241 q.delete(repo, patches, opts)
2245 q.delete(repo, patches, opts)
2242 q.save_dirty()
2246 q.save_dirty()
2243
2247
2244 def goto(ui, repo, patch, **opts):
2248 def goto(ui, repo, patch, **opts):
2245 '''push or pop patches until named patch is at top of stack
2249 '''push or pop patches until named patch is at top of stack
2246
2250
2247 Returns 0 on success.'''
2251 Returns 0 on success.'''
2248 q = repo.mq
2252 q = repo.mq
2249 patch = q.lookup(patch)
2253 patch = q.lookup(patch)
2250 if q.isapplied(patch):
2254 if q.isapplied(patch):
2251 ret = q.pop(repo, patch, force=opts.get('force'))
2255 ret = q.pop(repo, patch, force=opts.get('force'))
2252 else:
2256 else:
2253 ret = q.push(repo, patch, force=opts.get('force'))
2257 ret = q.push(repo, patch, force=opts.get('force'))
2254 q.save_dirty()
2258 q.save_dirty()
2255 return ret
2259 return ret
2256
2260
2257 def guard(ui, repo, *args, **opts):
2261 def guard(ui, repo, *args, **opts):
2258 '''set or print guards for a patch
2262 '''set or print guards for a patch
2259
2263
2260 Guards control whether a patch can be pushed. A patch with no
2264 Guards control whether a patch can be pushed. A patch with no
2261 guards is always pushed. A patch with a positive guard ("+foo") is
2265 guards is always pushed. A patch with a positive guard ("+foo") is
2262 pushed only if the :hg:`qselect` command has activated it. A patch with
2266 pushed only if the :hg:`qselect` command has activated it. A patch with
2263 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2267 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2264 has activated it.
2268 has activated it.
2265
2269
2266 With no arguments, print the currently active guards.
2270 With no arguments, print the currently active guards.
2267 With arguments, set guards for the named patch.
2271 With arguments, set guards for the named patch.
2268
2272
2269 .. note::
2273 .. note::
2270 Specifying negative guards now requires '--'.
2274 Specifying negative guards now requires '--'.
2271
2275
2272 To set guards on another patch::
2276 To set guards on another patch::
2273
2277
2274 hg qguard other.patch -- +2.6.17 -stable
2278 hg qguard other.patch -- +2.6.17 -stable
2275
2279
2276 Returns 0 on success.
2280 Returns 0 on success.
2277 '''
2281 '''
2278 def status(idx):
2282 def status(idx):
2279 guards = q.series_guards[idx] or ['unguarded']
2283 guards = q.series_guards[idx] or ['unguarded']
2280 if q.series[idx] in applied:
2284 if q.series[idx] in applied:
2281 state = 'applied'
2285 state = 'applied'
2282 elif q.pushable(idx)[0]:
2286 elif q.pushable(idx)[0]:
2283 state = 'unapplied'
2287 state = 'unapplied'
2284 else:
2288 else:
2285 state = 'guarded'
2289 state = 'guarded'
2286 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2290 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2287 ui.write('%s: ' % ui.label(q.series[idx], label))
2291 ui.write('%s: ' % ui.label(q.series[idx], label))
2288
2292
2289 for i, guard in enumerate(guards):
2293 for i, guard in enumerate(guards):
2290 if guard.startswith('+'):
2294 if guard.startswith('+'):
2291 ui.write(guard, label='qguard.positive')
2295 ui.write(guard, label='qguard.positive')
2292 elif guard.startswith('-'):
2296 elif guard.startswith('-'):
2293 ui.write(guard, label='qguard.negative')
2297 ui.write(guard, label='qguard.negative')
2294 else:
2298 else:
2295 ui.write(guard, label='qguard.unguarded')
2299 ui.write(guard, label='qguard.unguarded')
2296 if i != len(guards) - 1:
2300 if i != len(guards) - 1:
2297 ui.write(' ')
2301 ui.write(' ')
2298 ui.write('\n')
2302 ui.write('\n')
2299 q = repo.mq
2303 q = repo.mq
2300 applied = set(p.name for p in q.applied)
2304 applied = set(p.name for p in q.applied)
2301 patch = None
2305 patch = None
2302 args = list(args)
2306 args = list(args)
2303 if opts.get('list'):
2307 if opts.get('list'):
2304 if args or opts.get('none'):
2308 if args or opts.get('none'):
2305 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2309 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2306 for i in xrange(len(q.series)):
2310 for i in xrange(len(q.series)):
2307 status(i)
2311 status(i)
2308 return
2312 return
2309 if not args or args[0][0:1] in '-+':
2313 if not args or args[0][0:1] in '-+':
2310 if not q.applied:
2314 if not q.applied:
2311 raise util.Abort(_('no patches applied'))
2315 raise util.Abort(_('no patches applied'))
2312 patch = q.applied[-1].name
2316 patch = q.applied[-1].name
2313 if patch is None and args[0][0:1] not in '-+':
2317 if patch is None and args[0][0:1] not in '-+':
2314 patch = args.pop(0)
2318 patch = args.pop(0)
2315 if patch is None:
2319 if patch is None:
2316 raise util.Abort(_('no patch to work with'))
2320 raise util.Abort(_('no patch to work with'))
2317 if args or opts.get('none'):
2321 if args or opts.get('none'):
2318 idx = q.find_series(patch)
2322 idx = q.find_series(patch)
2319 if idx is None:
2323 if idx is None:
2320 raise util.Abort(_('no patch named %s') % patch)
2324 raise util.Abort(_('no patch named %s') % patch)
2321 q.set_guards(idx, args)
2325 q.set_guards(idx, args)
2322 q.save_dirty()
2326 q.save_dirty()
2323 else:
2327 else:
2324 status(q.series.index(q.lookup(patch)))
2328 status(q.series.index(q.lookup(patch)))
2325
2329
2326 def header(ui, repo, patch=None):
2330 def header(ui, repo, patch=None):
2327 """print the header of the topmost or specified patch
2331 """print the header of the topmost or specified patch
2328
2332
2329 Returns 0 on success."""
2333 Returns 0 on success."""
2330 q = repo.mq
2334 q = repo.mq
2331
2335
2332 if patch:
2336 if patch:
2333 patch = q.lookup(patch)
2337 patch = q.lookup(patch)
2334 else:
2338 else:
2335 if not q.applied:
2339 if not q.applied:
2336 ui.write(_('no patches applied\n'))
2340 ui.write(_('no patches applied\n'))
2337 return 1
2341 return 1
2338 patch = q.lookup('qtip')
2342 patch = q.lookup('qtip')
2339 ph = patchheader(q.join(patch), q.plainmode)
2343 ph = patchheader(q.join(patch), q.plainmode)
2340
2344
2341 ui.write('\n'.join(ph.message) + '\n')
2345 ui.write('\n'.join(ph.message) + '\n')
2342
2346
2343 def lastsavename(path):
2347 def lastsavename(path):
2344 (directory, base) = os.path.split(path)
2348 (directory, base) = os.path.split(path)
2345 names = os.listdir(directory)
2349 names = os.listdir(directory)
2346 namere = re.compile("%s.([0-9]+)" % base)
2350 namere = re.compile("%s.([0-9]+)" % base)
2347 maxindex = None
2351 maxindex = None
2348 maxname = None
2352 maxname = None
2349 for f in names:
2353 for f in names:
2350 m = namere.match(f)
2354 m = namere.match(f)
2351 if m:
2355 if m:
2352 index = int(m.group(1))
2356 index = int(m.group(1))
2353 if maxindex is None or index > maxindex:
2357 if maxindex is None or index > maxindex:
2354 maxindex = index
2358 maxindex = index
2355 maxname = f
2359 maxname = f
2356 if maxname:
2360 if maxname:
2357 return (os.path.join(directory, maxname), maxindex)
2361 return (os.path.join(directory, maxname), maxindex)
2358 return (None, None)
2362 return (None, None)
2359
2363
2360 def savename(path):
2364 def savename(path):
2361 (last, index) = lastsavename(path)
2365 (last, index) = lastsavename(path)
2362 if last is None:
2366 if last is None:
2363 index = 0
2367 index = 0
2364 newpath = path + ".%d" % (index + 1)
2368 newpath = path + ".%d" % (index + 1)
2365 return newpath
2369 return newpath
2366
2370
2367 def push(ui, repo, patch=None, **opts):
2371 def push(ui, repo, patch=None, **opts):
2368 """push the next patch onto the stack
2372 """push the next patch onto the stack
2369
2373
2370 When -f/--force is applied, all local changes in patched files
2374 When -f/--force is applied, all local changes in patched files
2371 will be lost.
2375 will be lost.
2372
2376
2373 Return 0 on succces.
2377 Return 0 on succces.
2374 """
2378 """
2375 q = repo.mq
2379 q = repo.mq
2376 mergeq = None
2380 mergeq = None
2377
2381
2378 if opts.get('merge'):
2382 if opts.get('merge'):
2379 if opts.get('name'):
2383 if opts.get('name'):
2380 newpath = repo.join(opts.get('name'))
2384 newpath = repo.join(opts.get('name'))
2381 else:
2385 else:
2382 newpath, i = lastsavename(q.path)
2386 newpath, i = lastsavename(q.path)
2383 if not newpath:
2387 if not newpath:
2384 ui.warn(_("no saved queues found, please use -n\n"))
2388 ui.warn(_("no saved queues found, please use -n\n"))
2385 return 1
2389 return 1
2386 mergeq = queue(ui, repo.join(""), newpath)
2390 mergeq = queue(ui, repo.join(""), newpath)
2387 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2391 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2388 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2392 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2389 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2393 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2390 exact=opts.get('exact'))
2394 exact=opts.get('exact'))
2391 return ret
2395 return ret
2392
2396
2393 def pop(ui, repo, patch=None, **opts):
2397 def pop(ui, repo, patch=None, **opts):
2394 """pop the current patch off the stack
2398 """pop the current patch off the stack
2395
2399
2396 By default, pops off the top of the patch stack. If given a patch
2400 By default, pops off the top of the patch stack. If given a patch
2397 name, keeps popping off patches until the named patch is at the
2401 name, keeps popping off patches until the named patch is at the
2398 top of the stack.
2402 top of the stack.
2399
2403
2400 Return 0 on success.
2404 Return 0 on success.
2401 """
2405 """
2402 localupdate = True
2406 localupdate = True
2403 if opts.get('name'):
2407 if opts.get('name'):
2404 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2408 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2405 ui.warn(_('using patch queue: %s\n') % q.path)
2409 ui.warn(_('using patch queue: %s\n') % q.path)
2406 localupdate = False
2410 localupdate = False
2407 else:
2411 else:
2408 q = repo.mq
2412 q = repo.mq
2409 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2413 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2410 all=opts.get('all'))
2414 all=opts.get('all'))
2411 q.save_dirty()
2415 q.save_dirty()
2412 return ret
2416 return ret
2413
2417
2414 def rename(ui, repo, patch, name=None, **opts):
2418 def rename(ui, repo, patch, name=None, **opts):
2415 """rename a patch
2419 """rename a patch
2416
2420
2417 With one argument, renames the current patch to PATCH1.
2421 With one argument, renames the current patch to PATCH1.
2418 With two arguments, renames PATCH1 to PATCH2.
2422 With two arguments, renames PATCH1 to PATCH2.
2419
2423
2420 Returns 0 on success."""
2424 Returns 0 on success."""
2421
2425
2422 q = repo.mq
2426 q = repo.mq
2423
2427
2424 if not name:
2428 if not name:
2425 name = patch
2429 name = patch
2426 patch = None
2430 patch = None
2427
2431
2428 if patch:
2432 if patch:
2429 patch = q.lookup(patch)
2433 patch = q.lookup(patch)
2430 else:
2434 else:
2431 if not q.applied:
2435 if not q.applied:
2432 ui.write(_('no patches applied\n'))
2436 ui.write(_('no patches applied\n'))
2433 return
2437 return
2434 patch = q.lookup('qtip')
2438 patch = q.lookup('qtip')
2435 absdest = q.join(name)
2439 absdest = q.join(name)
2436 if os.path.isdir(absdest):
2440 if os.path.isdir(absdest):
2437 name = normname(os.path.join(name, os.path.basename(patch)))
2441 name = normname(os.path.join(name, os.path.basename(patch)))
2438 absdest = q.join(name)
2442 absdest = q.join(name)
2439 if os.path.exists(absdest):
2443 if os.path.exists(absdest):
2440 raise util.Abort(_('%s already exists') % absdest)
2444 raise util.Abort(_('%s already exists') % absdest)
2441
2445
2442 if name in q.series:
2446 if name in q.series:
2443 raise util.Abort(
2447 raise util.Abort(
2444 _('A patch named %s already exists in the series file') % name)
2448 _('A patch named %s already exists in the series file') % name)
2445
2449
2446 ui.note(_('renaming %s to %s\n') % (patch, name))
2450 ui.note(_('renaming %s to %s\n') % (patch, name))
2447 i = q.find_series(patch)
2451 i = q.find_series(patch)
2448 guards = q.guard_re.findall(q.full_series[i])
2452 guards = q.guard_re.findall(q.full_series[i])
2449 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2453 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2450 q.parse_series()
2454 q.parse_series()
2451 q.series_dirty = 1
2455 q.series_dirty = 1
2452
2456
2453 info = q.isapplied(patch)
2457 info = q.isapplied(patch)
2454 if info:
2458 if info:
2455 q.applied[info[0]] = statusentry(info[1], name)
2459 q.applied[info[0]] = statusentry(info[1], name)
2456 q.applied_dirty = 1
2460 q.applied_dirty = 1
2457
2461
2458 destdir = os.path.dirname(absdest)
2462 destdir = os.path.dirname(absdest)
2459 if not os.path.isdir(destdir):
2463 if not os.path.isdir(destdir):
2460 os.makedirs(destdir)
2464 os.makedirs(destdir)
2461 util.rename(q.join(patch), absdest)
2465 util.rename(q.join(patch), absdest)
2462 r = q.qrepo()
2466 r = q.qrepo()
2463 if r and patch in r.dirstate:
2467 if r and patch in r.dirstate:
2464 wctx = r[None]
2468 wctx = r[None]
2465 wlock = r.wlock()
2469 wlock = r.wlock()
2466 try:
2470 try:
2467 if r.dirstate[patch] == 'a':
2471 if r.dirstate[patch] == 'a':
2468 r.dirstate.forget(patch)
2472 r.dirstate.forget(patch)
2469 r.dirstate.add(name)
2473 r.dirstate.add(name)
2470 else:
2474 else:
2471 if r.dirstate[name] == 'r':
2475 if r.dirstate[name] == 'r':
2472 wctx.undelete([name])
2476 wctx.undelete([name])
2473 wctx.copy(patch, name)
2477 wctx.copy(patch, name)
2474 wctx.remove([patch], False)
2478 wctx.remove([patch], False)
2475 finally:
2479 finally:
2476 wlock.release()
2480 wlock.release()
2477
2481
2478 q.save_dirty()
2482 q.save_dirty()
2479
2483
2480 def restore(ui, repo, rev, **opts):
2484 def restore(ui, repo, rev, **opts):
2481 """restore the queue state saved by a revision (DEPRECATED)
2485 """restore the queue state saved by a revision (DEPRECATED)
2482
2486
2483 This command is deprecated, use :hg:`rebase` instead."""
2487 This command is deprecated, use :hg:`rebase` instead."""
2484 rev = repo.lookup(rev)
2488 rev = repo.lookup(rev)
2485 q = repo.mq
2489 q = repo.mq
2486 q.restore(repo, rev, delete=opts.get('delete'),
2490 q.restore(repo, rev, delete=opts.get('delete'),
2487 qupdate=opts.get('update'))
2491 qupdate=opts.get('update'))
2488 q.save_dirty()
2492 q.save_dirty()
2489 return 0
2493 return 0
2490
2494
2491 def save(ui, repo, **opts):
2495 def save(ui, repo, **opts):
2492 """save current queue state (DEPRECATED)
2496 """save current queue state (DEPRECATED)
2493
2497
2494 This command is deprecated, use :hg:`rebase` instead."""
2498 This command is deprecated, use :hg:`rebase` instead."""
2495 q = repo.mq
2499 q = repo.mq
2496 message = cmdutil.logmessage(opts)
2500 message = cmdutil.logmessage(opts)
2497 ret = q.save(repo, msg=message)
2501 ret = q.save(repo, msg=message)
2498 if ret:
2502 if ret:
2499 return ret
2503 return ret
2500 q.save_dirty()
2504 q.save_dirty()
2501 if opts.get('copy'):
2505 if opts.get('copy'):
2502 path = q.path
2506 path = q.path
2503 if opts.get('name'):
2507 if opts.get('name'):
2504 newpath = os.path.join(q.basepath, opts.get('name'))
2508 newpath = os.path.join(q.basepath, opts.get('name'))
2505 if os.path.exists(newpath):
2509 if os.path.exists(newpath):
2506 if not os.path.isdir(newpath):
2510 if not os.path.isdir(newpath):
2507 raise util.Abort(_('destination %s exists and is not '
2511 raise util.Abort(_('destination %s exists and is not '
2508 'a directory') % newpath)
2512 'a directory') % newpath)
2509 if not opts.get('force'):
2513 if not opts.get('force'):
2510 raise util.Abort(_('destination %s exists, '
2514 raise util.Abort(_('destination %s exists, '
2511 'use -f to force') % newpath)
2515 'use -f to force') % newpath)
2512 else:
2516 else:
2513 newpath = savename(path)
2517 newpath = savename(path)
2514 ui.warn(_("copy %s to %s\n") % (path, newpath))
2518 ui.warn(_("copy %s to %s\n") % (path, newpath))
2515 util.copyfiles(path, newpath)
2519 util.copyfiles(path, newpath)
2516 if opts.get('empty'):
2520 if opts.get('empty'):
2517 try:
2521 try:
2518 os.unlink(q.join(q.status_path))
2522 os.unlink(q.join(q.status_path))
2519 except:
2523 except:
2520 pass
2524 pass
2521 return 0
2525 return 0
2522
2526
2523 def strip(ui, repo, *revs, **opts):
2527 def strip(ui, repo, *revs, **opts):
2524 """strip changesets and all their descendants from the repository
2528 """strip changesets and all their descendants from the repository
2525
2529
2526 The strip command removes the specified changesets and all their
2530 The strip command removes the specified changesets and all their
2527 descendants. If the working directory has uncommitted changes,
2531 descendants. If the working directory has uncommitted changes,
2528 the operation is aborted unless the --force flag is supplied.
2532 the operation is aborted unless the --force flag is supplied.
2529
2533
2530 If a parent of the working directory is stripped, then the working
2534 If a parent of the working directory is stripped, then the working
2531 directory will automatically be updated to the most recent
2535 directory will automatically be updated to the most recent
2532 available ancestor of the stripped parent after the operation
2536 available ancestor of the stripped parent after the operation
2533 completes.
2537 completes.
2534
2538
2535 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2539 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2536 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2540 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2537 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2541 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2538 where BUNDLE is the bundle file created by the strip. Note that
2542 where BUNDLE is the bundle file created by the strip. Note that
2539 the local revision numbers will in general be different after the
2543 the local revision numbers will in general be different after the
2540 restore.
2544 restore.
2541
2545
2542 Use the --no-backup option to discard the backup bundle once the
2546 Use the --no-backup option to discard the backup bundle once the
2543 operation completes.
2547 operation completes.
2544
2548
2545 Return 0 on success.
2549 Return 0 on success.
2546 """
2550 """
2547 backup = 'all'
2551 backup = 'all'
2548 if opts.get('backup'):
2552 if opts.get('backup'):
2549 backup = 'strip'
2553 backup = 'strip'
2550 elif opts.get('no_backup') or opts.get('nobackup'):
2554 elif opts.get('no_backup') or opts.get('nobackup'):
2551 backup = 'none'
2555 backup = 'none'
2552
2556
2553 cl = repo.changelog
2557 cl = repo.changelog
2554 revs = set(cmdutil.revrange(repo, revs))
2558 revs = set(cmdutil.revrange(repo, revs))
2555 if not revs:
2559 if not revs:
2556 raise util.Abort(_('empty revision set'))
2560 raise util.Abort(_('empty revision set'))
2557
2561
2558 descendants = set(cl.descendants(*revs))
2562 descendants = set(cl.descendants(*revs))
2559 strippedrevs = revs.union(descendants)
2563 strippedrevs = revs.union(descendants)
2560 roots = revs.difference(descendants)
2564 roots = revs.difference(descendants)
2561
2565
2562 update = False
2566 update = False
2563 # if one of the wdir parent is stripped we'll need
2567 # if one of the wdir parent is stripped we'll need
2564 # to update away to an earlier revision
2568 # to update away to an earlier revision
2565 for p in repo.dirstate.parents():
2569 for p in repo.dirstate.parents():
2566 if p != nullid and cl.rev(p) in strippedrevs:
2570 if p != nullid and cl.rev(p) in strippedrevs:
2567 update = True
2571 update = True
2568 break
2572 break
2569
2573
2570 rootnodes = set(cl.node(r) for r in roots)
2574 rootnodes = set(cl.node(r) for r in roots)
2571
2575
2572 q = repo.mq
2576 q = repo.mq
2573 if q.applied:
2577 if q.applied:
2574 # refresh queue state if we're about to strip
2578 # refresh queue state if we're about to strip
2575 # applied patches
2579 # applied patches
2576 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2580 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2577 q.applied_dirty = True
2581 q.applied_dirty = True
2578 start = 0
2582 start = 0
2579 end = len(q.applied)
2583 end = len(q.applied)
2580 for i, statusentry in enumerate(q.applied):
2584 for i, statusentry in enumerate(q.applied):
2581 if statusentry.node in rootnodes:
2585 if statusentry.node in rootnodes:
2582 # if one of the stripped roots is an applied
2586 # if one of the stripped roots is an applied
2583 # patch, only part of the queue is stripped
2587 # patch, only part of the queue is stripped
2584 start = i
2588 start = i
2585 break
2589 break
2586 del q.applied[start:end]
2590 del q.applied[start:end]
2587 q.save_dirty()
2591 q.save_dirty()
2588
2592
2589 revs = list(rootnodes)
2593 revs = list(rootnodes)
2590 if update and opts.get('keep'):
2594 if update and opts.get('keep'):
2591 wlock = repo.wlock()
2595 wlock = repo.wlock()
2592 try:
2596 try:
2593 urev = repo.mq.qparents(repo, revs[0])
2597 urev = repo.mq.qparents(repo, revs[0])
2594 repo.dirstate.rebuild(urev, repo[urev].manifest())
2598 repo.dirstate.rebuild(urev, repo[urev].manifest())
2595 repo.dirstate.write()
2599 repo.dirstate.write()
2596 update = False
2600 update = False
2597 finally:
2601 finally:
2598 wlock.release()
2602 wlock.release()
2599
2603
2600 repo.mq.strip(repo, revs, backup=backup, update=update,
2604 repo.mq.strip(repo, revs, backup=backup, update=update,
2601 force=opts.get('force'))
2605 force=opts.get('force'))
2602 return 0
2606 return 0
2603
2607
2604 def select(ui, repo, *args, **opts):
2608 def select(ui, repo, *args, **opts):
2605 '''set or print guarded patches to push
2609 '''set or print guarded patches to push
2606
2610
2607 Use the :hg:`qguard` command to set or print guards on patch, then use
2611 Use the :hg:`qguard` command to set or print guards on patch, then use
2608 qselect to tell mq which guards to use. A patch will be pushed if
2612 qselect to tell mq which guards to use. A patch will be pushed if
2609 it has no guards or any positive guards match the currently
2613 it has no guards or any positive guards match the currently
2610 selected guard, but will not be pushed if any negative guards
2614 selected guard, but will not be pushed if any negative guards
2611 match the current guard. For example::
2615 match the current guard. For example::
2612
2616
2613 qguard foo.patch -stable (negative guard)
2617 qguard foo.patch -stable (negative guard)
2614 qguard bar.patch +stable (positive guard)
2618 qguard bar.patch +stable (positive guard)
2615 qselect stable
2619 qselect stable
2616
2620
2617 This activates the "stable" guard. mq will skip foo.patch (because
2621 This activates the "stable" guard. mq will skip foo.patch (because
2618 it has a negative match) but push bar.patch (because it has a
2622 it has a negative match) but push bar.patch (because it has a
2619 positive match).
2623 positive match).
2620
2624
2621 With no arguments, prints the currently active guards.
2625 With no arguments, prints the currently active guards.
2622 With one argument, sets the active guard.
2626 With one argument, sets the active guard.
2623
2627
2624 Use -n/--none to deactivate guards (no other arguments needed).
2628 Use -n/--none to deactivate guards (no other arguments needed).
2625 When no guards are active, patches with positive guards are
2629 When no guards are active, patches with positive guards are
2626 skipped and patches with negative guards are pushed.
2630 skipped and patches with negative guards are pushed.
2627
2631
2628 qselect can change the guards on applied patches. It does not pop
2632 qselect can change the guards on applied patches. It does not pop
2629 guarded patches by default. Use --pop to pop back to the last
2633 guarded patches by default. Use --pop to pop back to the last
2630 applied patch that is not guarded. Use --reapply (which implies
2634 applied patch that is not guarded. Use --reapply (which implies
2631 --pop) to push back to the current patch afterwards, but skip
2635 --pop) to push back to the current patch afterwards, but skip
2632 guarded patches.
2636 guarded patches.
2633
2637
2634 Use -s/--series to print a list of all guards in the series file
2638 Use -s/--series to print a list of all guards in the series file
2635 (no other arguments needed). Use -v for more information.
2639 (no other arguments needed). Use -v for more information.
2636
2640
2637 Returns 0 on success.'''
2641 Returns 0 on success.'''
2638
2642
2639 q = repo.mq
2643 q = repo.mq
2640 guards = q.active()
2644 guards = q.active()
2641 if args or opts.get('none'):
2645 if args or opts.get('none'):
2642 old_unapplied = q.unapplied(repo)
2646 old_unapplied = q.unapplied(repo)
2643 old_guarded = [i for i in xrange(len(q.applied)) if
2647 old_guarded = [i for i in xrange(len(q.applied)) if
2644 not q.pushable(i)[0]]
2648 not q.pushable(i)[0]]
2645 q.set_active(args)
2649 q.set_active(args)
2646 q.save_dirty()
2650 q.save_dirty()
2647 if not args:
2651 if not args:
2648 ui.status(_('guards deactivated\n'))
2652 ui.status(_('guards deactivated\n'))
2649 if not opts.get('pop') and not opts.get('reapply'):
2653 if not opts.get('pop') and not opts.get('reapply'):
2650 unapplied = q.unapplied(repo)
2654 unapplied = q.unapplied(repo)
2651 guarded = [i for i in xrange(len(q.applied))
2655 guarded = [i for i in xrange(len(q.applied))
2652 if not q.pushable(i)[0]]
2656 if not q.pushable(i)[0]]
2653 if len(unapplied) != len(old_unapplied):
2657 if len(unapplied) != len(old_unapplied):
2654 ui.status(_('number of unguarded, unapplied patches has '
2658 ui.status(_('number of unguarded, unapplied patches has '
2655 'changed from %d to %d\n') %
2659 'changed from %d to %d\n') %
2656 (len(old_unapplied), len(unapplied)))
2660 (len(old_unapplied), len(unapplied)))
2657 if len(guarded) != len(old_guarded):
2661 if len(guarded) != len(old_guarded):
2658 ui.status(_('number of guarded, applied patches has changed '
2662 ui.status(_('number of guarded, applied patches has changed '
2659 'from %d to %d\n') %
2663 'from %d to %d\n') %
2660 (len(old_guarded), len(guarded)))
2664 (len(old_guarded), len(guarded)))
2661 elif opts.get('series'):
2665 elif opts.get('series'):
2662 guards = {}
2666 guards = {}
2663 noguards = 0
2667 noguards = 0
2664 for gs in q.series_guards:
2668 for gs in q.series_guards:
2665 if not gs:
2669 if not gs:
2666 noguards += 1
2670 noguards += 1
2667 for g in gs:
2671 for g in gs:
2668 guards.setdefault(g, 0)
2672 guards.setdefault(g, 0)
2669 guards[g] += 1
2673 guards[g] += 1
2670 if ui.verbose:
2674 if ui.verbose:
2671 guards['NONE'] = noguards
2675 guards['NONE'] = noguards
2672 guards = guards.items()
2676 guards = guards.items()
2673 guards.sort(key=lambda x: x[0][1:])
2677 guards.sort(key=lambda x: x[0][1:])
2674 if guards:
2678 if guards:
2675 ui.note(_('guards in series file:\n'))
2679 ui.note(_('guards in series file:\n'))
2676 for guard, count in guards:
2680 for guard, count in guards:
2677 ui.note('%2d ' % count)
2681 ui.note('%2d ' % count)
2678 ui.write(guard, '\n')
2682 ui.write(guard, '\n')
2679 else:
2683 else:
2680 ui.note(_('no guards in series file\n'))
2684 ui.note(_('no guards in series file\n'))
2681 else:
2685 else:
2682 if guards:
2686 if guards:
2683 ui.note(_('active guards:\n'))
2687 ui.note(_('active guards:\n'))
2684 for g in guards:
2688 for g in guards:
2685 ui.write(g, '\n')
2689 ui.write(g, '\n')
2686 else:
2690 else:
2687 ui.write(_('no active guards\n'))
2691 ui.write(_('no active guards\n'))
2688 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2692 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2689 popped = False
2693 popped = False
2690 if opts.get('pop') or opts.get('reapply'):
2694 if opts.get('pop') or opts.get('reapply'):
2691 for i in xrange(len(q.applied)):
2695 for i in xrange(len(q.applied)):
2692 pushable, reason = q.pushable(i)
2696 pushable, reason = q.pushable(i)
2693 if not pushable:
2697 if not pushable:
2694 ui.status(_('popping guarded patches\n'))
2698 ui.status(_('popping guarded patches\n'))
2695 popped = True
2699 popped = True
2696 if i == 0:
2700 if i == 0:
2697 q.pop(repo, all=True)
2701 q.pop(repo, all=True)
2698 else:
2702 else:
2699 q.pop(repo, i - 1)
2703 q.pop(repo, i - 1)
2700 break
2704 break
2701 if popped:
2705 if popped:
2702 try:
2706 try:
2703 if reapply:
2707 if reapply:
2704 ui.status(_('reapplying unguarded patches\n'))
2708 ui.status(_('reapplying unguarded patches\n'))
2705 q.push(repo, reapply)
2709 q.push(repo, reapply)
2706 finally:
2710 finally:
2707 q.save_dirty()
2711 q.save_dirty()
2708
2712
2709 def finish(ui, repo, *revrange, **opts):
2713 def finish(ui, repo, *revrange, **opts):
2710 """move applied patches into repository history
2714 """move applied patches into repository history
2711
2715
2712 Finishes the specified revisions (corresponding to applied
2716 Finishes the specified revisions (corresponding to applied
2713 patches) by moving them out of mq control into regular repository
2717 patches) by moving them out of mq control into regular repository
2714 history.
2718 history.
2715
2719
2716 Accepts a revision range or the -a/--applied option. If --applied
2720 Accepts a revision range or the -a/--applied option. If --applied
2717 is specified, all applied mq revisions are removed from mq
2721 is specified, all applied mq revisions are removed from mq
2718 control. Otherwise, the given revisions must be at the base of the
2722 control. Otherwise, the given revisions must be at the base of the
2719 stack of applied patches.
2723 stack of applied patches.
2720
2724
2721 This can be especially useful if your changes have been applied to
2725 This can be especially useful if your changes have been applied to
2722 an upstream repository, or if you are about to push your changes
2726 an upstream repository, or if you are about to push your changes
2723 to upstream.
2727 to upstream.
2724
2728
2725 Returns 0 on success.
2729 Returns 0 on success.
2726 """
2730 """
2727 if not opts.get('applied') and not revrange:
2731 if not opts.get('applied') and not revrange:
2728 raise util.Abort(_('no revisions specified'))
2732 raise util.Abort(_('no revisions specified'))
2729 elif opts.get('applied'):
2733 elif opts.get('applied'):
2730 revrange = ('qbase::qtip',) + revrange
2734 revrange = ('qbase::qtip',) + revrange
2731
2735
2732 q = repo.mq
2736 q = repo.mq
2733 if not q.applied:
2737 if not q.applied:
2734 ui.status(_('no patches applied\n'))
2738 ui.status(_('no patches applied\n'))
2735 return 0
2739 return 0
2736
2740
2737 revs = cmdutil.revrange(repo, revrange)
2741 revs = cmdutil.revrange(repo, revrange)
2738 q.finish(repo, revs)
2742 q.finish(repo, revs)
2739 q.save_dirty()
2743 q.save_dirty()
2740 return 0
2744 return 0
2741
2745
2742 def qqueue(ui, repo, name=None, **opts):
2746 def qqueue(ui, repo, name=None, **opts):
2743 '''manage multiple patch queues
2747 '''manage multiple patch queues
2744
2748
2745 Supports switching between different patch queues, as well as creating
2749 Supports switching between different patch queues, as well as creating
2746 new patch queues and deleting existing ones.
2750 new patch queues and deleting existing ones.
2747
2751
2748 Omitting a queue name or specifying -l/--list will show you the registered
2752 Omitting a queue name or specifying -l/--list will show you the registered
2749 queues - by default the "normal" patches queue is registered. The currently
2753 queues - by default the "normal" patches queue is registered. The currently
2750 active queue will be marked with "(active)".
2754 active queue will be marked with "(active)".
2751
2755
2752 To create a new queue, use -c/--create. The queue is automatically made
2756 To create a new queue, use -c/--create. The queue is automatically made
2753 active, except in the case where there are applied patches from the
2757 active, except in the case where there are applied patches from the
2754 currently active queue in the repository. Then the queue will only be
2758 currently active queue in the repository. Then the queue will only be
2755 created and switching will fail.
2759 created and switching will fail.
2756
2760
2757 To delete an existing queue, use --delete. You cannot delete the currently
2761 To delete an existing queue, use --delete. You cannot delete the currently
2758 active queue.
2762 active queue.
2759
2763
2760 Returns 0 on success.
2764 Returns 0 on success.
2761 '''
2765 '''
2762
2766
2763 q = repo.mq
2767 q = repo.mq
2764
2768
2765 _defaultqueue = 'patches'
2769 _defaultqueue = 'patches'
2766 _allqueues = 'patches.queues'
2770 _allqueues = 'patches.queues'
2767 _activequeue = 'patches.queue'
2771 _activequeue = 'patches.queue'
2768
2772
2769 def _getcurrent():
2773 def _getcurrent():
2770 cur = os.path.basename(q.path)
2774 cur = os.path.basename(q.path)
2771 if cur.startswith('patches-'):
2775 if cur.startswith('patches-'):
2772 cur = cur[8:]
2776 cur = cur[8:]
2773 return cur
2777 return cur
2774
2778
2775 def _noqueues():
2779 def _noqueues():
2776 try:
2780 try:
2777 fh = repo.opener(_allqueues, 'r')
2781 fh = repo.opener(_allqueues, 'r')
2778 fh.close()
2782 fh.close()
2779 except IOError:
2783 except IOError:
2780 return True
2784 return True
2781
2785
2782 return False
2786 return False
2783
2787
2784 def _getqueues():
2788 def _getqueues():
2785 current = _getcurrent()
2789 current = _getcurrent()
2786
2790
2787 try:
2791 try:
2788 fh = repo.opener(_allqueues, 'r')
2792 fh = repo.opener(_allqueues, 'r')
2789 queues = [queue.strip() for queue in fh if queue.strip()]
2793 queues = [queue.strip() for queue in fh if queue.strip()]
2794 fh.close()
2790 if current not in queues:
2795 if current not in queues:
2791 queues.append(current)
2796 queues.append(current)
2792 except IOError:
2797 except IOError:
2793 queues = [_defaultqueue]
2798 queues = [_defaultqueue]
2794
2799
2795 return sorted(queues)
2800 return sorted(queues)
2796
2801
2797 def _setactive(name):
2802 def _setactive(name):
2798 if q.applied:
2803 if q.applied:
2799 raise util.Abort(_('patches applied - cannot set new queue active'))
2804 raise util.Abort(_('patches applied - cannot set new queue active'))
2800 _setactivenocheck(name)
2805 _setactivenocheck(name)
2801
2806
2802 def _setactivenocheck(name):
2807 def _setactivenocheck(name):
2803 fh = repo.opener(_activequeue, 'w')
2808 fh = repo.opener(_activequeue, 'w')
2804 if name != 'patches':
2809 if name != 'patches':
2805 fh.write(name)
2810 fh.write(name)
2806 fh.close()
2811 fh.close()
2807
2812
2808 def _addqueue(name):
2813 def _addqueue(name):
2809 fh = repo.opener(_allqueues, 'a')
2814 fh = repo.opener(_allqueues, 'a')
2810 fh.write('%s\n' % (name,))
2815 fh.write('%s\n' % (name,))
2811 fh.close()
2816 fh.close()
2812
2817
2813 def _queuedir(name):
2818 def _queuedir(name):
2814 if name == 'patches':
2819 if name == 'patches':
2815 return repo.join('patches')
2820 return repo.join('patches')
2816 else:
2821 else:
2817 return repo.join('patches-' + name)
2822 return repo.join('patches-' + name)
2818
2823
2819 def _validname(name):
2824 def _validname(name):
2820 for n in name:
2825 for n in name:
2821 if n in ':\\/.':
2826 if n in ':\\/.':
2822 return False
2827 return False
2823 return True
2828 return True
2824
2829
2825 def _delete(name):
2830 def _delete(name):
2826 if name not in existing:
2831 if name not in existing:
2827 raise util.Abort(_('cannot delete queue that does not exist'))
2832 raise util.Abort(_('cannot delete queue that does not exist'))
2828
2833
2829 current = _getcurrent()
2834 current = _getcurrent()
2830
2835
2831 if name == current:
2836 if name == current:
2832 raise util.Abort(_('cannot delete currently active queue'))
2837 raise util.Abort(_('cannot delete currently active queue'))
2833
2838
2834 fh = repo.opener('patches.queues.new', 'w')
2839 fh = repo.opener('patches.queues.new', 'w')
2835 for queue in existing:
2840 for queue in existing:
2836 if queue == name:
2841 if queue == name:
2837 continue
2842 continue
2838 fh.write('%s\n' % (queue,))
2843 fh.write('%s\n' % (queue,))
2839 fh.close()
2844 fh.close()
2840 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2845 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2841
2846
2842 if not name or opts.get('list'):
2847 if not name or opts.get('list'):
2843 current = _getcurrent()
2848 current = _getcurrent()
2844 for queue in _getqueues():
2849 for queue in _getqueues():
2845 ui.write('%s' % (queue,))
2850 ui.write('%s' % (queue,))
2846 if queue == current and not ui.quiet:
2851 if queue == current and not ui.quiet:
2847 ui.write(_(' (active)\n'))
2852 ui.write(_(' (active)\n'))
2848 else:
2853 else:
2849 ui.write('\n')
2854 ui.write('\n')
2850 return
2855 return
2851
2856
2852 if not _validname(name):
2857 if not _validname(name):
2853 raise util.Abort(
2858 raise util.Abort(
2854 _('invalid queue name, may not contain the characters ":\\/."'))
2859 _('invalid queue name, may not contain the characters ":\\/."'))
2855
2860
2856 existing = _getqueues()
2861 existing = _getqueues()
2857
2862
2858 if opts.get('create'):
2863 if opts.get('create'):
2859 if name in existing:
2864 if name in existing:
2860 raise util.Abort(_('queue "%s" already exists') % name)
2865 raise util.Abort(_('queue "%s" already exists') % name)
2861 if _noqueues():
2866 if _noqueues():
2862 _addqueue(_defaultqueue)
2867 _addqueue(_defaultqueue)
2863 _addqueue(name)
2868 _addqueue(name)
2864 _setactive(name)
2869 _setactive(name)
2865 elif opts.get('rename'):
2870 elif opts.get('rename'):
2866 current = _getcurrent()
2871 current = _getcurrent()
2867 if name == current:
2872 if name == current:
2868 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2873 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2869 if name in existing:
2874 if name in existing:
2870 raise util.Abort(_('queue "%s" already exists') % name)
2875 raise util.Abort(_('queue "%s" already exists') % name)
2871
2876
2872 olddir = _queuedir(current)
2877 olddir = _queuedir(current)
2873 newdir = _queuedir(name)
2878 newdir = _queuedir(name)
2874
2879
2875 if os.path.exists(newdir):
2880 if os.path.exists(newdir):
2876 raise util.Abort(_('non-queue directory "%s" already exists') %
2881 raise util.Abort(_('non-queue directory "%s" already exists') %
2877 newdir)
2882 newdir)
2878
2883
2879 fh = repo.opener('patches.queues.new', 'w')
2884 fh = repo.opener('patches.queues.new', 'w')
2880 for queue in existing:
2885 for queue in existing:
2881 if queue == current:
2886 if queue == current:
2882 fh.write('%s\n' % (name,))
2887 fh.write('%s\n' % (name,))
2883 if os.path.exists(olddir):
2888 if os.path.exists(olddir):
2884 util.rename(olddir, newdir)
2889 util.rename(olddir, newdir)
2885 else:
2890 else:
2886 fh.write('%s\n' % (queue,))
2891 fh.write('%s\n' % (queue,))
2887 fh.close()
2892 fh.close()
2888 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2893 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2889 _setactivenocheck(name)
2894 _setactivenocheck(name)
2890 elif opts.get('delete'):
2895 elif opts.get('delete'):
2891 _delete(name)
2896 _delete(name)
2892 elif opts.get('purge'):
2897 elif opts.get('purge'):
2893 if name in existing:
2898 if name in existing:
2894 _delete(name)
2899 _delete(name)
2895 qdir = _queuedir(name)
2900 qdir = _queuedir(name)
2896 if os.path.exists(qdir):
2901 if os.path.exists(qdir):
2897 shutil.rmtree(qdir)
2902 shutil.rmtree(qdir)
2898 else:
2903 else:
2899 if name not in existing:
2904 if name not in existing:
2900 raise util.Abort(_('use --create to create a new queue'))
2905 raise util.Abort(_('use --create to create a new queue'))
2901 _setactive(name)
2906 _setactive(name)
2902
2907
2903 def reposetup(ui, repo):
2908 def reposetup(ui, repo):
2904 class mqrepo(repo.__class__):
2909 class mqrepo(repo.__class__):
2905 @util.propertycache
2910 @util.propertycache
2906 def mq(self):
2911 def mq(self):
2907 return queue(self.ui, self.join(""))
2912 return queue(self.ui, self.join(""))
2908
2913
2909 def abort_if_wdir_patched(self, errmsg, force=False):
2914 def abort_if_wdir_patched(self, errmsg, force=False):
2910 if self.mq.applied and not force:
2915 if self.mq.applied and not force:
2911 parent = self.dirstate.parents()[0]
2916 parent = self.dirstate.parents()[0]
2912 if parent in [s.node for s in self.mq.applied]:
2917 if parent in [s.node for s in self.mq.applied]:
2913 raise util.Abort(errmsg)
2918 raise util.Abort(errmsg)
2914
2919
2915 def commit(self, text="", user=None, date=None, match=None,
2920 def commit(self, text="", user=None, date=None, match=None,
2916 force=False, editor=False, extra={}):
2921 force=False, editor=False, extra={}):
2917 self.abort_if_wdir_patched(
2922 self.abort_if_wdir_patched(
2918 _('cannot commit over an applied mq patch'),
2923 _('cannot commit over an applied mq patch'),
2919 force)
2924 force)
2920
2925
2921 return super(mqrepo, self).commit(text, user, date, match, force,
2926 return super(mqrepo, self).commit(text, user, date, match, force,
2922 editor, extra)
2927 editor, extra)
2923
2928
2924 def checkpush(self, force, revs):
2929 def checkpush(self, force, revs):
2925 if self.mq.applied and not force:
2930 if self.mq.applied and not force:
2926 haspatches = True
2931 haspatches = True
2927 if revs:
2932 if revs:
2928 # Assume applied patches have no non-patch descendants
2933 # Assume applied patches have no non-patch descendants
2929 # and are not on remote already. If they appear in the
2934 # and are not on remote already. If they appear in the
2930 # set of resolved 'revs', bail out.
2935 # set of resolved 'revs', bail out.
2931 applied = set(e.node for e in self.mq.applied)
2936 applied = set(e.node for e in self.mq.applied)
2932 haspatches = bool([n for n in revs if n in applied])
2937 haspatches = bool([n for n in revs if n in applied])
2933 if haspatches:
2938 if haspatches:
2934 raise util.Abort(_('source has mq patches applied'))
2939 raise util.Abort(_('source has mq patches applied'))
2935 super(mqrepo, self).checkpush(force, revs)
2940 super(mqrepo, self).checkpush(force, revs)
2936
2941
2937 def _findtags(self):
2942 def _findtags(self):
2938 '''augment tags from base class with patch tags'''
2943 '''augment tags from base class with patch tags'''
2939 result = super(mqrepo, self)._findtags()
2944 result = super(mqrepo, self)._findtags()
2940
2945
2941 q = self.mq
2946 q = self.mq
2942 if not q.applied:
2947 if not q.applied:
2943 return result
2948 return result
2944
2949
2945 mqtags = [(patch.node, patch.name) for patch in q.applied]
2950 mqtags = [(patch.node, patch.name) for patch in q.applied]
2946
2951
2947 if mqtags[-1][0] not in self:
2952 if mqtags[-1][0] not in self:
2948 self.ui.warn(_('mq status file refers to unknown node %s\n')
2953 self.ui.warn(_('mq status file refers to unknown node %s\n')
2949 % short(mqtags[-1][0]))
2954 % short(mqtags[-1][0]))
2950 return result
2955 return result
2951
2956
2952 mqtags.append((mqtags[-1][0], 'qtip'))
2957 mqtags.append((mqtags[-1][0], 'qtip'))
2953 mqtags.append((mqtags[0][0], 'qbase'))
2958 mqtags.append((mqtags[0][0], 'qbase'))
2954 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2959 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2955 tags = result[0]
2960 tags = result[0]
2956 for patch in mqtags:
2961 for patch in mqtags:
2957 if patch[1] in tags:
2962 if patch[1] in tags:
2958 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2963 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2959 % patch[1])
2964 % patch[1])
2960 else:
2965 else:
2961 tags[patch[1]] = patch[0]
2966 tags[patch[1]] = patch[0]
2962
2967
2963 return result
2968 return result
2964
2969
2965 def _branchtags(self, partial, lrev):
2970 def _branchtags(self, partial, lrev):
2966 q = self.mq
2971 q = self.mq
2967 if not q.applied:
2972 if not q.applied:
2968 return super(mqrepo, self)._branchtags(partial, lrev)
2973 return super(mqrepo, self)._branchtags(partial, lrev)
2969
2974
2970 cl = self.changelog
2975 cl = self.changelog
2971 qbasenode = q.applied[0].node
2976 qbasenode = q.applied[0].node
2972 if qbasenode not in self:
2977 if qbasenode not in self:
2973 self.ui.warn(_('mq status file refers to unknown node %s\n')
2978 self.ui.warn(_('mq status file refers to unknown node %s\n')
2974 % short(qbasenode))
2979 % short(qbasenode))
2975 return super(mqrepo, self)._branchtags(partial, lrev)
2980 return super(mqrepo, self)._branchtags(partial, lrev)
2976
2981
2977 qbase = cl.rev(qbasenode)
2982 qbase = cl.rev(qbasenode)
2978 start = lrev + 1
2983 start = lrev + 1
2979 if start < qbase:
2984 if start < qbase:
2980 # update the cache (excluding the patches) and save it
2985 # update the cache (excluding the patches) and save it
2981 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2986 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2982 self._updatebranchcache(partial, ctxgen)
2987 self._updatebranchcache(partial, ctxgen)
2983 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2988 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2984 start = qbase
2989 start = qbase
2985 # if start = qbase, the cache is as updated as it should be.
2990 # if start = qbase, the cache is as updated as it should be.
2986 # if start > qbase, the cache includes (part of) the patches.
2991 # if start > qbase, the cache includes (part of) the patches.
2987 # we might as well use it, but we won't save it.
2992 # we might as well use it, but we won't save it.
2988
2993
2989 # update the cache up to the tip
2994 # update the cache up to the tip
2990 ctxgen = (self[r] for r in xrange(start, len(cl)))
2995 ctxgen = (self[r] for r in xrange(start, len(cl)))
2991 self._updatebranchcache(partial, ctxgen)
2996 self._updatebranchcache(partial, ctxgen)
2992
2997
2993 return partial
2998 return partial
2994
2999
2995 if repo.local():
3000 if repo.local():
2996 repo.__class__ = mqrepo
3001 repo.__class__ = mqrepo
2997
3002
2998 def mqimport(orig, ui, repo, *args, **kwargs):
3003 def mqimport(orig, ui, repo, *args, **kwargs):
2999 if (hasattr(repo, 'abort_if_wdir_patched')
3004 if (hasattr(repo, 'abort_if_wdir_patched')
3000 and not kwargs.get('no_commit', False)):
3005 and not kwargs.get('no_commit', False)):
3001 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3006 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3002 kwargs.get('force'))
3007 kwargs.get('force'))
3003 return orig(ui, repo, *args, **kwargs)
3008 return orig(ui, repo, *args, **kwargs)
3004
3009
3005 def mqinit(orig, ui, *args, **kwargs):
3010 def mqinit(orig, ui, *args, **kwargs):
3006 mq = kwargs.pop('mq', None)
3011 mq = kwargs.pop('mq', None)
3007
3012
3008 if not mq:
3013 if not mq:
3009 return orig(ui, *args, **kwargs)
3014 return orig(ui, *args, **kwargs)
3010
3015
3011 if args:
3016 if args:
3012 repopath = args[0]
3017 repopath = args[0]
3013 if not hg.islocal(repopath):
3018 if not hg.islocal(repopath):
3014 raise util.Abort(_('only a local queue repository '
3019 raise util.Abort(_('only a local queue repository '
3015 'may be initialized'))
3020 'may be initialized'))
3016 else:
3021 else:
3017 repopath = cmdutil.findrepo(os.getcwd())
3022 repopath = cmdutil.findrepo(os.getcwd())
3018 if not repopath:
3023 if not repopath:
3019 raise util.Abort(_('there is no Mercurial repository here '
3024 raise util.Abort(_('there is no Mercurial repository here '
3020 '(.hg not found)'))
3025 '(.hg not found)'))
3021 repo = hg.repository(ui, repopath)
3026 repo = hg.repository(ui, repopath)
3022 return qinit(ui, repo, True)
3027 return qinit(ui, repo, True)
3023
3028
3024 def mqcommand(orig, ui, repo, *args, **kwargs):
3029 def mqcommand(orig, ui, repo, *args, **kwargs):
3025 """Add --mq option to operate on patch repository instead of main"""
3030 """Add --mq option to operate on patch repository instead of main"""
3026
3031
3027 # some commands do not like getting unknown options
3032 # some commands do not like getting unknown options
3028 mq = kwargs.pop('mq', None)
3033 mq = kwargs.pop('mq', None)
3029
3034
3030 if not mq:
3035 if not mq:
3031 return orig(ui, repo, *args, **kwargs)
3036 return orig(ui, repo, *args, **kwargs)
3032
3037
3033 q = repo.mq
3038 q = repo.mq
3034 r = q.qrepo()
3039 r = q.qrepo()
3035 if not r:
3040 if not r:
3036 raise util.Abort(_('no queue repository'))
3041 raise util.Abort(_('no queue repository'))
3037 return orig(r.ui, r, *args, **kwargs)
3042 return orig(r.ui, r, *args, **kwargs)
3038
3043
3039 def summary(orig, ui, repo, *args, **kwargs):
3044 def summary(orig, ui, repo, *args, **kwargs):
3040 r = orig(ui, repo, *args, **kwargs)
3045 r = orig(ui, repo, *args, **kwargs)
3041 q = repo.mq
3046 q = repo.mq
3042 m = []
3047 m = []
3043 a, u = len(q.applied), len(q.unapplied(repo))
3048 a, u = len(q.applied), len(q.unapplied(repo))
3044 if a:
3049 if a:
3045 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3050 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3046 if u:
3051 if u:
3047 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3052 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3048 if m:
3053 if m:
3049 ui.write("mq: %s\n" % ', '.join(m))
3054 ui.write("mq: %s\n" % ', '.join(m))
3050 else:
3055 else:
3051 ui.note(_("mq: (empty queue)\n"))
3056 ui.note(_("mq: (empty queue)\n"))
3052 return r
3057 return r
3053
3058
3054 def uisetup(ui):
3059 def uisetup(ui):
3055 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3060 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3056
3061
3057 extensions.wrapcommand(commands.table, 'import', mqimport)
3062 extensions.wrapcommand(commands.table, 'import', mqimport)
3058 extensions.wrapcommand(commands.table, 'summary', summary)
3063 extensions.wrapcommand(commands.table, 'summary', summary)
3059
3064
3060 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3065 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3061 entry[1].extend(mqopt)
3066 entry[1].extend(mqopt)
3062
3067
3063 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3068 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3064
3069
3065 def dotable(cmdtable):
3070 def dotable(cmdtable):
3066 for cmd in cmdtable.keys():
3071 for cmd in cmdtable.keys():
3067 cmd = cmdutil.parsealiases(cmd)[0]
3072 cmd = cmdutil.parsealiases(cmd)[0]
3068 if cmd in nowrap:
3073 if cmd in nowrap:
3069 continue
3074 continue
3070 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3075 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3071 entry[1].extend(mqopt)
3076 entry[1].extend(mqopt)
3072
3077
3073 dotable(commands.table)
3078 dotable(commands.table)
3074
3079
3075 for extname, extmodule in extensions.extensions():
3080 for extname, extmodule in extensions.extensions():
3076 if extmodule.__file__ != __file__:
3081 if extmodule.__file__ != __file__:
3077 dotable(getattr(extmodule, 'cmdtable', {}))
3082 dotable(getattr(extmodule, 'cmdtable', {}))
3078
3083
3079 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3084 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3080
3085
3081 cmdtable = {
3086 cmdtable = {
3082 "qapplied":
3087 "qapplied":
3083 (applied,
3088 (applied,
3084 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3089 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3085 _('hg qapplied [-1] [-s] [PATCH]')),
3090 _('hg qapplied [-1] [-s] [PATCH]')),
3086 "qclone":
3091 "qclone":
3087 (clone,
3092 (clone,
3088 [('', 'pull', None, _('use pull protocol to copy metadata')),
3093 [('', 'pull', None, _('use pull protocol to copy metadata')),
3089 ('U', 'noupdate', None, _('do not update the new working directories')),
3094 ('U', 'noupdate', None, _('do not update the new working directories')),
3090 ('', 'uncompressed', None,
3095 ('', 'uncompressed', None,
3091 _('use uncompressed transfer (fast over LAN)')),
3096 _('use uncompressed transfer (fast over LAN)')),
3092 ('p', 'patches', '',
3097 ('p', 'patches', '',
3093 _('location of source patch repository'), _('REPO')),
3098 _('location of source patch repository'), _('REPO')),
3094 ] + commands.remoteopts,
3099 ] + commands.remoteopts,
3095 _('hg qclone [OPTION]... SOURCE [DEST]')),
3100 _('hg qclone [OPTION]... SOURCE [DEST]')),
3096 "qcommit|qci":
3101 "qcommit|qci":
3097 (commit,
3102 (commit,
3098 commands.table["^commit|ci"][1],
3103 commands.table["^commit|ci"][1],
3099 _('hg qcommit [OPTION]... [FILE]...')),
3104 _('hg qcommit [OPTION]... [FILE]...')),
3100 "^qdiff":
3105 "^qdiff":
3101 (diff,
3106 (diff,
3102 commands.diffopts + commands.diffopts2 + commands.walkopts,
3107 commands.diffopts + commands.diffopts2 + commands.walkopts,
3103 _('hg qdiff [OPTION]... [FILE]...')),
3108 _('hg qdiff [OPTION]... [FILE]...')),
3104 "qdelete|qremove|qrm":
3109 "qdelete|qremove|qrm":
3105 (delete,
3110 (delete,
3106 [('k', 'keep', None, _('keep patch file')),
3111 [('k', 'keep', None, _('keep patch file')),
3107 ('r', 'rev', [],
3112 ('r', 'rev', [],
3108 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3113 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3109 _('hg qdelete [-k] [PATCH]...')),
3114 _('hg qdelete [-k] [PATCH]...')),
3110 'qfold':
3115 'qfold':
3111 (fold,
3116 (fold,
3112 [('e', 'edit', None, _('edit patch header')),
3117 [('e', 'edit', None, _('edit patch header')),
3113 ('k', 'keep', None, _('keep folded patch files')),
3118 ('k', 'keep', None, _('keep folded patch files')),
3114 ] + commands.commitopts,
3119 ] + commands.commitopts,
3115 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3120 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3116 'qgoto':
3121 'qgoto':
3117 (goto,
3122 (goto,
3118 [('f', 'force', None, _('overwrite any local changes'))],
3123 [('f', 'force', None, _('overwrite any local changes'))],
3119 _('hg qgoto [OPTION]... PATCH')),
3124 _('hg qgoto [OPTION]... PATCH')),
3120 'qguard':
3125 'qguard':
3121 (guard,
3126 (guard,
3122 [('l', 'list', None, _('list all patches and guards')),
3127 [('l', 'list', None, _('list all patches and guards')),
3123 ('n', 'none', None, _('drop all guards'))],
3128 ('n', 'none', None, _('drop all guards'))],
3124 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3129 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3125 'qheader': (header, [], _('hg qheader [PATCH]')),
3130 'qheader': (header, [], _('hg qheader [PATCH]')),
3126 "qimport":
3131 "qimport":
3127 (qimport,
3132 (qimport,
3128 [('e', 'existing', None, _('import file in patch directory')),
3133 [('e', 'existing', None, _('import file in patch directory')),
3129 ('n', 'name', '',
3134 ('n', 'name', '',
3130 _('name of patch file'), _('NAME')),
3135 _('name of patch file'), _('NAME')),
3131 ('f', 'force', None, _('overwrite existing files')),
3136 ('f', 'force', None, _('overwrite existing files')),
3132 ('r', 'rev', [],
3137 ('r', 'rev', [],
3133 _('place existing revisions under mq control'), _('REV')),
3138 _('place existing revisions under mq control'), _('REV')),
3134 ('g', 'git', None, _('use git extended diff format')),
3139 ('g', 'git', None, _('use git extended diff format')),
3135 ('P', 'push', None, _('qpush after importing'))],
3140 ('P', 'push', None, _('qpush after importing'))],
3136 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3141 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3137 "^qinit":
3142 "^qinit":
3138 (init,
3143 (init,
3139 [('c', 'create-repo', None, _('create queue repository'))],
3144 [('c', 'create-repo', None, _('create queue repository'))],
3140 _('hg qinit [-c]')),
3145 _('hg qinit [-c]')),
3141 "^qnew":
3146 "^qnew":
3142 (new,
3147 (new,
3143 [('e', 'edit', None, _('edit commit message')),
3148 [('e', 'edit', None, _('edit commit message')),
3144 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3149 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3145 ('g', 'git', None, _('use git extended diff format')),
3150 ('g', 'git', None, _('use git extended diff format')),
3146 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3151 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3147 ('u', 'user', '',
3152 ('u', 'user', '',
3148 _('add "From: <USER>" to patch'), _('USER')),
3153 _('add "From: <USER>" to patch'), _('USER')),
3149 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3154 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3150 ('d', 'date', '',
3155 ('d', 'date', '',
3151 _('add "Date: <DATE>" to patch'), _('DATE'))
3156 _('add "Date: <DATE>" to patch'), _('DATE'))
3152 ] + commands.walkopts + commands.commitopts,
3157 ] + commands.walkopts + commands.commitopts,
3153 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3158 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3154 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3159 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3155 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3160 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3156 "^qpop":
3161 "^qpop":
3157 (pop,
3162 (pop,
3158 [('a', 'all', None, _('pop all patches')),
3163 [('a', 'all', None, _('pop all patches')),
3159 ('n', 'name', '',
3164 ('n', 'name', '',
3160 _('queue name to pop (DEPRECATED)'), _('NAME')),
3165 _('queue name to pop (DEPRECATED)'), _('NAME')),
3161 ('f', 'force', None, _('forget any local changes to patched files'))],
3166 ('f', 'force', None, _('forget any local changes to patched files'))],
3162 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3167 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3163 "^qpush":
3168 "^qpush":
3164 (push,
3169 (push,
3165 [('f', 'force', None, _('apply on top of local changes')),
3170 [('f', 'force', None, _('apply on top of local changes')),
3166 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3171 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3167 ('l', 'list', None, _('list patch name in commit text')),
3172 ('l', 'list', None, _('list patch name in commit text')),
3168 ('a', 'all', None, _('apply all patches')),
3173 ('a', 'all', None, _('apply all patches')),
3169 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3174 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3170 ('n', 'name', '',
3175 ('n', 'name', '',
3171 _('merge queue name (DEPRECATED)'), _('NAME')),
3176 _('merge queue name (DEPRECATED)'), _('NAME')),
3172 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3177 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3173 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3178 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3174 "^qrefresh":
3179 "^qrefresh":
3175 (refresh,
3180 (refresh,
3176 [('e', 'edit', None, _('edit commit message')),
3181 [('e', 'edit', None, _('edit commit message')),
3177 ('g', 'git', None, _('use git extended diff format')),
3182 ('g', 'git', None, _('use git extended diff format')),
3178 ('s', 'short', None,
3183 ('s', 'short', None,
3179 _('refresh only files already in the patch and specified files')),
3184 _('refresh only files already in the patch and specified files')),
3180 ('U', 'currentuser', None,
3185 ('U', 'currentuser', None,
3181 _('add/update author field in patch with current user')),
3186 _('add/update author field in patch with current user')),
3182 ('u', 'user', '',
3187 ('u', 'user', '',
3183 _('add/update author field in patch with given user'), _('USER')),
3188 _('add/update author field in patch with given user'), _('USER')),
3184 ('D', 'currentdate', None,
3189 ('D', 'currentdate', None,
3185 _('add/update date field in patch with current date')),
3190 _('add/update date field in patch with current date')),
3186 ('d', 'date', '',
3191 ('d', 'date', '',
3187 _('add/update date field in patch with given date'), _('DATE'))
3192 _('add/update date field in patch with given date'), _('DATE'))
3188 ] + commands.walkopts + commands.commitopts,
3193 ] + commands.walkopts + commands.commitopts,
3189 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3194 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3190 'qrename|qmv':
3195 'qrename|qmv':
3191 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3196 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3192 "qrestore":
3197 "qrestore":
3193 (restore,
3198 (restore,
3194 [('d', 'delete', None, _('delete save entry')),
3199 [('d', 'delete', None, _('delete save entry')),
3195 ('u', 'update', None, _('update queue working directory'))],
3200 ('u', 'update', None, _('update queue working directory'))],
3196 _('hg qrestore [-d] [-u] REV')),
3201 _('hg qrestore [-d] [-u] REV')),
3197 "qsave":
3202 "qsave":
3198 (save,
3203 (save,
3199 [('c', 'copy', None, _('copy patch directory')),
3204 [('c', 'copy', None, _('copy patch directory')),
3200 ('n', 'name', '',
3205 ('n', 'name', '',
3201 _('copy directory name'), _('NAME')),
3206 _('copy directory name'), _('NAME')),
3202 ('e', 'empty', None, _('clear queue status file')),
3207 ('e', 'empty', None, _('clear queue status file')),
3203 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3208 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3204 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3209 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3205 "qselect":
3210 "qselect":
3206 (select,
3211 (select,
3207 [('n', 'none', None, _('disable all guards')),
3212 [('n', 'none', None, _('disable all guards')),
3208 ('s', 'series', None, _('list all guards in series file')),
3213 ('s', 'series', None, _('list all guards in series file')),
3209 ('', 'pop', None, _('pop to before first guarded applied patch')),
3214 ('', 'pop', None, _('pop to before first guarded applied patch')),
3210 ('', 'reapply', None, _('pop, then reapply patches'))],
3215 ('', 'reapply', None, _('pop, then reapply patches'))],
3211 _('hg qselect [OPTION]... [GUARD]...')),
3216 _('hg qselect [OPTION]... [GUARD]...')),
3212 "qseries":
3217 "qseries":
3213 (series,
3218 (series,
3214 [('m', 'missing', None, _('print patches not in series')),
3219 [('m', 'missing', None, _('print patches not in series')),
3215 ] + seriesopts,
3220 ] + seriesopts,
3216 _('hg qseries [-ms]')),
3221 _('hg qseries [-ms]')),
3217 "strip":
3222 "strip":
3218 (strip,
3223 (strip,
3219 [('f', 'force', None, _('force removal of changesets even if the '
3224 [('f', 'force', None, _('force removal of changesets even if the '
3220 'working directory has uncommitted changes')),
3225 'working directory has uncommitted changes')),
3221 ('b', 'backup', None, _('bundle only changesets with local revision'
3226 ('b', 'backup', None, _('bundle only changesets with local revision'
3222 ' number greater than REV which are not'
3227 ' number greater than REV which are not'
3223 ' descendants of REV (DEPRECATED)')),
3228 ' descendants of REV (DEPRECATED)')),
3224 ('n', 'no-backup', None, _('no backups')),
3229 ('n', 'no-backup', None, _('no backups')),
3225 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3230 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3226 ('k', 'keep', None, _("do not modify working copy during strip"))],
3231 ('k', 'keep', None, _("do not modify working copy during strip"))],
3227 _('hg strip [-k] [-f] [-n] REV...')),
3232 _('hg strip [-k] [-f] [-n] REV...')),
3228 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3233 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3229 "qunapplied":
3234 "qunapplied":
3230 (unapplied,
3235 (unapplied,
3231 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3236 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3232 _('hg qunapplied [-1] [-s] [PATCH]')),
3237 _('hg qunapplied [-1] [-s] [PATCH]')),
3233 "qfinish":
3238 "qfinish":
3234 (finish,
3239 (finish,
3235 [('a', 'applied', None, _('finish all applied changesets'))],
3240 [('a', 'applied', None, _('finish all applied changesets'))],
3236 _('hg qfinish [-a] [REV]...')),
3241 _('hg qfinish [-a] [REV]...')),
3237 'qqueue':
3242 'qqueue':
3238 (qqueue,
3243 (qqueue,
3239 [
3244 [
3240 ('l', 'list', False, _('list all available queues')),
3245 ('l', 'list', False, _('list all available queues')),
3241 ('c', 'create', False, _('create new queue')),
3246 ('c', 'create', False, _('create new queue')),
3242 ('', 'rename', False, _('rename active queue')),
3247 ('', 'rename', False, _('rename active queue')),
3243 ('', 'delete', False, _('delete reference to queue')),
3248 ('', 'delete', False, _('delete reference to queue')),
3244 ('', 'purge', False, _('delete queue, and remove patch dir')),
3249 ('', 'purge', False, _('delete queue, and remove patch dir')),
3245 ],
3250 ],
3246 _('[OPTION] [QUEUE]')),
3251 _('[OPTION] [QUEUE]')),
3247 }
3252 }
3248
3253
3249 colortable = {'qguard.negative': 'red',
3254 colortable = {'qguard.negative': 'red',
3250 'qguard.positive': 'yellow',
3255 'qguard.positive': 'yellow',
3251 'qguard.unguarded': 'green',
3256 'qguard.unguarded': 'green',
3252 'qseries.applied': 'blue bold underline',
3257 'qseries.applied': 'blue bold underline',
3253 'qseries.guarded': 'black bold',
3258 'qseries.guarded': 'black bold',
3254 'qseries.missing': 'red bold',
3259 'qseries.missing': 'red bold',
3255 'qseries.unapplied': 'black bold'}
3260 'qseries.unapplied': 'black bold'}
@@ -1,560 +1,563 b''
1 # patchbomb.py - sending Mercurial changesets as patch emails
1 # patchbomb.py - sending Mercurial changesets as patch emails
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 '''command to send changesets as (a series of) patch emails
8 '''command to send changesets as (a series of) patch emails
9
9
10 The series is started off with a "[PATCH 0 of N]" introduction, which
10 The series is started off with a "[PATCH 0 of N]" introduction, which
11 describes the series as a whole.
11 describes the series as a whole.
12
12
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
14 first line of the changeset description as the subject text. The
14 first line of the changeset description as the subject text. The
15 message contains two or three body parts:
15 message contains two or three body parts:
16
16
17 - The changeset description.
17 - The changeset description.
18 - [Optional] The result of running diffstat on the patch.
18 - [Optional] The result of running diffstat on the patch.
19 - The patch itself, as generated by :hg:`export`.
19 - The patch itself, as generated by :hg:`export`.
20
20
21 Each message refers to the first in the series using the In-Reply-To
21 Each message refers to the first in the series using the In-Reply-To
22 and References headers, so they will show up as a sequence in threaded
22 and References headers, so they will show up as a sequence in threaded
23 mail and news readers, and in mail archives.
23 mail and news readers, and in mail archives.
24
24
25 To configure other defaults, add a section like this to your hgrc
25 To configure other defaults, add a section like this to your hgrc
26 file::
26 file::
27
27
28 [email]
28 [email]
29 from = My Name <my@email>
29 from = My Name <my@email>
30 to = recipient1, recipient2, ...
30 to = recipient1, recipient2, ...
31 cc = cc1, cc2, ...
31 cc = cc1, cc2, ...
32 bcc = bcc1, bcc2, ...
32 bcc = bcc1, bcc2, ...
33 reply-to = address1, address2, ...
33 reply-to = address1, address2, ...
34
34
35 Use ``[patchbomb]`` as configuration section name if you need to
35 Use ``[patchbomb]`` as configuration section name if you need to
36 override global ``[email]`` address settings.
36 override global ``[email]`` address settings.
37
37
38 Then you can use the :hg:`email` command to mail a series of
38 Then you can use the :hg:`email` command to mail a series of
39 changesets as a patchbomb.
39 changesets as a patchbomb.
40
40
41 You can also either configure the method option in the email section
41 You can also either configure the method option in the email section
42 to be a sendmail compatible mailer or fill out the [smtp] section so
42 to be a sendmail compatible mailer or fill out the [smtp] section so
43 that the patchbomb extension can automatically send patchbombs
43 that the patchbomb extension can automatically send patchbombs
44 directly from the commandline. See the [email] and [smtp] sections in
44 directly from the commandline. See the [email] and [smtp] sections in
45 hgrc(5) for details.
45 hgrc(5) for details.
46 '''
46 '''
47
47
48 import os, errno, socket, tempfile, cStringIO, time
48 import os, errno, socket, tempfile, cStringIO, time
49 import email.MIMEMultipart, email.MIMEBase
49 import email.MIMEMultipart, email.MIMEBase
50 import email.Utils, email.Encoders, email.Generator
50 import email.Utils, email.Encoders, email.Generator
51 from mercurial import cmdutil, commands, hg, mail, patch, util, discovery, url
51 from mercurial import cmdutil, commands, hg, mail, patch, util, discovery, url
52 from mercurial.i18n import _
52 from mercurial.i18n import _
53 from mercurial.node import bin
53 from mercurial.node import bin
54
54
55 def prompt(ui, prompt, default=None, rest=':'):
55 def prompt(ui, prompt, default=None, rest=':'):
56 if not ui.interactive() and default is None:
56 if not ui.interactive() and default is None:
57 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
57 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
58 if default:
58 if default:
59 prompt += ' [%s]' % default
59 prompt += ' [%s]' % default
60 prompt += rest
60 prompt += rest
61 while True:
61 while True:
62 r = ui.prompt(prompt, default=default)
62 r = ui.prompt(prompt, default=default)
63 if r:
63 if r:
64 return r
64 return r
65 if default is not None:
65 if default is not None:
66 return default
66 return default
67 ui.warn(_('Please enter a valid value.\n'))
67 ui.warn(_('Please enter a valid value.\n'))
68
68
69 def introneeded(opts, number):
69 def introneeded(opts, number):
70 '''is an introductory message required?'''
70 '''is an introductory message required?'''
71 return number > 1 or opts.get('intro') or opts.get('desc')
71 return number > 1 or opts.get('intro') or opts.get('desc')
72
72
73 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total,
73 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total,
74 patchname=None):
74 patchname=None):
75
75
76 desc = []
76 desc = []
77 node = None
77 node = None
78 body = ''
78 body = ''
79
79
80 for line in patchlines:
80 for line in patchlines:
81 if line.startswith('#'):
81 if line.startswith('#'):
82 if line.startswith('# Node ID'):
82 if line.startswith('# Node ID'):
83 node = line.split()[-1]
83 node = line.split()[-1]
84 continue
84 continue
85 if line.startswith('diff -r') or line.startswith('diff --git'):
85 if line.startswith('diff -r') or line.startswith('diff --git'):
86 break
86 break
87 desc.append(line)
87 desc.append(line)
88
88
89 if not patchname and not node:
89 if not patchname and not node:
90 raise ValueError
90 raise ValueError
91
91
92 if opts.get('attach'):
92 if opts.get('attach'):
93 body = ('\n'.join(desc[1:]).strip() or
93 body = ('\n'.join(desc[1:]).strip() or
94 'Patch subject is complete summary.')
94 'Patch subject is complete summary.')
95 body += '\n\n\n'
95 body += '\n\n\n'
96
96
97 if opts.get('plain'):
97 if opts.get('plain'):
98 while patchlines and patchlines[0].startswith('# '):
98 while patchlines and patchlines[0].startswith('# '):
99 patchlines.pop(0)
99 patchlines.pop(0)
100 if patchlines:
100 if patchlines:
101 patchlines.pop(0)
101 patchlines.pop(0)
102 while patchlines and not patchlines[0].strip():
102 while patchlines and not patchlines[0].strip():
103 patchlines.pop(0)
103 patchlines.pop(0)
104
104
105 ds = patch.diffstat(patchlines)
105 ds = patch.diffstat(patchlines)
106 if opts.get('diffstat'):
106 if opts.get('diffstat'):
107 body += ds + '\n\n'
107 body += ds + '\n\n'
108
108
109 if opts.get('attach') or opts.get('inline'):
109 if opts.get('attach') or opts.get('inline'):
110 msg = email.MIMEMultipart.MIMEMultipart()
110 msg = email.MIMEMultipart.MIMEMultipart()
111 if body:
111 if body:
112 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
112 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
113 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', opts.get('test'))
113 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', opts.get('test'))
114 binnode = bin(node)
114 binnode = bin(node)
115 # if node is mq patch, it will have the patch file's name as a tag
115 # if node is mq patch, it will have the patch file's name as a tag
116 if not patchname:
116 if not patchname:
117 patchtags = [t for t in repo.nodetags(binnode)
117 patchtags = [t for t in repo.nodetags(binnode)
118 if t.endswith('.patch') or t.endswith('.diff')]
118 if t.endswith('.patch') or t.endswith('.diff')]
119 if patchtags:
119 if patchtags:
120 patchname = patchtags[0]
120 patchname = patchtags[0]
121 elif total > 1:
121 elif total > 1:
122 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
122 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
123 binnode, seqno=idx, total=total)
123 binnode, seqno=idx, total=total)
124 else:
124 else:
125 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
125 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
126 disposition = 'inline'
126 disposition = 'inline'
127 if opts.get('attach'):
127 if opts.get('attach'):
128 disposition = 'attachment'
128 disposition = 'attachment'
129 p['Content-Disposition'] = disposition + '; filename=' + patchname
129 p['Content-Disposition'] = disposition + '; filename=' + patchname
130 msg.attach(p)
130 msg.attach(p)
131 else:
131 else:
132 body += '\n'.join(patchlines)
132 body += '\n'.join(patchlines)
133 msg = mail.mimetextpatch(body, display=opts.get('test'))
133 msg = mail.mimetextpatch(body, display=opts.get('test'))
134
134
135 flag = ' '.join(opts.get('flag'))
135 flag = ' '.join(opts.get('flag'))
136 if flag:
136 if flag:
137 flag = ' ' + flag
137 flag = ' ' + flag
138
138
139 subj = desc[0].strip().rstrip('. ')
139 subj = desc[0].strip().rstrip('. ')
140 if not introneeded(opts, total):
140 if not introneeded(opts, total):
141 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
141 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
142 else:
142 else:
143 tlen = len(str(total))
143 tlen = len(str(total))
144 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
144 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
145 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
145 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
146 msg['X-Mercurial-Node'] = node
146 msg['X-Mercurial-Node'] = node
147 return msg, subj, ds
147 return msg, subj, ds
148
148
149 def patchbomb(ui, repo, *revs, **opts):
149 def patchbomb(ui, repo, *revs, **opts):
150 '''send changesets by email
150 '''send changesets by email
151
151
152 By default, diffs are sent in the format generated by
152 By default, diffs are sent in the format generated by
153 :hg:`export`, one per message. The series starts with a "[PATCH 0
153 :hg:`export`, one per message. The series starts with a "[PATCH 0
154 of N]" introduction, which describes the series as a whole.
154 of N]" introduction, which describes the series as a whole.
155
155
156 Each patch email has a Subject line of "[PATCH M of N] ...", using
156 Each patch email has a Subject line of "[PATCH M of N] ...", using
157 the first line of the changeset description as the subject text.
157 the first line of the changeset description as the subject text.
158 The message contains two or three parts. First, the changeset
158 The message contains two or three parts. First, the changeset
159 description.
159 description.
160
160
161 With the -d/--diffstat option, if the diffstat program is
161 With the -d/--diffstat option, if the diffstat program is
162 installed, the result of running diffstat on the patch is inserted.
162 installed, the result of running diffstat on the patch is inserted.
163
163
164 Finally, the patch itself, as generated by :hg:`export`.
164 Finally, the patch itself, as generated by :hg:`export`.
165
165
166 With the -d/--diffstat or -c/--confirm options, you will be presented
166 With the -d/--diffstat or -c/--confirm options, you will be presented
167 with a final summary of all messages and asked for confirmation before
167 with a final summary of all messages and asked for confirmation before
168 the messages are sent.
168 the messages are sent.
169
169
170 By default the patch is included as text in the email body for
170 By default the patch is included as text in the email body for
171 easy reviewing. Using the -a/--attach option will instead create
171 easy reviewing. Using the -a/--attach option will instead create
172 an attachment for the patch. With -i/--inline an inline attachment
172 an attachment for the patch. With -i/--inline an inline attachment
173 will be created.
173 will be created.
174
174
175 With -o/--outgoing, emails will be generated for patches not found
175 With -o/--outgoing, emails will be generated for patches not found
176 in the destination repository (or only those which are ancestors
176 in the destination repository (or only those which are ancestors
177 of the specified revisions if any are provided)
177 of the specified revisions if any are provided)
178
178
179 With -b/--bundle, changesets are selected as for --outgoing, but a
179 With -b/--bundle, changesets are selected as for --outgoing, but a
180 single email containing a binary Mercurial bundle as an attachment
180 single email containing a binary Mercurial bundle as an attachment
181 will be sent.
181 will be sent.
182
182
183 With -m/--mbox, instead of previewing each patchbomb message in a
183 With -m/--mbox, instead of previewing each patchbomb message in a
184 pager or sending the messages directly, it will create a UNIX
184 pager or sending the messages directly, it will create a UNIX
185 mailbox file with the patch emails. This mailbox file can be
185 mailbox file with the patch emails. This mailbox file can be
186 previewed with any mail user agent which supports UNIX mbox
186 previewed with any mail user agent which supports UNIX mbox
187 files.
187 files.
188
188
189 With -n/--test, all steps will run, but mail will not be sent.
189 With -n/--test, all steps will run, but mail will not be sent.
190 You will be prompted for an email recipient address, a subject and
190 You will be prompted for an email recipient address, a subject and
191 an introductory message describing the patches of your patchbomb.
191 an introductory message describing the patches of your patchbomb.
192 Then when all is done, patchbomb messages are displayed. If the
192 Then when all is done, patchbomb messages are displayed. If the
193 PAGER environment variable is set, your pager will be fired up once
193 PAGER environment variable is set, your pager will be fired up once
194 for each patchbomb message, so you can verify everything is alright.
194 for each patchbomb message, so you can verify everything is alright.
195
195
196 In case email sending fails, you will find a backup of your series
196 In case email sending fails, you will find a backup of your series
197 introductory message in ``.hg/last-email.txt``.
197 introductory message in ``.hg/last-email.txt``.
198
198
199 Examples::
199 Examples::
200
200
201 hg email -r 3000 # send patch 3000 only
201 hg email -r 3000 # send patch 3000 only
202 hg email -r 3000 -r 3001 # send patches 3000 and 3001
202 hg email -r 3000 -r 3001 # send patches 3000 and 3001
203 hg email -r 3000:3005 # send patches 3000 through 3005
203 hg email -r 3000:3005 # send patches 3000 through 3005
204 hg email 3000 # send patch 3000 (deprecated)
204 hg email 3000 # send patch 3000 (deprecated)
205
205
206 hg email -o # send all patches not in default
206 hg email -o # send all patches not in default
207 hg email -o DEST # send all patches not in DEST
207 hg email -o DEST # send all patches not in DEST
208 hg email -o -r 3000 # send all ancestors of 3000 not in default
208 hg email -o -r 3000 # send all ancestors of 3000 not in default
209 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
209 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
210
210
211 hg email -b # send bundle of all patches not in default
211 hg email -b # send bundle of all patches not in default
212 hg email -b DEST # send bundle of all patches not in DEST
212 hg email -b DEST # send bundle of all patches not in DEST
213 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
213 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
214 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
214 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
215
215
216 hg email -o -m mbox && # generate an mbox file...
216 hg email -o -m mbox && # generate an mbox file...
217 mutt -R -f mbox # ... and view it with mutt
217 mutt -R -f mbox # ... and view it with mutt
218 hg email -o -m mbox && # generate an mbox file ...
218 hg email -o -m mbox && # generate an mbox file ...
219 formail -s sendmail \\ # ... and use formail to send from the mbox
219 formail -s sendmail \\ # ... and use formail to send from the mbox
220 -bm -t < mbox # ... using sendmail
220 -bm -t < mbox # ... using sendmail
221
221
222 Before using this command, you will need to enable email in your
222 Before using this command, you will need to enable email in your
223 hgrc. See the [email] section in hgrc(5) for details.
223 hgrc. See the [email] section in hgrc(5) for details.
224 '''
224 '''
225
225
226 _charsets = mail._charsets(ui)
226 _charsets = mail._charsets(ui)
227
227
228 bundle = opts.get('bundle')
228 bundle = opts.get('bundle')
229 date = opts.get('date')
229 date = opts.get('date')
230 mbox = opts.get('mbox')
230 mbox = opts.get('mbox')
231 outgoing = opts.get('outgoing')
231 outgoing = opts.get('outgoing')
232 rev = opts.get('rev')
232 rev = opts.get('rev')
233 # internal option used by pbranches
233 # internal option used by pbranches
234 patches = opts.get('patches')
234 patches = opts.get('patches')
235
235
236 def getoutgoing(dest, revs):
236 def getoutgoing(dest, revs):
237 '''Return the revisions present locally but not in dest'''
237 '''Return the revisions present locally but not in dest'''
238 dest = ui.expandpath(dest or 'default-push', dest or 'default')
238 dest = ui.expandpath(dest or 'default-push', dest or 'default')
239 dest, branches = hg.parseurl(dest)
239 dest, branches = hg.parseurl(dest)
240 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
240 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
241 if revs:
241 if revs:
242 revs = [repo.lookup(rev) for rev in revs]
242 revs = [repo.lookup(rev) for rev in revs]
243 other = hg.repository(hg.remoteui(repo, opts), dest)
243 other = hg.repository(hg.remoteui(repo, opts), dest)
244 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
244 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
245 o = discovery.findoutgoing(repo, other)
245 o = discovery.findoutgoing(repo, other)
246 if not o:
246 if not o:
247 ui.status(_("no changes found\n"))
247 ui.status(_("no changes found\n"))
248 return []
248 return []
249 o = repo.changelog.nodesbetween(o, revs)[0]
249 o = repo.changelog.nodesbetween(o, revs)[0]
250 return [str(repo.changelog.rev(r)) for r in o]
250 return [str(repo.changelog.rev(r)) for r in o]
251
251
252 def getpatches(revs):
252 def getpatches(revs):
253 for r in cmdutil.revrange(repo, revs):
253 for r in cmdutil.revrange(repo, revs):
254 output = cStringIO.StringIO()
254 output = cStringIO.StringIO()
255 cmdutil.export(repo, [r], fp=output,
255 cmdutil.export(repo, [r], fp=output,
256 opts=patch.diffopts(ui, opts))
256 opts=patch.diffopts(ui, opts))
257 yield output.getvalue().split('\n')
257 yield output.getvalue().split('\n')
258
258
259 def getbundle(dest):
259 def getbundle(dest):
260 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
260 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
261 tmpfn = os.path.join(tmpdir, 'bundle')
261 tmpfn = os.path.join(tmpdir, 'bundle')
262 try:
262 try:
263 commands.bundle(ui, repo, tmpfn, dest, **opts)
263 commands.bundle(ui, repo, tmpfn, dest, **opts)
264 return open(tmpfn, 'rb').read()
264 fp = open(tmpfn, 'rb')
265 data = fp.read()
266 fp.close()
267 return data
265 finally:
268 finally:
266 try:
269 try:
267 os.unlink(tmpfn)
270 os.unlink(tmpfn)
268 except:
271 except:
269 pass
272 pass
270 os.rmdir(tmpdir)
273 os.rmdir(tmpdir)
271
274
272 if not (opts.get('test') or mbox):
275 if not (opts.get('test') or mbox):
273 # really sending
276 # really sending
274 mail.validateconfig(ui)
277 mail.validateconfig(ui)
275
278
276 if not (revs or rev or outgoing or bundle or patches):
279 if not (revs or rev or outgoing or bundle or patches):
277 raise util.Abort(_('specify at least one changeset with -r or -o'))
280 raise util.Abort(_('specify at least one changeset with -r or -o'))
278
281
279 if outgoing and bundle:
282 if outgoing and bundle:
280 raise util.Abort(_("--outgoing mode always on with --bundle;"
283 raise util.Abort(_("--outgoing mode always on with --bundle;"
281 " do not re-specify --outgoing"))
284 " do not re-specify --outgoing"))
282
285
283 if outgoing or bundle:
286 if outgoing or bundle:
284 if len(revs) > 1:
287 if len(revs) > 1:
285 raise util.Abort(_("too many destinations"))
288 raise util.Abort(_("too many destinations"))
286 dest = revs and revs[0] or None
289 dest = revs and revs[0] or None
287 revs = []
290 revs = []
288
291
289 if rev:
292 if rev:
290 if revs:
293 if revs:
291 raise util.Abort(_('use only one form to specify the revision'))
294 raise util.Abort(_('use only one form to specify the revision'))
292 revs = rev
295 revs = rev
293
296
294 if outgoing:
297 if outgoing:
295 revs = getoutgoing(dest, rev)
298 revs = getoutgoing(dest, rev)
296 if bundle:
299 if bundle:
297 opts['revs'] = revs
300 opts['revs'] = revs
298
301
299 # start
302 # start
300 if date:
303 if date:
301 start_time = util.parsedate(date)
304 start_time = util.parsedate(date)
302 else:
305 else:
303 start_time = util.makedate()
306 start_time = util.makedate()
304
307
305 def genmsgid(id):
308 def genmsgid(id):
306 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
309 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
307
310
308 def getdescription(body, sender):
311 def getdescription(body, sender):
309 if opts.get('desc'):
312 if opts.get('desc'):
310 body = open(opts.get('desc')).read()
313 body = open(opts.get('desc')).read()
311 else:
314 else:
312 ui.write(_('\nWrite the introductory message for the '
315 ui.write(_('\nWrite the introductory message for the '
313 'patch series.\n\n'))
316 'patch series.\n\n'))
314 body = ui.edit(body, sender)
317 body = ui.edit(body, sender)
315 # Save serie description in case sendmail fails
318 # Save serie description in case sendmail fails
316 msgfile = repo.opener('last-email.txt', 'wb')
319 msgfile = repo.opener('last-email.txt', 'wb')
317 msgfile.write(body)
320 msgfile.write(body)
318 msgfile.close()
321 msgfile.close()
319 return body
322 return body
320
323
321 def getpatchmsgs(patches, patchnames=None):
324 def getpatchmsgs(patches, patchnames=None):
322 jumbo = []
325 jumbo = []
323 msgs = []
326 msgs = []
324
327
325 ui.write(_('This patch series consists of %d patches.\n\n')
328 ui.write(_('This patch series consists of %d patches.\n\n')
326 % len(patches))
329 % len(patches))
327
330
328 name = None
331 name = None
329 for i, p in enumerate(patches):
332 for i, p in enumerate(patches):
330 jumbo.extend(p)
333 jumbo.extend(p)
331 if patchnames:
334 if patchnames:
332 name = patchnames[i]
335 name = patchnames[i]
333 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
336 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
334 len(patches), name)
337 len(patches), name)
335 msgs.append(msg)
338 msgs.append(msg)
336
339
337 if introneeded(opts, len(patches)):
340 if introneeded(opts, len(patches)):
338 tlen = len(str(len(patches)))
341 tlen = len(str(len(patches)))
339
342
340 flag = ' '.join(opts.get('flag'))
343 flag = ' '.join(opts.get('flag'))
341 if flag:
344 if flag:
342 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
345 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
343 else:
346 else:
344 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
347 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
345 subj += ' ' + (opts.get('subject') or
348 subj += ' ' + (opts.get('subject') or
346 prompt(ui, 'Subject: ', rest=subj))
349 prompt(ui, 'Subject: ', rest=subj))
347
350
348 body = ''
351 body = ''
349 ds = patch.diffstat(jumbo)
352 ds = patch.diffstat(jumbo)
350 if ds and opts.get('diffstat'):
353 if ds and opts.get('diffstat'):
351 body = '\n' + ds
354 body = '\n' + ds
352
355
353 body = getdescription(body, sender)
356 body = getdescription(body, sender)
354 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
357 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
355 msg['Subject'] = mail.headencode(ui, subj, _charsets,
358 msg['Subject'] = mail.headencode(ui, subj, _charsets,
356 opts.get('test'))
359 opts.get('test'))
357
360
358 msgs.insert(0, (msg, subj, ds))
361 msgs.insert(0, (msg, subj, ds))
359 return msgs
362 return msgs
360
363
361 def getbundlemsgs(bundle):
364 def getbundlemsgs(bundle):
362 subj = (opts.get('subject')
365 subj = (opts.get('subject')
363 or prompt(ui, 'Subject:', 'A bundle for your repository'))
366 or prompt(ui, 'Subject:', 'A bundle for your repository'))
364
367
365 body = getdescription('', sender)
368 body = getdescription('', sender)
366 msg = email.MIMEMultipart.MIMEMultipart()
369 msg = email.MIMEMultipart.MIMEMultipart()
367 if body:
370 if body:
368 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
371 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
369 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
372 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
370 datapart.set_payload(bundle)
373 datapart.set_payload(bundle)
371 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
374 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
372 datapart.add_header('Content-Disposition', 'attachment',
375 datapart.add_header('Content-Disposition', 'attachment',
373 filename=bundlename)
376 filename=bundlename)
374 email.Encoders.encode_base64(datapart)
377 email.Encoders.encode_base64(datapart)
375 msg.attach(datapart)
378 msg.attach(datapart)
376 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
379 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
377 return [(msg, subj, None)]
380 return [(msg, subj, None)]
378
381
379 sender = (opts.get('from') or ui.config('email', 'from') or
382 sender = (opts.get('from') or ui.config('email', 'from') or
380 ui.config('patchbomb', 'from') or
383 ui.config('patchbomb', 'from') or
381 prompt(ui, 'From', ui.username()))
384 prompt(ui, 'From', ui.username()))
382
385
383 if patches:
386 if patches:
384 msgs = getpatchmsgs(patches, opts.get('patchnames'))
387 msgs = getpatchmsgs(patches, opts.get('patchnames'))
385 elif bundle:
388 elif bundle:
386 msgs = getbundlemsgs(getbundle(dest))
389 msgs = getbundlemsgs(getbundle(dest))
387 else:
390 else:
388 msgs = getpatchmsgs(list(getpatches(revs)))
391 msgs = getpatchmsgs(list(getpatches(revs)))
389
392
390 showaddrs = []
393 showaddrs = []
391
394
392 def getaddrs(opt, prpt=None, default=None):
395 def getaddrs(opt, prpt=None, default=None):
393 addrs = opts.get(opt.replace('-', '_'))
396 addrs = opts.get(opt.replace('-', '_'))
394 if opt != 'reply-to':
397 if opt != 'reply-to':
395 showaddr = '%s:' % opt.capitalize()
398 showaddr = '%s:' % opt.capitalize()
396 else:
399 else:
397 showaddr = 'Reply-To:'
400 showaddr = 'Reply-To:'
398
401
399 if addrs:
402 if addrs:
400 showaddrs.append('%s %s' % (showaddr, ', '.join(addrs)))
403 showaddrs.append('%s %s' % (showaddr, ', '.join(addrs)))
401 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
404 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
402
405
403 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or ''
406 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or ''
404 if not addrs and prpt:
407 if not addrs and prpt:
405 addrs = prompt(ui, prpt, default)
408 addrs = prompt(ui, prpt, default)
406
409
407 if addrs:
410 if addrs:
408 showaddrs.append('%s %s' % (showaddr, addrs))
411 showaddrs.append('%s %s' % (showaddr, addrs))
409 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
412 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
410
413
411 to = getaddrs('to', 'To')
414 to = getaddrs('to', 'To')
412 cc = getaddrs('cc', 'Cc', '')
415 cc = getaddrs('cc', 'Cc', '')
413 bcc = getaddrs('bcc')
416 bcc = getaddrs('bcc')
414 replyto = getaddrs('reply-to')
417 replyto = getaddrs('reply-to')
415
418
416 if opts.get('diffstat') or opts.get('confirm'):
419 if opts.get('diffstat') or opts.get('confirm'):
417 ui.write(_('\nFinal summary:\n\n'))
420 ui.write(_('\nFinal summary:\n\n'))
418 ui.write('From: %s\n' % sender)
421 ui.write('From: %s\n' % sender)
419 for addr in showaddrs:
422 for addr in showaddrs:
420 ui.write('%s\n' % addr)
423 ui.write('%s\n' % addr)
421 for m, subj, ds in msgs:
424 for m, subj, ds in msgs:
422 ui.write('Subject: %s\n' % subj)
425 ui.write('Subject: %s\n' % subj)
423 if ds:
426 if ds:
424 ui.write(ds)
427 ui.write(ds)
425 ui.write('\n')
428 ui.write('\n')
426 if ui.promptchoice(_('are you sure you want to send (yn)?'),
429 if ui.promptchoice(_('are you sure you want to send (yn)?'),
427 (_('&Yes'), _('&No'))):
430 (_('&Yes'), _('&No'))):
428 raise util.Abort(_('patchbomb canceled'))
431 raise util.Abort(_('patchbomb canceled'))
429
432
430 ui.write('\n')
433 ui.write('\n')
431
434
432 parent = opts.get('in_reply_to') or None
435 parent = opts.get('in_reply_to') or None
433 # angle brackets may be omitted, they're not semantically part of the msg-id
436 # angle brackets may be omitted, they're not semantically part of the msg-id
434 if parent is not None:
437 if parent is not None:
435 if not parent.startswith('<'):
438 if not parent.startswith('<'):
436 parent = '<' + parent
439 parent = '<' + parent
437 if not parent.endswith('>'):
440 if not parent.endswith('>'):
438 parent += '>'
441 parent += '>'
439
442
440 first = True
443 first = True
441
444
442 sender_addr = email.Utils.parseaddr(sender)[1]
445 sender_addr = email.Utils.parseaddr(sender)[1]
443 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
446 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
444 sendmail = None
447 sendmail = None
445 for i, (m, subj, ds) in enumerate(msgs):
448 for i, (m, subj, ds) in enumerate(msgs):
446 try:
449 try:
447 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
450 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
448 except TypeError:
451 except TypeError:
449 m['Message-Id'] = genmsgid('patchbomb')
452 m['Message-Id'] = genmsgid('patchbomb')
450 if parent:
453 if parent:
451 m['In-Reply-To'] = parent
454 m['In-Reply-To'] = parent
452 m['References'] = parent
455 m['References'] = parent
453 if first:
456 if first:
454 parent = m['Message-Id']
457 parent = m['Message-Id']
455 first = False
458 first = False
456
459
457 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
460 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
458 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
461 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
459
462
460 start_time = (start_time[0] + 1, start_time[1])
463 start_time = (start_time[0] + 1, start_time[1])
461 m['From'] = sender
464 m['From'] = sender
462 m['To'] = ', '.join(to)
465 m['To'] = ', '.join(to)
463 if cc:
466 if cc:
464 m['Cc'] = ', '.join(cc)
467 m['Cc'] = ', '.join(cc)
465 if bcc:
468 if bcc:
466 m['Bcc'] = ', '.join(bcc)
469 m['Bcc'] = ', '.join(bcc)
467 if replyto:
470 if replyto:
468 m['Reply-To'] = ', '.join(replyto)
471 m['Reply-To'] = ', '.join(replyto)
469 if opts.get('test'):
472 if opts.get('test'):
470 ui.status(_('Displaying '), subj, ' ...\n')
473 ui.status(_('Displaying '), subj, ' ...\n')
471 ui.flush()
474 ui.flush()
472 if 'PAGER' in os.environ and not ui.plain():
475 if 'PAGER' in os.environ and not ui.plain():
473 fp = util.popen(os.environ['PAGER'], 'w')
476 fp = util.popen(os.environ['PAGER'], 'w')
474 else:
477 else:
475 fp = ui
478 fp = ui
476 generator = email.Generator.Generator(fp, mangle_from_=False)
479 generator = email.Generator.Generator(fp, mangle_from_=False)
477 try:
480 try:
478 generator.flatten(m, 0)
481 generator.flatten(m, 0)
479 fp.write('\n')
482 fp.write('\n')
480 except IOError, inst:
483 except IOError, inst:
481 if inst.errno != errno.EPIPE:
484 if inst.errno != errno.EPIPE:
482 raise
485 raise
483 if fp is not ui:
486 if fp is not ui:
484 fp.close()
487 fp.close()
485 elif mbox:
488 elif mbox:
486 ui.status(_('Writing '), subj, ' ...\n')
489 ui.status(_('Writing '), subj, ' ...\n')
487 ui.progress(_('writing'), i, item=subj, total=len(msgs))
490 ui.progress(_('writing'), i, item=subj, total=len(msgs))
488 fp = open(mbox, 'In-Reply-To' in m and 'ab+' or 'wb+')
491 fp = open(mbox, 'In-Reply-To' in m and 'ab+' or 'wb+')
489 generator = email.Generator.Generator(fp, mangle_from_=True)
492 generator = email.Generator.Generator(fp, mangle_from_=True)
490 # Should be time.asctime(), but Windows prints 2-characters day
493 # Should be time.asctime(), but Windows prints 2-characters day
491 # of month instead of one. Make them print the same thing.
494 # of month instead of one. Make them print the same thing.
492 date = time.strftime('%a %b %d %H:%M:%S %Y',
495 date = time.strftime('%a %b %d %H:%M:%S %Y',
493 time.localtime(start_time[0]))
496 time.localtime(start_time[0]))
494 fp.write('From %s %s\n' % (sender_addr, date))
497 fp.write('From %s %s\n' % (sender_addr, date))
495 generator.flatten(m, 0)
498 generator.flatten(m, 0)
496 fp.write('\n\n')
499 fp.write('\n\n')
497 fp.close()
500 fp.close()
498 else:
501 else:
499 if not sendmail:
502 if not sendmail:
500 sendmail = mail.connect(ui)
503 sendmail = mail.connect(ui)
501 ui.status(_('Sending '), subj, ' ...\n')
504 ui.status(_('Sending '), subj, ' ...\n')
502 ui.progress(_('sending'), i, item=subj, total=len(msgs))
505 ui.progress(_('sending'), i, item=subj, total=len(msgs))
503 # Exim does not remove the Bcc field
506 # Exim does not remove the Bcc field
504 del m['Bcc']
507 del m['Bcc']
505 fp = cStringIO.StringIO()
508 fp = cStringIO.StringIO()
506 generator = email.Generator.Generator(fp, mangle_from_=False)
509 generator = email.Generator.Generator(fp, mangle_from_=False)
507 generator.flatten(m, 0)
510 generator.flatten(m, 0)
508 sendmail(sender, to + bcc + cc, fp.getvalue())
511 sendmail(sender, to + bcc + cc, fp.getvalue())
509
512
510 ui.progress(_('writing'), None)
513 ui.progress(_('writing'), None)
511 ui.progress(_('sending'), None)
514 ui.progress(_('sending'), None)
512
515
513 emailopts = [
516 emailopts = [
514 ('a', 'attach', None, _('send patches as attachments')),
517 ('a', 'attach', None, _('send patches as attachments')),
515 ('i', 'inline', None, _('send patches as inline attachments')),
518 ('i', 'inline', None, _('send patches as inline attachments')),
516 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
519 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
517 ('c', 'cc', [], _('email addresses of copy recipients')),
520 ('c', 'cc', [], _('email addresses of copy recipients')),
518 ('', 'confirm', None, _('ask for confirmation before sending')),
521 ('', 'confirm', None, _('ask for confirmation before sending')),
519 ('d', 'diffstat', None, _('add diffstat output to messages')),
522 ('d', 'diffstat', None, _('add diffstat output to messages')),
520 ('', 'date', '', _('use the given date as the sending date')),
523 ('', 'date', '', _('use the given date as the sending date')),
521 ('', 'desc', '', _('use the given file as the series description')),
524 ('', 'desc', '', _('use the given file as the series description')),
522 ('f', 'from', '', _('email address of sender')),
525 ('f', 'from', '', _('email address of sender')),
523 ('n', 'test', None, _('print messages that would be sent')),
526 ('n', 'test', None, _('print messages that would be sent')),
524 ('m', 'mbox', '',
527 ('m', 'mbox', '',
525 _('write messages to mbox file instead of sending them')),
528 _('write messages to mbox file instead of sending them')),
526 ('', 'reply-to', [], _('email addresses replies should be sent to')),
529 ('', 'reply-to', [], _('email addresses replies should be sent to')),
527 ('s', 'subject', '',
530 ('s', 'subject', '',
528 _('subject of first message (intro or single patch)')),
531 _('subject of first message (intro or single patch)')),
529 ('', 'in-reply-to', '',
532 ('', 'in-reply-to', '',
530 _('message identifier to reply to')),
533 _('message identifier to reply to')),
531 ('', 'flag', [], _('flags to add in subject prefixes')),
534 ('', 'flag', [], _('flags to add in subject prefixes')),
532 ('t', 'to', [], _('email addresses of recipients')),
535 ('t', 'to', [], _('email addresses of recipients')),
533 ]
536 ]
534
537
535
538
536 cmdtable = {
539 cmdtable = {
537 "email":
540 "email":
538 (patchbomb,
541 (patchbomb,
539 [('g', 'git', None, _('use git extended diff format')),
542 [('g', 'git', None, _('use git extended diff format')),
540 ('', 'plain', None, _('omit hg patch header')),
543 ('', 'plain', None, _('omit hg patch header')),
541 ('o', 'outgoing', None,
544 ('o', 'outgoing', None,
542 _('send changes not found in the target repository')),
545 _('send changes not found in the target repository')),
543 ('b', 'bundle', None,
546 ('b', 'bundle', None,
544 _('send changes not in target as a binary bundle')),
547 _('send changes not in target as a binary bundle')),
545 ('', 'bundlename', 'bundle',
548 ('', 'bundlename', 'bundle',
546 _('name of the bundle attachment file'), _('NAME')),
549 _('name of the bundle attachment file'), _('NAME')),
547 ('r', 'rev', [],
550 ('r', 'rev', [],
548 _('a revision to send'), _('REV')),
551 _('a revision to send'), _('REV')),
549 ('', 'force', None,
552 ('', 'force', None,
550 _('run even when remote repository is unrelated '
553 _('run even when remote repository is unrelated '
551 '(with -b/--bundle)')),
554 '(with -b/--bundle)')),
552 ('', 'base', [],
555 ('', 'base', [],
553 _('a base changeset to specify instead of a destination '
556 _('a base changeset to specify instead of a destination '
554 '(with -b/--bundle)'),
557 '(with -b/--bundle)'),
555 _('REV')),
558 _('REV')),
556 ('', 'intro', None,
559 ('', 'intro', None,
557 _('send an introduction email for a single patch')),
560 _('send an introduction email for a single patch')),
558 ] + emailopts + commands.remoteopts,
561 ] + emailopts + commands.remoteopts,
559 _('hg email [OPTION]... [DEST]...'))
562 _('hg email [OPTION]... [DEST]...'))
560 }
563 }
@@ -1,279 +1,284 b''
1 # archival.py - revision archival for mercurial
1 # archival.py - revision archival for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from i18n import _
8 from i18n import _
9 from node import hex
9 from node import hex
10 import cmdutil
10 import cmdutil
11 import util, encoding
11 import util, encoding
12 import cStringIO, os, stat, tarfile, time, zipfile
12 import cStringIO, os, stat, tarfile, time, zipfile
13 import zlib, gzip
13 import zlib, gzip
14
14
15 def tidyprefix(dest, kind, prefix):
15 def tidyprefix(dest, kind, prefix):
16 '''choose prefix to use for names in archive. make sure prefix is
16 '''choose prefix to use for names in archive. make sure prefix is
17 safe for consumers.'''
17 safe for consumers.'''
18
18
19 if prefix:
19 if prefix:
20 prefix = util.normpath(prefix)
20 prefix = util.normpath(prefix)
21 else:
21 else:
22 if not isinstance(dest, str):
22 if not isinstance(dest, str):
23 raise ValueError('dest must be string if no prefix')
23 raise ValueError('dest must be string if no prefix')
24 prefix = os.path.basename(dest)
24 prefix = os.path.basename(dest)
25 lower = prefix.lower()
25 lower = prefix.lower()
26 for sfx in exts.get(kind, []):
26 for sfx in exts.get(kind, []):
27 if lower.endswith(sfx):
27 if lower.endswith(sfx):
28 prefix = prefix[:-len(sfx)]
28 prefix = prefix[:-len(sfx)]
29 break
29 break
30 lpfx = os.path.normpath(util.localpath(prefix))
30 lpfx = os.path.normpath(util.localpath(prefix))
31 prefix = util.pconvert(lpfx)
31 prefix = util.pconvert(lpfx)
32 if not prefix.endswith('/'):
32 if not prefix.endswith('/'):
33 prefix += '/'
33 prefix += '/'
34 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
34 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
35 raise util.Abort(_('archive prefix contains illegal components'))
35 raise util.Abort(_('archive prefix contains illegal components'))
36 return prefix
36 return prefix
37
37
38 exts = {
38 exts = {
39 'tar': ['.tar'],
39 'tar': ['.tar'],
40 'tbz2': ['.tbz2', '.tar.bz2'],
40 'tbz2': ['.tbz2', '.tar.bz2'],
41 'tgz': ['.tgz', '.tar.gz'],
41 'tgz': ['.tgz', '.tar.gz'],
42 'zip': ['.zip'],
42 'zip': ['.zip'],
43 }
43 }
44
44
45 def guesskind(dest):
45 def guesskind(dest):
46 for kind, extensions in exts.iteritems():
46 for kind, extensions in exts.iteritems():
47 if util.any(dest.endswith(ext) for ext in extensions):
47 if util.any(dest.endswith(ext) for ext in extensions):
48 return kind
48 return kind
49 return None
49 return None
50
50
51
51
52 class tarit(object):
52 class tarit(object):
53 '''write archive to tar file or stream. can write uncompressed,
53 '''write archive to tar file or stream. can write uncompressed,
54 or compress with gzip or bzip2.'''
54 or compress with gzip or bzip2.'''
55
55
56 class GzipFileWithTime(gzip.GzipFile):
56 class GzipFileWithTime(gzip.GzipFile):
57
57
58 def __init__(self, *args, **kw):
58 def __init__(self, *args, **kw):
59 timestamp = None
59 timestamp = None
60 if 'timestamp' in kw:
60 if 'timestamp' in kw:
61 timestamp = kw.pop('timestamp')
61 timestamp = kw.pop('timestamp')
62 if timestamp is None:
62 if timestamp is None:
63 self.timestamp = time.time()
63 self.timestamp = time.time()
64 else:
64 else:
65 self.timestamp = timestamp
65 self.timestamp = timestamp
66 gzip.GzipFile.__init__(self, *args, **kw)
66 gzip.GzipFile.__init__(self, *args, **kw)
67
67
68 def _write_gzip_header(self):
68 def _write_gzip_header(self):
69 self.fileobj.write('\037\213') # magic header
69 self.fileobj.write('\037\213') # magic header
70 self.fileobj.write('\010') # compression method
70 self.fileobj.write('\010') # compression method
71 # Python 2.6 deprecates self.filename
71 # Python 2.6 deprecates self.filename
72 fname = getattr(self, 'name', None) or self.filename
72 fname = getattr(self, 'name', None) or self.filename
73 if fname and fname.endswith('.gz'):
73 if fname and fname.endswith('.gz'):
74 fname = fname[:-3]
74 fname = fname[:-3]
75 flags = 0
75 flags = 0
76 if fname:
76 if fname:
77 flags = gzip.FNAME
77 flags = gzip.FNAME
78 self.fileobj.write(chr(flags))
78 self.fileobj.write(chr(flags))
79 gzip.write32u(self.fileobj, long(self.timestamp))
79 gzip.write32u(self.fileobj, long(self.timestamp))
80 self.fileobj.write('\002')
80 self.fileobj.write('\002')
81 self.fileobj.write('\377')
81 self.fileobj.write('\377')
82 if fname:
82 if fname:
83 self.fileobj.write(fname + '\000')
83 self.fileobj.write(fname + '\000')
84
84
85 def __init__(self, dest, mtime, kind=''):
85 def __init__(self, dest, mtime, kind=''):
86 self.mtime = mtime
86 self.mtime = mtime
87 self.fileobj = None
87
88
88 def taropen(name, mode, fileobj=None):
89 def taropen(name, mode, fileobj=None):
89 if kind == 'gz':
90 if kind == 'gz':
90 mode = mode[0]
91 mode = mode[0]
91 if not fileobj:
92 if not fileobj:
92 fileobj = open(name, mode + 'b')
93 fileobj = open(name, mode + 'b')
93 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
94 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
94 zlib.Z_BEST_COMPRESSION,
95 zlib.Z_BEST_COMPRESSION,
95 fileobj, timestamp=mtime)
96 fileobj, timestamp=mtime)
97 self.fileobj = gzfileobj
96 return tarfile.TarFile.taropen(name, mode, gzfileobj)
98 return tarfile.TarFile.taropen(name, mode, gzfileobj)
97 else:
99 else:
100 self.fileobj = fileobj
98 return tarfile.open(name, mode + kind, fileobj)
101 return tarfile.open(name, mode + kind, fileobj)
99
102
100 if isinstance(dest, str):
103 if isinstance(dest, str):
101 self.z = taropen(dest, mode='w:')
104 self.z = taropen(dest, mode='w:')
102 else:
105 else:
103 # Python 2.5-2.5.1 have a regression that requires a name arg
106 # Python 2.5-2.5.1 have a regression that requires a name arg
104 self.z = taropen(name='', mode='w|', fileobj=dest)
107 self.z = taropen(name='', mode='w|', fileobj=dest)
105
108
106 def addfile(self, name, mode, islink, data):
109 def addfile(self, name, mode, islink, data):
107 i = tarfile.TarInfo(name)
110 i = tarfile.TarInfo(name)
108 i.mtime = self.mtime
111 i.mtime = self.mtime
109 i.size = len(data)
112 i.size = len(data)
110 if islink:
113 if islink:
111 i.type = tarfile.SYMTYPE
114 i.type = tarfile.SYMTYPE
112 i.mode = 0777
115 i.mode = 0777
113 i.linkname = data
116 i.linkname = data
114 data = None
117 data = None
115 i.size = 0
118 i.size = 0
116 else:
119 else:
117 i.mode = mode
120 i.mode = mode
118 data = cStringIO.StringIO(data)
121 data = cStringIO.StringIO(data)
119 self.z.addfile(i, data)
122 self.z.addfile(i, data)
120
123
121 def done(self):
124 def done(self):
122 self.z.close()
125 self.z.close()
126 if self.fileobj:
127 self.fileobj.close()
123
128
124 class tellable(object):
129 class tellable(object):
125 '''provide tell method for zipfile.ZipFile when writing to http
130 '''provide tell method for zipfile.ZipFile when writing to http
126 response file object.'''
131 response file object.'''
127
132
128 def __init__(self, fp):
133 def __init__(self, fp):
129 self.fp = fp
134 self.fp = fp
130 self.offset = 0
135 self.offset = 0
131
136
132 def __getattr__(self, key):
137 def __getattr__(self, key):
133 return getattr(self.fp, key)
138 return getattr(self.fp, key)
134
139
135 def write(self, s):
140 def write(self, s):
136 self.fp.write(s)
141 self.fp.write(s)
137 self.offset += len(s)
142 self.offset += len(s)
138
143
139 def tell(self):
144 def tell(self):
140 return self.offset
145 return self.offset
141
146
142 class zipit(object):
147 class zipit(object):
143 '''write archive to zip file or stream. can write uncompressed,
148 '''write archive to zip file or stream. can write uncompressed,
144 or compressed with deflate.'''
149 or compressed with deflate.'''
145
150
146 def __init__(self, dest, mtime, compress=True):
151 def __init__(self, dest, mtime, compress=True):
147 if not isinstance(dest, str):
152 if not isinstance(dest, str):
148 try:
153 try:
149 dest.tell()
154 dest.tell()
150 except (AttributeError, IOError):
155 except (AttributeError, IOError):
151 dest = tellable(dest)
156 dest = tellable(dest)
152 self.z = zipfile.ZipFile(dest, 'w',
157 self.z = zipfile.ZipFile(dest, 'w',
153 compress and zipfile.ZIP_DEFLATED or
158 compress and zipfile.ZIP_DEFLATED or
154 zipfile.ZIP_STORED)
159 zipfile.ZIP_STORED)
155
160
156 # Python's zipfile module emits deprecation warnings if we try
161 # Python's zipfile module emits deprecation warnings if we try
157 # to store files with a date before 1980.
162 # to store files with a date before 1980.
158 epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
163 epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
159 if mtime < epoch:
164 if mtime < epoch:
160 mtime = epoch
165 mtime = epoch
161
166
162 self.date_time = time.gmtime(mtime)[:6]
167 self.date_time = time.gmtime(mtime)[:6]
163
168
164 def addfile(self, name, mode, islink, data):
169 def addfile(self, name, mode, islink, data):
165 i = zipfile.ZipInfo(name, self.date_time)
170 i = zipfile.ZipInfo(name, self.date_time)
166 i.compress_type = self.z.compression
171 i.compress_type = self.z.compression
167 # unzip will not honor unix file modes unless file creator is
172 # unzip will not honor unix file modes unless file creator is
168 # set to unix (id 3).
173 # set to unix (id 3).
169 i.create_system = 3
174 i.create_system = 3
170 ftype = stat.S_IFREG
175 ftype = stat.S_IFREG
171 if islink:
176 if islink:
172 mode = 0777
177 mode = 0777
173 ftype = stat.S_IFLNK
178 ftype = stat.S_IFLNK
174 i.external_attr = (mode | ftype) << 16L
179 i.external_attr = (mode | ftype) << 16L
175 self.z.writestr(i, data)
180 self.z.writestr(i, data)
176
181
177 def done(self):
182 def done(self):
178 self.z.close()
183 self.z.close()
179
184
180 class fileit(object):
185 class fileit(object):
181 '''write archive as files in directory.'''
186 '''write archive as files in directory.'''
182
187
183 def __init__(self, name, mtime):
188 def __init__(self, name, mtime):
184 self.basedir = name
189 self.basedir = name
185 self.opener = util.opener(self.basedir)
190 self.opener = util.opener(self.basedir)
186
191
187 def addfile(self, name, mode, islink, data):
192 def addfile(self, name, mode, islink, data):
188 if islink:
193 if islink:
189 self.opener.symlink(data, name)
194 self.opener.symlink(data, name)
190 return
195 return
191 f = self.opener(name, "w", atomictemp=True)
196 f = self.opener(name, "w", atomictemp=True)
192 f.write(data)
197 f.write(data)
193 f.rename()
198 f.rename()
194 destfile = os.path.join(self.basedir, name)
199 destfile = os.path.join(self.basedir, name)
195 os.chmod(destfile, mode)
200 os.chmod(destfile, mode)
196
201
197 def done(self):
202 def done(self):
198 pass
203 pass
199
204
200 archivers = {
205 archivers = {
201 'files': fileit,
206 'files': fileit,
202 'tar': tarit,
207 'tar': tarit,
203 'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
208 'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
204 'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
209 'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
205 'uzip': lambda name, mtime: zipit(name, mtime, False),
210 'uzip': lambda name, mtime: zipit(name, mtime, False),
206 'zip': zipit,
211 'zip': zipit,
207 }
212 }
208
213
209 def archive(repo, dest, node, kind, decode=True, matchfn=None,
214 def archive(repo, dest, node, kind, decode=True, matchfn=None,
210 prefix=None, mtime=None, subrepos=False):
215 prefix=None, mtime=None, subrepos=False):
211 '''create archive of repo as it was at node.
216 '''create archive of repo as it was at node.
212
217
213 dest can be name of directory, name of archive file, or file
218 dest can be name of directory, name of archive file, or file
214 object to write archive to.
219 object to write archive to.
215
220
216 kind is type of archive to create.
221 kind is type of archive to create.
217
222
218 decode tells whether to put files through decode filters from
223 decode tells whether to put files through decode filters from
219 hgrc.
224 hgrc.
220
225
221 matchfn is function to filter names of files to write to archive.
226 matchfn is function to filter names of files to write to archive.
222
227
223 prefix is name of path to put before every archive member.'''
228 prefix is name of path to put before every archive member.'''
224
229
225 if kind == 'files':
230 if kind == 'files':
226 if prefix:
231 if prefix:
227 raise util.Abort(_('cannot give prefix when archiving to files'))
232 raise util.Abort(_('cannot give prefix when archiving to files'))
228 else:
233 else:
229 prefix = tidyprefix(dest, kind, prefix)
234 prefix = tidyprefix(dest, kind, prefix)
230
235
231 def write(name, mode, islink, getdata):
236 def write(name, mode, islink, getdata):
232 if matchfn and not matchfn(name):
237 if matchfn and not matchfn(name):
233 return
238 return
234 data = getdata()
239 data = getdata()
235 if decode:
240 if decode:
236 data = repo.wwritedata(name, data)
241 data = repo.wwritedata(name, data)
237 archiver.addfile(prefix + name, mode, islink, data)
242 archiver.addfile(prefix + name, mode, islink, data)
238
243
239 if kind not in archivers:
244 if kind not in archivers:
240 raise util.Abort(_("unknown archive type '%s'") % kind)
245 raise util.Abort(_("unknown archive type '%s'") % kind)
241
246
242 ctx = repo[node]
247 ctx = repo[node]
243 archiver = archivers[kind](dest, mtime or ctx.date()[0])
248 archiver = archivers[kind](dest, mtime or ctx.date()[0])
244
249
245 if repo.ui.configbool("ui", "archivemeta", True):
250 if repo.ui.configbool("ui", "archivemeta", True):
246 def metadata():
251 def metadata():
247 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
252 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
248 repo[0].hex(), hex(node), encoding.fromlocal(ctx.branch()))
253 repo[0].hex(), hex(node), encoding.fromlocal(ctx.branch()))
249
254
250 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
255 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
251 if repo.tagtype(t) == 'global')
256 if repo.tagtype(t) == 'global')
252 if not tags:
257 if not tags:
253 repo.ui.pushbuffer()
258 repo.ui.pushbuffer()
254 opts = {'template': '{latesttag}\n{latesttagdistance}',
259 opts = {'template': '{latesttag}\n{latesttagdistance}',
255 'style': '', 'patch': None, 'git': None}
260 'style': '', 'patch': None, 'git': None}
256 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
261 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
257 ltags, dist = repo.ui.popbuffer().split('\n')
262 ltags, dist = repo.ui.popbuffer().split('\n')
258 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
263 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
259 tags += 'latesttagdistance: %s\n' % dist
264 tags += 'latesttagdistance: %s\n' % dist
260
265
261 return base + tags
266 return base + tags
262
267
263 write('.hg_archival.txt', 0644, False, metadata)
268 write('.hg_archival.txt', 0644, False, metadata)
264
269
265 total = len(ctx.manifest())
270 total = len(ctx.manifest())
266 repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
271 repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
267 for i, f in enumerate(ctx):
272 for i, f in enumerate(ctx):
268 ff = ctx.flags(f)
273 ff = ctx.flags(f)
269 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
274 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
270 repo.ui.progress(_('archiving'), i + 1, item=f,
275 repo.ui.progress(_('archiving'), i + 1, item=f,
271 unit=_('files'), total=total)
276 unit=_('files'), total=total)
272 repo.ui.progress(_('archiving'), None)
277 repo.ui.progress(_('archiving'), None)
273
278
274 if subrepos:
279 if subrepos:
275 for subpath in ctx.substate:
280 for subpath in ctx.substate:
276 sub = ctx.sub(subpath)
281 sub = ctx.sub(subpath)
277 sub.archive(repo.ui, archiver, prefix)
282 sub.archive(repo.ui, archiver, prefix)
278
283
279 archiver.done()
284 archiver.done()
@@ -1,1383 +1,1386 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.parents()[1] != nullid:
75 if repo.dirstate.parents()[1] != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revsingle(repo, revspec, default='.'):
114 def revsingle(repo, revspec, default='.'):
115 if not revspec:
115 if not revspec:
116 return repo[default]
116 return repo[default]
117
117
118 l = revrange(repo, [revspec])
118 l = revrange(repo, [revspec])
119 if len(l) < 1:
119 if len(l) < 1:
120 raise util.Abort(_('empty revision set'))
120 raise util.Abort(_('empty revision set'))
121 return repo[l[-1]]
121 return repo[l[-1]]
122
122
123 def revpair(repo, revs):
123 def revpair(repo, revs):
124 if not revs:
124 if not revs:
125 return repo.dirstate.parents()[0], None
125 return repo.dirstate.parents()[0], None
126
126
127 l = revrange(repo, revs)
127 l = revrange(repo, revs)
128
128
129 if len(l) == 0:
129 if len(l) == 0:
130 return repo.dirstate.parents()[0], None
130 return repo.dirstate.parents()[0], None
131
131
132 if len(l) == 1:
132 if len(l) == 1:
133 return repo.lookup(l[0]), None
133 return repo.lookup(l[0]), None
134
134
135 return repo.lookup(l[0]), repo.lookup(l[-1])
135 return repo.lookup(l[0]), repo.lookup(l[-1])
136
136
137 def revrange(repo, revs):
137 def revrange(repo, revs):
138 """Yield revision as strings from a list of revision specifications."""
138 """Yield revision as strings from a list of revision specifications."""
139
139
140 def revfix(repo, val, defval):
140 def revfix(repo, val, defval):
141 if not val and val != 0 and defval is not None:
141 if not val and val != 0 and defval is not None:
142 return defval
142 return defval
143 return repo.changelog.rev(repo.lookup(val))
143 return repo.changelog.rev(repo.lookup(val))
144
144
145 seen, l = set(), []
145 seen, l = set(), []
146 for spec in revs:
146 for spec in revs:
147 # attempt to parse old-style ranges first to deal with
147 # attempt to parse old-style ranges first to deal with
148 # things like old-tag which contain query metacharacters
148 # things like old-tag which contain query metacharacters
149 try:
149 try:
150 if isinstance(spec, int):
150 if isinstance(spec, int):
151 seen.add(spec)
151 seen.add(spec)
152 l.append(spec)
152 l.append(spec)
153 continue
153 continue
154
154
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = 'w' in mode or 'a' in mode
233 writable = 'w' in mode or 'a' in mode
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 fp = writable and sys.stdout or sys.stdin
236 fp = writable and sys.stdout or sys.stdin
237 return os.fdopen(os.dup(fp.fileno()), mode)
237 return os.fdopen(os.dup(fp.fileno()), mode)
238 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
239 return pat
239 return pat
240 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
241 return pat
241 return pat
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 pathname),
243 pathname),
244 mode)
244 mode)
245
245
246 def expandpats(pats):
246 def expandpats(pats):
247 if not util.expandglobs:
247 if not util.expandglobs:
248 return list(pats)
248 return list(pats)
249 ret = []
249 ret = []
250 for p in pats:
250 for p in pats:
251 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
252 if kind is None:
252 if kind is None:
253 try:
253 try:
254 globbed = glob.glob(name)
254 globbed = glob.glob(name)
255 except re.error:
255 except re.error:
256 globbed = [name]
256 globbed = [name]
257 if globbed:
257 if globbed:
258 ret.extend(globbed)
258 ret.extend(globbed)
259 continue
259 continue
260 ret.append(p)
260 ret.append(p)
261 return ret
261 return ret
262
262
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 if not globbed and default == 'relpath':
264 if not globbed and default == 'relpath':
265 pats = expandpats(pats or [])
265 pats = expandpats(pats or [])
266 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 m = matchmod.match(repo.root, repo.getcwd(), pats,
267 opts.get('include'), opts.get('exclude'), default,
267 opts.get('include'), opts.get('exclude'), default,
268 auditor=repo.auditor)
268 auditor=repo.auditor)
269 def badfn(f, msg):
269 def badfn(f, msg):
270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
271 m.bad = badfn
271 m.bad = badfn
272 return m
272 return m
273
273
274 def matchall(repo):
274 def matchall(repo):
275 return matchmod.always(repo.root, repo.getcwd())
275 return matchmod.always(repo.root, repo.getcwd())
276
276
277 def matchfiles(repo, files):
277 def matchfiles(repo, files):
278 return matchmod.exact(repo.root, repo.getcwd(), files)
278 return matchmod.exact(repo.root, repo.getcwd(), files)
279
279
280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
281 if dry_run is None:
281 if dry_run is None:
282 dry_run = opts.get('dry_run')
282 dry_run = opts.get('dry_run')
283 if similarity is None:
283 if similarity is None:
284 similarity = float(opts.get('similarity') or 0)
284 similarity = float(opts.get('similarity') or 0)
285 # we'd use status here, except handling of symlinks and ignore is tricky
285 # we'd use status here, except handling of symlinks and ignore is tricky
286 added, unknown, deleted, removed = [], [], [], []
286 added, unknown, deleted, removed = [], [], [], []
287 audit_path = util.path_auditor(repo.root)
287 audit_path = util.path_auditor(repo.root)
288 m = match(repo, pats, opts)
288 m = match(repo, pats, opts)
289 for abs in repo.walk(m):
289 for abs in repo.walk(m):
290 target = repo.wjoin(abs)
290 target = repo.wjoin(abs)
291 good = True
291 good = True
292 try:
292 try:
293 audit_path(abs)
293 audit_path(abs)
294 except:
294 except:
295 good = False
295 good = False
296 rel = m.rel(abs)
296 rel = m.rel(abs)
297 exact = m.exact(abs)
297 exact = m.exact(abs)
298 if good and abs not in repo.dirstate:
298 if good and abs not in repo.dirstate:
299 unknown.append(abs)
299 unknown.append(abs)
300 if repo.ui.verbose or not exact:
300 if repo.ui.verbose or not exact:
301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
303 or (os.path.isdir(target) and not os.path.islink(target))):
303 or (os.path.isdir(target) and not os.path.islink(target))):
304 deleted.append(abs)
304 deleted.append(abs)
305 if repo.ui.verbose or not exact:
305 if repo.ui.verbose or not exact:
306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
307 # for finding renames
307 # for finding renames
308 elif repo.dirstate[abs] == 'r':
308 elif repo.dirstate[abs] == 'r':
309 removed.append(abs)
309 removed.append(abs)
310 elif repo.dirstate[abs] == 'a':
310 elif repo.dirstate[abs] == 'a':
311 added.append(abs)
311 added.append(abs)
312 copies = {}
312 copies = {}
313 if similarity > 0:
313 if similarity > 0:
314 for old, new, score in similar.findrenames(repo,
314 for old, new, score in similar.findrenames(repo,
315 added + unknown, removed + deleted, similarity):
315 added + unknown, removed + deleted, similarity):
316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
317 repo.ui.status(_('recording removal of %s as rename to %s '
317 repo.ui.status(_('recording removal of %s as rename to %s '
318 '(%d%% similar)\n') %
318 '(%d%% similar)\n') %
319 (m.rel(old), m.rel(new), score * 100))
319 (m.rel(old), m.rel(new), score * 100))
320 copies[new] = old
320 copies[new] = old
321
321
322 if not dry_run:
322 if not dry_run:
323 wctx = repo[None]
323 wctx = repo[None]
324 wlock = repo.wlock()
324 wlock = repo.wlock()
325 try:
325 try:
326 wctx.remove(deleted)
326 wctx.remove(deleted)
327 wctx.add(unknown)
327 wctx.add(unknown)
328 for new, old in copies.iteritems():
328 for new, old in copies.iteritems():
329 wctx.copy(old, new)
329 wctx.copy(old, new)
330 finally:
330 finally:
331 wlock.release()
331 wlock.release()
332
332
333 def updatedir(ui, repo, patches, similarity=0):
333 def updatedir(ui, repo, patches, similarity=0):
334 '''Update dirstate after patch application according to metadata'''
334 '''Update dirstate after patch application according to metadata'''
335 if not patches:
335 if not patches:
336 return
336 return
337 copies = []
337 copies = []
338 removes = set()
338 removes = set()
339 cfiles = patches.keys()
339 cfiles = patches.keys()
340 cwd = repo.getcwd()
340 cwd = repo.getcwd()
341 if cwd:
341 if cwd:
342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
343 for f in patches:
343 for f in patches:
344 gp = patches[f]
344 gp = patches[f]
345 if not gp:
345 if not gp:
346 continue
346 continue
347 if gp.op == 'RENAME':
347 if gp.op == 'RENAME':
348 copies.append((gp.oldpath, gp.path))
348 copies.append((gp.oldpath, gp.path))
349 removes.add(gp.oldpath)
349 removes.add(gp.oldpath)
350 elif gp.op == 'COPY':
350 elif gp.op == 'COPY':
351 copies.append((gp.oldpath, gp.path))
351 copies.append((gp.oldpath, gp.path))
352 elif gp.op == 'DELETE':
352 elif gp.op == 'DELETE':
353 removes.add(gp.path)
353 removes.add(gp.path)
354
354
355 wctx = repo[None]
355 wctx = repo[None]
356 for src, dst in copies:
356 for src, dst in copies:
357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
358 if (not similarity) and removes:
358 if (not similarity) and removes:
359 wctx.remove(sorted(removes), True)
359 wctx.remove(sorted(removes), True)
360
360
361 for f in patches:
361 for f in patches:
362 gp = patches[f]
362 gp = patches[f]
363 if gp and gp.mode:
363 if gp and gp.mode:
364 islink, isexec = gp.mode
364 islink, isexec = gp.mode
365 dst = repo.wjoin(gp.path)
365 dst = repo.wjoin(gp.path)
366 # patch won't create empty files
366 # patch won't create empty files
367 if gp.op == 'ADD' and not os.path.lexists(dst):
367 if gp.op == 'ADD' and not os.path.lexists(dst):
368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
369 repo.wwrite(gp.path, '', flags)
369 repo.wwrite(gp.path, '', flags)
370 util.set_flags(dst, islink, isexec)
370 util.set_flags(dst, islink, isexec)
371 addremove(repo, cfiles, similarity=similarity)
371 addremove(repo, cfiles, similarity=similarity)
372 files = patches.keys()
372 files = patches.keys()
373 files.extend([r for r in removes if r not in files])
373 files.extend([r for r in removes if r not in files])
374 return sorted(files)
374 return sorted(files)
375
375
376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
377 """Update the dirstate to reflect the intent of copying src to dst. For
377 """Update the dirstate to reflect the intent of copying src to dst. For
378 different reasons it might not end with dst being marked as copied from src.
378 different reasons it might not end with dst being marked as copied from src.
379 """
379 """
380 origsrc = repo.dirstate.copied(src) or src
380 origsrc = repo.dirstate.copied(src) or src
381 if dst == origsrc: # copying back a copy?
381 if dst == origsrc: # copying back a copy?
382 if repo.dirstate[dst] not in 'mn' and not dryrun:
382 if repo.dirstate[dst] not in 'mn' and not dryrun:
383 repo.dirstate.normallookup(dst)
383 repo.dirstate.normallookup(dst)
384 else:
384 else:
385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
386 if not ui.quiet:
386 if not ui.quiet:
387 ui.warn(_("%s has not been committed yet, so no copy "
387 ui.warn(_("%s has not been committed yet, so no copy "
388 "data will be stored for %s.\n")
388 "data will be stored for %s.\n")
389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
390 if repo.dirstate[dst] in '?r' and not dryrun:
390 if repo.dirstate[dst] in '?r' and not dryrun:
391 wctx.add([dst])
391 wctx.add([dst])
392 elif not dryrun:
392 elif not dryrun:
393 wctx.copy(origsrc, dst)
393 wctx.copy(origsrc, dst)
394
394
395 def copy(ui, repo, pats, opts, rename=False):
395 def copy(ui, repo, pats, opts, rename=False):
396 # called with the repo lock held
396 # called with the repo lock held
397 #
397 #
398 # hgsep => pathname that uses "/" to separate directories
398 # hgsep => pathname that uses "/" to separate directories
399 # ossep => pathname that uses os.sep to separate directories
399 # ossep => pathname that uses os.sep to separate directories
400 cwd = repo.getcwd()
400 cwd = repo.getcwd()
401 targets = {}
401 targets = {}
402 after = opts.get("after")
402 after = opts.get("after")
403 dryrun = opts.get("dry_run")
403 dryrun = opts.get("dry_run")
404 wctx = repo[None]
404 wctx = repo[None]
405
405
406 def walkpat(pat):
406 def walkpat(pat):
407 srcs = []
407 srcs = []
408 badstates = after and '?' or '?r'
408 badstates = after and '?' or '?r'
409 m = match(repo, [pat], opts, globbed=True)
409 m = match(repo, [pat], opts, globbed=True)
410 for abs in repo.walk(m):
410 for abs in repo.walk(m):
411 state = repo.dirstate[abs]
411 state = repo.dirstate[abs]
412 rel = m.rel(abs)
412 rel = m.rel(abs)
413 exact = m.exact(abs)
413 exact = m.exact(abs)
414 if state in badstates:
414 if state in badstates:
415 if exact and state == '?':
415 if exact and state == '?':
416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
417 if exact and state == 'r':
417 if exact and state == 'r':
418 ui.warn(_('%s: not copying - file has been marked for'
418 ui.warn(_('%s: not copying - file has been marked for'
419 ' remove\n') % rel)
419 ' remove\n') % rel)
420 continue
420 continue
421 # abs: hgsep
421 # abs: hgsep
422 # rel: ossep
422 # rel: ossep
423 srcs.append((abs, rel, exact))
423 srcs.append((abs, rel, exact))
424 return srcs
424 return srcs
425
425
426 # abssrc: hgsep
426 # abssrc: hgsep
427 # relsrc: ossep
427 # relsrc: ossep
428 # otarget: ossep
428 # otarget: ossep
429 def copyfile(abssrc, relsrc, otarget, exact):
429 def copyfile(abssrc, relsrc, otarget, exact):
430 abstarget = util.canonpath(repo.root, cwd, otarget)
430 abstarget = util.canonpath(repo.root, cwd, otarget)
431 reltarget = repo.pathto(abstarget, cwd)
431 reltarget = repo.pathto(abstarget, cwd)
432 target = repo.wjoin(abstarget)
432 target = repo.wjoin(abstarget)
433 src = repo.wjoin(abssrc)
433 src = repo.wjoin(abssrc)
434 state = repo.dirstate[abstarget]
434 state = repo.dirstate[abstarget]
435
435
436 # check for collisions
436 # check for collisions
437 prevsrc = targets.get(abstarget)
437 prevsrc = targets.get(abstarget)
438 if prevsrc is not None:
438 if prevsrc is not None:
439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
440 (reltarget, repo.pathto(abssrc, cwd),
440 (reltarget, repo.pathto(abssrc, cwd),
441 repo.pathto(prevsrc, cwd)))
441 repo.pathto(prevsrc, cwd)))
442 return
442 return
443
443
444 # check for overwrites
444 # check for overwrites
445 exists = os.path.lexists(target)
445 exists = os.path.lexists(target)
446 if not after and exists or after and state in 'mn':
446 if not after and exists or after and state in 'mn':
447 if not opts['force']:
447 if not opts['force']:
448 ui.warn(_('%s: not overwriting - file exists\n') %
448 ui.warn(_('%s: not overwriting - file exists\n') %
449 reltarget)
449 reltarget)
450 return
450 return
451
451
452 if after:
452 if after:
453 if not exists:
453 if not exists:
454 if rename:
454 if rename:
455 ui.warn(_('%s: not recording move - %s does not exist\n') %
455 ui.warn(_('%s: not recording move - %s does not exist\n') %
456 (relsrc, reltarget))
456 (relsrc, reltarget))
457 else:
457 else:
458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
459 (relsrc, reltarget))
459 (relsrc, reltarget))
460 return
460 return
461 elif not dryrun:
461 elif not dryrun:
462 try:
462 try:
463 if exists:
463 if exists:
464 os.unlink(target)
464 os.unlink(target)
465 targetdir = os.path.dirname(target) or '.'
465 targetdir = os.path.dirname(target) or '.'
466 if not os.path.isdir(targetdir):
466 if not os.path.isdir(targetdir):
467 os.makedirs(targetdir)
467 os.makedirs(targetdir)
468 util.copyfile(src, target)
468 util.copyfile(src, target)
469 except IOError, inst:
469 except IOError, inst:
470 if inst.errno == errno.ENOENT:
470 if inst.errno == errno.ENOENT:
471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
472 else:
472 else:
473 ui.warn(_('%s: cannot copy - %s\n') %
473 ui.warn(_('%s: cannot copy - %s\n') %
474 (relsrc, inst.strerror))
474 (relsrc, inst.strerror))
475 return True # report a failure
475 return True # report a failure
476
476
477 if ui.verbose or not exact:
477 if ui.verbose or not exact:
478 if rename:
478 if rename:
479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
480 else:
480 else:
481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
482
482
483 targets[abstarget] = abssrc
483 targets[abstarget] = abssrc
484
484
485 # fix up dirstate
485 # fix up dirstate
486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
487 if rename and not dryrun:
487 if rename and not dryrun:
488 wctx.remove([abssrc], not after)
488 wctx.remove([abssrc], not after)
489
489
490 # pat: ossep
490 # pat: ossep
491 # dest ossep
491 # dest ossep
492 # srcs: list of (hgsep, hgsep, ossep, bool)
492 # srcs: list of (hgsep, hgsep, ossep, bool)
493 # return: function that takes hgsep and returns ossep
493 # return: function that takes hgsep and returns ossep
494 def targetpathfn(pat, dest, srcs):
494 def targetpathfn(pat, dest, srcs):
495 if os.path.isdir(pat):
495 if os.path.isdir(pat):
496 abspfx = util.canonpath(repo.root, cwd, pat)
496 abspfx = util.canonpath(repo.root, cwd, pat)
497 abspfx = util.localpath(abspfx)
497 abspfx = util.localpath(abspfx)
498 if destdirexists:
498 if destdirexists:
499 striplen = len(os.path.split(abspfx)[0])
499 striplen = len(os.path.split(abspfx)[0])
500 else:
500 else:
501 striplen = len(abspfx)
501 striplen = len(abspfx)
502 if striplen:
502 if striplen:
503 striplen += len(os.sep)
503 striplen += len(os.sep)
504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
505 elif destdirexists:
505 elif destdirexists:
506 res = lambda p: os.path.join(dest,
506 res = lambda p: os.path.join(dest,
507 os.path.basename(util.localpath(p)))
507 os.path.basename(util.localpath(p)))
508 else:
508 else:
509 res = lambda p: dest
509 res = lambda p: dest
510 return res
510 return res
511
511
512 # pat: ossep
512 # pat: ossep
513 # dest ossep
513 # dest ossep
514 # srcs: list of (hgsep, hgsep, ossep, bool)
514 # srcs: list of (hgsep, hgsep, ossep, bool)
515 # return: function that takes hgsep and returns ossep
515 # return: function that takes hgsep and returns ossep
516 def targetpathafterfn(pat, dest, srcs):
516 def targetpathafterfn(pat, dest, srcs):
517 if matchmod.patkind(pat):
517 if matchmod.patkind(pat):
518 # a mercurial pattern
518 # a mercurial pattern
519 res = lambda p: os.path.join(dest,
519 res = lambda p: os.path.join(dest,
520 os.path.basename(util.localpath(p)))
520 os.path.basename(util.localpath(p)))
521 else:
521 else:
522 abspfx = util.canonpath(repo.root, cwd, pat)
522 abspfx = util.canonpath(repo.root, cwd, pat)
523 if len(abspfx) < len(srcs[0][0]):
523 if len(abspfx) < len(srcs[0][0]):
524 # A directory. Either the target path contains the last
524 # A directory. Either the target path contains the last
525 # component of the source path or it does not.
525 # component of the source path or it does not.
526 def evalpath(striplen):
526 def evalpath(striplen):
527 score = 0
527 score = 0
528 for s in srcs:
528 for s in srcs:
529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
530 if os.path.lexists(t):
530 if os.path.lexists(t):
531 score += 1
531 score += 1
532 return score
532 return score
533
533
534 abspfx = util.localpath(abspfx)
534 abspfx = util.localpath(abspfx)
535 striplen = len(abspfx)
535 striplen = len(abspfx)
536 if striplen:
536 if striplen:
537 striplen += len(os.sep)
537 striplen += len(os.sep)
538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
539 score = evalpath(striplen)
539 score = evalpath(striplen)
540 striplen1 = len(os.path.split(abspfx)[0])
540 striplen1 = len(os.path.split(abspfx)[0])
541 if striplen1:
541 if striplen1:
542 striplen1 += len(os.sep)
542 striplen1 += len(os.sep)
543 if evalpath(striplen1) > score:
543 if evalpath(striplen1) > score:
544 striplen = striplen1
544 striplen = striplen1
545 res = lambda p: os.path.join(dest,
545 res = lambda p: os.path.join(dest,
546 util.localpath(p)[striplen:])
546 util.localpath(p)[striplen:])
547 else:
547 else:
548 # a file
548 # a file
549 if destdirexists:
549 if destdirexists:
550 res = lambda p: os.path.join(dest,
550 res = lambda p: os.path.join(dest,
551 os.path.basename(util.localpath(p)))
551 os.path.basename(util.localpath(p)))
552 else:
552 else:
553 res = lambda p: dest
553 res = lambda p: dest
554 return res
554 return res
555
555
556
556
557 pats = expandpats(pats)
557 pats = expandpats(pats)
558 if not pats:
558 if not pats:
559 raise util.Abort(_('no source or destination specified'))
559 raise util.Abort(_('no source or destination specified'))
560 if len(pats) == 1:
560 if len(pats) == 1:
561 raise util.Abort(_('no destination specified'))
561 raise util.Abort(_('no destination specified'))
562 dest = pats.pop()
562 dest = pats.pop()
563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
564 if not destdirexists:
564 if not destdirexists:
565 if len(pats) > 1 or matchmod.patkind(pats[0]):
565 if len(pats) > 1 or matchmod.patkind(pats[0]):
566 raise util.Abort(_('with multiple sources, destination must be an '
566 raise util.Abort(_('with multiple sources, destination must be an '
567 'existing directory'))
567 'existing directory'))
568 if util.endswithsep(dest):
568 if util.endswithsep(dest):
569 raise util.Abort(_('destination %s is not a directory') % dest)
569 raise util.Abort(_('destination %s is not a directory') % dest)
570
570
571 tfn = targetpathfn
571 tfn = targetpathfn
572 if after:
572 if after:
573 tfn = targetpathafterfn
573 tfn = targetpathafterfn
574 copylist = []
574 copylist = []
575 for pat in pats:
575 for pat in pats:
576 srcs = walkpat(pat)
576 srcs = walkpat(pat)
577 if not srcs:
577 if not srcs:
578 continue
578 continue
579 copylist.append((tfn(pat, dest, srcs), srcs))
579 copylist.append((tfn(pat, dest, srcs), srcs))
580 if not copylist:
580 if not copylist:
581 raise util.Abort(_('no files to copy'))
581 raise util.Abort(_('no files to copy'))
582
582
583 errors = 0
583 errors = 0
584 for targetpath, srcs in copylist:
584 for targetpath, srcs in copylist:
585 for abssrc, relsrc, exact in srcs:
585 for abssrc, relsrc, exact in srcs:
586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
587 errors += 1
587 errors += 1
588
588
589 if errors:
589 if errors:
590 ui.warn(_('(consider using --after)\n'))
590 ui.warn(_('(consider using --after)\n'))
591
591
592 return errors != 0
592 return errors != 0
593
593
594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
595 runargs=None, appendpid=False):
595 runargs=None, appendpid=False):
596 '''Run a command as a service.'''
596 '''Run a command as a service.'''
597
597
598 if opts['daemon'] and not opts['daemon_pipefds']:
598 if opts['daemon'] and not opts['daemon_pipefds']:
599 # Signal child process startup with file removal
599 # Signal child process startup with file removal
600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
601 os.close(lockfd)
601 os.close(lockfd)
602 try:
602 try:
603 if not runargs:
603 if not runargs:
604 runargs = util.hgcmd() + sys.argv[1:]
604 runargs = util.hgcmd() + sys.argv[1:]
605 runargs.append('--daemon-pipefds=%s' % lockpath)
605 runargs.append('--daemon-pipefds=%s' % lockpath)
606 # Don't pass --cwd to the child process, because we've already
606 # Don't pass --cwd to the child process, because we've already
607 # changed directory.
607 # changed directory.
608 for i in xrange(1, len(runargs)):
608 for i in xrange(1, len(runargs)):
609 if runargs[i].startswith('--cwd='):
609 if runargs[i].startswith('--cwd='):
610 del runargs[i]
610 del runargs[i]
611 break
611 break
612 elif runargs[i].startswith('--cwd'):
612 elif runargs[i].startswith('--cwd'):
613 del runargs[i:i + 2]
613 del runargs[i:i + 2]
614 break
614 break
615 def condfn():
615 def condfn():
616 return not os.path.exists(lockpath)
616 return not os.path.exists(lockpath)
617 pid = util.rundetached(runargs, condfn)
617 pid = util.rundetached(runargs, condfn)
618 if pid < 0:
618 if pid < 0:
619 raise util.Abort(_('child process failed to start'))
619 raise util.Abort(_('child process failed to start'))
620 finally:
620 finally:
621 try:
621 try:
622 os.unlink(lockpath)
622 os.unlink(lockpath)
623 except OSError, e:
623 except OSError, e:
624 if e.errno != errno.ENOENT:
624 if e.errno != errno.ENOENT:
625 raise
625 raise
626 if parentfn:
626 if parentfn:
627 return parentfn(pid)
627 return parentfn(pid)
628 else:
628 else:
629 return
629 return
630
630
631 if initfn:
631 if initfn:
632 initfn()
632 initfn()
633
633
634 if opts['pid_file']:
634 if opts['pid_file']:
635 mode = appendpid and 'a' or 'w'
635 mode = appendpid and 'a' or 'w'
636 fp = open(opts['pid_file'], mode)
636 fp = open(opts['pid_file'], mode)
637 fp.write(str(os.getpid()) + '\n')
637 fp.write(str(os.getpid()) + '\n')
638 fp.close()
638 fp.close()
639
639
640 if opts['daemon_pipefds']:
640 if opts['daemon_pipefds']:
641 lockpath = opts['daemon_pipefds']
641 lockpath = opts['daemon_pipefds']
642 try:
642 try:
643 os.setsid()
643 os.setsid()
644 except AttributeError:
644 except AttributeError:
645 pass
645 pass
646 os.unlink(lockpath)
646 os.unlink(lockpath)
647 util.hidewindow()
647 util.hidewindow()
648 sys.stdout.flush()
648 sys.stdout.flush()
649 sys.stderr.flush()
649 sys.stderr.flush()
650
650
651 nullfd = os.open(util.nulldev, os.O_RDWR)
651 nullfd = os.open(util.nulldev, os.O_RDWR)
652 logfilefd = nullfd
652 logfilefd = nullfd
653 if logfile:
653 if logfile:
654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
655 os.dup2(nullfd, 0)
655 os.dup2(nullfd, 0)
656 os.dup2(logfilefd, 1)
656 os.dup2(logfilefd, 1)
657 os.dup2(logfilefd, 2)
657 os.dup2(logfilefd, 2)
658 if nullfd not in (0, 1, 2):
658 if nullfd not in (0, 1, 2):
659 os.close(nullfd)
659 os.close(nullfd)
660 if logfile and logfilefd not in (0, 1, 2):
660 if logfile and logfilefd not in (0, 1, 2):
661 os.close(logfilefd)
661 os.close(logfilefd)
662
662
663 if runfn:
663 if runfn:
664 return runfn()
664 return runfn()
665
665
666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
667 opts=None):
667 opts=None):
668 '''export changesets as hg patches.'''
668 '''export changesets as hg patches.'''
669
669
670 total = len(revs)
670 total = len(revs)
671 revwidth = max([len(str(rev)) for rev in revs])
671 revwidth = max([len(str(rev)) for rev in revs])
672
672
673 def single(rev, seqno, fp):
673 def single(rev, seqno, fp):
674 ctx = repo[rev]
674 ctx = repo[rev]
675 node = ctx.node()
675 node = ctx.node()
676 parents = [p.node() for p in ctx.parents() if p]
676 parents = [p.node() for p in ctx.parents() if p]
677 branch = ctx.branch()
677 branch = ctx.branch()
678 if switch_parent:
678 if switch_parent:
679 parents.reverse()
679 parents.reverse()
680 prev = (parents and parents[0]) or nullid
680 prev = (parents and parents[0]) or nullid
681
681
682 shouldclose = False
682 if not fp:
683 if not fp:
684 shouldclose = True
683 fp = make_file(repo, template, node, total=total, seqno=seqno,
685 fp = make_file(repo, template, node, total=total, seqno=seqno,
684 revwidth=revwidth, mode='ab')
686 revwidth=revwidth, mode='ab')
685 if fp != sys.stdout and hasattr(fp, 'name'):
687 if fp != sys.stdout and hasattr(fp, 'name'):
686 repo.ui.note("%s\n" % fp.name)
688 repo.ui.note("%s\n" % fp.name)
687
689
688 fp.write("# HG changeset patch\n")
690 fp.write("# HG changeset patch\n")
689 fp.write("# User %s\n" % ctx.user())
691 fp.write("# User %s\n" % ctx.user())
690 fp.write("# Date %d %d\n" % ctx.date())
692 fp.write("# Date %d %d\n" % ctx.date())
691 if branch and branch != 'default':
693 if branch and branch != 'default':
692 fp.write("# Branch %s\n" % branch)
694 fp.write("# Branch %s\n" % branch)
693 fp.write("# Node ID %s\n" % hex(node))
695 fp.write("# Node ID %s\n" % hex(node))
694 fp.write("# Parent %s\n" % hex(prev))
696 fp.write("# Parent %s\n" % hex(prev))
695 if len(parents) > 1:
697 if len(parents) > 1:
696 fp.write("# Parent %s\n" % hex(parents[1]))
698 fp.write("# Parent %s\n" % hex(parents[1]))
697 fp.write(ctx.description().rstrip())
699 fp.write(ctx.description().rstrip())
698 fp.write("\n\n")
700 fp.write("\n\n")
699
701
700 for chunk in patch.diff(repo, prev, node, opts=opts):
702 for chunk in patch.diff(repo, prev, node, opts=opts):
701 fp.write(chunk)
703 fp.write(chunk)
702
704
703 fp.flush()
705 if shouldclose:
706 fp.close()
704
707
705 for seqno, rev in enumerate(revs):
708 for seqno, rev in enumerate(revs):
706 single(rev, seqno + 1, fp)
709 single(rev, seqno + 1, fp)
707
710
708 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
711 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
709 changes=None, stat=False, fp=None, prefix='',
712 changes=None, stat=False, fp=None, prefix='',
710 listsubrepos=False):
713 listsubrepos=False):
711 '''show diff or diffstat.'''
714 '''show diff or diffstat.'''
712 if fp is None:
715 if fp is None:
713 write = ui.write
716 write = ui.write
714 else:
717 else:
715 def write(s, **kw):
718 def write(s, **kw):
716 fp.write(s)
719 fp.write(s)
717
720
718 if stat:
721 if stat:
719 diffopts = diffopts.copy(context=0)
722 diffopts = diffopts.copy(context=0)
720 width = 80
723 width = 80
721 if not ui.plain():
724 if not ui.plain():
722 width = ui.termwidth()
725 width = ui.termwidth()
723 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
726 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
724 prefix=prefix)
727 prefix=prefix)
725 for chunk, label in patch.diffstatui(util.iterlines(chunks),
728 for chunk, label in patch.diffstatui(util.iterlines(chunks),
726 width=width,
729 width=width,
727 git=diffopts.git):
730 git=diffopts.git):
728 write(chunk, label=label)
731 write(chunk, label=label)
729 else:
732 else:
730 for chunk, label in patch.diffui(repo, node1, node2, match,
733 for chunk, label in patch.diffui(repo, node1, node2, match,
731 changes, diffopts, prefix=prefix):
734 changes, diffopts, prefix=prefix):
732 write(chunk, label=label)
735 write(chunk, label=label)
733
736
734 if listsubrepos:
737 if listsubrepos:
735 ctx1 = repo[node1]
738 ctx1 = repo[node1]
736 ctx2 = repo[node2]
739 ctx2 = repo[node2]
737 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
740 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
738 if node2 is not None:
741 if node2 is not None:
739 node2 = ctx2.substate[subpath][1]
742 node2 = ctx2.substate[subpath][1]
740 submatch = matchmod.narrowmatcher(subpath, match)
743 submatch = matchmod.narrowmatcher(subpath, match)
741 sub.diff(diffopts, node2, submatch, changes=changes,
744 sub.diff(diffopts, node2, submatch, changes=changes,
742 stat=stat, fp=fp, prefix=prefix)
745 stat=stat, fp=fp, prefix=prefix)
743
746
744 class changeset_printer(object):
747 class changeset_printer(object):
745 '''show changeset information when templating not requested.'''
748 '''show changeset information when templating not requested.'''
746
749
747 def __init__(self, ui, repo, patch, diffopts, buffered):
750 def __init__(self, ui, repo, patch, diffopts, buffered):
748 self.ui = ui
751 self.ui = ui
749 self.repo = repo
752 self.repo = repo
750 self.buffered = buffered
753 self.buffered = buffered
751 self.patch = patch
754 self.patch = patch
752 self.diffopts = diffopts
755 self.diffopts = diffopts
753 self.header = {}
756 self.header = {}
754 self.hunk = {}
757 self.hunk = {}
755 self.lastheader = None
758 self.lastheader = None
756 self.footer = None
759 self.footer = None
757
760
758 def flush(self, rev):
761 def flush(self, rev):
759 if rev in self.header:
762 if rev in self.header:
760 h = self.header[rev]
763 h = self.header[rev]
761 if h != self.lastheader:
764 if h != self.lastheader:
762 self.lastheader = h
765 self.lastheader = h
763 self.ui.write(h)
766 self.ui.write(h)
764 del self.header[rev]
767 del self.header[rev]
765 if rev in self.hunk:
768 if rev in self.hunk:
766 self.ui.write(self.hunk[rev])
769 self.ui.write(self.hunk[rev])
767 del self.hunk[rev]
770 del self.hunk[rev]
768 return 1
771 return 1
769 return 0
772 return 0
770
773
771 def close(self):
774 def close(self):
772 if self.footer:
775 if self.footer:
773 self.ui.write(self.footer)
776 self.ui.write(self.footer)
774
777
775 def show(self, ctx, copies=None, matchfn=None, **props):
778 def show(self, ctx, copies=None, matchfn=None, **props):
776 if self.buffered:
779 if self.buffered:
777 self.ui.pushbuffer()
780 self.ui.pushbuffer()
778 self._show(ctx, copies, matchfn, props)
781 self._show(ctx, copies, matchfn, props)
779 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
782 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
780 else:
783 else:
781 self._show(ctx, copies, matchfn, props)
784 self._show(ctx, copies, matchfn, props)
782
785
783 def _show(self, ctx, copies, matchfn, props):
786 def _show(self, ctx, copies, matchfn, props):
784 '''show a single changeset or file revision'''
787 '''show a single changeset or file revision'''
785 changenode = ctx.node()
788 changenode = ctx.node()
786 rev = ctx.rev()
789 rev = ctx.rev()
787
790
788 if self.ui.quiet:
791 if self.ui.quiet:
789 self.ui.write("%d:%s\n" % (rev, short(changenode)),
792 self.ui.write("%d:%s\n" % (rev, short(changenode)),
790 label='log.node')
793 label='log.node')
791 return
794 return
792
795
793 log = self.repo.changelog
796 log = self.repo.changelog
794 date = util.datestr(ctx.date())
797 date = util.datestr(ctx.date())
795
798
796 hexfunc = self.ui.debugflag and hex or short
799 hexfunc = self.ui.debugflag and hex or short
797
800
798 parents = [(p, hexfunc(log.node(p)))
801 parents = [(p, hexfunc(log.node(p)))
799 for p in self._meaningful_parentrevs(log, rev)]
802 for p in self._meaningful_parentrevs(log, rev)]
800
803
801 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
804 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
802 label='log.changeset')
805 label='log.changeset')
803
806
804 branch = ctx.branch()
807 branch = ctx.branch()
805 # don't show the default branch name
808 # don't show the default branch name
806 if branch != 'default':
809 if branch != 'default':
807 self.ui.write(_("branch: %s\n") % branch,
810 self.ui.write(_("branch: %s\n") % branch,
808 label='log.branch')
811 label='log.branch')
809 for bookmark in self.repo.nodebookmarks(changenode):
812 for bookmark in self.repo.nodebookmarks(changenode):
810 self.ui.write(_("bookmark: %s\n") % bookmark,
813 self.ui.write(_("bookmark: %s\n") % bookmark,
811 label='log.bookmark')
814 label='log.bookmark')
812 for tag in self.repo.nodetags(changenode):
815 for tag in self.repo.nodetags(changenode):
813 self.ui.write(_("tag: %s\n") % tag,
816 self.ui.write(_("tag: %s\n") % tag,
814 label='log.tag')
817 label='log.tag')
815 for parent in parents:
818 for parent in parents:
816 self.ui.write(_("parent: %d:%s\n") % parent,
819 self.ui.write(_("parent: %d:%s\n") % parent,
817 label='log.parent')
820 label='log.parent')
818
821
819 if self.ui.debugflag:
822 if self.ui.debugflag:
820 mnode = ctx.manifestnode()
823 mnode = ctx.manifestnode()
821 self.ui.write(_("manifest: %d:%s\n") %
824 self.ui.write(_("manifest: %d:%s\n") %
822 (self.repo.manifest.rev(mnode), hex(mnode)),
825 (self.repo.manifest.rev(mnode), hex(mnode)),
823 label='ui.debug log.manifest')
826 label='ui.debug log.manifest')
824 self.ui.write(_("user: %s\n") % ctx.user(),
827 self.ui.write(_("user: %s\n") % ctx.user(),
825 label='log.user')
828 label='log.user')
826 self.ui.write(_("date: %s\n") % date,
829 self.ui.write(_("date: %s\n") % date,
827 label='log.date')
830 label='log.date')
828
831
829 if self.ui.debugflag:
832 if self.ui.debugflag:
830 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
833 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
831 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
834 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
832 files):
835 files):
833 if value:
836 if value:
834 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
837 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
835 label='ui.debug log.files')
838 label='ui.debug log.files')
836 elif ctx.files() and self.ui.verbose:
839 elif ctx.files() and self.ui.verbose:
837 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
840 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
838 label='ui.note log.files')
841 label='ui.note log.files')
839 if copies and self.ui.verbose:
842 if copies and self.ui.verbose:
840 copies = ['%s (%s)' % c for c in copies]
843 copies = ['%s (%s)' % c for c in copies]
841 self.ui.write(_("copies: %s\n") % ' '.join(copies),
844 self.ui.write(_("copies: %s\n") % ' '.join(copies),
842 label='ui.note log.copies')
845 label='ui.note log.copies')
843
846
844 extra = ctx.extra()
847 extra = ctx.extra()
845 if extra and self.ui.debugflag:
848 if extra and self.ui.debugflag:
846 for key, value in sorted(extra.items()):
849 for key, value in sorted(extra.items()):
847 self.ui.write(_("extra: %s=%s\n")
850 self.ui.write(_("extra: %s=%s\n")
848 % (key, value.encode('string_escape')),
851 % (key, value.encode('string_escape')),
849 label='ui.debug log.extra')
852 label='ui.debug log.extra')
850
853
851 description = ctx.description().strip()
854 description = ctx.description().strip()
852 if description:
855 if description:
853 if self.ui.verbose:
856 if self.ui.verbose:
854 self.ui.write(_("description:\n"),
857 self.ui.write(_("description:\n"),
855 label='ui.note log.description')
858 label='ui.note log.description')
856 self.ui.write(description,
859 self.ui.write(description,
857 label='ui.note log.description')
860 label='ui.note log.description')
858 self.ui.write("\n\n")
861 self.ui.write("\n\n")
859 else:
862 else:
860 self.ui.write(_("summary: %s\n") %
863 self.ui.write(_("summary: %s\n") %
861 description.splitlines()[0],
864 description.splitlines()[0],
862 label='log.summary')
865 label='log.summary')
863 self.ui.write("\n")
866 self.ui.write("\n")
864
867
865 self.showpatch(changenode, matchfn)
868 self.showpatch(changenode, matchfn)
866
869
867 def showpatch(self, node, matchfn):
870 def showpatch(self, node, matchfn):
868 if not matchfn:
871 if not matchfn:
869 matchfn = self.patch
872 matchfn = self.patch
870 if matchfn:
873 if matchfn:
871 stat = self.diffopts.get('stat')
874 stat = self.diffopts.get('stat')
872 diff = self.diffopts.get('patch')
875 diff = self.diffopts.get('patch')
873 diffopts = patch.diffopts(self.ui, self.diffopts)
876 diffopts = patch.diffopts(self.ui, self.diffopts)
874 prev = self.repo.changelog.parents(node)[0]
877 prev = self.repo.changelog.parents(node)[0]
875 if stat:
878 if stat:
876 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
879 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
877 match=matchfn, stat=True)
880 match=matchfn, stat=True)
878 if diff:
881 if diff:
879 if stat:
882 if stat:
880 self.ui.write("\n")
883 self.ui.write("\n")
881 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
884 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
882 match=matchfn, stat=False)
885 match=matchfn, stat=False)
883 self.ui.write("\n")
886 self.ui.write("\n")
884
887
885 def _meaningful_parentrevs(self, log, rev):
888 def _meaningful_parentrevs(self, log, rev):
886 """Return list of meaningful (or all if debug) parentrevs for rev.
889 """Return list of meaningful (or all if debug) parentrevs for rev.
887
890
888 For merges (two non-nullrev revisions) both parents are meaningful.
891 For merges (two non-nullrev revisions) both parents are meaningful.
889 Otherwise the first parent revision is considered meaningful if it
892 Otherwise the first parent revision is considered meaningful if it
890 is not the preceding revision.
893 is not the preceding revision.
891 """
894 """
892 parents = log.parentrevs(rev)
895 parents = log.parentrevs(rev)
893 if not self.ui.debugflag and parents[1] == nullrev:
896 if not self.ui.debugflag and parents[1] == nullrev:
894 if parents[0] >= rev - 1:
897 if parents[0] >= rev - 1:
895 parents = []
898 parents = []
896 else:
899 else:
897 parents = [parents[0]]
900 parents = [parents[0]]
898 return parents
901 return parents
899
902
900
903
901 class changeset_templater(changeset_printer):
904 class changeset_templater(changeset_printer):
902 '''format changeset information.'''
905 '''format changeset information.'''
903
906
904 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
907 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
905 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
908 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
906 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
909 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
907 defaulttempl = {
910 defaulttempl = {
908 'parent': '{rev}:{node|formatnode} ',
911 'parent': '{rev}:{node|formatnode} ',
909 'manifest': '{rev}:{node|formatnode}',
912 'manifest': '{rev}:{node|formatnode}',
910 'file_copy': '{name} ({source})',
913 'file_copy': '{name} ({source})',
911 'extra': '{key}={value|stringescape}'
914 'extra': '{key}={value|stringescape}'
912 }
915 }
913 # filecopy is preserved for compatibility reasons
916 # filecopy is preserved for compatibility reasons
914 defaulttempl['filecopy'] = defaulttempl['file_copy']
917 defaulttempl['filecopy'] = defaulttempl['file_copy']
915 self.t = templater.templater(mapfile, {'formatnode': formatnode},
918 self.t = templater.templater(mapfile, {'formatnode': formatnode},
916 cache=defaulttempl)
919 cache=defaulttempl)
917 self.cache = {}
920 self.cache = {}
918
921
919 def use_template(self, t):
922 def use_template(self, t):
920 '''set template string to use'''
923 '''set template string to use'''
921 self.t.cache['changeset'] = t
924 self.t.cache['changeset'] = t
922
925
923 def _meaningful_parentrevs(self, ctx):
926 def _meaningful_parentrevs(self, ctx):
924 """Return list of meaningful (or all if debug) parentrevs for rev.
927 """Return list of meaningful (or all if debug) parentrevs for rev.
925 """
928 """
926 parents = ctx.parents()
929 parents = ctx.parents()
927 if len(parents) > 1:
930 if len(parents) > 1:
928 return parents
931 return parents
929 if self.ui.debugflag:
932 if self.ui.debugflag:
930 return [parents[0], self.repo['null']]
933 return [parents[0], self.repo['null']]
931 if parents[0].rev() >= ctx.rev() - 1:
934 if parents[0].rev() >= ctx.rev() - 1:
932 return []
935 return []
933 return parents
936 return parents
934
937
935 def _show(self, ctx, copies, matchfn, props):
938 def _show(self, ctx, copies, matchfn, props):
936 '''show a single changeset or file revision'''
939 '''show a single changeset or file revision'''
937
940
938 showlist = templatekw.showlist
941 showlist = templatekw.showlist
939
942
940 # showparents() behaviour depends on ui trace level which
943 # showparents() behaviour depends on ui trace level which
941 # causes unexpected behaviours at templating level and makes
944 # causes unexpected behaviours at templating level and makes
942 # it harder to extract it in a standalone function. Its
945 # it harder to extract it in a standalone function. Its
943 # behaviour cannot be changed so leave it here for now.
946 # behaviour cannot be changed so leave it here for now.
944 def showparents(**args):
947 def showparents(**args):
945 ctx = args['ctx']
948 ctx = args['ctx']
946 parents = [[('rev', p.rev()), ('node', p.hex())]
949 parents = [[('rev', p.rev()), ('node', p.hex())]
947 for p in self._meaningful_parentrevs(ctx)]
950 for p in self._meaningful_parentrevs(ctx)]
948 return showlist('parent', parents, **args)
951 return showlist('parent', parents, **args)
949
952
950 props = props.copy()
953 props = props.copy()
951 props.update(templatekw.keywords)
954 props.update(templatekw.keywords)
952 props['parents'] = showparents
955 props['parents'] = showparents
953 props['templ'] = self.t
956 props['templ'] = self.t
954 props['ctx'] = ctx
957 props['ctx'] = ctx
955 props['repo'] = self.repo
958 props['repo'] = self.repo
956 props['revcache'] = {'copies': copies}
959 props['revcache'] = {'copies': copies}
957 props['cache'] = self.cache
960 props['cache'] = self.cache
958
961
959 # find correct templates for current mode
962 # find correct templates for current mode
960
963
961 tmplmodes = [
964 tmplmodes = [
962 (True, None),
965 (True, None),
963 (self.ui.verbose, 'verbose'),
966 (self.ui.verbose, 'verbose'),
964 (self.ui.quiet, 'quiet'),
967 (self.ui.quiet, 'quiet'),
965 (self.ui.debugflag, 'debug'),
968 (self.ui.debugflag, 'debug'),
966 ]
969 ]
967
970
968 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
971 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
969 for mode, postfix in tmplmodes:
972 for mode, postfix in tmplmodes:
970 for type in types:
973 for type in types:
971 cur = postfix and ('%s_%s' % (type, postfix)) or type
974 cur = postfix and ('%s_%s' % (type, postfix)) or type
972 if mode and cur in self.t:
975 if mode and cur in self.t:
973 types[type] = cur
976 types[type] = cur
974
977
975 try:
978 try:
976
979
977 # write header
980 # write header
978 if types['header']:
981 if types['header']:
979 h = templater.stringify(self.t(types['header'], **props))
982 h = templater.stringify(self.t(types['header'], **props))
980 if self.buffered:
983 if self.buffered:
981 self.header[ctx.rev()] = h
984 self.header[ctx.rev()] = h
982 else:
985 else:
983 if self.lastheader != h:
986 if self.lastheader != h:
984 self.lastheader = h
987 self.lastheader = h
985 self.ui.write(h)
988 self.ui.write(h)
986
989
987 # write changeset metadata, then patch if requested
990 # write changeset metadata, then patch if requested
988 key = types['changeset']
991 key = types['changeset']
989 self.ui.write(templater.stringify(self.t(key, **props)))
992 self.ui.write(templater.stringify(self.t(key, **props)))
990 self.showpatch(ctx.node(), matchfn)
993 self.showpatch(ctx.node(), matchfn)
991
994
992 if types['footer']:
995 if types['footer']:
993 if not self.footer:
996 if not self.footer:
994 self.footer = templater.stringify(self.t(types['footer'],
997 self.footer = templater.stringify(self.t(types['footer'],
995 **props))
998 **props))
996
999
997 except KeyError, inst:
1000 except KeyError, inst:
998 msg = _("%s: no key named '%s'")
1001 msg = _("%s: no key named '%s'")
999 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1002 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1000 except SyntaxError, inst:
1003 except SyntaxError, inst:
1001 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1004 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1002
1005
1003 def show_changeset(ui, repo, opts, buffered=False):
1006 def show_changeset(ui, repo, opts, buffered=False):
1004 """show one changeset using template or regular display.
1007 """show one changeset using template or regular display.
1005
1008
1006 Display format will be the first non-empty hit of:
1009 Display format will be the first non-empty hit of:
1007 1. option 'template'
1010 1. option 'template'
1008 2. option 'style'
1011 2. option 'style'
1009 3. [ui] setting 'logtemplate'
1012 3. [ui] setting 'logtemplate'
1010 4. [ui] setting 'style'
1013 4. [ui] setting 'style'
1011 If all of these values are either the unset or the empty string,
1014 If all of these values are either the unset or the empty string,
1012 regular display via changeset_printer() is done.
1015 regular display via changeset_printer() is done.
1013 """
1016 """
1014 # options
1017 # options
1015 patch = False
1018 patch = False
1016 if opts.get('patch') or opts.get('stat'):
1019 if opts.get('patch') or opts.get('stat'):
1017 patch = matchall(repo)
1020 patch = matchall(repo)
1018
1021
1019 tmpl = opts.get('template')
1022 tmpl = opts.get('template')
1020 style = None
1023 style = None
1021 if tmpl:
1024 if tmpl:
1022 tmpl = templater.parsestring(tmpl, quoted=False)
1025 tmpl = templater.parsestring(tmpl, quoted=False)
1023 else:
1026 else:
1024 style = opts.get('style')
1027 style = opts.get('style')
1025
1028
1026 # ui settings
1029 # ui settings
1027 if not (tmpl or style):
1030 if not (tmpl or style):
1028 tmpl = ui.config('ui', 'logtemplate')
1031 tmpl = ui.config('ui', 'logtemplate')
1029 if tmpl:
1032 if tmpl:
1030 tmpl = templater.parsestring(tmpl)
1033 tmpl = templater.parsestring(tmpl)
1031 else:
1034 else:
1032 style = util.expandpath(ui.config('ui', 'style', ''))
1035 style = util.expandpath(ui.config('ui', 'style', ''))
1033
1036
1034 if not (tmpl or style):
1037 if not (tmpl or style):
1035 return changeset_printer(ui, repo, patch, opts, buffered)
1038 return changeset_printer(ui, repo, patch, opts, buffered)
1036
1039
1037 mapfile = None
1040 mapfile = None
1038 if style and not tmpl:
1041 if style and not tmpl:
1039 mapfile = style
1042 mapfile = style
1040 if not os.path.split(mapfile)[0]:
1043 if not os.path.split(mapfile)[0]:
1041 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1044 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1042 or templater.templatepath(mapfile))
1045 or templater.templatepath(mapfile))
1043 if mapname:
1046 if mapname:
1044 mapfile = mapname
1047 mapfile = mapname
1045
1048
1046 try:
1049 try:
1047 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1050 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1048 except SyntaxError, inst:
1051 except SyntaxError, inst:
1049 raise util.Abort(inst.args[0])
1052 raise util.Abort(inst.args[0])
1050 if tmpl:
1053 if tmpl:
1051 t.use_template(tmpl)
1054 t.use_template(tmpl)
1052 return t
1055 return t
1053
1056
1054 def finddate(ui, repo, date):
1057 def finddate(ui, repo, date):
1055 """Find the tipmost changeset that matches the given date spec"""
1058 """Find the tipmost changeset that matches the given date spec"""
1056
1059
1057 df = util.matchdate(date)
1060 df = util.matchdate(date)
1058 m = matchall(repo)
1061 m = matchall(repo)
1059 results = {}
1062 results = {}
1060
1063
1061 def prep(ctx, fns):
1064 def prep(ctx, fns):
1062 d = ctx.date()
1065 d = ctx.date()
1063 if df(d[0]):
1066 if df(d[0]):
1064 results[ctx.rev()] = d
1067 results[ctx.rev()] = d
1065
1068
1066 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1069 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1067 rev = ctx.rev()
1070 rev = ctx.rev()
1068 if rev in results:
1071 if rev in results:
1069 ui.status(_("Found revision %s from %s\n") %
1072 ui.status(_("Found revision %s from %s\n") %
1070 (rev, util.datestr(results[rev])))
1073 (rev, util.datestr(results[rev])))
1071 return str(rev)
1074 return str(rev)
1072
1075
1073 raise util.Abort(_("revision matching date not found"))
1076 raise util.Abort(_("revision matching date not found"))
1074
1077
1075 def walkchangerevs(repo, match, opts, prepare):
1078 def walkchangerevs(repo, match, opts, prepare):
1076 '''Iterate over files and the revs in which they changed.
1079 '''Iterate over files and the revs in which they changed.
1077
1080
1078 Callers most commonly need to iterate backwards over the history
1081 Callers most commonly need to iterate backwards over the history
1079 in which they are interested. Doing so has awful (quadratic-looking)
1082 in which they are interested. Doing so has awful (quadratic-looking)
1080 performance, so we use iterators in a "windowed" way.
1083 performance, so we use iterators in a "windowed" way.
1081
1084
1082 We walk a window of revisions in the desired order. Within the
1085 We walk a window of revisions in the desired order. Within the
1083 window, we first walk forwards to gather data, then in the desired
1086 window, we first walk forwards to gather data, then in the desired
1084 order (usually backwards) to display it.
1087 order (usually backwards) to display it.
1085
1088
1086 This function returns an iterator yielding contexts. Before
1089 This function returns an iterator yielding contexts. Before
1087 yielding each context, the iterator will first call the prepare
1090 yielding each context, the iterator will first call the prepare
1088 function on each context in the window in forward order.'''
1091 function on each context in the window in forward order.'''
1089
1092
1090 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1093 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1091 if start < end:
1094 if start < end:
1092 while start < end:
1095 while start < end:
1093 yield start, min(windowsize, end - start)
1096 yield start, min(windowsize, end - start)
1094 start += windowsize
1097 start += windowsize
1095 if windowsize < sizelimit:
1098 if windowsize < sizelimit:
1096 windowsize *= 2
1099 windowsize *= 2
1097 else:
1100 else:
1098 while start > end:
1101 while start > end:
1099 yield start, min(windowsize, start - end - 1)
1102 yield start, min(windowsize, start - end - 1)
1100 start -= windowsize
1103 start -= windowsize
1101 if windowsize < sizelimit:
1104 if windowsize < sizelimit:
1102 windowsize *= 2
1105 windowsize *= 2
1103
1106
1104 follow = opts.get('follow') or opts.get('follow_first')
1107 follow = opts.get('follow') or opts.get('follow_first')
1105
1108
1106 if not len(repo):
1109 if not len(repo):
1107 return []
1110 return []
1108
1111
1109 if follow:
1112 if follow:
1110 defrange = '%s:0' % repo['.'].rev()
1113 defrange = '%s:0' % repo['.'].rev()
1111 else:
1114 else:
1112 defrange = '-1:0'
1115 defrange = '-1:0'
1113 revs = revrange(repo, opts['rev'] or [defrange])
1116 revs = revrange(repo, opts['rev'] or [defrange])
1114 if not revs:
1117 if not revs:
1115 return []
1118 return []
1116 wanted = set()
1119 wanted = set()
1117 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1120 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1118 fncache = {}
1121 fncache = {}
1119 change = util.cachefunc(repo.changectx)
1122 change = util.cachefunc(repo.changectx)
1120
1123
1121 # First step is to fill wanted, the set of revisions that we want to yield.
1124 # First step is to fill wanted, the set of revisions that we want to yield.
1122 # When it does not induce extra cost, we also fill fncache for revisions in
1125 # When it does not induce extra cost, we also fill fncache for revisions in
1123 # wanted: a cache of filenames that were changed (ctx.files()) and that
1126 # wanted: a cache of filenames that were changed (ctx.files()) and that
1124 # match the file filtering conditions.
1127 # match the file filtering conditions.
1125
1128
1126 if not slowpath and not match.files():
1129 if not slowpath and not match.files():
1127 # No files, no patterns. Display all revs.
1130 # No files, no patterns. Display all revs.
1128 wanted = set(revs)
1131 wanted = set(revs)
1129 copies = []
1132 copies = []
1130
1133
1131 if not slowpath:
1134 if not slowpath:
1132 # We only have to read through the filelog to find wanted revisions
1135 # We only have to read through the filelog to find wanted revisions
1133
1136
1134 minrev, maxrev = min(revs), max(revs)
1137 minrev, maxrev = min(revs), max(revs)
1135 def filerevgen(filelog, last):
1138 def filerevgen(filelog, last):
1136 """
1139 """
1137 Only files, no patterns. Check the history of each file.
1140 Only files, no patterns. Check the history of each file.
1138
1141
1139 Examines filelog entries within minrev, maxrev linkrev range
1142 Examines filelog entries within minrev, maxrev linkrev range
1140 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1143 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1141 tuples in backwards order
1144 tuples in backwards order
1142 """
1145 """
1143 cl_count = len(repo)
1146 cl_count = len(repo)
1144 revs = []
1147 revs = []
1145 for j in xrange(0, last + 1):
1148 for j in xrange(0, last + 1):
1146 linkrev = filelog.linkrev(j)
1149 linkrev = filelog.linkrev(j)
1147 if linkrev < minrev:
1150 if linkrev < minrev:
1148 continue
1151 continue
1149 # only yield rev for which we have the changelog, it can
1152 # only yield rev for which we have the changelog, it can
1150 # happen while doing "hg log" during a pull or commit
1153 # happen while doing "hg log" during a pull or commit
1151 if linkrev >= cl_count:
1154 if linkrev >= cl_count:
1152 break
1155 break
1153
1156
1154 parentlinkrevs = []
1157 parentlinkrevs = []
1155 for p in filelog.parentrevs(j):
1158 for p in filelog.parentrevs(j):
1156 if p != nullrev:
1159 if p != nullrev:
1157 parentlinkrevs.append(filelog.linkrev(p))
1160 parentlinkrevs.append(filelog.linkrev(p))
1158 n = filelog.node(j)
1161 n = filelog.node(j)
1159 revs.append((linkrev, parentlinkrevs,
1162 revs.append((linkrev, parentlinkrevs,
1160 follow and filelog.renamed(n)))
1163 follow and filelog.renamed(n)))
1161
1164
1162 return reversed(revs)
1165 return reversed(revs)
1163 def iterfiles():
1166 def iterfiles():
1164 for filename in match.files():
1167 for filename in match.files():
1165 yield filename, None
1168 yield filename, None
1166 for filename_node in copies:
1169 for filename_node in copies:
1167 yield filename_node
1170 yield filename_node
1168 for file_, node in iterfiles():
1171 for file_, node in iterfiles():
1169 filelog = repo.file(file_)
1172 filelog = repo.file(file_)
1170 if not len(filelog):
1173 if not len(filelog):
1171 if node is None:
1174 if node is None:
1172 # A zero count may be a directory or deleted file, so
1175 # A zero count may be a directory or deleted file, so
1173 # try to find matching entries on the slow path.
1176 # try to find matching entries on the slow path.
1174 if follow:
1177 if follow:
1175 raise util.Abort(
1178 raise util.Abort(
1176 _('cannot follow nonexistent file: "%s"') % file_)
1179 _('cannot follow nonexistent file: "%s"') % file_)
1177 slowpath = True
1180 slowpath = True
1178 break
1181 break
1179 else:
1182 else:
1180 continue
1183 continue
1181
1184
1182 if node is None:
1185 if node is None:
1183 last = len(filelog) - 1
1186 last = len(filelog) - 1
1184 else:
1187 else:
1185 last = filelog.rev(node)
1188 last = filelog.rev(node)
1186
1189
1187
1190
1188 # keep track of all ancestors of the file
1191 # keep track of all ancestors of the file
1189 ancestors = set([filelog.linkrev(last)])
1192 ancestors = set([filelog.linkrev(last)])
1190
1193
1191 # iterate from latest to oldest revision
1194 # iterate from latest to oldest revision
1192 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1195 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1193 if not follow:
1196 if not follow:
1194 if rev > maxrev:
1197 if rev > maxrev:
1195 continue
1198 continue
1196 else:
1199 else:
1197 # Note that last might not be the first interesting
1200 # Note that last might not be the first interesting
1198 # rev to us:
1201 # rev to us:
1199 # if the file has been changed after maxrev, we'll
1202 # if the file has been changed after maxrev, we'll
1200 # have linkrev(last) > maxrev, and we still need
1203 # have linkrev(last) > maxrev, and we still need
1201 # to explore the file graph
1204 # to explore the file graph
1202 if rev not in ancestors:
1205 if rev not in ancestors:
1203 continue
1206 continue
1204 # XXX insert 1327 fix here
1207 # XXX insert 1327 fix here
1205 if flparentlinkrevs:
1208 if flparentlinkrevs:
1206 ancestors.update(flparentlinkrevs)
1209 ancestors.update(flparentlinkrevs)
1207
1210
1208 fncache.setdefault(rev, []).append(file_)
1211 fncache.setdefault(rev, []).append(file_)
1209 wanted.add(rev)
1212 wanted.add(rev)
1210 if copied:
1213 if copied:
1211 copies.append(copied)
1214 copies.append(copied)
1212 if slowpath:
1215 if slowpath:
1213 # We have to read the changelog to match filenames against
1216 # We have to read the changelog to match filenames against
1214 # changed files
1217 # changed files
1215
1218
1216 if follow:
1219 if follow:
1217 raise util.Abort(_('can only follow copies/renames for explicit '
1220 raise util.Abort(_('can only follow copies/renames for explicit '
1218 'filenames'))
1221 'filenames'))
1219
1222
1220 # The slow path checks files modified in every changeset.
1223 # The slow path checks files modified in every changeset.
1221 for i in sorted(revs):
1224 for i in sorted(revs):
1222 ctx = change(i)
1225 ctx = change(i)
1223 matches = filter(match, ctx.files())
1226 matches = filter(match, ctx.files())
1224 if matches:
1227 if matches:
1225 fncache[i] = matches
1228 fncache[i] = matches
1226 wanted.add(i)
1229 wanted.add(i)
1227
1230
1228 class followfilter(object):
1231 class followfilter(object):
1229 def __init__(self, onlyfirst=False):
1232 def __init__(self, onlyfirst=False):
1230 self.startrev = nullrev
1233 self.startrev = nullrev
1231 self.roots = set()
1234 self.roots = set()
1232 self.onlyfirst = onlyfirst
1235 self.onlyfirst = onlyfirst
1233
1236
1234 def match(self, rev):
1237 def match(self, rev):
1235 def realparents(rev):
1238 def realparents(rev):
1236 if self.onlyfirst:
1239 if self.onlyfirst:
1237 return repo.changelog.parentrevs(rev)[0:1]
1240 return repo.changelog.parentrevs(rev)[0:1]
1238 else:
1241 else:
1239 return filter(lambda x: x != nullrev,
1242 return filter(lambda x: x != nullrev,
1240 repo.changelog.parentrevs(rev))
1243 repo.changelog.parentrevs(rev))
1241
1244
1242 if self.startrev == nullrev:
1245 if self.startrev == nullrev:
1243 self.startrev = rev
1246 self.startrev = rev
1244 return True
1247 return True
1245
1248
1246 if rev > self.startrev:
1249 if rev > self.startrev:
1247 # forward: all descendants
1250 # forward: all descendants
1248 if not self.roots:
1251 if not self.roots:
1249 self.roots.add(self.startrev)
1252 self.roots.add(self.startrev)
1250 for parent in realparents(rev):
1253 for parent in realparents(rev):
1251 if parent in self.roots:
1254 if parent in self.roots:
1252 self.roots.add(rev)
1255 self.roots.add(rev)
1253 return True
1256 return True
1254 else:
1257 else:
1255 # backwards: all parents
1258 # backwards: all parents
1256 if not self.roots:
1259 if not self.roots:
1257 self.roots.update(realparents(self.startrev))
1260 self.roots.update(realparents(self.startrev))
1258 if rev in self.roots:
1261 if rev in self.roots:
1259 self.roots.remove(rev)
1262 self.roots.remove(rev)
1260 self.roots.update(realparents(rev))
1263 self.roots.update(realparents(rev))
1261 return True
1264 return True
1262
1265
1263 return False
1266 return False
1264
1267
1265 # it might be worthwhile to do this in the iterator if the rev range
1268 # it might be worthwhile to do this in the iterator if the rev range
1266 # is descending and the prune args are all within that range
1269 # is descending and the prune args are all within that range
1267 for rev in opts.get('prune', ()):
1270 for rev in opts.get('prune', ()):
1268 rev = repo.changelog.rev(repo.lookup(rev))
1271 rev = repo.changelog.rev(repo.lookup(rev))
1269 ff = followfilter()
1272 ff = followfilter()
1270 stop = min(revs[0], revs[-1])
1273 stop = min(revs[0], revs[-1])
1271 for x in xrange(rev, stop - 1, -1):
1274 for x in xrange(rev, stop - 1, -1):
1272 if ff.match(x):
1275 if ff.match(x):
1273 wanted.discard(x)
1276 wanted.discard(x)
1274
1277
1275 # Now that wanted is correctly initialized, we can iterate over the
1278 # Now that wanted is correctly initialized, we can iterate over the
1276 # revision range, yielding only revisions in wanted.
1279 # revision range, yielding only revisions in wanted.
1277 def iterate():
1280 def iterate():
1278 if follow and not match.files():
1281 if follow and not match.files():
1279 ff = followfilter(onlyfirst=opts.get('follow_first'))
1282 ff = followfilter(onlyfirst=opts.get('follow_first'))
1280 def want(rev):
1283 def want(rev):
1281 return ff.match(rev) and rev in wanted
1284 return ff.match(rev) and rev in wanted
1282 else:
1285 else:
1283 def want(rev):
1286 def want(rev):
1284 return rev in wanted
1287 return rev in wanted
1285
1288
1286 for i, window in increasing_windows(0, len(revs)):
1289 for i, window in increasing_windows(0, len(revs)):
1287 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1290 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1288 for rev in sorted(nrevs):
1291 for rev in sorted(nrevs):
1289 fns = fncache.get(rev)
1292 fns = fncache.get(rev)
1290 ctx = change(rev)
1293 ctx = change(rev)
1291 if not fns:
1294 if not fns:
1292 def fns_generator():
1295 def fns_generator():
1293 for f in ctx.files():
1296 for f in ctx.files():
1294 if match(f):
1297 if match(f):
1295 yield f
1298 yield f
1296 fns = fns_generator()
1299 fns = fns_generator()
1297 prepare(ctx, fns)
1300 prepare(ctx, fns)
1298 for rev in nrevs:
1301 for rev in nrevs:
1299 yield change(rev)
1302 yield change(rev)
1300 return iterate()
1303 return iterate()
1301
1304
1302 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1305 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1303 join = lambda f: os.path.join(prefix, f)
1306 join = lambda f: os.path.join(prefix, f)
1304 bad = []
1307 bad = []
1305 oldbad = match.bad
1308 oldbad = match.bad
1306 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1309 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1307 names = []
1310 names = []
1308 wctx = repo[None]
1311 wctx = repo[None]
1309 for f in repo.walk(match):
1312 for f in repo.walk(match):
1310 exact = match.exact(f)
1313 exact = match.exact(f)
1311 if exact or f not in repo.dirstate:
1314 if exact or f not in repo.dirstate:
1312 names.append(f)
1315 names.append(f)
1313 if ui.verbose or not exact:
1316 if ui.verbose or not exact:
1314 ui.status(_('adding %s\n') % match.rel(join(f)))
1317 ui.status(_('adding %s\n') % match.rel(join(f)))
1315
1318
1316 if listsubrepos:
1319 if listsubrepos:
1317 for subpath in wctx.substate:
1320 for subpath in wctx.substate:
1318 sub = wctx.sub(subpath)
1321 sub = wctx.sub(subpath)
1319 try:
1322 try:
1320 submatch = matchmod.narrowmatcher(subpath, match)
1323 submatch = matchmod.narrowmatcher(subpath, match)
1321 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1324 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1322 except error.LookupError:
1325 except error.LookupError:
1323 ui.status(_("skipping missing subrepository: %s\n")
1326 ui.status(_("skipping missing subrepository: %s\n")
1324 % join(subpath))
1327 % join(subpath))
1325
1328
1326 if not dryrun:
1329 if not dryrun:
1327 rejected = wctx.add(names, prefix)
1330 rejected = wctx.add(names, prefix)
1328 bad.extend(f for f in rejected if f in match.files())
1331 bad.extend(f for f in rejected if f in match.files())
1329 return bad
1332 return bad
1330
1333
1331 def commit(ui, repo, commitfunc, pats, opts):
1334 def commit(ui, repo, commitfunc, pats, opts):
1332 '''commit the specified files or all outstanding changes'''
1335 '''commit the specified files or all outstanding changes'''
1333 date = opts.get('date')
1336 date = opts.get('date')
1334 if date:
1337 if date:
1335 opts['date'] = util.parsedate(date)
1338 opts['date'] = util.parsedate(date)
1336 message = logmessage(opts)
1339 message = logmessage(opts)
1337
1340
1338 # extract addremove carefully -- this function can be called from a command
1341 # extract addremove carefully -- this function can be called from a command
1339 # that doesn't support addremove
1342 # that doesn't support addremove
1340 if opts.get('addremove'):
1343 if opts.get('addremove'):
1341 addremove(repo, pats, opts)
1344 addremove(repo, pats, opts)
1342
1345
1343 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1346 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1344
1347
1345 def commiteditor(repo, ctx, subs):
1348 def commiteditor(repo, ctx, subs):
1346 if ctx.description():
1349 if ctx.description():
1347 return ctx.description()
1350 return ctx.description()
1348 return commitforceeditor(repo, ctx, subs)
1351 return commitforceeditor(repo, ctx, subs)
1349
1352
1350 def commitforceeditor(repo, ctx, subs):
1353 def commitforceeditor(repo, ctx, subs):
1351 edittext = []
1354 edittext = []
1352 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1355 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1353 if ctx.description():
1356 if ctx.description():
1354 edittext.append(ctx.description())
1357 edittext.append(ctx.description())
1355 edittext.append("")
1358 edittext.append("")
1356 edittext.append("") # Empty line between message and comments.
1359 edittext.append("") # Empty line between message and comments.
1357 edittext.append(_("HG: Enter commit message."
1360 edittext.append(_("HG: Enter commit message."
1358 " Lines beginning with 'HG:' are removed."))
1361 " Lines beginning with 'HG:' are removed."))
1359 edittext.append(_("HG: Leave message empty to abort commit."))
1362 edittext.append(_("HG: Leave message empty to abort commit."))
1360 edittext.append("HG: --")
1363 edittext.append("HG: --")
1361 edittext.append(_("HG: user: %s") % ctx.user())
1364 edittext.append(_("HG: user: %s") % ctx.user())
1362 if ctx.p2():
1365 if ctx.p2():
1363 edittext.append(_("HG: branch merge"))
1366 edittext.append(_("HG: branch merge"))
1364 if ctx.branch():
1367 if ctx.branch():
1365 edittext.append(_("HG: branch '%s'") % ctx.branch())
1368 edittext.append(_("HG: branch '%s'") % ctx.branch())
1366 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1369 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1367 edittext.extend([_("HG: added %s") % f for f in added])
1370 edittext.extend([_("HG: added %s") % f for f in added])
1368 edittext.extend([_("HG: changed %s") % f for f in modified])
1371 edittext.extend([_("HG: changed %s") % f for f in modified])
1369 edittext.extend([_("HG: removed %s") % f for f in removed])
1372 edittext.extend([_("HG: removed %s") % f for f in removed])
1370 if not added and not modified and not removed:
1373 if not added and not modified and not removed:
1371 edittext.append(_("HG: no files changed"))
1374 edittext.append(_("HG: no files changed"))
1372 edittext.append("")
1375 edittext.append("")
1373 # run editor in the repository root
1376 # run editor in the repository root
1374 olddir = os.getcwd()
1377 olddir = os.getcwd()
1375 os.chdir(repo.root)
1378 os.chdir(repo.root)
1376 text = repo.ui.edit("\n".join(edittext), ctx.user())
1379 text = repo.ui.edit("\n".join(edittext), ctx.user())
1377 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1380 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1378 os.chdir(olddir)
1381 os.chdir(olddir)
1379
1382
1380 if not text.strip():
1383 if not text.strip():
1381 raise util.Abort(_("empty commit message"))
1384 raise util.Abort(_("empty commit message"))
1382
1385
1383 return text
1386 return text
@@ -1,4720 +1,4721 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, util, revlog, extensions, copies, error, bookmarks
12 import hg, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, mdiff, url, encoding, templatekw, discovery
13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset
16 import minirst, revset
17 import dagparser
17 import dagparser
18
18
19 # Commands start here, listed alphabetically
19 # Commands start here, listed alphabetically
20
20
21 def add(ui, repo, *pats, **opts):
21 def add(ui, repo, *pats, **opts):
22 """add the specified files on the next commit
22 """add the specified files on the next commit
23
23
24 Schedule files to be version controlled and added to the
24 Schedule files to be version controlled and added to the
25 repository.
25 repository.
26
26
27 The files will be added to the repository at the next commit. To
27 The files will be added to the repository at the next commit. To
28 undo an add before that, see :hg:`forget`.
28 undo an add before that, see :hg:`forget`.
29
29
30 If no names are given, add all files to the repository.
30 If no names are given, add all files to the repository.
31
31
32 .. container:: verbose
32 .. container:: verbose
33
33
34 An example showing how new (unknown) files are added
34 An example showing how new (unknown) files are added
35 automatically by :hg:`add`::
35 automatically by :hg:`add`::
36
36
37 $ ls
37 $ ls
38 foo.c
38 foo.c
39 $ hg status
39 $ hg status
40 ? foo.c
40 ? foo.c
41 $ hg add
41 $ hg add
42 adding foo.c
42 adding foo.c
43 $ hg status
43 $ hg status
44 A foo.c
44 A foo.c
45
45
46 Returns 0 if all files are successfully added.
46 Returns 0 if all files are successfully added.
47 """
47 """
48
48
49 m = cmdutil.match(repo, pats, opts)
49 m = cmdutil.match(repo, pats, opts)
50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 opts.get('subrepos'), prefix="")
51 opts.get('subrepos'), prefix="")
52 return rejected and 1 or 0
52 return rejected and 1 or 0
53
53
54 def addremove(ui, repo, *pats, **opts):
54 def addremove(ui, repo, *pats, **opts):
55 """add all new files, delete all missing files
55 """add all new files, delete all missing files
56
56
57 Add all new files and remove all missing files from the
57 Add all new files and remove all missing files from the
58 repository.
58 repository.
59
59
60 New files are ignored if they match any of the patterns in
60 New files are ignored if they match any of the patterns in
61 ``.hgignore``. As with add, these changes take effect at the next
61 ``.hgignore``. As with add, these changes take effect at the next
62 commit.
62 commit.
63
63
64 Use the -s/--similarity option to detect renamed files. With a
64 Use the -s/--similarity option to detect renamed files. With a
65 parameter greater than 0, this compares every removed file with
65 parameter greater than 0, this compares every removed file with
66 every added file and records those similar enough as renames. This
66 every added file and records those similar enough as renames. This
67 option takes a percentage between 0 (disabled) and 100 (files must
67 option takes a percentage between 0 (disabled) and 100 (files must
68 be identical) as its parameter. Detecting renamed files this way
68 be identical) as its parameter. Detecting renamed files this way
69 can be expensive. After using this option, :hg:`status -C` can be
69 can be expensive. After using this option, :hg:`status -C` can be
70 used to check which files were identified as moved or renamed.
70 used to check which files were identified as moved or renamed.
71
71
72 Returns 0 if all files are successfully added.
72 Returns 0 if all files are successfully added.
73 """
73 """
74 try:
74 try:
75 sim = float(opts.get('similarity') or 100)
75 sim = float(opts.get('similarity') or 100)
76 except ValueError:
76 except ValueError:
77 raise util.Abort(_('similarity must be a number'))
77 raise util.Abort(_('similarity must be a number'))
78 if sim < 0 or sim > 100:
78 if sim < 0 or sim > 100:
79 raise util.Abort(_('similarity must be between 0 and 100'))
79 raise util.Abort(_('similarity must be between 0 and 100'))
80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81
81
82 def annotate(ui, repo, *pats, **opts):
82 def annotate(ui, repo, *pats, **opts):
83 """show changeset information by line for each file
83 """show changeset information by line for each file
84
84
85 List changes in files, showing the revision id responsible for
85 List changes in files, showing the revision id responsible for
86 each line
86 each line
87
87
88 This command is useful for discovering when a change was made and
88 This command is useful for discovering when a change was made and
89 by whom.
89 by whom.
90
90
91 Without the -a/--text option, annotate will avoid processing files
91 Without the -a/--text option, annotate will avoid processing files
92 it detects as binary. With -a, annotate will annotate the file
92 it detects as binary. With -a, annotate will annotate the file
93 anyway, although the results will probably be neither useful
93 anyway, although the results will probably be neither useful
94 nor desirable.
94 nor desirable.
95
95
96 Returns 0 on success.
96 Returns 0 on success.
97 """
97 """
98 if opts.get('follow'):
98 if opts.get('follow'):
99 # --follow is deprecated and now just an alias for -f/--file
99 # --follow is deprecated and now just an alias for -f/--file
100 # to mimic the behavior of Mercurial before version 1.5
100 # to mimic the behavior of Mercurial before version 1.5
101 opts['file'] = 1
101 opts['file'] = 1
102
102
103 datefunc = ui.quiet and util.shortdate or util.datestr
103 datefunc = ui.quiet and util.shortdate or util.datestr
104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105
105
106 if not pats:
106 if not pats:
107 raise util.Abort(_('at least one filename or pattern is required'))
107 raise util.Abort(_('at least one filename or pattern is required'))
108
108
109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 ('number', lambda x: str(x[0].rev())),
110 ('number', lambda x: str(x[0].rev())),
111 ('changeset', lambda x: short(x[0].node())),
111 ('changeset', lambda x: short(x[0].node())),
112 ('date', getdate),
112 ('date', getdate),
113 ('file', lambda x: x[0].path()),
113 ('file', lambda x: x[0].path()),
114 ]
114 ]
115
115
116 if (not opts.get('user') and not opts.get('changeset')
116 if (not opts.get('user') and not opts.get('changeset')
117 and not opts.get('date') and not opts.get('file')):
117 and not opts.get('date') and not opts.get('file')):
118 opts['number'] = 1
118 opts['number'] = 1
119
119
120 linenumber = opts.get('line_number') is not None
120 linenumber = opts.get('line_number') is not None
121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 raise util.Abort(_('at least one of -n/-c is required for -l'))
122 raise util.Abort(_('at least one of -n/-c is required for -l'))
123
123
124 funcmap = [func for op, func in opmap if opts.get(op)]
124 funcmap = [func for op, func in opmap if opts.get(op)]
125 if linenumber:
125 if linenumber:
126 lastfunc = funcmap[-1]
126 lastfunc = funcmap[-1]
127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128
128
129 ctx = cmdutil.revsingle(repo, opts.get('rev'))
129 ctx = cmdutil.revsingle(repo, opts.get('rev'))
130 m = cmdutil.match(repo, pats, opts)
130 m = cmdutil.match(repo, pats, opts)
131 follow = not opts.get('no_follow')
131 follow = not opts.get('no_follow')
132 for abs in ctx.walk(m):
132 for abs in ctx.walk(m):
133 fctx = ctx[abs]
133 fctx = ctx[abs]
134 if not opts.get('text') and util.binary(fctx.data()):
134 if not opts.get('text') and util.binary(fctx.data()):
135 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
135 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
136 continue
136 continue
137
137
138 lines = fctx.annotate(follow=follow, linenumber=linenumber)
138 lines = fctx.annotate(follow=follow, linenumber=linenumber)
139 pieces = []
139 pieces = []
140
140
141 for f in funcmap:
141 for f in funcmap:
142 l = [f(n) for n, dummy in lines]
142 l = [f(n) for n, dummy in lines]
143 if l:
143 if l:
144 sized = [(x, encoding.colwidth(x)) for x in l]
144 sized = [(x, encoding.colwidth(x)) for x in l]
145 ml = max([w for x, w in sized])
145 ml = max([w for x, w in sized])
146 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
146 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
147
147
148 if pieces:
148 if pieces:
149 for p, l in zip(zip(*pieces), lines):
149 for p, l in zip(zip(*pieces), lines):
150 ui.write("%s: %s" % (" ".join(p), l[1]))
150 ui.write("%s: %s" % (" ".join(p), l[1]))
151
151
152 def archive(ui, repo, dest, **opts):
152 def archive(ui, repo, dest, **opts):
153 '''create an unversioned archive of a repository revision
153 '''create an unversioned archive of a repository revision
154
154
155 By default, the revision used is the parent of the working
155 By default, the revision used is the parent of the working
156 directory; use -r/--rev to specify a different revision.
156 directory; use -r/--rev to specify a different revision.
157
157
158 The archive type is automatically detected based on file
158 The archive type is automatically detected based on file
159 extension (or override using -t/--type).
159 extension (or override using -t/--type).
160
160
161 Valid types are:
161 Valid types are:
162
162
163 :``files``: a directory full of files (default)
163 :``files``: a directory full of files (default)
164 :``tar``: tar archive, uncompressed
164 :``tar``: tar archive, uncompressed
165 :``tbz2``: tar archive, compressed using bzip2
165 :``tbz2``: tar archive, compressed using bzip2
166 :``tgz``: tar archive, compressed using gzip
166 :``tgz``: tar archive, compressed using gzip
167 :``uzip``: zip archive, uncompressed
167 :``uzip``: zip archive, uncompressed
168 :``zip``: zip archive, compressed using deflate
168 :``zip``: zip archive, compressed using deflate
169
169
170 The exact name of the destination archive or directory is given
170 The exact name of the destination archive or directory is given
171 using a format string; see :hg:`help export` for details.
171 using a format string; see :hg:`help export` for details.
172
172
173 Each member added to an archive file has a directory prefix
173 Each member added to an archive file has a directory prefix
174 prepended. Use -p/--prefix to specify a format string for the
174 prepended. Use -p/--prefix to specify a format string for the
175 prefix. The default is the basename of the archive, with suffixes
175 prefix. The default is the basename of the archive, with suffixes
176 removed.
176 removed.
177
177
178 Returns 0 on success.
178 Returns 0 on success.
179 '''
179 '''
180
180
181 ctx = cmdutil.revsingle(repo, opts.get('rev'))
181 ctx = cmdutil.revsingle(repo, opts.get('rev'))
182 if not ctx:
182 if not ctx:
183 raise util.Abort(_('no working directory: please specify a revision'))
183 raise util.Abort(_('no working directory: please specify a revision'))
184 node = ctx.node()
184 node = ctx.node()
185 dest = cmdutil.make_filename(repo, dest, node)
185 dest = cmdutil.make_filename(repo, dest, node)
186 if os.path.realpath(dest) == repo.root:
186 if os.path.realpath(dest) == repo.root:
187 raise util.Abort(_('repository root cannot be destination'))
187 raise util.Abort(_('repository root cannot be destination'))
188
188
189 kind = opts.get('type') or archival.guesskind(dest) or 'files'
189 kind = opts.get('type') or archival.guesskind(dest) or 'files'
190 prefix = opts.get('prefix')
190 prefix = opts.get('prefix')
191
191
192 if dest == '-':
192 if dest == '-':
193 if kind == 'files':
193 if kind == 'files':
194 raise util.Abort(_('cannot archive plain files to stdout'))
194 raise util.Abort(_('cannot archive plain files to stdout'))
195 dest = sys.stdout
195 dest = sys.stdout
196 if not prefix:
196 if not prefix:
197 prefix = os.path.basename(repo.root) + '-%h'
197 prefix = os.path.basename(repo.root) + '-%h'
198
198
199 prefix = cmdutil.make_filename(repo, prefix, node)
199 prefix = cmdutil.make_filename(repo, prefix, node)
200 matchfn = cmdutil.match(repo, [], opts)
200 matchfn = cmdutil.match(repo, [], opts)
201 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
201 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
202 matchfn, prefix, subrepos=opts.get('subrepos'))
202 matchfn, prefix, subrepos=opts.get('subrepos'))
203
203
204 def backout(ui, repo, node=None, rev=None, **opts):
204 def backout(ui, repo, node=None, rev=None, **opts):
205 '''reverse effect of earlier changeset
205 '''reverse effect of earlier changeset
206
206
207 Prepare a new changeset with the effect of REV undone in the
207 Prepare a new changeset with the effect of REV undone in the
208 current working directory.
208 current working directory.
209
209
210 If REV is the parent of the working directory, then this changeset
210 If REV is the parent of the working directory, then this changeset
211 is committed automatically. Otherwise, hg needs to merge the
211 is committed automatically. Otherwise, hg needs to merge the
212 changes and the merged result is left uncommitted.
212 changes and the merged result is left uncommitted.
213
213
214 By default, the pending changeset will have one parent,
214 By default, the pending changeset will have one parent,
215 maintaining a linear history. With --merge, the pending changeset
215 maintaining a linear history. With --merge, the pending changeset
216 will instead have two parents: the old parent of the working
216 will instead have two parents: the old parent of the working
217 directory and a child of REV that simply undoes REV.
217 directory and a child of REV that simply undoes REV.
218
218
219 Before version 1.7, the default behavior was equivalent to
219 Before version 1.7, the default behavior was equivalent to
220 specifying --merge followed by :hg:`update --clean .` to cancel
220 specifying --merge followed by :hg:`update --clean .` to cancel
221 the merge and leave the child of REV as a head to be merged
221 the merge and leave the child of REV as a head to be merged
222 separately.
222 separately.
223
223
224 See :hg:`help dates` for a list of formats valid for -d/--date.
224 See :hg:`help dates` for a list of formats valid for -d/--date.
225
225
226 Returns 0 on success.
226 Returns 0 on success.
227 '''
227 '''
228 if rev and node:
228 if rev and node:
229 raise util.Abort(_("please specify just one revision"))
229 raise util.Abort(_("please specify just one revision"))
230
230
231 if not rev:
231 if not rev:
232 rev = node
232 rev = node
233
233
234 if not rev:
234 if not rev:
235 raise util.Abort(_("please specify a revision to backout"))
235 raise util.Abort(_("please specify a revision to backout"))
236
236
237 date = opts.get('date')
237 date = opts.get('date')
238 if date:
238 if date:
239 opts['date'] = util.parsedate(date)
239 opts['date'] = util.parsedate(date)
240
240
241 cmdutil.bail_if_changed(repo)
241 cmdutil.bail_if_changed(repo)
242 node = cmdutil.revsingle(repo, rev).node()
242 node = cmdutil.revsingle(repo, rev).node()
243
243
244 op1, op2 = repo.dirstate.parents()
244 op1, op2 = repo.dirstate.parents()
245 a = repo.changelog.ancestor(op1, node)
245 a = repo.changelog.ancestor(op1, node)
246 if a != node:
246 if a != node:
247 raise util.Abort(_('cannot backout change on a different branch'))
247 raise util.Abort(_('cannot backout change on a different branch'))
248
248
249 p1, p2 = repo.changelog.parents(node)
249 p1, p2 = repo.changelog.parents(node)
250 if p1 == nullid:
250 if p1 == nullid:
251 raise util.Abort(_('cannot backout a change with no parents'))
251 raise util.Abort(_('cannot backout a change with no parents'))
252 if p2 != nullid:
252 if p2 != nullid:
253 if not opts.get('parent'):
253 if not opts.get('parent'):
254 raise util.Abort(_('cannot backout a merge changeset without '
254 raise util.Abort(_('cannot backout a merge changeset without '
255 '--parent'))
255 '--parent'))
256 p = repo.lookup(opts['parent'])
256 p = repo.lookup(opts['parent'])
257 if p not in (p1, p2):
257 if p not in (p1, p2):
258 raise util.Abort(_('%s is not a parent of %s') %
258 raise util.Abort(_('%s is not a parent of %s') %
259 (short(p), short(node)))
259 (short(p), short(node)))
260 parent = p
260 parent = p
261 else:
261 else:
262 if opts.get('parent'):
262 if opts.get('parent'):
263 raise util.Abort(_('cannot use --parent on non-merge changeset'))
263 raise util.Abort(_('cannot use --parent on non-merge changeset'))
264 parent = p1
264 parent = p1
265
265
266 # the backout should appear on the same branch
266 # the backout should appear on the same branch
267 branch = repo.dirstate.branch()
267 branch = repo.dirstate.branch()
268 hg.clean(repo, node, show_stats=False)
268 hg.clean(repo, node, show_stats=False)
269 repo.dirstate.setbranch(branch)
269 repo.dirstate.setbranch(branch)
270 revert_opts = opts.copy()
270 revert_opts = opts.copy()
271 revert_opts['date'] = None
271 revert_opts['date'] = None
272 revert_opts['all'] = True
272 revert_opts['all'] = True
273 revert_opts['rev'] = hex(parent)
273 revert_opts['rev'] = hex(parent)
274 revert_opts['no_backup'] = None
274 revert_opts['no_backup'] = None
275 revert(ui, repo, **revert_opts)
275 revert(ui, repo, **revert_opts)
276 if not opts.get('merge') and op1 != node:
276 if not opts.get('merge') and op1 != node:
277 try:
277 try:
278 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
278 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
279 return hg.update(repo, op1)
279 return hg.update(repo, op1)
280 finally:
280 finally:
281 ui.setconfig('ui', 'forcemerge', '')
281 ui.setconfig('ui', 'forcemerge', '')
282
282
283 commit_opts = opts.copy()
283 commit_opts = opts.copy()
284 commit_opts['addremove'] = False
284 commit_opts['addremove'] = False
285 if not commit_opts['message'] and not commit_opts['logfile']:
285 if not commit_opts['message'] and not commit_opts['logfile']:
286 # we don't translate commit messages
286 # we don't translate commit messages
287 commit_opts['message'] = "Backed out changeset %s" % short(node)
287 commit_opts['message'] = "Backed out changeset %s" % short(node)
288 commit_opts['force_editor'] = True
288 commit_opts['force_editor'] = True
289 commit(ui, repo, **commit_opts)
289 commit(ui, repo, **commit_opts)
290 def nice(node):
290 def nice(node):
291 return '%d:%s' % (repo.changelog.rev(node), short(node))
291 return '%d:%s' % (repo.changelog.rev(node), short(node))
292 ui.status(_('changeset %s backs out changeset %s\n') %
292 ui.status(_('changeset %s backs out changeset %s\n') %
293 (nice(repo.changelog.tip()), nice(node)))
293 (nice(repo.changelog.tip()), nice(node)))
294 if opts.get('merge') and op1 != node:
294 if opts.get('merge') and op1 != node:
295 hg.clean(repo, op1, show_stats=False)
295 hg.clean(repo, op1, show_stats=False)
296 ui.status(_('merging with changeset %s\n')
296 ui.status(_('merging with changeset %s\n')
297 % nice(repo.changelog.tip()))
297 % nice(repo.changelog.tip()))
298 try:
298 try:
299 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
299 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
300 return hg.merge(repo, hex(repo.changelog.tip()))
300 return hg.merge(repo, hex(repo.changelog.tip()))
301 finally:
301 finally:
302 ui.setconfig('ui', 'forcemerge', '')
302 ui.setconfig('ui', 'forcemerge', '')
303 return 0
303 return 0
304
304
305 def bisect(ui, repo, rev=None, extra=None, command=None,
305 def bisect(ui, repo, rev=None, extra=None, command=None,
306 reset=None, good=None, bad=None, skip=None, noupdate=None):
306 reset=None, good=None, bad=None, skip=None, noupdate=None):
307 """subdivision search of changesets
307 """subdivision search of changesets
308
308
309 This command helps to find changesets which introduce problems. To
309 This command helps to find changesets which introduce problems. To
310 use, mark the earliest changeset you know exhibits the problem as
310 use, mark the earliest changeset you know exhibits the problem as
311 bad, then mark the latest changeset which is free from the problem
311 bad, then mark the latest changeset which is free from the problem
312 as good. Bisect will update your working directory to a revision
312 as good. Bisect will update your working directory to a revision
313 for testing (unless the -U/--noupdate option is specified). Once
313 for testing (unless the -U/--noupdate option is specified). Once
314 you have performed tests, mark the working directory as good or
314 you have performed tests, mark the working directory as good or
315 bad, and bisect will either update to another candidate changeset
315 bad, and bisect will either update to another candidate changeset
316 or announce that it has found the bad revision.
316 or announce that it has found the bad revision.
317
317
318 As a shortcut, you can also use the revision argument to mark a
318 As a shortcut, you can also use the revision argument to mark a
319 revision as good or bad without checking it out first.
319 revision as good or bad without checking it out first.
320
320
321 If you supply a command, it will be used for automatic bisection.
321 If you supply a command, it will be used for automatic bisection.
322 Its exit status will be used to mark revisions as good or bad:
322 Its exit status will be used to mark revisions as good or bad:
323 status 0 means good, 125 means to skip the revision, 127
323 status 0 means good, 125 means to skip the revision, 127
324 (command not found) will abort the bisection, and any other
324 (command not found) will abort the bisection, and any other
325 non-zero exit status means the revision is bad.
325 non-zero exit status means the revision is bad.
326
326
327 Returns 0 on success.
327 Returns 0 on success.
328 """
328 """
329 def print_result(nodes, good):
329 def print_result(nodes, good):
330 displayer = cmdutil.show_changeset(ui, repo, {})
330 displayer = cmdutil.show_changeset(ui, repo, {})
331 if len(nodes) == 1:
331 if len(nodes) == 1:
332 # narrowed it down to a single revision
332 # narrowed it down to a single revision
333 if good:
333 if good:
334 ui.write(_("The first good revision is:\n"))
334 ui.write(_("The first good revision is:\n"))
335 else:
335 else:
336 ui.write(_("The first bad revision is:\n"))
336 ui.write(_("The first bad revision is:\n"))
337 displayer.show(repo[nodes[0]])
337 displayer.show(repo[nodes[0]])
338 parents = repo[nodes[0]].parents()
338 parents = repo[nodes[0]].parents()
339 if len(parents) > 1:
339 if len(parents) > 1:
340 side = good and state['bad'] or state['good']
340 side = good and state['bad'] or state['good']
341 num = len(set(i.node() for i in parents) & set(side))
341 num = len(set(i.node() for i in parents) & set(side))
342 if num == 1:
342 if num == 1:
343 common = parents[0].ancestor(parents[1])
343 common = parents[0].ancestor(parents[1])
344 ui.write(_('Not all ancestors of this changeset have been'
344 ui.write(_('Not all ancestors of this changeset have been'
345 ' checked.\nTo check the other ancestors, start'
345 ' checked.\nTo check the other ancestors, start'
346 ' from the common ancestor, %s.\n' % common))
346 ' from the common ancestor, %s.\n' % common))
347 else:
347 else:
348 # multiple possible revisions
348 # multiple possible revisions
349 if good:
349 if good:
350 ui.write(_("Due to skipped revisions, the first "
350 ui.write(_("Due to skipped revisions, the first "
351 "good revision could be any of:\n"))
351 "good revision could be any of:\n"))
352 else:
352 else:
353 ui.write(_("Due to skipped revisions, the first "
353 ui.write(_("Due to skipped revisions, the first "
354 "bad revision could be any of:\n"))
354 "bad revision could be any of:\n"))
355 for n in nodes:
355 for n in nodes:
356 displayer.show(repo[n])
356 displayer.show(repo[n])
357 displayer.close()
357 displayer.close()
358
358
359 def check_state(state, interactive=True):
359 def check_state(state, interactive=True):
360 if not state['good'] or not state['bad']:
360 if not state['good'] or not state['bad']:
361 if (good or bad or skip or reset) and interactive:
361 if (good or bad or skip or reset) and interactive:
362 return
362 return
363 if not state['good']:
363 if not state['good']:
364 raise util.Abort(_('cannot bisect (no known good revisions)'))
364 raise util.Abort(_('cannot bisect (no known good revisions)'))
365 else:
365 else:
366 raise util.Abort(_('cannot bisect (no known bad revisions)'))
366 raise util.Abort(_('cannot bisect (no known bad revisions)'))
367 return True
367 return True
368
368
369 # backward compatibility
369 # backward compatibility
370 if rev in "good bad reset init".split():
370 if rev in "good bad reset init".split():
371 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
371 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
372 cmd, rev, extra = rev, extra, None
372 cmd, rev, extra = rev, extra, None
373 if cmd == "good":
373 if cmd == "good":
374 good = True
374 good = True
375 elif cmd == "bad":
375 elif cmd == "bad":
376 bad = True
376 bad = True
377 else:
377 else:
378 reset = True
378 reset = True
379 elif extra or good + bad + skip + reset + bool(command) > 1:
379 elif extra or good + bad + skip + reset + bool(command) > 1:
380 raise util.Abort(_('incompatible arguments'))
380 raise util.Abort(_('incompatible arguments'))
381
381
382 if reset:
382 if reset:
383 p = repo.join("bisect.state")
383 p = repo.join("bisect.state")
384 if os.path.exists(p):
384 if os.path.exists(p):
385 os.unlink(p)
385 os.unlink(p)
386 return
386 return
387
387
388 state = hbisect.load_state(repo)
388 state = hbisect.load_state(repo)
389
389
390 if command:
390 if command:
391 changesets = 1
391 changesets = 1
392 try:
392 try:
393 while changesets:
393 while changesets:
394 # update state
394 # update state
395 status = util.system(command)
395 status = util.system(command)
396 if status == 125:
396 if status == 125:
397 transition = "skip"
397 transition = "skip"
398 elif status == 0:
398 elif status == 0:
399 transition = "good"
399 transition = "good"
400 # status < 0 means process was killed
400 # status < 0 means process was killed
401 elif status == 127:
401 elif status == 127:
402 raise util.Abort(_("failed to execute %s") % command)
402 raise util.Abort(_("failed to execute %s") % command)
403 elif status < 0:
403 elif status < 0:
404 raise util.Abort(_("%s killed") % command)
404 raise util.Abort(_("%s killed") % command)
405 else:
405 else:
406 transition = "bad"
406 transition = "bad"
407 ctx = cmdutil.revsingle(repo, rev)
407 ctx = cmdutil.revsingle(repo, rev)
408 rev = None # clear for future iterations
408 rev = None # clear for future iterations
409 state[transition].append(ctx.node())
409 state[transition].append(ctx.node())
410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
411 check_state(state, interactive=False)
411 check_state(state, interactive=False)
412 # bisect
412 # bisect
413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
414 # update to next check
414 # update to next check
415 cmdutil.bail_if_changed(repo)
415 cmdutil.bail_if_changed(repo)
416 hg.clean(repo, nodes[0], show_stats=False)
416 hg.clean(repo, nodes[0], show_stats=False)
417 finally:
417 finally:
418 hbisect.save_state(repo, state)
418 hbisect.save_state(repo, state)
419 print_result(nodes, good)
419 print_result(nodes, good)
420 return
420 return
421
421
422 # update state
422 # update state
423
423
424 if rev:
424 if rev:
425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
426 else:
426 else:
427 nodes = [repo.lookup('.')]
427 nodes = [repo.lookup('.')]
428
428
429 if good or bad or skip:
429 if good or bad or skip:
430 if good:
430 if good:
431 state['good'] += nodes
431 state['good'] += nodes
432 elif bad:
432 elif bad:
433 state['bad'] += nodes
433 state['bad'] += nodes
434 elif skip:
434 elif skip:
435 state['skip'] += nodes
435 state['skip'] += nodes
436 hbisect.save_state(repo, state)
436 hbisect.save_state(repo, state)
437
437
438 if not check_state(state):
438 if not check_state(state):
439 return
439 return
440
440
441 # actually bisect
441 # actually bisect
442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
443 if changesets == 0:
443 if changesets == 0:
444 print_result(nodes, good)
444 print_result(nodes, good)
445 else:
445 else:
446 assert len(nodes) == 1 # only a single node can be tested next
446 assert len(nodes) == 1 # only a single node can be tested next
447 node = nodes[0]
447 node = nodes[0]
448 # compute the approximate number of remaining tests
448 # compute the approximate number of remaining tests
449 tests, size = 0, 2
449 tests, size = 0, 2
450 while size <= changesets:
450 while size <= changesets:
451 tests, size = tests + 1, size * 2
451 tests, size = tests + 1, size * 2
452 rev = repo.changelog.rev(node)
452 rev = repo.changelog.rev(node)
453 ui.write(_("Testing changeset %d:%s "
453 ui.write(_("Testing changeset %d:%s "
454 "(%d changesets remaining, ~%d tests)\n")
454 "(%d changesets remaining, ~%d tests)\n")
455 % (rev, short(node), changesets, tests))
455 % (rev, short(node), changesets, tests))
456 if not noupdate:
456 if not noupdate:
457 cmdutil.bail_if_changed(repo)
457 cmdutil.bail_if_changed(repo)
458 return hg.clean(repo, node)
458 return hg.clean(repo, node)
459
459
460 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
460 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
461 '''track a line of development with movable markers
461 '''track a line of development with movable markers
462
462
463 Bookmarks are pointers to certain commits that move when
463 Bookmarks are pointers to certain commits that move when
464 committing. Bookmarks are local. They can be renamed, copied and
464 committing. Bookmarks are local. They can be renamed, copied and
465 deleted. It is possible to use bookmark names in :hg:`merge` and
465 deleted. It is possible to use bookmark names in :hg:`merge` and
466 :hg:`update` to merge and update respectively to a given bookmark.
466 :hg:`update` to merge and update respectively to a given bookmark.
467
467
468 You can use :hg:`bookmark NAME` to set a bookmark on the working
468 You can use :hg:`bookmark NAME` to set a bookmark on the working
469 directory's parent revision with the given name. If you specify
469 directory's parent revision with the given name. If you specify
470 a revision using -r REV (where REV may be an existing bookmark),
470 a revision using -r REV (where REV may be an existing bookmark),
471 the bookmark is assigned to that revision.
471 the bookmark is assigned to that revision.
472
472
473 Bookmarks can be pushed and pulled between repositories (see :hg:`help
473 Bookmarks can be pushed and pulled between repositories (see :hg:`help
474 push` and :hg:`help pull`). This requires the bookmark extension to be
474 push` and :hg:`help pull`). This requires the bookmark extension to be
475 enabled for both the local and remote repositories.
475 enabled for both the local and remote repositories.
476 '''
476 '''
477 hexfn = ui.debugflag and hex or short
477 hexfn = ui.debugflag and hex or short
478 marks = repo._bookmarks
478 marks = repo._bookmarks
479 cur = repo.changectx('.').node()
479 cur = repo.changectx('.').node()
480
480
481 if rename:
481 if rename:
482 if rename not in marks:
482 if rename not in marks:
483 raise util.Abort(_("a bookmark of this name does not exist"))
483 raise util.Abort(_("a bookmark of this name does not exist"))
484 if mark in marks and not force:
484 if mark in marks and not force:
485 raise util.Abort(_("a bookmark of the same name already exists"))
485 raise util.Abort(_("a bookmark of the same name already exists"))
486 if mark is None:
486 if mark is None:
487 raise util.Abort(_("new bookmark name required"))
487 raise util.Abort(_("new bookmark name required"))
488 marks[mark] = marks[rename]
488 marks[mark] = marks[rename]
489 del marks[rename]
489 del marks[rename]
490 if repo._bookmarkcurrent == rename:
490 if repo._bookmarkcurrent == rename:
491 bookmarks.setcurrent(repo, mark)
491 bookmarks.setcurrent(repo, mark)
492 bookmarks.write(repo)
492 bookmarks.write(repo)
493 return
493 return
494
494
495 if delete:
495 if delete:
496 if mark is None:
496 if mark is None:
497 raise util.Abort(_("bookmark name required"))
497 raise util.Abort(_("bookmark name required"))
498 if mark not in marks:
498 if mark not in marks:
499 raise util.Abort(_("a bookmark of this name does not exist"))
499 raise util.Abort(_("a bookmark of this name does not exist"))
500 if mark == repo._bookmarkcurrent:
500 if mark == repo._bookmarkcurrent:
501 bookmarks.setcurrent(repo, None)
501 bookmarks.setcurrent(repo, None)
502 del marks[mark]
502 del marks[mark]
503 bookmarks.write(repo)
503 bookmarks.write(repo)
504 return
504 return
505
505
506 if mark is not None:
506 if mark is not None:
507 if "\n" in mark:
507 if "\n" in mark:
508 raise util.Abort(_("bookmark name cannot contain newlines"))
508 raise util.Abort(_("bookmark name cannot contain newlines"))
509 mark = mark.strip()
509 mark = mark.strip()
510 if not mark:
510 if not mark:
511 raise util.Abort(_("bookmark names cannot consist entirely of "
511 raise util.Abort(_("bookmark names cannot consist entirely of "
512 "whitespace"))
512 "whitespace"))
513 if mark in marks and not force:
513 if mark in marks and not force:
514 raise util.Abort(_("a bookmark of the same name already exists"))
514 raise util.Abort(_("a bookmark of the same name already exists"))
515 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
515 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
516 and not force):
516 and not force):
517 raise util.Abort(
517 raise util.Abort(
518 _("a bookmark cannot have the name of an existing branch"))
518 _("a bookmark cannot have the name of an existing branch"))
519 if rev:
519 if rev:
520 marks[mark] = repo.lookup(rev)
520 marks[mark] = repo.lookup(rev)
521 else:
521 else:
522 marks[mark] = repo.changectx('.').node()
522 marks[mark] = repo.changectx('.').node()
523 bookmarks.setcurrent(repo, mark)
523 bookmarks.setcurrent(repo, mark)
524 bookmarks.write(repo)
524 bookmarks.write(repo)
525 return
525 return
526
526
527 if mark is None:
527 if mark is None:
528 if rev:
528 if rev:
529 raise util.Abort(_("bookmark name required"))
529 raise util.Abort(_("bookmark name required"))
530 if len(marks) == 0:
530 if len(marks) == 0:
531 ui.status(_("no bookmarks set\n"))
531 ui.status(_("no bookmarks set\n"))
532 else:
532 else:
533 for bmark, n in sorted(marks.iteritems()):
533 for bmark, n in sorted(marks.iteritems()):
534 if ui.configbool('bookmarks', 'track.current'):
534 if ui.configbool('bookmarks', 'track.current'):
535 current = repo._bookmarkcurrent
535 current = repo._bookmarkcurrent
536 if bmark == current and n == cur:
536 if bmark == current and n == cur:
537 prefix, label = '*', 'bookmarks.current'
537 prefix, label = '*', 'bookmarks.current'
538 else:
538 else:
539 prefix, label = ' ', ''
539 prefix, label = ' ', ''
540 else:
540 else:
541 if n == cur:
541 if n == cur:
542 prefix, label = '*', 'bookmarks.current'
542 prefix, label = '*', 'bookmarks.current'
543 else:
543 else:
544 prefix, label = ' ', ''
544 prefix, label = ' ', ''
545
545
546 if ui.quiet:
546 if ui.quiet:
547 ui.write("%s\n" % bmark, label=label)
547 ui.write("%s\n" % bmark, label=label)
548 else:
548 else:
549 ui.write(" %s %-25s %d:%s\n" % (
549 ui.write(" %s %-25s %d:%s\n" % (
550 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
550 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
551 label=label)
551 label=label)
552 return
552 return
553
553
554 def branch(ui, repo, label=None, **opts):
554 def branch(ui, repo, label=None, **opts):
555 """set or show the current branch name
555 """set or show the current branch name
556
556
557 With no argument, show the current branch name. With one argument,
557 With no argument, show the current branch name. With one argument,
558 set the working directory branch name (the branch will not exist
558 set the working directory branch name (the branch will not exist
559 in the repository until the next commit). Standard practice
559 in the repository until the next commit). Standard practice
560 recommends that primary development take place on the 'default'
560 recommends that primary development take place on the 'default'
561 branch.
561 branch.
562
562
563 Unless -f/--force is specified, branch will not let you set a
563 Unless -f/--force is specified, branch will not let you set a
564 branch name that already exists, even if it's inactive.
564 branch name that already exists, even if it's inactive.
565
565
566 Use -C/--clean to reset the working directory branch to that of
566 Use -C/--clean to reset the working directory branch to that of
567 the parent of the working directory, negating a previous branch
567 the parent of the working directory, negating a previous branch
568 change.
568 change.
569
569
570 Use the command :hg:`update` to switch to an existing branch. Use
570 Use the command :hg:`update` to switch to an existing branch. Use
571 :hg:`commit --close-branch` to mark this branch as closed.
571 :hg:`commit --close-branch` to mark this branch as closed.
572
572
573 Returns 0 on success.
573 Returns 0 on success.
574 """
574 """
575
575
576 if opts.get('clean'):
576 if opts.get('clean'):
577 label = repo[None].parents()[0].branch()
577 label = repo[None].parents()[0].branch()
578 repo.dirstate.setbranch(label)
578 repo.dirstate.setbranch(label)
579 ui.status(_('reset working directory to branch %s\n') % label)
579 ui.status(_('reset working directory to branch %s\n') % label)
580 elif label:
580 elif label:
581 if not opts.get('force') and label in repo.branchtags():
581 if not opts.get('force') and label in repo.branchtags():
582 if label not in [p.branch() for p in repo.parents()]:
582 if label not in [p.branch() for p in repo.parents()]:
583 raise util.Abort(_('a branch of the same name already exists'
583 raise util.Abort(_('a branch of the same name already exists'
584 " (use 'hg update' to switch to it)"))
584 " (use 'hg update' to switch to it)"))
585 repo.dirstate.setbranch(label)
585 repo.dirstate.setbranch(label)
586 ui.status(_('marked working directory as branch %s\n') % label)
586 ui.status(_('marked working directory as branch %s\n') % label)
587 else:
587 else:
588 ui.write("%s\n" % repo.dirstate.branch())
588 ui.write("%s\n" % repo.dirstate.branch())
589
589
590 def branches(ui, repo, active=False, closed=False):
590 def branches(ui, repo, active=False, closed=False):
591 """list repository named branches
591 """list repository named branches
592
592
593 List the repository's named branches, indicating which ones are
593 List the repository's named branches, indicating which ones are
594 inactive. If -c/--closed is specified, also list branches which have
594 inactive. If -c/--closed is specified, also list branches which have
595 been marked closed (see :hg:`commit --close-branch`).
595 been marked closed (see :hg:`commit --close-branch`).
596
596
597 If -a/--active is specified, only show active branches. A branch
597 If -a/--active is specified, only show active branches. A branch
598 is considered active if it contains repository heads.
598 is considered active if it contains repository heads.
599
599
600 Use the command :hg:`update` to switch to an existing branch.
600 Use the command :hg:`update` to switch to an existing branch.
601
601
602 Returns 0.
602 Returns 0.
603 """
603 """
604
604
605 hexfunc = ui.debugflag and hex or short
605 hexfunc = ui.debugflag and hex or short
606 activebranches = [repo[n].branch() for n in repo.heads()]
606 activebranches = [repo[n].branch() for n in repo.heads()]
607 def testactive(tag, node):
607 def testactive(tag, node):
608 realhead = tag in activebranches
608 realhead = tag in activebranches
609 open = node in repo.branchheads(tag, closed=False)
609 open = node in repo.branchheads(tag, closed=False)
610 return realhead and open
610 return realhead and open
611 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
611 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
612 for tag, node in repo.branchtags().items()],
612 for tag, node in repo.branchtags().items()],
613 reverse=True)
613 reverse=True)
614
614
615 for isactive, node, tag in branches:
615 for isactive, node, tag in branches:
616 if (not active) or isactive:
616 if (not active) or isactive:
617 if ui.quiet:
617 if ui.quiet:
618 ui.write("%s\n" % tag)
618 ui.write("%s\n" % tag)
619 else:
619 else:
620 hn = repo.lookup(node)
620 hn = repo.lookup(node)
621 if isactive:
621 if isactive:
622 label = 'branches.active'
622 label = 'branches.active'
623 notice = ''
623 notice = ''
624 elif hn not in repo.branchheads(tag, closed=False):
624 elif hn not in repo.branchheads(tag, closed=False):
625 if not closed:
625 if not closed:
626 continue
626 continue
627 label = 'branches.closed'
627 label = 'branches.closed'
628 notice = _(' (closed)')
628 notice = _(' (closed)')
629 else:
629 else:
630 label = 'branches.inactive'
630 label = 'branches.inactive'
631 notice = _(' (inactive)')
631 notice = _(' (inactive)')
632 if tag == repo.dirstate.branch():
632 if tag == repo.dirstate.branch():
633 label = 'branches.current'
633 label = 'branches.current'
634 rev = str(node).rjust(31 - encoding.colwidth(tag))
634 rev = str(node).rjust(31 - encoding.colwidth(tag))
635 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
635 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
636 tag = ui.label(tag, label)
636 tag = ui.label(tag, label)
637 ui.write("%s %s%s\n" % (tag, rev, notice))
637 ui.write("%s %s%s\n" % (tag, rev, notice))
638
638
639 def bundle(ui, repo, fname, dest=None, **opts):
639 def bundle(ui, repo, fname, dest=None, **opts):
640 """create a changegroup file
640 """create a changegroup file
641
641
642 Generate a compressed changegroup file collecting changesets not
642 Generate a compressed changegroup file collecting changesets not
643 known to be in another repository.
643 known to be in another repository.
644
644
645 If you omit the destination repository, then hg assumes the
645 If you omit the destination repository, then hg assumes the
646 destination will have all the nodes you specify with --base
646 destination will have all the nodes you specify with --base
647 parameters. To create a bundle containing all changesets, use
647 parameters. To create a bundle containing all changesets, use
648 -a/--all (or --base null).
648 -a/--all (or --base null).
649
649
650 You can change compression method with the -t/--type option.
650 You can change compression method with the -t/--type option.
651 The available compression methods are: none, bzip2, and
651 The available compression methods are: none, bzip2, and
652 gzip (by default, bundles are compressed using bzip2).
652 gzip (by default, bundles are compressed using bzip2).
653
653
654 The bundle file can then be transferred using conventional means
654 The bundle file can then be transferred using conventional means
655 and applied to another repository with the unbundle or pull
655 and applied to another repository with the unbundle or pull
656 command. This is useful when direct push and pull are not
656 command. This is useful when direct push and pull are not
657 available or when exporting an entire repository is undesirable.
657 available or when exporting an entire repository is undesirable.
658
658
659 Applying bundles preserves all changeset contents including
659 Applying bundles preserves all changeset contents including
660 permissions, copy/rename information, and revision history.
660 permissions, copy/rename information, and revision history.
661
661
662 Returns 0 on success, 1 if no changes found.
662 Returns 0 on success, 1 if no changes found.
663 """
663 """
664 revs = None
664 revs = None
665 if 'rev' in opts:
665 if 'rev' in opts:
666 revs = cmdutil.revrange(repo, opts['rev'])
666 revs = cmdutil.revrange(repo, opts['rev'])
667
667
668 if opts.get('all'):
668 if opts.get('all'):
669 base = ['null']
669 base = ['null']
670 else:
670 else:
671 base = cmdutil.revrange(repo, opts.get('base'))
671 base = cmdutil.revrange(repo, opts.get('base'))
672 if base:
672 if base:
673 if dest:
673 if dest:
674 raise util.Abort(_("--base is incompatible with specifying "
674 raise util.Abort(_("--base is incompatible with specifying "
675 "a destination"))
675 "a destination"))
676 base = [repo.lookup(rev) for rev in base]
676 base = [repo.lookup(rev) for rev in base]
677 # create the right base
677 # create the right base
678 # XXX: nodesbetween / changegroup* should be "fixed" instead
678 # XXX: nodesbetween / changegroup* should be "fixed" instead
679 o = []
679 o = []
680 has = set((nullid,))
680 has = set((nullid,))
681 for n in base:
681 for n in base:
682 has.update(repo.changelog.reachable(n))
682 has.update(repo.changelog.reachable(n))
683 if revs:
683 if revs:
684 revs = [repo.lookup(rev) for rev in revs]
684 revs = [repo.lookup(rev) for rev in revs]
685 visit = revs[:]
685 visit = revs[:]
686 has.difference_update(visit)
686 has.difference_update(visit)
687 else:
687 else:
688 visit = repo.changelog.heads()
688 visit = repo.changelog.heads()
689 seen = {}
689 seen = {}
690 while visit:
690 while visit:
691 n = visit.pop(0)
691 n = visit.pop(0)
692 parents = [p for p in repo.changelog.parents(n) if p not in has]
692 parents = [p for p in repo.changelog.parents(n) if p not in has]
693 if len(parents) == 0:
693 if len(parents) == 0:
694 if n not in has:
694 if n not in has:
695 o.append(n)
695 o.append(n)
696 else:
696 else:
697 for p in parents:
697 for p in parents:
698 if p not in seen:
698 if p not in seen:
699 seen[p] = 1
699 seen[p] = 1
700 visit.append(p)
700 visit.append(p)
701 else:
701 else:
702 dest = ui.expandpath(dest or 'default-push', dest or 'default')
702 dest = ui.expandpath(dest or 'default-push', dest or 'default')
703 dest, branches = hg.parseurl(dest, opts.get('branch'))
703 dest, branches = hg.parseurl(dest, opts.get('branch'))
704 other = hg.repository(hg.remoteui(repo, opts), dest)
704 other = hg.repository(hg.remoteui(repo, opts), dest)
705 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
705 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
706 if revs:
706 if revs:
707 revs = [repo.lookup(rev) for rev in revs]
707 revs = [repo.lookup(rev) for rev in revs]
708 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
708 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
709
709
710 if not o:
710 if not o:
711 ui.status(_("no changes found\n"))
711 ui.status(_("no changes found\n"))
712 return 1
712 return 1
713
713
714 if revs:
714 if revs:
715 cg = repo.changegroupsubset(o, revs, 'bundle')
715 cg = repo.changegroupsubset(o, revs, 'bundle')
716 else:
716 else:
717 cg = repo.changegroup(o, 'bundle')
717 cg = repo.changegroup(o, 'bundle')
718
718
719 bundletype = opts.get('type', 'bzip2').lower()
719 bundletype = opts.get('type', 'bzip2').lower()
720 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
720 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
721 bundletype = btypes.get(bundletype)
721 bundletype = btypes.get(bundletype)
722 if bundletype not in changegroup.bundletypes:
722 if bundletype not in changegroup.bundletypes:
723 raise util.Abort(_('unknown bundle type specified with --type'))
723 raise util.Abort(_('unknown bundle type specified with --type'))
724
724
725 changegroup.writebundle(cg, fname, bundletype)
725 changegroup.writebundle(cg, fname, bundletype)
726
726
727 def cat(ui, repo, file1, *pats, **opts):
727 def cat(ui, repo, file1, *pats, **opts):
728 """output the current or given revision of files
728 """output the current or given revision of files
729
729
730 Print the specified files as they were at the given revision. If
730 Print the specified files as they were at the given revision. If
731 no revision is given, the parent of the working directory is used,
731 no revision is given, the parent of the working directory is used,
732 or tip if no revision is checked out.
732 or tip if no revision is checked out.
733
733
734 Output may be to a file, in which case the name of the file is
734 Output may be to a file, in which case the name of the file is
735 given using a format string. The formatting rules are the same as
735 given using a format string. The formatting rules are the same as
736 for the export command, with the following additions:
736 for the export command, with the following additions:
737
737
738 :``%s``: basename of file being printed
738 :``%s``: basename of file being printed
739 :``%d``: dirname of file being printed, or '.' if in repository root
739 :``%d``: dirname of file being printed, or '.' if in repository root
740 :``%p``: root-relative path name of file being printed
740 :``%p``: root-relative path name of file being printed
741
741
742 Returns 0 on success.
742 Returns 0 on success.
743 """
743 """
744 ctx = cmdutil.revsingle(repo, opts.get('rev'))
744 ctx = cmdutil.revsingle(repo, opts.get('rev'))
745 err = 1
745 err = 1
746 m = cmdutil.match(repo, (file1,) + pats, opts)
746 m = cmdutil.match(repo, (file1,) + pats, opts)
747 for abs in ctx.walk(m):
747 for abs in ctx.walk(m):
748 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
748 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
749 data = ctx[abs].data()
749 data = ctx[abs].data()
750 if opts.get('decode'):
750 if opts.get('decode'):
751 data = repo.wwritedata(abs, data)
751 data = repo.wwritedata(abs, data)
752 fp.write(data)
752 fp.write(data)
753 fp.close()
753 err = 0
754 err = 0
754 return err
755 return err
755
756
756 def clone(ui, source, dest=None, **opts):
757 def clone(ui, source, dest=None, **opts):
757 """make a copy of an existing repository
758 """make a copy of an existing repository
758
759
759 Create a copy of an existing repository in a new directory.
760 Create a copy of an existing repository in a new directory.
760
761
761 If no destination directory name is specified, it defaults to the
762 If no destination directory name is specified, it defaults to the
762 basename of the source.
763 basename of the source.
763
764
764 The location of the source is added to the new repository's
765 The location of the source is added to the new repository's
765 ``.hg/hgrc`` file, as the default to be used for future pulls.
766 ``.hg/hgrc`` file, as the default to be used for future pulls.
766
767
767 See :hg:`help urls` for valid source format details.
768 See :hg:`help urls` for valid source format details.
768
769
769 It is possible to specify an ``ssh://`` URL as the destination, but no
770 It is possible to specify an ``ssh://`` URL as the destination, but no
770 ``.hg/hgrc`` and working directory will be created on the remote side.
771 ``.hg/hgrc`` and working directory will be created on the remote side.
771 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
772 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
772
773
773 A set of changesets (tags, or branch names) to pull may be specified
774 A set of changesets (tags, or branch names) to pull may be specified
774 by listing each changeset (tag, or branch name) with -r/--rev.
775 by listing each changeset (tag, or branch name) with -r/--rev.
775 If -r/--rev is used, the cloned repository will contain only a subset
776 If -r/--rev is used, the cloned repository will contain only a subset
776 of the changesets of the source repository. Only the set of changesets
777 of the changesets of the source repository. Only the set of changesets
777 defined by all -r/--rev options (including all their ancestors)
778 defined by all -r/--rev options (including all their ancestors)
778 will be pulled into the destination repository.
779 will be pulled into the destination repository.
779 No subsequent changesets (including subsequent tags) will be present
780 No subsequent changesets (including subsequent tags) will be present
780 in the destination.
781 in the destination.
781
782
782 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
783 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
783 local source repositories.
784 local source repositories.
784
785
785 For efficiency, hardlinks are used for cloning whenever the source
786 For efficiency, hardlinks are used for cloning whenever the source
786 and destination are on the same filesystem (note this applies only
787 and destination are on the same filesystem (note this applies only
787 to the repository data, not to the working directory). Some
788 to the repository data, not to the working directory). Some
788 filesystems, such as AFS, implement hardlinking incorrectly, but
789 filesystems, such as AFS, implement hardlinking incorrectly, but
789 do not report errors. In these cases, use the --pull option to
790 do not report errors. In these cases, use the --pull option to
790 avoid hardlinking.
791 avoid hardlinking.
791
792
792 In some cases, you can clone repositories and the working directory
793 In some cases, you can clone repositories and the working directory
793 using full hardlinks with ::
794 using full hardlinks with ::
794
795
795 $ cp -al REPO REPOCLONE
796 $ cp -al REPO REPOCLONE
796
797
797 This is the fastest way to clone, but it is not always safe. The
798 This is the fastest way to clone, but it is not always safe. The
798 operation is not atomic (making sure REPO is not modified during
799 operation is not atomic (making sure REPO is not modified during
799 the operation is up to you) and you have to make sure your editor
800 the operation is up to you) and you have to make sure your editor
800 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
801 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
801 this is not compatible with certain extensions that place their
802 this is not compatible with certain extensions that place their
802 metadata under the .hg directory, such as mq.
803 metadata under the .hg directory, such as mq.
803
804
804 Mercurial will update the working directory to the first applicable
805 Mercurial will update the working directory to the first applicable
805 revision from this list:
806 revision from this list:
806
807
807 a) null if -U or the source repository has no changesets
808 a) null if -U or the source repository has no changesets
808 b) if -u . and the source repository is local, the first parent of
809 b) if -u . and the source repository is local, the first parent of
809 the source repository's working directory
810 the source repository's working directory
810 c) the changeset specified with -u (if a branch name, this means the
811 c) the changeset specified with -u (if a branch name, this means the
811 latest head of that branch)
812 latest head of that branch)
812 d) the changeset specified with -r
813 d) the changeset specified with -r
813 e) the tipmost head specified with -b
814 e) the tipmost head specified with -b
814 f) the tipmost head specified with the url#branch source syntax
815 f) the tipmost head specified with the url#branch source syntax
815 g) the tipmost head of the default branch
816 g) the tipmost head of the default branch
816 h) tip
817 h) tip
817
818
818 Returns 0 on success.
819 Returns 0 on success.
819 """
820 """
820 if opts.get('noupdate') and opts.get('updaterev'):
821 if opts.get('noupdate') and opts.get('updaterev'):
821 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
822 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
822
823
823 r = hg.clone(hg.remoteui(ui, opts), source, dest,
824 r = hg.clone(hg.remoteui(ui, opts), source, dest,
824 pull=opts.get('pull'),
825 pull=opts.get('pull'),
825 stream=opts.get('uncompressed'),
826 stream=opts.get('uncompressed'),
826 rev=opts.get('rev'),
827 rev=opts.get('rev'),
827 update=opts.get('updaterev') or not opts.get('noupdate'),
828 update=opts.get('updaterev') or not opts.get('noupdate'),
828 branch=opts.get('branch'))
829 branch=opts.get('branch'))
829
830
830 return r is None
831 return r is None
831
832
832 def commit(ui, repo, *pats, **opts):
833 def commit(ui, repo, *pats, **opts):
833 """commit the specified files or all outstanding changes
834 """commit the specified files or all outstanding changes
834
835
835 Commit changes to the given files into the repository. Unlike a
836 Commit changes to the given files into the repository. Unlike a
836 centralized SCM, this operation is a local operation. See
837 centralized SCM, this operation is a local operation. See
837 :hg:`push` for a way to actively distribute your changes.
838 :hg:`push` for a way to actively distribute your changes.
838
839
839 If a list of files is omitted, all changes reported by :hg:`status`
840 If a list of files is omitted, all changes reported by :hg:`status`
840 will be committed.
841 will be committed.
841
842
842 If you are committing the result of a merge, do not provide any
843 If you are committing the result of a merge, do not provide any
843 filenames or -I/-X filters.
844 filenames or -I/-X filters.
844
845
845 If no commit message is specified, Mercurial starts your
846 If no commit message is specified, Mercurial starts your
846 configured editor where you can enter a message. In case your
847 configured editor where you can enter a message. In case your
847 commit fails, you will find a backup of your message in
848 commit fails, you will find a backup of your message in
848 ``.hg/last-message.txt``.
849 ``.hg/last-message.txt``.
849
850
850 See :hg:`help dates` for a list of formats valid for -d/--date.
851 See :hg:`help dates` for a list of formats valid for -d/--date.
851
852
852 Returns 0 on success, 1 if nothing changed.
853 Returns 0 on success, 1 if nothing changed.
853 """
854 """
854 extra = {}
855 extra = {}
855 if opts.get('close_branch'):
856 if opts.get('close_branch'):
856 if repo['.'].node() not in repo.branchheads():
857 if repo['.'].node() not in repo.branchheads():
857 # The topo heads set is included in the branch heads set of the
858 # The topo heads set is included in the branch heads set of the
858 # current branch, so it's sufficient to test branchheads
859 # current branch, so it's sufficient to test branchheads
859 raise util.Abort(_('can only close branch heads'))
860 raise util.Abort(_('can only close branch heads'))
860 extra['close'] = 1
861 extra['close'] = 1
861 e = cmdutil.commiteditor
862 e = cmdutil.commiteditor
862 if opts.get('force_editor'):
863 if opts.get('force_editor'):
863 e = cmdutil.commitforceeditor
864 e = cmdutil.commitforceeditor
864
865
865 def commitfunc(ui, repo, message, match, opts):
866 def commitfunc(ui, repo, message, match, opts):
866 return repo.commit(message, opts.get('user'), opts.get('date'), match,
867 return repo.commit(message, opts.get('user'), opts.get('date'), match,
867 editor=e, extra=extra)
868 editor=e, extra=extra)
868
869
869 branch = repo[None].branch()
870 branch = repo[None].branch()
870 bheads = repo.branchheads(branch)
871 bheads = repo.branchheads(branch)
871
872
872 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
873 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
873 if not node:
874 if not node:
874 ui.status(_("nothing changed\n"))
875 ui.status(_("nothing changed\n"))
875 return 1
876 return 1
876
877
877 ctx = repo[node]
878 ctx = repo[node]
878 parents = ctx.parents()
879 parents = ctx.parents()
879
880
880 if bheads and not [x for x in parents
881 if bheads and not [x for x in parents
881 if x.node() in bheads and x.branch() == branch]:
882 if x.node() in bheads and x.branch() == branch]:
882 ui.status(_('created new head\n'))
883 ui.status(_('created new head\n'))
883 # The message is not printed for initial roots. For the other
884 # The message is not printed for initial roots. For the other
884 # changesets, it is printed in the following situations:
885 # changesets, it is printed in the following situations:
885 #
886 #
886 # Par column: for the 2 parents with ...
887 # Par column: for the 2 parents with ...
887 # N: null or no parent
888 # N: null or no parent
888 # B: parent is on another named branch
889 # B: parent is on another named branch
889 # C: parent is a regular non head changeset
890 # C: parent is a regular non head changeset
890 # H: parent was a branch head of the current branch
891 # H: parent was a branch head of the current branch
891 # Msg column: whether we print "created new head" message
892 # Msg column: whether we print "created new head" message
892 # In the following, it is assumed that there already exists some
893 # In the following, it is assumed that there already exists some
893 # initial branch heads of the current branch, otherwise nothing is
894 # initial branch heads of the current branch, otherwise nothing is
894 # printed anyway.
895 # printed anyway.
895 #
896 #
896 # Par Msg Comment
897 # Par Msg Comment
897 # NN y additional topo root
898 # NN y additional topo root
898 #
899 #
899 # BN y additional branch root
900 # BN y additional branch root
900 # CN y additional topo head
901 # CN y additional topo head
901 # HN n usual case
902 # HN n usual case
902 #
903 #
903 # BB y weird additional branch root
904 # BB y weird additional branch root
904 # CB y branch merge
905 # CB y branch merge
905 # HB n merge with named branch
906 # HB n merge with named branch
906 #
907 #
907 # CC y additional head from merge
908 # CC y additional head from merge
908 # CH n merge with a head
909 # CH n merge with a head
909 #
910 #
910 # HH n head merge: head count decreases
911 # HH n head merge: head count decreases
911
912
912 if not opts.get('close_branch'):
913 if not opts.get('close_branch'):
913 for r in parents:
914 for r in parents:
914 if r.extra().get('close') and r.branch() == branch:
915 if r.extra().get('close') and r.branch() == branch:
915 ui.status(_('reopening closed branch head %d\n') % r)
916 ui.status(_('reopening closed branch head %d\n') % r)
916
917
917 if ui.debugflag:
918 if ui.debugflag:
918 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
919 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
919 elif ui.verbose:
920 elif ui.verbose:
920 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
921 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
921
922
922 def copy(ui, repo, *pats, **opts):
923 def copy(ui, repo, *pats, **opts):
923 """mark files as copied for the next commit
924 """mark files as copied for the next commit
924
925
925 Mark dest as having copies of source files. If dest is a
926 Mark dest as having copies of source files. If dest is a
926 directory, copies are put in that directory. If dest is a file,
927 directory, copies are put in that directory. If dest is a file,
927 the source must be a single file.
928 the source must be a single file.
928
929
929 By default, this command copies the contents of files as they
930 By default, this command copies the contents of files as they
930 exist in the working directory. If invoked with -A/--after, the
931 exist in the working directory. If invoked with -A/--after, the
931 operation is recorded, but no copying is performed.
932 operation is recorded, but no copying is performed.
932
933
933 This command takes effect with the next commit. To undo a copy
934 This command takes effect with the next commit. To undo a copy
934 before that, see :hg:`revert`.
935 before that, see :hg:`revert`.
935
936
936 Returns 0 on success, 1 if errors are encountered.
937 Returns 0 on success, 1 if errors are encountered.
937 """
938 """
938 wlock = repo.wlock(False)
939 wlock = repo.wlock(False)
939 try:
940 try:
940 return cmdutil.copy(ui, repo, pats, opts)
941 return cmdutil.copy(ui, repo, pats, opts)
941 finally:
942 finally:
942 wlock.release()
943 wlock.release()
943
944
944 def debugancestor(ui, repo, *args):
945 def debugancestor(ui, repo, *args):
945 """find the ancestor revision of two revisions in a given index"""
946 """find the ancestor revision of two revisions in a given index"""
946 if len(args) == 3:
947 if len(args) == 3:
947 index, rev1, rev2 = args
948 index, rev1, rev2 = args
948 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
949 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
949 lookup = r.lookup
950 lookup = r.lookup
950 elif len(args) == 2:
951 elif len(args) == 2:
951 if not repo:
952 if not repo:
952 raise util.Abort(_("there is no Mercurial repository here "
953 raise util.Abort(_("there is no Mercurial repository here "
953 "(.hg not found)"))
954 "(.hg not found)"))
954 rev1, rev2 = args
955 rev1, rev2 = args
955 r = repo.changelog
956 r = repo.changelog
956 lookup = repo.lookup
957 lookup = repo.lookup
957 else:
958 else:
958 raise util.Abort(_('either two or three arguments required'))
959 raise util.Abort(_('either two or three arguments required'))
959 a = r.ancestor(lookup(rev1), lookup(rev2))
960 a = r.ancestor(lookup(rev1), lookup(rev2))
960 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
961 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
961
962
962 def debugbuilddag(ui, repo, text,
963 def debugbuilddag(ui, repo, text,
963 mergeable_file=False,
964 mergeable_file=False,
964 appended_file=False,
965 appended_file=False,
965 overwritten_file=False,
966 overwritten_file=False,
966 new_file=False):
967 new_file=False):
967 """builds a repo with a given dag from scratch in the current empty repo
968 """builds a repo with a given dag from scratch in the current empty repo
968
969
969 Elements:
970 Elements:
970
971
971 - "+n" is a linear run of n nodes based on the current default parent
972 - "+n" is a linear run of n nodes based on the current default parent
972 - "." is a single node based on the current default parent
973 - "." is a single node based on the current default parent
973 - "$" resets the default parent to null (implied at the start);
974 - "$" resets the default parent to null (implied at the start);
974 otherwise the default parent is always the last node created
975 otherwise the default parent is always the last node created
975 - "<p" sets the default parent to the backref p
976 - "<p" sets the default parent to the backref p
976 - "*p" is a fork at parent p, which is a backref
977 - "*p" is a fork at parent p, which is a backref
977 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
978 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
978 - "/p2" is a merge of the preceding node and p2
979 - "/p2" is a merge of the preceding node and p2
979 - ":tag" defines a local tag for the preceding node
980 - ":tag" defines a local tag for the preceding node
980 - "@branch" sets the named branch for subsequent nodes
981 - "@branch" sets the named branch for subsequent nodes
981 - "!command" runs the command using your shell
982 - "!command" runs the command using your shell
982 - "!!my command\\n" is like "!", but to the end of the line
983 - "!!my command\\n" is like "!", but to the end of the line
983 - "#...\\n" is a comment up to the end of the line
984 - "#...\\n" is a comment up to the end of the line
984
985
985 Whitespace between the above elements is ignored.
986 Whitespace between the above elements is ignored.
986
987
987 A backref is either
988 A backref is either
988
989
989 - a number n, which references the node curr-n, where curr is the current
990 - a number n, which references the node curr-n, where curr is the current
990 node, or
991 node, or
991 - the name of a local tag you placed earlier using ":tag", or
992 - the name of a local tag you placed earlier using ":tag", or
992 - empty to denote the default parent.
993 - empty to denote the default parent.
993
994
994 All string valued-elements are either strictly alphanumeric, or must
995 All string valued-elements are either strictly alphanumeric, or must
995 be enclosed in double quotes ("..."), with "\\" as escape character.
996 be enclosed in double quotes ("..."), with "\\" as escape character.
996
997
997 Note that the --overwritten-file and --appended-file options imply the
998 Note that the --overwritten-file and --appended-file options imply the
998 use of "HGMERGE=internal:local" during DAG buildup.
999 use of "HGMERGE=internal:local" during DAG buildup.
999 """
1000 """
1000
1001
1001 if not (mergeable_file or appended_file or overwritten_file or new_file):
1002 if not (mergeable_file or appended_file or overwritten_file or new_file):
1002 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1003 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1003
1004
1004 if len(repo.changelog) > 0:
1005 if len(repo.changelog) > 0:
1005 raise util.Abort(_('repository is not empty'))
1006 raise util.Abort(_('repository is not empty'))
1006
1007
1007 if overwritten_file or appended_file:
1008 if overwritten_file or appended_file:
1008 # we don't want to fail in merges during buildup
1009 # we don't want to fail in merges during buildup
1009 os.environ['HGMERGE'] = 'internal:local'
1010 os.environ['HGMERGE'] = 'internal:local'
1010
1011
1011 def writefile(fname, text, fmode="wb"):
1012 def writefile(fname, text, fmode="wb"):
1012 f = open(fname, fmode)
1013 f = open(fname, fmode)
1013 try:
1014 try:
1014 f.write(text)
1015 f.write(text)
1015 finally:
1016 finally:
1016 f.close()
1017 f.close()
1017
1018
1018 if mergeable_file:
1019 if mergeable_file:
1019 linesperrev = 2
1020 linesperrev = 2
1020 # determine number of revs in DAG
1021 # determine number of revs in DAG
1021 n = 0
1022 n = 0
1022 for type, data in dagparser.parsedag(text):
1023 for type, data in dagparser.parsedag(text):
1023 if type == 'n':
1024 if type == 'n':
1024 n += 1
1025 n += 1
1025 # make a file with k lines per rev
1026 # make a file with k lines per rev
1026 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1027 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1027 + "\n")
1028 + "\n")
1028
1029
1029 at = -1
1030 at = -1
1030 atbranch = 'default'
1031 atbranch = 'default'
1031 for type, data in dagparser.parsedag(text):
1032 for type, data in dagparser.parsedag(text):
1032 if type == 'n':
1033 if type == 'n':
1033 ui.status('node %s\n' % str(data))
1034 ui.status('node %s\n' % str(data))
1034 id, ps = data
1035 id, ps = data
1035 p1 = ps[0]
1036 p1 = ps[0]
1036 if p1 != at:
1037 if p1 != at:
1037 update(ui, repo, node=str(p1), clean=True)
1038 update(ui, repo, node=str(p1), clean=True)
1038 at = p1
1039 at = p1
1039 if repo.dirstate.branch() != atbranch:
1040 if repo.dirstate.branch() != atbranch:
1040 branch(ui, repo, atbranch, force=True)
1041 branch(ui, repo, atbranch, force=True)
1041 if len(ps) > 1:
1042 if len(ps) > 1:
1042 p2 = ps[1]
1043 p2 = ps[1]
1043 merge(ui, repo, node=p2)
1044 merge(ui, repo, node=p2)
1044
1045
1045 if mergeable_file:
1046 if mergeable_file:
1046 f = open("mf", "rb+")
1047 f = open("mf", "rb+")
1047 try:
1048 try:
1048 lines = f.read().split("\n")
1049 lines = f.read().split("\n")
1049 lines[id * linesperrev] += " r%i" % id
1050 lines[id * linesperrev] += " r%i" % id
1050 f.seek(0)
1051 f.seek(0)
1051 f.write("\n".join(lines))
1052 f.write("\n".join(lines))
1052 finally:
1053 finally:
1053 f.close()
1054 f.close()
1054
1055
1055 if appended_file:
1056 if appended_file:
1056 writefile("af", "r%i\n" % id, "ab")
1057 writefile("af", "r%i\n" % id, "ab")
1057
1058
1058 if overwritten_file:
1059 if overwritten_file:
1059 writefile("of", "r%i\n" % id)
1060 writefile("of", "r%i\n" % id)
1060
1061
1061 if new_file:
1062 if new_file:
1062 writefile("nf%i" % id, "r%i\n" % id)
1063 writefile("nf%i" % id, "r%i\n" % id)
1063
1064
1064 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1065 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1065 at = id
1066 at = id
1066 elif type == 'l':
1067 elif type == 'l':
1067 id, name = data
1068 id, name = data
1068 ui.status('tag %s\n' % name)
1069 ui.status('tag %s\n' % name)
1069 tag(ui, repo, name, local=True)
1070 tag(ui, repo, name, local=True)
1070 elif type == 'a':
1071 elif type == 'a':
1071 ui.status('branch %s\n' % data)
1072 ui.status('branch %s\n' % data)
1072 atbranch = data
1073 atbranch = data
1073 elif type in 'cC':
1074 elif type in 'cC':
1074 r = util.system(data, cwd=repo.root)
1075 r = util.system(data, cwd=repo.root)
1075 if r:
1076 if r:
1076 desc, r = util.explain_exit(r)
1077 desc, r = util.explain_exit(r)
1077 raise util.Abort(_('%s command %s') % (data, desc))
1078 raise util.Abort(_('%s command %s') % (data, desc))
1078
1079
1079 def debugcommands(ui, cmd='', *args):
1080 def debugcommands(ui, cmd='', *args):
1080 """list all available commands and options"""
1081 """list all available commands and options"""
1081 for cmd, vals in sorted(table.iteritems()):
1082 for cmd, vals in sorted(table.iteritems()):
1082 cmd = cmd.split('|')[0].strip('^')
1083 cmd = cmd.split('|')[0].strip('^')
1083 opts = ', '.join([i[1] for i in vals[1]])
1084 opts = ', '.join([i[1] for i in vals[1]])
1084 ui.write('%s: %s\n' % (cmd, opts))
1085 ui.write('%s: %s\n' % (cmd, opts))
1085
1086
1086 def debugcomplete(ui, cmd='', **opts):
1087 def debugcomplete(ui, cmd='', **opts):
1087 """returns the completion list associated with the given command"""
1088 """returns the completion list associated with the given command"""
1088
1089
1089 if opts.get('options'):
1090 if opts.get('options'):
1090 options = []
1091 options = []
1091 otables = [globalopts]
1092 otables = [globalopts]
1092 if cmd:
1093 if cmd:
1093 aliases, entry = cmdutil.findcmd(cmd, table, False)
1094 aliases, entry = cmdutil.findcmd(cmd, table, False)
1094 otables.append(entry[1])
1095 otables.append(entry[1])
1095 for t in otables:
1096 for t in otables:
1096 for o in t:
1097 for o in t:
1097 if "(DEPRECATED)" in o[3]:
1098 if "(DEPRECATED)" in o[3]:
1098 continue
1099 continue
1099 if o[0]:
1100 if o[0]:
1100 options.append('-%s' % o[0])
1101 options.append('-%s' % o[0])
1101 options.append('--%s' % o[1])
1102 options.append('--%s' % o[1])
1102 ui.write("%s\n" % "\n".join(options))
1103 ui.write("%s\n" % "\n".join(options))
1103 return
1104 return
1104
1105
1105 cmdlist = cmdutil.findpossible(cmd, table)
1106 cmdlist = cmdutil.findpossible(cmd, table)
1106 if ui.verbose:
1107 if ui.verbose:
1107 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1108 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1108 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1109 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1109
1110
1110 def debugfsinfo(ui, path = "."):
1111 def debugfsinfo(ui, path = "."):
1111 """show information detected about current filesystem"""
1112 """show information detected about current filesystem"""
1112 open('.debugfsinfo', 'w').write('')
1113 open('.debugfsinfo', 'w').write('')
1113 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1114 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1114 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1115 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1115 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1116 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1116 and 'yes' or 'no'))
1117 and 'yes' or 'no'))
1117 os.unlink('.debugfsinfo')
1118 os.unlink('.debugfsinfo')
1118
1119
1119 def debugrebuildstate(ui, repo, rev="tip"):
1120 def debugrebuildstate(ui, repo, rev="tip"):
1120 """rebuild the dirstate as it would look like for the given revision"""
1121 """rebuild the dirstate as it would look like for the given revision"""
1121 ctx = cmdutil.revsingle(repo, rev)
1122 ctx = cmdutil.revsingle(repo, rev)
1122 wlock = repo.wlock()
1123 wlock = repo.wlock()
1123 try:
1124 try:
1124 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1125 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1125 finally:
1126 finally:
1126 wlock.release()
1127 wlock.release()
1127
1128
1128 def debugcheckstate(ui, repo):
1129 def debugcheckstate(ui, repo):
1129 """validate the correctness of the current dirstate"""
1130 """validate the correctness of the current dirstate"""
1130 parent1, parent2 = repo.dirstate.parents()
1131 parent1, parent2 = repo.dirstate.parents()
1131 m1 = repo[parent1].manifest()
1132 m1 = repo[parent1].manifest()
1132 m2 = repo[parent2].manifest()
1133 m2 = repo[parent2].manifest()
1133 errors = 0
1134 errors = 0
1134 for f in repo.dirstate:
1135 for f in repo.dirstate:
1135 state = repo.dirstate[f]
1136 state = repo.dirstate[f]
1136 if state in "nr" and f not in m1:
1137 if state in "nr" and f not in m1:
1137 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1138 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1138 errors += 1
1139 errors += 1
1139 if state in "a" and f in m1:
1140 if state in "a" and f in m1:
1140 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1141 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1141 errors += 1
1142 errors += 1
1142 if state in "m" and f not in m1 and f not in m2:
1143 if state in "m" and f not in m1 and f not in m2:
1143 ui.warn(_("%s in state %s, but not in either manifest\n") %
1144 ui.warn(_("%s in state %s, but not in either manifest\n") %
1144 (f, state))
1145 (f, state))
1145 errors += 1
1146 errors += 1
1146 for f in m1:
1147 for f in m1:
1147 state = repo.dirstate[f]
1148 state = repo.dirstate[f]
1148 if state not in "nrm":
1149 if state not in "nrm":
1149 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1150 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1150 errors += 1
1151 errors += 1
1151 if errors:
1152 if errors:
1152 error = _(".hg/dirstate inconsistent with current parent's manifest")
1153 error = _(".hg/dirstate inconsistent with current parent's manifest")
1153 raise util.Abort(error)
1154 raise util.Abort(error)
1154
1155
1155 def showconfig(ui, repo, *values, **opts):
1156 def showconfig(ui, repo, *values, **opts):
1156 """show combined config settings from all hgrc files
1157 """show combined config settings from all hgrc files
1157
1158
1158 With no arguments, print names and values of all config items.
1159 With no arguments, print names and values of all config items.
1159
1160
1160 With one argument of the form section.name, print just the value
1161 With one argument of the form section.name, print just the value
1161 of that config item.
1162 of that config item.
1162
1163
1163 With multiple arguments, print names and values of all config
1164 With multiple arguments, print names and values of all config
1164 items with matching section names.
1165 items with matching section names.
1165
1166
1166 With --debug, the source (filename and line number) is printed
1167 With --debug, the source (filename and line number) is printed
1167 for each config item.
1168 for each config item.
1168
1169
1169 Returns 0 on success.
1170 Returns 0 on success.
1170 """
1171 """
1171
1172
1172 for f in util.rcpath():
1173 for f in util.rcpath():
1173 ui.debug(_('read config from: %s\n') % f)
1174 ui.debug(_('read config from: %s\n') % f)
1174 untrusted = bool(opts.get('untrusted'))
1175 untrusted = bool(opts.get('untrusted'))
1175 if values:
1176 if values:
1176 sections = [v for v in values if '.' not in v]
1177 sections = [v for v in values if '.' not in v]
1177 items = [v for v in values if '.' in v]
1178 items = [v for v in values if '.' in v]
1178 if len(items) > 1 or items and sections:
1179 if len(items) > 1 or items and sections:
1179 raise util.Abort(_('only one config item permitted'))
1180 raise util.Abort(_('only one config item permitted'))
1180 for section, name, value in ui.walkconfig(untrusted=untrusted):
1181 for section, name, value in ui.walkconfig(untrusted=untrusted):
1181 sectname = section + '.' + name
1182 sectname = section + '.' + name
1182 if values:
1183 if values:
1183 for v in values:
1184 for v in values:
1184 if v == section:
1185 if v == section:
1185 ui.debug('%s: ' %
1186 ui.debug('%s: ' %
1186 ui.configsource(section, name, untrusted))
1187 ui.configsource(section, name, untrusted))
1187 ui.write('%s=%s\n' % (sectname, value))
1188 ui.write('%s=%s\n' % (sectname, value))
1188 elif v == sectname:
1189 elif v == sectname:
1189 ui.debug('%s: ' %
1190 ui.debug('%s: ' %
1190 ui.configsource(section, name, untrusted))
1191 ui.configsource(section, name, untrusted))
1191 ui.write(value, '\n')
1192 ui.write(value, '\n')
1192 else:
1193 else:
1193 ui.debug('%s: ' %
1194 ui.debug('%s: ' %
1194 ui.configsource(section, name, untrusted))
1195 ui.configsource(section, name, untrusted))
1195 ui.write('%s=%s\n' % (sectname, value))
1196 ui.write('%s=%s\n' % (sectname, value))
1196
1197
1197 def debugpushkey(ui, repopath, namespace, *keyinfo):
1198 def debugpushkey(ui, repopath, namespace, *keyinfo):
1198 '''access the pushkey key/value protocol
1199 '''access the pushkey key/value protocol
1199
1200
1200 With two args, list the keys in the given namespace.
1201 With two args, list the keys in the given namespace.
1201
1202
1202 With five args, set a key to new if it currently is set to old.
1203 With five args, set a key to new if it currently is set to old.
1203 Reports success or failure.
1204 Reports success or failure.
1204 '''
1205 '''
1205
1206
1206 target = hg.repository(ui, repopath)
1207 target = hg.repository(ui, repopath)
1207 if keyinfo:
1208 if keyinfo:
1208 key, old, new = keyinfo
1209 key, old, new = keyinfo
1209 r = target.pushkey(namespace, key, old, new)
1210 r = target.pushkey(namespace, key, old, new)
1210 ui.status(str(r) + '\n')
1211 ui.status(str(r) + '\n')
1211 return not r
1212 return not r
1212 else:
1213 else:
1213 for k, v in target.listkeys(namespace).iteritems():
1214 for k, v in target.listkeys(namespace).iteritems():
1214 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1215 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1215 v.encode('string-escape')))
1216 v.encode('string-escape')))
1216
1217
1217 def debugrevspec(ui, repo, expr):
1218 def debugrevspec(ui, repo, expr):
1218 '''parse and apply a revision specification'''
1219 '''parse and apply a revision specification'''
1219 if ui.verbose:
1220 if ui.verbose:
1220 tree = revset.parse(expr)
1221 tree = revset.parse(expr)
1221 ui.note(tree, "\n")
1222 ui.note(tree, "\n")
1222 func = revset.match(expr)
1223 func = revset.match(expr)
1223 for c in func(repo, range(len(repo))):
1224 for c in func(repo, range(len(repo))):
1224 ui.write("%s\n" % c)
1225 ui.write("%s\n" % c)
1225
1226
1226 def debugsetparents(ui, repo, rev1, rev2=None):
1227 def debugsetparents(ui, repo, rev1, rev2=None):
1227 """manually set the parents of the current working directory
1228 """manually set the parents of the current working directory
1228
1229
1229 This is useful for writing repository conversion tools, but should
1230 This is useful for writing repository conversion tools, but should
1230 be used with care.
1231 be used with care.
1231
1232
1232 Returns 0 on success.
1233 Returns 0 on success.
1233 """
1234 """
1234
1235
1235 r1 = cmdutil.revsingle(repo, rev1).node()
1236 r1 = cmdutil.revsingle(repo, rev1).node()
1236 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1237 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1237
1238
1238 wlock = repo.wlock()
1239 wlock = repo.wlock()
1239 try:
1240 try:
1240 repo.dirstate.setparents(r1, r2)
1241 repo.dirstate.setparents(r1, r2)
1241 finally:
1242 finally:
1242 wlock.release()
1243 wlock.release()
1243
1244
1244 def debugstate(ui, repo, nodates=None):
1245 def debugstate(ui, repo, nodates=None):
1245 """show the contents of the current dirstate"""
1246 """show the contents of the current dirstate"""
1246 timestr = ""
1247 timestr = ""
1247 showdate = not nodates
1248 showdate = not nodates
1248 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1249 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1249 if showdate:
1250 if showdate:
1250 if ent[3] == -1:
1251 if ent[3] == -1:
1251 # Pad or slice to locale representation
1252 # Pad or slice to locale representation
1252 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1253 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1253 time.localtime(0)))
1254 time.localtime(0)))
1254 timestr = 'unset'
1255 timestr = 'unset'
1255 timestr = (timestr[:locale_len] +
1256 timestr = (timestr[:locale_len] +
1256 ' ' * (locale_len - len(timestr)))
1257 ' ' * (locale_len - len(timestr)))
1257 else:
1258 else:
1258 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1259 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1259 time.localtime(ent[3]))
1260 time.localtime(ent[3]))
1260 if ent[1] & 020000:
1261 if ent[1] & 020000:
1261 mode = 'lnk'
1262 mode = 'lnk'
1262 else:
1263 else:
1263 mode = '%3o' % (ent[1] & 0777)
1264 mode = '%3o' % (ent[1] & 0777)
1264 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1265 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1265 for f in repo.dirstate.copies():
1266 for f in repo.dirstate.copies():
1266 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1267 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1267
1268
1268 def debugsub(ui, repo, rev=None):
1269 def debugsub(ui, repo, rev=None):
1269 ctx = cmdutil.revsingle(repo, rev, None)
1270 ctx = cmdutil.revsingle(repo, rev, None)
1270 for k, v in sorted(ctx.substate.items()):
1271 for k, v in sorted(ctx.substate.items()):
1271 ui.write('path %s\n' % k)
1272 ui.write('path %s\n' % k)
1272 ui.write(' source %s\n' % v[0])
1273 ui.write(' source %s\n' % v[0])
1273 ui.write(' revision %s\n' % v[1])
1274 ui.write(' revision %s\n' % v[1])
1274
1275
1275 def debugdag(ui, repo, file_=None, *revs, **opts):
1276 def debugdag(ui, repo, file_=None, *revs, **opts):
1276 """format the changelog or an index DAG as a concise textual description
1277 """format the changelog or an index DAG as a concise textual description
1277
1278
1278 If you pass a revlog index, the revlog's DAG is emitted. If you list
1279 If you pass a revlog index, the revlog's DAG is emitted. If you list
1279 revision numbers, they get labelled in the output as rN.
1280 revision numbers, they get labelled in the output as rN.
1280
1281
1281 Otherwise, the changelog DAG of the current repo is emitted.
1282 Otherwise, the changelog DAG of the current repo is emitted.
1282 """
1283 """
1283 spaces = opts.get('spaces')
1284 spaces = opts.get('spaces')
1284 dots = opts.get('dots')
1285 dots = opts.get('dots')
1285 if file_:
1286 if file_:
1286 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1287 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1287 revs = set((int(r) for r in revs))
1288 revs = set((int(r) for r in revs))
1288 def events():
1289 def events():
1289 for r in rlog:
1290 for r in rlog:
1290 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1291 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1291 if r in revs:
1292 if r in revs:
1292 yield 'l', (r, "r%i" % r)
1293 yield 'l', (r, "r%i" % r)
1293 elif repo:
1294 elif repo:
1294 cl = repo.changelog
1295 cl = repo.changelog
1295 tags = opts.get('tags')
1296 tags = opts.get('tags')
1296 branches = opts.get('branches')
1297 branches = opts.get('branches')
1297 if tags:
1298 if tags:
1298 labels = {}
1299 labels = {}
1299 for l, n in repo.tags().items():
1300 for l, n in repo.tags().items():
1300 labels.setdefault(cl.rev(n), []).append(l)
1301 labels.setdefault(cl.rev(n), []).append(l)
1301 def events():
1302 def events():
1302 b = "default"
1303 b = "default"
1303 for r in cl:
1304 for r in cl:
1304 if branches:
1305 if branches:
1305 newb = cl.read(cl.node(r))[5]['branch']
1306 newb = cl.read(cl.node(r))[5]['branch']
1306 if newb != b:
1307 if newb != b:
1307 yield 'a', newb
1308 yield 'a', newb
1308 b = newb
1309 b = newb
1309 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1310 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1310 if tags:
1311 if tags:
1311 ls = labels.get(r)
1312 ls = labels.get(r)
1312 if ls:
1313 if ls:
1313 for l in ls:
1314 for l in ls:
1314 yield 'l', (r, l)
1315 yield 'l', (r, l)
1315 else:
1316 else:
1316 raise util.Abort(_('need repo for changelog dag'))
1317 raise util.Abort(_('need repo for changelog dag'))
1317
1318
1318 for line in dagparser.dagtextlines(events(),
1319 for line in dagparser.dagtextlines(events(),
1319 addspaces=spaces,
1320 addspaces=spaces,
1320 wraplabels=True,
1321 wraplabels=True,
1321 wrapannotations=True,
1322 wrapannotations=True,
1322 wrapnonlinear=dots,
1323 wrapnonlinear=dots,
1323 usedots=dots,
1324 usedots=dots,
1324 maxlinewidth=70):
1325 maxlinewidth=70):
1325 ui.write(line)
1326 ui.write(line)
1326 ui.write("\n")
1327 ui.write("\n")
1327
1328
1328 def debugdata(ui, repo, file_, rev):
1329 def debugdata(ui, repo, file_, rev):
1329 """dump the contents of a data file revision"""
1330 """dump the contents of a data file revision"""
1330 r = None
1331 r = None
1331 if repo:
1332 if repo:
1332 filelog = repo.file(file_)
1333 filelog = repo.file(file_)
1333 if len(filelog):
1334 if len(filelog):
1334 r = filelog
1335 r = filelog
1335 if not r:
1336 if not r:
1336 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1337 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1337 try:
1338 try:
1338 ui.write(r.revision(r.lookup(rev)))
1339 ui.write(r.revision(r.lookup(rev)))
1339 except KeyError:
1340 except KeyError:
1340 raise util.Abort(_('invalid revision identifier %s') % rev)
1341 raise util.Abort(_('invalid revision identifier %s') % rev)
1341
1342
1342 def debugdate(ui, date, range=None, **opts):
1343 def debugdate(ui, date, range=None, **opts):
1343 """parse and display a date"""
1344 """parse and display a date"""
1344 if opts["extended"]:
1345 if opts["extended"]:
1345 d = util.parsedate(date, util.extendeddateformats)
1346 d = util.parsedate(date, util.extendeddateformats)
1346 else:
1347 else:
1347 d = util.parsedate(date)
1348 d = util.parsedate(date)
1348 ui.write("internal: %s %s\n" % d)
1349 ui.write("internal: %s %s\n" % d)
1349 ui.write("standard: %s\n" % util.datestr(d))
1350 ui.write("standard: %s\n" % util.datestr(d))
1350 if range:
1351 if range:
1351 m = util.matchdate(range)
1352 m = util.matchdate(range)
1352 ui.write("match: %s\n" % m(d[0]))
1353 ui.write("match: %s\n" % m(d[0]))
1353
1354
1354 def debugignore(ui, repo, *values, **opts):
1355 def debugignore(ui, repo, *values, **opts):
1355 """display the combined ignore pattern"""
1356 """display the combined ignore pattern"""
1356 ignore = repo.dirstate._ignore
1357 ignore = repo.dirstate._ignore
1357 ui.write("%s\n" % ignore.includepat)
1358 ui.write("%s\n" % ignore.includepat)
1358
1359
1359 def debugindex(ui, repo, file_, **opts):
1360 def debugindex(ui, repo, file_, **opts):
1360 """dump the contents of an index file"""
1361 """dump the contents of an index file"""
1361 r = None
1362 r = None
1362 if repo:
1363 if repo:
1363 filelog = repo.file(file_)
1364 filelog = repo.file(file_)
1364 if len(filelog):
1365 if len(filelog):
1365 r = filelog
1366 r = filelog
1366
1367
1367 format = opts.get('format', 0)
1368 format = opts.get('format', 0)
1368 if format not in (0, 1):
1369 if format not in (0, 1):
1369 raise util.Abort("unknown format %d" % format)
1370 raise util.Abort("unknown format %d" % format)
1370
1371
1371 if not r:
1372 if not r:
1372 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1373 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1373
1374
1374 if format == 0:
1375 if format == 0:
1375 ui.write(" rev offset length base linkrev"
1376 ui.write(" rev offset length base linkrev"
1376 " nodeid p1 p2\n")
1377 " nodeid p1 p2\n")
1377 elif format == 1:
1378 elif format == 1:
1378 ui.write(" rev flag offset length"
1379 ui.write(" rev flag offset length"
1379 " size base link p1 p2 nodeid\n")
1380 " size base link p1 p2 nodeid\n")
1380
1381
1381 for i in r:
1382 for i in r:
1382 node = r.node(i)
1383 node = r.node(i)
1383 if format == 0:
1384 if format == 0:
1384 try:
1385 try:
1385 pp = r.parents(node)
1386 pp = r.parents(node)
1386 except:
1387 except:
1387 pp = [nullid, nullid]
1388 pp = [nullid, nullid]
1388 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1389 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1389 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1390 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1390 short(node), short(pp[0]), short(pp[1])))
1391 short(node), short(pp[0]), short(pp[1])))
1391 elif format == 1:
1392 elif format == 1:
1392 pr = r.parentrevs(i)
1393 pr = r.parentrevs(i)
1393 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1394 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1394 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1395 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1395 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1396 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1396
1397
1397 def debugindexdot(ui, repo, file_):
1398 def debugindexdot(ui, repo, file_):
1398 """dump an index DAG as a graphviz dot file"""
1399 """dump an index DAG as a graphviz dot file"""
1399 r = None
1400 r = None
1400 if repo:
1401 if repo:
1401 filelog = repo.file(file_)
1402 filelog = repo.file(file_)
1402 if len(filelog):
1403 if len(filelog):
1403 r = filelog
1404 r = filelog
1404 if not r:
1405 if not r:
1405 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1406 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1406 ui.write("digraph G {\n")
1407 ui.write("digraph G {\n")
1407 for i in r:
1408 for i in r:
1408 node = r.node(i)
1409 node = r.node(i)
1409 pp = r.parents(node)
1410 pp = r.parents(node)
1410 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1411 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1411 if pp[1] != nullid:
1412 if pp[1] != nullid:
1412 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1413 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1413 ui.write("}\n")
1414 ui.write("}\n")
1414
1415
1415 def debuginstall(ui):
1416 def debuginstall(ui):
1416 '''test Mercurial installation
1417 '''test Mercurial installation
1417
1418
1418 Returns 0 on success.
1419 Returns 0 on success.
1419 '''
1420 '''
1420
1421
1421 def writetemp(contents):
1422 def writetemp(contents):
1422 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1423 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1423 f = os.fdopen(fd, "wb")
1424 f = os.fdopen(fd, "wb")
1424 f.write(contents)
1425 f.write(contents)
1425 f.close()
1426 f.close()
1426 return name
1427 return name
1427
1428
1428 problems = 0
1429 problems = 0
1429
1430
1430 # encoding
1431 # encoding
1431 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1432 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1432 try:
1433 try:
1433 encoding.fromlocal("test")
1434 encoding.fromlocal("test")
1434 except util.Abort, inst:
1435 except util.Abort, inst:
1435 ui.write(" %s\n" % inst)
1436 ui.write(" %s\n" % inst)
1436 ui.write(_(" (check that your locale is properly set)\n"))
1437 ui.write(_(" (check that your locale is properly set)\n"))
1437 problems += 1
1438 problems += 1
1438
1439
1439 # compiled modules
1440 # compiled modules
1440 ui.status(_("Checking installed modules (%s)...\n")
1441 ui.status(_("Checking installed modules (%s)...\n")
1441 % os.path.dirname(__file__))
1442 % os.path.dirname(__file__))
1442 try:
1443 try:
1443 import bdiff, mpatch, base85, osutil
1444 import bdiff, mpatch, base85, osutil
1444 except Exception, inst:
1445 except Exception, inst:
1445 ui.write(" %s\n" % inst)
1446 ui.write(" %s\n" % inst)
1446 ui.write(_(" One or more extensions could not be found"))
1447 ui.write(_(" One or more extensions could not be found"))
1447 ui.write(_(" (check that you compiled the extensions)\n"))
1448 ui.write(_(" (check that you compiled the extensions)\n"))
1448 problems += 1
1449 problems += 1
1449
1450
1450 # templates
1451 # templates
1451 ui.status(_("Checking templates...\n"))
1452 ui.status(_("Checking templates...\n"))
1452 try:
1453 try:
1453 import templater
1454 import templater
1454 templater.templater(templater.templatepath("map-cmdline.default"))
1455 templater.templater(templater.templatepath("map-cmdline.default"))
1455 except Exception, inst:
1456 except Exception, inst:
1456 ui.write(" %s\n" % inst)
1457 ui.write(" %s\n" % inst)
1457 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1458 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1458 problems += 1
1459 problems += 1
1459
1460
1460 # patch
1461 # patch
1461 ui.status(_("Checking patch...\n"))
1462 ui.status(_("Checking patch...\n"))
1462 patchproblems = 0
1463 patchproblems = 0
1463 a = "1\n2\n3\n4\n"
1464 a = "1\n2\n3\n4\n"
1464 b = "1\n2\n3\ninsert\n4\n"
1465 b = "1\n2\n3\ninsert\n4\n"
1465 fa = writetemp(a)
1466 fa = writetemp(a)
1466 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1467 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1467 os.path.basename(fa))
1468 os.path.basename(fa))
1468 fd = writetemp(d)
1469 fd = writetemp(d)
1469
1470
1470 files = {}
1471 files = {}
1471 try:
1472 try:
1472 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1473 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1473 except util.Abort, e:
1474 except util.Abort, e:
1474 ui.write(_(" patch call failed:\n"))
1475 ui.write(_(" patch call failed:\n"))
1475 ui.write(" " + str(e) + "\n")
1476 ui.write(" " + str(e) + "\n")
1476 patchproblems += 1
1477 patchproblems += 1
1477 else:
1478 else:
1478 if list(files) != [os.path.basename(fa)]:
1479 if list(files) != [os.path.basename(fa)]:
1479 ui.write(_(" unexpected patch output!\n"))
1480 ui.write(_(" unexpected patch output!\n"))
1480 patchproblems += 1
1481 patchproblems += 1
1481 a = open(fa).read()
1482 a = open(fa).read()
1482 if a != b:
1483 if a != b:
1483 ui.write(_(" patch test failed!\n"))
1484 ui.write(_(" patch test failed!\n"))
1484 patchproblems += 1
1485 patchproblems += 1
1485
1486
1486 if patchproblems:
1487 if patchproblems:
1487 if ui.config('ui', 'patch'):
1488 if ui.config('ui', 'patch'):
1488 ui.write(_(" (Current patch tool may be incompatible with patch,"
1489 ui.write(_(" (Current patch tool may be incompatible with patch,"
1489 " or misconfigured. Please check your configuration"
1490 " or misconfigured. Please check your configuration"
1490 " file)\n"))
1491 " file)\n"))
1491 else:
1492 else:
1492 ui.write(_(" Internal patcher failure, please report this error"
1493 ui.write(_(" Internal patcher failure, please report this error"
1493 " to http://mercurial.selenic.com/wiki/BugTracker\n"))
1494 " to http://mercurial.selenic.com/wiki/BugTracker\n"))
1494 problems += patchproblems
1495 problems += patchproblems
1495
1496
1496 os.unlink(fa)
1497 os.unlink(fa)
1497 os.unlink(fd)
1498 os.unlink(fd)
1498
1499
1499 # editor
1500 # editor
1500 ui.status(_("Checking commit editor...\n"))
1501 ui.status(_("Checking commit editor...\n"))
1501 editor = ui.geteditor()
1502 editor = ui.geteditor()
1502 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1503 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1503 if not cmdpath:
1504 if not cmdpath:
1504 if editor == 'vi':
1505 if editor == 'vi':
1505 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1506 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1506 ui.write(_(" (specify a commit editor in your configuration"
1507 ui.write(_(" (specify a commit editor in your configuration"
1507 " file)\n"))
1508 " file)\n"))
1508 else:
1509 else:
1509 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1510 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1510 ui.write(_(" (specify a commit editor in your configuration"
1511 ui.write(_(" (specify a commit editor in your configuration"
1511 " file)\n"))
1512 " file)\n"))
1512 problems += 1
1513 problems += 1
1513
1514
1514 # check username
1515 # check username
1515 ui.status(_("Checking username...\n"))
1516 ui.status(_("Checking username...\n"))
1516 try:
1517 try:
1517 ui.username()
1518 ui.username()
1518 except util.Abort, e:
1519 except util.Abort, e:
1519 ui.write(" %s\n" % e)
1520 ui.write(" %s\n" % e)
1520 ui.write(_(" (specify a username in your configuration file)\n"))
1521 ui.write(_(" (specify a username in your configuration file)\n"))
1521 problems += 1
1522 problems += 1
1522
1523
1523 if not problems:
1524 if not problems:
1524 ui.status(_("No problems detected\n"))
1525 ui.status(_("No problems detected\n"))
1525 else:
1526 else:
1526 ui.write(_("%s problems detected,"
1527 ui.write(_("%s problems detected,"
1527 " please check your install!\n") % problems)
1528 " please check your install!\n") % problems)
1528
1529
1529 return problems
1530 return problems
1530
1531
1531 def debugrename(ui, repo, file1, *pats, **opts):
1532 def debugrename(ui, repo, file1, *pats, **opts):
1532 """dump rename information"""
1533 """dump rename information"""
1533
1534
1534 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1535 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1535 m = cmdutil.match(repo, (file1,) + pats, opts)
1536 m = cmdutil.match(repo, (file1,) + pats, opts)
1536 for abs in ctx.walk(m):
1537 for abs in ctx.walk(m):
1537 fctx = ctx[abs]
1538 fctx = ctx[abs]
1538 o = fctx.filelog().renamed(fctx.filenode())
1539 o = fctx.filelog().renamed(fctx.filenode())
1539 rel = m.rel(abs)
1540 rel = m.rel(abs)
1540 if o:
1541 if o:
1541 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1542 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1542 else:
1543 else:
1543 ui.write(_("%s not renamed\n") % rel)
1544 ui.write(_("%s not renamed\n") % rel)
1544
1545
1545 def debugwalk(ui, repo, *pats, **opts):
1546 def debugwalk(ui, repo, *pats, **opts):
1546 """show how files match on given patterns"""
1547 """show how files match on given patterns"""
1547 m = cmdutil.match(repo, pats, opts)
1548 m = cmdutil.match(repo, pats, opts)
1548 items = list(repo.walk(m))
1549 items = list(repo.walk(m))
1549 if not items:
1550 if not items:
1550 return
1551 return
1551 fmt = 'f %%-%ds %%-%ds %%s' % (
1552 fmt = 'f %%-%ds %%-%ds %%s' % (
1552 max([len(abs) for abs in items]),
1553 max([len(abs) for abs in items]),
1553 max([len(m.rel(abs)) for abs in items]))
1554 max([len(m.rel(abs)) for abs in items]))
1554 for abs in items:
1555 for abs in items:
1555 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1556 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1556 ui.write("%s\n" % line.rstrip())
1557 ui.write("%s\n" % line.rstrip())
1557
1558
1558 def diff(ui, repo, *pats, **opts):
1559 def diff(ui, repo, *pats, **opts):
1559 """diff repository (or selected files)
1560 """diff repository (or selected files)
1560
1561
1561 Show differences between revisions for the specified files.
1562 Show differences between revisions for the specified files.
1562
1563
1563 Differences between files are shown using the unified diff format.
1564 Differences between files are shown using the unified diff format.
1564
1565
1565 .. note::
1566 .. note::
1566 diff may generate unexpected results for merges, as it will
1567 diff may generate unexpected results for merges, as it will
1567 default to comparing against the working directory's first
1568 default to comparing against the working directory's first
1568 parent changeset if no revisions are specified.
1569 parent changeset if no revisions are specified.
1569
1570
1570 When two revision arguments are given, then changes are shown
1571 When two revision arguments are given, then changes are shown
1571 between those revisions. If only one revision is specified then
1572 between those revisions. If only one revision is specified then
1572 that revision is compared to the working directory, and, when no
1573 that revision is compared to the working directory, and, when no
1573 revisions are specified, the working directory files are compared
1574 revisions are specified, the working directory files are compared
1574 to its parent.
1575 to its parent.
1575
1576
1576 Alternatively you can specify -c/--change with a revision to see
1577 Alternatively you can specify -c/--change with a revision to see
1577 the changes in that changeset relative to its first parent.
1578 the changes in that changeset relative to its first parent.
1578
1579
1579 Without the -a/--text option, diff will avoid generating diffs of
1580 Without the -a/--text option, diff will avoid generating diffs of
1580 files it detects as binary. With -a, diff will generate a diff
1581 files it detects as binary. With -a, diff will generate a diff
1581 anyway, probably with undesirable results.
1582 anyway, probably with undesirable results.
1582
1583
1583 Use the -g/--git option to generate diffs in the git extended diff
1584 Use the -g/--git option to generate diffs in the git extended diff
1584 format. For more information, read :hg:`help diffs`.
1585 format. For more information, read :hg:`help diffs`.
1585
1586
1586 Returns 0 on success.
1587 Returns 0 on success.
1587 """
1588 """
1588
1589
1589 revs = opts.get('rev')
1590 revs = opts.get('rev')
1590 change = opts.get('change')
1591 change = opts.get('change')
1591 stat = opts.get('stat')
1592 stat = opts.get('stat')
1592 reverse = opts.get('reverse')
1593 reverse = opts.get('reverse')
1593
1594
1594 if revs and change:
1595 if revs and change:
1595 msg = _('cannot specify --rev and --change at the same time')
1596 msg = _('cannot specify --rev and --change at the same time')
1596 raise util.Abort(msg)
1597 raise util.Abort(msg)
1597 elif change:
1598 elif change:
1598 node2 = repo.lookup(change)
1599 node2 = repo.lookup(change)
1599 node1 = repo[node2].parents()[0].node()
1600 node1 = repo[node2].parents()[0].node()
1600 else:
1601 else:
1601 node1, node2 = cmdutil.revpair(repo, revs)
1602 node1, node2 = cmdutil.revpair(repo, revs)
1602
1603
1603 if reverse:
1604 if reverse:
1604 node1, node2 = node2, node1
1605 node1, node2 = node2, node1
1605
1606
1606 diffopts = patch.diffopts(ui, opts)
1607 diffopts = patch.diffopts(ui, opts)
1607 m = cmdutil.match(repo, pats, opts)
1608 m = cmdutil.match(repo, pats, opts)
1608 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1609 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1609 listsubrepos=opts.get('subrepos'))
1610 listsubrepos=opts.get('subrepos'))
1610
1611
1611 def export(ui, repo, *changesets, **opts):
1612 def export(ui, repo, *changesets, **opts):
1612 """dump the header and diffs for one or more changesets
1613 """dump the header and diffs for one or more changesets
1613
1614
1614 Print the changeset header and diffs for one or more revisions.
1615 Print the changeset header and diffs for one or more revisions.
1615
1616
1616 The information shown in the changeset header is: author, date,
1617 The information shown in the changeset header is: author, date,
1617 branch name (if non-default), changeset hash, parent(s) and commit
1618 branch name (if non-default), changeset hash, parent(s) and commit
1618 comment.
1619 comment.
1619
1620
1620 .. note::
1621 .. note::
1621 export may generate unexpected diff output for merge
1622 export may generate unexpected diff output for merge
1622 changesets, as it will compare the merge changeset against its
1623 changesets, as it will compare the merge changeset against its
1623 first parent only.
1624 first parent only.
1624
1625
1625 Output may be to a file, in which case the name of the file is
1626 Output may be to a file, in which case the name of the file is
1626 given using a format string. The formatting rules are as follows:
1627 given using a format string. The formatting rules are as follows:
1627
1628
1628 :``%%``: literal "%" character
1629 :``%%``: literal "%" character
1629 :``%H``: changeset hash (40 hexadecimal digits)
1630 :``%H``: changeset hash (40 hexadecimal digits)
1630 :``%N``: number of patches being generated
1631 :``%N``: number of patches being generated
1631 :``%R``: changeset revision number
1632 :``%R``: changeset revision number
1632 :``%b``: basename of the exporting repository
1633 :``%b``: basename of the exporting repository
1633 :``%h``: short-form changeset hash (12 hexadecimal digits)
1634 :``%h``: short-form changeset hash (12 hexadecimal digits)
1634 :``%n``: zero-padded sequence number, starting at 1
1635 :``%n``: zero-padded sequence number, starting at 1
1635 :``%r``: zero-padded changeset revision number
1636 :``%r``: zero-padded changeset revision number
1636
1637
1637 Without the -a/--text option, export will avoid generating diffs
1638 Without the -a/--text option, export will avoid generating diffs
1638 of files it detects as binary. With -a, export will generate a
1639 of files it detects as binary. With -a, export will generate a
1639 diff anyway, probably with undesirable results.
1640 diff anyway, probably with undesirable results.
1640
1641
1641 Use the -g/--git option to generate diffs in the git extended diff
1642 Use the -g/--git option to generate diffs in the git extended diff
1642 format. See :hg:`help diffs` for more information.
1643 format. See :hg:`help diffs` for more information.
1643
1644
1644 With the --switch-parent option, the diff will be against the
1645 With the --switch-parent option, the diff will be against the
1645 second parent. It can be useful to review a merge.
1646 second parent. It can be useful to review a merge.
1646
1647
1647 Returns 0 on success.
1648 Returns 0 on success.
1648 """
1649 """
1649 changesets += tuple(opts.get('rev', []))
1650 changesets += tuple(opts.get('rev', []))
1650 if not changesets:
1651 if not changesets:
1651 raise util.Abort(_("export requires at least one changeset"))
1652 raise util.Abort(_("export requires at least one changeset"))
1652 revs = cmdutil.revrange(repo, changesets)
1653 revs = cmdutil.revrange(repo, changesets)
1653 if len(revs) > 1:
1654 if len(revs) > 1:
1654 ui.note(_('exporting patches:\n'))
1655 ui.note(_('exporting patches:\n'))
1655 else:
1656 else:
1656 ui.note(_('exporting patch:\n'))
1657 ui.note(_('exporting patch:\n'))
1657 cmdutil.export(repo, revs, template=opts.get('output'),
1658 cmdutil.export(repo, revs, template=opts.get('output'),
1658 switch_parent=opts.get('switch_parent'),
1659 switch_parent=opts.get('switch_parent'),
1659 opts=patch.diffopts(ui, opts))
1660 opts=patch.diffopts(ui, opts))
1660
1661
1661 def forget(ui, repo, *pats, **opts):
1662 def forget(ui, repo, *pats, **opts):
1662 """forget the specified files on the next commit
1663 """forget the specified files on the next commit
1663
1664
1664 Mark the specified files so they will no longer be tracked
1665 Mark the specified files so they will no longer be tracked
1665 after the next commit.
1666 after the next commit.
1666
1667
1667 This only removes files from the current branch, not from the
1668 This only removes files from the current branch, not from the
1668 entire project history, and it does not delete them from the
1669 entire project history, and it does not delete them from the
1669 working directory.
1670 working directory.
1670
1671
1671 To undo a forget before the next commit, see :hg:`add`.
1672 To undo a forget before the next commit, see :hg:`add`.
1672
1673
1673 Returns 0 on success.
1674 Returns 0 on success.
1674 """
1675 """
1675
1676
1676 if not pats:
1677 if not pats:
1677 raise util.Abort(_('no files specified'))
1678 raise util.Abort(_('no files specified'))
1678
1679
1679 m = cmdutil.match(repo, pats, opts)
1680 m = cmdutil.match(repo, pats, opts)
1680 s = repo.status(match=m, clean=True)
1681 s = repo.status(match=m, clean=True)
1681 forget = sorted(s[0] + s[1] + s[3] + s[6])
1682 forget = sorted(s[0] + s[1] + s[3] + s[6])
1682 errs = 0
1683 errs = 0
1683
1684
1684 for f in m.files():
1685 for f in m.files():
1685 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1686 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1686 ui.warn(_('not removing %s: file is already untracked\n')
1687 ui.warn(_('not removing %s: file is already untracked\n')
1687 % m.rel(f))
1688 % m.rel(f))
1688 errs = 1
1689 errs = 1
1689
1690
1690 for f in forget:
1691 for f in forget:
1691 if ui.verbose or not m.exact(f):
1692 if ui.verbose or not m.exact(f):
1692 ui.status(_('removing %s\n') % m.rel(f))
1693 ui.status(_('removing %s\n') % m.rel(f))
1693
1694
1694 repo[None].remove(forget, unlink=False)
1695 repo[None].remove(forget, unlink=False)
1695 return errs
1696 return errs
1696
1697
1697 def grep(ui, repo, pattern, *pats, **opts):
1698 def grep(ui, repo, pattern, *pats, **opts):
1698 """search for a pattern in specified files and revisions
1699 """search for a pattern in specified files and revisions
1699
1700
1700 Search revisions of files for a regular expression.
1701 Search revisions of files for a regular expression.
1701
1702
1702 This command behaves differently than Unix grep. It only accepts
1703 This command behaves differently than Unix grep. It only accepts
1703 Python/Perl regexps. It searches repository history, not the
1704 Python/Perl regexps. It searches repository history, not the
1704 working directory. It always prints the revision number in which a
1705 working directory. It always prints the revision number in which a
1705 match appears.
1706 match appears.
1706
1707
1707 By default, grep only prints output for the first revision of a
1708 By default, grep only prints output for the first revision of a
1708 file in which it finds a match. To get it to print every revision
1709 file in which it finds a match. To get it to print every revision
1709 that contains a change in match status ("-" for a match that
1710 that contains a change in match status ("-" for a match that
1710 becomes a non-match, or "+" for a non-match that becomes a match),
1711 becomes a non-match, or "+" for a non-match that becomes a match),
1711 use the --all flag.
1712 use the --all flag.
1712
1713
1713 Returns 0 if a match is found, 1 otherwise.
1714 Returns 0 if a match is found, 1 otherwise.
1714 """
1715 """
1715 reflags = 0
1716 reflags = 0
1716 if opts.get('ignore_case'):
1717 if opts.get('ignore_case'):
1717 reflags |= re.I
1718 reflags |= re.I
1718 try:
1719 try:
1719 regexp = re.compile(pattern, reflags)
1720 regexp = re.compile(pattern, reflags)
1720 except re.error, inst:
1721 except re.error, inst:
1721 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1722 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1722 return 1
1723 return 1
1723 sep, eol = ':', '\n'
1724 sep, eol = ':', '\n'
1724 if opts.get('print0'):
1725 if opts.get('print0'):
1725 sep = eol = '\0'
1726 sep = eol = '\0'
1726
1727
1727 getfile = util.lrucachefunc(repo.file)
1728 getfile = util.lrucachefunc(repo.file)
1728
1729
1729 def matchlines(body):
1730 def matchlines(body):
1730 begin = 0
1731 begin = 0
1731 linenum = 0
1732 linenum = 0
1732 while True:
1733 while True:
1733 match = regexp.search(body, begin)
1734 match = regexp.search(body, begin)
1734 if not match:
1735 if not match:
1735 break
1736 break
1736 mstart, mend = match.span()
1737 mstart, mend = match.span()
1737 linenum += body.count('\n', begin, mstart) + 1
1738 linenum += body.count('\n', begin, mstart) + 1
1738 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1739 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1739 begin = body.find('\n', mend) + 1 or len(body)
1740 begin = body.find('\n', mend) + 1 or len(body)
1740 lend = begin - 1
1741 lend = begin - 1
1741 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1742 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1742
1743
1743 class linestate(object):
1744 class linestate(object):
1744 def __init__(self, line, linenum, colstart, colend):
1745 def __init__(self, line, linenum, colstart, colend):
1745 self.line = line
1746 self.line = line
1746 self.linenum = linenum
1747 self.linenum = linenum
1747 self.colstart = colstart
1748 self.colstart = colstart
1748 self.colend = colend
1749 self.colend = colend
1749
1750
1750 def __hash__(self):
1751 def __hash__(self):
1751 return hash((self.linenum, self.line))
1752 return hash((self.linenum, self.line))
1752
1753
1753 def __eq__(self, other):
1754 def __eq__(self, other):
1754 return self.line == other.line
1755 return self.line == other.line
1755
1756
1756 matches = {}
1757 matches = {}
1757 copies = {}
1758 copies = {}
1758 def grepbody(fn, rev, body):
1759 def grepbody(fn, rev, body):
1759 matches[rev].setdefault(fn, [])
1760 matches[rev].setdefault(fn, [])
1760 m = matches[rev][fn]
1761 m = matches[rev][fn]
1761 for lnum, cstart, cend, line in matchlines(body):
1762 for lnum, cstart, cend, line in matchlines(body):
1762 s = linestate(line, lnum, cstart, cend)
1763 s = linestate(line, lnum, cstart, cend)
1763 m.append(s)
1764 m.append(s)
1764
1765
1765 def difflinestates(a, b):
1766 def difflinestates(a, b):
1766 sm = difflib.SequenceMatcher(None, a, b)
1767 sm = difflib.SequenceMatcher(None, a, b)
1767 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1768 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1768 if tag == 'insert':
1769 if tag == 'insert':
1769 for i in xrange(blo, bhi):
1770 for i in xrange(blo, bhi):
1770 yield ('+', b[i])
1771 yield ('+', b[i])
1771 elif tag == 'delete':
1772 elif tag == 'delete':
1772 for i in xrange(alo, ahi):
1773 for i in xrange(alo, ahi):
1773 yield ('-', a[i])
1774 yield ('-', a[i])
1774 elif tag == 'replace':
1775 elif tag == 'replace':
1775 for i in xrange(alo, ahi):
1776 for i in xrange(alo, ahi):
1776 yield ('-', a[i])
1777 yield ('-', a[i])
1777 for i in xrange(blo, bhi):
1778 for i in xrange(blo, bhi):
1778 yield ('+', b[i])
1779 yield ('+', b[i])
1779
1780
1780 def display(fn, ctx, pstates, states):
1781 def display(fn, ctx, pstates, states):
1781 rev = ctx.rev()
1782 rev = ctx.rev()
1782 datefunc = ui.quiet and util.shortdate or util.datestr
1783 datefunc = ui.quiet and util.shortdate or util.datestr
1783 found = False
1784 found = False
1784 filerevmatches = {}
1785 filerevmatches = {}
1785 if opts.get('all'):
1786 if opts.get('all'):
1786 iter = difflinestates(pstates, states)
1787 iter = difflinestates(pstates, states)
1787 else:
1788 else:
1788 iter = [('', l) for l in states]
1789 iter = [('', l) for l in states]
1789 for change, l in iter:
1790 for change, l in iter:
1790 cols = [fn, str(rev)]
1791 cols = [fn, str(rev)]
1791 before, match, after = None, None, None
1792 before, match, after = None, None, None
1792 if opts.get('line_number'):
1793 if opts.get('line_number'):
1793 cols.append(str(l.linenum))
1794 cols.append(str(l.linenum))
1794 if opts.get('all'):
1795 if opts.get('all'):
1795 cols.append(change)
1796 cols.append(change)
1796 if opts.get('user'):
1797 if opts.get('user'):
1797 cols.append(ui.shortuser(ctx.user()))
1798 cols.append(ui.shortuser(ctx.user()))
1798 if opts.get('date'):
1799 if opts.get('date'):
1799 cols.append(datefunc(ctx.date()))
1800 cols.append(datefunc(ctx.date()))
1800 if opts.get('files_with_matches'):
1801 if opts.get('files_with_matches'):
1801 c = (fn, rev)
1802 c = (fn, rev)
1802 if c in filerevmatches:
1803 if c in filerevmatches:
1803 continue
1804 continue
1804 filerevmatches[c] = 1
1805 filerevmatches[c] = 1
1805 else:
1806 else:
1806 before = l.line[:l.colstart]
1807 before = l.line[:l.colstart]
1807 match = l.line[l.colstart:l.colend]
1808 match = l.line[l.colstart:l.colend]
1808 after = l.line[l.colend:]
1809 after = l.line[l.colend:]
1809 ui.write(sep.join(cols))
1810 ui.write(sep.join(cols))
1810 if before is not None:
1811 if before is not None:
1811 ui.write(sep + before)
1812 ui.write(sep + before)
1812 ui.write(match, label='grep.match')
1813 ui.write(match, label='grep.match')
1813 ui.write(after)
1814 ui.write(after)
1814 ui.write(eol)
1815 ui.write(eol)
1815 found = True
1816 found = True
1816 return found
1817 return found
1817
1818
1818 skip = {}
1819 skip = {}
1819 revfiles = {}
1820 revfiles = {}
1820 matchfn = cmdutil.match(repo, pats, opts)
1821 matchfn = cmdutil.match(repo, pats, opts)
1821 found = False
1822 found = False
1822 follow = opts.get('follow')
1823 follow = opts.get('follow')
1823
1824
1824 def prep(ctx, fns):
1825 def prep(ctx, fns):
1825 rev = ctx.rev()
1826 rev = ctx.rev()
1826 pctx = ctx.parents()[0]
1827 pctx = ctx.parents()[0]
1827 parent = pctx.rev()
1828 parent = pctx.rev()
1828 matches.setdefault(rev, {})
1829 matches.setdefault(rev, {})
1829 matches.setdefault(parent, {})
1830 matches.setdefault(parent, {})
1830 files = revfiles.setdefault(rev, [])
1831 files = revfiles.setdefault(rev, [])
1831 for fn in fns:
1832 for fn in fns:
1832 flog = getfile(fn)
1833 flog = getfile(fn)
1833 try:
1834 try:
1834 fnode = ctx.filenode(fn)
1835 fnode = ctx.filenode(fn)
1835 except error.LookupError:
1836 except error.LookupError:
1836 continue
1837 continue
1837
1838
1838 copied = flog.renamed(fnode)
1839 copied = flog.renamed(fnode)
1839 copy = follow and copied and copied[0]
1840 copy = follow and copied and copied[0]
1840 if copy:
1841 if copy:
1841 copies.setdefault(rev, {})[fn] = copy
1842 copies.setdefault(rev, {})[fn] = copy
1842 if fn in skip:
1843 if fn in skip:
1843 if copy:
1844 if copy:
1844 skip[copy] = True
1845 skip[copy] = True
1845 continue
1846 continue
1846 files.append(fn)
1847 files.append(fn)
1847
1848
1848 if fn not in matches[rev]:
1849 if fn not in matches[rev]:
1849 grepbody(fn, rev, flog.read(fnode))
1850 grepbody(fn, rev, flog.read(fnode))
1850
1851
1851 pfn = copy or fn
1852 pfn = copy or fn
1852 if pfn not in matches[parent]:
1853 if pfn not in matches[parent]:
1853 try:
1854 try:
1854 fnode = pctx.filenode(pfn)
1855 fnode = pctx.filenode(pfn)
1855 grepbody(pfn, parent, flog.read(fnode))
1856 grepbody(pfn, parent, flog.read(fnode))
1856 except error.LookupError:
1857 except error.LookupError:
1857 pass
1858 pass
1858
1859
1859 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1860 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1860 rev = ctx.rev()
1861 rev = ctx.rev()
1861 parent = ctx.parents()[0].rev()
1862 parent = ctx.parents()[0].rev()
1862 for fn in sorted(revfiles.get(rev, [])):
1863 for fn in sorted(revfiles.get(rev, [])):
1863 states = matches[rev][fn]
1864 states = matches[rev][fn]
1864 copy = copies.get(rev, {}).get(fn)
1865 copy = copies.get(rev, {}).get(fn)
1865 if fn in skip:
1866 if fn in skip:
1866 if copy:
1867 if copy:
1867 skip[copy] = True
1868 skip[copy] = True
1868 continue
1869 continue
1869 pstates = matches.get(parent, {}).get(copy or fn, [])
1870 pstates = matches.get(parent, {}).get(copy or fn, [])
1870 if pstates or states:
1871 if pstates or states:
1871 r = display(fn, ctx, pstates, states)
1872 r = display(fn, ctx, pstates, states)
1872 found = found or r
1873 found = found or r
1873 if r and not opts.get('all'):
1874 if r and not opts.get('all'):
1874 skip[fn] = True
1875 skip[fn] = True
1875 if copy:
1876 if copy:
1876 skip[copy] = True
1877 skip[copy] = True
1877 del matches[rev]
1878 del matches[rev]
1878 del revfiles[rev]
1879 del revfiles[rev]
1879
1880
1880 return not found
1881 return not found
1881
1882
1882 def heads(ui, repo, *branchrevs, **opts):
1883 def heads(ui, repo, *branchrevs, **opts):
1883 """show current repository heads or show branch heads
1884 """show current repository heads or show branch heads
1884
1885
1885 With no arguments, show all repository branch heads.
1886 With no arguments, show all repository branch heads.
1886
1887
1887 Repository "heads" are changesets with no child changesets. They are
1888 Repository "heads" are changesets with no child changesets. They are
1888 where development generally takes place and are the usual targets
1889 where development generally takes place and are the usual targets
1889 for update and merge operations. Branch heads are changesets that have
1890 for update and merge operations. Branch heads are changesets that have
1890 no child changeset on the same branch.
1891 no child changeset on the same branch.
1891
1892
1892 If one or more REVs are given, only branch heads on the branches
1893 If one or more REVs are given, only branch heads on the branches
1893 associated with the specified changesets are shown.
1894 associated with the specified changesets are shown.
1894
1895
1895 If -c/--closed is specified, also show branch heads marked closed
1896 If -c/--closed is specified, also show branch heads marked closed
1896 (see :hg:`commit --close-branch`).
1897 (see :hg:`commit --close-branch`).
1897
1898
1898 If STARTREV is specified, only those heads that are descendants of
1899 If STARTREV is specified, only those heads that are descendants of
1899 STARTREV will be displayed.
1900 STARTREV will be displayed.
1900
1901
1901 If -t/--topo is specified, named branch mechanics will be ignored and only
1902 If -t/--topo is specified, named branch mechanics will be ignored and only
1902 changesets without children will be shown.
1903 changesets without children will be shown.
1903
1904
1904 Returns 0 if matching heads are found, 1 if not.
1905 Returns 0 if matching heads are found, 1 if not.
1905 """
1906 """
1906
1907
1907 start = None
1908 start = None
1908 if 'rev' in opts:
1909 if 'rev' in opts:
1909 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1910 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1910
1911
1911 if opts.get('topo'):
1912 if opts.get('topo'):
1912 heads = [repo[h] for h in repo.heads(start)]
1913 heads = [repo[h] for h in repo.heads(start)]
1913 else:
1914 else:
1914 heads = []
1915 heads = []
1915 for b, ls in repo.branchmap().iteritems():
1916 for b, ls in repo.branchmap().iteritems():
1916 if start is None:
1917 if start is None:
1917 heads += [repo[h] for h in ls]
1918 heads += [repo[h] for h in ls]
1918 continue
1919 continue
1919 startrev = repo.changelog.rev(start)
1920 startrev = repo.changelog.rev(start)
1920 descendants = set(repo.changelog.descendants(startrev))
1921 descendants = set(repo.changelog.descendants(startrev))
1921 descendants.add(startrev)
1922 descendants.add(startrev)
1922 rev = repo.changelog.rev
1923 rev = repo.changelog.rev
1923 heads += [repo[h] for h in ls if rev(h) in descendants]
1924 heads += [repo[h] for h in ls if rev(h) in descendants]
1924
1925
1925 if branchrevs:
1926 if branchrevs:
1926 branches = set(repo[br].branch() for br in branchrevs)
1927 branches = set(repo[br].branch() for br in branchrevs)
1927 heads = [h for h in heads if h.branch() in branches]
1928 heads = [h for h in heads if h.branch() in branches]
1928
1929
1929 if not opts.get('closed'):
1930 if not opts.get('closed'):
1930 heads = [h for h in heads if not h.extra().get('close')]
1931 heads = [h for h in heads if not h.extra().get('close')]
1931
1932
1932 if opts.get('active') and branchrevs:
1933 if opts.get('active') and branchrevs:
1933 dagheads = repo.heads(start)
1934 dagheads = repo.heads(start)
1934 heads = [h for h in heads if h.node() in dagheads]
1935 heads = [h for h in heads if h.node() in dagheads]
1935
1936
1936 if branchrevs:
1937 if branchrevs:
1937 haveheads = set(h.branch() for h in heads)
1938 haveheads = set(h.branch() for h in heads)
1938 if branches - haveheads:
1939 if branches - haveheads:
1939 headless = ', '.join(b for b in branches - haveheads)
1940 headless = ', '.join(b for b in branches - haveheads)
1940 msg = _('no open branch heads found on branches %s')
1941 msg = _('no open branch heads found on branches %s')
1941 if opts.get('rev'):
1942 if opts.get('rev'):
1942 msg += _(' (started at %s)' % opts['rev'])
1943 msg += _(' (started at %s)' % opts['rev'])
1943 ui.warn((msg + '\n') % headless)
1944 ui.warn((msg + '\n') % headless)
1944
1945
1945 if not heads:
1946 if not heads:
1946 return 1
1947 return 1
1947
1948
1948 heads = sorted(heads, key=lambda x: -x.rev())
1949 heads = sorted(heads, key=lambda x: -x.rev())
1949 displayer = cmdutil.show_changeset(ui, repo, opts)
1950 displayer = cmdutil.show_changeset(ui, repo, opts)
1950 for ctx in heads:
1951 for ctx in heads:
1951 displayer.show(ctx)
1952 displayer.show(ctx)
1952 displayer.close()
1953 displayer.close()
1953
1954
1954 def help_(ui, name=None, with_version=False, unknowncmd=False):
1955 def help_(ui, name=None, with_version=False, unknowncmd=False):
1955 """show help for a given topic or a help overview
1956 """show help for a given topic or a help overview
1956
1957
1957 With no arguments, print a list of commands with short help messages.
1958 With no arguments, print a list of commands with short help messages.
1958
1959
1959 Given a topic, extension, or command name, print help for that
1960 Given a topic, extension, or command name, print help for that
1960 topic.
1961 topic.
1961
1962
1962 Returns 0 if successful.
1963 Returns 0 if successful.
1963 """
1964 """
1964 option_lists = []
1965 option_lists = []
1965 textwidth = ui.termwidth() - 2
1966 textwidth = ui.termwidth() - 2
1966
1967
1967 def addglobalopts(aliases):
1968 def addglobalopts(aliases):
1968 if ui.verbose:
1969 if ui.verbose:
1969 option_lists.append((_("global options:"), globalopts))
1970 option_lists.append((_("global options:"), globalopts))
1970 if name == 'shortlist':
1971 if name == 'shortlist':
1971 option_lists.append((_('use "hg help" for the full list '
1972 option_lists.append((_('use "hg help" for the full list '
1972 'of commands'), ()))
1973 'of commands'), ()))
1973 else:
1974 else:
1974 if name == 'shortlist':
1975 if name == 'shortlist':
1975 msg = _('use "hg help" for the full list of commands '
1976 msg = _('use "hg help" for the full list of commands '
1976 'or "hg -v" for details')
1977 'or "hg -v" for details')
1977 elif aliases:
1978 elif aliases:
1978 msg = _('use "hg -v help%s" to show builtin aliases and '
1979 msg = _('use "hg -v help%s" to show builtin aliases and '
1979 'global options') % (name and " " + name or "")
1980 'global options') % (name and " " + name or "")
1980 else:
1981 else:
1981 msg = _('use "hg -v help %s" to show global options') % name
1982 msg = _('use "hg -v help %s" to show global options') % name
1982 option_lists.append((msg, ()))
1983 option_lists.append((msg, ()))
1983
1984
1984 def helpcmd(name):
1985 def helpcmd(name):
1985 if with_version:
1986 if with_version:
1986 version_(ui)
1987 version_(ui)
1987 ui.write('\n')
1988 ui.write('\n')
1988
1989
1989 try:
1990 try:
1990 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1991 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1991 except error.AmbiguousCommand, inst:
1992 except error.AmbiguousCommand, inst:
1992 # py3k fix: except vars can't be used outside the scope of the
1993 # py3k fix: except vars can't be used outside the scope of the
1993 # except block, nor can be used inside a lambda. python issue4617
1994 # except block, nor can be used inside a lambda. python issue4617
1994 prefix = inst.args[0]
1995 prefix = inst.args[0]
1995 select = lambda c: c.lstrip('^').startswith(prefix)
1996 select = lambda c: c.lstrip('^').startswith(prefix)
1996 helplist(_('list of commands:\n\n'), select)
1997 helplist(_('list of commands:\n\n'), select)
1997 return
1998 return
1998
1999
1999 # check if it's an invalid alias and display its error if it is
2000 # check if it's an invalid alias and display its error if it is
2000 if getattr(entry[0], 'badalias', False):
2001 if getattr(entry[0], 'badalias', False):
2001 if not unknowncmd:
2002 if not unknowncmd:
2002 entry[0](ui)
2003 entry[0](ui)
2003 return
2004 return
2004
2005
2005 # synopsis
2006 # synopsis
2006 if len(entry) > 2:
2007 if len(entry) > 2:
2007 if entry[2].startswith('hg'):
2008 if entry[2].startswith('hg'):
2008 ui.write("%s\n" % entry[2])
2009 ui.write("%s\n" % entry[2])
2009 else:
2010 else:
2010 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2011 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2011 else:
2012 else:
2012 ui.write('hg %s\n' % aliases[0])
2013 ui.write('hg %s\n' % aliases[0])
2013
2014
2014 # aliases
2015 # aliases
2015 if not ui.quiet and len(aliases) > 1:
2016 if not ui.quiet and len(aliases) > 1:
2016 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2017 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2017
2018
2018 # description
2019 # description
2019 doc = gettext(entry[0].__doc__)
2020 doc = gettext(entry[0].__doc__)
2020 if not doc:
2021 if not doc:
2021 doc = _("(no help text available)")
2022 doc = _("(no help text available)")
2022 if hasattr(entry[0], 'definition'): # aliased command
2023 if hasattr(entry[0], 'definition'): # aliased command
2023 if entry[0].definition.startswith('!'): # shell alias
2024 if entry[0].definition.startswith('!'): # shell alias
2024 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2025 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2025 else:
2026 else:
2026 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2027 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2027 if ui.quiet:
2028 if ui.quiet:
2028 doc = doc.splitlines()[0]
2029 doc = doc.splitlines()[0]
2029 keep = ui.verbose and ['verbose'] or []
2030 keep = ui.verbose and ['verbose'] or []
2030 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2031 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2031 ui.write("\n%s\n" % formatted)
2032 ui.write("\n%s\n" % formatted)
2032 if pruned:
2033 if pruned:
2033 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2034 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2034
2035
2035 if not ui.quiet:
2036 if not ui.quiet:
2036 # options
2037 # options
2037 if entry[1]:
2038 if entry[1]:
2038 option_lists.append((_("options:\n"), entry[1]))
2039 option_lists.append((_("options:\n"), entry[1]))
2039
2040
2040 addglobalopts(False)
2041 addglobalopts(False)
2041
2042
2042 def helplist(header, select=None):
2043 def helplist(header, select=None):
2043 h = {}
2044 h = {}
2044 cmds = {}
2045 cmds = {}
2045 for c, e in table.iteritems():
2046 for c, e in table.iteritems():
2046 f = c.split("|", 1)[0]
2047 f = c.split("|", 1)[0]
2047 if select and not select(f):
2048 if select and not select(f):
2048 continue
2049 continue
2049 if (not select and name != 'shortlist' and
2050 if (not select and name != 'shortlist' and
2050 e[0].__module__ != __name__):
2051 e[0].__module__ != __name__):
2051 continue
2052 continue
2052 if name == "shortlist" and not f.startswith("^"):
2053 if name == "shortlist" and not f.startswith("^"):
2053 continue
2054 continue
2054 f = f.lstrip("^")
2055 f = f.lstrip("^")
2055 if not ui.debugflag and f.startswith("debug"):
2056 if not ui.debugflag and f.startswith("debug"):
2056 continue
2057 continue
2057 doc = e[0].__doc__
2058 doc = e[0].__doc__
2058 if doc and 'DEPRECATED' in doc and not ui.verbose:
2059 if doc and 'DEPRECATED' in doc and not ui.verbose:
2059 continue
2060 continue
2060 doc = gettext(doc)
2061 doc = gettext(doc)
2061 if not doc:
2062 if not doc:
2062 doc = _("(no help text available)")
2063 doc = _("(no help text available)")
2063 h[f] = doc.splitlines()[0].rstrip()
2064 h[f] = doc.splitlines()[0].rstrip()
2064 cmds[f] = c.lstrip("^")
2065 cmds[f] = c.lstrip("^")
2065
2066
2066 if not h:
2067 if not h:
2067 ui.status(_('no commands defined\n'))
2068 ui.status(_('no commands defined\n'))
2068 return
2069 return
2069
2070
2070 ui.status(header)
2071 ui.status(header)
2071 fns = sorted(h)
2072 fns = sorted(h)
2072 m = max(map(len, fns))
2073 m = max(map(len, fns))
2073 for f in fns:
2074 for f in fns:
2074 if ui.verbose:
2075 if ui.verbose:
2075 commands = cmds[f].replace("|",", ")
2076 commands = cmds[f].replace("|",", ")
2076 ui.write(" %s:\n %s\n"%(commands, h[f]))
2077 ui.write(" %s:\n %s\n"%(commands, h[f]))
2077 else:
2078 else:
2078 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2079 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2079 initindent=' %-*s ' % (m, f),
2080 initindent=' %-*s ' % (m, f),
2080 hangindent=' ' * (m + 4))))
2081 hangindent=' ' * (m + 4))))
2081
2082
2082 if not ui.quiet:
2083 if not ui.quiet:
2083 addglobalopts(True)
2084 addglobalopts(True)
2084
2085
2085 def helptopic(name):
2086 def helptopic(name):
2086 for names, header, doc in help.helptable:
2087 for names, header, doc in help.helptable:
2087 if name in names:
2088 if name in names:
2088 break
2089 break
2089 else:
2090 else:
2090 raise error.UnknownCommand(name)
2091 raise error.UnknownCommand(name)
2091
2092
2092 # description
2093 # description
2093 if not doc:
2094 if not doc:
2094 doc = _("(no help text available)")
2095 doc = _("(no help text available)")
2095 if hasattr(doc, '__call__'):
2096 if hasattr(doc, '__call__'):
2096 doc = doc()
2097 doc = doc()
2097
2098
2098 ui.write("%s\n\n" % header)
2099 ui.write("%s\n\n" % header)
2099 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2100 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2100
2101
2101 def helpext(name):
2102 def helpext(name):
2102 try:
2103 try:
2103 mod = extensions.find(name)
2104 mod = extensions.find(name)
2104 doc = gettext(mod.__doc__) or _('no help text available')
2105 doc = gettext(mod.__doc__) or _('no help text available')
2105 except KeyError:
2106 except KeyError:
2106 mod = None
2107 mod = None
2107 doc = extensions.disabledext(name)
2108 doc = extensions.disabledext(name)
2108 if not doc:
2109 if not doc:
2109 raise error.UnknownCommand(name)
2110 raise error.UnknownCommand(name)
2110
2111
2111 if '\n' not in doc:
2112 if '\n' not in doc:
2112 head, tail = doc, ""
2113 head, tail = doc, ""
2113 else:
2114 else:
2114 head, tail = doc.split('\n', 1)
2115 head, tail = doc.split('\n', 1)
2115 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2116 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2116 if tail:
2117 if tail:
2117 ui.write(minirst.format(tail, textwidth))
2118 ui.write(minirst.format(tail, textwidth))
2118 ui.status('\n\n')
2119 ui.status('\n\n')
2119
2120
2120 if mod:
2121 if mod:
2121 try:
2122 try:
2122 ct = mod.cmdtable
2123 ct = mod.cmdtable
2123 except AttributeError:
2124 except AttributeError:
2124 ct = {}
2125 ct = {}
2125 modcmds = set([c.split('|', 1)[0] for c in ct])
2126 modcmds = set([c.split('|', 1)[0] for c in ct])
2126 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2127 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2127 else:
2128 else:
2128 ui.write(_('use "hg help extensions" for information on enabling '
2129 ui.write(_('use "hg help extensions" for information on enabling '
2129 'extensions\n'))
2130 'extensions\n'))
2130
2131
2131 def helpextcmd(name):
2132 def helpextcmd(name):
2132 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2133 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2133 doc = gettext(mod.__doc__).splitlines()[0]
2134 doc = gettext(mod.__doc__).splitlines()[0]
2134
2135
2135 msg = help.listexts(_("'%s' is provided by the following "
2136 msg = help.listexts(_("'%s' is provided by the following "
2136 "extension:") % cmd, {ext: doc}, len(ext),
2137 "extension:") % cmd, {ext: doc}, len(ext),
2137 indent=4)
2138 indent=4)
2138 ui.write(minirst.format(msg, textwidth))
2139 ui.write(minirst.format(msg, textwidth))
2139 ui.write('\n\n')
2140 ui.write('\n\n')
2140 ui.write(_('use "hg help extensions" for information on enabling '
2141 ui.write(_('use "hg help extensions" for information on enabling '
2141 'extensions\n'))
2142 'extensions\n'))
2142
2143
2143 help.addtopichook('revsets', revset.makedoc)
2144 help.addtopichook('revsets', revset.makedoc)
2144
2145
2145 if name and name != 'shortlist':
2146 if name and name != 'shortlist':
2146 i = None
2147 i = None
2147 if unknowncmd:
2148 if unknowncmd:
2148 queries = (helpextcmd,)
2149 queries = (helpextcmd,)
2149 else:
2150 else:
2150 queries = (helptopic, helpcmd, helpext, helpextcmd)
2151 queries = (helptopic, helpcmd, helpext, helpextcmd)
2151 for f in queries:
2152 for f in queries:
2152 try:
2153 try:
2153 f(name)
2154 f(name)
2154 i = None
2155 i = None
2155 break
2156 break
2156 except error.UnknownCommand, inst:
2157 except error.UnknownCommand, inst:
2157 i = inst
2158 i = inst
2158 if i:
2159 if i:
2159 raise i
2160 raise i
2160
2161
2161 else:
2162 else:
2162 # program name
2163 # program name
2163 if ui.verbose or with_version:
2164 if ui.verbose or with_version:
2164 version_(ui)
2165 version_(ui)
2165 else:
2166 else:
2166 ui.status(_("Mercurial Distributed SCM\n"))
2167 ui.status(_("Mercurial Distributed SCM\n"))
2167 ui.status('\n')
2168 ui.status('\n')
2168
2169
2169 # list of commands
2170 # list of commands
2170 if name == "shortlist":
2171 if name == "shortlist":
2171 header = _('basic commands:\n\n')
2172 header = _('basic commands:\n\n')
2172 else:
2173 else:
2173 header = _('list of commands:\n\n')
2174 header = _('list of commands:\n\n')
2174
2175
2175 helplist(header)
2176 helplist(header)
2176 if name != 'shortlist':
2177 if name != 'shortlist':
2177 exts, maxlength = extensions.enabled()
2178 exts, maxlength = extensions.enabled()
2178 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2179 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2179 if text:
2180 if text:
2180 ui.write("\n%s\n" % minirst.format(text, textwidth))
2181 ui.write("\n%s\n" % minirst.format(text, textwidth))
2181
2182
2182 # list all option lists
2183 # list all option lists
2183 opt_output = []
2184 opt_output = []
2184 multioccur = False
2185 multioccur = False
2185 for title, options in option_lists:
2186 for title, options in option_lists:
2186 opt_output.append(("\n%s" % title, None))
2187 opt_output.append(("\n%s" % title, None))
2187 for option in options:
2188 for option in options:
2188 if len(option) == 5:
2189 if len(option) == 5:
2189 shortopt, longopt, default, desc, optlabel = option
2190 shortopt, longopt, default, desc, optlabel = option
2190 else:
2191 else:
2191 shortopt, longopt, default, desc = option
2192 shortopt, longopt, default, desc = option
2192 optlabel = _("VALUE") # default label
2193 optlabel = _("VALUE") # default label
2193
2194
2194 if _("DEPRECATED") in desc and not ui.verbose:
2195 if _("DEPRECATED") in desc and not ui.verbose:
2195 continue
2196 continue
2196 if isinstance(default, list):
2197 if isinstance(default, list):
2197 numqualifier = " %s [+]" % optlabel
2198 numqualifier = " %s [+]" % optlabel
2198 multioccur = True
2199 multioccur = True
2199 elif (default is not None) and not isinstance(default, bool):
2200 elif (default is not None) and not isinstance(default, bool):
2200 numqualifier = " %s" % optlabel
2201 numqualifier = " %s" % optlabel
2201 else:
2202 else:
2202 numqualifier = ""
2203 numqualifier = ""
2203 opt_output.append(("%2s%s" %
2204 opt_output.append(("%2s%s" %
2204 (shortopt and "-%s" % shortopt,
2205 (shortopt and "-%s" % shortopt,
2205 longopt and " --%s%s" %
2206 longopt and " --%s%s" %
2206 (longopt, numqualifier)),
2207 (longopt, numqualifier)),
2207 "%s%s" % (desc,
2208 "%s%s" % (desc,
2208 default
2209 default
2209 and _(" (default: %s)") % default
2210 and _(" (default: %s)") % default
2210 or "")))
2211 or "")))
2211 if multioccur:
2212 if multioccur:
2212 msg = _("\n[+] marked option can be specified multiple times")
2213 msg = _("\n[+] marked option can be specified multiple times")
2213 if ui.verbose and name != 'shortlist':
2214 if ui.verbose and name != 'shortlist':
2214 opt_output.append((msg, None))
2215 opt_output.append((msg, None))
2215 else:
2216 else:
2216 opt_output.insert(-1, (msg, None))
2217 opt_output.insert(-1, (msg, None))
2217
2218
2218 if not name:
2219 if not name:
2219 ui.write(_("\nadditional help topics:\n\n"))
2220 ui.write(_("\nadditional help topics:\n\n"))
2220 topics = []
2221 topics = []
2221 for names, header, doc in help.helptable:
2222 for names, header, doc in help.helptable:
2222 topics.append((sorted(names, key=len, reverse=True)[0], header))
2223 topics.append((sorted(names, key=len, reverse=True)[0], header))
2223 topics_len = max([len(s[0]) for s in topics])
2224 topics_len = max([len(s[0]) for s in topics])
2224 for t, desc in topics:
2225 for t, desc in topics:
2225 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2226 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2226
2227
2227 if opt_output:
2228 if opt_output:
2228 colwidth = encoding.colwidth
2229 colwidth = encoding.colwidth
2229 # normalize: (opt or message, desc or None, width of opt)
2230 # normalize: (opt or message, desc or None, width of opt)
2230 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2231 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2231 for opt, desc in opt_output]
2232 for opt, desc in opt_output]
2232 hanging = max([e[2] for e in entries])
2233 hanging = max([e[2] for e in entries])
2233 for opt, desc, width in entries:
2234 for opt, desc, width in entries:
2234 if desc:
2235 if desc:
2235 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2236 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2236 hangindent = ' ' * (hanging + 3)
2237 hangindent = ' ' * (hanging + 3)
2237 ui.write('%s\n' % (util.wrap(desc, textwidth,
2238 ui.write('%s\n' % (util.wrap(desc, textwidth,
2238 initindent=initindent,
2239 initindent=initindent,
2239 hangindent=hangindent)))
2240 hangindent=hangindent)))
2240 else:
2241 else:
2241 ui.write("%s\n" % opt)
2242 ui.write("%s\n" % opt)
2242
2243
2243 def identify(ui, repo, source=None,
2244 def identify(ui, repo, source=None,
2244 rev=None, num=None, id=None, branch=None, tags=None):
2245 rev=None, num=None, id=None, branch=None, tags=None):
2245 """identify the working copy or specified revision
2246 """identify the working copy or specified revision
2246
2247
2247 With no revision, print a summary of the current state of the
2248 With no revision, print a summary of the current state of the
2248 repository.
2249 repository.
2249
2250
2250 Specifying a path to a repository root or Mercurial bundle will
2251 Specifying a path to a repository root or Mercurial bundle will
2251 cause lookup to operate on that repository/bundle.
2252 cause lookup to operate on that repository/bundle.
2252
2253
2253 This summary identifies the repository state using one or two
2254 This summary identifies the repository state using one or two
2254 parent hash identifiers, followed by a "+" if there are
2255 parent hash identifiers, followed by a "+" if there are
2255 uncommitted changes in the working directory, a list of tags for
2256 uncommitted changes in the working directory, a list of tags for
2256 this revision and a branch name for non-default branches.
2257 this revision and a branch name for non-default branches.
2257
2258
2258 Returns 0 if successful.
2259 Returns 0 if successful.
2259 """
2260 """
2260
2261
2261 if not repo and not source:
2262 if not repo and not source:
2262 raise util.Abort(_("there is no Mercurial repository here "
2263 raise util.Abort(_("there is no Mercurial repository here "
2263 "(.hg not found)"))
2264 "(.hg not found)"))
2264
2265
2265 hexfunc = ui.debugflag and hex or short
2266 hexfunc = ui.debugflag and hex or short
2266 default = not (num or id or branch or tags)
2267 default = not (num or id or branch or tags)
2267 output = []
2268 output = []
2268
2269
2269 revs = []
2270 revs = []
2270 if source:
2271 if source:
2271 source, branches = hg.parseurl(ui.expandpath(source))
2272 source, branches = hg.parseurl(ui.expandpath(source))
2272 repo = hg.repository(ui, source)
2273 repo = hg.repository(ui, source)
2273 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2274 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2274
2275
2275 if not repo.local():
2276 if not repo.local():
2276 if not rev and revs:
2277 if not rev and revs:
2277 rev = revs[0]
2278 rev = revs[0]
2278 if not rev:
2279 if not rev:
2279 rev = "tip"
2280 rev = "tip"
2280 if num or branch or tags:
2281 if num or branch or tags:
2281 raise util.Abort(
2282 raise util.Abort(
2282 "can't query remote revision number, branch, or tags")
2283 "can't query remote revision number, branch, or tags")
2283 output = [hexfunc(repo.lookup(rev))]
2284 output = [hexfunc(repo.lookup(rev))]
2284 elif not rev:
2285 elif not rev:
2285 ctx = repo[None]
2286 ctx = repo[None]
2286 parents = ctx.parents()
2287 parents = ctx.parents()
2287 changed = False
2288 changed = False
2288 if default or id or num:
2289 if default or id or num:
2289 changed = util.any(repo.status())
2290 changed = util.any(repo.status())
2290 if default or id:
2291 if default or id:
2291 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2292 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2292 (changed) and "+" or "")]
2293 (changed) and "+" or "")]
2293 if num:
2294 if num:
2294 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2295 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2295 (changed) and "+" or ""))
2296 (changed) and "+" or ""))
2296 else:
2297 else:
2297 ctx = cmdutil.revsingle(repo, rev)
2298 ctx = cmdutil.revsingle(repo, rev)
2298 if default or id:
2299 if default or id:
2299 output = [hexfunc(ctx.node())]
2300 output = [hexfunc(ctx.node())]
2300 if num:
2301 if num:
2301 output.append(str(ctx.rev()))
2302 output.append(str(ctx.rev()))
2302
2303
2303 if repo.local() and default and not ui.quiet:
2304 if repo.local() and default and not ui.quiet:
2304 b = ctx.branch()
2305 b = ctx.branch()
2305 if b != 'default':
2306 if b != 'default':
2306 output.append("(%s)" % b)
2307 output.append("(%s)" % b)
2307
2308
2308 # multiple tags for a single parent separated by '/'
2309 # multiple tags for a single parent separated by '/'
2309 t = "/".join(ctx.tags())
2310 t = "/".join(ctx.tags())
2310 if t:
2311 if t:
2311 output.append(t)
2312 output.append(t)
2312
2313
2313 if branch:
2314 if branch:
2314 output.append(ctx.branch())
2315 output.append(ctx.branch())
2315
2316
2316 if tags:
2317 if tags:
2317 output.extend(ctx.tags())
2318 output.extend(ctx.tags())
2318
2319
2319 ui.write("%s\n" % ' '.join(output))
2320 ui.write("%s\n" % ' '.join(output))
2320
2321
2321 def import_(ui, repo, patch1, *patches, **opts):
2322 def import_(ui, repo, patch1, *patches, **opts):
2322 """import an ordered set of patches
2323 """import an ordered set of patches
2323
2324
2324 Import a list of patches and commit them individually (unless
2325 Import a list of patches and commit them individually (unless
2325 --no-commit is specified).
2326 --no-commit is specified).
2326
2327
2327 If there are outstanding changes in the working directory, import
2328 If there are outstanding changes in the working directory, import
2328 will abort unless given the -f/--force flag.
2329 will abort unless given the -f/--force flag.
2329
2330
2330 You can import a patch straight from a mail message. Even patches
2331 You can import a patch straight from a mail message. Even patches
2331 as attachments work (to use the body part, it must have type
2332 as attachments work (to use the body part, it must have type
2332 text/plain or text/x-patch). From and Subject headers of email
2333 text/plain or text/x-patch). From and Subject headers of email
2333 message are used as default committer and commit message. All
2334 message are used as default committer and commit message. All
2334 text/plain body parts before first diff are added to commit
2335 text/plain body parts before first diff are added to commit
2335 message.
2336 message.
2336
2337
2337 If the imported patch was generated by :hg:`export`, user and
2338 If the imported patch was generated by :hg:`export`, user and
2338 description from patch override values from message headers and
2339 description from patch override values from message headers and
2339 body. Values given on command line with -m/--message and -u/--user
2340 body. Values given on command line with -m/--message and -u/--user
2340 override these.
2341 override these.
2341
2342
2342 If --exact is specified, import will set the working directory to
2343 If --exact is specified, import will set the working directory to
2343 the parent of each patch before applying it, and will abort if the
2344 the parent of each patch before applying it, and will abort if the
2344 resulting changeset has a different ID than the one recorded in
2345 resulting changeset has a different ID than the one recorded in
2345 the patch. This may happen due to character set problems or other
2346 the patch. This may happen due to character set problems or other
2346 deficiencies in the text patch format.
2347 deficiencies in the text patch format.
2347
2348
2348 With -s/--similarity, hg will attempt to discover renames and
2349 With -s/--similarity, hg will attempt to discover renames and
2349 copies in the patch in the same way as 'addremove'.
2350 copies in the patch in the same way as 'addremove'.
2350
2351
2351 To read a patch from standard input, use "-" as the patch name. If
2352 To read a patch from standard input, use "-" as the patch name. If
2352 a URL is specified, the patch will be downloaded from it.
2353 a URL is specified, the patch will be downloaded from it.
2353 See :hg:`help dates` for a list of formats valid for -d/--date.
2354 See :hg:`help dates` for a list of formats valid for -d/--date.
2354
2355
2355 Returns 0 on success.
2356 Returns 0 on success.
2356 """
2357 """
2357 patches = (patch1,) + patches
2358 patches = (patch1,) + patches
2358
2359
2359 date = opts.get('date')
2360 date = opts.get('date')
2360 if date:
2361 if date:
2361 opts['date'] = util.parsedate(date)
2362 opts['date'] = util.parsedate(date)
2362
2363
2363 try:
2364 try:
2364 sim = float(opts.get('similarity') or 0)
2365 sim = float(opts.get('similarity') or 0)
2365 except ValueError:
2366 except ValueError:
2366 raise util.Abort(_('similarity must be a number'))
2367 raise util.Abort(_('similarity must be a number'))
2367 if sim < 0 or sim > 100:
2368 if sim < 0 or sim > 100:
2368 raise util.Abort(_('similarity must be between 0 and 100'))
2369 raise util.Abort(_('similarity must be between 0 and 100'))
2369
2370
2370 if opts.get('exact') or not opts.get('force'):
2371 if opts.get('exact') or not opts.get('force'):
2371 cmdutil.bail_if_changed(repo)
2372 cmdutil.bail_if_changed(repo)
2372
2373
2373 d = opts["base"]
2374 d = opts["base"]
2374 strip = opts["strip"]
2375 strip = opts["strip"]
2375 wlock = lock = None
2376 wlock = lock = None
2376 msgs = []
2377 msgs = []
2377
2378
2378 def tryone(ui, hunk):
2379 def tryone(ui, hunk):
2379 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2380 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2380 patch.extract(ui, hunk)
2381 patch.extract(ui, hunk)
2381
2382
2382 if not tmpname:
2383 if not tmpname:
2383 return None
2384 return None
2384 commitid = _('to working directory')
2385 commitid = _('to working directory')
2385
2386
2386 try:
2387 try:
2387 cmdline_message = cmdutil.logmessage(opts)
2388 cmdline_message = cmdutil.logmessage(opts)
2388 if cmdline_message:
2389 if cmdline_message:
2389 # pickup the cmdline msg
2390 # pickup the cmdline msg
2390 message = cmdline_message
2391 message = cmdline_message
2391 elif message:
2392 elif message:
2392 # pickup the patch msg
2393 # pickup the patch msg
2393 message = message.strip()
2394 message = message.strip()
2394 else:
2395 else:
2395 # launch the editor
2396 # launch the editor
2396 message = None
2397 message = None
2397 ui.debug('message:\n%s\n' % message)
2398 ui.debug('message:\n%s\n' % message)
2398
2399
2399 wp = repo.parents()
2400 wp = repo.parents()
2400 if opts.get('exact'):
2401 if opts.get('exact'):
2401 if not nodeid or not p1:
2402 if not nodeid or not p1:
2402 raise util.Abort(_('not a Mercurial patch'))
2403 raise util.Abort(_('not a Mercurial patch'))
2403 p1 = repo.lookup(p1)
2404 p1 = repo.lookup(p1)
2404 p2 = repo.lookup(p2 or hex(nullid))
2405 p2 = repo.lookup(p2 or hex(nullid))
2405
2406
2406 if p1 != wp[0].node():
2407 if p1 != wp[0].node():
2407 hg.clean(repo, p1)
2408 hg.clean(repo, p1)
2408 repo.dirstate.setparents(p1, p2)
2409 repo.dirstate.setparents(p1, p2)
2409 elif p2:
2410 elif p2:
2410 try:
2411 try:
2411 p1 = repo.lookup(p1)
2412 p1 = repo.lookup(p1)
2412 p2 = repo.lookup(p2)
2413 p2 = repo.lookup(p2)
2413 if p1 == wp[0].node():
2414 if p1 == wp[0].node():
2414 repo.dirstate.setparents(p1, p2)
2415 repo.dirstate.setparents(p1, p2)
2415 except error.RepoError:
2416 except error.RepoError:
2416 pass
2417 pass
2417 if opts.get('exact') or opts.get('import_branch'):
2418 if opts.get('exact') or opts.get('import_branch'):
2418 repo.dirstate.setbranch(branch or 'default')
2419 repo.dirstate.setbranch(branch or 'default')
2419
2420
2420 files = {}
2421 files = {}
2421 try:
2422 try:
2422 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2423 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2423 files=files, eolmode=None)
2424 files=files, eolmode=None)
2424 finally:
2425 finally:
2425 files = cmdutil.updatedir(ui, repo, files,
2426 files = cmdutil.updatedir(ui, repo, files,
2426 similarity=sim / 100.0)
2427 similarity=sim / 100.0)
2427 if opts.get('no_commit'):
2428 if opts.get('no_commit'):
2428 if message:
2429 if message:
2429 msgs.append(message)
2430 msgs.append(message)
2430 else:
2431 else:
2431 if opts.get('exact'):
2432 if opts.get('exact'):
2432 m = None
2433 m = None
2433 else:
2434 else:
2434 m = cmdutil.matchfiles(repo, files or [])
2435 m = cmdutil.matchfiles(repo, files or [])
2435 n = repo.commit(message, opts.get('user') or user,
2436 n = repo.commit(message, opts.get('user') or user,
2436 opts.get('date') or date, match=m,
2437 opts.get('date') or date, match=m,
2437 editor=cmdutil.commiteditor)
2438 editor=cmdutil.commiteditor)
2438 if opts.get('exact'):
2439 if opts.get('exact'):
2439 if hex(n) != nodeid:
2440 if hex(n) != nodeid:
2440 repo.rollback()
2441 repo.rollback()
2441 raise util.Abort(_('patch is damaged'
2442 raise util.Abort(_('patch is damaged'
2442 ' or loses information'))
2443 ' or loses information'))
2443 # Force a dirstate write so that the next transaction
2444 # Force a dirstate write so that the next transaction
2444 # backups an up-do-date file.
2445 # backups an up-do-date file.
2445 repo.dirstate.write()
2446 repo.dirstate.write()
2446 if n:
2447 if n:
2447 commitid = short(n)
2448 commitid = short(n)
2448
2449
2449 return commitid
2450 return commitid
2450 finally:
2451 finally:
2451 os.unlink(tmpname)
2452 os.unlink(tmpname)
2452
2453
2453 try:
2454 try:
2454 wlock = repo.wlock()
2455 wlock = repo.wlock()
2455 lock = repo.lock()
2456 lock = repo.lock()
2456 lastcommit = None
2457 lastcommit = None
2457 for p in patches:
2458 for p in patches:
2458 pf = os.path.join(d, p)
2459 pf = os.path.join(d, p)
2459
2460
2460 if pf == '-':
2461 if pf == '-':
2461 ui.status(_("applying patch from stdin\n"))
2462 ui.status(_("applying patch from stdin\n"))
2462 pf = sys.stdin
2463 pf = sys.stdin
2463 else:
2464 else:
2464 ui.status(_("applying %s\n") % p)
2465 ui.status(_("applying %s\n") % p)
2465 pf = url.open(ui, pf)
2466 pf = url.open(ui, pf)
2466
2467
2467 haspatch = False
2468 haspatch = False
2468 for hunk in patch.split(pf):
2469 for hunk in patch.split(pf):
2469 commitid = tryone(ui, hunk)
2470 commitid = tryone(ui, hunk)
2470 if commitid:
2471 if commitid:
2471 haspatch = True
2472 haspatch = True
2472 if lastcommit:
2473 if lastcommit:
2473 ui.status(_('applied %s\n') % lastcommit)
2474 ui.status(_('applied %s\n') % lastcommit)
2474 lastcommit = commitid
2475 lastcommit = commitid
2475
2476
2476 if not haspatch:
2477 if not haspatch:
2477 raise util.Abort(_('no diffs found'))
2478 raise util.Abort(_('no diffs found'))
2478
2479
2479 if msgs:
2480 if msgs:
2480 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2481 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2481 finally:
2482 finally:
2482 release(lock, wlock)
2483 release(lock, wlock)
2483
2484
2484 def incoming(ui, repo, source="default", **opts):
2485 def incoming(ui, repo, source="default", **opts):
2485 """show new changesets found in source
2486 """show new changesets found in source
2486
2487
2487 Show new changesets found in the specified path/URL or the default
2488 Show new changesets found in the specified path/URL or the default
2488 pull location. These are the changesets that would have been pulled
2489 pull location. These are the changesets that would have been pulled
2489 if a pull at the time you issued this command.
2490 if a pull at the time you issued this command.
2490
2491
2491 For remote repository, using --bundle avoids downloading the
2492 For remote repository, using --bundle avoids downloading the
2492 changesets twice if the incoming is followed by a pull.
2493 changesets twice if the incoming is followed by a pull.
2493
2494
2494 See pull for valid source format details.
2495 See pull for valid source format details.
2495
2496
2496 Returns 0 if there are incoming changes, 1 otherwise.
2497 Returns 0 if there are incoming changes, 1 otherwise.
2497 """
2498 """
2498 if opts.get('bundle') and opts.get('subrepos'):
2499 if opts.get('bundle') and opts.get('subrepos'):
2499 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2500 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2500
2501
2501 if opts.get('bookmarks'):
2502 if opts.get('bookmarks'):
2502 source, branches = hg.parseurl(ui.expandpath(source),
2503 source, branches = hg.parseurl(ui.expandpath(source),
2503 opts.get('branch'))
2504 opts.get('branch'))
2504 other = hg.repository(hg.remoteui(repo, opts), source)
2505 other = hg.repository(hg.remoteui(repo, opts), source)
2505 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2506 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2506 return bookmarks.diff(ui, repo, other)
2507 return bookmarks.diff(ui, repo, other)
2507
2508
2508 ret = hg.incoming(ui, repo, source, opts)
2509 ret = hg.incoming(ui, repo, source, opts)
2509 return ret
2510 return ret
2510
2511
2511 def init(ui, dest=".", **opts):
2512 def init(ui, dest=".", **opts):
2512 """create a new repository in the given directory
2513 """create a new repository in the given directory
2513
2514
2514 Initialize a new repository in the given directory. If the given
2515 Initialize a new repository in the given directory. If the given
2515 directory does not exist, it will be created.
2516 directory does not exist, it will be created.
2516
2517
2517 If no directory is given, the current directory is used.
2518 If no directory is given, the current directory is used.
2518
2519
2519 It is possible to specify an ``ssh://`` URL as the destination.
2520 It is possible to specify an ``ssh://`` URL as the destination.
2520 See :hg:`help urls` for more information.
2521 See :hg:`help urls` for more information.
2521
2522
2522 Returns 0 on success.
2523 Returns 0 on success.
2523 """
2524 """
2524 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2525 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2525
2526
2526 def locate(ui, repo, *pats, **opts):
2527 def locate(ui, repo, *pats, **opts):
2527 """locate files matching specific patterns
2528 """locate files matching specific patterns
2528
2529
2529 Print files under Mercurial control in the working directory whose
2530 Print files under Mercurial control in the working directory whose
2530 names match the given patterns.
2531 names match the given patterns.
2531
2532
2532 By default, this command searches all directories in the working
2533 By default, this command searches all directories in the working
2533 directory. To search just the current directory and its
2534 directory. To search just the current directory and its
2534 subdirectories, use "--include .".
2535 subdirectories, use "--include .".
2535
2536
2536 If no patterns are given to match, this command prints the names
2537 If no patterns are given to match, this command prints the names
2537 of all files under Mercurial control in the working directory.
2538 of all files under Mercurial control in the working directory.
2538
2539
2539 If you want to feed the output of this command into the "xargs"
2540 If you want to feed the output of this command into the "xargs"
2540 command, use the -0 option to both this command and "xargs". This
2541 command, use the -0 option to both this command and "xargs". This
2541 will avoid the problem of "xargs" treating single filenames that
2542 will avoid the problem of "xargs" treating single filenames that
2542 contain whitespace as multiple filenames.
2543 contain whitespace as multiple filenames.
2543
2544
2544 Returns 0 if a match is found, 1 otherwise.
2545 Returns 0 if a match is found, 1 otherwise.
2545 """
2546 """
2546 end = opts.get('print0') and '\0' or '\n'
2547 end = opts.get('print0') and '\0' or '\n'
2547 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2548 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2548
2549
2549 ret = 1
2550 ret = 1
2550 m = cmdutil.match(repo, pats, opts, default='relglob')
2551 m = cmdutil.match(repo, pats, opts, default='relglob')
2551 m.bad = lambda x, y: False
2552 m.bad = lambda x, y: False
2552 for abs in repo[rev].walk(m):
2553 for abs in repo[rev].walk(m):
2553 if not rev and abs not in repo.dirstate:
2554 if not rev and abs not in repo.dirstate:
2554 continue
2555 continue
2555 if opts.get('fullpath'):
2556 if opts.get('fullpath'):
2556 ui.write(repo.wjoin(abs), end)
2557 ui.write(repo.wjoin(abs), end)
2557 else:
2558 else:
2558 ui.write(((pats and m.rel(abs)) or abs), end)
2559 ui.write(((pats and m.rel(abs)) or abs), end)
2559 ret = 0
2560 ret = 0
2560
2561
2561 return ret
2562 return ret
2562
2563
2563 def log(ui, repo, *pats, **opts):
2564 def log(ui, repo, *pats, **opts):
2564 """show revision history of entire repository or files
2565 """show revision history of entire repository or files
2565
2566
2566 Print the revision history of the specified files or the entire
2567 Print the revision history of the specified files or the entire
2567 project.
2568 project.
2568
2569
2569 File history is shown without following rename or copy history of
2570 File history is shown without following rename or copy history of
2570 files. Use -f/--follow with a filename to follow history across
2571 files. Use -f/--follow with a filename to follow history across
2571 renames and copies. --follow without a filename will only show
2572 renames and copies. --follow without a filename will only show
2572 ancestors or descendants of the starting revision. --follow-first
2573 ancestors or descendants of the starting revision. --follow-first
2573 only follows the first parent of merge revisions.
2574 only follows the first parent of merge revisions.
2574
2575
2575 If no revision range is specified, the default is ``tip:0`` unless
2576 If no revision range is specified, the default is ``tip:0`` unless
2576 --follow is set, in which case the working directory parent is
2577 --follow is set, in which case the working directory parent is
2577 used as the starting revision. You can specify a revision set for
2578 used as the starting revision. You can specify a revision set for
2578 log, see :hg:`help revsets` for more information.
2579 log, see :hg:`help revsets` for more information.
2579
2580
2580 See :hg:`help dates` for a list of formats valid for -d/--date.
2581 See :hg:`help dates` for a list of formats valid for -d/--date.
2581
2582
2582 By default this command prints revision number and changeset id,
2583 By default this command prints revision number and changeset id,
2583 tags, non-trivial parents, user, date and time, and a summary for
2584 tags, non-trivial parents, user, date and time, and a summary for
2584 each commit. When the -v/--verbose switch is used, the list of
2585 each commit. When the -v/--verbose switch is used, the list of
2585 changed files and full commit message are shown.
2586 changed files and full commit message are shown.
2586
2587
2587 .. note::
2588 .. note::
2588 log -p/--patch may generate unexpected diff output for merge
2589 log -p/--patch may generate unexpected diff output for merge
2589 changesets, as it will only compare the merge changeset against
2590 changesets, as it will only compare the merge changeset against
2590 its first parent. Also, only files different from BOTH parents
2591 its first parent. Also, only files different from BOTH parents
2591 will appear in files:.
2592 will appear in files:.
2592
2593
2593 Returns 0 on success.
2594 Returns 0 on success.
2594 """
2595 """
2595
2596
2596 matchfn = cmdutil.match(repo, pats, opts)
2597 matchfn = cmdutil.match(repo, pats, opts)
2597 limit = cmdutil.loglimit(opts)
2598 limit = cmdutil.loglimit(opts)
2598 count = 0
2599 count = 0
2599
2600
2600 endrev = None
2601 endrev = None
2601 if opts.get('copies') and opts.get('rev'):
2602 if opts.get('copies') and opts.get('rev'):
2602 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2603 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2603
2604
2604 df = False
2605 df = False
2605 if opts["date"]:
2606 if opts["date"]:
2606 df = util.matchdate(opts["date"])
2607 df = util.matchdate(opts["date"])
2607
2608
2608 branches = opts.get('branch', []) + opts.get('only_branch', [])
2609 branches = opts.get('branch', []) + opts.get('only_branch', [])
2609 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2610 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2610
2611
2611 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2612 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2612 def prep(ctx, fns):
2613 def prep(ctx, fns):
2613 rev = ctx.rev()
2614 rev = ctx.rev()
2614 parents = [p for p in repo.changelog.parentrevs(rev)
2615 parents = [p for p in repo.changelog.parentrevs(rev)
2615 if p != nullrev]
2616 if p != nullrev]
2616 if opts.get('no_merges') and len(parents) == 2:
2617 if opts.get('no_merges') and len(parents) == 2:
2617 return
2618 return
2618 if opts.get('only_merges') and len(parents) != 2:
2619 if opts.get('only_merges') and len(parents) != 2:
2619 return
2620 return
2620 if opts.get('branch') and ctx.branch() not in opts['branch']:
2621 if opts.get('branch') and ctx.branch() not in opts['branch']:
2621 return
2622 return
2622 if df and not df(ctx.date()[0]):
2623 if df and not df(ctx.date()[0]):
2623 return
2624 return
2624 if opts['user'] and not [k for k in opts['user']
2625 if opts['user'] and not [k for k in opts['user']
2625 if k.lower() in ctx.user().lower()]:
2626 if k.lower() in ctx.user().lower()]:
2626 return
2627 return
2627 if opts.get('keyword'):
2628 if opts.get('keyword'):
2628 for k in [kw.lower() for kw in opts['keyword']]:
2629 for k in [kw.lower() for kw in opts['keyword']]:
2629 if (k in ctx.user().lower() or
2630 if (k in ctx.user().lower() or
2630 k in ctx.description().lower() or
2631 k in ctx.description().lower() or
2631 k in " ".join(ctx.files()).lower()):
2632 k in " ".join(ctx.files()).lower()):
2632 break
2633 break
2633 else:
2634 else:
2634 return
2635 return
2635
2636
2636 copies = None
2637 copies = None
2637 if opts.get('copies') and rev:
2638 if opts.get('copies') and rev:
2638 copies = []
2639 copies = []
2639 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2640 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2640 for fn in ctx.files():
2641 for fn in ctx.files():
2641 rename = getrenamed(fn, rev)
2642 rename = getrenamed(fn, rev)
2642 if rename:
2643 if rename:
2643 copies.append((fn, rename[0]))
2644 copies.append((fn, rename[0]))
2644
2645
2645 revmatchfn = None
2646 revmatchfn = None
2646 if opts.get('patch') or opts.get('stat'):
2647 if opts.get('patch') or opts.get('stat'):
2647 if opts.get('follow') or opts.get('follow_first'):
2648 if opts.get('follow') or opts.get('follow_first'):
2648 # note: this might be wrong when following through merges
2649 # note: this might be wrong when following through merges
2649 revmatchfn = cmdutil.match(repo, fns, default='path')
2650 revmatchfn = cmdutil.match(repo, fns, default='path')
2650 else:
2651 else:
2651 revmatchfn = matchfn
2652 revmatchfn = matchfn
2652
2653
2653 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2654 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2654
2655
2655 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2656 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2656 if count == limit:
2657 if count == limit:
2657 break
2658 break
2658 if displayer.flush(ctx.rev()):
2659 if displayer.flush(ctx.rev()):
2659 count += 1
2660 count += 1
2660 displayer.close()
2661 displayer.close()
2661
2662
2662 def manifest(ui, repo, node=None, rev=None):
2663 def manifest(ui, repo, node=None, rev=None):
2663 """output the current or given revision of the project manifest
2664 """output the current or given revision of the project manifest
2664
2665
2665 Print a list of version controlled files for the given revision.
2666 Print a list of version controlled files for the given revision.
2666 If no revision is given, the first parent of the working directory
2667 If no revision is given, the first parent of the working directory
2667 is used, or the null revision if no revision is checked out.
2668 is used, or the null revision if no revision is checked out.
2668
2669
2669 With -v, print file permissions, symlink and executable bits.
2670 With -v, print file permissions, symlink and executable bits.
2670 With --debug, print file revision hashes.
2671 With --debug, print file revision hashes.
2671
2672
2672 Returns 0 on success.
2673 Returns 0 on success.
2673 """
2674 """
2674
2675
2675 if rev and node:
2676 if rev and node:
2676 raise util.Abort(_("please specify just one revision"))
2677 raise util.Abort(_("please specify just one revision"))
2677
2678
2678 if not node:
2679 if not node:
2679 node = rev
2680 node = rev
2680
2681
2681 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2682 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2682 ctx = cmdutil.revsingle(repo, node)
2683 ctx = cmdutil.revsingle(repo, node)
2683 for f in ctx:
2684 for f in ctx:
2684 if ui.debugflag:
2685 if ui.debugflag:
2685 ui.write("%40s " % hex(ctx.manifest()[f]))
2686 ui.write("%40s " % hex(ctx.manifest()[f]))
2686 if ui.verbose:
2687 if ui.verbose:
2687 ui.write(decor[ctx.flags(f)])
2688 ui.write(decor[ctx.flags(f)])
2688 ui.write("%s\n" % f)
2689 ui.write("%s\n" % f)
2689
2690
2690 def merge(ui, repo, node=None, **opts):
2691 def merge(ui, repo, node=None, **opts):
2691 """merge working directory with another revision
2692 """merge working directory with another revision
2692
2693
2693 The current working directory is updated with all changes made in
2694 The current working directory is updated with all changes made in
2694 the requested revision since the last common predecessor revision.
2695 the requested revision since the last common predecessor revision.
2695
2696
2696 Files that changed between either parent are marked as changed for
2697 Files that changed between either parent are marked as changed for
2697 the next commit and a commit must be performed before any further
2698 the next commit and a commit must be performed before any further
2698 updates to the repository are allowed. The next commit will have
2699 updates to the repository are allowed. The next commit will have
2699 two parents.
2700 two parents.
2700
2701
2701 ``--tool`` can be used to specify the merge tool used for file
2702 ``--tool`` can be used to specify the merge tool used for file
2702 merges. It overrides the HGMERGE environment variable and your
2703 merges. It overrides the HGMERGE environment variable and your
2703 configuration files.
2704 configuration files.
2704
2705
2705 If no revision is specified, the working directory's parent is a
2706 If no revision is specified, the working directory's parent is a
2706 head revision, and the current branch contains exactly one other
2707 head revision, and the current branch contains exactly one other
2707 head, the other head is merged with by default. Otherwise, an
2708 head, the other head is merged with by default. Otherwise, an
2708 explicit revision with which to merge with must be provided.
2709 explicit revision with which to merge with must be provided.
2709
2710
2710 :hg:`resolve` must be used to resolve unresolved files.
2711 :hg:`resolve` must be used to resolve unresolved files.
2711
2712
2712 To undo an uncommitted merge, use :hg:`update --clean .` which
2713 To undo an uncommitted merge, use :hg:`update --clean .` which
2713 will check out a clean copy of the original merge parent, losing
2714 will check out a clean copy of the original merge parent, losing
2714 all changes.
2715 all changes.
2715
2716
2716 Returns 0 on success, 1 if there are unresolved files.
2717 Returns 0 on success, 1 if there are unresolved files.
2717 """
2718 """
2718
2719
2719 if opts.get('rev') and node:
2720 if opts.get('rev') and node:
2720 raise util.Abort(_("please specify just one revision"))
2721 raise util.Abort(_("please specify just one revision"))
2721 if not node:
2722 if not node:
2722 node = opts.get('rev')
2723 node = opts.get('rev')
2723
2724
2724 if not node:
2725 if not node:
2725 branch = repo[None].branch()
2726 branch = repo[None].branch()
2726 bheads = repo.branchheads(branch)
2727 bheads = repo.branchheads(branch)
2727 if len(bheads) > 2:
2728 if len(bheads) > 2:
2728 raise util.Abort(_(
2729 raise util.Abort(_(
2729 'branch \'%s\' has %d heads - '
2730 'branch \'%s\' has %d heads - '
2730 'please merge with an explicit rev\n'
2731 'please merge with an explicit rev\n'
2731 '(run \'hg heads .\' to see heads)')
2732 '(run \'hg heads .\' to see heads)')
2732 % (branch, len(bheads)))
2733 % (branch, len(bheads)))
2733
2734
2734 parent = repo.dirstate.parents()[0]
2735 parent = repo.dirstate.parents()[0]
2735 if len(bheads) == 1:
2736 if len(bheads) == 1:
2736 if len(repo.heads()) > 1:
2737 if len(repo.heads()) > 1:
2737 raise util.Abort(_(
2738 raise util.Abort(_(
2738 'branch \'%s\' has one head - '
2739 'branch \'%s\' has one head - '
2739 'please merge with an explicit rev\n'
2740 'please merge with an explicit rev\n'
2740 '(run \'hg heads\' to see all heads)')
2741 '(run \'hg heads\' to see all heads)')
2741 % branch)
2742 % branch)
2742 msg = _('there is nothing to merge')
2743 msg = _('there is nothing to merge')
2743 if parent != repo.lookup(repo[None].branch()):
2744 if parent != repo.lookup(repo[None].branch()):
2744 msg = _('%s - use "hg update" instead') % msg
2745 msg = _('%s - use "hg update" instead') % msg
2745 raise util.Abort(msg)
2746 raise util.Abort(msg)
2746
2747
2747 if parent not in bheads:
2748 if parent not in bheads:
2748 raise util.Abort(_('working dir not at a head rev - '
2749 raise util.Abort(_('working dir not at a head rev - '
2749 'use "hg update" or merge with an explicit rev'))
2750 'use "hg update" or merge with an explicit rev'))
2750 node = parent == bheads[0] and bheads[-1] or bheads[0]
2751 node = parent == bheads[0] and bheads[-1] or bheads[0]
2751 else:
2752 else:
2752 node = cmdutil.revsingle(repo, node).node()
2753 node = cmdutil.revsingle(repo, node).node()
2753
2754
2754 if opts.get('preview'):
2755 if opts.get('preview'):
2755 # find nodes that are ancestors of p2 but not of p1
2756 # find nodes that are ancestors of p2 but not of p1
2756 p1 = repo.lookup('.')
2757 p1 = repo.lookup('.')
2757 p2 = repo.lookup(node)
2758 p2 = repo.lookup(node)
2758 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2759 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2759
2760
2760 displayer = cmdutil.show_changeset(ui, repo, opts)
2761 displayer = cmdutil.show_changeset(ui, repo, opts)
2761 for node in nodes:
2762 for node in nodes:
2762 displayer.show(repo[node])
2763 displayer.show(repo[node])
2763 displayer.close()
2764 displayer.close()
2764 return 0
2765 return 0
2765
2766
2766 try:
2767 try:
2767 # ui.forcemerge is an internal variable, do not document
2768 # ui.forcemerge is an internal variable, do not document
2768 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2769 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2769 return hg.merge(repo, node, force=opts.get('force'))
2770 return hg.merge(repo, node, force=opts.get('force'))
2770 finally:
2771 finally:
2771 ui.setconfig('ui', 'forcemerge', '')
2772 ui.setconfig('ui', 'forcemerge', '')
2772
2773
2773 def outgoing(ui, repo, dest=None, **opts):
2774 def outgoing(ui, repo, dest=None, **opts):
2774 """show changesets not found in the destination
2775 """show changesets not found in the destination
2775
2776
2776 Show changesets not found in the specified destination repository
2777 Show changesets not found in the specified destination repository
2777 or the default push location. These are the changesets that would
2778 or the default push location. These are the changesets that would
2778 be pushed if a push was requested.
2779 be pushed if a push was requested.
2779
2780
2780 See pull for details of valid destination formats.
2781 See pull for details of valid destination formats.
2781
2782
2782 Returns 0 if there are outgoing changes, 1 otherwise.
2783 Returns 0 if there are outgoing changes, 1 otherwise.
2783 """
2784 """
2784
2785
2785 if opts.get('bookmarks'):
2786 if opts.get('bookmarks'):
2786 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2787 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2787 dest, branches = hg.parseurl(dest, opts.get('branch'))
2788 dest, branches = hg.parseurl(dest, opts.get('branch'))
2788 other = hg.repository(hg.remoteui(repo, opts), dest)
2789 other = hg.repository(hg.remoteui(repo, opts), dest)
2789 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2790 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2790 return bookmarks.diff(ui, other, repo)
2791 return bookmarks.diff(ui, other, repo)
2791
2792
2792 ret = hg.outgoing(ui, repo, dest, opts)
2793 ret = hg.outgoing(ui, repo, dest, opts)
2793 return ret
2794 return ret
2794
2795
2795 def parents(ui, repo, file_=None, **opts):
2796 def parents(ui, repo, file_=None, **opts):
2796 """show the parents of the working directory or revision
2797 """show the parents of the working directory or revision
2797
2798
2798 Print the working directory's parent revisions. If a revision is
2799 Print the working directory's parent revisions. If a revision is
2799 given via -r/--rev, the parent of that revision will be printed.
2800 given via -r/--rev, the parent of that revision will be printed.
2800 If a file argument is given, the revision in which the file was
2801 If a file argument is given, the revision in which the file was
2801 last changed (before the working directory revision or the
2802 last changed (before the working directory revision or the
2802 argument to --rev if given) is printed.
2803 argument to --rev if given) is printed.
2803
2804
2804 Returns 0 on success.
2805 Returns 0 on success.
2805 """
2806 """
2806
2807
2807 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2808 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2808
2809
2809 if file_:
2810 if file_:
2810 m = cmdutil.match(repo, (file_,), opts)
2811 m = cmdutil.match(repo, (file_,), opts)
2811 if m.anypats() or len(m.files()) != 1:
2812 if m.anypats() or len(m.files()) != 1:
2812 raise util.Abort(_('can only specify an explicit filename'))
2813 raise util.Abort(_('can only specify an explicit filename'))
2813 file_ = m.files()[0]
2814 file_ = m.files()[0]
2814 filenodes = []
2815 filenodes = []
2815 for cp in ctx.parents():
2816 for cp in ctx.parents():
2816 if not cp:
2817 if not cp:
2817 continue
2818 continue
2818 try:
2819 try:
2819 filenodes.append(cp.filenode(file_))
2820 filenodes.append(cp.filenode(file_))
2820 except error.LookupError:
2821 except error.LookupError:
2821 pass
2822 pass
2822 if not filenodes:
2823 if not filenodes:
2823 raise util.Abort(_("'%s' not found in manifest!") % file_)
2824 raise util.Abort(_("'%s' not found in manifest!") % file_)
2824 fl = repo.file(file_)
2825 fl = repo.file(file_)
2825 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2826 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2826 else:
2827 else:
2827 p = [cp.node() for cp in ctx.parents()]
2828 p = [cp.node() for cp in ctx.parents()]
2828
2829
2829 displayer = cmdutil.show_changeset(ui, repo, opts)
2830 displayer = cmdutil.show_changeset(ui, repo, opts)
2830 for n in p:
2831 for n in p:
2831 if n != nullid:
2832 if n != nullid:
2832 displayer.show(repo[n])
2833 displayer.show(repo[n])
2833 displayer.close()
2834 displayer.close()
2834
2835
2835 def paths(ui, repo, search=None):
2836 def paths(ui, repo, search=None):
2836 """show aliases for remote repositories
2837 """show aliases for remote repositories
2837
2838
2838 Show definition of symbolic path name NAME. If no name is given,
2839 Show definition of symbolic path name NAME. If no name is given,
2839 show definition of all available names.
2840 show definition of all available names.
2840
2841
2841 Path names are defined in the [paths] section of your
2842 Path names are defined in the [paths] section of your
2842 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2843 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2843 repository, ``.hg/hgrc`` is used, too.
2844 repository, ``.hg/hgrc`` is used, too.
2844
2845
2845 The path names ``default`` and ``default-push`` have a special
2846 The path names ``default`` and ``default-push`` have a special
2846 meaning. When performing a push or pull operation, they are used
2847 meaning. When performing a push or pull operation, they are used
2847 as fallbacks if no location is specified on the command-line.
2848 as fallbacks if no location is specified on the command-line.
2848 When ``default-push`` is set, it will be used for push and
2849 When ``default-push`` is set, it will be used for push and
2849 ``default`` will be used for pull; otherwise ``default`` is used
2850 ``default`` will be used for pull; otherwise ``default`` is used
2850 as the fallback for both. When cloning a repository, the clone
2851 as the fallback for both. When cloning a repository, the clone
2851 source is written as ``default`` in ``.hg/hgrc``. Note that
2852 source is written as ``default`` in ``.hg/hgrc``. Note that
2852 ``default`` and ``default-push`` apply to all inbound (e.g.
2853 ``default`` and ``default-push`` apply to all inbound (e.g.
2853 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2854 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2854 :hg:`bundle`) operations.
2855 :hg:`bundle`) operations.
2855
2856
2856 See :hg:`help urls` for more information.
2857 See :hg:`help urls` for more information.
2857
2858
2858 Returns 0 on success.
2859 Returns 0 on success.
2859 """
2860 """
2860 if search:
2861 if search:
2861 for name, path in ui.configitems("paths"):
2862 for name, path in ui.configitems("paths"):
2862 if name == search:
2863 if name == search:
2863 ui.write("%s\n" % url.hidepassword(path))
2864 ui.write("%s\n" % url.hidepassword(path))
2864 return
2865 return
2865 ui.warn(_("not found!\n"))
2866 ui.warn(_("not found!\n"))
2866 return 1
2867 return 1
2867 else:
2868 else:
2868 for name, path in ui.configitems("paths"):
2869 for name, path in ui.configitems("paths"):
2869 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2870 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2870
2871
2871 def postincoming(ui, repo, modheads, optupdate, checkout):
2872 def postincoming(ui, repo, modheads, optupdate, checkout):
2872 if modheads == 0:
2873 if modheads == 0:
2873 return
2874 return
2874 if optupdate:
2875 if optupdate:
2875 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2876 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2876 return hg.update(repo, checkout)
2877 return hg.update(repo, checkout)
2877 else:
2878 else:
2878 ui.status(_("not updating, since new heads added\n"))
2879 ui.status(_("not updating, since new heads added\n"))
2879 if modheads > 1:
2880 if modheads > 1:
2880 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2881 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2881 else:
2882 else:
2882 ui.status(_("(run 'hg update' to get a working copy)\n"))
2883 ui.status(_("(run 'hg update' to get a working copy)\n"))
2883
2884
2884 def pull(ui, repo, source="default", **opts):
2885 def pull(ui, repo, source="default", **opts):
2885 """pull changes from the specified source
2886 """pull changes from the specified source
2886
2887
2887 Pull changes from a remote repository to a local one.
2888 Pull changes from a remote repository to a local one.
2888
2889
2889 This finds all changes from the repository at the specified path
2890 This finds all changes from the repository at the specified path
2890 or URL and adds them to a local repository (the current one unless
2891 or URL and adds them to a local repository (the current one unless
2891 -R is specified). By default, this does not update the copy of the
2892 -R is specified). By default, this does not update the copy of the
2892 project in the working directory.
2893 project in the working directory.
2893
2894
2894 Use :hg:`incoming` if you want to see what would have been added
2895 Use :hg:`incoming` if you want to see what would have been added
2895 by a pull at the time you issued this command. If you then decide
2896 by a pull at the time you issued this command. If you then decide
2896 to add those changes to the repository, you should use :hg:`pull
2897 to add those changes to the repository, you should use :hg:`pull
2897 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2898 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2898
2899
2899 If SOURCE is omitted, the 'default' path will be used.
2900 If SOURCE is omitted, the 'default' path will be used.
2900 See :hg:`help urls` for more information.
2901 See :hg:`help urls` for more information.
2901
2902
2902 Returns 0 on success, 1 if an update had unresolved files.
2903 Returns 0 on success, 1 if an update had unresolved files.
2903 """
2904 """
2904 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2905 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2905 other = hg.repository(hg.remoteui(repo, opts), source)
2906 other = hg.repository(hg.remoteui(repo, opts), source)
2906 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2907 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2907 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2908 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2908
2909
2909 if opts.get('bookmark'):
2910 if opts.get('bookmark'):
2910 if not revs:
2911 if not revs:
2911 revs = []
2912 revs = []
2912 rb = other.listkeys('bookmarks')
2913 rb = other.listkeys('bookmarks')
2913 for b in opts['bookmark']:
2914 for b in opts['bookmark']:
2914 if b not in rb:
2915 if b not in rb:
2915 raise util.Abort(_('remote bookmark %s not found!') % b)
2916 raise util.Abort(_('remote bookmark %s not found!') % b)
2916 revs.append(rb[b])
2917 revs.append(rb[b])
2917
2918
2918 if revs:
2919 if revs:
2919 try:
2920 try:
2920 revs = [other.lookup(rev) for rev in revs]
2921 revs = [other.lookup(rev) for rev in revs]
2921 except error.CapabilityError:
2922 except error.CapabilityError:
2922 err = _("other repository doesn't support revision lookup, "
2923 err = _("other repository doesn't support revision lookup, "
2923 "so a rev cannot be specified.")
2924 "so a rev cannot be specified.")
2924 raise util.Abort(err)
2925 raise util.Abort(err)
2925
2926
2926 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2927 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2927 if checkout:
2928 if checkout:
2928 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2929 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2929 repo._subtoppath = source
2930 repo._subtoppath = source
2930 try:
2931 try:
2931 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
2932 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
2932
2933
2933 finally:
2934 finally:
2934 del repo._subtoppath
2935 del repo._subtoppath
2935
2936
2936 # update specified bookmarks
2937 # update specified bookmarks
2937 if opts.get('bookmark'):
2938 if opts.get('bookmark'):
2938 for b in opts['bookmark']:
2939 for b in opts['bookmark']:
2939 # explicit pull overrides local bookmark if any
2940 # explicit pull overrides local bookmark if any
2940 ui.status(_("importing bookmark %s\n") % b)
2941 ui.status(_("importing bookmark %s\n") % b)
2941 repo._bookmarks[b] = repo[rb[b]].node()
2942 repo._bookmarks[b] = repo[rb[b]].node()
2942 bookmarks.write(repo)
2943 bookmarks.write(repo)
2943
2944
2944 return ret
2945 return ret
2945
2946
2946 def push(ui, repo, dest=None, **opts):
2947 def push(ui, repo, dest=None, **opts):
2947 """push changes to the specified destination
2948 """push changes to the specified destination
2948
2949
2949 Push changesets from the local repository to the specified
2950 Push changesets from the local repository to the specified
2950 destination.
2951 destination.
2951
2952
2952 This operation is symmetrical to pull: it is identical to a pull
2953 This operation is symmetrical to pull: it is identical to a pull
2953 in the destination repository from the current one.
2954 in the destination repository from the current one.
2954
2955
2955 By default, push will not allow creation of new heads at the
2956 By default, push will not allow creation of new heads at the
2956 destination, since multiple heads would make it unclear which head
2957 destination, since multiple heads would make it unclear which head
2957 to use. In this situation, it is recommended to pull and merge
2958 to use. In this situation, it is recommended to pull and merge
2958 before pushing.
2959 before pushing.
2959
2960
2960 Use --new-branch if you want to allow push to create a new named
2961 Use --new-branch if you want to allow push to create a new named
2961 branch that is not present at the destination. This allows you to
2962 branch that is not present at the destination. This allows you to
2962 only create a new branch without forcing other changes.
2963 only create a new branch without forcing other changes.
2963
2964
2964 Use -f/--force to override the default behavior and push all
2965 Use -f/--force to override the default behavior and push all
2965 changesets on all branches.
2966 changesets on all branches.
2966
2967
2967 If -r/--rev is used, the specified revision and all its ancestors
2968 If -r/--rev is used, the specified revision and all its ancestors
2968 will be pushed to the remote repository.
2969 will be pushed to the remote repository.
2969
2970
2970 Please see :hg:`help urls` for important details about ``ssh://``
2971 Please see :hg:`help urls` for important details about ``ssh://``
2971 URLs. If DESTINATION is omitted, a default path will be used.
2972 URLs. If DESTINATION is omitted, a default path will be used.
2972
2973
2973 Returns 0 if push was successful, 1 if nothing to push.
2974 Returns 0 if push was successful, 1 if nothing to push.
2974 """
2975 """
2975
2976
2976 if opts.get('bookmark'):
2977 if opts.get('bookmark'):
2977 for b in opts['bookmark']:
2978 for b in opts['bookmark']:
2978 # translate -B options to -r so changesets get pushed
2979 # translate -B options to -r so changesets get pushed
2979 if b in repo._bookmarks:
2980 if b in repo._bookmarks:
2980 opts.setdefault('rev', []).append(b)
2981 opts.setdefault('rev', []).append(b)
2981 else:
2982 else:
2982 # if we try to push a deleted bookmark, translate it to null
2983 # if we try to push a deleted bookmark, translate it to null
2983 # this lets simultaneous -r, -b options continue working
2984 # this lets simultaneous -r, -b options continue working
2984 opts.setdefault('rev', []).append("null")
2985 opts.setdefault('rev', []).append("null")
2985
2986
2986 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2987 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2987 dest, branches = hg.parseurl(dest, opts.get('branch'))
2988 dest, branches = hg.parseurl(dest, opts.get('branch'))
2988 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2989 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2989 other = hg.repository(hg.remoteui(repo, opts), dest)
2990 other = hg.repository(hg.remoteui(repo, opts), dest)
2990 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2991 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2991 if revs:
2992 if revs:
2992 revs = [repo.lookup(rev) for rev in revs]
2993 revs = [repo.lookup(rev) for rev in revs]
2993
2994
2994 repo._subtoppath = dest
2995 repo._subtoppath = dest
2995 try:
2996 try:
2996 # push subrepos depth-first for coherent ordering
2997 # push subrepos depth-first for coherent ordering
2997 c = repo['']
2998 c = repo['']
2998 subs = c.substate # only repos that are committed
2999 subs = c.substate # only repos that are committed
2999 for s in sorted(subs):
3000 for s in sorted(subs):
3000 if not c.sub(s).push(opts.get('force')):
3001 if not c.sub(s).push(opts.get('force')):
3001 return False
3002 return False
3002 finally:
3003 finally:
3003 del repo._subtoppath
3004 del repo._subtoppath
3004 result = repo.push(other, opts.get('force'), revs=revs,
3005 result = repo.push(other, opts.get('force'), revs=revs,
3005 newbranch=opts.get('new_branch'))
3006 newbranch=opts.get('new_branch'))
3006
3007
3007 result = (result == 0)
3008 result = (result == 0)
3008
3009
3009 if opts.get('bookmark'):
3010 if opts.get('bookmark'):
3010 rb = other.listkeys('bookmarks')
3011 rb = other.listkeys('bookmarks')
3011 for b in opts['bookmark']:
3012 for b in opts['bookmark']:
3012 # explicit push overrides remote bookmark if any
3013 # explicit push overrides remote bookmark if any
3013 if b in repo._bookmarks:
3014 if b in repo._bookmarks:
3014 ui.status(_("exporting bookmark %s\n") % b)
3015 ui.status(_("exporting bookmark %s\n") % b)
3015 new = repo[b].hex()
3016 new = repo[b].hex()
3016 elif b in rb:
3017 elif b in rb:
3017 ui.status(_("deleting remote bookmark %s\n") % b)
3018 ui.status(_("deleting remote bookmark %s\n") % b)
3018 new = '' # delete
3019 new = '' # delete
3019 else:
3020 else:
3020 ui.warn(_('bookmark %s does not exist on the local '
3021 ui.warn(_('bookmark %s does not exist on the local '
3021 'or remote repository!\n') % b)
3022 'or remote repository!\n') % b)
3022 return 2
3023 return 2
3023 old = rb.get(b, '')
3024 old = rb.get(b, '')
3024 r = other.pushkey('bookmarks', b, old, new)
3025 r = other.pushkey('bookmarks', b, old, new)
3025 if not r:
3026 if not r:
3026 ui.warn(_('updating bookmark %s failed!\n') % b)
3027 ui.warn(_('updating bookmark %s failed!\n') % b)
3027 if not result:
3028 if not result:
3028 result = 2
3029 result = 2
3029
3030
3030 return result
3031 return result
3031
3032
3032 def recover(ui, repo):
3033 def recover(ui, repo):
3033 """roll back an interrupted transaction
3034 """roll back an interrupted transaction
3034
3035
3035 Recover from an interrupted commit or pull.
3036 Recover from an interrupted commit or pull.
3036
3037
3037 This command tries to fix the repository status after an
3038 This command tries to fix the repository status after an
3038 interrupted operation. It should only be necessary when Mercurial
3039 interrupted operation. It should only be necessary when Mercurial
3039 suggests it.
3040 suggests it.
3040
3041
3041 Returns 0 if successful, 1 if nothing to recover or verify fails.
3042 Returns 0 if successful, 1 if nothing to recover or verify fails.
3042 """
3043 """
3043 if repo.recover():
3044 if repo.recover():
3044 return hg.verify(repo)
3045 return hg.verify(repo)
3045 return 1
3046 return 1
3046
3047
3047 def remove(ui, repo, *pats, **opts):
3048 def remove(ui, repo, *pats, **opts):
3048 """remove the specified files on the next commit
3049 """remove the specified files on the next commit
3049
3050
3050 Schedule the indicated files for removal from the repository.
3051 Schedule the indicated files for removal from the repository.
3051
3052
3052 This only removes files from the current branch, not from the
3053 This only removes files from the current branch, not from the
3053 entire project history. -A/--after can be used to remove only
3054 entire project history. -A/--after can be used to remove only
3054 files that have already been deleted, -f/--force can be used to
3055 files that have already been deleted, -f/--force can be used to
3055 force deletion, and -Af can be used to remove files from the next
3056 force deletion, and -Af can be used to remove files from the next
3056 revision without deleting them from the working directory.
3057 revision without deleting them from the working directory.
3057
3058
3058 The following table details the behavior of remove for different
3059 The following table details the behavior of remove for different
3059 file states (columns) and option combinations (rows). The file
3060 file states (columns) and option combinations (rows). The file
3060 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3061 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3061 reported by :hg:`status`). The actions are Warn, Remove (from
3062 reported by :hg:`status`). The actions are Warn, Remove (from
3062 branch) and Delete (from disk)::
3063 branch) and Delete (from disk)::
3063
3064
3064 A C M !
3065 A C M !
3065 none W RD W R
3066 none W RD W R
3066 -f R RD RD R
3067 -f R RD RD R
3067 -A W W W R
3068 -A W W W R
3068 -Af R R R R
3069 -Af R R R R
3069
3070
3070 This command schedules the files to be removed at the next commit.
3071 This command schedules the files to be removed at the next commit.
3071 To undo a remove before that, see :hg:`revert`.
3072 To undo a remove before that, see :hg:`revert`.
3072
3073
3073 Returns 0 on success, 1 if any warnings encountered.
3074 Returns 0 on success, 1 if any warnings encountered.
3074 """
3075 """
3075
3076
3076 ret = 0
3077 ret = 0
3077 after, force = opts.get('after'), opts.get('force')
3078 after, force = opts.get('after'), opts.get('force')
3078 if not pats and not after:
3079 if not pats and not after:
3079 raise util.Abort(_('no files specified'))
3080 raise util.Abort(_('no files specified'))
3080
3081
3081 m = cmdutil.match(repo, pats, opts)
3082 m = cmdutil.match(repo, pats, opts)
3082 s = repo.status(match=m, clean=True)
3083 s = repo.status(match=m, clean=True)
3083 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3084 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3084
3085
3085 for f in m.files():
3086 for f in m.files():
3086 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3087 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3087 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3088 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3088 ret = 1
3089 ret = 1
3089
3090
3090 if force:
3091 if force:
3091 remove, forget = modified + deleted + clean, added
3092 remove, forget = modified + deleted + clean, added
3092 elif after:
3093 elif after:
3093 remove, forget = deleted, []
3094 remove, forget = deleted, []
3094 for f in modified + added + clean:
3095 for f in modified + added + clean:
3095 ui.warn(_('not removing %s: file still exists (use -f'
3096 ui.warn(_('not removing %s: file still exists (use -f'
3096 ' to force removal)\n') % m.rel(f))
3097 ' to force removal)\n') % m.rel(f))
3097 ret = 1
3098 ret = 1
3098 else:
3099 else:
3099 remove, forget = deleted + clean, []
3100 remove, forget = deleted + clean, []
3100 for f in modified:
3101 for f in modified:
3101 ui.warn(_('not removing %s: file is modified (use -f'
3102 ui.warn(_('not removing %s: file is modified (use -f'
3102 ' to force removal)\n') % m.rel(f))
3103 ' to force removal)\n') % m.rel(f))
3103 ret = 1
3104 ret = 1
3104 for f in added:
3105 for f in added:
3105 ui.warn(_('not removing %s: file has been marked for add (use -f'
3106 ui.warn(_('not removing %s: file has been marked for add (use -f'
3106 ' to force removal)\n') % m.rel(f))
3107 ' to force removal)\n') % m.rel(f))
3107 ret = 1
3108 ret = 1
3108
3109
3109 for f in sorted(remove + forget):
3110 for f in sorted(remove + forget):
3110 if ui.verbose or not m.exact(f):
3111 if ui.verbose or not m.exact(f):
3111 ui.status(_('removing %s\n') % m.rel(f))
3112 ui.status(_('removing %s\n') % m.rel(f))
3112
3113
3113 repo[None].forget(forget)
3114 repo[None].forget(forget)
3114 repo[None].remove(remove, unlink=not after)
3115 repo[None].remove(remove, unlink=not after)
3115 return ret
3116 return ret
3116
3117
3117 def rename(ui, repo, *pats, **opts):
3118 def rename(ui, repo, *pats, **opts):
3118 """rename files; equivalent of copy + remove
3119 """rename files; equivalent of copy + remove
3119
3120
3120 Mark dest as copies of sources; mark sources for deletion. If dest
3121 Mark dest as copies of sources; mark sources for deletion. If dest
3121 is a directory, copies are put in that directory. If dest is a
3122 is a directory, copies are put in that directory. If dest is a
3122 file, there can only be one source.
3123 file, there can only be one source.
3123
3124
3124 By default, this command copies the contents of files as they
3125 By default, this command copies the contents of files as they
3125 exist in the working directory. If invoked with -A/--after, the
3126 exist in the working directory. If invoked with -A/--after, the
3126 operation is recorded, but no copying is performed.
3127 operation is recorded, but no copying is performed.
3127
3128
3128 This command takes effect at the next commit. To undo a rename
3129 This command takes effect at the next commit. To undo a rename
3129 before that, see :hg:`revert`.
3130 before that, see :hg:`revert`.
3130
3131
3131 Returns 0 on success, 1 if errors are encountered.
3132 Returns 0 on success, 1 if errors are encountered.
3132 """
3133 """
3133 wlock = repo.wlock(False)
3134 wlock = repo.wlock(False)
3134 try:
3135 try:
3135 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3136 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3136 finally:
3137 finally:
3137 wlock.release()
3138 wlock.release()
3138
3139
3139 def resolve(ui, repo, *pats, **opts):
3140 def resolve(ui, repo, *pats, **opts):
3140 """redo merges or set/view the merge status of files
3141 """redo merges or set/view the merge status of files
3141
3142
3142 Merges with unresolved conflicts are often the result of
3143 Merges with unresolved conflicts are often the result of
3143 non-interactive merging using the ``internal:merge`` configuration
3144 non-interactive merging using the ``internal:merge`` configuration
3144 setting, or a command-line merge tool like ``diff3``. The resolve
3145 setting, or a command-line merge tool like ``diff3``. The resolve
3145 command is used to manage the files involved in a merge, after
3146 command is used to manage the files involved in a merge, after
3146 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3147 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3147 working directory must have two parents).
3148 working directory must have two parents).
3148
3149
3149 The resolve command can be used in the following ways:
3150 The resolve command can be used in the following ways:
3150
3151
3151 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3152 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3152 files, discarding any previous merge attempts. Re-merging is not
3153 files, discarding any previous merge attempts. Re-merging is not
3153 performed for files already marked as resolved. Use ``--all/-a``
3154 performed for files already marked as resolved. Use ``--all/-a``
3154 to selects all unresolved files. ``--tool`` can be used to specify
3155 to selects all unresolved files. ``--tool`` can be used to specify
3155 the merge tool used for the given files. It overrides the HGMERGE
3156 the merge tool used for the given files. It overrides the HGMERGE
3156 environment variable and your configuration files.
3157 environment variable and your configuration files.
3157
3158
3158 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3159 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3159 (e.g. after having manually fixed-up the files). The default is
3160 (e.g. after having manually fixed-up the files). The default is
3160 to mark all unresolved files.
3161 to mark all unresolved files.
3161
3162
3162 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3163 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3163 default is to mark all resolved files.
3164 default is to mark all resolved files.
3164
3165
3165 - :hg:`resolve -l`: list files which had or still have conflicts.
3166 - :hg:`resolve -l`: list files which had or still have conflicts.
3166 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3167 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3167
3168
3168 Note that Mercurial will not let you commit files with unresolved
3169 Note that Mercurial will not let you commit files with unresolved
3169 merge conflicts. You must use :hg:`resolve -m ...` before you can
3170 merge conflicts. You must use :hg:`resolve -m ...` before you can
3170 commit after a conflicting merge.
3171 commit after a conflicting merge.
3171
3172
3172 Returns 0 on success, 1 if any files fail a resolve attempt.
3173 Returns 0 on success, 1 if any files fail a resolve attempt.
3173 """
3174 """
3174
3175
3175 all, mark, unmark, show, nostatus = \
3176 all, mark, unmark, show, nostatus = \
3176 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3177 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3177
3178
3178 if (show and (mark or unmark)) or (mark and unmark):
3179 if (show and (mark or unmark)) or (mark and unmark):
3179 raise util.Abort(_("too many options specified"))
3180 raise util.Abort(_("too many options specified"))
3180 if pats and all:
3181 if pats and all:
3181 raise util.Abort(_("can't specify --all and patterns"))
3182 raise util.Abort(_("can't specify --all and patterns"))
3182 if not (all or pats or show or mark or unmark):
3183 if not (all or pats or show or mark or unmark):
3183 raise util.Abort(_('no files or directories specified; '
3184 raise util.Abort(_('no files or directories specified; '
3184 'use --all to remerge all files'))
3185 'use --all to remerge all files'))
3185
3186
3186 ms = mergemod.mergestate(repo)
3187 ms = mergemod.mergestate(repo)
3187 m = cmdutil.match(repo, pats, opts)
3188 m = cmdutil.match(repo, pats, opts)
3188 ret = 0
3189 ret = 0
3189
3190
3190 for f in ms:
3191 for f in ms:
3191 if m(f):
3192 if m(f):
3192 if show:
3193 if show:
3193 if nostatus:
3194 if nostatus:
3194 ui.write("%s\n" % f)
3195 ui.write("%s\n" % f)
3195 else:
3196 else:
3196 ui.write("%s %s\n" % (ms[f].upper(), f),
3197 ui.write("%s %s\n" % (ms[f].upper(), f),
3197 label='resolve.' +
3198 label='resolve.' +
3198 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3199 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3199 elif mark:
3200 elif mark:
3200 ms.mark(f, "r")
3201 ms.mark(f, "r")
3201 elif unmark:
3202 elif unmark:
3202 ms.mark(f, "u")
3203 ms.mark(f, "u")
3203 else:
3204 else:
3204 wctx = repo[None]
3205 wctx = repo[None]
3205 mctx = wctx.parents()[-1]
3206 mctx = wctx.parents()[-1]
3206
3207
3207 # backup pre-resolve (merge uses .orig for its own purposes)
3208 # backup pre-resolve (merge uses .orig for its own purposes)
3208 a = repo.wjoin(f)
3209 a = repo.wjoin(f)
3209 util.copyfile(a, a + ".resolve")
3210 util.copyfile(a, a + ".resolve")
3210
3211
3211 try:
3212 try:
3212 # resolve file
3213 # resolve file
3213 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3214 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3214 if ms.resolve(f, wctx, mctx):
3215 if ms.resolve(f, wctx, mctx):
3215 ret = 1
3216 ret = 1
3216 finally:
3217 finally:
3217 ui.setconfig('ui', 'forcemerge', '')
3218 ui.setconfig('ui', 'forcemerge', '')
3218
3219
3219 # replace filemerge's .orig file with our resolve file
3220 # replace filemerge's .orig file with our resolve file
3220 util.rename(a + ".resolve", a + ".orig")
3221 util.rename(a + ".resolve", a + ".orig")
3221
3222
3222 ms.commit()
3223 ms.commit()
3223 return ret
3224 return ret
3224
3225
3225 def revert(ui, repo, *pats, **opts):
3226 def revert(ui, repo, *pats, **opts):
3226 """restore individual files or directories to an earlier state
3227 """restore individual files or directories to an earlier state
3227
3228
3228 .. note::
3229 .. note::
3229 This command is most likely not what you are looking for.
3230 This command is most likely not what you are looking for.
3230 Revert will partially overwrite content in the working
3231 Revert will partially overwrite content in the working
3231 directory without changing the working directory parents. Use
3232 directory without changing the working directory parents. Use
3232 :hg:`update -r rev` to check out earlier revisions, or
3233 :hg:`update -r rev` to check out earlier revisions, or
3233 :hg:`update --clean .` to undo a merge which has added another
3234 :hg:`update --clean .` to undo a merge which has added another
3234 parent.
3235 parent.
3235
3236
3236 With no revision specified, revert the named files or directories
3237 With no revision specified, revert the named files or directories
3237 to the contents they had in the parent of the working directory.
3238 to the contents they had in the parent of the working directory.
3238 This restores the contents of the affected files to an unmodified
3239 This restores the contents of the affected files to an unmodified
3239 state and unschedules adds, removes, copies, and renames. If the
3240 state and unschedules adds, removes, copies, and renames. If the
3240 working directory has two parents, you must explicitly specify a
3241 working directory has two parents, you must explicitly specify a
3241 revision.
3242 revision.
3242
3243
3243 Using the -r/--rev option, revert the given files or directories
3244 Using the -r/--rev option, revert the given files or directories
3244 to their contents as of a specific revision. This can be helpful
3245 to their contents as of a specific revision. This can be helpful
3245 to "roll back" some or all of an earlier change. See :hg:`help
3246 to "roll back" some or all of an earlier change. See :hg:`help
3246 dates` for a list of formats valid for -d/--date.
3247 dates` for a list of formats valid for -d/--date.
3247
3248
3248 Revert modifies the working directory. It does not commit any
3249 Revert modifies the working directory. It does not commit any
3249 changes, or change the parent of the working directory. If you
3250 changes, or change the parent of the working directory. If you
3250 revert to a revision other than the parent of the working
3251 revert to a revision other than the parent of the working
3251 directory, the reverted files will thus appear modified
3252 directory, the reverted files will thus appear modified
3252 afterwards.
3253 afterwards.
3253
3254
3254 If a file has been deleted, it is restored. If the executable mode
3255 If a file has been deleted, it is restored. If the executable mode
3255 of a file was changed, it is reset.
3256 of a file was changed, it is reset.
3256
3257
3257 If names are given, all files matching the names are reverted.
3258 If names are given, all files matching the names are reverted.
3258 If no arguments are given, no files are reverted.
3259 If no arguments are given, no files are reverted.
3259
3260
3260 Modified files are saved with a .orig suffix before reverting.
3261 Modified files are saved with a .orig suffix before reverting.
3261 To disable these backups, use --no-backup.
3262 To disable these backups, use --no-backup.
3262
3263
3263 Returns 0 on success.
3264 Returns 0 on success.
3264 """
3265 """
3265
3266
3266 if opts.get("date"):
3267 if opts.get("date"):
3267 if opts.get("rev"):
3268 if opts.get("rev"):
3268 raise util.Abort(_("you can't specify a revision and a date"))
3269 raise util.Abort(_("you can't specify a revision and a date"))
3269 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3270 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3270
3271
3271 parent, p2 = repo.dirstate.parents()
3272 parent, p2 = repo.dirstate.parents()
3272 if not opts.get('rev') and p2 != nullid:
3273 if not opts.get('rev') and p2 != nullid:
3273 raise util.Abort(_('uncommitted merge - '
3274 raise util.Abort(_('uncommitted merge - '
3274 'use "hg update", see "hg help revert"'))
3275 'use "hg update", see "hg help revert"'))
3275
3276
3276 if not pats and not opts.get('all'):
3277 if not pats and not opts.get('all'):
3277 raise util.Abort(_('no files or directories specified; '
3278 raise util.Abort(_('no files or directories specified; '
3278 'use --all to revert the whole repo'))
3279 'use --all to revert the whole repo'))
3279
3280
3280 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3281 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3281 node = ctx.node()
3282 node = ctx.node()
3282 mf = ctx.manifest()
3283 mf = ctx.manifest()
3283 if node == parent:
3284 if node == parent:
3284 pmf = mf
3285 pmf = mf
3285 else:
3286 else:
3286 pmf = None
3287 pmf = None
3287
3288
3288 # need all matching names in dirstate and manifest of target rev,
3289 # need all matching names in dirstate and manifest of target rev,
3289 # so have to walk both. do not print errors if files exist in one
3290 # so have to walk both. do not print errors if files exist in one
3290 # but not other.
3291 # but not other.
3291
3292
3292 names = {}
3293 names = {}
3293
3294
3294 wlock = repo.wlock()
3295 wlock = repo.wlock()
3295 try:
3296 try:
3296 # walk dirstate.
3297 # walk dirstate.
3297
3298
3298 m = cmdutil.match(repo, pats, opts)
3299 m = cmdutil.match(repo, pats, opts)
3299 m.bad = lambda x, y: False
3300 m.bad = lambda x, y: False
3300 for abs in repo.walk(m):
3301 for abs in repo.walk(m):
3301 names[abs] = m.rel(abs), m.exact(abs)
3302 names[abs] = m.rel(abs), m.exact(abs)
3302
3303
3303 # walk target manifest.
3304 # walk target manifest.
3304
3305
3305 def badfn(path, msg):
3306 def badfn(path, msg):
3306 if path in names:
3307 if path in names:
3307 return
3308 return
3308 path_ = path + '/'
3309 path_ = path + '/'
3309 for f in names:
3310 for f in names:
3310 if f.startswith(path_):
3311 if f.startswith(path_):
3311 return
3312 return
3312 ui.warn("%s: %s\n" % (m.rel(path), msg))
3313 ui.warn("%s: %s\n" % (m.rel(path), msg))
3313
3314
3314 m = cmdutil.match(repo, pats, opts)
3315 m = cmdutil.match(repo, pats, opts)
3315 m.bad = badfn
3316 m.bad = badfn
3316 for abs in repo[node].walk(m):
3317 for abs in repo[node].walk(m):
3317 if abs not in names:
3318 if abs not in names:
3318 names[abs] = m.rel(abs), m.exact(abs)
3319 names[abs] = m.rel(abs), m.exact(abs)
3319
3320
3320 m = cmdutil.matchfiles(repo, names)
3321 m = cmdutil.matchfiles(repo, names)
3321 changes = repo.status(match=m)[:4]
3322 changes = repo.status(match=m)[:4]
3322 modified, added, removed, deleted = map(set, changes)
3323 modified, added, removed, deleted = map(set, changes)
3323
3324
3324 # if f is a rename, also revert the source
3325 # if f is a rename, also revert the source
3325 cwd = repo.getcwd()
3326 cwd = repo.getcwd()
3326 for f in added:
3327 for f in added:
3327 src = repo.dirstate.copied(f)
3328 src = repo.dirstate.copied(f)
3328 if src and src not in names and repo.dirstate[src] == 'r':
3329 if src and src not in names and repo.dirstate[src] == 'r':
3329 removed.add(src)
3330 removed.add(src)
3330 names[src] = (repo.pathto(src, cwd), True)
3331 names[src] = (repo.pathto(src, cwd), True)
3331
3332
3332 def removeforget(abs):
3333 def removeforget(abs):
3333 if repo.dirstate[abs] == 'a':
3334 if repo.dirstate[abs] == 'a':
3334 return _('forgetting %s\n')
3335 return _('forgetting %s\n')
3335 return _('removing %s\n')
3336 return _('removing %s\n')
3336
3337
3337 revert = ([], _('reverting %s\n'))
3338 revert = ([], _('reverting %s\n'))
3338 add = ([], _('adding %s\n'))
3339 add = ([], _('adding %s\n'))
3339 remove = ([], removeforget)
3340 remove = ([], removeforget)
3340 undelete = ([], _('undeleting %s\n'))
3341 undelete = ([], _('undeleting %s\n'))
3341
3342
3342 disptable = (
3343 disptable = (
3343 # dispatch table:
3344 # dispatch table:
3344 # file state
3345 # file state
3345 # action if in target manifest
3346 # action if in target manifest
3346 # action if not in target manifest
3347 # action if not in target manifest
3347 # make backup if in target manifest
3348 # make backup if in target manifest
3348 # make backup if not in target manifest
3349 # make backup if not in target manifest
3349 (modified, revert, remove, True, True),
3350 (modified, revert, remove, True, True),
3350 (added, revert, remove, True, False),
3351 (added, revert, remove, True, False),
3351 (removed, undelete, None, False, False),
3352 (removed, undelete, None, False, False),
3352 (deleted, revert, remove, False, False),
3353 (deleted, revert, remove, False, False),
3353 )
3354 )
3354
3355
3355 for abs, (rel, exact) in sorted(names.items()):
3356 for abs, (rel, exact) in sorted(names.items()):
3356 mfentry = mf.get(abs)
3357 mfentry = mf.get(abs)
3357 target = repo.wjoin(abs)
3358 target = repo.wjoin(abs)
3358 def handle(xlist, dobackup):
3359 def handle(xlist, dobackup):
3359 xlist[0].append(abs)
3360 xlist[0].append(abs)
3360 if (dobackup and not opts.get('no_backup') and
3361 if (dobackup and not opts.get('no_backup') and
3361 os.path.lexists(target)):
3362 os.path.lexists(target)):
3362 bakname = "%s.orig" % rel
3363 bakname = "%s.orig" % rel
3363 ui.note(_('saving current version of %s as %s\n') %
3364 ui.note(_('saving current version of %s as %s\n') %
3364 (rel, bakname))
3365 (rel, bakname))
3365 if not opts.get('dry_run'):
3366 if not opts.get('dry_run'):
3366 util.rename(target, bakname)
3367 util.rename(target, bakname)
3367 if ui.verbose or not exact:
3368 if ui.verbose or not exact:
3368 msg = xlist[1]
3369 msg = xlist[1]
3369 if not isinstance(msg, basestring):
3370 if not isinstance(msg, basestring):
3370 msg = msg(abs)
3371 msg = msg(abs)
3371 ui.status(msg % rel)
3372 ui.status(msg % rel)
3372 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3373 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3373 if abs not in table:
3374 if abs not in table:
3374 continue
3375 continue
3375 # file has changed in dirstate
3376 # file has changed in dirstate
3376 if mfentry:
3377 if mfentry:
3377 handle(hitlist, backuphit)
3378 handle(hitlist, backuphit)
3378 elif misslist is not None:
3379 elif misslist is not None:
3379 handle(misslist, backupmiss)
3380 handle(misslist, backupmiss)
3380 break
3381 break
3381 else:
3382 else:
3382 if abs not in repo.dirstate:
3383 if abs not in repo.dirstate:
3383 if mfentry:
3384 if mfentry:
3384 handle(add, True)
3385 handle(add, True)
3385 elif exact:
3386 elif exact:
3386 ui.warn(_('file not managed: %s\n') % rel)
3387 ui.warn(_('file not managed: %s\n') % rel)
3387 continue
3388 continue
3388 # file has not changed in dirstate
3389 # file has not changed in dirstate
3389 if node == parent:
3390 if node == parent:
3390 if exact:
3391 if exact:
3391 ui.warn(_('no changes needed to %s\n') % rel)
3392 ui.warn(_('no changes needed to %s\n') % rel)
3392 continue
3393 continue
3393 if pmf is None:
3394 if pmf is None:
3394 # only need parent manifest in this unlikely case,
3395 # only need parent manifest in this unlikely case,
3395 # so do not read by default
3396 # so do not read by default
3396 pmf = repo[parent].manifest()
3397 pmf = repo[parent].manifest()
3397 if abs in pmf:
3398 if abs in pmf:
3398 if mfentry:
3399 if mfentry:
3399 # if version of file is same in parent and target
3400 # if version of file is same in parent and target
3400 # manifests, do nothing
3401 # manifests, do nothing
3401 if (pmf[abs] != mfentry or
3402 if (pmf[abs] != mfentry or
3402 pmf.flags(abs) != mf.flags(abs)):
3403 pmf.flags(abs) != mf.flags(abs)):
3403 handle(revert, False)
3404 handle(revert, False)
3404 else:
3405 else:
3405 handle(remove, False)
3406 handle(remove, False)
3406
3407
3407 if not opts.get('dry_run'):
3408 if not opts.get('dry_run'):
3408 def checkout(f):
3409 def checkout(f):
3409 fc = ctx[f]
3410 fc = ctx[f]
3410 repo.wwrite(f, fc.data(), fc.flags())
3411 repo.wwrite(f, fc.data(), fc.flags())
3411
3412
3412 audit_path = util.path_auditor(repo.root)
3413 audit_path = util.path_auditor(repo.root)
3413 for f in remove[0]:
3414 for f in remove[0]:
3414 if repo.dirstate[f] == 'a':
3415 if repo.dirstate[f] == 'a':
3415 repo.dirstate.forget(f)
3416 repo.dirstate.forget(f)
3416 continue
3417 continue
3417 audit_path(f)
3418 audit_path(f)
3418 try:
3419 try:
3419 util.unlinkpath(repo.wjoin(f))
3420 util.unlinkpath(repo.wjoin(f))
3420 except OSError:
3421 except OSError:
3421 pass
3422 pass
3422 repo.dirstate.remove(f)
3423 repo.dirstate.remove(f)
3423
3424
3424 normal = None
3425 normal = None
3425 if node == parent:
3426 if node == parent:
3426 # We're reverting to our parent. If possible, we'd like status
3427 # We're reverting to our parent. If possible, we'd like status
3427 # to report the file as clean. We have to use normallookup for
3428 # to report the file as clean. We have to use normallookup for
3428 # merges to avoid losing information about merged/dirty files.
3429 # merges to avoid losing information about merged/dirty files.
3429 if p2 != nullid:
3430 if p2 != nullid:
3430 normal = repo.dirstate.normallookup
3431 normal = repo.dirstate.normallookup
3431 else:
3432 else:
3432 normal = repo.dirstate.normal
3433 normal = repo.dirstate.normal
3433 for f in revert[0]:
3434 for f in revert[0]:
3434 checkout(f)
3435 checkout(f)
3435 if normal:
3436 if normal:
3436 normal(f)
3437 normal(f)
3437
3438
3438 for f in add[0]:
3439 for f in add[0]:
3439 checkout(f)
3440 checkout(f)
3440 repo.dirstate.add(f)
3441 repo.dirstate.add(f)
3441
3442
3442 normal = repo.dirstate.normallookup
3443 normal = repo.dirstate.normallookup
3443 if node == parent and p2 == nullid:
3444 if node == parent and p2 == nullid:
3444 normal = repo.dirstate.normal
3445 normal = repo.dirstate.normal
3445 for f in undelete[0]:
3446 for f in undelete[0]:
3446 checkout(f)
3447 checkout(f)
3447 normal(f)
3448 normal(f)
3448
3449
3449 finally:
3450 finally:
3450 wlock.release()
3451 wlock.release()
3451
3452
3452 def rollback(ui, repo, **opts):
3453 def rollback(ui, repo, **opts):
3453 """roll back the last transaction (dangerous)
3454 """roll back the last transaction (dangerous)
3454
3455
3455 This command should be used with care. There is only one level of
3456 This command should be used with care. There is only one level of
3456 rollback, and there is no way to undo a rollback. It will also
3457 rollback, and there is no way to undo a rollback. It will also
3457 restore the dirstate at the time of the last transaction, losing
3458 restore the dirstate at the time of the last transaction, losing
3458 any dirstate changes since that time. This command does not alter
3459 any dirstate changes since that time. This command does not alter
3459 the working directory.
3460 the working directory.
3460
3461
3461 Transactions are used to encapsulate the effects of all commands
3462 Transactions are used to encapsulate the effects of all commands
3462 that create new changesets or propagate existing changesets into a
3463 that create new changesets or propagate existing changesets into a
3463 repository. For example, the following commands are transactional,
3464 repository. For example, the following commands are transactional,
3464 and their effects can be rolled back:
3465 and their effects can be rolled back:
3465
3466
3466 - commit
3467 - commit
3467 - import
3468 - import
3468 - pull
3469 - pull
3469 - push (with this repository as the destination)
3470 - push (with this repository as the destination)
3470 - unbundle
3471 - unbundle
3471
3472
3472 This command is not intended for use on public repositories. Once
3473 This command is not intended for use on public repositories. Once
3473 changes are visible for pull by other users, rolling a transaction
3474 changes are visible for pull by other users, rolling a transaction
3474 back locally is ineffective (someone else may already have pulled
3475 back locally is ineffective (someone else may already have pulled
3475 the changes). Furthermore, a race is possible with readers of the
3476 the changes). Furthermore, a race is possible with readers of the
3476 repository; for example an in-progress pull from the repository
3477 repository; for example an in-progress pull from the repository
3477 may fail if a rollback is performed.
3478 may fail if a rollback is performed.
3478
3479
3479 Returns 0 on success, 1 if no rollback data is available.
3480 Returns 0 on success, 1 if no rollback data is available.
3480 """
3481 """
3481 return repo.rollback(opts.get('dry_run'))
3482 return repo.rollback(opts.get('dry_run'))
3482
3483
3483 def root(ui, repo):
3484 def root(ui, repo):
3484 """print the root (top) of the current working directory
3485 """print the root (top) of the current working directory
3485
3486
3486 Print the root directory of the current repository.
3487 Print the root directory of the current repository.
3487
3488
3488 Returns 0 on success.
3489 Returns 0 on success.
3489 """
3490 """
3490 ui.write(repo.root + "\n")
3491 ui.write(repo.root + "\n")
3491
3492
3492 def serve(ui, repo, **opts):
3493 def serve(ui, repo, **opts):
3493 """start stand-alone webserver
3494 """start stand-alone webserver
3494
3495
3495 Start a local HTTP repository browser and pull server. You can use
3496 Start a local HTTP repository browser and pull server. You can use
3496 this for ad-hoc sharing and browsing of repositories. It is
3497 this for ad-hoc sharing and browsing of repositories. It is
3497 recommended to use a real web server to serve a repository for
3498 recommended to use a real web server to serve a repository for
3498 longer periods of time.
3499 longer periods of time.
3499
3500
3500 Please note that the server does not implement access control.
3501 Please note that the server does not implement access control.
3501 This means that, by default, anybody can read from the server and
3502 This means that, by default, anybody can read from the server and
3502 nobody can write to it by default. Set the ``web.allow_push``
3503 nobody can write to it by default. Set the ``web.allow_push``
3503 option to ``*`` to allow everybody to push to the server. You
3504 option to ``*`` to allow everybody to push to the server. You
3504 should use a real web server if you need to authenticate users.
3505 should use a real web server if you need to authenticate users.
3505
3506
3506 By default, the server logs accesses to stdout and errors to
3507 By default, the server logs accesses to stdout and errors to
3507 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3508 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3508 files.
3509 files.
3509
3510
3510 To have the server choose a free port number to listen on, specify
3511 To have the server choose a free port number to listen on, specify
3511 a port number of 0; in this case, the server will print the port
3512 a port number of 0; in this case, the server will print the port
3512 number it uses.
3513 number it uses.
3513
3514
3514 Returns 0 on success.
3515 Returns 0 on success.
3515 """
3516 """
3516
3517
3517 if opts["stdio"]:
3518 if opts["stdio"]:
3518 if repo is None:
3519 if repo is None:
3519 raise error.RepoError(_("There is no Mercurial repository here"
3520 raise error.RepoError(_("There is no Mercurial repository here"
3520 " (.hg not found)"))
3521 " (.hg not found)"))
3521 s = sshserver.sshserver(ui, repo)
3522 s = sshserver.sshserver(ui, repo)
3522 s.serve_forever()
3523 s.serve_forever()
3523
3524
3524 # this way we can check if something was given in the command-line
3525 # this way we can check if something was given in the command-line
3525 if opts.get('port'):
3526 if opts.get('port'):
3526 opts['port'] = util.getport(opts.get('port'))
3527 opts['port'] = util.getport(opts.get('port'))
3527
3528
3528 baseui = repo and repo.baseui or ui
3529 baseui = repo and repo.baseui or ui
3529 optlist = ("name templates style address port prefix ipv6"
3530 optlist = ("name templates style address port prefix ipv6"
3530 " accesslog errorlog certificate encoding")
3531 " accesslog errorlog certificate encoding")
3531 for o in optlist.split():
3532 for o in optlist.split():
3532 val = opts.get(o, '')
3533 val = opts.get(o, '')
3533 if val in (None, ''): # should check against default options instead
3534 if val in (None, ''): # should check against default options instead
3534 continue
3535 continue
3535 baseui.setconfig("web", o, val)
3536 baseui.setconfig("web", o, val)
3536 if repo and repo.ui != baseui:
3537 if repo and repo.ui != baseui:
3537 repo.ui.setconfig("web", o, val)
3538 repo.ui.setconfig("web", o, val)
3538
3539
3539 o = opts.get('web_conf') or opts.get('webdir_conf')
3540 o = opts.get('web_conf') or opts.get('webdir_conf')
3540 if not o:
3541 if not o:
3541 if not repo:
3542 if not repo:
3542 raise error.RepoError(_("There is no Mercurial repository"
3543 raise error.RepoError(_("There is no Mercurial repository"
3543 " here (.hg not found)"))
3544 " here (.hg not found)"))
3544 o = repo.root
3545 o = repo.root
3545
3546
3546 app = hgweb.hgweb(o, baseui=ui)
3547 app = hgweb.hgweb(o, baseui=ui)
3547
3548
3548 class service(object):
3549 class service(object):
3549 def init(self):
3550 def init(self):
3550 util.set_signal_handler()
3551 util.set_signal_handler()
3551 self.httpd = hgweb.server.create_server(ui, app)
3552 self.httpd = hgweb.server.create_server(ui, app)
3552
3553
3553 if opts['port'] and not ui.verbose:
3554 if opts['port'] and not ui.verbose:
3554 return
3555 return
3555
3556
3556 if self.httpd.prefix:
3557 if self.httpd.prefix:
3557 prefix = self.httpd.prefix.strip('/') + '/'
3558 prefix = self.httpd.prefix.strip('/') + '/'
3558 else:
3559 else:
3559 prefix = ''
3560 prefix = ''
3560
3561
3561 port = ':%d' % self.httpd.port
3562 port = ':%d' % self.httpd.port
3562 if port == ':80':
3563 if port == ':80':
3563 port = ''
3564 port = ''
3564
3565
3565 bindaddr = self.httpd.addr
3566 bindaddr = self.httpd.addr
3566 if bindaddr == '0.0.0.0':
3567 if bindaddr == '0.0.0.0':
3567 bindaddr = '*'
3568 bindaddr = '*'
3568 elif ':' in bindaddr: # IPv6
3569 elif ':' in bindaddr: # IPv6
3569 bindaddr = '[%s]' % bindaddr
3570 bindaddr = '[%s]' % bindaddr
3570
3571
3571 fqaddr = self.httpd.fqaddr
3572 fqaddr = self.httpd.fqaddr
3572 if ':' in fqaddr:
3573 if ':' in fqaddr:
3573 fqaddr = '[%s]' % fqaddr
3574 fqaddr = '[%s]' % fqaddr
3574 if opts['port']:
3575 if opts['port']:
3575 write = ui.status
3576 write = ui.status
3576 else:
3577 else:
3577 write = ui.write
3578 write = ui.write
3578 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3579 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3579 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3580 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3580
3581
3581 def run(self):
3582 def run(self):
3582 self.httpd.serve_forever()
3583 self.httpd.serve_forever()
3583
3584
3584 service = service()
3585 service = service()
3585
3586
3586 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3587 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3587
3588
3588 def status(ui, repo, *pats, **opts):
3589 def status(ui, repo, *pats, **opts):
3589 """show changed files in the working directory
3590 """show changed files in the working directory
3590
3591
3591 Show status of files in the repository. If names are given, only
3592 Show status of files in the repository. If names are given, only
3592 files that match are shown. Files that are clean or ignored or
3593 files that match are shown. Files that are clean or ignored or
3593 the source of a copy/move operation, are not listed unless
3594 the source of a copy/move operation, are not listed unless
3594 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3595 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3595 Unless options described with "show only ..." are given, the
3596 Unless options described with "show only ..." are given, the
3596 options -mardu are used.
3597 options -mardu are used.
3597
3598
3598 Option -q/--quiet hides untracked (unknown and ignored) files
3599 Option -q/--quiet hides untracked (unknown and ignored) files
3599 unless explicitly requested with -u/--unknown or -i/--ignored.
3600 unless explicitly requested with -u/--unknown or -i/--ignored.
3600
3601
3601 .. note::
3602 .. note::
3602 status may appear to disagree with diff if permissions have
3603 status may appear to disagree with diff if permissions have
3603 changed or a merge has occurred. The standard diff format does
3604 changed or a merge has occurred. The standard diff format does
3604 not report permission changes and diff only reports changes
3605 not report permission changes and diff only reports changes
3605 relative to one merge parent.
3606 relative to one merge parent.
3606
3607
3607 If one revision is given, it is used as the base revision.
3608 If one revision is given, it is used as the base revision.
3608 If two revisions are given, the differences between them are
3609 If two revisions are given, the differences between them are
3609 shown. The --change option can also be used as a shortcut to list
3610 shown. The --change option can also be used as a shortcut to list
3610 the changed files of a revision from its first parent.
3611 the changed files of a revision from its first parent.
3611
3612
3612 The codes used to show the status of files are::
3613 The codes used to show the status of files are::
3613
3614
3614 M = modified
3615 M = modified
3615 A = added
3616 A = added
3616 R = removed
3617 R = removed
3617 C = clean
3618 C = clean
3618 ! = missing (deleted by non-hg command, but still tracked)
3619 ! = missing (deleted by non-hg command, but still tracked)
3619 ? = not tracked
3620 ? = not tracked
3620 I = ignored
3621 I = ignored
3621 = origin of the previous file listed as A (added)
3622 = origin of the previous file listed as A (added)
3622
3623
3623 Returns 0 on success.
3624 Returns 0 on success.
3624 """
3625 """
3625
3626
3626 revs = opts.get('rev')
3627 revs = opts.get('rev')
3627 change = opts.get('change')
3628 change = opts.get('change')
3628
3629
3629 if revs and change:
3630 if revs and change:
3630 msg = _('cannot specify --rev and --change at the same time')
3631 msg = _('cannot specify --rev and --change at the same time')
3631 raise util.Abort(msg)
3632 raise util.Abort(msg)
3632 elif change:
3633 elif change:
3633 node2 = repo.lookup(change)
3634 node2 = repo.lookup(change)
3634 node1 = repo[node2].parents()[0].node()
3635 node1 = repo[node2].parents()[0].node()
3635 else:
3636 else:
3636 node1, node2 = cmdutil.revpair(repo, revs)
3637 node1, node2 = cmdutil.revpair(repo, revs)
3637
3638
3638 cwd = (pats and repo.getcwd()) or ''
3639 cwd = (pats and repo.getcwd()) or ''
3639 end = opts.get('print0') and '\0' or '\n'
3640 end = opts.get('print0') and '\0' or '\n'
3640 copy = {}
3641 copy = {}
3641 states = 'modified added removed deleted unknown ignored clean'.split()
3642 states = 'modified added removed deleted unknown ignored clean'.split()
3642 show = [k for k in states if opts.get(k)]
3643 show = [k for k in states if opts.get(k)]
3643 if opts.get('all'):
3644 if opts.get('all'):
3644 show += ui.quiet and (states[:4] + ['clean']) or states
3645 show += ui.quiet and (states[:4] + ['clean']) or states
3645 if not show:
3646 if not show:
3646 show = ui.quiet and states[:4] or states[:5]
3647 show = ui.quiet and states[:4] or states[:5]
3647
3648
3648 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3649 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3649 'ignored' in show, 'clean' in show, 'unknown' in show,
3650 'ignored' in show, 'clean' in show, 'unknown' in show,
3650 opts.get('subrepos'))
3651 opts.get('subrepos'))
3651 changestates = zip(states, 'MAR!?IC', stat)
3652 changestates = zip(states, 'MAR!?IC', stat)
3652
3653
3653 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3654 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3654 ctxn = repo[nullid]
3655 ctxn = repo[nullid]
3655 ctx1 = repo[node1]
3656 ctx1 = repo[node1]
3656 ctx2 = repo[node2]
3657 ctx2 = repo[node2]
3657 added = stat[1]
3658 added = stat[1]
3658 if node2 is None:
3659 if node2 is None:
3659 added = stat[0] + stat[1] # merged?
3660 added = stat[0] + stat[1] # merged?
3660
3661
3661 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3662 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3662 if k in added:
3663 if k in added:
3663 copy[k] = v
3664 copy[k] = v
3664 elif v in added:
3665 elif v in added:
3665 copy[v] = k
3666 copy[v] = k
3666
3667
3667 for state, char, files in changestates:
3668 for state, char, files in changestates:
3668 if state in show:
3669 if state in show:
3669 format = "%s %%s%s" % (char, end)
3670 format = "%s %%s%s" % (char, end)
3670 if opts.get('no_status'):
3671 if opts.get('no_status'):
3671 format = "%%s%s" % end
3672 format = "%%s%s" % end
3672
3673
3673 for f in files:
3674 for f in files:
3674 ui.write(format % repo.pathto(f, cwd),
3675 ui.write(format % repo.pathto(f, cwd),
3675 label='status.' + state)
3676 label='status.' + state)
3676 if f in copy:
3677 if f in copy:
3677 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3678 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3678 label='status.copied')
3679 label='status.copied')
3679
3680
3680 def summary(ui, repo, **opts):
3681 def summary(ui, repo, **opts):
3681 """summarize working directory state
3682 """summarize working directory state
3682
3683
3683 This generates a brief summary of the working directory state,
3684 This generates a brief summary of the working directory state,
3684 including parents, branch, commit status, and available updates.
3685 including parents, branch, commit status, and available updates.
3685
3686
3686 With the --remote option, this will check the default paths for
3687 With the --remote option, this will check the default paths for
3687 incoming and outgoing changes. This can be time-consuming.
3688 incoming and outgoing changes. This can be time-consuming.
3688
3689
3689 Returns 0 on success.
3690 Returns 0 on success.
3690 """
3691 """
3691
3692
3692 ctx = repo[None]
3693 ctx = repo[None]
3693 parents = ctx.parents()
3694 parents = ctx.parents()
3694 pnode = parents[0].node()
3695 pnode = parents[0].node()
3695
3696
3696 for p in parents:
3697 for p in parents:
3697 # label with log.changeset (instead of log.parent) since this
3698 # label with log.changeset (instead of log.parent) since this
3698 # shows a working directory parent *changeset*:
3699 # shows a working directory parent *changeset*:
3699 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3700 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3700 label='log.changeset')
3701 label='log.changeset')
3701 ui.write(' '.join(p.tags()), label='log.tag')
3702 ui.write(' '.join(p.tags()), label='log.tag')
3702 if p.rev() == -1:
3703 if p.rev() == -1:
3703 if not len(repo):
3704 if not len(repo):
3704 ui.write(_(' (empty repository)'))
3705 ui.write(_(' (empty repository)'))
3705 else:
3706 else:
3706 ui.write(_(' (no revision checked out)'))
3707 ui.write(_(' (no revision checked out)'))
3707 ui.write('\n')
3708 ui.write('\n')
3708 if p.description():
3709 if p.description():
3709 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3710 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3710 label='log.summary')
3711 label='log.summary')
3711
3712
3712 branch = ctx.branch()
3713 branch = ctx.branch()
3713 bheads = repo.branchheads(branch)
3714 bheads = repo.branchheads(branch)
3714 m = _('branch: %s\n') % branch
3715 m = _('branch: %s\n') % branch
3715 if branch != 'default':
3716 if branch != 'default':
3716 ui.write(m, label='log.branch')
3717 ui.write(m, label='log.branch')
3717 else:
3718 else:
3718 ui.status(m, label='log.branch')
3719 ui.status(m, label='log.branch')
3719
3720
3720 st = list(repo.status(unknown=True))[:6]
3721 st = list(repo.status(unknown=True))[:6]
3721
3722
3722 c = repo.dirstate.copies()
3723 c = repo.dirstate.copies()
3723 copied, renamed = [], []
3724 copied, renamed = [], []
3724 for d, s in c.iteritems():
3725 for d, s in c.iteritems():
3725 if s in st[2]:
3726 if s in st[2]:
3726 st[2].remove(s)
3727 st[2].remove(s)
3727 renamed.append(d)
3728 renamed.append(d)
3728 else:
3729 else:
3729 copied.append(d)
3730 copied.append(d)
3730 if d in st[1]:
3731 if d in st[1]:
3731 st[1].remove(d)
3732 st[1].remove(d)
3732 st.insert(3, renamed)
3733 st.insert(3, renamed)
3733 st.insert(4, copied)
3734 st.insert(4, copied)
3734
3735
3735 ms = mergemod.mergestate(repo)
3736 ms = mergemod.mergestate(repo)
3736 st.append([f for f in ms if ms[f] == 'u'])
3737 st.append([f for f in ms if ms[f] == 'u'])
3737
3738
3738 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3739 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3739 st.append(subs)
3740 st.append(subs)
3740
3741
3741 labels = [ui.label(_('%d modified'), 'status.modified'),
3742 labels = [ui.label(_('%d modified'), 'status.modified'),
3742 ui.label(_('%d added'), 'status.added'),
3743 ui.label(_('%d added'), 'status.added'),
3743 ui.label(_('%d removed'), 'status.removed'),
3744 ui.label(_('%d removed'), 'status.removed'),
3744 ui.label(_('%d renamed'), 'status.copied'),
3745 ui.label(_('%d renamed'), 'status.copied'),
3745 ui.label(_('%d copied'), 'status.copied'),
3746 ui.label(_('%d copied'), 'status.copied'),
3746 ui.label(_('%d deleted'), 'status.deleted'),
3747 ui.label(_('%d deleted'), 'status.deleted'),
3747 ui.label(_('%d unknown'), 'status.unknown'),
3748 ui.label(_('%d unknown'), 'status.unknown'),
3748 ui.label(_('%d ignored'), 'status.ignored'),
3749 ui.label(_('%d ignored'), 'status.ignored'),
3749 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3750 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3750 ui.label(_('%d subrepos'), 'status.modified')]
3751 ui.label(_('%d subrepos'), 'status.modified')]
3751 t = []
3752 t = []
3752 for s, l in zip(st, labels):
3753 for s, l in zip(st, labels):
3753 if s:
3754 if s:
3754 t.append(l % len(s))
3755 t.append(l % len(s))
3755
3756
3756 t = ', '.join(t)
3757 t = ', '.join(t)
3757 cleanworkdir = False
3758 cleanworkdir = False
3758
3759
3759 if len(parents) > 1:
3760 if len(parents) > 1:
3760 t += _(' (merge)')
3761 t += _(' (merge)')
3761 elif branch != parents[0].branch():
3762 elif branch != parents[0].branch():
3762 t += _(' (new branch)')
3763 t += _(' (new branch)')
3763 elif (parents[0].extra().get('close') and
3764 elif (parents[0].extra().get('close') and
3764 pnode in repo.branchheads(branch, closed=True)):
3765 pnode in repo.branchheads(branch, closed=True)):
3765 t += _(' (head closed)')
3766 t += _(' (head closed)')
3766 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3767 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3767 t += _(' (clean)')
3768 t += _(' (clean)')
3768 cleanworkdir = True
3769 cleanworkdir = True
3769 elif pnode not in bheads:
3770 elif pnode not in bheads:
3770 t += _(' (new branch head)')
3771 t += _(' (new branch head)')
3771
3772
3772 if cleanworkdir:
3773 if cleanworkdir:
3773 ui.status(_('commit: %s\n') % t.strip())
3774 ui.status(_('commit: %s\n') % t.strip())
3774 else:
3775 else:
3775 ui.write(_('commit: %s\n') % t.strip())
3776 ui.write(_('commit: %s\n') % t.strip())
3776
3777
3777 # all ancestors of branch heads - all ancestors of parent = new csets
3778 # all ancestors of branch heads - all ancestors of parent = new csets
3778 new = [0] * len(repo)
3779 new = [0] * len(repo)
3779 cl = repo.changelog
3780 cl = repo.changelog
3780 for a in [cl.rev(n) for n in bheads]:
3781 for a in [cl.rev(n) for n in bheads]:
3781 new[a] = 1
3782 new[a] = 1
3782 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3783 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3783 new[a] = 1
3784 new[a] = 1
3784 for a in [p.rev() for p in parents]:
3785 for a in [p.rev() for p in parents]:
3785 if a >= 0:
3786 if a >= 0:
3786 new[a] = 0
3787 new[a] = 0
3787 for a in cl.ancestors(*[p.rev() for p in parents]):
3788 for a in cl.ancestors(*[p.rev() for p in parents]):
3788 new[a] = 0
3789 new[a] = 0
3789 new = sum(new)
3790 new = sum(new)
3790
3791
3791 if new == 0:
3792 if new == 0:
3792 ui.status(_('update: (current)\n'))
3793 ui.status(_('update: (current)\n'))
3793 elif pnode not in bheads:
3794 elif pnode not in bheads:
3794 ui.write(_('update: %d new changesets (update)\n') % new)
3795 ui.write(_('update: %d new changesets (update)\n') % new)
3795 else:
3796 else:
3796 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3797 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3797 (new, len(bheads)))
3798 (new, len(bheads)))
3798
3799
3799 if opts.get('remote'):
3800 if opts.get('remote'):
3800 t = []
3801 t = []
3801 source, branches = hg.parseurl(ui.expandpath('default'))
3802 source, branches = hg.parseurl(ui.expandpath('default'))
3802 other = hg.repository(hg.remoteui(repo, {}), source)
3803 other = hg.repository(hg.remoteui(repo, {}), source)
3803 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3804 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3804 ui.debug('comparing with %s\n' % url.hidepassword(source))
3805 ui.debug('comparing with %s\n' % url.hidepassword(source))
3805 repo.ui.pushbuffer()
3806 repo.ui.pushbuffer()
3806 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3807 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3807 repo.ui.popbuffer()
3808 repo.ui.popbuffer()
3808 if incoming:
3809 if incoming:
3809 t.append(_('1 or more incoming'))
3810 t.append(_('1 or more incoming'))
3810
3811
3811 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3812 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3812 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3813 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3813 other = hg.repository(hg.remoteui(repo, {}), dest)
3814 other = hg.repository(hg.remoteui(repo, {}), dest)
3814 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3815 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3815 repo.ui.pushbuffer()
3816 repo.ui.pushbuffer()
3816 o = discovery.findoutgoing(repo, other)
3817 o = discovery.findoutgoing(repo, other)
3817 repo.ui.popbuffer()
3818 repo.ui.popbuffer()
3818 o = repo.changelog.nodesbetween(o, None)[0]
3819 o = repo.changelog.nodesbetween(o, None)[0]
3819 if o:
3820 if o:
3820 t.append(_('%d outgoing') % len(o))
3821 t.append(_('%d outgoing') % len(o))
3821
3822
3822 if t:
3823 if t:
3823 ui.write(_('remote: %s\n') % (', '.join(t)))
3824 ui.write(_('remote: %s\n') % (', '.join(t)))
3824 else:
3825 else:
3825 ui.status(_('remote: (synced)\n'))
3826 ui.status(_('remote: (synced)\n'))
3826
3827
3827 def tag(ui, repo, name1, *names, **opts):
3828 def tag(ui, repo, name1, *names, **opts):
3828 """add one or more tags for the current or given revision
3829 """add one or more tags for the current or given revision
3829
3830
3830 Name a particular revision using <name>.
3831 Name a particular revision using <name>.
3831
3832
3832 Tags are used to name particular revisions of the repository and are
3833 Tags are used to name particular revisions of the repository and are
3833 very useful to compare different revisions, to go back to significant
3834 very useful to compare different revisions, to go back to significant
3834 earlier versions or to mark branch points as releases, etc. Changing
3835 earlier versions or to mark branch points as releases, etc. Changing
3835 an existing tag is normally disallowed; use -f/--force to override.
3836 an existing tag is normally disallowed; use -f/--force to override.
3836
3837
3837 If no revision is given, the parent of the working directory is
3838 If no revision is given, the parent of the working directory is
3838 used, or tip if no revision is checked out.
3839 used, or tip if no revision is checked out.
3839
3840
3840 To facilitate version control, distribution, and merging of tags,
3841 To facilitate version control, distribution, and merging of tags,
3841 they are stored as a file named ".hgtags" which is managed similarly
3842 they are stored as a file named ".hgtags" which is managed similarly
3842 to other project files and can be hand-edited if necessary. This
3843 to other project files and can be hand-edited if necessary. This
3843 also means that tagging creates a new commit. The file
3844 also means that tagging creates a new commit. The file
3844 ".hg/localtags" is used for local tags (not shared among
3845 ".hg/localtags" is used for local tags (not shared among
3845 repositories).
3846 repositories).
3846
3847
3847 Tag commits are usually made at the head of a branch. If the parent
3848 Tag commits are usually made at the head of a branch. If the parent
3848 of the working directory is not a branch head, :hg:`tag` aborts; use
3849 of the working directory is not a branch head, :hg:`tag` aborts; use
3849 -f/--force to force the tag commit to be based on a non-head
3850 -f/--force to force the tag commit to be based on a non-head
3850 changeset.
3851 changeset.
3851
3852
3852 See :hg:`help dates` for a list of formats valid for -d/--date.
3853 See :hg:`help dates` for a list of formats valid for -d/--date.
3853
3854
3854 Since tag names have priority over branch names during revision
3855 Since tag names have priority over branch names during revision
3855 lookup, using an existing branch name as a tag name is discouraged.
3856 lookup, using an existing branch name as a tag name is discouraged.
3856
3857
3857 Returns 0 on success.
3858 Returns 0 on success.
3858 """
3859 """
3859
3860
3860 rev_ = "."
3861 rev_ = "."
3861 names = [t.strip() for t in (name1,) + names]
3862 names = [t.strip() for t in (name1,) + names]
3862 if len(names) != len(set(names)):
3863 if len(names) != len(set(names)):
3863 raise util.Abort(_('tag names must be unique'))
3864 raise util.Abort(_('tag names must be unique'))
3864 for n in names:
3865 for n in names:
3865 if n in ['tip', '.', 'null']:
3866 if n in ['tip', '.', 'null']:
3866 raise util.Abort(_('the name \'%s\' is reserved') % n)
3867 raise util.Abort(_('the name \'%s\' is reserved') % n)
3867 if not n:
3868 if not n:
3868 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3869 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3869 if opts.get('rev') and opts.get('remove'):
3870 if opts.get('rev') and opts.get('remove'):
3870 raise util.Abort(_("--rev and --remove are incompatible"))
3871 raise util.Abort(_("--rev and --remove are incompatible"))
3871 if opts.get('rev'):
3872 if opts.get('rev'):
3872 rev_ = opts['rev']
3873 rev_ = opts['rev']
3873 message = opts.get('message')
3874 message = opts.get('message')
3874 if opts.get('remove'):
3875 if opts.get('remove'):
3875 expectedtype = opts.get('local') and 'local' or 'global'
3876 expectedtype = opts.get('local') and 'local' or 'global'
3876 for n in names:
3877 for n in names:
3877 if not repo.tagtype(n):
3878 if not repo.tagtype(n):
3878 raise util.Abort(_('tag \'%s\' does not exist') % n)
3879 raise util.Abort(_('tag \'%s\' does not exist') % n)
3879 if repo.tagtype(n) != expectedtype:
3880 if repo.tagtype(n) != expectedtype:
3880 if expectedtype == 'global':
3881 if expectedtype == 'global':
3881 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3882 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3882 else:
3883 else:
3883 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3884 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3884 rev_ = nullid
3885 rev_ = nullid
3885 if not message:
3886 if not message:
3886 # we don't translate commit messages
3887 # we don't translate commit messages
3887 message = 'Removed tag %s' % ', '.join(names)
3888 message = 'Removed tag %s' % ', '.join(names)
3888 elif not opts.get('force'):
3889 elif not opts.get('force'):
3889 for n in names:
3890 for n in names:
3890 if n in repo.tags():
3891 if n in repo.tags():
3891 raise util.Abort(_('tag \'%s\' already exists '
3892 raise util.Abort(_('tag \'%s\' already exists '
3892 '(use -f to force)') % n)
3893 '(use -f to force)') % n)
3893 if not opts.get('local'):
3894 if not opts.get('local'):
3894 p1, p2 = repo.dirstate.parents()
3895 p1, p2 = repo.dirstate.parents()
3895 if p2 != nullid:
3896 if p2 != nullid:
3896 raise util.Abort(_('uncommitted merge'))
3897 raise util.Abort(_('uncommitted merge'))
3897 bheads = repo.branchheads()
3898 bheads = repo.branchheads()
3898 if not opts.get('force') and bheads and p1 not in bheads:
3899 if not opts.get('force') and bheads and p1 not in bheads:
3899 raise util.Abort(_('not at a branch head (use -f to force)'))
3900 raise util.Abort(_('not at a branch head (use -f to force)'))
3900 r = cmdutil.revsingle(repo, rev_).node()
3901 r = cmdutil.revsingle(repo, rev_).node()
3901
3902
3902 if not message:
3903 if not message:
3903 # we don't translate commit messages
3904 # we don't translate commit messages
3904 message = ('Added tag %s for changeset %s' %
3905 message = ('Added tag %s for changeset %s' %
3905 (', '.join(names), short(r)))
3906 (', '.join(names), short(r)))
3906
3907
3907 date = opts.get('date')
3908 date = opts.get('date')
3908 if date:
3909 if date:
3909 date = util.parsedate(date)
3910 date = util.parsedate(date)
3910
3911
3911 if opts.get('edit'):
3912 if opts.get('edit'):
3912 message = ui.edit(message, ui.username())
3913 message = ui.edit(message, ui.username())
3913
3914
3914 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3915 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3915
3916
3916 def tags(ui, repo):
3917 def tags(ui, repo):
3917 """list repository tags
3918 """list repository tags
3918
3919
3919 This lists both regular and local tags. When the -v/--verbose
3920 This lists both regular and local tags. When the -v/--verbose
3920 switch is used, a third column "local" is printed for local tags.
3921 switch is used, a third column "local" is printed for local tags.
3921
3922
3922 Returns 0 on success.
3923 Returns 0 on success.
3923 """
3924 """
3924
3925
3925 hexfunc = ui.debugflag and hex or short
3926 hexfunc = ui.debugflag and hex or short
3926 tagtype = ""
3927 tagtype = ""
3927
3928
3928 for t, n in reversed(repo.tagslist()):
3929 for t, n in reversed(repo.tagslist()):
3929 if ui.quiet:
3930 if ui.quiet:
3930 ui.write("%s\n" % t)
3931 ui.write("%s\n" % t)
3931 continue
3932 continue
3932
3933
3933 try:
3934 try:
3934 hn = hexfunc(n)
3935 hn = hexfunc(n)
3935 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3936 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3936 except error.LookupError:
3937 except error.LookupError:
3937 r = " ?:%s" % hn
3938 r = " ?:%s" % hn
3938 else:
3939 else:
3939 spaces = " " * (30 - encoding.colwidth(t))
3940 spaces = " " * (30 - encoding.colwidth(t))
3940 if ui.verbose:
3941 if ui.verbose:
3941 if repo.tagtype(t) == 'local':
3942 if repo.tagtype(t) == 'local':
3942 tagtype = " local"
3943 tagtype = " local"
3943 else:
3944 else:
3944 tagtype = ""
3945 tagtype = ""
3945 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3946 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3946
3947
3947 def tip(ui, repo, **opts):
3948 def tip(ui, repo, **opts):
3948 """show the tip revision
3949 """show the tip revision
3949
3950
3950 The tip revision (usually just called the tip) is the changeset
3951 The tip revision (usually just called the tip) is the changeset
3951 most recently added to the repository (and therefore the most
3952 most recently added to the repository (and therefore the most
3952 recently changed head).
3953 recently changed head).
3953
3954
3954 If you have just made a commit, that commit will be the tip. If
3955 If you have just made a commit, that commit will be the tip. If
3955 you have just pulled changes from another repository, the tip of
3956 you have just pulled changes from another repository, the tip of
3956 that repository becomes the current tip. The "tip" tag is special
3957 that repository becomes the current tip. The "tip" tag is special
3957 and cannot be renamed or assigned to a different changeset.
3958 and cannot be renamed or assigned to a different changeset.
3958
3959
3959 Returns 0 on success.
3960 Returns 0 on success.
3960 """
3961 """
3961 displayer = cmdutil.show_changeset(ui, repo, opts)
3962 displayer = cmdutil.show_changeset(ui, repo, opts)
3962 displayer.show(repo[len(repo) - 1])
3963 displayer.show(repo[len(repo) - 1])
3963 displayer.close()
3964 displayer.close()
3964
3965
3965 def unbundle(ui, repo, fname1, *fnames, **opts):
3966 def unbundle(ui, repo, fname1, *fnames, **opts):
3966 """apply one or more changegroup files
3967 """apply one or more changegroup files
3967
3968
3968 Apply one or more compressed changegroup files generated by the
3969 Apply one or more compressed changegroup files generated by the
3969 bundle command.
3970 bundle command.
3970
3971
3971 Returns 0 on success, 1 if an update has unresolved files.
3972 Returns 0 on success, 1 if an update has unresolved files.
3972 """
3973 """
3973 fnames = (fname1,) + fnames
3974 fnames = (fname1,) + fnames
3974
3975
3975 lock = repo.lock()
3976 lock = repo.lock()
3976 try:
3977 try:
3977 for fname in fnames:
3978 for fname in fnames:
3978 f = url.open(ui, fname)
3979 f = url.open(ui, fname)
3979 gen = changegroup.readbundle(f, fname)
3980 gen = changegroup.readbundle(f, fname)
3980 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3981 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3981 lock=lock)
3982 lock=lock)
3982 finally:
3983 finally:
3983 lock.release()
3984 lock.release()
3984
3985
3985 return postincoming(ui, repo, modheads, opts.get('update'), None)
3986 return postincoming(ui, repo, modheads, opts.get('update'), None)
3986
3987
3987 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3988 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3988 """update working directory (or switch revisions)
3989 """update working directory (or switch revisions)
3989
3990
3990 Update the repository's working directory to the specified
3991 Update the repository's working directory to the specified
3991 changeset. If no changeset is specified, update to the tip of the
3992 changeset. If no changeset is specified, update to the tip of the
3992 current named branch.
3993 current named branch.
3993
3994
3994 If the changeset is not a descendant of the working directory's
3995 If the changeset is not a descendant of the working directory's
3995 parent, the update is aborted. With the -c/--check option, the
3996 parent, the update is aborted. With the -c/--check option, the
3996 working directory is checked for uncommitted changes; if none are
3997 working directory is checked for uncommitted changes; if none are
3997 found, the working directory is updated to the specified
3998 found, the working directory is updated to the specified
3998 changeset.
3999 changeset.
3999
4000
4000 The following rules apply when the working directory contains
4001 The following rules apply when the working directory contains
4001 uncommitted changes:
4002 uncommitted changes:
4002
4003
4003 1. If neither -c/--check nor -C/--clean is specified, and if
4004 1. If neither -c/--check nor -C/--clean is specified, and if
4004 the requested changeset is an ancestor or descendant of
4005 the requested changeset is an ancestor or descendant of
4005 the working directory's parent, the uncommitted changes
4006 the working directory's parent, the uncommitted changes
4006 are merged into the requested changeset and the merged
4007 are merged into the requested changeset and the merged
4007 result is left uncommitted. If the requested changeset is
4008 result is left uncommitted. If the requested changeset is
4008 not an ancestor or descendant (that is, it is on another
4009 not an ancestor or descendant (that is, it is on another
4009 branch), the update is aborted and the uncommitted changes
4010 branch), the update is aborted and the uncommitted changes
4010 are preserved.
4011 are preserved.
4011
4012
4012 2. With the -c/--check option, the update is aborted and the
4013 2. With the -c/--check option, the update is aborted and the
4013 uncommitted changes are preserved.
4014 uncommitted changes are preserved.
4014
4015
4015 3. With the -C/--clean option, uncommitted changes are discarded and
4016 3. With the -C/--clean option, uncommitted changes are discarded and
4016 the working directory is updated to the requested changeset.
4017 the working directory is updated to the requested changeset.
4017
4018
4018 Use null as the changeset to remove the working directory (like
4019 Use null as the changeset to remove the working directory (like
4019 :hg:`clone -U`).
4020 :hg:`clone -U`).
4020
4021
4021 If you want to update just one file to an older changeset, use
4022 If you want to update just one file to an older changeset, use
4022 :hg:`revert`.
4023 :hg:`revert`.
4023
4024
4024 See :hg:`help dates` for a list of formats valid for -d/--date.
4025 See :hg:`help dates` for a list of formats valid for -d/--date.
4025
4026
4026 Returns 0 on success, 1 if there are unresolved files.
4027 Returns 0 on success, 1 if there are unresolved files.
4027 """
4028 """
4028 if rev and node:
4029 if rev and node:
4029 raise util.Abort(_("please specify just one revision"))
4030 raise util.Abort(_("please specify just one revision"))
4030
4031
4031 if not rev:
4032 if not rev:
4032 rev = node
4033 rev = node
4033
4034
4034 rev = cmdutil.revsingle(repo, rev, rev).rev()
4035 rev = cmdutil.revsingle(repo, rev, rev).rev()
4035
4036
4036 if check and clean:
4037 if check and clean:
4037 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4038 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4038
4039
4039 if check:
4040 if check:
4040 # we could use dirty() but we can ignore merge and branch trivia
4041 # we could use dirty() but we can ignore merge and branch trivia
4041 c = repo[None]
4042 c = repo[None]
4042 if c.modified() or c.added() or c.removed():
4043 if c.modified() or c.added() or c.removed():
4043 raise util.Abort(_("uncommitted local changes"))
4044 raise util.Abort(_("uncommitted local changes"))
4044
4045
4045 if date:
4046 if date:
4046 if rev:
4047 if rev:
4047 raise util.Abort(_("you can't specify a revision and a date"))
4048 raise util.Abort(_("you can't specify a revision and a date"))
4048 rev = cmdutil.finddate(ui, repo, date)
4049 rev = cmdutil.finddate(ui, repo, date)
4049
4050
4050 if clean or check:
4051 if clean or check:
4051 ret = hg.clean(repo, rev)
4052 ret = hg.clean(repo, rev)
4052 else:
4053 else:
4053 ret = hg.update(repo, rev)
4054 ret = hg.update(repo, rev)
4054
4055
4055 if repo.ui.configbool('bookmarks', 'track.current'):
4056 if repo.ui.configbool('bookmarks', 'track.current'):
4056 bookmarks.setcurrent(repo, rev)
4057 bookmarks.setcurrent(repo, rev)
4057
4058
4058 return ret
4059 return ret
4059
4060
4060 def verify(ui, repo):
4061 def verify(ui, repo):
4061 """verify the integrity of the repository
4062 """verify the integrity of the repository
4062
4063
4063 Verify the integrity of the current repository.
4064 Verify the integrity of the current repository.
4064
4065
4065 This will perform an extensive check of the repository's
4066 This will perform an extensive check of the repository's
4066 integrity, validating the hashes and checksums of each entry in
4067 integrity, validating the hashes and checksums of each entry in
4067 the changelog, manifest, and tracked files, as well as the
4068 the changelog, manifest, and tracked files, as well as the
4068 integrity of their crosslinks and indices.
4069 integrity of their crosslinks and indices.
4069
4070
4070 Returns 0 on success, 1 if errors are encountered.
4071 Returns 0 on success, 1 if errors are encountered.
4071 """
4072 """
4072 return hg.verify(repo)
4073 return hg.verify(repo)
4073
4074
4074 def version_(ui):
4075 def version_(ui):
4075 """output version and copyright information"""
4076 """output version and copyright information"""
4076 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4077 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4077 % util.version())
4078 % util.version())
4078 ui.status(_(
4079 ui.status(_(
4079 "(see http://mercurial.selenic.com for more information)\n"
4080 "(see http://mercurial.selenic.com for more information)\n"
4080 "\nCopyright (C) 2005-2010 Matt Mackall and others\n"
4081 "\nCopyright (C) 2005-2010 Matt Mackall and others\n"
4081 "This is free software; see the source for copying conditions. "
4082 "This is free software; see the source for copying conditions. "
4082 "There is NO\nwarranty; "
4083 "There is NO\nwarranty; "
4083 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4084 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4084 ))
4085 ))
4085
4086
4086 # Command options and aliases are listed here, alphabetically
4087 # Command options and aliases are listed here, alphabetically
4087
4088
4088 globalopts = [
4089 globalopts = [
4089 ('R', 'repository', '',
4090 ('R', 'repository', '',
4090 _('repository root directory or name of overlay bundle file'),
4091 _('repository root directory or name of overlay bundle file'),
4091 _('REPO')),
4092 _('REPO')),
4092 ('', 'cwd', '',
4093 ('', 'cwd', '',
4093 _('change working directory'), _('DIR')),
4094 _('change working directory'), _('DIR')),
4094 ('y', 'noninteractive', None,
4095 ('y', 'noninteractive', None,
4095 _('do not prompt, assume \'yes\' for any required answers')),
4096 _('do not prompt, assume \'yes\' for any required answers')),
4096 ('q', 'quiet', None, _('suppress output')),
4097 ('q', 'quiet', None, _('suppress output')),
4097 ('v', 'verbose', None, _('enable additional output')),
4098 ('v', 'verbose', None, _('enable additional output')),
4098 ('', 'config', [],
4099 ('', 'config', [],
4099 _('set/override config option (use \'section.name=value\')'),
4100 _('set/override config option (use \'section.name=value\')'),
4100 _('CONFIG')),
4101 _('CONFIG')),
4101 ('', 'debug', None, _('enable debugging output')),
4102 ('', 'debug', None, _('enable debugging output')),
4102 ('', 'debugger', None, _('start debugger')),
4103 ('', 'debugger', None, _('start debugger')),
4103 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4104 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4104 _('ENCODE')),
4105 _('ENCODE')),
4105 ('', 'encodingmode', encoding.encodingmode,
4106 ('', 'encodingmode', encoding.encodingmode,
4106 _('set the charset encoding mode'), _('MODE')),
4107 _('set the charset encoding mode'), _('MODE')),
4107 ('', 'traceback', None, _('always print a traceback on exception')),
4108 ('', 'traceback', None, _('always print a traceback on exception')),
4108 ('', 'time', None, _('time how long the command takes')),
4109 ('', 'time', None, _('time how long the command takes')),
4109 ('', 'profile', None, _('print command execution profile')),
4110 ('', 'profile', None, _('print command execution profile')),
4110 ('', 'version', None, _('output version information and exit')),
4111 ('', 'version', None, _('output version information and exit')),
4111 ('h', 'help', None, _('display help and exit')),
4112 ('h', 'help', None, _('display help and exit')),
4112 ]
4113 ]
4113
4114
4114 dryrunopts = [('n', 'dry-run', None,
4115 dryrunopts = [('n', 'dry-run', None,
4115 _('do not perform actions, just print output'))]
4116 _('do not perform actions, just print output'))]
4116
4117
4117 remoteopts = [
4118 remoteopts = [
4118 ('e', 'ssh', '',
4119 ('e', 'ssh', '',
4119 _('specify ssh command to use'), _('CMD')),
4120 _('specify ssh command to use'), _('CMD')),
4120 ('', 'remotecmd', '',
4121 ('', 'remotecmd', '',
4121 _('specify hg command to run on the remote side'), _('CMD')),
4122 _('specify hg command to run on the remote side'), _('CMD')),
4122 ('', 'insecure', None,
4123 ('', 'insecure', None,
4123 _('do not verify server certificate (ignoring web.cacerts config)')),
4124 _('do not verify server certificate (ignoring web.cacerts config)')),
4124 ]
4125 ]
4125
4126
4126 walkopts = [
4127 walkopts = [
4127 ('I', 'include', [],
4128 ('I', 'include', [],
4128 _('include names matching the given patterns'), _('PATTERN')),
4129 _('include names matching the given patterns'), _('PATTERN')),
4129 ('X', 'exclude', [],
4130 ('X', 'exclude', [],
4130 _('exclude names matching the given patterns'), _('PATTERN')),
4131 _('exclude names matching the given patterns'), _('PATTERN')),
4131 ]
4132 ]
4132
4133
4133 commitopts = [
4134 commitopts = [
4134 ('m', 'message', '',
4135 ('m', 'message', '',
4135 _('use text as commit message'), _('TEXT')),
4136 _('use text as commit message'), _('TEXT')),
4136 ('l', 'logfile', '',
4137 ('l', 'logfile', '',
4137 _('read commit message from file'), _('FILE')),
4138 _('read commit message from file'), _('FILE')),
4138 ]
4139 ]
4139
4140
4140 commitopts2 = [
4141 commitopts2 = [
4141 ('d', 'date', '',
4142 ('d', 'date', '',
4142 _('record datecode as commit date'), _('DATE')),
4143 _('record datecode as commit date'), _('DATE')),
4143 ('u', 'user', '',
4144 ('u', 'user', '',
4144 _('record the specified user as committer'), _('USER')),
4145 _('record the specified user as committer'), _('USER')),
4145 ]
4146 ]
4146
4147
4147 templateopts = [
4148 templateopts = [
4148 ('', 'style', '',
4149 ('', 'style', '',
4149 _('display using template map file'), _('STYLE')),
4150 _('display using template map file'), _('STYLE')),
4150 ('', 'template', '',
4151 ('', 'template', '',
4151 _('display with template'), _('TEMPLATE')),
4152 _('display with template'), _('TEMPLATE')),
4152 ]
4153 ]
4153
4154
4154 logopts = [
4155 logopts = [
4155 ('p', 'patch', None, _('show patch')),
4156 ('p', 'patch', None, _('show patch')),
4156 ('g', 'git', None, _('use git extended diff format')),
4157 ('g', 'git', None, _('use git extended diff format')),
4157 ('l', 'limit', '',
4158 ('l', 'limit', '',
4158 _('limit number of changes displayed'), _('NUM')),
4159 _('limit number of changes displayed'), _('NUM')),
4159 ('M', 'no-merges', None, _('do not show merges')),
4160 ('M', 'no-merges', None, _('do not show merges')),
4160 ('', 'stat', None, _('output diffstat-style summary of changes')),
4161 ('', 'stat', None, _('output diffstat-style summary of changes')),
4161 ] + templateopts
4162 ] + templateopts
4162
4163
4163 diffopts = [
4164 diffopts = [
4164 ('a', 'text', None, _('treat all files as text')),
4165 ('a', 'text', None, _('treat all files as text')),
4165 ('g', 'git', None, _('use git extended diff format')),
4166 ('g', 'git', None, _('use git extended diff format')),
4166 ('', 'nodates', None, _('omit dates from diff headers'))
4167 ('', 'nodates', None, _('omit dates from diff headers'))
4167 ]
4168 ]
4168
4169
4169 diffopts2 = [
4170 diffopts2 = [
4170 ('p', 'show-function', None, _('show which function each change is in')),
4171 ('p', 'show-function', None, _('show which function each change is in')),
4171 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4172 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4172 ('w', 'ignore-all-space', None,
4173 ('w', 'ignore-all-space', None,
4173 _('ignore white space when comparing lines')),
4174 _('ignore white space when comparing lines')),
4174 ('b', 'ignore-space-change', None,
4175 ('b', 'ignore-space-change', None,
4175 _('ignore changes in the amount of white space')),
4176 _('ignore changes in the amount of white space')),
4176 ('B', 'ignore-blank-lines', None,
4177 ('B', 'ignore-blank-lines', None,
4177 _('ignore changes whose lines are all blank')),
4178 _('ignore changes whose lines are all blank')),
4178 ('U', 'unified', '',
4179 ('U', 'unified', '',
4179 _('number of lines of context to show'), _('NUM')),
4180 _('number of lines of context to show'), _('NUM')),
4180 ('', 'stat', None, _('output diffstat-style summary of changes')),
4181 ('', 'stat', None, _('output diffstat-style summary of changes')),
4181 ]
4182 ]
4182
4183
4183 similarityopts = [
4184 similarityopts = [
4184 ('s', 'similarity', '',
4185 ('s', 'similarity', '',
4185 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4186 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4186 ]
4187 ]
4187
4188
4188 subrepoopts = [
4189 subrepoopts = [
4189 ('S', 'subrepos', None,
4190 ('S', 'subrepos', None,
4190 _('recurse into subrepositories'))
4191 _('recurse into subrepositories'))
4191 ]
4192 ]
4192
4193
4193 table = {
4194 table = {
4194 "^add": (add, walkopts + subrepoopts + dryrunopts,
4195 "^add": (add, walkopts + subrepoopts + dryrunopts,
4195 _('[OPTION]... [FILE]...')),
4196 _('[OPTION]... [FILE]...')),
4196 "addremove":
4197 "addremove":
4197 (addremove, similarityopts + walkopts + dryrunopts,
4198 (addremove, similarityopts + walkopts + dryrunopts,
4198 _('[OPTION]... [FILE]...')),
4199 _('[OPTION]... [FILE]...')),
4199 "^annotate|blame":
4200 "^annotate|blame":
4200 (annotate,
4201 (annotate,
4201 [('r', 'rev', '',
4202 [('r', 'rev', '',
4202 _('annotate the specified revision'), _('REV')),
4203 _('annotate the specified revision'), _('REV')),
4203 ('', 'follow', None,
4204 ('', 'follow', None,
4204 _('follow copies/renames and list the filename (DEPRECATED)')),
4205 _('follow copies/renames and list the filename (DEPRECATED)')),
4205 ('', 'no-follow', None, _("don't follow copies and renames")),
4206 ('', 'no-follow', None, _("don't follow copies and renames")),
4206 ('a', 'text', None, _('treat all files as text')),
4207 ('a', 'text', None, _('treat all files as text')),
4207 ('u', 'user', None, _('list the author (long with -v)')),
4208 ('u', 'user', None, _('list the author (long with -v)')),
4208 ('f', 'file', None, _('list the filename')),
4209 ('f', 'file', None, _('list the filename')),
4209 ('d', 'date', None, _('list the date (short with -q)')),
4210 ('d', 'date', None, _('list the date (short with -q)')),
4210 ('n', 'number', None, _('list the revision number (default)')),
4211 ('n', 'number', None, _('list the revision number (default)')),
4211 ('c', 'changeset', None, _('list the changeset')),
4212 ('c', 'changeset', None, _('list the changeset')),
4212 ('l', 'line-number', None,
4213 ('l', 'line-number', None,
4213 _('show line number at the first appearance'))
4214 _('show line number at the first appearance'))
4214 ] + walkopts,
4215 ] + walkopts,
4215 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4216 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4216 "archive":
4217 "archive":
4217 (archive,
4218 (archive,
4218 [('', 'no-decode', None, _('do not pass files through decoders')),
4219 [('', 'no-decode', None, _('do not pass files through decoders')),
4219 ('p', 'prefix', '',
4220 ('p', 'prefix', '',
4220 _('directory prefix for files in archive'), _('PREFIX')),
4221 _('directory prefix for files in archive'), _('PREFIX')),
4221 ('r', 'rev', '',
4222 ('r', 'rev', '',
4222 _('revision to distribute'), _('REV')),
4223 _('revision to distribute'), _('REV')),
4223 ('t', 'type', '',
4224 ('t', 'type', '',
4224 _('type of distribution to create'), _('TYPE')),
4225 _('type of distribution to create'), _('TYPE')),
4225 ] + subrepoopts + walkopts,
4226 ] + subrepoopts + walkopts,
4226 _('[OPTION]... DEST')),
4227 _('[OPTION]... DEST')),
4227 "backout":
4228 "backout":
4228 (backout,
4229 (backout,
4229 [('', 'merge', None,
4230 [('', 'merge', None,
4230 _('merge with old dirstate parent after backout')),
4231 _('merge with old dirstate parent after backout')),
4231 ('', 'parent', '',
4232 ('', 'parent', '',
4232 _('parent to choose when backing out merge'), _('REV')),
4233 _('parent to choose when backing out merge'), _('REV')),
4233 ('t', 'tool', '',
4234 ('t', 'tool', '',
4234 _('specify merge tool')),
4235 _('specify merge tool')),
4235 ('r', 'rev', '',
4236 ('r', 'rev', '',
4236 _('revision to backout'), _('REV')),
4237 _('revision to backout'), _('REV')),
4237 ] + walkopts + commitopts + commitopts2,
4238 ] + walkopts + commitopts + commitopts2,
4238 _('[OPTION]... [-r] REV')),
4239 _('[OPTION]... [-r] REV')),
4239 "bisect":
4240 "bisect":
4240 (bisect,
4241 (bisect,
4241 [('r', 'reset', False, _('reset bisect state')),
4242 [('r', 'reset', False, _('reset bisect state')),
4242 ('g', 'good', False, _('mark changeset good')),
4243 ('g', 'good', False, _('mark changeset good')),
4243 ('b', 'bad', False, _('mark changeset bad')),
4244 ('b', 'bad', False, _('mark changeset bad')),
4244 ('s', 'skip', False, _('skip testing changeset')),
4245 ('s', 'skip', False, _('skip testing changeset')),
4245 ('c', 'command', '',
4246 ('c', 'command', '',
4246 _('use command to check changeset state'), _('CMD')),
4247 _('use command to check changeset state'), _('CMD')),
4247 ('U', 'noupdate', False, _('do not update to target'))],
4248 ('U', 'noupdate', False, _('do not update to target'))],
4248 _("[-gbsr] [-U] [-c CMD] [REV]")),
4249 _("[-gbsr] [-U] [-c CMD] [REV]")),
4249 "bookmarks":
4250 "bookmarks":
4250 (bookmark,
4251 (bookmark,
4251 [('f', 'force', False, _('force')),
4252 [('f', 'force', False, _('force')),
4252 ('r', 'rev', '', _('revision'), _('REV')),
4253 ('r', 'rev', '', _('revision'), _('REV')),
4253 ('d', 'delete', False, _('delete a given bookmark')),
4254 ('d', 'delete', False, _('delete a given bookmark')),
4254 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4255 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4255 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4256 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4256 "branch":
4257 "branch":
4257 (branch,
4258 (branch,
4258 [('f', 'force', None,
4259 [('f', 'force', None,
4259 _('set branch name even if it shadows an existing branch')),
4260 _('set branch name even if it shadows an existing branch')),
4260 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4261 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4261 _('[-fC] [NAME]')),
4262 _('[-fC] [NAME]')),
4262 "branches":
4263 "branches":
4263 (branches,
4264 (branches,
4264 [('a', 'active', False,
4265 [('a', 'active', False,
4265 _('show only branches that have unmerged heads')),
4266 _('show only branches that have unmerged heads')),
4266 ('c', 'closed', False,
4267 ('c', 'closed', False,
4267 _('show normal and closed branches'))],
4268 _('show normal and closed branches'))],
4268 _('[-ac]')),
4269 _('[-ac]')),
4269 "bundle":
4270 "bundle":
4270 (bundle,
4271 (bundle,
4271 [('f', 'force', None,
4272 [('f', 'force', None,
4272 _('run even when the destination is unrelated')),
4273 _('run even when the destination is unrelated')),
4273 ('r', 'rev', [],
4274 ('r', 'rev', [],
4274 _('a changeset intended to be added to the destination'),
4275 _('a changeset intended to be added to the destination'),
4275 _('REV')),
4276 _('REV')),
4276 ('b', 'branch', [],
4277 ('b', 'branch', [],
4277 _('a specific branch you would like to bundle'),
4278 _('a specific branch you would like to bundle'),
4278 _('BRANCH')),
4279 _('BRANCH')),
4279 ('', 'base', [],
4280 ('', 'base', [],
4280 _('a base changeset assumed to be available at the destination'),
4281 _('a base changeset assumed to be available at the destination'),
4281 _('REV')),
4282 _('REV')),
4282 ('a', 'all', None, _('bundle all changesets in the repository')),
4283 ('a', 'all', None, _('bundle all changesets in the repository')),
4283 ('t', 'type', 'bzip2',
4284 ('t', 'type', 'bzip2',
4284 _('bundle compression type to use'), _('TYPE')),
4285 _('bundle compression type to use'), _('TYPE')),
4285 ] + remoteopts,
4286 ] + remoteopts,
4286 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4287 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4287 "cat":
4288 "cat":
4288 (cat,
4289 (cat,
4289 [('o', 'output', '',
4290 [('o', 'output', '',
4290 _('print output to file with formatted name'), _('FORMAT')),
4291 _('print output to file with formatted name'), _('FORMAT')),
4291 ('r', 'rev', '',
4292 ('r', 'rev', '',
4292 _('print the given revision'), _('REV')),
4293 _('print the given revision'), _('REV')),
4293 ('', 'decode', None, _('apply any matching decode filter')),
4294 ('', 'decode', None, _('apply any matching decode filter')),
4294 ] + walkopts,
4295 ] + walkopts,
4295 _('[OPTION]... FILE...')),
4296 _('[OPTION]... FILE...')),
4296 "^clone":
4297 "^clone":
4297 (clone,
4298 (clone,
4298 [('U', 'noupdate', None,
4299 [('U', 'noupdate', None,
4299 _('the clone will include an empty working copy (only a repository)')),
4300 _('the clone will include an empty working copy (only a repository)')),
4300 ('u', 'updaterev', '',
4301 ('u', 'updaterev', '',
4301 _('revision, tag or branch to check out'), _('REV')),
4302 _('revision, tag or branch to check out'), _('REV')),
4302 ('r', 'rev', [],
4303 ('r', 'rev', [],
4303 _('include the specified changeset'), _('REV')),
4304 _('include the specified changeset'), _('REV')),
4304 ('b', 'branch', [],
4305 ('b', 'branch', [],
4305 _('clone only the specified branch'), _('BRANCH')),
4306 _('clone only the specified branch'), _('BRANCH')),
4306 ('', 'pull', None, _('use pull protocol to copy metadata')),
4307 ('', 'pull', None, _('use pull protocol to copy metadata')),
4307 ('', 'uncompressed', None,
4308 ('', 'uncompressed', None,
4308 _('use uncompressed transfer (fast over LAN)')),
4309 _('use uncompressed transfer (fast over LAN)')),
4309 ] + remoteopts,
4310 ] + remoteopts,
4310 _('[OPTION]... SOURCE [DEST]')),
4311 _('[OPTION]... SOURCE [DEST]')),
4311 "^commit|ci":
4312 "^commit|ci":
4312 (commit,
4313 (commit,
4313 [('A', 'addremove', None,
4314 [('A', 'addremove', None,
4314 _('mark new/missing files as added/removed before committing')),
4315 _('mark new/missing files as added/removed before committing')),
4315 ('', 'close-branch', None,
4316 ('', 'close-branch', None,
4316 _('mark a branch as closed, hiding it from the branch list')),
4317 _('mark a branch as closed, hiding it from the branch list')),
4317 ] + walkopts + commitopts + commitopts2,
4318 ] + walkopts + commitopts + commitopts2,
4318 _('[OPTION]... [FILE]...')),
4319 _('[OPTION]... [FILE]...')),
4319 "copy|cp":
4320 "copy|cp":
4320 (copy,
4321 (copy,
4321 [('A', 'after', None, _('record a copy that has already occurred')),
4322 [('A', 'after', None, _('record a copy that has already occurred')),
4322 ('f', 'force', None,
4323 ('f', 'force', None,
4323 _('forcibly copy over an existing managed file')),
4324 _('forcibly copy over an existing managed file')),
4324 ] + walkopts + dryrunopts,
4325 ] + walkopts + dryrunopts,
4325 _('[OPTION]... [SOURCE]... DEST')),
4326 _('[OPTION]... [SOURCE]... DEST')),
4326 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4327 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4327 "debugbuilddag":
4328 "debugbuilddag":
4328 (debugbuilddag,
4329 (debugbuilddag,
4329 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4330 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4330 ('a', 'appended-file', None, _('add single file all revs append to')),
4331 ('a', 'appended-file', None, _('add single file all revs append to')),
4331 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4332 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4332 ('n', 'new-file', None, _('add new file at each rev')),
4333 ('n', 'new-file', None, _('add new file at each rev')),
4333 ],
4334 ],
4334 _('[OPTION]... TEXT')),
4335 _('[OPTION]... TEXT')),
4335 "debugcheckstate": (debugcheckstate, [], ''),
4336 "debugcheckstate": (debugcheckstate, [], ''),
4336 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4337 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4337 "debugcomplete":
4338 "debugcomplete":
4338 (debugcomplete,
4339 (debugcomplete,
4339 [('o', 'options', None, _('show the command options'))],
4340 [('o', 'options', None, _('show the command options'))],
4340 _('[-o] CMD')),
4341 _('[-o] CMD')),
4341 "debugdag":
4342 "debugdag":
4342 (debugdag,
4343 (debugdag,
4343 [('t', 'tags', None, _('use tags as labels')),
4344 [('t', 'tags', None, _('use tags as labels')),
4344 ('b', 'branches', None, _('annotate with branch names')),
4345 ('b', 'branches', None, _('annotate with branch names')),
4345 ('', 'dots', None, _('use dots for runs')),
4346 ('', 'dots', None, _('use dots for runs')),
4346 ('s', 'spaces', None, _('separate elements by spaces')),
4347 ('s', 'spaces', None, _('separate elements by spaces')),
4347 ],
4348 ],
4348 _('[OPTION]... [FILE [REV]...]')),
4349 _('[OPTION]... [FILE [REV]...]')),
4349 "debugdate":
4350 "debugdate":
4350 (debugdate,
4351 (debugdate,
4351 [('e', 'extended', None, _('try extended date formats'))],
4352 [('e', 'extended', None, _('try extended date formats'))],
4352 _('[-e] DATE [RANGE]')),
4353 _('[-e] DATE [RANGE]')),
4353 "debugdata": (debugdata, [], _('FILE REV')),
4354 "debugdata": (debugdata, [], _('FILE REV')),
4354 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4355 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4355 "debugignore": (debugignore, [], ''),
4356 "debugignore": (debugignore, [], ''),
4356 "debugindex": (debugindex,
4357 "debugindex": (debugindex,
4357 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4358 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4358 _('FILE')),
4359 _('FILE')),
4359 "debugindexdot": (debugindexdot, [], _('FILE')),
4360 "debugindexdot": (debugindexdot, [], _('FILE')),
4360 "debuginstall": (debuginstall, [], ''),
4361 "debuginstall": (debuginstall, [], ''),
4361 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4362 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4362 "debugrebuildstate":
4363 "debugrebuildstate":
4363 (debugrebuildstate,
4364 (debugrebuildstate,
4364 [('r', 'rev', '',
4365 [('r', 'rev', '',
4365 _('revision to rebuild to'), _('REV'))],
4366 _('revision to rebuild to'), _('REV'))],
4366 _('[-r REV] [REV]')),
4367 _('[-r REV] [REV]')),
4367 "debugrename":
4368 "debugrename":
4368 (debugrename,
4369 (debugrename,
4369 [('r', 'rev', '',
4370 [('r', 'rev', '',
4370 _('revision to debug'), _('REV'))],
4371 _('revision to debug'), _('REV'))],
4371 _('[-r REV] FILE')),
4372 _('[-r REV] FILE')),
4372 "debugrevspec":
4373 "debugrevspec":
4373 (debugrevspec, [], ('REVSPEC')),
4374 (debugrevspec, [], ('REVSPEC')),
4374 "debugsetparents":
4375 "debugsetparents":
4375 (debugsetparents, [], _('REV1 [REV2]')),
4376 (debugsetparents, [], _('REV1 [REV2]')),
4376 "debugstate":
4377 "debugstate":
4377 (debugstate,
4378 (debugstate,
4378 [('', 'nodates', None, _('do not display the saved mtime'))],
4379 [('', 'nodates', None, _('do not display the saved mtime'))],
4379 _('[OPTION]...')),
4380 _('[OPTION]...')),
4380 "debugsub":
4381 "debugsub":
4381 (debugsub,
4382 (debugsub,
4382 [('r', 'rev', '',
4383 [('r', 'rev', '',
4383 _('revision to check'), _('REV'))],
4384 _('revision to check'), _('REV'))],
4384 _('[-r REV] [REV]')),
4385 _('[-r REV] [REV]')),
4385 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4386 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4386 "^diff":
4387 "^diff":
4387 (diff,
4388 (diff,
4388 [('r', 'rev', [],
4389 [('r', 'rev', [],
4389 _('revision'), _('REV')),
4390 _('revision'), _('REV')),
4390 ('c', 'change', '',
4391 ('c', 'change', '',
4391 _('change made by revision'), _('REV'))
4392 _('change made by revision'), _('REV'))
4392 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4393 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4393 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4394 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4394 "^export":
4395 "^export":
4395 (export,
4396 (export,
4396 [('o', 'output', '',
4397 [('o', 'output', '',
4397 _('print output to file with formatted name'), _('FORMAT')),
4398 _('print output to file with formatted name'), _('FORMAT')),
4398 ('', 'switch-parent', None, _('diff against the second parent')),
4399 ('', 'switch-parent', None, _('diff against the second parent')),
4399 ('r', 'rev', [],
4400 ('r', 'rev', [],
4400 _('revisions to export'), _('REV')),
4401 _('revisions to export'), _('REV')),
4401 ] + diffopts,
4402 ] + diffopts,
4402 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4403 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4403 "^forget":
4404 "^forget":
4404 (forget,
4405 (forget,
4405 [] + walkopts,
4406 [] + walkopts,
4406 _('[OPTION]... FILE...')),
4407 _('[OPTION]... FILE...')),
4407 "grep":
4408 "grep":
4408 (grep,
4409 (grep,
4409 [('0', 'print0', None, _('end fields with NUL')),
4410 [('0', 'print0', None, _('end fields with NUL')),
4410 ('', 'all', None, _('print all revisions that match')),
4411 ('', 'all', None, _('print all revisions that match')),
4411 ('f', 'follow', None,
4412 ('f', 'follow', None,
4412 _('follow changeset history,'
4413 _('follow changeset history,'
4413 ' or file history across copies and renames')),
4414 ' or file history across copies and renames')),
4414 ('i', 'ignore-case', None, _('ignore case when matching')),
4415 ('i', 'ignore-case', None, _('ignore case when matching')),
4415 ('l', 'files-with-matches', None,
4416 ('l', 'files-with-matches', None,
4416 _('print only filenames and revisions that match')),
4417 _('print only filenames and revisions that match')),
4417 ('n', 'line-number', None, _('print matching line numbers')),
4418 ('n', 'line-number', None, _('print matching line numbers')),
4418 ('r', 'rev', [],
4419 ('r', 'rev', [],
4419 _('only search files changed within revision range'), _('REV')),
4420 _('only search files changed within revision range'), _('REV')),
4420 ('u', 'user', None, _('list the author (long with -v)')),
4421 ('u', 'user', None, _('list the author (long with -v)')),
4421 ('d', 'date', None, _('list the date (short with -q)')),
4422 ('d', 'date', None, _('list the date (short with -q)')),
4422 ] + walkopts,
4423 ] + walkopts,
4423 _('[OPTION]... PATTERN [FILE]...')),
4424 _('[OPTION]... PATTERN [FILE]...')),
4424 "heads":
4425 "heads":
4425 (heads,
4426 (heads,
4426 [('r', 'rev', '',
4427 [('r', 'rev', '',
4427 _('show only heads which are descendants of STARTREV'),
4428 _('show only heads which are descendants of STARTREV'),
4428 _('STARTREV')),
4429 _('STARTREV')),
4429 ('t', 'topo', False, _('show topological heads only')),
4430 ('t', 'topo', False, _('show topological heads only')),
4430 ('a', 'active', False,
4431 ('a', 'active', False,
4431 _('show active branchheads only (DEPRECATED)')),
4432 _('show active branchheads only (DEPRECATED)')),
4432 ('c', 'closed', False,
4433 ('c', 'closed', False,
4433 _('show normal and closed branch heads')),
4434 _('show normal and closed branch heads')),
4434 ] + templateopts,
4435 ] + templateopts,
4435 _('[-ac] [-r STARTREV] [REV]...')),
4436 _('[-ac] [-r STARTREV] [REV]...')),
4436 "help": (help_, [], _('[TOPIC]')),
4437 "help": (help_, [], _('[TOPIC]')),
4437 "identify|id":
4438 "identify|id":
4438 (identify,
4439 (identify,
4439 [('r', 'rev', '',
4440 [('r', 'rev', '',
4440 _('identify the specified revision'), _('REV')),
4441 _('identify the specified revision'), _('REV')),
4441 ('n', 'num', None, _('show local revision number')),
4442 ('n', 'num', None, _('show local revision number')),
4442 ('i', 'id', None, _('show global revision id')),
4443 ('i', 'id', None, _('show global revision id')),
4443 ('b', 'branch', None, _('show branch')),
4444 ('b', 'branch', None, _('show branch')),
4444 ('t', 'tags', None, _('show tags'))],
4445 ('t', 'tags', None, _('show tags'))],
4445 _('[-nibt] [-r REV] [SOURCE]')),
4446 _('[-nibt] [-r REV] [SOURCE]')),
4446 "import|patch":
4447 "import|patch":
4447 (import_,
4448 (import_,
4448 [('p', 'strip', 1,
4449 [('p', 'strip', 1,
4449 _('directory strip option for patch. This has the same '
4450 _('directory strip option for patch. This has the same '
4450 'meaning as the corresponding patch option'),
4451 'meaning as the corresponding patch option'),
4451 _('NUM')),
4452 _('NUM')),
4452 ('b', 'base', '',
4453 ('b', 'base', '',
4453 _('base path'), _('PATH')),
4454 _('base path'), _('PATH')),
4454 ('f', 'force', None,
4455 ('f', 'force', None,
4455 _('skip check for outstanding uncommitted changes')),
4456 _('skip check for outstanding uncommitted changes')),
4456 ('', 'no-commit', None,
4457 ('', 'no-commit', None,
4457 _("don't commit, just update the working directory")),
4458 _("don't commit, just update the working directory")),
4458 ('', 'exact', None,
4459 ('', 'exact', None,
4459 _('apply patch to the nodes from which it was generated')),
4460 _('apply patch to the nodes from which it was generated')),
4460 ('', 'import-branch', None,
4461 ('', 'import-branch', None,
4461 _('use any branch information in patch (implied by --exact)'))] +
4462 _('use any branch information in patch (implied by --exact)'))] +
4462 commitopts + commitopts2 + similarityopts,
4463 commitopts + commitopts2 + similarityopts,
4463 _('[OPTION]... PATCH...')),
4464 _('[OPTION]... PATCH...')),
4464 "incoming|in":
4465 "incoming|in":
4465 (incoming,
4466 (incoming,
4466 [('f', 'force', None,
4467 [('f', 'force', None,
4467 _('run even if remote repository is unrelated')),
4468 _('run even if remote repository is unrelated')),
4468 ('n', 'newest-first', None, _('show newest record first')),
4469 ('n', 'newest-first', None, _('show newest record first')),
4469 ('', 'bundle', '',
4470 ('', 'bundle', '',
4470 _('file to store the bundles into'), _('FILE')),
4471 _('file to store the bundles into'), _('FILE')),
4471 ('r', 'rev', [],
4472 ('r', 'rev', [],
4472 _('a remote changeset intended to be added'), _('REV')),
4473 _('a remote changeset intended to be added'), _('REV')),
4473 ('B', 'bookmarks', False, _("compare bookmarks")),
4474 ('B', 'bookmarks', False, _("compare bookmarks")),
4474 ('b', 'branch', [],
4475 ('b', 'branch', [],
4475 _('a specific branch you would like to pull'), _('BRANCH')),
4476 _('a specific branch you would like to pull'), _('BRANCH')),
4476 ] + logopts + remoteopts + subrepoopts,
4477 ] + logopts + remoteopts + subrepoopts,
4477 _('[-p] [-n] [-M] [-f] [-r REV]...'
4478 _('[-p] [-n] [-M] [-f] [-r REV]...'
4478 ' [--bundle FILENAME] [SOURCE]')),
4479 ' [--bundle FILENAME] [SOURCE]')),
4479 "^init":
4480 "^init":
4480 (init,
4481 (init,
4481 remoteopts,
4482 remoteopts,
4482 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4483 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4483 "locate":
4484 "locate":
4484 (locate,
4485 (locate,
4485 [('r', 'rev', '',
4486 [('r', 'rev', '',
4486 _('search the repository as it is in REV'), _('REV')),
4487 _('search the repository as it is in REV'), _('REV')),
4487 ('0', 'print0', None,
4488 ('0', 'print0', None,
4488 _('end filenames with NUL, for use with xargs')),
4489 _('end filenames with NUL, for use with xargs')),
4489 ('f', 'fullpath', None,
4490 ('f', 'fullpath', None,
4490 _('print complete paths from the filesystem root')),
4491 _('print complete paths from the filesystem root')),
4491 ] + walkopts,
4492 ] + walkopts,
4492 _('[OPTION]... [PATTERN]...')),
4493 _('[OPTION]... [PATTERN]...')),
4493 "^log|history":
4494 "^log|history":
4494 (log,
4495 (log,
4495 [('f', 'follow', None,
4496 [('f', 'follow', None,
4496 _('follow changeset history,'
4497 _('follow changeset history,'
4497 ' or file history across copies and renames')),
4498 ' or file history across copies and renames')),
4498 ('', 'follow-first', None,
4499 ('', 'follow-first', None,
4499 _('only follow the first parent of merge changesets')),
4500 _('only follow the first parent of merge changesets')),
4500 ('d', 'date', '',
4501 ('d', 'date', '',
4501 _('show revisions matching date spec'), _('DATE')),
4502 _('show revisions matching date spec'), _('DATE')),
4502 ('C', 'copies', None, _('show copied files')),
4503 ('C', 'copies', None, _('show copied files')),
4503 ('k', 'keyword', [],
4504 ('k', 'keyword', [],
4504 _('do case-insensitive search for a given text'), _('TEXT')),
4505 _('do case-insensitive search for a given text'), _('TEXT')),
4505 ('r', 'rev', [],
4506 ('r', 'rev', [],
4506 _('show the specified revision or range'), _('REV')),
4507 _('show the specified revision or range'), _('REV')),
4507 ('', 'removed', None, _('include revisions where files were removed')),
4508 ('', 'removed', None, _('include revisions where files were removed')),
4508 ('m', 'only-merges', None, _('show only merges')),
4509 ('m', 'only-merges', None, _('show only merges')),
4509 ('u', 'user', [],
4510 ('u', 'user', [],
4510 _('revisions committed by user'), _('USER')),
4511 _('revisions committed by user'), _('USER')),
4511 ('', 'only-branch', [],
4512 ('', 'only-branch', [],
4512 _('show only changesets within the given named branch (DEPRECATED)'),
4513 _('show only changesets within the given named branch (DEPRECATED)'),
4513 _('BRANCH')),
4514 _('BRANCH')),
4514 ('b', 'branch', [],
4515 ('b', 'branch', [],
4515 _('show changesets within the given named branch'), _('BRANCH')),
4516 _('show changesets within the given named branch'), _('BRANCH')),
4516 ('P', 'prune', [],
4517 ('P', 'prune', [],
4517 _('do not display revision or any of its ancestors'), _('REV')),
4518 _('do not display revision or any of its ancestors'), _('REV')),
4518 ] + logopts + walkopts,
4519 ] + logopts + walkopts,
4519 _('[OPTION]... [FILE]')),
4520 _('[OPTION]... [FILE]')),
4520 "manifest":
4521 "manifest":
4521 (manifest,
4522 (manifest,
4522 [('r', 'rev', '',
4523 [('r', 'rev', '',
4523 _('revision to display'), _('REV'))],
4524 _('revision to display'), _('REV'))],
4524 _('[-r REV]')),
4525 _('[-r REV]')),
4525 "^merge":
4526 "^merge":
4526 (merge,
4527 (merge,
4527 [('f', 'force', None, _('force a merge with outstanding changes')),
4528 [('f', 'force', None, _('force a merge with outstanding changes')),
4528 ('t', 'tool', '', _('specify merge tool')),
4529 ('t', 'tool', '', _('specify merge tool')),
4529 ('r', 'rev', '',
4530 ('r', 'rev', '',
4530 _('revision to merge'), _('REV')),
4531 _('revision to merge'), _('REV')),
4531 ('P', 'preview', None,
4532 ('P', 'preview', None,
4532 _('review revisions to merge (no merge is performed)'))],
4533 _('review revisions to merge (no merge is performed)'))],
4533 _('[-P] [-f] [[-r] REV]')),
4534 _('[-P] [-f] [[-r] REV]')),
4534 "outgoing|out":
4535 "outgoing|out":
4535 (outgoing,
4536 (outgoing,
4536 [('f', 'force', None,
4537 [('f', 'force', None,
4537 _('run even when the destination is unrelated')),
4538 _('run even when the destination is unrelated')),
4538 ('r', 'rev', [],
4539 ('r', 'rev', [],
4539 _('a changeset intended to be included in the destination'),
4540 _('a changeset intended to be included in the destination'),
4540 _('REV')),
4541 _('REV')),
4541 ('n', 'newest-first', None, _('show newest record first')),
4542 ('n', 'newest-first', None, _('show newest record first')),
4542 ('B', 'bookmarks', False, _("compare bookmarks")),
4543 ('B', 'bookmarks', False, _("compare bookmarks")),
4543 ('b', 'branch', [],
4544 ('b', 'branch', [],
4544 _('a specific branch you would like to push'), _('BRANCH')),
4545 _('a specific branch you would like to push'), _('BRANCH')),
4545 ] + logopts + remoteopts + subrepoopts,
4546 ] + logopts + remoteopts + subrepoopts,
4546 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4547 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4547 "parents":
4548 "parents":
4548 (parents,
4549 (parents,
4549 [('r', 'rev', '',
4550 [('r', 'rev', '',
4550 _('show parents of the specified revision'), _('REV')),
4551 _('show parents of the specified revision'), _('REV')),
4551 ] + templateopts,
4552 ] + templateopts,
4552 _('[-r REV] [FILE]')),
4553 _('[-r REV] [FILE]')),
4553 "paths": (paths, [], _('[NAME]')),
4554 "paths": (paths, [], _('[NAME]')),
4554 "^pull":
4555 "^pull":
4555 (pull,
4556 (pull,
4556 [('u', 'update', None,
4557 [('u', 'update', None,
4557 _('update to new branch head if changesets were pulled')),
4558 _('update to new branch head if changesets were pulled')),
4558 ('f', 'force', None,
4559 ('f', 'force', None,
4559 _('run even when remote repository is unrelated')),
4560 _('run even when remote repository is unrelated')),
4560 ('r', 'rev', [],
4561 ('r', 'rev', [],
4561 _('a remote changeset intended to be added'), _('REV')),
4562 _('a remote changeset intended to be added'), _('REV')),
4562 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4563 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4563 ('b', 'branch', [],
4564 ('b', 'branch', [],
4564 _('a specific branch you would like to pull'), _('BRANCH')),
4565 _('a specific branch you would like to pull'), _('BRANCH')),
4565 ] + remoteopts,
4566 ] + remoteopts,
4566 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4567 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4567 "^push":
4568 "^push":
4568 (push,
4569 (push,
4569 [('f', 'force', None, _('force push')),
4570 [('f', 'force', None, _('force push')),
4570 ('r', 'rev', [],
4571 ('r', 'rev', [],
4571 _('a changeset intended to be included in the destination'),
4572 _('a changeset intended to be included in the destination'),
4572 _('REV')),
4573 _('REV')),
4573 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4574 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4574 ('b', 'branch', [],
4575 ('b', 'branch', [],
4575 _('a specific branch you would like to push'), _('BRANCH')),
4576 _('a specific branch you would like to push'), _('BRANCH')),
4576 ('', 'new-branch', False, _('allow pushing a new branch')),
4577 ('', 'new-branch', False, _('allow pushing a new branch')),
4577 ] + remoteopts,
4578 ] + remoteopts,
4578 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4579 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4579 "recover": (recover, []),
4580 "recover": (recover, []),
4580 "^remove|rm":
4581 "^remove|rm":
4581 (remove,
4582 (remove,
4582 [('A', 'after', None, _('record delete for missing files')),
4583 [('A', 'after', None, _('record delete for missing files')),
4583 ('f', 'force', None,
4584 ('f', 'force', None,
4584 _('remove (and delete) file even if added or modified')),
4585 _('remove (and delete) file even if added or modified')),
4585 ] + walkopts,
4586 ] + walkopts,
4586 _('[OPTION]... FILE...')),
4587 _('[OPTION]... FILE...')),
4587 "rename|move|mv":
4588 "rename|move|mv":
4588 (rename,
4589 (rename,
4589 [('A', 'after', None, _('record a rename that has already occurred')),
4590 [('A', 'after', None, _('record a rename that has already occurred')),
4590 ('f', 'force', None,
4591 ('f', 'force', None,
4591 _('forcibly copy over an existing managed file')),
4592 _('forcibly copy over an existing managed file')),
4592 ] + walkopts + dryrunopts,
4593 ] + walkopts + dryrunopts,
4593 _('[OPTION]... SOURCE... DEST')),
4594 _('[OPTION]... SOURCE... DEST')),
4594 "resolve":
4595 "resolve":
4595 (resolve,
4596 (resolve,
4596 [('a', 'all', None, _('select all unresolved files')),
4597 [('a', 'all', None, _('select all unresolved files')),
4597 ('l', 'list', None, _('list state of files needing merge')),
4598 ('l', 'list', None, _('list state of files needing merge')),
4598 ('m', 'mark', None, _('mark files as resolved')),
4599 ('m', 'mark', None, _('mark files as resolved')),
4599 ('u', 'unmark', None, _('mark files as unresolved')),
4600 ('u', 'unmark', None, _('mark files as unresolved')),
4600 ('t', 'tool', '', _('specify merge tool')),
4601 ('t', 'tool', '', _('specify merge tool')),
4601 ('n', 'no-status', None, _('hide status prefix'))]
4602 ('n', 'no-status', None, _('hide status prefix'))]
4602 + walkopts,
4603 + walkopts,
4603 _('[OPTION]... [FILE]...')),
4604 _('[OPTION]... [FILE]...')),
4604 "revert":
4605 "revert":
4605 (revert,
4606 (revert,
4606 [('a', 'all', None, _('revert all changes when no arguments given')),
4607 [('a', 'all', None, _('revert all changes when no arguments given')),
4607 ('d', 'date', '',
4608 ('d', 'date', '',
4608 _('tipmost revision matching date'), _('DATE')),
4609 _('tipmost revision matching date'), _('DATE')),
4609 ('r', 'rev', '',
4610 ('r', 'rev', '',
4610 _('revert to the specified revision'), _('REV')),
4611 _('revert to the specified revision'), _('REV')),
4611 ('', 'no-backup', None, _('do not save backup copies of files')),
4612 ('', 'no-backup', None, _('do not save backup copies of files')),
4612 ] + walkopts + dryrunopts,
4613 ] + walkopts + dryrunopts,
4613 _('[OPTION]... [-r REV] [NAME]...')),
4614 _('[OPTION]... [-r REV] [NAME]...')),
4614 "rollback": (rollback, dryrunopts),
4615 "rollback": (rollback, dryrunopts),
4615 "root": (root, []),
4616 "root": (root, []),
4616 "^serve":
4617 "^serve":
4617 (serve,
4618 (serve,
4618 [('A', 'accesslog', '',
4619 [('A', 'accesslog', '',
4619 _('name of access log file to write to'), _('FILE')),
4620 _('name of access log file to write to'), _('FILE')),
4620 ('d', 'daemon', None, _('run server in background')),
4621 ('d', 'daemon', None, _('run server in background')),
4621 ('', 'daemon-pipefds', '',
4622 ('', 'daemon-pipefds', '',
4622 _('used internally by daemon mode'), _('NUM')),
4623 _('used internally by daemon mode'), _('NUM')),
4623 ('E', 'errorlog', '',
4624 ('E', 'errorlog', '',
4624 _('name of error log file to write to'), _('FILE')),
4625 _('name of error log file to write to'), _('FILE')),
4625 # use string type, then we can check if something was passed
4626 # use string type, then we can check if something was passed
4626 ('p', 'port', '',
4627 ('p', 'port', '',
4627 _('port to listen on (default: 8000)'), _('PORT')),
4628 _('port to listen on (default: 8000)'), _('PORT')),
4628 ('a', 'address', '',
4629 ('a', 'address', '',
4629 _('address to listen on (default: all interfaces)'), _('ADDR')),
4630 _('address to listen on (default: all interfaces)'), _('ADDR')),
4630 ('', 'prefix', '',
4631 ('', 'prefix', '',
4631 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4632 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4632 ('n', 'name', '',
4633 ('n', 'name', '',
4633 _('name to show in web pages (default: working directory)'),
4634 _('name to show in web pages (default: working directory)'),
4634 _('NAME')),
4635 _('NAME')),
4635 ('', 'web-conf', '',
4636 ('', 'web-conf', '',
4636 _('name of the hgweb config file (see "hg help hgweb")'),
4637 _('name of the hgweb config file (see "hg help hgweb")'),
4637 _('FILE')),
4638 _('FILE')),
4638 ('', 'webdir-conf', '',
4639 ('', 'webdir-conf', '',
4639 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4640 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4640 ('', 'pid-file', '',
4641 ('', 'pid-file', '',
4641 _('name of file to write process ID to'), _('FILE')),
4642 _('name of file to write process ID to'), _('FILE')),
4642 ('', 'stdio', None, _('for remote clients')),
4643 ('', 'stdio', None, _('for remote clients')),
4643 ('t', 'templates', '',
4644 ('t', 'templates', '',
4644 _('web templates to use'), _('TEMPLATE')),
4645 _('web templates to use'), _('TEMPLATE')),
4645 ('', 'style', '',
4646 ('', 'style', '',
4646 _('template style to use'), _('STYLE')),
4647 _('template style to use'), _('STYLE')),
4647 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4648 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4648 ('', 'certificate', '',
4649 ('', 'certificate', '',
4649 _('SSL certificate file'), _('FILE'))],
4650 _('SSL certificate file'), _('FILE'))],
4650 _('[OPTION]...')),
4651 _('[OPTION]...')),
4651 "showconfig|debugconfig":
4652 "showconfig|debugconfig":
4652 (showconfig,
4653 (showconfig,
4653 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4654 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4654 _('[-u] [NAME]...')),
4655 _('[-u] [NAME]...')),
4655 "^summary|sum":
4656 "^summary|sum":
4656 (summary,
4657 (summary,
4657 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4658 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4658 "^status|st":
4659 "^status|st":
4659 (status,
4660 (status,
4660 [('A', 'all', None, _('show status of all files')),
4661 [('A', 'all', None, _('show status of all files')),
4661 ('m', 'modified', None, _('show only modified files')),
4662 ('m', 'modified', None, _('show only modified files')),
4662 ('a', 'added', None, _('show only added files')),
4663 ('a', 'added', None, _('show only added files')),
4663 ('r', 'removed', None, _('show only removed files')),
4664 ('r', 'removed', None, _('show only removed files')),
4664 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4665 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4665 ('c', 'clean', None, _('show only files without changes')),
4666 ('c', 'clean', None, _('show only files without changes')),
4666 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4667 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4667 ('i', 'ignored', None, _('show only ignored files')),
4668 ('i', 'ignored', None, _('show only ignored files')),
4668 ('n', 'no-status', None, _('hide status prefix')),
4669 ('n', 'no-status', None, _('hide status prefix')),
4669 ('C', 'copies', None, _('show source of copied files')),
4670 ('C', 'copies', None, _('show source of copied files')),
4670 ('0', 'print0', None,
4671 ('0', 'print0', None,
4671 _('end filenames with NUL, for use with xargs')),
4672 _('end filenames with NUL, for use with xargs')),
4672 ('', 'rev', [],
4673 ('', 'rev', [],
4673 _('show difference from revision'), _('REV')),
4674 _('show difference from revision'), _('REV')),
4674 ('', 'change', '',
4675 ('', 'change', '',
4675 _('list the changed files of a revision'), _('REV')),
4676 _('list the changed files of a revision'), _('REV')),
4676 ] + walkopts + subrepoopts,
4677 ] + walkopts + subrepoopts,
4677 _('[OPTION]... [FILE]...')),
4678 _('[OPTION]... [FILE]...')),
4678 "tag":
4679 "tag":
4679 (tag,
4680 (tag,
4680 [('f', 'force', None, _('force tag')),
4681 [('f', 'force', None, _('force tag')),
4681 ('l', 'local', None, _('make the tag local')),
4682 ('l', 'local', None, _('make the tag local')),
4682 ('r', 'rev', '',
4683 ('r', 'rev', '',
4683 _('revision to tag'), _('REV')),
4684 _('revision to tag'), _('REV')),
4684 ('', 'remove', None, _('remove a tag')),
4685 ('', 'remove', None, _('remove a tag')),
4685 # -l/--local is already there, commitopts cannot be used
4686 # -l/--local is already there, commitopts cannot be used
4686 ('e', 'edit', None, _('edit commit message')),
4687 ('e', 'edit', None, _('edit commit message')),
4687 ('m', 'message', '',
4688 ('m', 'message', '',
4688 _('use <text> as commit message'), _('TEXT')),
4689 _('use <text> as commit message'), _('TEXT')),
4689 ] + commitopts2,
4690 ] + commitopts2,
4690 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4691 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4691 "tags": (tags, [], ''),
4692 "tags": (tags, [], ''),
4692 "tip":
4693 "tip":
4693 (tip,
4694 (tip,
4694 [('p', 'patch', None, _('show patch')),
4695 [('p', 'patch', None, _('show patch')),
4695 ('g', 'git', None, _('use git extended diff format')),
4696 ('g', 'git', None, _('use git extended diff format')),
4696 ] + templateopts,
4697 ] + templateopts,
4697 _('[-p] [-g]')),
4698 _('[-p] [-g]')),
4698 "unbundle":
4699 "unbundle":
4699 (unbundle,
4700 (unbundle,
4700 [('u', 'update', None,
4701 [('u', 'update', None,
4701 _('update to new branch head if changesets were unbundled'))],
4702 _('update to new branch head if changesets were unbundled'))],
4702 _('[-u] FILE...')),
4703 _('[-u] FILE...')),
4703 "^update|up|checkout|co":
4704 "^update|up|checkout|co":
4704 (update,
4705 (update,
4705 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4706 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4706 ('c', 'check', None,
4707 ('c', 'check', None,
4707 _('update across branches if no uncommitted changes')),
4708 _('update across branches if no uncommitted changes')),
4708 ('d', 'date', '',
4709 ('d', 'date', '',
4709 _('tipmost revision matching date'), _('DATE')),
4710 _('tipmost revision matching date'), _('DATE')),
4710 ('r', 'rev', '',
4711 ('r', 'rev', '',
4711 _('revision'), _('REV'))],
4712 _('revision'), _('REV'))],
4712 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4713 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4713 "verify": (verify, []),
4714 "verify": (verify, []),
4714 "version": (version_, []),
4715 "version": (version_, []),
4715 }
4716 }
4716
4717
4717 norepo = ("clone init version help debugcommands debugcomplete"
4718 norepo = ("clone init version help debugcommands debugcomplete"
4718 " debugdate debuginstall debugfsinfo debugpushkey")
4719 " debugdate debuginstall debugfsinfo debugpushkey")
4719 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4720 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4720 " debugdata debugindex debugindexdot")
4721 " debugdata debugindex debugindexdot")
@@ -1,683 +1,685 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import util, ignore, osutil, parsers, encoding
10 import util, ignore, osutil, parsers, encoding
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO
12 import cStringIO
13
13
14 _format = ">cllll"
14 _format = ">cllll"
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 def _finddirs(path):
17 def _finddirs(path):
18 pos = path.rfind('/')
18 pos = path.rfind('/')
19 while pos != -1:
19 while pos != -1:
20 yield path[:pos]
20 yield path[:pos]
21 pos = path.rfind('/', 0, pos)
21 pos = path.rfind('/', 0, pos)
22
22
23 def _incdirs(dirs, path):
23 def _incdirs(dirs, path):
24 for base in _finddirs(path):
24 for base in _finddirs(path):
25 if base in dirs:
25 if base in dirs:
26 dirs[base] += 1
26 dirs[base] += 1
27 return
27 return
28 dirs[base] = 1
28 dirs[base] = 1
29
29
30 def _decdirs(dirs, path):
30 def _decdirs(dirs, path):
31 for base in _finddirs(path):
31 for base in _finddirs(path):
32 if dirs[base] > 1:
32 if dirs[base] > 1:
33 dirs[base] -= 1
33 dirs[base] -= 1
34 return
34 return
35 del dirs[base]
35 del dirs[base]
36
36
37 class dirstate(object):
37 class dirstate(object):
38
38
39 def __init__(self, opener, ui, root, validate):
39 def __init__(self, opener, ui, root, validate):
40 '''Create a new dirstate object.
40 '''Create a new dirstate object.
41
41
42 opener is an open()-like callable that can be used to open the
42 opener is an open()-like callable that can be used to open the
43 dirstate file; root is the root of the directory tracked by
43 dirstate file; root is the root of the directory tracked by
44 the dirstate.
44 the dirstate.
45 '''
45 '''
46 self._opener = opener
46 self._opener = opener
47 self._validate = validate
47 self._validate = validate
48 self._root = root
48 self._root = root
49 self._rootdir = os.path.join(root, '')
49 self._rootdir = os.path.join(root, '')
50 self._dirty = False
50 self._dirty = False
51 self._dirtypl = False
51 self._dirtypl = False
52 self._ui = ui
52 self._ui = ui
53
53
54 @propertycache
54 @propertycache
55 def _map(self):
55 def _map(self):
56 '''Return the dirstate contents as a map from filename to
56 '''Return the dirstate contents as a map from filename to
57 (state, mode, size, time).'''
57 (state, mode, size, time).'''
58 self._read()
58 self._read()
59 return self._map
59 return self._map
60
60
61 @propertycache
61 @propertycache
62 def _copymap(self):
62 def _copymap(self):
63 self._read()
63 self._read()
64 return self._copymap
64 return self._copymap
65
65
66 @propertycache
66 @propertycache
67 def _foldmap(self):
67 def _foldmap(self):
68 f = {}
68 f = {}
69 for name in self._map:
69 for name in self._map:
70 f[os.path.normcase(name)] = name
70 f[os.path.normcase(name)] = name
71 return f
71 return f
72
72
73 @propertycache
73 @propertycache
74 def _branch(self):
74 def _branch(self):
75 try:
75 try:
76 return self._opener("branch").read().strip() or "default"
76 return self._opener("branch").read().strip() or "default"
77 except IOError:
77 except IOError:
78 return "default"
78 return "default"
79
79
80 @propertycache
80 @propertycache
81 def _pl(self):
81 def _pl(self):
82 try:
82 try:
83 st = self._opener("dirstate").read(40)
83 fp = self._opener("dirstate")
84 st = fp.read(40)
85 fp.close()
84 l = len(st)
86 l = len(st)
85 if l == 40:
87 if l == 40:
86 return st[:20], st[20:40]
88 return st[:20], st[20:40]
87 elif l > 0 and l < 40:
89 elif l > 0 and l < 40:
88 raise util.Abort(_('working directory state appears damaged!'))
90 raise util.Abort(_('working directory state appears damaged!'))
89 except IOError, err:
91 except IOError, err:
90 if err.errno != errno.ENOENT:
92 if err.errno != errno.ENOENT:
91 raise
93 raise
92 return [nullid, nullid]
94 return [nullid, nullid]
93
95
94 @propertycache
96 @propertycache
95 def _dirs(self):
97 def _dirs(self):
96 dirs = {}
98 dirs = {}
97 for f, s in self._map.iteritems():
99 for f, s in self._map.iteritems():
98 if s[0] != 'r':
100 if s[0] != 'r':
99 _incdirs(dirs, f)
101 _incdirs(dirs, f)
100 return dirs
102 return dirs
101
103
102 @propertycache
104 @propertycache
103 def _ignore(self):
105 def _ignore(self):
104 files = [self._join('.hgignore')]
106 files = [self._join('.hgignore')]
105 for name, path in self._ui.configitems("ui"):
107 for name, path in self._ui.configitems("ui"):
106 if name == 'ignore' or name.startswith('ignore.'):
108 if name == 'ignore' or name.startswith('ignore.'):
107 files.append(util.expandpath(path))
109 files.append(util.expandpath(path))
108 return ignore.ignore(self._root, files, self._ui.warn)
110 return ignore.ignore(self._root, files, self._ui.warn)
109
111
110 @propertycache
112 @propertycache
111 def _slash(self):
113 def _slash(self):
112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
114 return self._ui.configbool('ui', 'slash') and os.sep != '/'
113
115
114 @propertycache
116 @propertycache
115 def _checklink(self):
117 def _checklink(self):
116 return util.checklink(self._root)
118 return util.checklink(self._root)
117
119
118 @propertycache
120 @propertycache
119 def _checkexec(self):
121 def _checkexec(self):
120 return util.checkexec(self._root)
122 return util.checkexec(self._root)
121
123
122 @propertycache
124 @propertycache
123 def _checkcase(self):
125 def _checkcase(self):
124 return not util.checkcase(self._join('.hg'))
126 return not util.checkcase(self._join('.hg'))
125
127
126 def _join(self, f):
128 def _join(self, f):
127 # much faster than os.path.join()
129 # much faster than os.path.join()
128 # it's safe because f is always a relative path
130 # it's safe because f is always a relative path
129 return self._rootdir + f
131 return self._rootdir + f
130
132
131 def flagfunc(self, fallback):
133 def flagfunc(self, fallback):
132 if self._checklink:
134 if self._checklink:
133 if self._checkexec:
135 if self._checkexec:
134 def f(x):
136 def f(x):
135 p = self._join(x)
137 p = self._join(x)
136 if os.path.islink(p):
138 if os.path.islink(p):
137 return 'l'
139 return 'l'
138 if util.is_exec(p):
140 if util.is_exec(p):
139 return 'x'
141 return 'x'
140 return ''
142 return ''
141 return f
143 return f
142 def f(x):
144 def f(x):
143 if os.path.islink(self._join(x)):
145 if os.path.islink(self._join(x)):
144 return 'l'
146 return 'l'
145 if 'x' in fallback(x):
147 if 'x' in fallback(x):
146 return 'x'
148 return 'x'
147 return ''
149 return ''
148 return f
150 return f
149 if self._checkexec:
151 if self._checkexec:
150 def f(x):
152 def f(x):
151 if 'l' in fallback(x):
153 if 'l' in fallback(x):
152 return 'l'
154 return 'l'
153 if util.is_exec(self._join(x)):
155 if util.is_exec(self._join(x)):
154 return 'x'
156 return 'x'
155 return ''
157 return ''
156 return f
158 return f
157 return fallback
159 return fallback
158
160
159 def getcwd(self):
161 def getcwd(self):
160 cwd = os.getcwd()
162 cwd = os.getcwd()
161 if cwd == self._root:
163 if cwd == self._root:
162 return ''
164 return ''
163 # self._root ends with a path separator if self._root is '/' or 'C:\'
165 # self._root ends with a path separator if self._root is '/' or 'C:\'
164 rootsep = self._root
166 rootsep = self._root
165 if not util.endswithsep(rootsep):
167 if not util.endswithsep(rootsep):
166 rootsep += os.sep
168 rootsep += os.sep
167 if cwd.startswith(rootsep):
169 if cwd.startswith(rootsep):
168 return cwd[len(rootsep):]
170 return cwd[len(rootsep):]
169 else:
171 else:
170 # we're outside the repo. return an absolute path.
172 # we're outside the repo. return an absolute path.
171 return cwd
173 return cwd
172
174
173 def pathto(self, f, cwd=None):
175 def pathto(self, f, cwd=None):
174 if cwd is None:
176 if cwd is None:
175 cwd = self.getcwd()
177 cwd = self.getcwd()
176 path = util.pathto(self._root, cwd, f)
178 path = util.pathto(self._root, cwd, f)
177 if self._slash:
179 if self._slash:
178 return util.normpath(path)
180 return util.normpath(path)
179 return path
181 return path
180
182
181 def __getitem__(self, key):
183 def __getitem__(self, key):
182 '''Return the current state of key (a filename) in the dirstate.
184 '''Return the current state of key (a filename) in the dirstate.
183
185
184 States are:
186 States are:
185 n normal
187 n normal
186 m needs merging
188 m needs merging
187 r marked for removal
189 r marked for removal
188 a marked for addition
190 a marked for addition
189 ? not tracked
191 ? not tracked
190 '''
192 '''
191 return self._map.get(key, ("?",))[0]
193 return self._map.get(key, ("?",))[0]
192
194
193 def __contains__(self, key):
195 def __contains__(self, key):
194 return key in self._map
196 return key in self._map
195
197
196 def __iter__(self):
198 def __iter__(self):
197 for x in sorted(self._map):
199 for x in sorted(self._map):
198 yield x
200 yield x
199
201
200 def parents(self):
202 def parents(self):
201 return [self._validate(p) for p in self._pl]
203 return [self._validate(p) for p in self._pl]
202
204
203 def branch(self):
205 def branch(self):
204 return encoding.tolocal(self._branch)
206 return encoding.tolocal(self._branch)
205
207
206 def setparents(self, p1, p2=nullid):
208 def setparents(self, p1, p2=nullid):
207 self._dirty = self._dirtypl = True
209 self._dirty = self._dirtypl = True
208 self._pl = p1, p2
210 self._pl = p1, p2
209
211
210 def setbranch(self, branch):
212 def setbranch(self, branch):
211 if branch in ['tip', '.', 'null']:
213 if branch in ['tip', '.', 'null']:
212 raise util.Abort(_('the name \'%s\' is reserved') % branch)
214 raise util.Abort(_('the name \'%s\' is reserved') % branch)
213 self._branch = encoding.fromlocal(branch)
215 self._branch = encoding.fromlocal(branch)
214 self._opener("branch", "w").write(self._branch + '\n')
216 self._opener("branch", "w").write(self._branch + '\n')
215
217
216 def _read(self):
218 def _read(self):
217 self._map = {}
219 self._map = {}
218 self._copymap = {}
220 self._copymap = {}
219 try:
221 try:
220 st = self._opener("dirstate").read()
222 st = self._opener("dirstate").read()
221 except IOError, err:
223 except IOError, err:
222 if err.errno != errno.ENOENT:
224 if err.errno != errno.ENOENT:
223 raise
225 raise
224 return
226 return
225 if not st:
227 if not st:
226 return
228 return
227
229
228 p = parsers.parse_dirstate(self._map, self._copymap, st)
230 p = parsers.parse_dirstate(self._map, self._copymap, st)
229 if not self._dirtypl:
231 if not self._dirtypl:
230 self._pl = p
232 self._pl = p
231
233
232 def invalidate(self):
234 def invalidate(self):
233 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
235 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
234 "_ignore"):
236 "_ignore"):
235 if a in self.__dict__:
237 if a in self.__dict__:
236 delattr(self, a)
238 delattr(self, a)
237 self._dirty = False
239 self._dirty = False
238
240
239 def copy(self, source, dest):
241 def copy(self, source, dest):
240 """Mark dest as a copy of source. Unmark dest if source is None."""
242 """Mark dest as a copy of source. Unmark dest if source is None."""
241 if source == dest:
243 if source == dest:
242 return
244 return
243 self._dirty = True
245 self._dirty = True
244 if source is not None:
246 if source is not None:
245 self._copymap[dest] = source
247 self._copymap[dest] = source
246 elif dest in self._copymap:
248 elif dest in self._copymap:
247 del self._copymap[dest]
249 del self._copymap[dest]
248
250
249 def copied(self, file):
251 def copied(self, file):
250 return self._copymap.get(file, None)
252 return self._copymap.get(file, None)
251
253
252 def copies(self):
254 def copies(self):
253 return self._copymap
255 return self._copymap
254
256
255 def _droppath(self, f):
257 def _droppath(self, f):
256 if self[f] not in "?r" and "_dirs" in self.__dict__:
258 if self[f] not in "?r" and "_dirs" in self.__dict__:
257 _decdirs(self._dirs, f)
259 _decdirs(self._dirs, f)
258
260
259 def _addpath(self, f, check=False):
261 def _addpath(self, f, check=False):
260 oldstate = self[f]
262 oldstate = self[f]
261 if check or oldstate == "r":
263 if check or oldstate == "r":
262 if '\r' in f or '\n' in f:
264 if '\r' in f or '\n' in f:
263 raise util.Abort(
265 raise util.Abort(
264 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
266 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
265 if f in self._dirs:
267 if f in self._dirs:
266 raise util.Abort(_('directory %r already in dirstate') % f)
268 raise util.Abort(_('directory %r already in dirstate') % f)
267 # shadows
269 # shadows
268 for d in _finddirs(f):
270 for d in _finddirs(f):
269 if d in self._dirs:
271 if d in self._dirs:
270 break
272 break
271 if d in self._map and self[d] != 'r':
273 if d in self._map and self[d] != 'r':
272 raise util.Abort(
274 raise util.Abort(
273 _('file %r in dirstate clashes with %r') % (d, f))
275 _('file %r in dirstate clashes with %r') % (d, f))
274 if oldstate in "?r" and "_dirs" in self.__dict__:
276 if oldstate in "?r" and "_dirs" in self.__dict__:
275 _incdirs(self._dirs, f)
277 _incdirs(self._dirs, f)
276
278
277 def normal(self, f):
279 def normal(self, f):
278 '''Mark a file normal and clean.'''
280 '''Mark a file normal and clean.'''
279 self._dirty = True
281 self._dirty = True
280 self._addpath(f)
282 self._addpath(f)
281 s = os.lstat(self._join(f))
283 s = os.lstat(self._join(f))
282 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
284 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
283 if f in self._copymap:
285 if f in self._copymap:
284 del self._copymap[f]
286 del self._copymap[f]
285
287
286 def normallookup(self, f):
288 def normallookup(self, f):
287 '''Mark a file normal, but possibly dirty.'''
289 '''Mark a file normal, but possibly dirty.'''
288 if self._pl[1] != nullid and f in self._map:
290 if self._pl[1] != nullid and f in self._map:
289 # if there is a merge going on and the file was either
291 # if there is a merge going on and the file was either
290 # in state 'm' (-1) or coming from other parent (-2) before
292 # in state 'm' (-1) or coming from other parent (-2) before
291 # being removed, restore that state.
293 # being removed, restore that state.
292 entry = self._map[f]
294 entry = self._map[f]
293 if entry[0] == 'r' and entry[2] in (-1, -2):
295 if entry[0] == 'r' and entry[2] in (-1, -2):
294 source = self._copymap.get(f)
296 source = self._copymap.get(f)
295 if entry[2] == -1:
297 if entry[2] == -1:
296 self.merge(f)
298 self.merge(f)
297 elif entry[2] == -2:
299 elif entry[2] == -2:
298 self.otherparent(f)
300 self.otherparent(f)
299 if source:
301 if source:
300 self.copy(source, f)
302 self.copy(source, f)
301 return
303 return
302 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
304 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
303 return
305 return
304 self._dirty = True
306 self._dirty = True
305 self._addpath(f)
307 self._addpath(f)
306 self._map[f] = ('n', 0, -1, -1)
308 self._map[f] = ('n', 0, -1, -1)
307 if f in self._copymap:
309 if f in self._copymap:
308 del self._copymap[f]
310 del self._copymap[f]
309
311
310 def otherparent(self, f):
312 def otherparent(self, f):
311 '''Mark as coming from the other parent, always dirty.'''
313 '''Mark as coming from the other parent, always dirty.'''
312 if self._pl[1] == nullid:
314 if self._pl[1] == nullid:
313 raise util.Abort(_("setting %r to other parent "
315 raise util.Abort(_("setting %r to other parent "
314 "only allowed in merges") % f)
316 "only allowed in merges") % f)
315 self._dirty = True
317 self._dirty = True
316 self._addpath(f)
318 self._addpath(f)
317 self._map[f] = ('n', 0, -2, -1)
319 self._map[f] = ('n', 0, -2, -1)
318 if f in self._copymap:
320 if f in self._copymap:
319 del self._copymap[f]
321 del self._copymap[f]
320
322
321 def add(self, f):
323 def add(self, f):
322 '''Mark a file added.'''
324 '''Mark a file added.'''
323 self._dirty = True
325 self._dirty = True
324 self._addpath(f, True)
326 self._addpath(f, True)
325 self._map[f] = ('a', 0, -1, -1)
327 self._map[f] = ('a', 0, -1, -1)
326 if f in self._copymap:
328 if f in self._copymap:
327 del self._copymap[f]
329 del self._copymap[f]
328
330
329 def remove(self, f):
331 def remove(self, f):
330 '''Mark a file removed.'''
332 '''Mark a file removed.'''
331 self._dirty = True
333 self._dirty = True
332 self._droppath(f)
334 self._droppath(f)
333 size = 0
335 size = 0
334 if self._pl[1] != nullid and f in self._map:
336 if self._pl[1] != nullid and f in self._map:
335 # backup the previous state
337 # backup the previous state
336 entry = self._map[f]
338 entry = self._map[f]
337 if entry[0] == 'm': # merge
339 if entry[0] == 'm': # merge
338 size = -1
340 size = -1
339 elif entry[0] == 'n' and entry[2] == -2: # other parent
341 elif entry[0] == 'n' and entry[2] == -2: # other parent
340 size = -2
342 size = -2
341 self._map[f] = ('r', 0, size, 0)
343 self._map[f] = ('r', 0, size, 0)
342 if size == 0 and f in self._copymap:
344 if size == 0 and f in self._copymap:
343 del self._copymap[f]
345 del self._copymap[f]
344
346
345 def merge(self, f):
347 def merge(self, f):
346 '''Mark a file merged.'''
348 '''Mark a file merged.'''
347 self._dirty = True
349 self._dirty = True
348 s = os.lstat(self._join(f))
350 s = os.lstat(self._join(f))
349 self._addpath(f)
351 self._addpath(f)
350 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
352 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
351 if f in self._copymap:
353 if f in self._copymap:
352 del self._copymap[f]
354 del self._copymap[f]
353
355
354 def forget(self, f):
356 def forget(self, f):
355 '''Forget a file.'''
357 '''Forget a file.'''
356 self._dirty = True
358 self._dirty = True
357 try:
359 try:
358 self._droppath(f)
360 self._droppath(f)
359 del self._map[f]
361 del self._map[f]
360 except KeyError:
362 except KeyError:
361 self._ui.warn(_("not in dirstate: %s\n") % f)
363 self._ui.warn(_("not in dirstate: %s\n") % f)
362
364
363 def _normalize(self, path, knownpath):
365 def _normalize(self, path, knownpath):
364 norm_path = os.path.normcase(path)
366 norm_path = os.path.normcase(path)
365 fold_path = self._foldmap.get(norm_path, None)
367 fold_path = self._foldmap.get(norm_path, None)
366 if fold_path is None:
368 if fold_path is None:
367 if knownpath or not os.path.lexists(os.path.join(self._root, path)):
369 if knownpath or not os.path.lexists(os.path.join(self._root, path)):
368 fold_path = path
370 fold_path = path
369 else:
371 else:
370 fold_path = self._foldmap.setdefault(norm_path,
372 fold_path = self._foldmap.setdefault(norm_path,
371 util.fspath(path, self._root))
373 util.fspath(path, self._root))
372 return fold_path
374 return fold_path
373
375
374 def clear(self):
376 def clear(self):
375 self._map = {}
377 self._map = {}
376 if "_dirs" in self.__dict__:
378 if "_dirs" in self.__dict__:
377 delattr(self, "_dirs")
379 delattr(self, "_dirs")
378 self._copymap = {}
380 self._copymap = {}
379 self._pl = [nullid, nullid]
381 self._pl = [nullid, nullid]
380 self._dirty = True
382 self._dirty = True
381
383
382 def rebuild(self, parent, files):
384 def rebuild(self, parent, files):
383 self.clear()
385 self.clear()
384 for f in files:
386 for f in files:
385 if 'x' in files.flags(f):
387 if 'x' in files.flags(f):
386 self._map[f] = ('n', 0777, -1, 0)
388 self._map[f] = ('n', 0777, -1, 0)
387 else:
389 else:
388 self._map[f] = ('n', 0666, -1, 0)
390 self._map[f] = ('n', 0666, -1, 0)
389 self._pl = (parent, nullid)
391 self._pl = (parent, nullid)
390 self._dirty = True
392 self._dirty = True
391
393
392 def write(self):
394 def write(self):
393 if not self._dirty:
395 if not self._dirty:
394 return
396 return
395 st = self._opener("dirstate", "w", atomictemp=True)
397 st = self._opener("dirstate", "w", atomictemp=True)
396
398
397 # use the modification time of the newly created temporary file as the
399 # use the modification time of the newly created temporary file as the
398 # filesystem's notion of 'now'
400 # filesystem's notion of 'now'
399 now = int(util.fstat(st).st_mtime)
401 now = int(util.fstat(st).st_mtime)
400
402
401 cs = cStringIO.StringIO()
403 cs = cStringIO.StringIO()
402 copymap = self._copymap
404 copymap = self._copymap
403 pack = struct.pack
405 pack = struct.pack
404 write = cs.write
406 write = cs.write
405 write("".join(self._pl))
407 write("".join(self._pl))
406 for f, e in self._map.iteritems():
408 for f, e in self._map.iteritems():
407 if e[0] == 'n' and e[3] == now:
409 if e[0] == 'n' and e[3] == now:
408 # The file was last modified "simultaneously" with the current
410 # The file was last modified "simultaneously" with the current
409 # write to dirstate (i.e. within the same second for file-
411 # write to dirstate (i.e. within the same second for file-
410 # systems with a granularity of 1 sec). This commonly happens
412 # systems with a granularity of 1 sec). This commonly happens
411 # for at least a couple of files on 'update'.
413 # for at least a couple of files on 'update'.
412 # The user could change the file without changing its size
414 # The user could change the file without changing its size
413 # within the same second. Invalidate the file's stat data in
415 # within the same second. Invalidate the file's stat data in
414 # dirstate, forcing future 'status' calls to compare the
416 # dirstate, forcing future 'status' calls to compare the
415 # contents of the file. This prevents mistakenly treating such
417 # contents of the file. This prevents mistakenly treating such
416 # files as clean.
418 # files as clean.
417 e = (e[0], 0, -1, -1) # mark entry as 'unset'
419 e = (e[0], 0, -1, -1) # mark entry as 'unset'
418 self._map[f] = e
420 self._map[f] = e
419
421
420 if f in copymap:
422 if f in copymap:
421 f = "%s\0%s" % (f, copymap[f])
423 f = "%s\0%s" % (f, copymap[f])
422 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
424 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
423 write(e)
425 write(e)
424 write(f)
426 write(f)
425 st.write(cs.getvalue())
427 st.write(cs.getvalue())
426 st.rename()
428 st.rename()
427 self._dirty = self._dirtypl = False
429 self._dirty = self._dirtypl = False
428
430
429 def _dirignore(self, f):
431 def _dirignore(self, f):
430 if f == '.':
432 if f == '.':
431 return False
433 return False
432 if self._ignore(f):
434 if self._ignore(f):
433 return True
435 return True
434 for p in _finddirs(f):
436 for p in _finddirs(f):
435 if self._ignore(p):
437 if self._ignore(p):
436 return True
438 return True
437 return False
439 return False
438
440
439 def walk(self, match, subrepos, unknown, ignored):
441 def walk(self, match, subrepos, unknown, ignored):
440 '''
442 '''
441 Walk recursively through the directory tree, finding all files
443 Walk recursively through the directory tree, finding all files
442 matched by match.
444 matched by match.
443
445
444 Return a dict mapping filename to stat-like object (either
446 Return a dict mapping filename to stat-like object (either
445 mercurial.osutil.stat instance or return value of os.stat()).
447 mercurial.osutil.stat instance or return value of os.stat()).
446 '''
448 '''
447
449
448 def fwarn(f, msg):
450 def fwarn(f, msg):
449 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
451 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
450 return False
452 return False
451
453
452 def badtype(mode):
454 def badtype(mode):
453 kind = _('unknown')
455 kind = _('unknown')
454 if stat.S_ISCHR(mode):
456 if stat.S_ISCHR(mode):
455 kind = _('character device')
457 kind = _('character device')
456 elif stat.S_ISBLK(mode):
458 elif stat.S_ISBLK(mode):
457 kind = _('block device')
459 kind = _('block device')
458 elif stat.S_ISFIFO(mode):
460 elif stat.S_ISFIFO(mode):
459 kind = _('fifo')
461 kind = _('fifo')
460 elif stat.S_ISSOCK(mode):
462 elif stat.S_ISSOCK(mode):
461 kind = _('socket')
463 kind = _('socket')
462 elif stat.S_ISDIR(mode):
464 elif stat.S_ISDIR(mode):
463 kind = _('directory')
465 kind = _('directory')
464 return _('unsupported file type (type is %s)') % kind
466 return _('unsupported file type (type is %s)') % kind
465
467
466 ignore = self._ignore
468 ignore = self._ignore
467 dirignore = self._dirignore
469 dirignore = self._dirignore
468 if ignored:
470 if ignored:
469 ignore = util.never
471 ignore = util.never
470 dirignore = util.never
472 dirignore = util.never
471 elif not unknown:
473 elif not unknown:
472 # if unknown and ignored are False, skip step 2
474 # if unknown and ignored are False, skip step 2
473 ignore = util.always
475 ignore = util.always
474 dirignore = util.always
476 dirignore = util.always
475
477
476 matchfn = match.matchfn
478 matchfn = match.matchfn
477 badfn = match.bad
479 badfn = match.bad
478 dmap = self._map
480 dmap = self._map
479 normpath = util.normpath
481 normpath = util.normpath
480 listdir = osutil.listdir
482 listdir = osutil.listdir
481 lstat = os.lstat
483 lstat = os.lstat
482 getkind = stat.S_IFMT
484 getkind = stat.S_IFMT
483 dirkind = stat.S_IFDIR
485 dirkind = stat.S_IFDIR
484 regkind = stat.S_IFREG
486 regkind = stat.S_IFREG
485 lnkkind = stat.S_IFLNK
487 lnkkind = stat.S_IFLNK
486 join = self._join
488 join = self._join
487 work = []
489 work = []
488 wadd = work.append
490 wadd = work.append
489
491
490 exact = skipstep3 = False
492 exact = skipstep3 = False
491 if matchfn == match.exact: # match.exact
493 if matchfn == match.exact: # match.exact
492 exact = True
494 exact = True
493 dirignore = util.always # skip step 2
495 dirignore = util.always # skip step 2
494 elif match.files() and not match.anypats(): # match.match, no patterns
496 elif match.files() and not match.anypats(): # match.match, no patterns
495 skipstep3 = True
497 skipstep3 = True
496
498
497 if self._checkcase:
499 if self._checkcase:
498 normalize = self._normalize
500 normalize = self._normalize
499 skipstep3 = False
501 skipstep3 = False
500 else:
502 else:
501 normalize = lambda x, y: x
503 normalize = lambda x, y: x
502
504
503 files = sorted(match.files())
505 files = sorted(match.files())
504 subrepos.sort()
506 subrepos.sort()
505 i, j = 0, 0
507 i, j = 0, 0
506 while i < len(files) and j < len(subrepos):
508 while i < len(files) and j < len(subrepos):
507 subpath = subrepos[j] + "/"
509 subpath = subrepos[j] + "/"
508 if files[i] < subpath:
510 if files[i] < subpath:
509 i += 1
511 i += 1
510 continue
512 continue
511 while i < len(files) and files[i].startswith(subpath):
513 while i < len(files) and files[i].startswith(subpath):
512 del files[i]
514 del files[i]
513 j += 1
515 j += 1
514
516
515 if not files or '.' in files:
517 if not files or '.' in files:
516 files = ['']
518 files = ['']
517 results = dict.fromkeys(subrepos)
519 results = dict.fromkeys(subrepos)
518 results['.hg'] = None
520 results['.hg'] = None
519
521
520 # step 1: find all explicit files
522 # step 1: find all explicit files
521 for ff in files:
523 for ff in files:
522 nf = normalize(normpath(ff), False)
524 nf = normalize(normpath(ff), False)
523 if nf in results:
525 if nf in results:
524 continue
526 continue
525
527
526 try:
528 try:
527 st = lstat(join(nf))
529 st = lstat(join(nf))
528 kind = getkind(st.st_mode)
530 kind = getkind(st.st_mode)
529 if kind == dirkind:
531 if kind == dirkind:
530 skipstep3 = False
532 skipstep3 = False
531 if nf in dmap:
533 if nf in dmap:
532 #file deleted on disk but still in dirstate
534 #file deleted on disk but still in dirstate
533 results[nf] = None
535 results[nf] = None
534 match.dir(nf)
536 match.dir(nf)
535 if not dirignore(nf):
537 if not dirignore(nf):
536 wadd(nf)
538 wadd(nf)
537 elif kind == regkind or kind == lnkkind:
539 elif kind == regkind or kind == lnkkind:
538 results[nf] = st
540 results[nf] = st
539 else:
541 else:
540 badfn(ff, badtype(kind))
542 badfn(ff, badtype(kind))
541 if nf in dmap:
543 if nf in dmap:
542 results[nf] = None
544 results[nf] = None
543 except OSError, inst:
545 except OSError, inst:
544 if nf in dmap: # does it exactly match a file?
546 if nf in dmap: # does it exactly match a file?
545 results[nf] = None
547 results[nf] = None
546 else: # does it match a directory?
548 else: # does it match a directory?
547 prefix = nf + "/"
549 prefix = nf + "/"
548 for fn in dmap:
550 for fn in dmap:
549 if fn.startswith(prefix):
551 if fn.startswith(prefix):
550 match.dir(nf)
552 match.dir(nf)
551 skipstep3 = False
553 skipstep3 = False
552 break
554 break
553 else:
555 else:
554 badfn(ff, inst.strerror)
556 badfn(ff, inst.strerror)
555
557
556 # step 2: visit subdirectories
558 # step 2: visit subdirectories
557 while work:
559 while work:
558 nd = work.pop()
560 nd = work.pop()
559 skip = None
561 skip = None
560 if nd == '.':
562 if nd == '.':
561 nd = ''
563 nd = ''
562 else:
564 else:
563 skip = '.hg'
565 skip = '.hg'
564 try:
566 try:
565 entries = listdir(join(nd), stat=True, skip=skip)
567 entries = listdir(join(nd), stat=True, skip=skip)
566 except OSError, inst:
568 except OSError, inst:
567 if inst.errno == errno.EACCES:
569 if inst.errno == errno.EACCES:
568 fwarn(nd, inst.strerror)
570 fwarn(nd, inst.strerror)
569 continue
571 continue
570 raise
572 raise
571 for f, kind, st in entries:
573 for f, kind, st in entries:
572 nf = normalize(nd and (nd + "/" + f) or f, True)
574 nf = normalize(nd and (nd + "/" + f) or f, True)
573 if nf not in results:
575 if nf not in results:
574 if kind == dirkind:
576 if kind == dirkind:
575 if not ignore(nf):
577 if not ignore(nf):
576 match.dir(nf)
578 match.dir(nf)
577 wadd(nf)
579 wadd(nf)
578 if nf in dmap and matchfn(nf):
580 if nf in dmap and matchfn(nf):
579 results[nf] = None
581 results[nf] = None
580 elif kind == regkind or kind == lnkkind:
582 elif kind == regkind or kind == lnkkind:
581 if nf in dmap:
583 if nf in dmap:
582 if matchfn(nf):
584 if matchfn(nf):
583 results[nf] = st
585 results[nf] = st
584 elif matchfn(nf) and not ignore(nf):
586 elif matchfn(nf) and not ignore(nf):
585 results[nf] = st
587 results[nf] = st
586 elif nf in dmap and matchfn(nf):
588 elif nf in dmap and matchfn(nf):
587 results[nf] = None
589 results[nf] = None
588
590
589 # step 3: report unseen items in the dmap hash
591 # step 3: report unseen items in the dmap hash
590 if not skipstep3 and not exact:
592 if not skipstep3 and not exact:
591 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
593 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
592 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
594 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
593 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
595 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
594 st = None
596 st = None
595 results[nf] = st
597 results[nf] = st
596 for s in subrepos:
598 for s in subrepos:
597 del results[s]
599 del results[s]
598 del results['.hg']
600 del results['.hg']
599 return results
601 return results
600
602
601 def status(self, match, subrepos, ignored, clean, unknown):
603 def status(self, match, subrepos, ignored, clean, unknown):
602 '''Determine the status of the working copy relative to the
604 '''Determine the status of the working copy relative to the
603 dirstate and return a tuple of lists (unsure, modified, added,
605 dirstate and return a tuple of lists (unsure, modified, added,
604 removed, deleted, unknown, ignored, clean), where:
606 removed, deleted, unknown, ignored, clean), where:
605
607
606 unsure:
608 unsure:
607 files that might have been modified since the dirstate was
609 files that might have been modified since the dirstate was
608 written, but need to be read to be sure (size is the same
610 written, but need to be read to be sure (size is the same
609 but mtime differs)
611 but mtime differs)
610 modified:
612 modified:
611 files that have definitely been modified since the dirstate
613 files that have definitely been modified since the dirstate
612 was written (different size or mode)
614 was written (different size or mode)
613 added:
615 added:
614 files that have been explicitly added with hg add
616 files that have been explicitly added with hg add
615 removed:
617 removed:
616 files that have been explicitly removed with hg remove
618 files that have been explicitly removed with hg remove
617 deleted:
619 deleted:
618 files that have been deleted through other means ("missing")
620 files that have been deleted through other means ("missing")
619 unknown:
621 unknown:
620 files not in the dirstate that are not ignored
622 files not in the dirstate that are not ignored
621 ignored:
623 ignored:
622 files not in the dirstate that are ignored
624 files not in the dirstate that are ignored
623 (by _dirignore())
625 (by _dirignore())
624 clean:
626 clean:
625 files that have definitely not been modified since the
627 files that have definitely not been modified since the
626 dirstate was written
628 dirstate was written
627 '''
629 '''
628 listignored, listclean, listunknown = ignored, clean, unknown
630 listignored, listclean, listunknown = ignored, clean, unknown
629 lookup, modified, added, unknown, ignored = [], [], [], [], []
631 lookup, modified, added, unknown, ignored = [], [], [], [], []
630 removed, deleted, clean = [], [], []
632 removed, deleted, clean = [], [], []
631
633
632 dmap = self._map
634 dmap = self._map
633 ladd = lookup.append # aka "unsure"
635 ladd = lookup.append # aka "unsure"
634 madd = modified.append
636 madd = modified.append
635 aadd = added.append
637 aadd = added.append
636 uadd = unknown.append
638 uadd = unknown.append
637 iadd = ignored.append
639 iadd = ignored.append
638 radd = removed.append
640 radd = removed.append
639 dadd = deleted.append
641 dadd = deleted.append
640 cadd = clean.append
642 cadd = clean.append
641
643
642 lnkkind = stat.S_IFLNK
644 lnkkind = stat.S_IFLNK
643
645
644 for fn, st in self.walk(match, subrepos, listunknown,
646 for fn, st in self.walk(match, subrepos, listunknown,
645 listignored).iteritems():
647 listignored).iteritems():
646 if fn not in dmap:
648 if fn not in dmap:
647 if (listignored or match.exact(fn)) and self._dirignore(fn):
649 if (listignored or match.exact(fn)) and self._dirignore(fn):
648 if listignored:
650 if listignored:
649 iadd(fn)
651 iadd(fn)
650 elif listunknown:
652 elif listunknown:
651 uadd(fn)
653 uadd(fn)
652 continue
654 continue
653
655
654 state, mode, size, time = dmap[fn]
656 state, mode, size, time = dmap[fn]
655
657
656 if not st and state in "nma":
658 if not st and state in "nma":
657 dadd(fn)
659 dadd(fn)
658 elif state == 'n':
660 elif state == 'n':
659 # The "mode & lnkkind != lnkkind or self._checklink"
661 # The "mode & lnkkind != lnkkind or self._checklink"
660 # lines are an expansion of "islink => checklink"
662 # lines are an expansion of "islink => checklink"
661 # where islink means "is this a link?" and checklink
663 # where islink means "is this a link?" and checklink
662 # means "can we check links?".
664 # means "can we check links?".
663 if (size >= 0 and
665 if (size >= 0 and
664 (size != st.st_size
666 (size != st.st_size
665 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
667 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
666 and (mode & lnkkind != lnkkind or self._checklink)
668 and (mode & lnkkind != lnkkind or self._checklink)
667 or size == -2 # other parent
669 or size == -2 # other parent
668 or fn in self._copymap):
670 or fn in self._copymap):
669 madd(fn)
671 madd(fn)
670 elif (time != int(st.st_mtime)
672 elif (time != int(st.st_mtime)
671 and (mode & lnkkind != lnkkind or self._checklink)):
673 and (mode & lnkkind != lnkkind or self._checklink)):
672 ladd(fn)
674 ladd(fn)
673 elif listclean:
675 elif listclean:
674 cadd(fn)
676 cadd(fn)
675 elif state == 'm':
677 elif state == 'm':
676 madd(fn)
678 madd(fn)
677 elif state == 'a':
679 elif state == 'a':
678 aadd(fn)
680 aadd(fn)
679 elif state == 'r':
681 elif state == 'r':
680 radd(fn)
682 radd(fn)
681
683
682 return (lookup, modified, added, removed, deleted, unknown, ignored,
684 return (lookup, modified, added, removed, deleted, unknown, ignored,
683 clean)
685 clean)
@@ -1,161 +1,164 b''
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import errno, mimetypes, os
9 import errno, mimetypes, os
10
10
11 HTTP_OK = 200
11 HTTP_OK = 200
12 HTTP_NOT_MODIFIED = 304
12 HTTP_NOT_MODIFIED = 304
13 HTTP_BAD_REQUEST = 400
13 HTTP_BAD_REQUEST = 400
14 HTTP_UNAUTHORIZED = 401
14 HTTP_UNAUTHORIZED = 401
15 HTTP_FORBIDDEN = 403
15 HTTP_FORBIDDEN = 403
16 HTTP_NOT_FOUND = 404
16 HTTP_NOT_FOUND = 404
17 HTTP_METHOD_NOT_ALLOWED = 405
17 HTTP_METHOD_NOT_ALLOWED = 405
18 HTTP_SERVER_ERROR = 500
18 HTTP_SERVER_ERROR = 500
19
19
20 # Hooks for hgweb permission checks; extensions can add hooks here. Each hook
20 # Hooks for hgweb permission checks; extensions can add hooks here. Each hook
21 # is invoked like this: hook(hgweb, request, operation), where operation is
21 # is invoked like this: hook(hgweb, request, operation), where operation is
22 # either read, pull or push. Hooks should either raise an ErrorResponse
22 # either read, pull or push. Hooks should either raise an ErrorResponse
23 # exception, or just return.
23 # exception, or just return.
24 # It is possible to do both authentication and authorization through this.
24 # It is possible to do both authentication and authorization through this.
25 permhooks = []
25 permhooks = []
26
26
27 def checkauthz(hgweb, req, op):
27 def checkauthz(hgweb, req, op):
28 '''Check permission for operation based on request data (including
28 '''Check permission for operation based on request data (including
29 authentication info). Return if op allowed, else raise an ErrorResponse
29 authentication info). Return if op allowed, else raise an ErrorResponse
30 exception.'''
30 exception.'''
31
31
32 user = req.env.get('REMOTE_USER')
32 user = req.env.get('REMOTE_USER')
33
33
34 deny_read = hgweb.configlist('web', 'deny_read')
34 deny_read = hgweb.configlist('web', 'deny_read')
35 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
35 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
36 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
36 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
37
37
38 allow_read = hgweb.configlist('web', 'allow_read')
38 allow_read = hgweb.configlist('web', 'allow_read')
39 result = (not allow_read) or (allow_read == ['*'])
39 result = (not allow_read) or (allow_read == ['*'])
40 if not (result or user in allow_read):
40 if not (result or user in allow_read):
41 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
41 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
42
42
43 if op == 'pull' and not hgweb.allowpull:
43 if op == 'pull' and not hgweb.allowpull:
44 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
44 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
45 elif op == 'pull' or op is None: # op is None for interface requests
45 elif op == 'pull' or op is None: # op is None for interface requests
46 return
46 return
47
47
48 # enforce that you can only push using POST requests
48 # enforce that you can only push using POST requests
49 if req.env['REQUEST_METHOD'] != 'POST':
49 if req.env['REQUEST_METHOD'] != 'POST':
50 msg = 'push requires POST request'
50 msg = 'push requires POST request'
51 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
51 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
52
52
53 # require ssl by default for pushing, auth info cannot be sniffed
53 # require ssl by default for pushing, auth info cannot be sniffed
54 # and replayed
54 # and replayed
55 scheme = req.env.get('wsgi.url_scheme')
55 scheme = req.env.get('wsgi.url_scheme')
56 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
56 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
57 raise ErrorResponse(HTTP_OK, 'ssl required')
57 raise ErrorResponse(HTTP_OK, 'ssl required')
58
58
59 deny = hgweb.configlist('web', 'deny_push')
59 deny = hgweb.configlist('web', 'deny_push')
60 if deny and (not user or deny == ['*'] or user in deny):
60 if deny and (not user or deny == ['*'] or user in deny):
61 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
61 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
62
62
63 allow = hgweb.configlist('web', 'allow_push')
63 allow = hgweb.configlist('web', 'allow_push')
64 result = allow and (allow == ['*'] or user in allow)
64 result = allow and (allow == ['*'] or user in allow)
65 if not result:
65 if not result:
66 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
66 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
67
67
68 # Add the default permhook, which provides simple authorization.
68 # Add the default permhook, which provides simple authorization.
69 permhooks.append(checkauthz)
69 permhooks.append(checkauthz)
70
70
71
71
72 class ErrorResponse(Exception):
72 class ErrorResponse(Exception):
73 def __init__(self, code, message=None, headers=[]):
73 def __init__(self, code, message=None, headers=[]):
74 Exception.__init__(self)
74 Exception.__init__(self)
75 self.code = code
75 self.code = code
76 self.headers = headers
76 self.headers = headers
77 if message is not None:
77 if message is not None:
78 self.message = message
78 self.message = message
79 else:
79 else:
80 self.message = _statusmessage(code)
80 self.message = _statusmessage(code)
81
81
82 def _statusmessage(code):
82 def _statusmessage(code):
83 from BaseHTTPServer import BaseHTTPRequestHandler
83 from BaseHTTPServer import BaseHTTPRequestHandler
84 responses = BaseHTTPRequestHandler.responses
84 responses = BaseHTTPRequestHandler.responses
85 return responses.get(code, ('Error', 'Unknown error'))[0]
85 return responses.get(code, ('Error', 'Unknown error'))[0]
86
86
87 def statusmessage(code, message=None):
87 def statusmessage(code, message=None):
88 return '%d %s' % (code, message or _statusmessage(code))
88 return '%d %s' % (code, message or _statusmessage(code))
89
89
90 def get_mtime(spath):
90 def get_mtime(spath):
91 cl_path = os.path.join(spath, "00changelog.i")
91 cl_path = os.path.join(spath, "00changelog.i")
92 if os.path.exists(cl_path):
92 if os.path.exists(cl_path):
93 return os.stat(cl_path).st_mtime
93 return os.stat(cl_path).st_mtime
94 else:
94 else:
95 return os.stat(spath).st_mtime
95 return os.stat(spath).st_mtime
96
96
97 def staticfile(directory, fname, req):
97 def staticfile(directory, fname, req):
98 """return a file inside directory with guessed Content-Type header
98 """return a file inside directory with guessed Content-Type header
99
99
100 fname always uses '/' as directory separator and isn't allowed to
100 fname always uses '/' as directory separator and isn't allowed to
101 contain unusual path components.
101 contain unusual path components.
102 Content-Type is guessed using the mimetypes module.
102 Content-Type is guessed using the mimetypes module.
103 Return an empty string if fname is illegal or file not found.
103 Return an empty string if fname is illegal or file not found.
104
104
105 """
105 """
106 parts = fname.split('/')
106 parts = fname.split('/')
107 for part in parts:
107 for part in parts:
108 if (part in ('', os.curdir, os.pardir) or
108 if (part in ('', os.curdir, os.pardir) or
109 os.sep in part or os.altsep is not None and os.altsep in part):
109 os.sep in part or os.altsep is not None and os.altsep in part):
110 return ""
110 return ""
111 fpath = os.path.join(*parts)
111 fpath = os.path.join(*parts)
112 if isinstance(directory, str):
112 if isinstance(directory, str):
113 directory = [directory]
113 directory = [directory]
114 for d in directory:
114 for d in directory:
115 path = os.path.join(d, fpath)
115 path = os.path.join(d, fpath)
116 if os.path.exists(path):
116 if os.path.exists(path):
117 break
117 break
118 try:
118 try:
119 os.stat(path)
119 os.stat(path)
120 ct = mimetypes.guess_type(path)[0] or "text/plain"
120 ct = mimetypes.guess_type(path)[0] or "text/plain"
121 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
121 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
122 return open(path, 'rb').read()
122 fp = open(path, 'rb')
123 data = fp.read()
124 fp.close()
125 return data
123 except TypeError:
126 except TypeError:
124 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
127 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
125 except OSError, err:
128 except OSError, err:
126 if err.errno == errno.ENOENT:
129 if err.errno == errno.ENOENT:
127 raise ErrorResponse(HTTP_NOT_FOUND)
130 raise ErrorResponse(HTTP_NOT_FOUND)
128 else:
131 else:
129 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
132 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
130
133
131 def paritygen(stripecount, offset=0):
134 def paritygen(stripecount, offset=0):
132 """count parity of horizontal stripes for easier reading"""
135 """count parity of horizontal stripes for easier reading"""
133 if stripecount and offset:
136 if stripecount and offset:
134 # account for offset, e.g. due to building the list in reverse
137 # account for offset, e.g. due to building the list in reverse
135 count = (stripecount + offset) % stripecount
138 count = (stripecount + offset) % stripecount
136 parity = (stripecount + offset) / stripecount & 1
139 parity = (stripecount + offset) / stripecount & 1
137 else:
140 else:
138 count = 0
141 count = 0
139 parity = 0
142 parity = 0
140 while True:
143 while True:
141 yield parity
144 yield parity
142 count += 1
145 count += 1
143 if stripecount and count >= stripecount:
146 if stripecount and count >= stripecount:
144 parity = 1 - parity
147 parity = 1 - parity
145 count = 0
148 count = 0
146
149
147 def get_contact(config):
150 def get_contact(config):
148 """Return repo contact information or empty string.
151 """Return repo contact information or empty string.
149
152
150 web.contact is the primary source, but if that is not set, try
153 web.contact is the primary source, but if that is not set, try
151 ui.username or $EMAIL as a fallback to display something useful.
154 ui.username or $EMAIL as a fallback to display something useful.
152 """
155 """
153 return (config("web", "contact") or
156 return (config("web", "contact") or
154 config("ui", "username") or
157 config("ui", "username") or
155 os.environ.get("EMAIL") or "")
158 os.environ.get("EMAIL") or "")
156
159
157 def caching(web, req):
160 def caching(web, req):
158 tag = str(web.mtime)
161 tag = str(web.mtime)
159 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
162 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
160 raise ErrorResponse(HTTP_NOT_MODIFIED)
163 raise ErrorResponse(HTTP_NOT_MODIFIED)
161 req.headers.append(('ETag', tag))
164 req.headers.append(('ETag', tag))
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