##// END OF EJS Templates
fix up a bunch of check-code warnings
Matt Mackall -
r10413:e433002a default
parent child Browse files
Show More
@@ -1,1114 +1,1114 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 # $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $
3 # $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $
4 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
4 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
5 # Copyright: This module is put into the public domain.
5 # Copyright: This module is put into the public domain.
6
6
7 """
7 """
8 Simple man page writer for reStructuredText.
8 Simple man page writer for reStructuredText.
9
9
10 Man pages (short for "manual pages") contain system documentation on unix-like
10 Man pages (short for "manual pages") contain system documentation on unix-like
11 systems. The pages are grouped in numbered sections:
11 systems. The pages are grouped in numbered sections:
12
12
13 1 executable programs and shell commands
13 1 executable programs and shell commands
14 2 system calls
14 2 system calls
15 3 library functions
15 3 library functions
16 4 special files
16 4 special files
17 5 file formats
17 5 file formats
18 6 games
18 6 games
19 7 miscellaneous
19 7 miscellaneous
20 8 system administration
20 8 system administration
21
21
22 Man pages are written *troff*, a text file formatting system.
22 Man pages are written *troff*, a text file formatting system.
23
23
24 See http://www.tldp.org/HOWTO/Man-Page for a start.
24 See http://www.tldp.org/HOWTO/Man-Page for a start.
25
25
26 Man pages have no subsection only parts.
26 Man pages have no subsection only parts.
27 Standard parts
27 Standard parts
28
28
29 NAME ,
29 NAME ,
30 SYNOPSIS ,
30 SYNOPSIS ,
31 DESCRIPTION ,
31 DESCRIPTION ,
32 OPTIONS ,
32 OPTIONS ,
33 FILES ,
33 FILES ,
34 SEE ALSO ,
34 SEE ALSO ,
35 BUGS ,
35 BUGS ,
36
36
37 and
37 and
38
38
39 AUTHOR .
39 AUTHOR .
40
40
41 A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
41 A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
42 by the command whatis or apropos.
42 by the command whatis or apropos.
43
43
44 """
44 """
45
45
46 __docformat__ = 'reStructuredText'
46 __docformat__ = 'reStructuredText'
47
47
48 import sys
48 import sys
49 import os
49 import os
50 import time
50 import time
51 import re
51 import re
52 from types import ListType
52 from types import ListType
53
53
54 import docutils
54 import docutils
55 from docutils import nodes, utils, writers, languages
55 from docutils import nodes, utils, writers, languages
56 import roman
56 import roman
57
57
58 FIELD_LIST_INDENT = 7
58 FIELD_LIST_INDENT = 7
59 DEFINITION_LIST_INDENT = 7
59 DEFINITION_LIST_INDENT = 7
60 OPTION_LIST_INDENT = 7
60 OPTION_LIST_INDENT = 7
61 BLOCKQOUTE_INDENT = 3.5
61 BLOCKQOUTE_INDENT = 3.5
62
62
63 # Define two macros so man/roff can calculate the
63 # Define two macros so man/roff can calculate the
64 # indent/unindent margins by itself
64 # indent/unindent margins by itself
65 MACRO_DEF = (r""".
65 MACRO_DEF = (r""".
66 .nr rst2man-indent-level 0
66 .nr rst2man-indent-level 0
67 .
67 .
68 .de1 rstReportMargin
68 .de1 rstReportMargin
69 \\$1 \\n[an-margin]
69 \\$1 \\n[an-margin]
70 level \\n[rst2man-indent-level]
70 level \\n[rst2man-indent-level]
71 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
71 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
72 -
72 -
73 \\n[rst2man-indent0]
73 \\n[rst2man-indent0]
74 \\n[rst2man-indent1]
74 \\n[rst2man-indent1]
75 \\n[rst2man-indent2]
75 \\n[rst2man-indent2]
76 ..
76 ..
77 .de1 INDENT
77 .de1 INDENT
78 .\" .rstReportMargin pre:
78 .\" .rstReportMargin pre:
79 . RS \\$1
79 . RS \\$1
80 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
80 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
81 . nr rst2man-indent-level +1
81 . nr rst2man-indent-level +1
82 .\" .rstReportMargin post:
82 .\" .rstReportMargin post:
83 ..
83 ..
84 .de UNINDENT
84 .de UNINDENT
85 . RE
85 . RE
86 .\" indent \\n[an-margin]
86 .\" indent \\n[an-margin]
87 .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
87 .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
88 .nr rst2man-indent-level -1
88 .nr rst2man-indent-level -1
89 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
89 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
90 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
90 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
91 ..
91 ..
92 """)
92 """)
93
93
94 class Writer(writers.Writer):
94 class Writer(writers.Writer):
95
95
96 supported = ('manpage')
96 supported = ('manpage')
97 """Formats this writer supports."""
97 """Formats this writer supports."""
98
98
99 output = None
99 output = None
100 """Final translated form of `document`."""
100 """Final translated form of `document`."""
101
101
102 def __init__(self):
102 def __init__(self):
103 writers.Writer.__init__(self)
103 writers.Writer.__init__(self)
104 self.translator_class = Translator
104 self.translator_class = Translator
105
105
106 def translate(self):
106 def translate(self):
107 visitor = self.translator_class(self.document)
107 visitor = self.translator_class(self.document)
108 self.document.walkabout(visitor)
108 self.document.walkabout(visitor)
109 self.output = visitor.astext()
109 self.output = visitor.astext()
110
110
111
111
112 class Table:
112 class Table:
113 def __init__(self):
113 def __init__(self):
114 self._rows = []
114 self._rows = []
115 self._options = ['center',]
115 self._options = ['center']
116 self._tab_char = '\t'
116 self._tab_char = '\t'
117 self._coldefs = []
117 self._coldefs = []
118 def new_row(self):
118 def new_row(self):
119 self._rows.append([])
119 self._rows.append([])
120 def append_separator(self, separator):
120 def append_separator(self, separator):
121 """Append the separator for table head."""
121 """Append the separator for table head."""
122 self._rows.append([separator])
122 self._rows.append([separator])
123 def append_cell(self, cell_lines):
123 def append_cell(self, cell_lines):
124 """cell_lines is an array of lines"""
124 """cell_lines is an array of lines"""
125 start = 0
125 start = 0
126 if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
126 if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
127 start = 1
127 start = 1
128 self._rows[-1].append(cell_lines[start:])
128 self._rows[-1].append(cell_lines[start:])
129 if len(self._coldefs) < len(self._rows[-1]):
129 if len(self._coldefs) < len(self._rows[-1]):
130 self._coldefs.append('l')
130 self._coldefs.append('l')
131 def _minimize_cell(self, cell_lines):
131 def _minimize_cell(self, cell_lines):
132 """Remove leading and trailing blank and ``.sp`` lines"""
132 """Remove leading and trailing blank and ``.sp`` lines"""
133 while (cell_lines and cell_lines[0] in ('\n', '.sp\n')):
133 while (cell_lines and cell_lines[0] in ('\n', '.sp\n')):
134 del cell_lines[0]
134 del cell_lines[0]
135 while (cell_lines and cell_lines[-1] in ('\n', '.sp\n')):
135 while (cell_lines and cell_lines[-1] in ('\n', '.sp\n')):
136 del cell_lines[-1]
136 del cell_lines[-1]
137 def as_list(self):
137 def as_list(self):
138 text = ['.TS\n']
138 text = ['.TS\n']
139 text.append(' '.join(self._options) + ';\n')
139 text.append(' '.join(self._options) + ';\n')
140 text.append('|%s|.\n' % ('|'.join(self._coldefs)))
140 text.append('|%s|.\n' % ('|'.join(self._coldefs)))
141 for row in self._rows:
141 for row in self._rows:
142 # row = array of cells. cell = array of lines.
142 # row = array of cells. cell = array of lines.
143 text.append('_\n') # line above
143 text.append('_\n') # line above
144 text.append('T{\n')
144 text.append('T{\n')
145 for i in range(len(row)):
145 for i in range(len(row)):
146 cell = row[i]
146 cell = row[i]
147 self._minimize_cell(cell)
147 self._minimize_cell(cell)
148 text.extend(cell)
148 text.extend(cell)
149 if not text[-1].endswith('\n'):
149 if not text[-1].endswith('\n'):
150 text[-1] += '\n'
150 text[-1] += '\n'
151 if i < len(row)-1:
151 if i < len(row)-1:
152 text.append('T}'+self._tab_char+'T{\n')
152 text.append('T}'+self._tab_char+'T{\n')
153 else:
153 else:
154 text.append('T}\n')
154 text.append('T}\n')
155 text.append('_\n')
155 text.append('_\n')
156 text.append('.TE\n')
156 text.append('.TE\n')
157 return text
157 return text
158
158
159 class Translator(nodes.NodeVisitor):
159 class Translator(nodes.NodeVisitor):
160 """"""
160 """"""
161
161
162 words_and_spaces = re.compile(r'\S+| +|\n')
162 words_and_spaces = re.compile(r'\S+| +|\n')
163 document_start = """Man page generated from reStructeredText."""
163 document_start = """Man page generated from reStructeredText."""
164
164
165 def __init__(self, document):
165 def __init__(self, document):
166 nodes.NodeVisitor.__init__(self, document)
166 nodes.NodeVisitor.__init__(self, document)
167 self.settings = settings = document.settings
167 self.settings = settings = document.settings
168 lcode = settings.language_code
168 lcode = settings.language_code
169 self.language = languages.get_language(lcode)
169 self.language = languages.get_language(lcode)
170 self.head = []
170 self.head = []
171 self.body = []
171 self.body = []
172 self.foot = []
172 self.foot = []
173 self.section_level = 0
173 self.section_level = 0
174 self.context = []
174 self.context = []
175 self.topic_class = ''
175 self.topic_class = ''
176 self.colspecs = []
176 self.colspecs = []
177 self.compact_p = 1
177 self.compact_p = 1
178 self.compact_simple = None
178 self.compact_simple = None
179 # the list style "*" bullet or "#" numbered
179 # the list style "*" bullet or "#" numbered
180 self._list_char = []
180 self._list_char = []
181 # writing the header .TH and .SH NAME is postboned after
181 # writing the header .TH and .SH NAME is postboned after
182 # docinfo.
182 # docinfo.
183 self._docinfo = {
183 self._docinfo = {
184 "title" : "", "title_upper": "",
184 "title" : "", "title_upper": "",
185 "subtitle" : "",
185 "subtitle" : "",
186 "manual_section" : "", "manual_group" : "",
186 "manual_section" : "", "manual_group" : "",
187 "author" : [],
187 "author" : [],
188 "date" : "",
188 "date" : "",
189 "copyright" : "",
189 "copyright" : "",
190 "version" : "",
190 "version" : "",
191 }
191 }
192 self._docinfo_keys = [] # a list to keep the sequence as in source.
192 self._docinfo_keys = [] # a list to keep the sequence as in source.
193 self._docinfo_names = {} # to get name from text not normalized.
193 self._docinfo_names = {} # to get name from text not normalized.
194 self._in_docinfo = None
194 self._in_docinfo = None
195 self._active_table = None
195 self._active_table = None
196 self._in_literal = False
196 self._in_literal = False
197 self.header_written = 0
197 self.header_written = 0
198 self._line_block = 0
198 self._line_block = 0
199 self.authors = []
199 self.authors = []
200 self.section_level = 0
200 self.section_level = 0
201 self._indent = [0]
201 self._indent = [0]
202 # central definition of simple processing rules
202 # central definition of simple processing rules
203 # what to output on : visit, depart
203 # what to output on : visit, depart
204 # Do not use paragraph requests ``.PP`` because these set indentation.
204 # Do not use paragraph requests ``.PP`` because these set indentation.
205 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
205 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
206 #
206 #
207 # Fonts are put on a stack, the top one is used.
207 # Fonts are put on a stack, the top one is used.
208 # ``.ft P`` or ``\\fP`` pop from stack.
208 # ``.ft P`` or ``\\fP`` pop from stack.
209 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
209 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
210 # Hopefully ``C`` courier too.
210 # Hopefully ``C`` courier too.
211 self.defs = {
211 self.defs = {
212 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'),
212 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'),
213 'definition_list_item' : ('.TP', ''),
213 'definition_list_item' : ('.TP', ''),
214 'field_name' : ('.TP\n.B ', '\n'),
214 'field_name' : ('.TP\n.B ', '\n'),
215 'literal' : ('\\fB', '\\fP'),
215 'literal' : ('\\fB', '\\fP'),
216 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
216 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
217
217
218 'option_list_item' : ('.TP\n', ''),
218 'option_list_item' : ('.TP\n', ''),
219
219
220 'reference' : (r'\%', r'\:'),
220 'reference' : (r'\%', r'\:'),
221 'emphasis': ('\\fI', '\\fP'),
221 'emphasis': ('\\fI', '\\fP'),
222 'strong' : ('\\fB', '\\fP'),
222 'strong' : ('\\fB', '\\fP'),
223 'term' : ('\n.B ', '\n'),
223 'term' : ('\n.B ', '\n'),
224 'title_reference' : ('\\fI', '\\fP'),
224 'title_reference' : ('\\fI', '\\fP'),
225
225
226 'topic-title' : ('.SS ',),
226 'topic-title' : ('.SS ',),
227 'sidebar-title' : ('.SS ',),
227 'sidebar-title' : ('.SS ',),
228
228
229 'problematic' : ('\n.nf\n', '\n.fi\n'),
229 'problematic' : ('\n.nf\n', '\n.fi\n'),
230 }
230 }
231 # NOTE don't specify the newline before a dot-command, but ensure
231 # NOTE don't specify the newline before a dot-command, but ensure
232 # it is there.
232 # it is there.
233
233
234 def comment_begin(self, text):
234 def comment_begin(self, text):
235 """Return commented version of the passed text WITHOUT end of
235 """Return commented version of the passed text WITHOUT end of
236 line/comment."""
236 line/comment."""
237 prefix = '.\\" '
237 prefix = '.\\" '
238 out_text = ''.join(
238 out_text = ''.join(
239 [(prefix + in_line + '\n')
239 [(prefix + in_line + '\n')
240 for in_line in text.split('\n')])
240 for in_line in text.split('\n')])
241 return out_text
241 return out_text
242
242
243 def comment(self, text):
243 def comment(self, text):
244 """Return commented version of the passed text."""
244 """Return commented version of the passed text."""
245 return self.comment_begin(text)+'.\n'
245 return self.comment_begin(text)+'.\n'
246
246
247 def ensure_eol(self):
247 def ensure_eol(self):
248 """Ensure the last line in body is terminated by new line."""
248 """Ensure the last line in body is terminated by new line."""
249 if self.body[-1][-1] != '\n':
249 if self.body[-1][-1] != '\n':
250 self.body.append('\n')
250 self.body.append('\n')
251
251
252 def astext(self):
252 def astext(self):
253 """Return the final formatted document as a string."""
253 """Return the final formatted document as a string."""
254 if not self.header_written:
254 if not self.header_written:
255 # ensure we get a ".TH" as viewers require it.
255 # ensure we get a ".TH" as viewers require it.
256 self.head.append(self.header())
256 self.head.append(self.header())
257 # filter body
257 # filter body
258 for i in xrange(len(self.body)-1, 0, -1):
258 for i in xrange(len(self.body)-1, 0, -1):
259 # remove superfluous vertical gaps.
259 # remove superfluous vertical gaps.
260 if self.body[i] == '.sp\n':
260 if self.body[i] == '.sp\n':
261 if self.body[i - 1][:4] in ('.BI ','.IP '):
261 if self.body[i - 1][:4] in ('.BI ','.IP '):
262 self.body[i] = '.\n'
262 self.body[i] = '.\n'
263 elif (self.body[i - 1][:3] == '.B ' and
263 elif (self.body[i - 1][:3] == '.B ' and
264 self.body[i - 2][:4] == '.TP\n'):
264 self.body[i - 2][:4] == '.TP\n'):
265 self.body[i] = '.\n'
265 self.body[i] = '.\n'
266 elif (self.body[i - 1] == '\n' and
266 elif (self.body[i - 1] == '\n' and
267 self.body[i - 2][0] != '.' and
267 self.body[i - 2][0] != '.' and
268 (self.body[i - 3][:7] == '.TP\n.B '
268 (self.body[i - 3][:7] == '.TP\n.B '
269 or self.body[i - 3][:4] == '\n.B ')
269 or self.body[i - 3][:4] == '\n.B ')
270 ):
270 ):
271 self.body[i] = '.\n'
271 self.body[i] = '.\n'
272 return ''.join(self.head + self.body + self.foot)
272 return ''.join(self.head + self.body + self.foot)
273
273
274 def deunicode(self, text):
274 def deunicode(self, text):
275 text = text.replace(u'\xa0', '\\ ')
275 text = text.replace(u'\xa0', '\\ ')
276 text = text.replace(u'\u2020', '\\(dg')
276 text = text.replace(u'\u2020', '\\(dg')
277 return text
277 return text
278
278
279 def visit_Text(self, node):
279 def visit_Text(self, node):
280 text = node.astext()
280 text = node.astext()
281 text = text.replace('\\','\\e')
281 text = text.replace('\\','\\e')
282 replace_pairs = [
282 replace_pairs = [
283 (u'-', ur'\-'),
283 (u'-', ur'\-'),
284 (u'\'', ur'\(aq'),
284 (u'\'', ur'\(aq'),
285 (u'Β΄', ur'\''),
285 (u'Β΄', ur'\''),
286 (u'`', ur'\(ga'),
286 (u'`', ur'\(ga'),
287 ]
287 ]
288 for (in_char, out_markup) in replace_pairs:
288 for (in_char, out_markup) in replace_pairs:
289 text = text.replace(in_char, out_markup)
289 text = text.replace(in_char, out_markup)
290 # unicode
290 # unicode
291 text = self.deunicode(text)
291 text = self.deunicode(text)
292 if self._in_literal:
292 if self._in_literal:
293 # prevent interpretation of "." at line start
293 # prevent interpretation of "." at line start
294 if text[0] == '.':
294 if text[0] == '.':
295 text = '\\&' + text
295 text = '\\&' + text
296 text = text.replace('\n.', '\n\\&.')
296 text = text.replace('\n.', '\n\\&.')
297 self.body.append(text)
297 self.body.append(text)
298
298
299 def depart_Text(self, node):
299 def depart_Text(self, node):
300 pass
300 pass
301
301
302 def list_start(self, node):
302 def list_start(self, node):
303 class enum_char:
303 class enum_char:
304 enum_style = {
304 enum_style = {
305 'bullet' : '\\(bu',
305 'bullet' : '\\(bu',
306 'emdash' : '\\(em',
306 'emdash' : '\\(em',
307 }
307 }
308
308
309 def __init__(self, style):
309 def __init__(self, style):
310 self._style = style
310 self._style = style
311 if node.has_key('start'):
311 if node.has_key('start'):
312 self._cnt = node['start'] - 1
312 self._cnt = node['start'] - 1
313 else:
313 else:
314 self._cnt = 0
314 self._cnt = 0
315 self._indent = 2
315 self._indent = 2
316 if style == 'arabic':
316 if style == 'arabic':
317 # indentation depends on number of childrens
317 # indentation depends on number of childrens
318 # and start value.
318 # and start value.
319 self._indent = len(str(len(node.children)))
319 self._indent = len(str(len(node.children)))
320 self._indent += len(str(self._cnt)) + 1
320 self._indent += len(str(self._cnt)) + 1
321 elif style == 'loweralpha':
321 elif style == 'loweralpha':
322 self._cnt += ord('a') - 1
322 self._cnt += ord('a') - 1
323 self._indent = 3
323 self._indent = 3
324 elif style == 'upperalpha':
324 elif style == 'upperalpha':
325 self._cnt += ord('A') - 1
325 self._cnt += ord('A') - 1
326 self._indent = 3
326 self._indent = 3
327 elif style.endswith('roman'):
327 elif style.endswith('roman'):
328 self._indent = 5
328 self._indent = 5
329
329
330 def next(self):
330 def next(self):
331 if self._style == 'bullet':
331 if self._style == 'bullet':
332 return self.enum_style[self._style]
332 return self.enum_style[self._style]
333 elif self._style == 'emdash':
333 elif self._style == 'emdash':
334 return self.enum_style[self._style]
334 return self.enum_style[self._style]
335 self._cnt += 1
335 self._cnt += 1
336 # TODO add prefix postfix
336 # TODO add prefix postfix
337 if self._style == 'arabic':
337 if self._style == 'arabic':
338 return "%d." % self._cnt
338 return "%d." % self._cnt
339 elif self._style in ('loweralpha', 'upperalpha'):
339 elif self._style in ('loweralpha', 'upperalpha'):
340 return "%c." % self._cnt
340 return "%c." % self._cnt
341 elif self._style.endswith('roman'):
341 elif self._style.endswith('roman'):
342 res = roman.toRoman(self._cnt) + '.'
342 res = roman.toRoman(self._cnt) + '.'
343 if self._style.startswith('upper'):
343 if self._style.startswith('upper'):
344 return res.upper()
344 return res.upper()
345 return res.lower()
345 return res.lower()
346 else:
346 else:
347 return "%d." % self._cnt
347 return "%d." % self._cnt
348 def get_width(self):
348 def get_width(self):
349 return self._indent
349 return self._indent
350 def __repr__(self):
350 def __repr__(self):
351 return 'enum_style-%s' % list(self._style)
351 return 'enum_style-%s' % list(self._style)
352
352
353 if node.has_key('enumtype'):
353 if node.has_key('enumtype'):
354 self._list_char.append(enum_char(node['enumtype']))
354 self._list_char.append(enum_char(node['enumtype']))
355 else:
355 else:
356 self._list_char.append(enum_char('bullet'))
356 self._list_char.append(enum_char('bullet'))
357 if len(self._list_char) > 1:
357 if len(self._list_char) > 1:
358 # indent nested lists
358 # indent nested lists
359 self.indent(self._list_char[-2].get_width())
359 self.indent(self._list_char[-2].get_width())
360 else:
360 else:
361 self.indent(self._list_char[-1].get_width())
361 self.indent(self._list_char[-1].get_width())
362
362
363 def list_end(self):
363 def list_end(self):
364 self.dedent()
364 self.dedent()
365 self._list_char.pop()
365 self._list_char.pop()
366
366
367 def header(self):
367 def header(self):
368 tmpl = (".TH %(title_upper)s %(manual_section)s"
368 tmpl = (".TH %(title_upper)s %(manual_section)s"
369 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
369 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
370 ".SH NAME\n"
370 ".SH NAME\n"
371 "%(title)s \- %(subtitle)s\n")
371 "%(title)s \- %(subtitle)s\n")
372 return tmpl % self._docinfo
372 return tmpl % self._docinfo
373
373
374 def append_header(self):
374 def append_header(self):
375 """append header with .TH and .SH NAME"""
375 """append header with .TH and .SH NAME"""
376 # NOTE before everything
376 # NOTE before everything
377 # .TH title_upper section date source manual
377 # .TH title_upper section date source manual
378 if self.header_written:
378 if self.header_written:
379 return
379 return
380 self.body.append(self.header())
380 self.body.append(self.header())
381 self.body.append(MACRO_DEF)
381 self.body.append(MACRO_DEF)
382 self.header_written = 1
382 self.header_written = 1
383
383
384 def visit_address(self, node):
384 def visit_address(self, node):
385 self.visit_docinfo_item(node, 'address')
385 self.visit_docinfo_item(node, 'address')
386
386
387 def depart_address(self, node):
387 def depart_address(self, node):
388 pass
388 pass
389
389
390 def visit_admonition(self, node, name=None):
390 def visit_admonition(self, node, name=None):
391 if name:
391 if name:
392 self.body.append('.IP %s\n' %
392 self.body.append('.IP %s\n' %
393 self.language.labels.get(name, name))
393 self.language.labels.get(name, name))
394
394
395 def depart_admonition(self, node):
395 def depart_admonition(self, node):
396 self.body.append('.RE\n')
396 self.body.append('.RE\n')
397
397
398 def visit_attention(self, node):
398 def visit_attention(self, node):
399 self.visit_admonition(node, 'attention')
399 self.visit_admonition(node, 'attention')
400
400
401 depart_attention = depart_admonition
401 depart_attention = depart_admonition
402
402
403 def visit_docinfo_item(self, node, name):
403 def visit_docinfo_item(self, node, name):
404 if name == 'author':
404 if name == 'author':
405 self._docinfo[name].append(node.astext())
405 self._docinfo[name].append(node.astext())
406 else:
406 else:
407 self._docinfo[name] = node.astext()
407 self._docinfo[name] = node.astext()
408 self._docinfo_keys.append(name)
408 self._docinfo_keys.append(name)
409 raise nodes.SkipNode
409 raise nodes.SkipNode
410
410
411 def depart_docinfo_item(self, node):
411 def depart_docinfo_item(self, node):
412 pass
412 pass
413
413
414 def visit_author(self, node):
414 def visit_author(self, node):
415 self.visit_docinfo_item(node, 'author')
415 self.visit_docinfo_item(node, 'author')
416
416
417 depart_author = depart_docinfo_item
417 depart_author = depart_docinfo_item
418
418
419 def visit_authors(self, node):
419 def visit_authors(self, node):
420 # _author is called anyway.
420 # _author is called anyway.
421 pass
421 pass
422
422
423 def depart_authors(self, node):
423 def depart_authors(self, node):
424 pass
424 pass
425
425
426 def visit_block_quote(self, node):
426 def visit_block_quote(self, node):
427 # BUG/HACK: indent alway uses the _last_ indention,
427 # BUG/HACK: indent alway uses the _last_ indention,
428 # thus we need two of them.
428 # thus we need two of them.
429 self.indent(BLOCKQOUTE_INDENT)
429 self.indent(BLOCKQOUTE_INDENT)
430 self.indent(0)
430 self.indent(0)
431
431
432 def depart_block_quote(self, node):
432 def depart_block_quote(self, node):
433 self.dedent()
433 self.dedent()
434 self.dedent()
434 self.dedent()
435
435
436 def visit_bullet_list(self, node):
436 def visit_bullet_list(self, node):
437 self.list_start(node)
437 self.list_start(node)
438
438
439 def depart_bullet_list(self, node):
439 def depart_bullet_list(self, node):
440 self.list_end()
440 self.list_end()
441
441
442 def visit_caption(self, node):
442 def visit_caption(self, node):
443 pass
443 pass
444
444
445 def depart_caption(self, node):
445 def depart_caption(self, node):
446 pass
446 pass
447
447
448 def visit_caution(self, node):
448 def visit_caution(self, node):
449 self.visit_admonition(node, 'caution')
449 self.visit_admonition(node, 'caution')
450
450
451 depart_caution = depart_admonition
451 depart_caution = depart_admonition
452
452
453 def visit_citation(self, node):
453 def visit_citation(self, node):
454 num, text = node.astext().split(None, 1)
454 num, text = node.astext().split(None, 1)
455 num = num.strip()
455 num = num.strip()
456 self.body.append('.IP [%s] 5\n' % num)
456 self.body.append('.IP [%s] 5\n' % num)
457
457
458 def depart_citation(self, node):
458 def depart_citation(self, node):
459 pass
459 pass
460
460
461 def visit_citation_reference(self, node):
461 def visit_citation_reference(self, node):
462 self.body.append('['+node.astext()+']')
462 self.body.append('['+node.astext()+']')
463 raise nodes.SkipNode
463 raise nodes.SkipNode
464
464
465 def visit_classifier(self, node):
465 def visit_classifier(self, node):
466 pass
466 pass
467
467
468 def depart_classifier(self, node):
468 def depart_classifier(self, node):
469 pass
469 pass
470
470
471 def visit_colspec(self, node):
471 def visit_colspec(self, node):
472 self.colspecs.append(node)
472 self.colspecs.append(node)
473
473
474 def depart_colspec(self, node):
474 def depart_colspec(self, node):
475 pass
475 pass
476
476
477 def write_colspecs(self):
477 def write_colspecs(self):
478 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
478 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
479
479
480 def visit_comment(self, node,
480 def visit_comment(self, node,
481 sub=re.compile('-(?=-)').sub):
481 sub=re.compile('-(?=-)').sub):
482 self.body.append(self.comment(node.astext()))
482 self.body.append(self.comment(node.astext()))
483 raise nodes.SkipNode
483 raise nodes.SkipNode
484
484
485 def visit_contact(self, node):
485 def visit_contact(self, node):
486 self.visit_docinfo_item(node, 'contact')
486 self.visit_docinfo_item(node, 'contact')
487
487
488 depart_contact = depart_docinfo_item
488 depart_contact = depart_docinfo_item
489
489
490 def visit_container(self, node):
490 def visit_container(self, node):
491 pass
491 pass
492
492
493 def depart_container(self, node):
493 def depart_container(self, node):
494 pass
494 pass
495
495
496 def visit_compound(self, node):
496 def visit_compound(self, node):
497 pass
497 pass
498
498
499 def depart_compound(self, node):
499 def depart_compound(self, node):
500 pass
500 pass
501
501
502 def visit_copyright(self, node):
502 def visit_copyright(self, node):
503 self.visit_docinfo_item(node, 'copyright')
503 self.visit_docinfo_item(node, 'copyright')
504
504
505 def visit_danger(self, node):
505 def visit_danger(self, node):
506 self.visit_admonition(node, 'danger')
506 self.visit_admonition(node, 'danger')
507
507
508 depart_danger = depart_admonition
508 depart_danger = depart_admonition
509
509
510 def visit_date(self, node):
510 def visit_date(self, node):
511 self.visit_docinfo_item(node, 'date')
511 self.visit_docinfo_item(node, 'date')
512
512
513 def visit_decoration(self, node):
513 def visit_decoration(self, node):
514 pass
514 pass
515
515
516 def depart_decoration(self, node):
516 def depart_decoration(self, node):
517 pass
517 pass
518
518
519 def visit_definition(self, node):
519 def visit_definition(self, node):
520 pass
520 pass
521
521
522 def depart_definition(self, node):
522 def depart_definition(self, node):
523 pass
523 pass
524
524
525 def visit_definition_list(self, node):
525 def visit_definition_list(self, node):
526 self.indent(DEFINITION_LIST_INDENT)
526 self.indent(DEFINITION_LIST_INDENT)
527
527
528 def depart_definition_list(self, node):
528 def depart_definition_list(self, node):
529 self.dedent()
529 self.dedent()
530
530
531 def visit_definition_list_item(self, node):
531 def visit_definition_list_item(self, node):
532 self.body.append(self.defs['definition_list_item'][0])
532 self.body.append(self.defs['definition_list_item'][0])
533
533
534 def depart_definition_list_item(self, node):
534 def depart_definition_list_item(self, node):
535 self.body.append(self.defs['definition_list_item'][1])
535 self.body.append(self.defs['definition_list_item'][1])
536
536
537 def visit_description(self, node):
537 def visit_description(self, node):
538 pass
538 pass
539
539
540 def depart_description(self, node):
540 def depart_description(self, node):
541 pass
541 pass
542
542
543 def visit_docinfo(self, node):
543 def visit_docinfo(self, node):
544 self._in_docinfo = 1
544 self._in_docinfo = 1
545
545
546 def depart_docinfo(self, node):
546 def depart_docinfo(self, node):
547 self._in_docinfo = None
547 self._in_docinfo = None
548 # NOTE nothing should be written before this
548 # NOTE nothing should be written before this
549 self.append_header()
549 self.append_header()
550
550
551 def visit_doctest_block(self, node):
551 def visit_doctest_block(self, node):
552 self.body.append(self.defs['literal_block'][0])
552 self.body.append(self.defs['literal_block'][0])
553 self._in_literal = True
553 self._in_literal = True
554
554
555 def depart_doctest_block(self, node):
555 def depart_doctest_block(self, node):
556 self._in_literal = False
556 self._in_literal = False
557 self.body.append(self.defs['literal_block'][1])
557 self.body.append(self.defs['literal_block'][1])
558
558
559 def visit_document(self, node):
559 def visit_document(self, node):
560 # no blank line between comment and header.
560 # no blank line between comment and header.
561 self.body.append(self.comment(self.document_start).rstrip()+'\n')
561 self.body.append(self.comment(self.document_start).rstrip()+'\n')
562 # writing header is postboned
562 # writing header is postboned
563 self.header_written = 0
563 self.header_written = 0
564
564
565 def depart_document(self, node):
565 def depart_document(self, node):
566 if self._docinfo['author']:
566 if self._docinfo['author']:
567 self.body.append('.SH AUTHOR\n%s\n'
567 self.body.append('.SH AUTHOR\n%s\n'
568 % ', '.join(self._docinfo['author']))
568 % ', '.join(self._docinfo['author']))
569 skip = ('author', 'copyright', 'date',
569 skip = ('author', 'copyright', 'date',
570 'manual_group', 'manual_section',
570 'manual_group', 'manual_section',
571 'subtitle',
571 'subtitle',
572 'title', 'title_upper', 'version')
572 'title', 'title_upper', 'version')
573 for name in self._docinfo_keys:
573 for name in self._docinfo_keys:
574 if name == 'address':
574 if name == 'address':
575 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
575 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
576 self.language.labels.get(name, name),
576 self.language.labels.get(name, name),
577 self.defs['indent'][0] % 0,
577 self.defs['indent'][0] % 0,
578 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
578 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
579 self._docinfo[name],
579 self._docinfo[name],
580 self.defs['indent'][1],
580 self.defs['indent'][1],
581 self.defs['indent'][1]))
581 self.defs['indent'][1]))
582 elif not name in skip:
582 elif not name in skip:
583 if name in self._docinfo_names:
583 if name in self._docinfo_names:
584 label = self._docinfo_names[name]
584 label = self._docinfo_names[name]
585 else:
585 else:
586 label = self.language.labels.get(name, name)
586 label = self.language.labels.get(name, name)
587 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
587 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
588 if self._docinfo['copyright']:
588 if self._docinfo['copyright']:
589 self.body.append('.SH COPYRIGHT\n%s\n'
589 self.body.append('.SH COPYRIGHT\n%s\n'
590 % self._docinfo['copyright'])
590 % self._docinfo['copyright'])
591 self.body.append(self.comment(
591 self.body.append(self.comment(
592 'Generated by docutils manpage writer.\n'))
592 'Generated by docutils manpage writer.\n'))
593
593
594 def visit_emphasis(self, node):
594 def visit_emphasis(self, node):
595 self.body.append(self.defs['emphasis'][0])
595 self.body.append(self.defs['emphasis'][0])
596
596
597 def depart_emphasis(self, node):
597 def depart_emphasis(self, node):
598 self.body.append(self.defs['emphasis'][1])
598 self.body.append(self.defs['emphasis'][1])
599
599
600 def visit_entry(self, node):
600 def visit_entry(self, node):
601 # a cell in a table row
601 # a cell in a table row
602 if 'morerows' in node:
602 if 'morerows' in node:
603 self.document.reporter.warning('"table row spanning" not supported',
603 self.document.reporter.warning('"table row spanning" not supported',
604 base_node=node)
604 base_node=node)
605 if 'morecols' in node:
605 if 'morecols' in node:
606 self.document.reporter.warning(
606 self.document.reporter.warning(
607 '"table cell spanning" not supported', base_node=node)
607 '"table cell spanning" not supported', base_node=node)
608 self.context.append(len(self.body))
608 self.context.append(len(self.body))
609
609
610 def depart_entry(self, node):
610 def depart_entry(self, node):
611 start = self.context.pop()
611 start = self.context.pop()
612 self._active_table.append_cell(self.body[start:])
612 self._active_table.append_cell(self.body[start:])
613 del self.body[start:]
613 del self.body[start:]
614
614
615 def visit_enumerated_list(self, node):
615 def visit_enumerated_list(self, node):
616 self.list_start(node)
616 self.list_start(node)
617
617
618 def depart_enumerated_list(self, node):
618 def depart_enumerated_list(self, node):
619 self.list_end()
619 self.list_end()
620
620
621 def visit_error(self, node):
621 def visit_error(self, node):
622 self.visit_admonition(node, 'error')
622 self.visit_admonition(node, 'error')
623
623
624 depart_error = depart_admonition
624 depart_error = depart_admonition
625
625
626 def visit_field(self, node):
626 def visit_field(self, node):
627 pass
627 pass
628
628
629 def depart_field(self, node):
629 def depart_field(self, node):
630 pass
630 pass
631
631
632 def visit_field_body(self, node):
632 def visit_field_body(self, node):
633 if self._in_docinfo:
633 if self._in_docinfo:
634 name_normalized = self._field_name.lower().replace(" ","_")
634 name_normalized = self._field_name.lower().replace(" ","_")
635 self._docinfo_names[name_normalized] = self._field_name
635 self._docinfo_names[name_normalized] = self._field_name
636 self.visit_docinfo_item(node, name_normalized)
636 self.visit_docinfo_item(node, name_normalized)
637 raise nodes.SkipNode
637 raise nodes.SkipNode
638
638
639 def depart_field_body(self, node):
639 def depart_field_body(self, node):
640 pass
640 pass
641
641
642 def visit_field_list(self, node):
642 def visit_field_list(self, node):
643 self.indent(FIELD_LIST_INDENT)
643 self.indent(FIELD_LIST_INDENT)
644
644
645 def depart_field_list(self, node):
645 def depart_field_list(self, node):
646 self.dedent()
646 self.dedent()
647
647
648 def visit_field_name(self, node):
648 def visit_field_name(self, node):
649 if self._in_docinfo:
649 if self._in_docinfo:
650 self._field_name = node.astext()
650 self._field_name = node.astext()
651 raise nodes.SkipNode
651 raise nodes.SkipNode
652 else:
652 else:
653 self.body.append(self.defs['field_name'][0])
653 self.body.append(self.defs['field_name'][0])
654
654
655 def depart_field_name(self, node):
655 def depart_field_name(self, node):
656 self.body.append(self.defs['field_name'][1])
656 self.body.append(self.defs['field_name'][1])
657
657
658 def visit_figure(self, node):
658 def visit_figure(self, node):
659 self.indent(2.5)
659 self.indent(2.5)
660 self.indent(0)
660 self.indent(0)
661
661
662 def depart_figure(self, node):
662 def depart_figure(self, node):
663 self.dedent()
663 self.dedent()
664 self.dedent()
664 self.dedent()
665
665
666 def visit_footer(self, node):
666 def visit_footer(self, node):
667 self.document.reporter.warning('"footer" not supported',
667 self.document.reporter.warning('"footer" not supported',
668 base_node=node)
668 base_node=node)
669
669
670 def depart_footer(self, node):
670 def depart_footer(self, node):
671 pass
671 pass
672
672
673 def visit_footnote(self, node):
673 def visit_footnote(self, node):
674 num, text = node.astext().split(None, 1)
674 num, text = node.astext().split(None, 1)
675 num = num.strip()
675 num = num.strip()
676 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
676 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
677
677
678 def depart_footnote(self, node):
678 def depart_footnote(self, node):
679 pass
679 pass
680
680
681 def footnote_backrefs(self, node):
681 def footnote_backrefs(self, node):
682 self.document.reporter.warning('"footnote_backrefs" not supported',
682 self.document.reporter.warning('"footnote_backrefs" not supported',
683 base_node=node)
683 base_node=node)
684
684
685 def visit_footnote_reference(self, node):
685 def visit_footnote_reference(self, node):
686 self.body.append('['+self.deunicode(node.astext())+']')
686 self.body.append('['+self.deunicode(node.astext())+']')
687 raise nodes.SkipNode
687 raise nodes.SkipNode
688
688
689 def depart_footnote_reference(self, node):
689 def depart_footnote_reference(self, node):
690 pass
690 pass
691
691
692 def visit_generated(self, node):
692 def visit_generated(self, node):
693 pass
693 pass
694
694
695 def depart_generated(self, node):
695 def depart_generated(self, node):
696 pass
696 pass
697
697
698 def visit_header(self, node):
698 def visit_header(self, node):
699 raise NotImplementedError, node.astext()
699 raise NotImplementedError, node.astext()
700
700
701 def depart_header(self, node):
701 def depart_header(self, node):
702 pass
702 pass
703
703
704 def visit_hint(self, node):
704 def visit_hint(self, node):
705 self.visit_admonition(node, 'hint')
705 self.visit_admonition(node, 'hint')
706
706
707 depart_hint = depart_admonition
707 depart_hint = depart_admonition
708
708
709 def visit_subscript(self, node):
709 def visit_subscript(self, node):
710 self.body.append('\\s-2\\d')
710 self.body.append('\\s-2\\d')
711
711
712 def depart_subscript(self, node):
712 def depart_subscript(self, node):
713 self.body.append('\\u\\s0')
713 self.body.append('\\u\\s0')
714
714
715 def visit_superscript(self, node):
715 def visit_superscript(self, node):
716 self.body.append('\\s-2\\u')
716 self.body.append('\\s-2\\u')
717
717
718 def depart_superscript(self, node):
718 def depart_superscript(self, node):
719 self.body.append('\\d\\s0')
719 self.body.append('\\d\\s0')
720
720
721 def visit_attribution(self, node):
721 def visit_attribution(self, node):
722 self.body.append('\\(em ')
722 self.body.append('\\(em ')
723
723
724 def depart_attribution(self, node):
724 def depart_attribution(self, node):
725 self.body.append('\n')
725 self.body.append('\n')
726
726
727 def visit_image(self, node):
727 def visit_image(self, node):
728 self.document.reporter.warning('"image" not supported',
728 self.document.reporter.warning('"image" not supported',
729 base_node=node)
729 base_node=node)
730 text = []
730 text = []
731 if 'alt' in node.attributes:
731 if 'alt' in node.attributes:
732 text.append(node.attributes['alt'])
732 text.append(node.attributes['alt'])
733 if 'uri' in node.attributes:
733 if 'uri' in node.attributes:
734 text.append(node.attributes['uri'])
734 text.append(node.attributes['uri'])
735 self.body.append('[image: %s]\n' % ('/'.join(text)))
735 self.body.append('[image: %s]\n' % ('/'.join(text)))
736 raise nodes.SkipNode
736 raise nodes.SkipNode
737
737
738 def visit_important(self, node):
738 def visit_important(self, node):
739 self.visit_admonition(node, 'important')
739 self.visit_admonition(node, 'important')
740
740
741 depart_important = depart_admonition
741 depart_important = depart_admonition
742
742
743 def visit_label(self, node):
743 def visit_label(self, node):
744 # footnote and citation
744 # footnote and citation
745 if (isinstance(node.parent, nodes.footnote)
745 if (isinstance(node.parent, nodes.footnote)
746 or isinstance(node.parent, nodes.citation)):
746 or isinstance(node.parent, nodes.citation)):
747 raise nodes.SkipNode
747 raise nodes.SkipNode
748 self.document.reporter.warning('"unsupported "label"',
748 self.document.reporter.warning('"unsupported "label"',
749 base_node=node)
749 base_node=node)
750 self.body.append('[')
750 self.body.append('[')
751
751
752 def depart_label(self, node):
752 def depart_label(self, node):
753 self.body.append(']\n')
753 self.body.append(']\n')
754
754
755 def visit_legend(self, node):
755 def visit_legend(self, node):
756 pass
756 pass
757
757
758 def depart_legend(self, node):
758 def depart_legend(self, node):
759 pass
759 pass
760
760
761 # WHAT should we use .INDENT, .UNINDENT ?
761 # WHAT should we use .INDENT, .UNINDENT ?
762 def visit_line_block(self, node):
762 def visit_line_block(self, node):
763 self._line_block += 1
763 self._line_block += 1
764 if self._line_block == 1:
764 if self._line_block == 1:
765 self.body.append('.sp\n')
765 self.body.append('.sp\n')
766 self.body.append('.nf\n')
766 self.body.append('.nf\n')
767 else:
767 else:
768 self.body.append('.in +2\n')
768 self.body.append('.in +2\n')
769
769
770 def depart_line_block(self, node):
770 def depart_line_block(self, node):
771 self._line_block -= 1
771 self._line_block -= 1
772 if self._line_block == 0:
772 if self._line_block == 0:
773 self.body.append('.fi\n')
773 self.body.append('.fi\n')
774 self.body.append('.sp\n')
774 self.body.append('.sp\n')
775 else:
775 else:
776 self.body.append('.in -2\n')
776 self.body.append('.in -2\n')
777
777
778 def visit_line(self, node):
778 def visit_line(self, node):
779 pass
779 pass
780
780
781 def depart_line(self, node):
781 def depart_line(self, node):
782 self.body.append('\n')
782 self.body.append('\n')
783
783
784 def visit_list_item(self, node):
784 def visit_list_item(self, node):
785 # man 7 man argues to use ".IP" instead of ".TP"
785 # man 7 man argues to use ".IP" instead of ".TP"
786 self.body.append('.IP %s %d\n' % (
786 self.body.append('.IP %s %d\n' % (
787 self._list_char[-1].next(),
787 self._list_char[-1].next(),
788 self._list_char[-1].get_width(),))
788 self._list_char[-1].get_width(),))
789
789
790 def depart_list_item(self, node):
790 def depart_list_item(self, node):
791 pass
791 pass
792
792
793 def visit_literal(self, node):
793 def visit_literal(self, node):
794 self.body.append(self.defs['literal'][0])
794 self.body.append(self.defs['literal'][0])
795
795
796 def depart_literal(self, node):
796 def depart_literal(self, node):
797 self.body.append(self.defs['literal'][1])
797 self.body.append(self.defs['literal'][1])
798
798
799 def visit_literal_block(self, node):
799 def visit_literal_block(self, node):
800 self.body.append(self.defs['literal_block'][0])
800 self.body.append(self.defs['literal_block'][0])
801 self._in_literal = True
801 self._in_literal = True
802
802
803 def depart_literal_block(self, node):
803 def depart_literal_block(self, node):
804 self._in_literal = False
804 self._in_literal = False
805 self.body.append(self.defs['literal_block'][1])
805 self.body.append(self.defs['literal_block'][1])
806
806
807 def visit_meta(self, node):
807 def visit_meta(self, node):
808 raise NotImplementedError, node.astext()
808 raise NotImplementedError, node.astext()
809
809
810 def depart_meta(self, node):
810 def depart_meta(self, node):
811 pass
811 pass
812
812
813 def visit_note(self, node):
813 def visit_note(self, node):
814 self.visit_admonition(node, 'note')
814 self.visit_admonition(node, 'note')
815
815
816 depart_note = depart_admonition
816 depart_note = depart_admonition
817
817
818 def indent(self, by=0.5):
818 def indent(self, by=0.5):
819 # if we are in a section ".SH" there already is a .RS
819 # if we are in a section ".SH" there already is a .RS
820 step = self._indent[-1]
820 step = self._indent[-1]
821 self._indent.append(by)
821 self._indent.append(by)
822 self.body.append(self.defs['indent'][0] % step)
822 self.body.append(self.defs['indent'][0] % step)
823
823
824 def dedent(self):
824 def dedent(self):
825 self._indent.pop()
825 self._indent.pop()
826 self.body.append(self.defs['indent'][1])
826 self.body.append(self.defs['indent'][1])
827
827
828 def visit_option_list(self, node):
828 def visit_option_list(self, node):
829 self.indent(OPTION_LIST_INDENT)
829 self.indent(OPTION_LIST_INDENT)
830
830
831 def depart_option_list(self, node):
831 def depart_option_list(self, node):
832 self.dedent()
832 self.dedent()
833
833
834 def visit_option_list_item(self, node):
834 def visit_option_list_item(self, node):
835 # one item of the list
835 # one item of the list
836 self.body.append(self.defs['option_list_item'][0])
836 self.body.append(self.defs['option_list_item'][0])
837
837
838 def depart_option_list_item(self, node):
838 def depart_option_list_item(self, node):
839 self.body.append(self.defs['option_list_item'][1])
839 self.body.append(self.defs['option_list_item'][1])
840
840
841 def visit_option_group(self, node):
841 def visit_option_group(self, node):
842 # as one option could have several forms it is a group
842 # as one option could have several forms it is a group
843 # options without parameter bold only, .B, -v
843 # options without parameter bold only, .B, -v
844 # options with parameter bold italic, .BI, -f file
844 # options with parameter bold italic, .BI, -f file
845 #
845 #
846 # we do not know if .B or .BI
846 # we do not know if .B or .BI
847 self.context.append('.B') # blind guess
847 self.context.append('.B') # blind guess
848 self.context.append(len(self.body)) # to be able to insert later
848 self.context.append(len(self.body)) # to be able to insert later
849 self.context.append(0) # option counter
849 self.context.append(0) # option counter
850
850
851 def depart_option_group(self, node):
851 def depart_option_group(self, node):
852 self.context.pop() # the counter
852 self.context.pop() # the counter
853 start_position = self.context.pop()
853 start_position = self.context.pop()
854 text = self.body[start_position:]
854 text = self.body[start_position:]
855 del self.body[start_position:]
855 del self.body[start_position:]
856 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
856 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
857
857
858 def visit_option(self, node):
858 def visit_option(self, node):
859 # each form of the option will be presented separately
859 # each form of the option will be presented separately
860 if self.context[-1] > 0:
860 if self.context[-1] > 0:
861 self.body.append(', ')
861 self.body.append(', ')
862 if self.context[-3] == '.BI':
862 if self.context[-3] == '.BI':
863 self.body.append('\\')
863 self.body.append('\\')
864 self.body.append(' ')
864 self.body.append(' ')
865
865
866 def depart_option(self, node):
866 def depart_option(self, node):
867 self.context[-1] += 1
867 self.context[-1] += 1
868
868
869 def visit_option_string(self, node):
869 def visit_option_string(self, node):
870 # do not know if .B or .BI
870 # do not know if .B or .BI
871 pass
871 pass
872
872
873 def depart_option_string(self, node):
873 def depart_option_string(self, node):
874 pass
874 pass
875
875
876 def visit_option_argument(self, node):
876 def visit_option_argument(self, node):
877 self.context[-3] = '.BI' # bold/italic alternate
877 self.context[-3] = '.BI' # bold/italic alternate
878 if node['delimiter'] != ' ':
878 if node['delimiter'] != ' ':
879 self.body.append('\\fB%s ' % node['delimiter'])
879 self.body.append('\\fB%s ' % node['delimiter'])
880 elif self.body[len(self.body)-1].endswith('='):
880 elif self.body[len(self.body)-1].endswith('='):
881 # a blank only means no blank in output, just changing font
881 # a blank only means no blank in output, just changing font
882 self.body.append(' ')
882 self.body.append(' ')
883 else:
883 else:
884 # blank backslash blank, switch font then a blank
884 # blank backslash blank, switch font then a blank
885 self.body.append(' \\ ')
885 self.body.append(' \\ ')
886
886
887 def depart_option_argument(self, node):
887 def depart_option_argument(self, node):
888 pass
888 pass
889
889
890 def visit_organization(self, node):
890 def visit_organization(self, node):
891 self.visit_docinfo_item(node, 'organization')
891 self.visit_docinfo_item(node, 'organization')
892
892
893 def depart_organization(self, node):
893 def depart_organization(self, node):
894 pass
894 pass
895
895
896 def visit_paragraph(self, node):
896 def visit_paragraph(self, node):
897 # ``.PP`` : Start standard indented paragraph.
897 # ``.PP`` : Start standard indented paragraph.
898 # ``.LP`` : Start block paragraph, all except the first.
898 # ``.LP`` : Start block paragraph, all except the first.
899 # ``.P [type]`` : Start paragraph type.
899 # ``.P [type]`` : Start paragraph type.
900 # NOTE dont use paragraph starts because they reset indentation.
900 # NOTE dont use paragraph starts because they reset indentation.
901 # ``.sp`` is only vertical space
901 # ``.sp`` is only vertical space
902 self.ensure_eol()
902 self.ensure_eol()
903 self.body.append('.sp\n')
903 self.body.append('.sp\n')
904
904
905 def depart_paragraph(self, node):
905 def depart_paragraph(self, node):
906 self.body.append('\n')
906 self.body.append('\n')
907
907
908 def visit_problematic(self, node):
908 def visit_problematic(self, node):
909 self.body.append(self.defs['problematic'][0])
909 self.body.append(self.defs['problematic'][0])
910
910
911 def depart_problematic(self, node):
911 def depart_problematic(self, node):
912 self.body.append(self.defs['problematic'][1])
912 self.body.append(self.defs['problematic'][1])
913
913
914 def visit_raw(self, node):
914 def visit_raw(self, node):
915 if node.get('format') == 'manpage':
915 if node.get('format') == 'manpage':
916 self.body.append(node.astext() + "\n")
916 self.body.append(node.astext() + "\n")
917 # Keep non-manpage raw text out of output:
917 # Keep non-manpage raw text out of output:
918 raise nodes.SkipNode
918 raise nodes.SkipNode
919
919
920 def visit_reference(self, node):
920 def visit_reference(self, node):
921 """E.g. link or email address."""
921 """E.g. link or email address."""
922 self.body.append(self.defs['reference'][0])
922 self.body.append(self.defs['reference'][0])
923
923
924 def depart_reference(self, node):
924 def depart_reference(self, node):
925 self.body.append(self.defs['reference'][1])
925 self.body.append(self.defs['reference'][1])
926
926
927 def visit_revision(self, node):
927 def visit_revision(self, node):
928 self.visit_docinfo_item(node, 'revision')
928 self.visit_docinfo_item(node, 'revision')
929
929
930 depart_revision = depart_docinfo_item
930 depart_revision = depart_docinfo_item
931
931
932 def visit_row(self, node):
932 def visit_row(self, node):
933 self._active_table.new_row()
933 self._active_table.new_row()
934
934
935 def depart_row(self, node):
935 def depart_row(self, node):
936 pass
936 pass
937
937
938 def visit_section(self, node):
938 def visit_section(self, node):
939 self.section_level += 1
939 self.section_level += 1
940
940
941 def depart_section(self, node):
941 def depart_section(self, node):
942 self.section_level -= 1
942 self.section_level -= 1
943
943
944 def visit_status(self, node):
944 def visit_status(self, node):
945 self.visit_docinfo_item(node, 'status')
945 self.visit_docinfo_item(node, 'status')
946
946
947 depart_status = depart_docinfo_item
947 depart_status = depart_docinfo_item
948
948
949 def visit_strong(self, node):
949 def visit_strong(self, node):
950 self.body.append(self.defs['strong'][0])
950 self.body.append(self.defs['strong'][0])
951
951
952 def depart_strong(self, node):
952 def depart_strong(self, node):
953 self.body.append(self.defs['strong'][1])
953 self.body.append(self.defs['strong'][1])
954
954
955 def visit_substitution_definition(self, node):
955 def visit_substitution_definition(self, node):
956 """Internal only."""
956 """Internal only."""
957 raise nodes.SkipNode
957 raise nodes.SkipNode
958
958
959 def visit_substitution_reference(self, node):
959 def visit_substitution_reference(self, node):
960 self.document.reporter.warning('"substitution_reference" not supported',
960 self.document.reporter.warning('"substitution_reference" not supported',
961 base_node=node)
961 base_node=node)
962
962
963 def visit_subtitle(self, node):
963 def visit_subtitle(self, node):
964 if isinstance(node.parent, nodes.sidebar):
964 if isinstance(node.parent, nodes.sidebar):
965 self.body.append(self.defs['strong'][0])
965 self.body.append(self.defs['strong'][0])
966 elif isinstance(node.parent, nodes.document):
966 elif isinstance(node.parent, nodes.document):
967 self.visit_docinfo_item(node, 'subtitle')
967 self.visit_docinfo_item(node, 'subtitle')
968 elif isinstance(node.parent, nodes.section):
968 elif isinstance(node.parent, nodes.section):
969 self.body.append(self.defs['strong'][0])
969 self.body.append(self.defs['strong'][0])
970
970
971 def depart_subtitle(self, node):
971 def depart_subtitle(self, node):
972 # document subtitle calls SkipNode
972 # document subtitle calls SkipNode
973 self.body.append(self.defs['strong'][1]+'\n.PP\n')
973 self.body.append(self.defs['strong'][1]+'\n.PP\n')
974
974
975 def visit_system_message(self, node):
975 def visit_system_message(self, node):
976 # TODO add report_level
976 # TODO add report_level
977 #if node['level'] < self.document.reporter['writer'].report_level:
977 #if node['level'] < self.document.reporter['writer'].report_level:
978 # Level is too low to display:
978 # Level is too low to display:
979 # raise nodes.SkipNode
979 # raise nodes.SkipNode
980 attr = {}
980 attr = {}
981 backref_text = ''
981 backref_text = ''
982 if node.hasattr('id'):
982 if node.hasattr('id'):
983 attr['name'] = node['id']
983 attr['name'] = node['id']
984 if node.hasattr('line'):
984 if node.hasattr('line'):
985 line = ', line %s' % node['line']
985 line = ', line %s' % node['line']
986 else:
986 else:
987 line = ''
987 line = ''
988 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
988 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
989 % (node['type'], node['level'], node['source'], line))
989 % (node['type'], node['level'], node['source'], line))
990
990
991 def depart_system_message(self, node):
991 def depart_system_message(self, node):
992 pass
992 pass
993
993
994 def visit_table(self, node):
994 def visit_table(self, node):
995 self._active_table = Table()
995 self._active_table = Table()
996
996
997 def depart_table(self, node):
997 def depart_table(self, node):
998 self.ensure_eol()
998 self.ensure_eol()
999 self.body.extend(self._active_table.as_list())
999 self.body.extend(self._active_table.as_list())
1000 self._active_table = None
1000 self._active_table = None
1001
1001
1002 def visit_target(self, node):
1002 def visit_target(self, node):
1003 # targets are in-document hyper targets, without any use for man-pages.
1003 # targets are in-document hyper targets, without any use for man-pages.
1004 raise nodes.SkipNode
1004 raise nodes.SkipNode
1005
1005
1006 def visit_tbody(self, node):
1006 def visit_tbody(self, node):
1007 pass
1007 pass
1008
1008
1009 def depart_tbody(self, node):
1009 def depart_tbody(self, node):
1010 pass
1010 pass
1011
1011
1012 def visit_term(self, node):
1012 def visit_term(self, node):
1013 self.body.append(self.defs['term'][0])
1013 self.body.append(self.defs['term'][0])
1014
1014
1015 def depart_term(self, node):
1015 def depart_term(self, node):
1016 self.body.append(self.defs['term'][1])
1016 self.body.append(self.defs['term'][1])
1017
1017
1018 def visit_tgroup(self, node):
1018 def visit_tgroup(self, node):
1019 pass
1019 pass
1020
1020
1021 def depart_tgroup(self, node):
1021 def depart_tgroup(self, node):
1022 pass
1022 pass
1023
1023
1024 def visit_thead(self, node):
1024 def visit_thead(self, node):
1025 # MAYBE double line '='
1025 # MAYBE double line '='
1026 pass
1026 pass
1027
1027
1028 def depart_thead(self, node):
1028 def depart_thead(self, node):
1029 # MAYBE double line '='
1029 # MAYBE double line '='
1030 pass
1030 pass
1031
1031
1032 def visit_tip(self, node):
1032 def visit_tip(self, node):
1033 self.visit_admonition(node, 'tip')
1033 self.visit_admonition(node, 'tip')
1034
1034
1035 depart_tip = depart_admonition
1035 depart_tip = depart_admonition
1036
1036
1037 def visit_title(self, node):
1037 def visit_title(self, node):
1038 if isinstance(node.parent, nodes.topic):
1038 if isinstance(node.parent, nodes.topic):
1039 self.body.append(self.defs['topic-title'][0])
1039 self.body.append(self.defs['topic-title'][0])
1040 elif isinstance(node.parent, nodes.sidebar):
1040 elif isinstance(node.parent, nodes.sidebar):
1041 self.body.append(self.defs['sidebar-title'][0])
1041 self.body.append(self.defs['sidebar-title'][0])
1042 elif isinstance(node.parent, nodes.admonition):
1042 elif isinstance(node.parent, nodes.admonition):
1043 self.body.append('.IP "')
1043 self.body.append('.IP "')
1044 elif self.section_level == 0:
1044 elif self.section_level == 0:
1045 self._docinfo['title'] = node.astext()
1045 self._docinfo['title'] = node.astext()
1046 # document title for .TH
1046 # document title for .TH
1047 self._docinfo['title_upper'] = node.astext().upper()
1047 self._docinfo['title_upper'] = node.astext().upper()
1048 raise nodes.SkipNode
1048 raise nodes.SkipNode
1049 elif self.section_level == 1:
1049 elif self.section_level == 1:
1050 self.body.append('.SH ')
1050 self.body.append('.SH ')
1051 for n in node.traverse(nodes.Text):
1051 for n in node.traverse(nodes.Text):
1052 n.parent.replace(n, nodes.Text(n.astext().upper()))
1052 n.parent.replace(n, nodes.Text(n.astext().upper()))
1053 else:
1053 else:
1054 self.body.append('.SS ')
1054 self.body.append('.SS ')
1055
1055
1056 def depart_title(self, node):
1056 def depart_title(self, node):
1057 if isinstance(node.parent, nodes.admonition):
1057 if isinstance(node.parent, nodes.admonition):
1058 self.body.append('"')
1058 self.body.append('"')
1059 self.body.append('\n')
1059 self.body.append('\n')
1060
1060
1061 def visit_title_reference(self, node):
1061 def visit_title_reference(self, node):
1062 """inline citation reference"""
1062 """inline citation reference"""
1063 self.body.append(self.defs['title_reference'][0])
1063 self.body.append(self.defs['title_reference'][0])
1064
1064
1065 def depart_title_reference(self, node):
1065 def depart_title_reference(self, node):
1066 self.body.append(self.defs['title_reference'][1])
1066 self.body.append(self.defs['title_reference'][1])
1067
1067
1068 def visit_topic(self, node):
1068 def visit_topic(self, node):
1069 pass
1069 pass
1070
1070
1071 def depart_topic(self, node):
1071 def depart_topic(self, node):
1072 pass
1072 pass
1073
1073
1074 def visit_sidebar(self, node):
1074 def visit_sidebar(self, node):
1075 pass
1075 pass
1076
1076
1077 def depart_sidebar(self, node):
1077 def depart_sidebar(self, node):
1078 pass
1078 pass
1079
1079
1080 def visit_rubric(self, node):
1080 def visit_rubric(self, node):
1081 pass
1081 pass
1082
1082
1083 def depart_rubric(self, node):
1083 def depart_rubric(self, node):
1084 pass
1084 pass
1085
1085
1086 def visit_transition(self, node):
1086 def visit_transition(self, node):
1087 # .PP Begin a new paragraph and reset prevailing indent.
1087 # .PP Begin a new paragraph and reset prevailing indent.
1088 # .sp N leaves N lines of blank space.
1088 # .sp N leaves N lines of blank space.
1089 # .ce centers the next line
1089 # .ce centers the next line
1090 self.body.append('\n.sp\n.ce\n----\n')
1090 self.body.append('\n.sp\n.ce\n----\n')
1091
1091
1092 def depart_transition(self, node):
1092 def depart_transition(self, node):
1093 self.body.append('\n.ce 0\n.sp\n')
1093 self.body.append('\n.ce 0\n.sp\n')
1094
1094
1095 def visit_version(self, node):
1095 def visit_version(self, node):
1096 self.visit_docinfo_item(node, 'version')
1096 self.visit_docinfo_item(node, 'version')
1097
1097
1098 def visit_warning(self, node):
1098 def visit_warning(self, node):
1099 self.visit_admonition(node, 'warning')
1099 self.visit_admonition(node, 'warning')
1100
1100
1101 depart_warning = depart_admonition
1101 depart_warning = depart_admonition
1102
1102
1103 def unimplemented_visit(self, node):
1103 def unimplemented_visit(self, node):
1104 raise NotImplementedError('visiting unimplemented node type: %s'
1104 raise NotImplementedError('visiting unimplemented node type: %s'
1105 % node.__class__.__name__)
1105 % node.__class__.__name__)
1106
1106
1107 # The following part is taken from the Docutils rst2man.py script:
1107 # The following part is taken from the Docutils rst2man.py script:
1108 if __name__ == "__main__":
1108 if __name__ == "__main__":
1109 from docutils.core import publish_cmdline, default_description
1109 from docutils.core import publish_cmdline, default_description
1110 description = ("Generates plain unix manual documents. " +
1110 description = ("Generates plain unix manual documents. " +
1111 default_description)
1111 default_description)
1112 publish_cmdline(writer=Writer(), description=description)
1112 publish_cmdline(writer=Writer(), description=description)
1113
1113
1114 # vim: set fileencoding=utf-8 et ts=4 ai :
1114 # vim: set fileencoding=utf-8 et ts=4 ai :
@@ -1,2822 +1,2823 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 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25
25
26 add known patch to applied stack qpush
26 add known patch to applied stack qpush
27 remove patch from applied stack qpop
27 remove patch from applied stack qpop
28 refresh contents of top applied patch qrefresh
28 refresh contents of top applied patch qrefresh
29
29
30 By default, mq will automatically use git patches when required to
30 By default, mq will automatically use git patches when required to
31 avoid losing file mode changes, copy records, binary files or empty
31 avoid losing file mode changes, copy records, binary files or empty
32 files creations or deletions. This behaviour can be configured with::
32 files creations or deletions. This behaviour can be configured with::
33
33
34 [mq]
34 [mq]
35 git = auto/keep/yes/no
35 git = auto/keep/yes/no
36
36
37 If set to 'keep', mq will obey the [diff] section configuration while
37 If set to 'keep', mq will obey the [diff] section configuration while
38 preserving existing git patches upon qrefresh. If set to 'yes' or
38 preserving existing git patches upon qrefresh. If set to 'yes' or
39 'no', mq will override the [diff] section and always generate git or
39 'no', mq will override the [diff] section and always generate git or
40 regular patches, possibly losing data in the second case.
40 regular patches, possibly losing data in the second case.
41 '''
41 '''
42
42
43 from mercurial.i18n import _
43 from mercurial.i18n import _
44 from mercurial.node import bin, hex, short, nullid, nullrev
44 from mercurial.node import bin, hex, short, nullid, nullrev
45 from mercurial.lock import release
45 from mercurial.lock import release
46 from mercurial import commands, cmdutil, hg, patch, util
46 from mercurial import commands, cmdutil, hg, patch, util
47 from mercurial import repair, extensions, url, error
47 from mercurial import repair, extensions, url, error
48 import os, sys, re, errno
48 import os, sys, re, errno
49
49
50 commands.norepo += " qclone"
50 commands.norepo += " qclone"
51
51
52 # Patch names looks like unix-file names.
52 # Patch names looks like unix-file names.
53 # They must be joinable with queue directory and result in the patch path.
53 # They must be joinable with queue directory and result in the patch path.
54 normname = util.normpath
54 normname = util.normpath
55
55
56 class statusentry(object):
56 class statusentry(object):
57 def __init__(self, rev, name=None):
57 def __init__(self, rev, name=None):
58 if not name:
58 if not name:
59 fields = rev.split(':', 1)
59 fields = rev.split(':', 1)
60 if len(fields) == 2:
60 if len(fields) == 2:
61 self.rev, self.name = fields
61 self.rev, self.name = fields
62 else:
62 else:
63 self.rev, self.name = None, None
63 self.rev, self.name = None, None
64 else:
64 else:
65 self.rev, self.name = rev, name
65 self.rev, self.name = rev, name
66
66
67 def __str__(self):
67 def __str__(self):
68 return self.rev + ':' + self.name
68 return self.rev + ':' + self.name
69
69
70 class patchheader(object):
70 class patchheader(object):
71 def __init__(self, pf, plainmode=False):
71 def __init__(self, pf, plainmode=False):
72 def eatdiff(lines):
72 def eatdiff(lines):
73 while lines:
73 while lines:
74 l = lines[-1]
74 l = lines[-1]
75 if (l.startswith("diff -") or
75 if (l.startswith("diff -") or
76 l.startswith("Index:") or
76 l.startswith("Index:") or
77 l.startswith("===========")):
77 l.startswith("===========")):
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81 def eatempty(lines):
81 def eatempty(lines):
82 while lines:
82 while lines:
83 l = lines[-1]
83 l = lines[-1]
84 if re.match('\s*$', l):
84 if re.match('\s*$', l):
85 del lines[-1]
85 del lines[-1]
86 else:
86 else:
87 break
87 break
88
88
89 message = []
89 message = []
90 comments = []
90 comments = []
91 user = None
91 user = None
92 date = None
92 date = None
93 parent = None
93 parent = None
94 format = None
94 format = None
95 subject = None
95 subject = None
96 diffstart = 0
96 diffstart = 0
97
97
98 for line in file(pf):
98 for line in file(pf):
99 line = line.rstrip()
99 line = line.rstrip()
100 if line.startswith('diff --git'):
100 if line.startswith('diff --git'):
101 diffstart = 2
101 diffstart = 2
102 break
102 break
103 if diffstart:
103 if diffstart:
104 if line.startswith('+++ '):
104 if line.startswith('+++ '):
105 diffstart = 2
105 diffstart = 2
106 break
106 break
107 if line.startswith("--- "):
107 if line.startswith("--- "):
108 diffstart = 1
108 diffstart = 1
109 continue
109 continue
110 elif format == "hgpatch":
110 elif format == "hgpatch":
111 # parse values when importing the result of an hg export
111 # parse values when importing the result of an hg export
112 if line.startswith("# User "):
112 if line.startswith("# User "):
113 user = line[7:]
113 user = line[7:]
114 elif line.startswith("# Date "):
114 elif line.startswith("# Date "):
115 date = line[7:]
115 date = line[7:]
116 elif line.startswith("# Parent "):
116 elif line.startswith("# Parent "):
117 parent = line[9:]
117 parent = line[9:]
118 elif not line.startswith("# ") and line:
118 elif not line.startswith("# ") and line:
119 message.append(line)
119 message.append(line)
120 format = None
120 format = None
121 elif line == '# HG changeset patch':
121 elif line == '# HG changeset patch':
122 message = []
122 message = []
123 format = "hgpatch"
123 format = "hgpatch"
124 elif (format != "tagdone" and (line.startswith("Subject: ") or
124 elif (format != "tagdone" and (line.startswith("Subject: ") or
125 line.startswith("subject: "))):
125 line.startswith("subject: "))):
126 subject = line[9:]
126 subject = line[9:]
127 format = "tag"
127 format = "tag"
128 elif (format != "tagdone" and (line.startswith("From: ") or
128 elif (format != "tagdone" and (line.startswith("From: ") or
129 line.startswith("from: "))):
129 line.startswith("from: "))):
130 user = line[6:]
130 user = line[6:]
131 format = "tag"
131 format = "tag"
132 elif (format != "tagdone" and (line.startswith("Date: ") or
132 elif (format != "tagdone" and (line.startswith("Date: ") or
133 line.startswith("date: "))):
133 line.startswith("date: "))):
134 date = line[6:]
134 date = line[6:]
135 format = "tag"
135 format = "tag"
136 elif format == "tag" and line == "":
136 elif format == "tag" and line == "":
137 # when looking for tags (subject: from: etc) they
137 # when looking for tags (subject: from: etc) they
138 # end once you find a blank line in the source
138 # end once you find a blank line in the source
139 format = "tagdone"
139 format = "tagdone"
140 elif message or line:
140 elif message or line:
141 message.append(line)
141 message.append(line)
142 comments.append(line)
142 comments.append(line)
143
143
144 eatdiff(message)
144 eatdiff(message)
145 eatdiff(comments)
145 eatdiff(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 self.haspatch = diffstart > 1
159 self.haspatch = diffstart > 1
160 self.plainmode = plainmode
160 self.plainmode = plainmode
161
161
162 def setuser(self, user):
162 def setuser(self, user):
163 if not self.updateheader(['From: ', '# User '], user):
163 if not self.updateheader(['From: ', '# User '], user):
164 try:
164 try:
165 patchheaderat = self.comments.index('# HG changeset patch')
165 patchheaderat = self.comments.index('# HG changeset patch')
166 self.comments.insert(patchheaderat + 1, '# User ' + user)
166 self.comments.insert(patchheaderat + 1, '# User ' + user)
167 except ValueError:
167 except ValueError:
168 if self.plainmode or self._hasheader(['Date: ']):
168 if self.plainmode or self._hasheader(['Date: ']):
169 self.comments = ['From: ' + user] + self.comments
169 self.comments = ['From: ' + user] + self.comments
170 else:
170 else:
171 tmp = ['# HG changeset patch', '# User ' + user, '']
171 tmp = ['# HG changeset patch', '# User ' + user, '']
172 self.comments = tmp + self.comments
172 self.comments = tmp + self.comments
173 self.user = user
173 self.user = user
174
174
175 def setdate(self, date):
175 def setdate(self, date):
176 if not self.updateheader(['Date: ', '# Date '], date):
176 if not self.updateheader(['Date: ', '# Date '], date):
177 try:
177 try:
178 patchheaderat = self.comments.index('# HG changeset patch')
178 patchheaderat = self.comments.index('# HG changeset patch')
179 self.comments.insert(patchheaderat + 1, '# Date ' + date)
179 self.comments.insert(patchheaderat + 1, '# Date ' + date)
180 except ValueError:
180 except ValueError:
181 if self.plainmode or self._hasheader(['From: ']):
181 if self.plainmode or self._hasheader(['From: ']):
182 self.comments = ['Date: ' + date] + self.comments
182 self.comments = ['Date: ' + date] + self.comments
183 else:
183 else:
184 tmp = ['# HG changeset patch', '# Date ' + date, '']
184 tmp = ['# HG changeset patch', '# Date ' + date, '']
185 self.comments = tmp + self.comments
185 self.comments = tmp + self.comments
186 self.date = date
186 self.date = date
187
187
188 def setparent(self, parent):
188 def setparent(self, parent):
189 if not self.updateheader(['# Parent '], parent):
189 if not self.updateheader(['# Parent '], parent):
190 try:
190 try:
191 patchheaderat = self.comments.index('# HG changeset patch')
191 patchheaderat = self.comments.index('# HG changeset patch')
192 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
192 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
193 except ValueError:
193 except ValueError:
194 pass
194 pass
195 self.parent = parent
195 self.parent = parent
196
196
197 def setmessage(self, message):
197 def setmessage(self, message):
198 if self.comments:
198 if self.comments:
199 self._delmsg()
199 self._delmsg()
200 self.message = [message]
200 self.message = [message]
201 self.comments += self.message
201 self.comments += self.message
202
202
203 def updateheader(self, prefixes, new):
203 def updateheader(self, prefixes, new):
204 '''Update all references to a field in the patch header.
204 '''Update all references to a field in the patch header.
205 Return whether the field is present.'''
205 Return whether the field is present.'''
206 res = False
206 res = False
207 for prefix in prefixes:
207 for prefix in prefixes:
208 for i in xrange(len(self.comments)):
208 for i in xrange(len(self.comments)):
209 if self.comments[i].startswith(prefix):
209 if self.comments[i].startswith(prefix):
210 self.comments[i] = prefix + new
210 self.comments[i] = prefix + new
211 res = True
211 res = True
212 break
212 break
213 return res
213 return res
214
214
215 def _hasheader(self, prefixes):
215 def _hasheader(self, prefixes):
216 '''Check if a header starts with any of the given prefixes.'''
216 '''Check if a header starts with any of the given prefixes.'''
217 for prefix in prefixes:
217 for prefix in prefixes:
218 for comment in self.comments:
218 for comment in self.comments:
219 if comment.startswith(prefix):
219 if comment.startswith(prefix):
220 return True
220 return True
221 return False
221 return False
222
222
223 def __str__(self):
223 def __str__(self):
224 if not self.comments:
224 if not self.comments:
225 return ''
225 return ''
226 return '\n'.join(self.comments) + '\n\n'
226 return '\n'.join(self.comments) + '\n\n'
227
227
228 def _delmsg(self):
228 def _delmsg(self):
229 '''Remove existing message, keeping the rest of the comments fields.
229 '''Remove existing message, keeping the rest of the comments fields.
230 If comments contains 'subject: ', message will prepend
230 If comments contains 'subject: ', message will prepend
231 the field and a blank line.'''
231 the field and a blank line.'''
232 if self.message:
232 if self.message:
233 subj = 'subject: ' + self.message[0].lower()
233 subj = 'subject: ' + self.message[0].lower()
234 for i in xrange(len(self.comments)):
234 for i in xrange(len(self.comments)):
235 if subj == self.comments[i].lower():
235 if subj == self.comments[i].lower():
236 del self.comments[i]
236 del self.comments[i]
237 self.message = self.message[2:]
237 self.message = self.message[2:]
238 break
238 break
239 ci = 0
239 ci = 0
240 for mi in self.message:
240 for mi in self.message:
241 while mi != self.comments[ci]:
241 while mi != self.comments[ci]:
242 ci += 1
242 ci += 1
243 del self.comments[ci]
243 del self.comments[ci]
244
244
245 class queue(object):
245 class queue(object):
246 def __init__(self, ui, path, patchdir=None):
246 def __init__(self, ui, path, patchdir=None):
247 self.basepath = path
247 self.basepath = path
248 self.path = patchdir or os.path.join(path, "patches")
248 self.path = patchdir or os.path.join(path, "patches")
249 self.opener = util.opener(self.path)
249 self.opener = util.opener(self.path)
250 self.ui = ui
250 self.ui = ui
251 self.applied_dirty = 0
251 self.applied_dirty = 0
252 self.series_dirty = 0
252 self.series_dirty = 0
253 self.series_path = "series"
253 self.series_path = "series"
254 self.status_path = "status"
254 self.status_path = "status"
255 self.guards_path = "guards"
255 self.guards_path = "guards"
256 self.active_guards = None
256 self.active_guards = None
257 self.guards_dirty = False
257 self.guards_dirty = False
258 # Handle mq.git as a bool with extended values
258 # Handle mq.git as a bool with extended values
259 try:
259 try:
260 gitmode = ui.configbool('mq', 'git', None)
260 gitmode = ui.configbool('mq', 'git', None)
261 if gitmode is None:
261 if gitmode is None:
262 raise error.ConfigError()
262 raise error.ConfigError()
263 self.gitmode = gitmode and 'yes' or 'no'
263 self.gitmode = gitmode and 'yes' or 'no'
264 except error.ConfigError:
264 except error.ConfigError:
265 self.gitmode = ui.config('mq', 'git', 'auto').lower()
265 self.gitmode = ui.config('mq', 'git', 'auto').lower()
266 self.plainmode = ui.configbool('mq', 'plain', False)
266 self.plainmode = ui.configbool('mq', 'plain', False)
267
267
268 @util.propertycache
268 @util.propertycache
269 def applied(self):
269 def applied(self):
270 if os.path.exists(self.join(self.status_path)):
270 if os.path.exists(self.join(self.status_path)):
271 lines = self.opener(self.status_path).read().splitlines()
271 lines = self.opener(self.status_path).read().splitlines()
272 return [statusentry(l) for l in lines]
272 return [statusentry(l) for l in lines]
273 return []
273 return []
274
274
275 @util.propertycache
275 @util.propertycache
276 def full_series(self):
276 def full_series(self):
277 if os.path.exists(self.join(self.series_path)):
277 if os.path.exists(self.join(self.series_path)):
278 return self.opener(self.series_path).read().splitlines()
278 return self.opener(self.series_path).read().splitlines()
279 return []
279 return []
280
280
281 @util.propertycache
281 @util.propertycache
282 def series(self):
282 def series(self):
283 self.parse_series()
283 self.parse_series()
284 return self.series
284 return self.series
285
285
286 @util.propertycache
286 @util.propertycache
287 def series_guards(self):
287 def series_guards(self):
288 self.parse_series()
288 self.parse_series()
289 return self.series_guards
289 return self.series_guards
290
290
291 def invalidate(self):
291 def invalidate(self):
292 for a in 'applied full_series series series_guards'.split():
292 for a in 'applied full_series series series_guards'.split():
293 if a in self.__dict__:
293 if a in self.__dict__:
294 delattr(self, a)
294 delattr(self, a)
295 self.applied_dirty = 0
295 self.applied_dirty = 0
296 self.series_dirty = 0
296 self.series_dirty = 0
297 self.guards_dirty = False
297 self.guards_dirty = False
298 self.active_guards = None
298 self.active_guards = None
299
299
300 def diffopts(self, opts={}, patchfn=None):
300 def diffopts(self, opts={}, patchfn=None):
301 diffopts = patch.diffopts(self.ui, opts)
301 diffopts = patch.diffopts(self.ui, opts)
302 if self.gitmode == 'auto':
302 if self.gitmode == 'auto':
303 diffopts.upgrade = True
303 diffopts.upgrade = True
304 elif self.gitmode == 'keep':
304 elif self.gitmode == 'keep':
305 pass
305 pass
306 elif self.gitmode in ('yes', 'no'):
306 elif self.gitmode in ('yes', 'no'):
307 diffopts.git = self.gitmode == 'yes'
307 diffopts.git = self.gitmode == 'yes'
308 else:
308 else:
309 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
309 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
310 ' got %s') % self.gitmode)
310 ' got %s') % self.gitmode)
311 if patchfn:
311 if patchfn:
312 diffopts = self.patchopts(diffopts, patchfn)
312 diffopts = self.patchopts(diffopts, patchfn)
313 return diffopts
313 return diffopts
314
314
315 def patchopts(self, diffopts, *patches):
315 def patchopts(self, diffopts, *patches):
316 """Return a copy of input diff options with git set to true if
316 """Return a copy of input diff options with git set to true if
317 referenced patch is a git patch and should be preserved as such.
317 referenced patch is a git patch and should be preserved as such.
318 """
318 """
319 diffopts = diffopts.copy()
319 diffopts = diffopts.copy()
320 if not diffopts.git and self.gitmode == 'keep':
320 if not diffopts.git and self.gitmode == 'keep':
321 for patchfn in patches:
321 for patchfn in patches:
322 patchf = self.opener(patchfn, 'r')
322 patchf = self.opener(patchfn, 'r')
323 # if the patch was a git patch, refresh it as a git patch
323 # if the patch was a git patch, refresh it as a git patch
324 for line in patchf:
324 for line in patchf:
325 if line.startswith('diff --git'):
325 if line.startswith('diff --git'):
326 diffopts.git = True
326 diffopts.git = True
327 break
327 break
328 patchf.close()
328 patchf.close()
329 return diffopts
329 return diffopts
330
330
331 def join(self, *p):
331 def join(self, *p):
332 return os.path.join(self.path, *p)
332 return os.path.join(self.path, *p)
333
333
334 def find_series(self, patch):
334 def find_series(self, patch):
335 pre = re.compile("(\s*)([^#]+)")
335 pre = re.compile("(\s*)([^#]+)")
336 index = 0
336 index = 0
337 for l in self.full_series:
337 for l in self.full_series:
338 m = pre.match(l)
338 m = pre.match(l)
339 if m:
339 if m:
340 s = m.group(2)
340 s = m.group(2)
341 s = s.rstrip()
341 s = s.rstrip()
342 if s == patch:
342 if s == patch:
343 return index
343 return index
344 index += 1
344 index += 1
345 return None
345 return None
346
346
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348
348
349 def parse_series(self):
349 def parse_series(self):
350 self.series = []
350 self.series = []
351 self.series_guards = []
351 self.series_guards = []
352 for l in self.full_series:
352 for l in self.full_series:
353 h = l.find('#')
353 h = l.find('#')
354 if h == -1:
354 if h == -1:
355 patch = l
355 patch = l
356 comment = ''
356 comment = ''
357 elif h == 0:
357 elif h == 0:
358 continue
358 continue
359 else:
359 else:
360 patch = l[:h]
360 patch = l[:h]
361 comment = l[h:]
361 comment = l[h:]
362 patch = patch.strip()
362 patch = patch.strip()
363 if patch:
363 if patch:
364 if patch in self.series:
364 if patch in self.series:
365 raise util.Abort(_('%s appears more than once in %s') %
365 raise util.Abort(_('%s appears more than once in %s') %
366 (patch, self.join(self.series_path)))
366 (patch, self.join(self.series_path)))
367 self.series.append(patch)
367 self.series.append(patch)
368 self.series_guards.append(self.guard_re.findall(comment))
368 self.series_guards.append(self.guard_re.findall(comment))
369
369
370 def check_guard(self, guard):
370 def check_guard(self, guard):
371 if not guard:
371 if not guard:
372 return _('guard cannot be an empty string')
372 return _('guard cannot be an empty string')
373 bad_chars = '# \t\r\n\f'
373 bad_chars = '# \t\r\n\f'
374 first = guard[0]
374 first = guard[0]
375 if first in '-+':
375 if first in '-+':
376 return (_('guard %r starts with invalid character: %r') %
376 return (_('guard %r starts with invalid character: %r') %
377 (guard, first))
377 (guard, first))
378 for c in bad_chars:
378 for c in bad_chars:
379 if c in guard:
379 if c in guard:
380 return _('invalid character in guard %r: %r') % (guard, c)
380 return _('invalid character in guard %r: %r') % (guard, c)
381
381
382 def set_active(self, guards):
382 def set_active(self, guards):
383 for guard in guards:
383 for guard in guards:
384 bad = self.check_guard(guard)
384 bad = self.check_guard(guard)
385 if bad:
385 if bad:
386 raise util.Abort(bad)
386 raise util.Abort(bad)
387 guards = sorted(set(guards))
387 guards = sorted(set(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 self.active_guards = guards
389 self.active_guards = guards
390 self.guards_dirty = True
390 self.guards_dirty = True
391
391
392 def active(self):
392 def active(self):
393 if self.active_guards is None:
393 if self.active_guards is None:
394 self.active_guards = []
394 self.active_guards = []
395 try:
395 try:
396 guards = self.opener(self.guards_path).read().split()
396 guards = self.opener(self.guards_path).read().split()
397 except IOError, err:
397 except IOError, err:
398 if err.errno != errno.ENOENT:
398 if err.errno != errno.ENOENT:
399 raise
399 raise
400 guards = []
400 guards = []
401 for i, guard in enumerate(guards):
401 for i, guard in enumerate(guards):
402 bad = self.check_guard(guard)
402 bad = self.check_guard(guard)
403 if bad:
403 if bad:
404 self.ui.warn('%s:%d: %s\n' %
404 self.ui.warn('%s:%d: %s\n' %
405 (self.join(self.guards_path), i + 1, bad))
405 (self.join(self.guards_path), i + 1, bad))
406 else:
406 else:
407 self.active_guards.append(guard)
407 self.active_guards.append(guard)
408 return self.active_guards
408 return self.active_guards
409
409
410 def set_guards(self, idx, guards):
410 def set_guards(self, idx, guards):
411 for g in guards:
411 for g in guards:
412 if len(g) < 2:
412 if len(g) < 2:
413 raise util.Abort(_('guard %r too short') % g)
413 raise util.Abort(_('guard %r too short') % g)
414 if g[0] not in '-+':
414 if g[0] not in '-+':
415 raise util.Abort(_('guard %r starts with invalid char') % g)
415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 bad = self.check_guard(g[1:])
416 bad = self.check_guard(g[1:])
417 if bad:
417 if bad:
418 raise util.Abort(bad)
418 raise util.Abort(bad)
419 drop = self.guard_re.sub('', self.full_series[idx])
419 drop = self.guard_re.sub('', self.full_series[idx])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 self.parse_series()
421 self.parse_series()
422 self.series_dirty = True
422 self.series_dirty = True
423
423
424 def pushable(self, idx):
424 def pushable(self, idx):
425 if isinstance(idx, str):
425 if isinstance(idx, str):
426 idx = self.series.index(idx)
426 idx = self.series.index(idx)
427 patchguards = self.series_guards[idx]
427 patchguards = self.series_guards[idx]
428 if not patchguards:
428 if not patchguards:
429 return True, None
429 return True, None
430 guards = self.active()
430 guards = self.active()
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 if exactneg:
432 if exactneg:
433 return False, exactneg[0]
433 return False, exactneg[0]
434 pos = [g for g in patchguards if g[0] == '+']
434 pos = [g for g in patchguards if g[0] == '+']
435 exactpos = [g for g in pos if g[1:] in guards]
435 exactpos = [g for g in pos if g[1:] in guards]
436 if pos:
436 if pos:
437 if exactpos:
437 if exactpos:
438 return True, exactpos[0]
438 return True, exactpos[0]
439 return False, pos
439 return False, pos
440 return True, ''
440 return True, ''
441
441
442 def explain_pushable(self, idx, all_patches=False):
442 def explain_pushable(self, idx, all_patches=False):
443 write = all_patches and self.ui.write or self.ui.warn
443 write = all_patches and self.ui.write or self.ui.warn
444 if all_patches or self.ui.verbose:
444 if all_patches or self.ui.verbose:
445 if isinstance(idx, str):
445 if isinstance(idx, str):
446 idx = self.series.index(idx)
446 idx = self.series.index(idx)
447 pushable, why = self.pushable(idx)
447 pushable, why = self.pushable(idx)
448 if all_patches and pushable:
448 if all_patches and pushable:
449 if why is None:
449 if why is None:
450 write(_('allowing %s - no guards in effect\n') %
450 write(_('allowing %s - no guards in effect\n') %
451 self.series[idx])
451 self.series[idx])
452 else:
452 else:
453 if not why:
453 if not why:
454 write(_('allowing %s - no matching negative guards\n') %
454 write(_('allowing %s - no matching negative guards\n') %
455 self.series[idx])
455 self.series[idx])
456 else:
456 else:
457 write(_('allowing %s - guarded by %r\n') %
457 write(_('allowing %s - guarded by %r\n') %
458 (self.series[idx], why))
458 (self.series[idx], why))
459 if not pushable:
459 if not pushable:
460 if why:
460 if why:
461 write(_('skipping %s - guarded by %r\n') %
461 write(_('skipping %s - guarded by %r\n') %
462 (self.series[idx], why))
462 (self.series[idx], why))
463 else:
463 else:
464 write(_('skipping %s - no matching guards\n') %
464 write(_('skipping %s - no matching guards\n') %
465 self.series[idx])
465 self.series[idx])
466
466
467 def save_dirty(self):
467 def save_dirty(self):
468 def write_list(items, path):
468 def write_list(items, path):
469 fp = self.opener(path, 'w')
469 fp = self.opener(path, 'w')
470 for i in items:
470 for i in items:
471 fp.write("%s\n" % i)
471 fp.write("%s\n" % i)
472 fp.close()
472 fp.close()
473 if self.applied_dirty:
473 if self.applied_dirty:
474 write_list(map(str, self.applied), self.status_path)
474 write_list(map(str, self.applied), self.status_path)
475 if self.series_dirty:
475 if self.series_dirty:
476 write_list(self.full_series, self.series_path)
476 write_list(self.full_series, self.series_path)
477 if self.guards_dirty:
477 if self.guards_dirty:
478 write_list(self.active_guards, self.guards_path)
478 write_list(self.active_guards, self.guards_path)
479
479
480 def removeundo(self, repo):
480 def removeundo(self, repo):
481 undo = repo.sjoin('undo')
481 undo = repo.sjoin('undo')
482 if not os.path.exists(undo):
482 if not os.path.exists(undo):
483 return
483 return
484 try:
484 try:
485 os.unlink(undo)
485 os.unlink(undo)
486 except OSError, inst:
486 except OSError, inst:
487 self.ui.warn(_('error removing undo: %s\n') % str(inst))
487 self.ui.warn(_('error removing undo: %s\n') % str(inst))
488
488
489 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
489 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
490 fp=None, changes=None, opts={}):
490 fp=None, changes=None, opts={}):
491 stat = opts.get('stat')
491 stat = opts.get('stat')
492 if stat:
492 if stat:
493 opts['unified'] = '0'
493 opts['unified'] = '0'
494
494
495 m = cmdutil.match(repo, files, opts)
495 m = cmdutil.match(repo, files, opts)
496 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
496 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
497 write = fp is None and repo.ui.write or fp.write
497 write = fp is None and repo.ui.write or fp.write
498 if stat:
498 if stat:
499 width = self.ui.interactive() and util.termwidth() or 80
499 width = self.ui.interactive() and util.termwidth() or 80
500 write(patch.diffstat(util.iterlines(chunks), width=width,
500 write(patch.diffstat(util.iterlines(chunks), width=width,
501 git=diffopts.git))
501 git=diffopts.git))
502 else:
502 else:
503 for chunk in chunks:
503 for chunk in chunks:
504 write(chunk)
504 write(chunk)
505
505
506 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
506 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
507 # first try just applying the patch
507 # first try just applying the patch
508 (err, n) = self.apply(repo, [patch], update_status=False,
508 (err, n) = self.apply(repo, [patch], update_status=False,
509 strict=True, merge=rev)
509 strict=True, merge=rev)
510
510
511 if err == 0:
511 if err == 0:
512 return (err, n)
512 return (err, n)
513
513
514 if n is None:
514 if n is None:
515 raise util.Abort(_("apply failed for patch %s") % patch)
515 raise util.Abort(_("apply failed for patch %s") % patch)
516
516
517 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
517 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
518
518
519 # apply failed, strip away that rev and merge.
519 # apply failed, strip away that rev and merge.
520 hg.clean(repo, head)
520 hg.clean(repo, head)
521 self.strip(repo, n, update=False, backup='strip')
521 self.strip(repo, n, update=False, backup='strip')
522
522
523 ctx = repo[rev]
523 ctx = repo[rev]
524 ret = hg.merge(repo, rev)
524 ret = hg.merge(repo, rev)
525 if ret:
525 if ret:
526 raise util.Abort(_("update returned %d") % ret)
526 raise util.Abort(_("update returned %d") % ret)
527 n = repo.commit(ctx.description(), ctx.user(), force=True)
527 n = repo.commit(ctx.description(), ctx.user(), force=True)
528 if n is None:
528 if n is None:
529 raise util.Abort(_("repo commit failed"))
529 raise util.Abort(_("repo commit failed"))
530 try:
530 try:
531 ph = patchheader(mergeq.join(patch), self.plainmode)
531 ph = patchheader(mergeq.join(patch), self.plainmode)
532 except:
532 except:
533 raise util.Abort(_("unable to read %s") % patch)
533 raise util.Abort(_("unable to read %s") % patch)
534
534
535 diffopts = self.patchopts(diffopts, patch)
535 diffopts = self.patchopts(diffopts, patch)
536 patchf = self.opener(patch, "w")
536 patchf = self.opener(patch, "w")
537 comments = str(ph)
537 comments = str(ph)
538 if comments:
538 if comments:
539 patchf.write(comments)
539 patchf.write(comments)
540 self.printdiff(repo, diffopts, head, n, fp=patchf)
540 self.printdiff(repo, diffopts, head, n, fp=patchf)
541 patchf.close()
541 patchf.close()
542 self.removeundo(repo)
542 self.removeundo(repo)
543 return (0, n)
543 return (0, n)
544
544
545 def qparents(self, repo, rev=None):
545 def qparents(self, repo, rev=None):
546 if rev is None:
546 if rev is None:
547 (p1, p2) = repo.dirstate.parents()
547 (p1, p2) = repo.dirstate.parents()
548 if p2 == nullid:
548 if p2 == nullid:
549 return p1
549 return p1
550 if len(self.applied) == 0:
550 if len(self.applied) == 0:
551 return None
551 return None
552 return bin(self.applied[-1].rev)
552 return bin(self.applied[-1].rev)
553 pp = repo.changelog.parents(rev)
553 pp = repo.changelog.parents(rev)
554 if pp[1] != nullid:
554 if pp[1] != nullid:
555 arevs = [x.rev for x in self.applied]
555 arevs = [x.rev for x in self.applied]
556 p0 = hex(pp[0])
556 p0 = hex(pp[0])
557 p1 = hex(pp[1])
557 p1 = hex(pp[1])
558 if p0 in arevs:
558 if p0 in arevs:
559 return pp[0]
559 return pp[0]
560 if p1 in arevs:
560 if p1 in arevs:
561 return pp[1]
561 return pp[1]
562 return pp[0]
562 return pp[0]
563
563
564 def mergepatch(self, repo, mergeq, series, diffopts):
564 def mergepatch(self, repo, mergeq, series, diffopts):
565 if len(self.applied) == 0:
565 if len(self.applied) == 0:
566 # each of the patches merged in will have two parents. This
566 # each of the patches merged in will have two parents. This
567 # can confuse the qrefresh, qdiff, and strip code because it
567 # can confuse the qrefresh, qdiff, and strip code because it
568 # needs to know which parent is actually in the patch queue.
568 # needs to know which parent is actually in the patch queue.
569 # so, we insert a merge marker with only one parent. This way
569 # so, we insert a merge marker with only one parent. This way
570 # the first patch in the queue is never a merge patch
570 # the first patch in the queue is never a merge patch
571 #
571 #
572 pname = ".hg.patches.merge.marker"
572 pname = ".hg.patches.merge.marker"
573 n = repo.commit('[mq]: merge marker', force=True)
573 n = repo.commit('[mq]: merge marker', force=True)
574 self.removeundo(repo)
574 self.removeundo(repo)
575 self.applied.append(statusentry(hex(n), pname))
575 self.applied.append(statusentry(hex(n), pname))
576 self.applied_dirty = 1
576 self.applied_dirty = 1
577
577
578 head = self.qparents(repo)
578 head = self.qparents(repo)
579
579
580 for patch in series:
580 for patch in series:
581 patch = mergeq.lookup(patch, strict=True)
581 patch = mergeq.lookup(patch, strict=True)
582 if not patch:
582 if not patch:
583 self.ui.warn(_("patch %s does not exist\n") % patch)
583 self.ui.warn(_("patch %s does not exist\n") % patch)
584 return (1, None)
584 return (1, None)
585 pushable, reason = self.pushable(patch)
585 pushable, reason = self.pushable(patch)
586 if not pushable:
586 if not pushable:
587 self.explain_pushable(patch, all_patches=True)
587 self.explain_pushable(patch, all_patches=True)
588 continue
588 continue
589 info = mergeq.isapplied(patch)
589 info = mergeq.isapplied(patch)
590 if not info:
590 if not info:
591 self.ui.warn(_("patch %s is not applied\n") % patch)
591 self.ui.warn(_("patch %s is not applied\n") % patch)
592 return (1, None)
592 return (1, None)
593 rev = bin(info[1])
593 rev = bin(info[1])
594 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
594 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
595 if head:
595 if head:
596 self.applied.append(statusentry(hex(head), patch))
596 self.applied.append(statusentry(hex(head), patch))
597 self.applied_dirty = 1
597 self.applied_dirty = 1
598 if err:
598 if err:
599 return (err, head)
599 return (err, head)
600 self.save_dirty()
600 self.save_dirty()
601 return (0, head)
601 return (0, head)
602
602
603 def patch(self, repo, patchfile):
603 def patch(self, repo, patchfile):
604 '''Apply patchfile to the working directory.
604 '''Apply patchfile to the working directory.
605 patchfile: name of patch file'''
605 patchfile: name of patch file'''
606 files = {}
606 files = {}
607 try:
607 try:
608 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
608 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
609 files=files, eolmode=None)
609 files=files, eolmode=None)
610 except Exception, inst:
610 except Exception, inst:
611 self.ui.note(str(inst) + '\n')
611 self.ui.note(str(inst) + '\n')
612 if not self.ui.verbose:
612 if not self.ui.verbose:
613 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
613 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
614 return (False, files, False)
614 return (False, files, False)
615
615
616 return (True, files, fuzz)
616 return (True, files, fuzz)
617
617
618 def apply(self, repo, series, list=False, update_status=True,
618 def apply(self, repo, series, list=False, update_status=True,
619 strict=False, patchdir=None, merge=None, all_files={}):
619 strict=False, patchdir=None, merge=None, all_files={}):
620 wlock = lock = tr = None
620 wlock = lock = tr = None
621 try:
621 try:
622 wlock = repo.wlock()
622 wlock = repo.wlock()
623 lock = repo.lock()
623 lock = repo.lock()
624 tr = repo.transaction()
624 tr = repo.transaction()
625 try:
625 try:
626 ret = self._apply(repo, series, list, update_status,
626 ret = self._apply(repo, series, list, update_status,
627 strict, patchdir, merge, all_files=all_files)
627 strict, patchdir, merge, all_files=all_files)
628 tr.close()
628 tr.close()
629 self.save_dirty()
629 self.save_dirty()
630 return ret
630 return ret
631 except:
631 except:
632 try:
632 try:
633 tr.abort()
633 tr.abort()
634 finally:
634 finally:
635 repo.invalidate()
635 repo.invalidate()
636 repo.dirstate.invalidate()
636 repo.dirstate.invalidate()
637 raise
637 raise
638 finally:
638 finally:
639 del tr
639 del tr
640 release(lock, wlock)
640 release(lock, wlock)
641 self.removeundo(repo)
641 self.removeundo(repo)
642
642
643 def _apply(self, repo, series, list=False, update_status=True,
643 def _apply(self, repo, series, list=False, update_status=True,
644 strict=False, patchdir=None, merge=None, all_files={}):
644 strict=False, patchdir=None, merge=None, all_files={}):
645 '''returns (error, hash)
645 '''returns (error, hash)
646 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
646 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
647 # TODO unify with commands.py
647 # TODO unify with commands.py
648 if not patchdir:
648 if not patchdir:
649 patchdir = self.path
649 patchdir = self.path
650 err = 0
650 err = 0
651 n = None
651 n = None
652 for patchname in series:
652 for patchname in series:
653 pushable, reason = self.pushable(patchname)
653 pushable, reason = self.pushable(patchname)
654 if not pushable:
654 if not pushable:
655 self.explain_pushable(patchname, all_patches=True)
655 self.explain_pushable(patchname, all_patches=True)
656 continue
656 continue
657 self.ui.status(_("applying %s\n") % patchname)
657 self.ui.status(_("applying %s\n") % patchname)
658 pf = os.path.join(patchdir, patchname)
658 pf = os.path.join(patchdir, patchname)
659
659
660 try:
660 try:
661 ph = patchheader(self.join(patchname), self.plainmode)
661 ph = patchheader(self.join(patchname), self.plainmode)
662 except:
662 except:
663 self.ui.warn(_("unable to read %s\n") % patchname)
663 self.ui.warn(_("unable to read %s\n") % patchname)
664 err = 1
664 err = 1
665 break
665 break
666
666
667 message = ph.message
667 message = ph.message
668 if not message:
668 if not message:
669 message = "imported patch %s\n" % patchname
669 message = "imported patch %s\n" % patchname
670 else:
670 else:
671 if list:
671 if list:
672 message.append("\nimported patch %s" % patchname)
672 message.append("\nimported patch %s" % patchname)
673 message = '\n'.join(message)
673 message = '\n'.join(message)
674
674
675 if ph.haspatch:
675 if ph.haspatch:
676 (patcherr, files, fuzz) = self.patch(repo, pf)
676 (patcherr, files, fuzz) = self.patch(repo, pf)
677 all_files.update(files)
677 all_files.update(files)
678 patcherr = not patcherr
678 patcherr = not patcherr
679 else:
679 else:
680 self.ui.warn(_("patch %s is empty\n") % patchname)
680 self.ui.warn(_("patch %s is empty\n") % patchname)
681 patcherr, files, fuzz = 0, [], 0
681 patcherr, files, fuzz = 0, [], 0
682
682
683 if merge and files:
683 if merge and files:
684 # Mark as removed/merged and update dirstate parent info
684 # Mark as removed/merged and update dirstate parent info
685 removed = []
685 removed = []
686 merged = []
686 merged = []
687 for f in files:
687 for f in files:
688 if os.path.exists(repo.wjoin(f)):
688 if os.path.exists(repo.wjoin(f)):
689 merged.append(f)
689 merged.append(f)
690 else:
690 else:
691 removed.append(f)
691 removed.append(f)
692 for f in removed:
692 for f in removed:
693 repo.dirstate.remove(f)
693 repo.dirstate.remove(f)
694 for f in merged:
694 for f in merged:
695 repo.dirstate.merge(f)
695 repo.dirstate.merge(f)
696 p1, p2 = repo.dirstate.parents()
696 p1, p2 = repo.dirstate.parents()
697 repo.dirstate.setparents(p1, merge)
697 repo.dirstate.setparents(p1, merge)
698
698
699 files = patch.updatedir(self.ui, repo, files)
699 files = patch.updatedir(self.ui, repo, files)
700 match = cmdutil.matchfiles(repo, files or [])
700 match = cmdutil.matchfiles(repo, files or [])
701 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
701 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
702
702
703 if n is None:
703 if n is None:
704 raise util.Abort(_("repo commit failed"))
704 raise util.Abort(_("repo commit failed"))
705
705
706 if update_status:
706 if update_status:
707 self.applied.append(statusentry(hex(n), patchname))
707 self.applied.append(statusentry(hex(n), patchname))
708
708
709 if patcherr:
709 if patcherr:
710 self.ui.warn(_("patch failed, rejects left in working dir\n"))
710 self.ui.warn(_("patch failed, rejects left in working dir\n"))
711 err = 2
711 err = 2
712 break
712 break
713
713
714 if fuzz and strict:
714 if fuzz and strict:
715 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
715 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
716 err = 3
716 err = 3
717 break
717 break
718 return (err, n)
718 return (err, n)
719
719
720 def _cleanup(self, patches, numrevs, keep=False):
720 def _cleanup(self, patches, numrevs, keep=False):
721 if not keep:
721 if not keep:
722 r = self.qrepo()
722 r = self.qrepo()
723 if r:
723 if r:
724 r.remove(patches, True)
724 r.remove(patches, True)
725 else:
725 else:
726 for p in patches:
726 for p in patches:
727 os.unlink(self.join(p))
727 os.unlink(self.join(p))
728
728
729 if numrevs:
729 if numrevs:
730 del self.applied[:numrevs]
730 del self.applied[:numrevs]
731 self.applied_dirty = 1
731 self.applied_dirty = 1
732
732
733 for i in sorted([self.find_series(p) for p in patches], reverse=True):
733 for i in sorted([self.find_series(p) for p in patches], reverse=True):
734 del self.full_series[i]
734 del self.full_series[i]
735 self.parse_series()
735 self.parse_series()
736 self.series_dirty = 1
736 self.series_dirty = 1
737
737
738 def _revpatches(self, repo, revs):
738 def _revpatches(self, repo, revs):
739 firstrev = repo[self.applied[0].rev].rev()
739 firstrev = repo[self.applied[0].rev].rev()
740 patches = []
740 patches = []
741 for i, rev in enumerate(revs):
741 for i, rev in enumerate(revs):
742
742
743 if rev < firstrev:
743 if rev < firstrev:
744 raise util.Abort(_('revision %d is not managed') % rev)
744 raise util.Abort(_('revision %d is not managed') % rev)
745
745
746 ctx = repo[rev]
746 ctx = repo[rev]
747 base = bin(self.applied[i].rev)
747 base = bin(self.applied[i].rev)
748 if ctx.node() != base:
748 if ctx.node() != base:
749 msg = _('cannot delete revision %d above applied patches')
749 msg = _('cannot delete revision %d above applied patches')
750 raise util.Abort(msg % rev)
750 raise util.Abort(msg % rev)
751
751
752 patch = self.applied[i].name
752 patch = self.applied[i].name
753 for fmt in ('[mq]: %s', 'imported patch %s'):
753 for fmt in ('[mq]: %s', 'imported patch %s'):
754 if ctx.description() == fmt % patch:
754 if ctx.description() == fmt % patch:
755 msg = _('patch %s finalized without changeset message\n')
755 msg = _('patch %s finalized without changeset message\n')
756 repo.ui.status(msg % patch)
756 repo.ui.status(msg % patch)
757 break
757 break
758
758
759 patches.append(patch)
759 patches.append(patch)
760 return patches
760 return patches
761
761
762 def finish(self, repo, revs):
762 def finish(self, repo, revs):
763 patches = self._revpatches(repo, sorted(revs))
763 patches = self._revpatches(repo, sorted(revs))
764 self._cleanup(patches, len(patches))
764 self._cleanup(patches, len(patches))
765
765
766 def delete(self, repo, patches, opts):
766 def delete(self, repo, patches, opts):
767 if not patches and not opts.get('rev'):
767 if not patches and not opts.get('rev'):
768 raise util.Abort(_('qdelete requires at least one revision or '
768 raise util.Abort(_('qdelete requires at least one revision or '
769 'patch name'))
769 'patch name'))
770
770
771 realpatches = []
771 realpatches = []
772 for patch in patches:
772 for patch in patches:
773 patch = self.lookup(patch, strict=True)
773 patch = self.lookup(patch, strict=True)
774 info = self.isapplied(patch)
774 info = self.isapplied(patch)
775 if info:
775 if info:
776 raise util.Abort(_("cannot delete applied patch %s") % patch)
776 raise util.Abort(_("cannot delete applied patch %s") % patch)
777 if patch not in self.series:
777 if patch not in self.series:
778 raise util.Abort(_("patch %s not in series file") % patch)
778 raise util.Abort(_("patch %s not in series file") % patch)
779 realpatches.append(patch)
779 realpatches.append(patch)
780
780
781 numrevs = 0
781 numrevs = 0
782 if opts.get('rev'):
782 if opts.get('rev'):
783 if not self.applied:
783 if not self.applied:
784 raise util.Abort(_('no patches applied'))
784 raise util.Abort(_('no patches applied'))
785 revs = cmdutil.revrange(repo, opts['rev'])
785 revs = cmdutil.revrange(repo, opts['rev'])
786 if len(revs) > 1 and revs[0] > revs[1]:
786 if len(revs) > 1 and revs[0] > revs[1]:
787 revs.reverse()
787 revs.reverse()
788 revpatches = self._revpatches(repo, revs)
788 revpatches = self._revpatches(repo, revs)
789 realpatches += revpatches
789 realpatches += revpatches
790 numrevs = len(revpatches)
790 numrevs = len(revpatches)
791
791
792 self._cleanup(realpatches, numrevs, opts.get('keep'))
792 self._cleanup(realpatches, numrevs, opts.get('keep'))
793
793
794 def check_toppatch(self, repo):
794 def check_toppatch(self, repo):
795 if len(self.applied) > 0:
795 if len(self.applied) > 0:
796 top = bin(self.applied[-1].rev)
796 top = bin(self.applied[-1].rev)
797 patch = self.applied[-1].name
797 patch = self.applied[-1].name
798 pp = repo.dirstate.parents()
798 pp = repo.dirstate.parents()
799 if top not in pp:
799 if top not in pp:
800 raise util.Abort(_("working directory revision is not qtip"))
800 raise util.Abort(_("working directory revision is not qtip"))
801 return top, patch
801 return top, patch
802 return None, None
802 return None, None
803
803
804 def check_localchanges(self, repo, force=False, refresh=True):
804 def check_localchanges(self, repo, force=False, refresh=True):
805 m, a, r, d = repo.status()[:4]
805 m, a, r, d = repo.status()[:4]
806 if (m or a or r or d) and not force:
806 if (m or a or r or d) and not force:
807 if refresh:
807 if refresh:
808 raise util.Abort(_("local changes found, refresh first"))
808 raise util.Abort(_("local changes found, refresh first"))
809 else:
809 else:
810 raise util.Abort(_("local changes found"))
810 raise util.Abort(_("local changes found"))
811 return m, a, r, d
811 return m, a, r, d
812
812
813 _reserved = ('series', 'status', 'guards')
813 _reserved = ('series', 'status', 'guards')
814 def check_reserved_name(self, name):
814 def check_reserved_name(self, name):
815 if (name in self._reserved or name.startswith('.hg')
815 if (name in self._reserved or name.startswith('.hg')
816 or name.startswith('.mq')):
816 or name.startswith('.mq')):
817 raise util.Abort(_('"%s" cannot be used as the name of a patch')
817 raise util.Abort(_('"%s" cannot be used as the name of a patch')
818 % name)
818 % name)
819
819
820 def new(self, repo, patchfn, *pats, **opts):
820 def new(self, repo, patchfn, *pats, **opts):
821 """options:
821 """options:
822 msg: a string or a no-argument function returning a string
822 msg: a string or a no-argument function returning a string
823 """
823 """
824 msg = opts.get('msg')
824 msg = opts.get('msg')
825 user = opts.get('user')
825 user = opts.get('user')
826 date = opts.get('date')
826 date = opts.get('date')
827 if date:
827 if date:
828 date = util.parsedate(date)
828 date = util.parsedate(date)
829 diffopts = self.diffopts({'git': opts.get('git')})
829 diffopts = self.diffopts({'git': opts.get('git')})
830 self.check_reserved_name(patchfn)
830 self.check_reserved_name(patchfn)
831 if os.path.exists(self.join(patchfn)):
831 if os.path.exists(self.join(patchfn)):
832 raise util.Abort(_('patch "%s" already exists') % patchfn)
832 raise util.Abort(_('patch "%s" already exists') % patchfn)
833 if opts.get('include') or opts.get('exclude') or pats:
833 if opts.get('include') or opts.get('exclude') or pats:
834 match = cmdutil.match(repo, pats, opts)
834 match = cmdutil.match(repo, pats, opts)
835 # detect missing files in pats
835 # detect missing files in pats
836 def badfn(f, msg):
836 def badfn(f, msg):
837 raise util.Abort('%s: %s' % (f, msg))
837 raise util.Abort('%s: %s' % (f, msg))
838 match.bad = badfn
838 match.bad = badfn
839 m, a, r, d = repo.status(match=match)[:4]
839 m, a, r, d = repo.status(match=match)[:4]
840 else:
840 else:
841 m, a, r, d = self.check_localchanges(repo, force=True)
841 m, a, r, d = self.check_localchanges(repo, force=True)
842 match = cmdutil.matchfiles(repo, m + a + r)
842 match = cmdutil.matchfiles(repo, m + a + r)
843 if len(repo[None].parents()) > 1:
843 if len(repo[None].parents()) > 1:
844 raise util.Abort(_('cannot manage merge changesets'))
844 raise util.Abort(_('cannot manage merge changesets'))
845 commitfiles = m + a + r
845 commitfiles = m + a + r
846 self.check_toppatch(repo)
846 self.check_toppatch(repo)
847 insert = self.full_series_end()
847 insert = self.full_series_end()
848 wlock = repo.wlock()
848 wlock = repo.wlock()
849 try:
849 try:
850 # if patch file write fails, abort early
850 # if patch file write fails, abort early
851 p = self.opener(patchfn, "w")
851 p = self.opener(patchfn, "w")
852 try:
852 try:
853 if self.plainmode:
853 if self.plainmode:
854 if user:
854 if user:
855 p.write("From: " + user + "\n")
855 p.write("From: " + user + "\n")
856 if not date:
856 if not date:
857 p.write("\n")
857 p.write("\n")
858 if date:
858 if date:
859 p.write("Date: %d %d\n\n" % date)
859 p.write("Date: %d %d\n\n" % date)
860 else:
860 else:
861 p.write("# HG changeset patch\n")
861 p.write("# HG changeset patch\n")
862 p.write("# Parent " + hex(repo[None].parents()[0].node()) + "\n")
862 p.write("# Parent "
863 + hex(repo[None].parents()[0].node()) + "\n")
863 if user:
864 if user:
864 p.write("# User " + user + "\n")
865 p.write("# User " + user + "\n")
865 if date:
866 if date:
866 p.write("# Date %s %s\n\n" % date)
867 p.write("# Date %s %s\n\n" % date)
867 if hasattr(msg, '__call__'):
868 if hasattr(msg, '__call__'):
868 msg = msg()
869 msg = msg()
869 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
870 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
870 n = repo.commit(commitmsg, user, date, match=match, force=True)
871 n = repo.commit(commitmsg, user, date, match=match, force=True)
871 if n is None:
872 if n is None:
872 raise util.Abort(_("repo commit failed"))
873 raise util.Abort(_("repo commit failed"))
873 try:
874 try:
874 self.full_series[insert:insert] = [patchfn]
875 self.full_series[insert:insert] = [patchfn]
875 self.applied.append(statusentry(hex(n), patchfn))
876 self.applied.append(statusentry(hex(n), patchfn))
876 self.parse_series()
877 self.parse_series()
877 self.series_dirty = 1
878 self.series_dirty = 1
878 self.applied_dirty = 1
879 self.applied_dirty = 1
879 if msg:
880 if msg:
880 msg = msg + "\n\n"
881 msg = msg + "\n\n"
881 p.write(msg)
882 p.write(msg)
882 if commitfiles:
883 if commitfiles:
883 parent = self.qparents(repo, n)
884 parent = self.qparents(repo, n)
884 chunks = patch.diff(repo, node1=parent, node2=n,
885 chunks = patch.diff(repo, node1=parent, node2=n,
885 match=match, opts=diffopts)
886 match=match, opts=diffopts)
886 for chunk in chunks:
887 for chunk in chunks:
887 p.write(chunk)
888 p.write(chunk)
888 p.close()
889 p.close()
889 wlock.release()
890 wlock.release()
890 wlock = None
891 wlock = None
891 r = self.qrepo()
892 r = self.qrepo()
892 if r:
893 if r:
893 r.add([patchfn])
894 r.add([patchfn])
894 except:
895 except:
895 repo.rollback()
896 repo.rollback()
896 raise
897 raise
897 except Exception:
898 except Exception:
898 patchpath = self.join(patchfn)
899 patchpath = self.join(patchfn)
899 try:
900 try:
900 os.unlink(patchpath)
901 os.unlink(patchpath)
901 except:
902 except:
902 self.ui.warn(_('error unlinking %s\n') % patchpath)
903 self.ui.warn(_('error unlinking %s\n') % patchpath)
903 raise
904 raise
904 self.removeundo(repo)
905 self.removeundo(repo)
905 finally:
906 finally:
906 release(wlock)
907 release(wlock)
907
908
908 def strip(self, repo, rev, update=True, backup="all", force=None):
909 def strip(self, repo, rev, update=True, backup="all", force=None):
909 wlock = lock = None
910 wlock = lock = None
910 try:
911 try:
911 wlock = repo.wlock()
912 wlock = repo.wlock()
912 lock = repo.lock()
913 lock = repo.lock()
913
914
914 if update:
915 if update:
915 self.check_localchanges(repo, force=force, refresh=False)
916 self.check_localchanges(repo, force=force, refresh=False)
916 urev = self.qparents(repo, rev)
917 urev = self.qparents(repo, rev)
917 hg.clean(repo, urev)
918 hg.clean(repo, urev)
918 repo.dirstate.write()
919 repo.dirstate.write()
919
920
920 self.removeundo(repo)
921 self.removeundo(repo)
921 repair.strip(self.ui, repo, rev, backup)
922 repair.strip(self.ui, repo, rev, backup)
922 # strip may have unbundled a set of backed up revisions after
923 # strip may have unbundled a set of backed up revisions after
923 # the actual strip
924 # the actual strip
924 self.removeundo(repo)
925 self.removeundo(repo)
925 finally:
926 finally:
926 release(lock, wlock)
927 release(lock, wlock)
927
928
928 def isapplied(self, patch):
929 def isapplied(self, patch):
929 """returns (index, rev, patch)"""
930 """returns (index, rev, patch)"""
930 for i, a in enumerate(self.applied):
931 for i, a in enumerate(self.applied):
931 if a.name == patch:
932 if a.name == patch:
932 return (i, a.rev, a.name)
933 return (i, a.rev, a.name)
933 return None
934 return None
934
935
935 # if the exact patch name does not exist, we try a few
936 # if the exact patch name does not exist, we try a few
936 # variations. If strict is passed, we try only #1
937 # variations. If strict is passed, we try only #1
937 #
938 #
938 # 1) a number to indicate an offset in the series file
939 # 1) a number to indicate an offset in the series file
939 # 2) a unique substring of the patch name was given
940 # 2) a unique substring of the patch name was given
940 # 3) patchname[-+]num to indicate an offset in the series file
941 # 3) patchname[-+]num to indicate an offset in the series file
941 def lookup(self, patch, strict=False):
942 def lookup(self, patch, strict=False):
942 patch = patch and str(patch)
943 patch = patch and str(patch)
943
944
944 def partial_name(s):
945 def partial_name(s):
945 if s in self.series:
946 if s in self.series:
946 return s
947 return s
947 matches = [x for x in self.series if s in x]
948 matches = [x for x in self.series if s in x]
948 if len(matches) > 1:
949 if len(matches) > 1:
949 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
950 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
950 for m in matches:
951 for m in matches:
951 self.ui.warn(' %s\n' % m)
952 self.ui.warn(' %s\n' % m)
952 return None
953 return None
953 if matches:
954 if matches:
954 return matches[0]
955 return matches[0]
955 if len(self.series) > 0 and len(self.applied) > 0:
956 if len(self.series) > 0 and len(self.applied) > 0:
956 if s == 'qtip':
957 if s == 'qtip':
957 return self.series[self.series_end(True)-1]
958 return self.series[self.series_end(True)-1]
958 if s == 'qbase':
959 if s == 'qbase':
959 return self.series[0]
960 return self.series[0]
960 return None
961 return None
961
962
962 if patch is None:
963 if patch is None:
963 return None
964 return None
964 if patch in self.series:
965 if patch in self.series:
965 return patch
966 return patch
966
967
967 if not os.path.isfile(self.join(patch)):
968 if not os.path.isfile(self.join(patch)):
968 try:
969 try:
969 sno = int(patch)
970 sno = int(patch)
970 except (ValueError, OverflowError):
971 except (ValueError, OverflowError):
971 pass
972 pass
972 else:
973 else:
973 if -len(self.series) <= sno < len(self.series):
974 if -len(self.series) <= sno < len(self.series):
974 return self.series[sno]
975 return self.series[sno]
975
976
976 if not strict:
977 if not strict:
977 res = partial_name(patch)
978 res = partial_name(patch)
978 if res:
979 if res:
979 return res
980 return res
980 minus = patch.rfind('-')
981 minus = patch.rfind('-')
981 if minus >= 0:
982 if minus >= 0:
982 res = partial_name(patch[:minus])
983 res = partial_name(patch[:minus])
983 if res:
984 if res:
984 i = self.series.index(res)
985 i = self.series.index(res)
985 try:
986 try:
986 off = int(patch[minus + 1:] or 1)
987 off = int(patch[minus + 1:] or 1)
987 except (ValueError, OverflowError):
988 except (ValueError, OverflowError):
988 pass
989 pass
989 else:
990 else:
990 if i - off >= 0:
991 if i - off >= 0:
991 return self.series[i - off]
992 return self.series[i - off]
992 plus = patch.rfind('+')
993 plus = patch.rfind('+')
993 if plus >= 0:
994 if plus >= 0:
994 res = partial_name(patch[:plus])
995 res = partial_name(patch[:plus])
995 if res:
996 if res:
996 i = self.series.index(res)
997 i = self.series.index(res)
997 try:
998 try:
998 off = int(patch[plus + 1:] or 1)
999 off = int(patch[plus + 1:] or 1)
999 except (ValueError, OverflowError):
1000 except (ValueError, OverflowError):
1000 pass
1001 pass
1001 else:
1002 else:
1002 if i + off < len(self.series):
1003 if i + off < len(self.series):
1003 return self.series[i + off]
1004 return self.series[i + off]
1004 raise util.Abort(_("patch %s not in series") % patch)
1005 raise util.Abort(_("patch %s not in series") % patch)
1005
1006
1006 def push(self, repo, patch=None, force=False, list=False,
1007 def push(self, repo, patch=None, force=False, list=False,
1007 mergeq=None, all=False):
1008 mergeq=None, all=False):
1008 diffopts = self.diffopts()
1009 diffopts = self.diffopts()
1009 wlock = repo.wlock()
1010 wlock = repo.wlock()
1010 try:
1011 try:
1011 heads = []
1012 heads = []
1012 for b, ls in repo.branchmap().iteritems():
1013 for b, ls in repo.branchmap().iteritems():
1013 heads += ls
1014 heads += ls
1014 if not heads:
1015 if not heads:
1015 heads = [nullid]
1016 heads = [nullid]
1016 if repo.dirstate.parents()[0] not in heads:
1017 if repo.dirstate.parents()[0] not in heads:
1017 self.ui.status(_("(working directory not at a head)\n"))
1018 self.ui.status(_("(working directory not at a head)\n"))
1018
1019
1019 if not self.series:
1020 if not self.series:
1020 self.ui.warn(_('no patches in series\n'))
1021 self.ui.warn(_('no patches in series\n'))
1021 return 0
1022 return 0
1022
1023
1023 patch = self.lookup(patch)
1024 patch = self.lookup(patch)
1024 # Suppose our series file is: A B C and the current 'top'
1025 # Suppose our series file is: A B C and the current 'top'
1025 # patch is B. qpush C should be performed (moving forward)
1026 # patch is B. qpush C should be performed (moving forward)
1026 # qpush B is a NOP (no change) qpush A is an error (can't
1027 # qpush B is a NOP (no change) qpush A is an error (can't
1027 # go backwards with qpush)
1028 # go backwards with qpush)
1028 if patch:
1029 if patch:
1029 info = self.isapplied(patch)
1030 info = self.isapplied(patch)
1030 if info:
1031 if info:
1031 if info[0] < len(self.applied) - 1:
1032 if info[0] < len(self.applied) - 1:
1032 raise util.Abort(
1033 raise util.Abort(
1033 _("cannot push to a previous patch: %s") % patch)
1034 _("cannot push to a previous patch: %s") % patch)
1034 self.ui.warn(
1035 self.ui.warn(
1035 _('qpush: %s is already at the top\n') % patch)
1036 _('qpush: %s is already at the top\n') % patch)
1036 return
1037 return
1037 pushable, reason = self.pushable(patch)
1038 pushable, reason = self.pushable(patch)
1038 if not pushable:
1039 if not pushable:
1039 if reason:
1040 if reason:
1040 reason = _('guarded by %r') % reason
1041 reason = _('guarded by %r') % reason
1041 else:
1042 else:
1042 reason = _('no matching guards')
1043 reason = _('no matching guards')
1043 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1044 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1044 return 1
1045 return 1
1045 elif all:
1046 elif all:
1046 patch = self.series[-1]
1047 patch = self.series[-1]
1047 if self.isapplied(patch):
1048 if self.isapplied(patch):
1048 self.ui.warn(_('all patches are currently applied\n'))
1049 self.ui.warn(_('all patches are currently applied\n'))
1049 return 0
1050 return 0
1050
1051
1051 # Following the above example, starting at 'top' of B:
1052 # Following the above example, starting at 'top' of B:
1052 # qpush should be performed (pushes C), but a subsequent
1053 # qpush should be performed (pushes C), but a subsequent
1053 # qpush without an argument is an error (nothing to
1054 # qpush without an argument is an error (nothing to
1054 # apply). This allows a loop of "...while hg qpush..." to
1055 # apply). This allows a loop of "...while hg qpush..." to
1055 # work as it detects an error when done
1056 # work as it detects an error when done
1056 start = self.series_end()
1057 start = self.series_end()
1057 if start == len(self.series):
1058 if start == len(self.series):
1058 self.ui.warn(_('patch series already fully applied\n'))
1059 self.ui.warn(_('patch series already fully applied\n'))
1059 return 1
1060 return 1
1060 if not force:
1061 if not force:
1061 self.check_localchanges(repo)
1062 self.check_localchanges(repo)
1062
1063
1063 self.applied_dirty = 1
1064 self.applied_dirty = 1
1064 if start > 0:
1065 if start > 0:
1065 self.check_toppatch(repo)
1066 self.check_toppatch(repo)
1066 if not patch:
1067 if not patch:
1067 patch = self.series[start]
1068 patch = self.series[start]
1068 end = start + 1
1069 end = start + 1
1069 else:
1070 else:
1070 end = self.series.index(patch, start) + 1
1071 end = self.series.index(patch, start) + 1
1071
1072
1072 s = self.series[start:end]
1073 s = self.series[start:end]
1073 all_files = {}
1074 all_files = {}
1074 try:
1075 try:
1075 if mergeq:
1076 if mergeq:
1076 ret = self.mergepatch(repo, mergeq, s, diffopts)
1077 ret = self.mergepatch(repo, mergeq, s, diffopts)
1077 else:
1078 else:
1078 ret = self.apply(repo, s, list, all_files=all_files)
1079 ret = self.apply(repo, s, list, all_files=all_files)
1079 except:
1080 except:
1080 self.ui.warn(_('cleaning up working directory...'))
1081 self.ui.warn(_('cleaning up working directory...'))
1081 node = repo.dirstate.parents()[0]
1082 node = repo.dirstate.parents()[0]
1082 hg.revert(repo, node, None)
1083 hg.revert(repo, node, None)
1083 unknown = repo.status(unknown=True)[4]
1084 unknown = repo.status(unknown=True)[4]
1084 # only remove unknown files that we know we touched or
1085 # only remove unknown files that we know we touched or
1085 # created while patching
1086 # created while patching
1086 for f in unknown:
1087 for f in unknown:
1087 if f in all_files:
1088 if f in all_files:
1088 util.unlink(repo.wjoin(f))
1089 util.unlink(repo.wjoin(f))
1089 self.ui.warn(_('done\n'))
1090 self.ui.warn(_('done\n'))
1090 raise
1091 raise
1091
1092
1092 if not self.applied:
1093 if not self.applied:
1093 return ret[0]
1094 return ret[0]
1094 top = self.applied[-1].name
1095 top = self.applied[-1].name
1095 if ret[0] and ret[0] > 1:
1096 if ret[0] and ret[0] > 1:
1096 msg = _("errors during apply, please fix and refresh %s\n")
1097 msg = _("errors during apply, please fix and refresh %s\n")
1097 self.ui.write(msg % top)
1098 self.ui.write(msg % top)
1098 else:
1099 else:
1099 self.ui.write(_("now at: %s\n") % top)
1100 self.ui.write(_("now at: %s\n") % top)
1100 return ret[0]
1101 return ret[0]
1101
1102
1102 finally:
1103 finally:
1103 wlock.release()
1104 wlock.release()
1104
1105
1105 def pop(self, repo, patch=None, force=False, update=True, all=False):
1106 def pop(self, repo, patch=None, force=False, update=True, all=False):
1106 def getfile(f, rev, flags):
1107 def getfile(f, rev, flags):
1107 t = repo.file(f).read(rev)
1108 t = repo.file(f).read(rev)
1108 repo.wwrite(f, t, flags)
1109 repo.wwrite(f, t, flags)
1109
1110
1110 wlock = repo.wlock()
1111 wlock = repo.wlock()
1111 try:
1112 try:
1112 if patch:
1113 if patch:
1113 # index, rev, patch
1114 # index, rev, patch
1114 info = self.isapplied(patch)
1115 info = self.isapplied(patch)
1115 if not info:
1116 if not info:
1116 patch = self.lookup(patch)
1117 patch = self.lookup(patch)
1117 info = self.isapplied(patch)
1118 info = self.isapplied(patch)
1118 if not info:
1119 if not info:
1119 raise util.Abort(_("patch %s is not applied") % patch)
1120 raise util.Abort(_("patch %s is not applied") % patch)
1120
1121
1121 if len(self.applied) == 0:
1122 if len(self.applied) == 0:
1122 # Allow qpop -a to work repeatedly,
1123 # Allow qpop -a to work repeatedly,
1123 # but not qpop without an argument
1124 # but not qpop without an argument
1124 self.ui.warn(_("no patches applied\n"))
1125 self.ui.warn(_("no patches applied\n"))
1125 return not all
1126 return not all
1126
1127
1127 if all:
1128 if all:
1128 start = 0
1129 start = 0
1129 elif patch:
1130 elif patch:
1130 start = info[0] + 1
1131 start = info[0] + 1
1131 else:
1132 else:
1132 start = len(self.applied) - 1
1133 start = len(self.applied) - 1
1133
1134
1134 if start >= len(self.applied):
1135 if start >= len(self.applied):
1135 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1136 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1136 return
1137 return
1137
1138
1138 if not update:
1139 if not update:
1139 parents = repo.dirstate.parents()
1140 parents = repo.dirstate.parents()
1140 rr = [bin(x.rev) for x in self.applied]
1141 rr = [bin(x.rev) for x in self.applied]
1141 for p in parents:
1142 for p in parents:
1142 if p in rr:
1143 if p in rr:
1143 self.ui.warn(_("qpop: forcing dirstate update\n"))
1144 self.ui.warn(_("qpop: forcing dirstate update\n"))
1144 update = True
1145 update = True
1145 else:
1146 else:
1146 parents = [p.hex() for p in repo[None].parents()]
1147 parents = [p.hex() for p in repo[None].parents()]
1147 needupdate = False
1148 needupdate = False
1148 for entry in self.applied[start:]:
1149 for entry in self.applied[start:]:
1149 if entry.rev in parents:
1150 if entry.rev in parents:
1150 needupdate = True
1151 needupdate = True
1151 break
1152 break
1152 update = needupdate
1153 update = needupdate
1153
1154
1154 if not force and update:
1155 if not force and update:
1155 self.check_localchanges(repo)
1156 self.check_localchanges(repo)
1156
1157
1157 self.applied_dirty = 1
1158 self.applied_dirty = 1
1158 end = len(self.applied)
1159 end = len(self.applied)
1159 rev = bin(self.applied[start].rev)
1160 rev = bin(self.applied[start].rev)
1160 if update:
1161 if update:
1161 top = self.check_toppatch(repo)[0]
1162 top = self.check_toppatch(repo)[0]
1162
1163
1163 try:
1164 try:
1164 heads = repo.changelog.heads(rev)
1165 heads = repo.changelog.heads(rev)
1165 except error.LookupError:
1166 except error.LookupError:
1166 node = short(rev)
1167 node = short(rev)
1167 raise util.Abort(_('trying to pop unknown node %s') % node)
1168 raise util.Abort(_('trying to pop unknown node %s') % node)
1168
1169
1169 if heads != [bin(self.applied[-1].rev)]:
1170 if heads != [bin(self.applied[-1].rev)]:
1170 raise util.Abort(_("popping would remove a revision not "
1171 raise util.Abort(_("popping would remove a revision not "
1171 "managed by this patch queue"))
1172 "managed by this patch queue"))
1172
1173
1173 # we know there are no local changes, so we can make a simplified
1174 # we know there are no local changes, so we can make a simplified
1174 # form of hg.update.
1175 # form of hg.update.
1175 if update:
1176 if update:
1176 qp = self.qparents(repo, rev)
1177 qp = self.qparents(repo, rev)
1177 changes = repo.changelog.read(qp)
1178 changes = repo.changelog.read(qp)
1178 mmap = repo.manifest.read(changes[0])
1179 mmap = repo.manifest.read(changes[0])
1179 m, a, r, d = repo.status(qp, top)[:4]
1180 m, a, r, d = repo.status(qp, top)[:4]
1180 if d:
1181 if d:
1181 raise util.Abort(_("deletions found between repo revs"))
1182 raise util.Abort(_("deletions found between repo revs"))
1182 for f in a:
1183 for f in a:
1183 try:
1184 try:
1184 os.unlink(repo.wjoin(f))
1185 os.unlink(repo.wjoin(f))
1185 except OSError, e:
1186 except OSError, e:
1186 if e.errno != errno.ENOENT:
1187 if e.errno != errno.ENOENT:
1187 raise
1188 raise
1188 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1189 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1189 except: pass
1190 except: pass
1190 repo.dirstate.forget(f)
1191 repo.dirstate.forget(f)
1191 for f in m:
1192 for f in m:
1192 getfile(f, mmap[f], mmap.flags(f))
1193 getfile(f, mmap[f], mmap.flags(f))
1193 for f in r:
1194 for f in r:
1194 getfile(f, mmap[f], mmap.flags(f))
1195 getfile(f, mmap[f], mmap.flags(f))
1195 for f in m + r:
1196 for f in m + r:
1196 repo.dirstate.normal(f)
1197 repo.dirstate.normal(f)
1197 repo.dirstate.setparents(qp, nullid)
1198 repo.dirstate.setparents(qp, nullid)
1198 for patch in reversed(self.applied[start:end]):
1199 for patch in reversed(self.applied[start:end]):
1199 self.ui.status(_("popping %s\n") % patch.name)
1200 self.ui.status(_("popping %s\n") % patch.name)
1200 del self.applied[start:end]
1201 del self.applied[start:end]
1201 self.strip(repo, rev, update=False, backup='strip')
1202 self.strip(repo, rev, update=False, backup='strip')
1202 if len(self.applied):
1203 if len(self.applied):
1203 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1204 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1204 else:
1205 else:
1205 self.ui.write(_("patch queue now empty\n"))
1206 self.ui.write(_("patch queue now empty\n"))
1206 finally:
1207 finally:
1207 wlock.release()
1208 wlock.release()
1208
1209
1209 def diff(self, repo, pats, opts):
1210 def diff(self, repo, pats, opts):
1210 top, patch = self.check_toppatch(repo)
1211 top, patch = self.check_toppatch(repo)
1211 if not top:
1212 if not top:
1212 self.ui.write(_("no patches applied\n"))
1213 self.ui.write(_("no patches applied\n"))
1213 return
1214 return
1214 qp = self.qparents(repo, top)
1215 qp = self.qparents(repo, top)
1215 if opts.get('reverse'):
1216 if opts.get('reverse'):
1216 node1, node2 = None, qp
1217 node1, node2 = None, qp
1217 else:
1218 else:
1218 node1, node2 = qp, None
1219 node1, node2 = qp, None
1219 diffopts = self.diffopts(opts, patch)
1220 diffopts = self.diffopts(opts, patch)
1220 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1221 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1221
1222
1222 def refresh(self, repo, pats=None, **opts):
1223 def refresh(self, repo, pats=None, **opts):
1223 if len(self.applied) == 0:
1224 if len(self.applied) == 0:
1224 self.ui.write(_("no patches applied\n"))
1225 self.ui.write(_("no patches applied\n"))
1225 return 1
1226 return 1
1226 msg = opts.get('msg', '').rstrip()
1227 msg = opts.get('msg', '').rstrip()
1227 newuser = opts.get('user')
1228 newuser = opts.get('user')
1228 newdate = opts.get('date')
1229 newdate = opts.get('date')
1229 if newdate:
1230 if newdate:
1230 newdate = '%d %d' % util.parsedate(newdate)
1231 newdate = '%d %d' % util.parsedate(newdate)
1231 wlock = repo.wlock()
1232 wlock = repo.wlock()
1232
1233
1233 try:
1234 try:
1234 self.check_toppatch(repo)
1235 self.check_toppatch(repo)
1235 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1236 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1236 top = bin(top)
1237 top = bin(top)
1237 if repo.changelog.heads(top) != [top]:
1238 if repo.changelog.heads(top) != [top]:
1238 raise util.Abort(_("cannot refresh a revision with children"))
1239 raise util.Abort(_("cannot refresh a revision with children"))
1239
1240
1240 cparents = repo.changelog.parents(top)
1241 cparents = repo.changelog.parents(top)
1241 patchparent = self.qparents(repo, top)
1242 patchparent = self.qparents(repo, top)
1242 ph = patchheader(self.join(patchfn), self.plainmode)
1243 ph = patchheader(self.join(patchfn), self.plainmode)
1243 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1244 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1244 if msg:
1245 if msg:
1245 ph.setmessage(msg)
1246 ph.setmessage(msg)
1246 if newuser:
1247 if newuser:
1247 ph.setuser(newuser)
1248 ph.setuser(newuser)
1248 if newdate:
1249 if newdate:
1249 ph.setdate(newdate)
1250 ph.setdate(newdate)
1250 ph.setparent(hex(patchparent))
1251 ph.setparent(hex(patchparent))
1251
1252
1252 # only commit new patch when write is complete
1253 # only commit new patch when write is complete
1253 patchf = self.opener(patchfn, 'w', atomictemp=True)
1254 patchf = self.opener(patchfn, 'w', atomictemp=True)
1254
1255
1255 comments = str(ph)
1256 comments = str(ph)
1256 if comments:
1257 if comments:
1257 patchf.write(comments)
1258 patchf.write(comments)
1258
1259
1259 # update the dirstate in place, strip off the qtip commit
1260 # update the dirstate in place, strip off the qtip commit
1260 # and then commit.
1261 # and then commit.
1261 #
1262 #
1262 # this should really read:
1263 # this should really read:
1263 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1264 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1264 # but we do it backwards to take advantage of manifest/chlog
1265 # but we do it backwards to take advantage of manifest/chlog
1265 # caching against the next repo.status call
1266 # caching against the next repo.status call
1266 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1267 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1267 changes = repo.changelog.read(top)
1268 changes = repo.changelog.read(top)
1268 man = repo.manifest.read(changes[0])
1269 man = repo.manifest.read(changes[0])
1269 aaa = aa[:]
1270 aaa = aa[:]
1270 matchfn = cmdutil.match(repo, pats, opts)
1271 matchfn = cmdutil.match(repo, pats, opts)
1271 # in short mode, we only diff the files included in the
1272 # in short mode, we only diff the files included in the
1272 # patch already plus specified files
1273 # patch already plus specified files
1273 if opts.get('short'):
1274 if opts.get('short'):
1274 # if amending a patch, we start with existing
1275 # if amending a patch, we start with existing
1275 # files plus specified files - unfiltered
1276 # files plus specified files - unfiltered
1276 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1277 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1277 # filter with inc/exl options
1278 # filter with inc/exl options
1278 matchfn = cmdutil.match(repo, opts=opts)
1279 matchfn = cmdutil.match(repo, opts=opts)
1279 else:
1280 else:
1280 match = cmdutil.matchall(repo)
1281 match = cmdutil.matchall(repo)
1281 m, a, r, d = repo.status(match=match)[:4]
1282 m, a, r, d = repo.status(match=match)[:4]
1282
1283
1283 # we might end up with files that were added between
1284 # we might end up with files that were added between
1284 # qtip and the dirstate parent, but then changed in the
1285 # qtip and the dirstate parent, but then changed in the
1285 # local dirstate. in this case, we want them to only
1286 # local dirstate. in this case, we want them to only
1286 # show up in the added section
1287 # show up in the added section
1287 for x in m:
1288 for x in m:
1288 if x not in aa:
1289 if x not in aa:
1289 mm.append(x)
1290 mm.append(x)
1290 # we might end up with files added by the local dirstate that
1291 # we might end up with files added by the local dirstate that
1291 # were deleted by the patch. In this case, they should only
1292 # were deleted by the patch. In this case, they should only
1292 # show up in the changed section.
1293 # show up in the changed section.
1293 for x in a:
1294 for x in a:
1294 if x in dd:
1295 if x in dd:
1295 del dd[dd.index(x)]
1296 del dd[dd.index(x)]
1296 mm.append(x)
1297 mm.append(x)
1297 else:
1298 else:
1298 aa.append(x)
1299 aa.append(x)
1299 # make sure any files deleted in the local dirstate
1300 # make sure any files deleted in the local dirstate
1300 # are not in the add or change column of the patch
1301 # are not in the add or change column of the patch
1301 forget = []
1302 forget = []
1302 for x in d + r:
1303 for x in d + r:
1303 if x in aa:
1304 if x in aa:
1304 del aa[aa.index(x)]
1305 del aa[aa.index(x)]
1305 forget.append(x)
1306 forget.append(x)
1306 continue
1307 continue
1307 elif x in mm:
1308 elif x in mm:
1308 del mm[mm.index(x)]
1309 del mm[mm.index(x)]
1309 dd.append(x)
1310 dd.append(x)
1310
1311
1311 m = list(set(mm))
1312 m = list(set(mm))
1312 r = list(set(dd))
1313 r = list(set(dd))
1313 a = list(set(aa))
1314 a = list(set(aa))
1314 c = [filter(matchfn, l) for l in (m, a, r)]
1315 c = [filter(matchfn, l) for l in (m, a, r)]
1315 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1316 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1316 chunks = patch.diff(repo, patchparent, match=match,
1317 chunks = patch.diff(repo, patchparent, match=match,
1317 changes=c, opts=diffopts)
1318 changes=c, opts=diffopts)
1318 for chunk in chunks:
1319 for chunk in chunks:
1319 patchf.write(chunk)
1320 patchf.write(chunk)
1320
1321
1321 try:
1322 try:
1322 if diffopts.git or diffopts.upgrade:
1323 if diffopts.git or diffopts.upgrade:
1323 copies = {}
1324 copies = {}
1324 for dst in a:
1325 for dst in a:
1325 src = repo.dirstate.copied(dst)
1326 src = repo.dirstate.copied(dst)
1326 # during qfold, the source file for copies may
1327 # during qfold, the source file for copies may
1327 # be removed. Treat this as a simple add.
1328 # be removed. Treat this as a simple add.
1328 if src is not None and src in repo.dirstate:
1329 if src is not None and src in repo.dirstate:
1329 copies.setdefault(src, []).append(dst)
1330 copies.setdefault(src, []).append(dst)
1330 repo.dirstate.add(dst)
1331 repo.dirstate.add(dst)
1331 # remember the copies between patchparent and qtip
1332 # remember the copies between patchparent and qtip
1332 for dst in aaa:
1333 for dst in aaa:
1333 f = repo.file(dst)
1334 f = repo.file(dst)
1334 src = f.renamed(man[dst])
1335 src = f.renamed(man[dst])
1335 if src:
1336 if src:
1336 copies.setdefault(src[0], []).extend(
1337 copies.setdefault(src[0], []).extend(
1337 copies.get(dst, []))
1338 copies.get(dst, []))
1338 if dst in a:
1339 if dst in a:
1339 copies[src[0]].append(dst)
1340 copies[src[0]].append(dst)
1340 # we can't copy a file created by the patch itself
1341 # we can't copy a file created by the patch itself
1341 if dst in copies:
1342 if dst in copies:
1342 del copies[dst]
1343 del copies[dst]
1343 for src, dsts in copies.iteritems():
1344 for src, dsts in copies.iteritems():
1344 for dst in dsts:
1345 for dst in dsts:
1345 repo.dirstate.copy(src, dst)
1346 repo.dirstate.copy(src, dst)
1346 else:
1347 else:
1347 for dst in a:
1348 for dst in a:
1348 repo.dirstate.add(dst)
1349 repo.dirstate.add(dst)
1349 # Drop useless copy information
1350 # Drop useless copy information
1350 for f in list(repo.dirstate.copies()):
1351 for f in list(repo.dirstate.copies()):
1351 repo.dirstate.copy(None, f)
1352 repo.dirstate.copy(None, f)
1352 for f in r:
1353 for f in r:
1353 repo.dirstate.remove(f)
1354 repo.dirstate.remove(f)
1354 # if the patch excludes a modified file, mark that
1355 # if the patch excludes a modified file, mark that
1355 # file with mtime=0 so status can see it.
1356 # file with mtime=0 so status can see it.
1356 mm = []
1357 mm = []
1357 for i in xrange(len(m)-1, -1, -1):
1358 for i in xrange(len(m)-1, -1, -1):
1358 if not matchfn(m[i]):
1359 if not matchfn(m[i]):
1359 mm.append(m[i])
1360 mm.append(m[i])
1360 del m[i]
1361 del m[i]
1361 for f in m:
1362 for f in m:
1362 repo.dirstate.normal(f)
1363 repo.dirstate.normal(f)
1363 for f in mm:
1364 for f in mm:
1364 repo.dirstate.normallookup(f)
1365 repo.dirstate.normallookup(f)
1365 for f in forget:
1366 for f in forget:
1366 repo.dirstate.forget(f)
1367 repo.dirstate.forget(f)
1367
1368
1368 if not msg:
1369 if not msg:
1369 if not ph.message:
1370 if not ph.message:
1370 message = "[mq]: %s\n" % patchfn
1371 message = "[mq]: %s\n" % patchfn
1371 else:
1372 else:
1372 message = "\n".join(ph.message)
1373 message = "\n".join(ph.message)
1373 else:
1374 else:
1374 message = msg
1375 message = msg
1375
1376
1376 user = ph.user or changes[1]
1377 user = ph.user or changes[1]
1377
1378
1378 # assumes strip can roll itself back if interrupted
1379 # assumes strip can roll itself back if interrupted
1379 repo.dirstate.setparents(*cparents)
1380 repo.dirstate.setparents(*cparents)
1380 self.applied.pop()
1381 self.applied.pop()
1381 self.applied_dirty = 1
1382 self.applied_dirty = 1
1382 self.strip(repo, top, update=False,
1383 self.strip(repo, top, update=False,
1383 backup='strip')
1384 backup='strip')
1384 except:
1385 except:
1385 repo.dirstate.invalidate()
1386 repo.dirstate.invalidate()
1386 raise
1387 raise
1387
1388
1388 try:
1389 try:
1389 # might be nice to attempt to roll back strip after this
1390 # might be nice to attempt to roll back strip after this
1390 patchf.rename()
1391 patchf.rename()
1391 n = repo.commit(message, user, ph.date, match=match,
1392 n = repo.commit(message, user, ph.date, match=match,
1392 force=True)
1393 force=True)
1393 self.applied.append(statusentry(hex(n), patchfn))
1394 self.applied.append(statusentry(hex(n), patchfn))
1394 except:
1395 except:
1395 ctx = repo[cparents[0]]
1396 ctx = repo[cparents[0]]
1396 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1397 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1397 self.save_dirty()
1398 self.save_dirty()
1398 self.ui.warn(_('refresh interrupted while patch was popped! '
1399 self.ui.warn(_('refresh interrupted while patch was popped! '
1399 '(revert --all, qpush to recover)\n'))
1400 '(revert --all, qpush to recover)\n'))
1400 raise
1401 raise
1401 finally:
1402 finally:
1402 wlock.release()
1403 wlock.release()
1403 self.removeundo(repo)
1404 self.removeundo(repo)
1404
1405
1405 def init(self, repo, create=False):
1406 def init(self, repo, create=False):
1406 if not create and os.path.isdir(self.path):
1407 if not create and os.path.isdir(self.path):
1407 raise util.Abort(_("patch queue directory already exists"))
1408 raise util.Abort(_("patch queue directory already exists"))
1408 try:
1409 try:
1409 os.mkdir(self.path)
1410 os.mkdir(self.path)
1410 except OSError, inst:
1411 except OSError, inst:
1411 if inst.errno != errno.EEXIST or not create:
1412 if inst.errno != errno.EEXIST or not create:
1412 raise
1413 raise
1413 if create:
1414 if create:
1414 return self.qrepo(create=True)
1415 return self.qrepo(create=True)
1415
1416
1416 def unapplied(self, repo, patch=None):
1417 def unapplied(self, repo, patch=None):
1417 if patch and patch not in self.series:
1418 if patch and patch not in self.series:
1418 raise util.Abort(_("patch %s is not in series file") % patch)
1419 raise util.Abort(_("patch %s is not in series file") % patch)
1419 if not patch:
1420 if not patch:
1420 start = self.series_end()
1421 start = self.series_end()
1421 else:
1422 else:
1422 start = self.series.index(patch) + 1
1423 start = self.series.index(patch) + 1
1423 unapplied = []
1424 unapplied = []
1424 for i in xrange(start, len(self.series)):
1425 for i in xrange(start, len(self.series)):
1425 pushable, reason = self.pushable(i)
1426 pushable, reason = self.pushable(i)
1426 if pushable:
1427 if pushable:
1427 unapplied.append((i, self.series[i]))
1428 unapplied.append((i, self.series[i]))
1428 self.explain_pushable(i)
1429 self.explain_pushable(i)
1429 return unapplied
1430 return unapplied
1430
1431
1431 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1432 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1432 summary=False):
1433 summary=False):
1433 def displayname(pfx, patchname):
1434 def displayname(pfx, patchname):
1434 if summary:
1435 if summary:
1435 ph = patchheader(self.join(patchname), self.plainmode)
1436 ph = patchheader(self.join(patchname), self.plainmode)
1436 msg = ph.message and ph.message[0] or ''
1437 msg = ph.message and ph.message[0] or ''
1437 if self.ui.interactive():
1438 if self.ui.interactive():
1438 width = util.termwidth() - len(pfx) - len(patchname) - 2
1439 width = util.termwidth() - len(pfx) - len(patchname) - 2
1439 if width > 0:
1440 if width > 0:
1440 msg = util.ellipsis(msg, width)
1441 msg = util.ellipsis(msg, width)
1441 else:
1442 else:
1442 msg = ''
1443 msg = ''
1443 msg = "%s%s: %s" % (pfx, patchname, msg)
1444 msg = "%s%s: %s" % (pfx, patchname, msg)
1444 else:
1445 else:
1445 msg = pfx + patchname
1446 msg = pfx + patchname
1446 self.ui.write(msg + '\n')
1447 self.ui.write(msg + '\n')
1447
1448
1448 applied = set([p.name for p in self.applied])
1449 applied = set([p.name for p in self.applied])
1449 if length is None:
1450 if length is None:
1450 length = len(self.series) - start
1451 length = len(self.series) - start
1451 if not missing:
1452 if not missing:
1452 if self.ui.verbose:
1453 if self.ui.verbose:
1453 idxwidth = len(str(start + length - 1))
1454 idxwidth = len(str(start + length - 1))
1454 for i in xrange(start, start + length):
1455 for i in xrange(start, start + length):
1455 patch = self.series[i]
1456 patch = self.series[i]
1456 if patch in applied:
1457 if patch in applied:
1457 stat = 'A'
1458 stat = 'A'
1458 elif self.pushable(i)[0]:
1459 elif self.pushable(i)[0]:
1459 stat = 'U'
1460 stat = 'U'
1460 else:
1461 else:
1461 stat = 'G'
1462 stat = 'G'
1462 pfx = ''
1463 pfx = ''
1463 if self.ui.verbose:
1464 if self.ui.verbose:
1464 pfx = '%*d %s ' % (idxwidth, i, stat)
1465 pfx = '%*d %s ' % (idxwidth, i, stat)
1465 elif status and status != stat:
1466 elif status and status != stat:
1466 continue
1467 continue
1467 displayname(pfx, patch)
1468 displayname(pfx, patch)
1468 else:
1469 else:
1469 msng_list = []
1470 msng_list = []
1470 for root, dirs, files in os.walk(self.path):
1471 for root, dirs, files in os.walk(self.path):
1471 d = root[len(self.path) + 1:]
1472 d = root[len(self.path) + 1:]
1472 for f in files:
1473 for f in files:
1473 fl = os.path.join(d, f)
1474 fl = os.path.join(d, f)
1474 if (fl not in self.series and
1475 if (fl not in self.series and
1475 fl not in (self.status_path, self.series_path,
1476 fl not in (self.status_path, self.series_path,
1476 self.guards_path)
1477 self.guards_path)
1477 and not fl.startswith('.')):
1478 and not fl.startswith('.')):
1478 msng_list.append(fl)
1479 msng_list.append(fl)
1479 for x in sorted(msng_list):
1480 for x in sorted(msng_list):
1480 pfx = self.ui.verbose and ('D ') or ''
1481 pfx = self.ui.verbose and ('D ') or ''
1481 displayname(pfx, x)
1482 displayname(pfx, x)
1482
1483
1483 def issaveline(self, l):
1484 def issaveline(self, l):
1484 if l.name == '.hg.patches.save.line':
1485 if l.name == '.hg.patches.save.line':
1485 return True
1486 return True
1486
1487
1487 def qrepo(self, create=False):
1488 def qrepo(self, create=False):
1488 if create or os.path.isdir(self.join(".hg")):
1489 if create or os.path.isdir(self.join(".hg")):
1489 return hg.repository(self.ui, path=self.path, create=create)
1490 return hg.repository(self.ui, path=self.path, create=create)
1490
1491
1491 def restore(self, repo, rev, delete=None, qupdate=None):
1492 def restore(self, repo, rev, delete=None, qupdate=None):
1492 c = repo.changelog.read(rev)
1493 c = repo.changelog.read(rev)
1493 desc = c[4].strip()
1494 desc = c[4].strip()
1494 lines = desc.splitlines()
1495 lines = desc.splitlines()
1495 i = 0
1496 i = 0
1496 datastart = None
1497 datastart = None
1497 series = []
1498 series = []
1498 applied = []
1499 applied = []
1499 qpp = None
1500 qpp = None
1500 for i, line in enumerate(lines):
1501 for i, line in enumerate(lines):
1501 if line == 'Patch Data:':
1502 if line == 'Patch Data:':
1502 datastart = i + 1
1503 datastart = i + 1
1503 elif line.startswith('Dirstate:'):
1504 elif line.startswith('Dirstate:'):
1504 l = line.rstrip()
1505 l = line.rstrip()
1505 l = l[10:].split(' ')
1506 l = l[10:].split(' ')
1506 qpp = [bin(x) for x in l]
1507 qpp = [bin(x) for x in l]
1507 elif datastart != None:
1508 elif datastart != None:
1508 l = line.rstrip()
1509 l = line.rstrip()
1509 se = statusentry(l)
1510 se = statusentry(l)
1510 file_ = se.name
1511 file_ = se.name
1511 if se.rev:
1512 if se.rev:
1512 applied.append(se)
1513 applied.append(se)
1513 else:
1514 else:
1514 series.append(file_)
1515 series.append(file_)
1515 if datastart is None:
1516 if datastart is None:
1516 self.ui.warn(_("No saved patch data found\n"))
1517 self.ui.warn(_("No saved patch data found\n"))
1517 return 1
1518 return 1
1518 self.ui.warn(_("restoring status: %s\n") % lines[0])
1519 self.ui.warn(_("restoring status: %s\n") % lines[0])
1519 self.full_series = series
1520 self.full_series = series
1520 self.applied = applied
1521 self.applied = applied
1521 self.parse_series()
1522 self.parse_series()
1522 self.series_dirty = 1
1523 self.series_dirty = 1
1523 self.applied_dirty = 1
1524 self.applied_dirty = 1
1524 heads = repo.changelog.heads()
1525 heads = repo.changelog.heads()
1525 if delete:
1526 if delete:
1526 if rev not in heads:
1527 if rev not in heads:
1527 self.ui.warn(_("save entry has children, leaving it alone\n"))
1528 self.ui.warn(_("save entry has children, leaving it alone\n"))
1528 else:
1529 else:
1529 self.ui.warn(_("removing save entry %s\n") % short(rev))
1530 self.ui.warn(_("removing save entry %s\n") % short(rev))
1530 pp = repo.dirstate.parents()
1531 pp = repo.dirstate.parents()
1531 if rev in pp:
1532 if rev in pp:
1532 update = True
1533 update = True
1533 else:
1534 else:
1534 update = False
1535 update = False
1535 self.strip(repo, rev, update=update, backup='strip')
1536 self.strip(repo, rev, update=update, backup='strip')
1536 if qpp:
1537 if qpp:
1537 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1538 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1538 (short(qpp[0]), short(qpp[1])))
1539 (short(qpp[0]), short(qpp[1])))
1539 if qupdate:
1540 if qupdate:
1540 self.ui.status(_("queue directory updating\n"))
1541 self.ui.status(_("queue directory updating\n"))
1541 r = self.qrepo()
1542 r = self.qrepo()
1542 if not r:
1543 if not r:
1543 self.ui.warn(_("Unable to load queue repository\n"))
1544 self.ui.warn(_("Unable to load queue repository\n"))
1544 return 1
1545 return 1
1545 hg.clean(r, qpp[0])
1546 hg.clean(r, qpp[0])
1546
1547
1547 def save(self, repo, msg=None):
1548 def save(self, repo, msg=None):
1548 if len(self.applied) == 0:
1549 if len(self.applied) == 0:
1549 self.ui.warn(_("save: no patches applied, exiting\n"))
1550 self.ui.warn(_("save: no patches applied, exiting\n"))
1550 return 1
1551 return 1
1551 if self.issaveline(self.applied[-1]):
1552 if self.issaveline(self.applied[-1]):
1552 self.ui.warn(_("status is already saved\n"))
1553 self.ui.warn(_("status is already saved\n"))
1553 return 1
1554 return 1
1554
1555
1555 ar = [':' + x for x in self.full_series]
1556 ar = [':' + x for x in self.full_series]
1556 if not msg:
1557 if not msg:
1557 msg = _("hg patches saved state")
1558 msg = _("hg patches saved state")
1558 else:
1559 else:
1559 msg = "hg patches: " + msg.rstrip('\r\n')
1560 msg = "hg patches: " + msg.rstrip('\r\n')
1560 r = self.qrepo()
1561 r = self.qrepo()
1561 if r:
1562 if r:
1562 pp = r.dirstate.parents()
1563 pp = r.dirstate.parents()
1563 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1564 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1564 msg += "\n\nPatch Data:\n"
1565 msg += "\n\nPatch Data:\n"
1565 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1566 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1566 "\n".join(ar) + '\n' or "")
1567 "\n".join(ar) + '\n' or "")
1567 n = repo.commit(text, force=True)
1568 n = repo.commit(text, force=True)
1568 if not n:
1569 if not n:
1569 self.ui.warn(_("repo commit failed\n"))
1570 self.ui.warn(_("repo commit failed\n"))
1570 return 1
1571 return 1
1571 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1572 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1572 self.applied_dirty = 1
1573 self.applied_dirty = 1
1573 self.removeundo(repo)
1574 self.removeundo(repo)
1574
1575
1575 def full_series_end(self):
1576 def full_series_end(self):
1576 if len(self.applied) > 0:
1577 if len(self.applied) > 0:
1577 p = self.applied[-1].name
1578 p = self.applied[-1].name
1578 end = self.find_series(p)
1579 end = self.find_series(p)
1579 if end is None:
1580 if end is None:
1580 return len(self.full_series)
1581 return len(self.full_series)
1581 return end + 1
1582 return end + 1
1582 return 0
1583 return 0
1583
1584
1584 def series_end(self, all_patches=False):
1585 def series_end(self, all_patches=False):
1585 """If all_patches is False, return the index of the next pushable patch
1586 """If all_patches is False, return the index of the next pushable patch
1586 in the series, or the series length. If all_patches is True, return the
1587 in the series, or the series length. If all_patches is True, return the
1587 index of the first patch past the last applied one.
1588 index of the first patch past the last applied one.
1588 """
1589 """
1589 end = 0
1590 end = 0
1590 def next(start):
1591 def next(start):
1591 if all_patches:
1592 if all_patches:
1592 return start
1593 return start
1593 i = start
1594 i = start
1594 while i < len(self.series):
1595 while i < len(self.series):
1595 p, reason = self.pushable(i)
1596 p, reason = self.pushable(i)
1596 if p:
1597 if p:
1597 break
1598 break
1598 self.explain_pushable(i)
1599 self.explain_pushable(i)
1599 i += 1
1600 i += 1
1600 return i
1601 return i
1601 if len(self.applied) > 0:
1602 if len(self.applied) > 0:
1602 p = self.applied[-1].name
1603 p = self.applied[-1].name
1603 try:
1604 try:
1604 end = self.series.index(p)
1605 end = self.series.index(p)
1605 except ValueError:
1606 except ValueError:
1606 return 0
1607 return 0
1607 return next(end + 1)
1608 return next(end + 1)
1608 return next(end)
1609 return next(end)
1609
1610
1610 def appliedname(self, index):
1611 def appliedname(self, index):
1611 pname = self.applied[index].name
1612 pname = self.applied[index].name
1612 if not self.ui.verbose:
1613 if not self.ui.verbose:
1613 p = pname
1614 p = pname
1614 else:
1615 else:
1615 p = str(self.series.index(pname)) + " " + pname
1616 p = str(self.series.index(pname)) + " " + pname
1616 return p
1617 return p
1617
1618
1618 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1619 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1619 force=None, git=False):
1620 force=None, git=False):
1620 def checkseries(patchname):
1621 def checkseries(patchname):
1621 if patchname in self.series:
1622 if patchname in self.series:
1622 raise util.Abort(_('patch %s is already in the series file')
1623 raise util.Abort(_('patch %s is already in the series file')
1623 % patchname)
1624 % patchname)
1624 def checkfile(patchname):
1625 def checkfile(patchname):
1625 if not force and os.path.exists(self.join(patchname)):
1626 if not force and os.path.exists(self.join(patchname)):
1626 raise util.Abort(_('patch "%s" already exists')
1627 raise util.Abort(_('patch "%s" already exists')
1627 % patchname)
1628 % patchname)
1628
1629
1629 if rev:
1630 if rev:
1630 if files:
1631 if files:
1631 raise util.Abort(_('option "-r" not valid when importing '
1632 raise util.Abort(_('option "-r" not valid when importing '
1632 'files'))
1633 'files'))
1633 rev = cmdutil.revrange(repo, rev)
1634 rev = cmdutil.revrange(repo, rev)
1634 rev.sort(reverse=True)
1635 rev.sort(reverse=True)
1635 if (len(files) > 1 or len(rev) > 1) and patchname:
1636 if (len(files) > 1 or len(rev) > 1) and patchname:
1636 raise util.Abort(_('option "-n" not valid when importing multiple '
1637 raise util.Abort(_('option "-n" not valid when importing multiple '
1637 'patches'))
1638 'patches'))
1638 i = 0
1639 i = 0
1639 added = []
1640 added = []
1640 if rev:
1641 if rev:
1641 # If mq patches are applied, we can only import revisions
1642 # If mq patches are applied, we can only import revisions
1642 # that form a linear path to qbase.
1643 # that form a linear path to qbase.
1643 # Otherwise, they should form a linear path to a head.
1644 # Otherwise, they should form a linear path to a head.
1644 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1645 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1645 if len(heads) > 1:
1646 if len(heads) > 1:
1646 raise util.Abort(_('revision %d is the root of more than one '
1647 raise util.Abort(_('revision %d is the root of more than one '
1647 'branch') % rev[-1])
1648 'branch') % rev[-1])
1648 if self.applied:
1649 if self.applied:
1649 base = hex(repo.changelog.node(rev[0]))
1650 base = hex(repo.changelog.node(rev[0]))
1650 if base in [n.rev for n in self.applied]:
1651 if base in [n.rev for n in self.applied]:
1651 raise util.Abort(_('revision %d is already managed')
1652 raise util.Abort(_('revision %d is already managed')
1652 % rev[0])
1653 % rev[0])
1653 if heads != [bin(self.applied[-1].rev)]:
1654 if heads != [bin(self.applied[-1].rev)]:
1654 raise util.Abort(_('revision %d is not the parent of '
1655 raise util.Abort(_('revision %d is not the parent of '
1655 'the queue') % rev[0])
1656 'the queue') % rev[0])
1656 base = repo.changelog.rev(bin(self.applied[0].rev))
1657 base = repo.changelog.rev(bin(self.applied[0].rev))
1657 lastparent = repo.changelog.parentrevs(base)[0]
1658 lastparent = repo.changelog.parentrevs(base)[0]
1658 else:
1659 else:
1659 if heads != [repo.changelog.node(rev[0])]:
1660 if heads != [repo.changelog.node(rev[0])]:
1660 raise util.Abort(_('revision %d has unmanaged children')
1661 raise util.Abort(_('revision %d has unmanaged children')
1661 % rev[0])
1662 % rev[0])
1662 lastparent = None
1663 lastparent = None
1663
1664
1664 diffopts = self.diffopts({'git': git})
1665 diffopts = self.diffopts({'git': git})
1665 for r in rev:
1666 for r in rev:
1666 p1, p2 = repo.changelog.parentrevs(r)
1667 p1, p2 = repo.changelog.parentrevs(r)
1667 n = repo.changelog.node(r)
1668 n = repo.changelog.node(r)
1668 if p2 != nullrev:
1669 if p2 != nullrev:
1669 raise util.Abort(_('cannot import merge revision %d') % r)
1670 raise util.Abort(_('cannot import merge revision %d') % r)
1670 if lastparent and lastparent != r:
1671 if lastparent and lastparent != r:
1671 raise util.Abort(_('revision %d is not the parent of %d')
1672 raise util.Abort(_('revision %d is not the parent of %d')
1672 % (r, lastparent))
1673 % (r, lastparent))
1673 lastparent = p1
1674 lastparent = p1
1674
1675
1675 if not patchname:
1676 if not patchname:
1676 patchname = normname('%d.diff' % r)
1677 patchname = normname('%d.diff' % r)
1677 self.check_reserved_name(patchname)
1678 self.check_reserved_name(patchname)
1678 checkseries(patchname)
1679 checkseries(patchname)
1679 checkfile(patchname)
1680 checkfile(patchname)
1680 self.full_series.insert(0, patchname)
1681 self.full_series.insert(0, patchname)
1681
1682
1682 patchf = self.opener(patchname, "w")
1683 patchf = self.opener(patchname, "w")
1683 patch.export(repo, [n], fp=patchf, opts=diffopts)
1684 patch.export(repo, [n], fp=patchf, opts=diffopts)
1684 patchf.close()
1685 patchf.close()
1685
1686
1686 se = statusentry(hex(n), patchname)
1687 se = statusentry(hex(n), patchname)
1687 self.applied.insert(0, se)
1688 self.applied.insert(0, se)
1688
1689
1689 added.append(patchname)
1690 added.append(patchname)
1690 patchname = None
1691 patchname = None
1691 self.parse_series()
1692 self.parse_series()
1692 self.applied_dirty = 1
1693 self.applied_dirty = 1
1693
1694
1694 for filename in files:
1695 for filename in files:
1695 if existing:
1696 if existing:
1696 if filename == '-':
1697 if filename == '-':
1697 raise util.Abort(_('-e is incompatible with import from -'))
1698 raise util.Abort(_('-e is incompatible with import from -'))
1698 if not patchname:
1699 if not patchname:
1699 patchname = normname(filename)
1700 patchname = normname(filename)
1700 self.check_reserved_name(patchname)
1701 self.check_reserved_name(patchname)
1701 if not os.path.isfile(self.join(patchname)):
1702 if not os.path.isfile(self.join(patchname)):
1702 raise util.Abort(_("patch %s does not exist") % patchname)
1703 raise util.Abort(_("patch %s does not exist") % patchname)
1703 else:
1704 else:
1704 try:
1705 try:
1705 if filename == '-':
1706 if filename == '-':
1706 if not patchname:
1707 if not patchname:
1707 raise util.Abort(
1708 raise util.Abort(
1708 _('need --name to import a patch from -'))
1709 _('need --name to import a patch from -'))
1709 text = sys.stdin.read()
1710 text = sys.stdin.read()
1710 else:
1711 else:
1711 text = url.open(self.ui, filename).read()
1712 text = url.open(self.ui, filename).read()
1712 except (OSError, IOError):
1713 except (OSError, IOError):
1713 raise util.Abort(_("unable to read %s") % filename)
1714 raise util.Abort(_("unable to read %s") % filename)
1714 if not patchname:
1715 if not patchname:
1715 patchname = normname(os.path.basename(filename))
1716 patchname = normname(os.path.basename(filename))
1716 self.check_reserved_name(patchname)
1717 self.check_reserved_name(patchname)
1717 checkfile(patchname)
1718 checkfile(patchname)
1718 patchf = self.opener(patchname, "w")
1719 patchf = self.opener(patchname, "w")
1719 patchf.write(text)
1720 patchf.write(text)
1720 if not force:
1721 if not force:
1721 checkseries(patchname)
1722 checkseries(patchname)
1722 if patchname not in self.series:
1723 if patchname not in self.series:
1723 index = self.full_series_end() + i
1724 index = self.full_series_end() + i
1724 self.full_series[index:index] = [patchname]
1725 self.full_series[index:index] = [patchname]
1725 self.parse_series()
1726 self.parse_series()
1726 self.ui.warn(_("adding %s to series file\n") % patchname)
1727 self.ui.warn(_("adding %s to series file\n") % patchname)
1727 i += 1
1728 i += 1
1728 added.append(patchname)
1729 added.append(patchname)
1729 patchname = None
1730 patchname = None
1730 self.series_dirty = 1
1731 self.series_dirty = 1
1731 qrepo = self.qrepo()
1732 qrepo = self.qrepo()
1732 if qrepo:
1733 if qrepo:
1733 qrepo.add(added)
1734 qrepo.add(added)
1734
1735
1735 def delete(ui, repo, *patches, **opts):
1736 def delete(ui, repo, *patches, **opts):
1736 """remove patches from queue
1737 """remove patches from queue
1737
1738
1738 The patches must not be applied, and at least one patch is required. With
1739 The patches must not be applied, and at least one patch is required. With
1739 -k/--keep, the patch files are preserved in the patch directory.
1740 -k/--keep, the patch files are preserved in the patch directory.
1740
1741
1741 To stop managing a patch and move it into permanent history,
1742 To stop managing a patch and move it into permanent history,
1742 use the qfinish command."""
1743 use the qfinish command."""
1743 q = repo.mq
1744 q = repo.mq
1744 q.delete(repo, patches, opts)
1745 q.delete(repo, patches, opts)
1745 q.save_dirty()
1746 q.save_dirty()
1746 return 0
1747 return 0
1747
1748
1748 def applied(ui, repo, patch=None, **opts):
1749 def applied(ui, repo, patch=None, **opts):
1749 """print the patches already applied"""
1750 """print the patches already applied"""
1750
1751
1751 q = repo.mq
1752 q = repo.mq
1752 l = len(q.applied)
1753 l = len(q.applied)
1753
1754
1754 if patch:
1755 if patch:
1755 if patch not in q.series:
1756 if patch not in q.series:
1756 raise util.Abort(_("patch %s is not in series file") % patch)
1757 raise util.Abort(_("patch %s is not in series file") % patch)
1757 end = q.series.index(patch) + 1
1758 end = q.series.index(patch) + 1
1758 else:
1759 else:
1759 end = q.series_end(True)
1760 end = q.series_end(True)
1760
1761
1761 if opts.get('last') and not end:
1762 if opts.get('last') and not end:
1762 ui.write(_("no patches applied\n"))
1763 ui.write(_("no patches applied\n"))
1763 return 1
1764 return 1
1764 elif opts.get('last') and end == 1:
1765 elif opts.get('last') and end == 1:
1765 ui.write(_("only one patch applied\n"))
1766 ui.write(_("only one patch applied\n"))
1766 return 1
1767 return 1
1767 elif opts.get('last'):
1768 elif opts.get('last'):
1768 start = end - 2
1769 start = end - 2
1769 end = 1
1770 end = 1
1770 else:
1771 else:
1771 start = 0
1772 start = 0
1772
1773
1773 return q.qseries(repo, length=end, start=start, status='A',
1774 return q.qseries(repo, length=end, start=start, status='A',
1774 summary=opts.get('summary'))
1775 summary=opts.get('summary'))
1775
1776
1776 def unapplied(ui, repo, patch=None, **opts):
1777 def unapplied(ui, repo, patch=None, **opts):
1777 """print the patches not yet applied"""
1778 """print the patches not yet applied"""
1778
1779
1779 q = repo.mq
1780 q = repo.mq
1780 if patch:
1781 if patch:
1781 if patch not in q.series:
1782 if patch not in q.series:
1782 raise util.Abort(_("patch %s is not in series file") % patch)
1783 raise util.Abort(_("patch %s is not in series file") % patch)
1783 start = q.series.index(patch) + 1
1784 start = q.series.index(patch) + 1
1784 else:
1785 else:
1785 start = q.series_end(True)
1786 start = q.series_end(True)
1786
1787
1787 if start == len(q.series) and opts.get('first'):
1788 if start == len(q.series) and opts.get('first'):
1788 ui.write(_("all patches applied\n"))
1789 ui.write(_("all patches applied\n"))
1789 return 1
1790 return 1
1790
1791
1791 length = opts.get('first') and 1 or None
1792 length = opts.get('first') and 1 or None
1792 return q.qseries(repo, start=start, length=length, status='U',
1793 return q.qseries(repo, start=start, length=length, status='U',
1793 summary=opts.get('summary'))
1794 summary=opts.get('summary'))
1794
1795
1795 def qimport(ui, repo, *filename, **opts):
1796 def qimport(ui, repo, *filename, **opts):
1796 """import a patch
1797 """import a patch
1797
1798
1798 The patch is inserted into the series after the last applied
1799 The patch is inserted into the series after the last applied
1799 patch. If no patches have been applied, qimport prepends the patch
1800 patch. If no patches have been applied, qimport prepends the patch
1800 to the series.
1801 to the series.
1801
1802
1802 The patch will have the same name as its source file unless you
1803 The patch will have the same name as its source file unless you
1803 give it a new one with -n/--name.
1804 give it a new one with -n/--name.
1804
1805
1805 You can register an existing patch inside the patch directory with
1806 You can register an existing patch inside the patch directory with
1806 the -e/--existing flag.
1807 the -e/--existing flag.
1807
1808
1808 With -f/--force, an existing patch of the same name will be
1809 With -f/--force, an existing patch of the same name will be
1809 overwritten.
1810 overwritten.
1810
1811
1811 An existing changeset may be placed under mq control with -r/--rev
1812 An existing changeset may be placed under mq control with -r/--rev
1812 (e.g. qimport --rev tip -n patch will place tip under mq control).
1813 (e.g. qimport --rev tip -n patch will place tip under mq control).
1813 With -g/--git, patches imported with --rev will use the git diff
1814 With -g/--git, patches imported with --rev will use the git diff
1814 format. See the diffs help topic for information on why this is
1815 format. See the diffs help topic for information on why this is
1815 important for preserving rename/copy information and permission
1816 important for preserving rename/copy information and permission
1816 changes.
1817 changes.
1817
1818
1818 To import a patch from standard input, pass - as the patch file.
1819 To import a patch from standard input, pass - as the patch file.
1819 When importing from standard input, a patch name must be specified
1820 When importing from standard input, a patch name must be specified
1820 using the --name flag.
1821 using the --name flag.
1821 """
1822 """
1822 q = repo.mq
1823 q = repo.mq
1823 q.qimport(repo, filename, patchname=opts['name'],
1824 q.qimport(repo, filename, patchname=opts['name'],
1824 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1825 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1825 git=opts['git'])
1826 git=opts['git'])
1826 q.save_dirty()
1827 q.save_dirty()
1827
1828
1828 if opts.get('push') and not opts.get('rev'):
1829 if opts.get('push') and not opts.get('rev'):
1829 return q.push(repo, None)
1830 return q.push(repo, None)
1830 return 0
1831 return 0
1831
1832
1832 def init(ui, repo, **opts):
1833 def init(ui, repo, **opts):
1833 """init a new queue repository (DEPRECATED)
1834 """init a new queue repository (DEPRECATED)
1834
1835
1835 The queue repository is unversioned by default. If
1836 The queue repository is unversioned by default. If
1836 -c/--create-repo is specified, qinit will create a separate nested
1837 -c/--create-repo is specified, qinit will create a separate nested
1837 repository for patches (qinit -c may also be run later to convert
1838 repository for patches (qinit -c may also be run later to convert
1838 an unversioned patch repository into a versioned one). You can use
1839 an unversioned patch repository into a versioned one). You can use
1839 qcommit to commit changes to this queue repository.
1840 qcommit to commit changes to this queue repository.
1840
1841
1841 This command is deprecated. Without -c, it's implied by other relevant
1842 This command is deprecated. Without -c, it's implied by other relevant
1842 commands. With -c, use hg init -Q instead."""
1843 commands. With -c, use hg init -Q instead."""
1843 q = repo.mq
1844 q = repo.mq
1844 r = q.init(repo, create=opts['create_repo'])
1845 r = q.init(repo, create=opts['create_repo'])
1845 q.save_dirty()
1846 q.save_dirty()
1846 if r:
1847 if r:
1847 if not os.path.exists(r.wjoin('.hgignore')):
1848 if not os.path.exists(r.wjoin('.hgignore')):
1848 fp = r.wopener('.hgignore', 'w')
1849 fp = r.wopener('.hgignore', 'w')
1849 fp.write('^\\.hg\n')
1850 fp.write('^\\.hg\n')
1850 fp.write('^\\.mq\n')
1851 fp.write('^\\.mq\n')
1851 fp.write('syntax: glob\n')
1852 fp.write('syntax: glob\n')
1852 fp.write('status\n')
1853 fp.write('status\n')
1853 fp.write('guards\n')
1854 fp.write('guards\n')
1854 fp.close()
1855 fp.close()
1855 if not os.path.exists(r.wjoin('series')):
1856 if not os.path.exists(r.wjoin('series')):
1856 r.wopener('series', 'w').close()
1857 r.wopener('series', 'w').close()
1857 r.add(['.hgignore', 'series'])
1858 r.add(['.hgignore', 'series'])
1858 commands.add(ui, r)
1859 commands.add(ui, r)
1859 return 0
1860 return 0
1860
1861
1861 def clone(ui, source, dest=None, **opts):
1862 def clone(ui, source, dest=None, **opts):
1862 '''clone main and patch repository at same time
1863 '''clone main and patch repository at same time
1863
1864
1864 If source is local, destination will have no patches applied. If
1865 If source is local, destination will have no patches applied. If
1865 source is remote, this command can not check if patches are
1866 source is remote, this command can not check if patches are
1866 applied in source, so cannot guarantee that patches are not
1867 applied in source, so cannot guarantee that patches are not
1867 applied in destination. If you clone remote repository, be sure
1868 applied in destination. If you clone remote repository, be sure
1868 before that it has no patches applied.
1869 before that it has no patches applied.
1869
1870
1870 Source patch repository is looked for in <src>/.hg/patches by
1871 Source patch repository is looked for in <src>/.hg/patches by
1871 default. Use -p <url> to change.
1872 default. Use -p <url> to change.
1872
1873
1873 The patch directory must be a nested Mercurial repository, as
1874 The patch directory must be a nested Mercurial repository, as
1874 would be created by qinit -c.
1875 would be created by qinit -c.
1875 '''
1876 '''
1876 def patchdir(repo):
1877 def patchdir(repo):
1877 url = repo.url()
1878 url = repo.url()
1878 if url.endswith('/'):
1879 if url.endswith('/'):
1879 url = url[:-1]
1880 url = url[:-1]
1880 return url + '/.hg/patches'
1881 return url + '/.hg/patches'
1881 if dest is None:
1882 if dest is None:
1882 dest = hg.defaultdest(source)
1883 dest = hg.defaultdest(source)
1883 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1884 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1884 if opts['patches']:
1885 if opts['patches']:
1885 patchespath = ui.expandpath(opts['patches'])
1886 patchespath = ui.expandpath(opts['patches'])
1886 else:
1887 else:
1887 patchespath = patchdir(sr)
1888 patchespath = patchdir(sr)
1888 try:
1889 try:
1889 hg.repository(ui, patchespath)
1890 hg.repository(ui, patchespath)
1890 except error.RepoError:
1891 except error.RepoError:
1891 raise util.Abort(_('versioned patch repository not found'
1892 raise util.Abort(_('versioned patch repository not found'
1892 ' (see qinit -c)'))
1893 ' (see qinit -c)'))
1893 qbase, destrev = None, None
1894 qbase, destrev = None, None
1894 if sr.local():
1895 if sr.local():
1895 if sr.mq.applied:
1896 if sr.mq.applied:
1896 qbase = bin(sr.mq.applied[0].rev)
1897 qbase = bin(sr.mq.applied[0].rev)
1897 if not hg.islocal(dest):
1898 if not hg.islocal(dest):
1898 heads = set(sr.heads())
1899 heads = set(sr.heads())
1899 destrev = list(heads.difference(sr.heads(qbase)))
1900 destrev = list(heads.difference(sr.heads(qbase)))
1900 destrev.append(sr.changelog.parents(qbase)[0])
1901 destrev.append(sr.changelog.parents(qbase)[0])
1901 elif sr.capable('lookup'):
1902 elif sr.capable('lookup'):
1902 try:
1903 try:
1903 qbase = sr.lookup('qbase')
1904 qbase = sr.lookup('qbase')
1904 except error.RepoError:
1905 except error.RepoError:
1905 pass
1906 pass
1906 ui.note(_('cloning main repository\n'))
1907 ui.note(_('cloning main repository\n'))
1907 sr, dr = hg.clone(ui, sr.url(), dest,
1908 sr, dr = hg.clone(ui, sr.url(), dest,
1908 pull=opts['pull'],
1909 pull=opts['pull'],
1909 rev=destrev,
1910 rev=destrev,
1910 update=False,
1911 update=False,
1911 stream=opts['uncompressed'])
1912 stream=opts['uncompressed'])
1912 ui.note(_('cloning patch repository\n'))
1913 ui.note(_('cloning patch repository\n'))
1913 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1914 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1914 pull=opts['pull'], update=not opts['noupdate'],
1915 pull=opts['pull'], update=not opts['noupdate'],
1915 stream=opts['uncompressed'])
1916 stream=opts['uncompressed'])
1916 if dr.local():
1917 if dr.local():
1917 if qbase:
1918 if qbase:
1918 ui.note(_('stripping applied patches from destination '
1919 ui.note(_('stripping applied patches from destination '
1919 'repository\n'))
1920 'repository\n'))
1920 dr.mq.strip(dr, qbase, update=False, backup=None)
1921 dr.mq.strip(dr, qbase, update=False, backup=None)
1921 if not opts['noupdate']:
1922 if not opts['noupdate']:
1922 ui.note(_('updating destination repository\n'))
1923 ui.note(_('updating destination repository\n'))
1923 hg.update(dr, dr.changelog.tip())
1924 hg.update(dr, dr.changelog.tip())
1924
1925
1925 def commit(ui, repo, *pats, **opts):
1926 def commit(ui, repo, *pats, **opts):
1926 """commit changes in the queue repository (DEPRECATED)
1927 """commit changes in the queue repository (DEPRECATED)
1927
1928
1928 This command is deprecated; use hg -Q commit instead."""
1929 This command is deprecated; use hg -Q commit instead."""
1929 q = repo.mq
1930 q = repo.mq
1930 r = q.qrepo()
1931 r = q.qrepo()
1931 if not r:
1932 if not r:
1932 raise util.Abort('no queue repository')
1933 raise util.Abort('no queue repository')
1933 commands.commit(r.ui, r, *pats, **opts)
1934 commands.commit(r.ui, r, *pats, **opts)
1934
1935
1935 def series(ui, repo, **opts):
1936 def series(ui, repo, **opts):
1936 """print the entire series file"""
1937 """print the entire series file"""
1937 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1938 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1938 return 0
1939 return 0
1939
1940
1940 def top(ui, repo, **opts):
1941 def top(ui, repo, **opts):
1941 """print the name of the current patch"""
1942 """print the name of the current patch"""
1942 q = repo.mq
1943 q = repo.mq
1943 t = q.applied and q.series_end(True) or 0
1944 t = q.applied and q.series_end(True) or 0
1944 if t:
1945 if t:
1945 return q.qseries(repo, start=t - 1, length=1, status='A',
1946 return q.qseries(repo, start=t - 1, length=1, status='A',
1946 summary=opts.get('summary'))
1947 summary=opts.get('summary'))
1947 else:
1948 else:
1948 ui.write(_("no patches applied\n"))
1949 ui.write(_("no patches applied\n"))
1949 return 1
1950 return 1
1950
1951
1951 def next(ui, repo, **opts):
1952 def next(ui, repo, **opts):
1952 """print the name of the next patch"""
1953 """print the name of the next patch"""
1953 q = repo.mq
1954 q = repo.mq
1954 end = q.series_end()
1955 end = q.series_end()
1955 if end == len(q.series):
1956 if end == len(q.series):
1956 ui.write(_("all patches applied\n"))
1957 ui.write(_("all patches applied\n"))
1957 return 1
1958 return 1
1958 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1959 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1959
1960
1960 def prev(ui, repo, **opts):
1961 def prev(ui, repo, **opts):
1961 """print the name of the previous patch"""
1962 """print the name of the previous patch"""
1962 q = repo.mq
1963 q = repo.mq
1963 l = len(q.applied)
1964 l = len(q.applied)
1964 if l == 1:
1965 if l == 1:
1965 ui.write(_("only one patch applied\n"))
1966 ui.write(_("only one patch applied\n"))
1966 return 1
1967 return 1
1967 if not l:
1968 if not l:
1968 ui.write(_("no patches applied\n"))
1969 ui.write(_("no patches applied\n"))
1969 return 1
1970 return 1
1970 return q.qseries(repo, start=l - 2, length=1, status='A',
1971 return q.qseries(repo, start=l - 2, length=1, status='A',
1971 summary=opts.get('summary'))
1972 summary=opts.get('summary'))
1972
1973
1973 def setupheaderopts(ui, opts):
1974 def setupheaderopts(ui, opts):
1974 if not opts.get('user') and opts.get('currentuser'):
1975 if not opts.get('user') and opts.get('currentuser'):
1975 opts['user'] = ui.username()
1976 opts['user'] = ui.username()
1976 if not opts.get('date') and opts.get('currentdate'):
1977 if not opts.get('date') and opts.get('currentdate'):
1977 opts['date'] = "%d %d" % util.makedate()
1978 opts['date'] = "%d %d" % util.makedate()
1978
1979
1979 def new(ui, repo, patch, *args, **opts):
1980 def new(ui, repo, patch, *args, **opts):
1980 """create a new patch
1981 """create a new patch
1981
1982
1982 qnew creates a new patch on top of the currently-applied patch (if
1983 qnew creates a new patch on top of the currently-applied patch (if
1983 any). It will refuse to run if there are any outstanding changes
1984 any). It will refuse to run if there are any outstanding changes
1984 unless -f/--force is specified, in which case the patch will be
1985 unless -f/--force is specified, in which case the patch will be
1985 initialized with them. You may also use -I/--include,
1986 initialized with them. You may also use -I/--include,
1986 -X/--exclude, and/or a list of files after the patch name to add
1987 -X/--exclude, and/or a list of files after the patch name to add
1987 only changes to matching files to the new patch, leaving the rest
1988 only changes to matching files to the new patch, leaving the rest
1988 as uncommitted modifications.
1989 as uncommitted modifications.
1989
1990
1990 -u/--user and -d/--date can be used to set the (given) user and
1991 -u/--user and -d/--date can be used to set the (given) user and
1991 date, respectively. -U/--currentuser and -D/--currentdate set user
1992 date, respectively. -U/--currentuser and -D/--currentdate set user
1992 to current user and date to current date.
1993 to current user and date to current date.
1993
1994
1994 -e/--edit, -m/--message or -l/--logfile set the patch header as
1995 -e/--edit, -m/--message or -l/--logfile set the patch header as
1995 well as the commit message. If none is specified, the header is
1996 well as the commit message. If none is specified, the header is
1996 empty and the commit message is '[mq]: PATCH'.
1997 empty and the commit message is '[mq]: PATCH'.
1997
1998
1998 Use the -g/--git option to keep the patch in the git extended diff
1999 Use the -g/--git option to keep the patch in the git extended diff
1999 format. Read the diffs help topic for more information on why this
2000 format. Read the diffs help topic for more information on why this
2000 is important for preserving permission changes and copy/rename
2001 is important for preserving permission changes and copy/rename
2001 information.
2002 information.
2002 """
2003 """
2003 msg = cmdutil.logmessage(opts)
2004 msg = cmdutil.logmessage(opts)
2004 def getmsg():
2005 def getmsg():
2005 return ui.edit(msg, ui.username())
2006 return ui.edit(msg, ui.username())
2006 q = repo.mq
2007 q = repo.mq
2007 opts['msg'] = msg
2008 opts['msg'] = msg
2008 if opts.get('edit'):
2009 if opts.get('edit'):
2009 opts['msg'] = getmsg
2010 opts['msg'] = getmsg
2010 else:
2011 else:
2011 opts['msg'] = msg
2012 opts['msg'] = msg
2012 setupheaderopts(ui, opts)
2013 setupheaderopts(ui, opts)
2013 q.new(repo, patch, *args, **opts)
2014 q.new(repo, patch, *args, **opts)
2014 q.save_dirty()
2015 q.save_dirty()
2015 return 0
2016 return 0
2016
2017
2017 def refresh(ui, repo, *pats, **opts):
2018 def refresh(ui, repo, *pats, **opts):
2018 """update the current patch
2019 """update the current patch
2019
2020
2020 If any file patterns are provided, the refreshed patch will
2021 If any file patterns are provided, the refreshed patch will
2021 contain only the modifications that match those patterns; the
2022 contain only the modifications that match those patterns; the
2022 remaining modifications will remain in the working directory.
2023 remaining modifications will remain in the working directory.
2023
2024
2024 If -s/--short is specified, files currently included in the patch
2025 If -s/--short is specified, files currently included in the patch
2025 will be refreshed just like matched files and remain in the patch.
2026 will be refreshed just like matched files and remain in the patch.
2026
2027
2027 hg add/remove/copy/rename work as usual, though you might want to
2028 hg add/remove/copy/rename work as usual, though you might want to
2028 use git-style patches (-g/--git or [diff] git=1) to track copies
2029 use git-style patches (-g/--git or [diff] git=1) to track copies
2029 and renames. See the diffs help topic for more information on the
2030 and renames. See the diffs help topic for more information on the
2030 git diff format.
2031 git diff format.
2031 """
2032 """
2032 q = repo.mq
2033 q = repo.mq
2033 message = cmdutil.logmessage(opts)
2034 message = cmdutil.logmessage(opts)
2034 if opts['edit']:
2035 if opts['edit']:
2035 if not q.applied:
2036 if not q.applied:
2036 ui.write(_("no patches applied\n"))
2037 ui.write(_("no patches applied\n"))
2037 return 1
2038 return 1
2038 if message:
2039 if message:
2039 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2040 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2040 patch = q.applied[-1].name
2041 patch = q.applied[-1].name
2041 ph = patchheader(q.join(patch), q.plainmode)
2042 ph = patchheader(q.join(patch), q.plainmode)
2042 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2043 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2043 setupheaderopts(ui, opts)
2044 setupheaderopts(ui, opts)
2044 ret = q.refresh(repo, pats, msg=message, **opts)
2045 ret = q.refresh(repo, pats, msg=message, **opts)
2045 q.save_dirty()
2046 q.save_dirty()
2046 return ret
2047 return ret
2047
2048
2048 def diff(ui, repo, *pats, **opts):
2049 def diff(ui, repo, *pats, **opts):
2049 """diff of the current patch and subsequent modifications
2050 """diff of the current patch and subsequent modifications
2050
2051
2051 Shows a diff which includes the current patch as well as any
2052 Shows a diff which includes the current patch as well as any
2052 changes which have been made in the working directory since the
2053 changes which have been made in the working directory since the
2053 last refresh (thus showing what the current patch would become
2054 last refresh (thus showing what the current patch would become
2054 after a qrefresh).
2055 after a qrefresh).
2055
2056
2056 Use 'hg diff' if you only want to see the changes made since the
2057 Use 'hg diff' if you only want to see the changes made since the
2057 last qrefresh, or 'hg export qtip' if you want to see changes made
2058 last qrefresh, or 'hg export qtip' if you want to see changes made
2058 by the current patch without including changes made since the
2059 by the current patch without including changes made since the
2059 qrefresh.
2060 qrefresh.
2060 """
2061 """
2061 repo.mq.diff(repo, pats, opts)
2062 repo.mq.diff(repo, pats, opts)
2062 return 0
2063 return 0
2063
2064
2064 def fold(ui, repo, *files, **opts):
2065 def fold(ui, repo, *files, **opts):
2065 """fold the named patches into the current patch
2066 """fold the named patches into the current patch
2066
2067
2067 Patches must not yet be applied. Each patch will be successively
2068 Patches must not yet be applied. Each patch will be successively
2068 applied to the current patch in the order given. If all the
2069 applied to the current patch in the order given. If all the
2069 patches apply successfully, the current patch will be refreshed
2070 patches apply successfully, the current patch will be refreshed
2070 with the new cumulative patch, and the folded patches will be
2071 with the new cumulative patch, and the folded patches will be
2071 deleted. With -k/--keep, the folded patch files will not be
2072 deleted. With -k/--keep, the folded patch files will not be
2072 removed afterwards.
2073 removed afterwards.
2073
2074
2074 The header for each folded patch will be concatenated with the
2075 The header for each folded patch will be concatenated with the
2075 current patch header, separated by a line of '* * *'."""
2076 current patch header, separated by a line of '* * *'."""
2076
2077
2077 q = repo.mq
2078 q = repo.mq
2078
2079
2079 if not files:
2080 if not files:
2080 raise util.Abort(_('qfold requires at least one patch name'))
2081 raise util.Abort(_('qfold requires at least one patch name'))
2081 if not q.check_toppatch(repo)[0]:
2082 if not q.check_toppatch(repo)[0]:
2082 raise util.Abort(_('No patches applied'))
2083 raise util.Abort(_('No patches applied'))
2083 q.check_localchanges(repo)
2084 q.check_localchanges(repo)
2084
2085
2085 message = cmdutil.logmessage(opts)
2086 message = cmdutil.logmessage(opts)
2086 if opts['edit']:
2087 if opts['edit']:
2087 if message:
2088 if message:
2088 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2089 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2089
2090
2090 parent = q.lookup('qtip')
2091 parent = q.lookup('qtip')
2091 patches = []
2092 patches = []
2092 messages = []
2093 messages = []
2093 for f in files:
2094 for f in files:
2094 p = q.lookup(f)
2095 p = q.lookup(f)
2095 if p in patches or p == parent:
2096 if p in patches or p == parent:
2096 ui.warn(_('Skipping already folded patch %s') % p)
2097 ui.warn(_('Skipping already folded patch %s') % p)
2097 if q.isapplied(p):
2098 if q.isapplied(p):
2098 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2099 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2099 patches.append(p)
2100 patches.append(p)
2100
2101
2101 for p in patches:
2102 for p in patches:
2102 if not message:
2103 if not message:
2103 ph = patchheader(q.join(p), q.plainmode)
2104 ph = patchheader(q.join(p), q.plainmode)
2104 if ph.message:
2105 if ph.message:
2105 messages.append(ph.message)
2106 messages.append(ph.message)
2106 pf = q.join(p)
2107 pf = q.join(p)
2107 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2108 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2108 if not patchsuccess:
2109 if not patchsuccess:
2109 raise util.Abort(_('Error folding patch %s') % p)
2110 raise util.Abort(_('Error folding patch %s') % p)
2110 patch.updatedir(ui, repo, files)
2111 patch.updatedir(ui, repo, files)
2111
2112
2112 if not message:
2113 if not message:
2113 ph = patchheader(q.join(parent), q.plainmode)
2114 ph = patchheader(q.join(parent), q.plainmode)
2114 message, user = ph.message, ph.user
2115 message, user = ph.message, ph.user
2115 for msg in messages:
2116 for msg in messages:
2116 message.append('* * *')
2117 message.append('* * *')
2117 message.extend(msg)
2118 message.extend(msg)
2118 message = '\n'.join(message)
2119 message = '\n'.join(message)
2119
2120
2120 if opts['edit']:
2121 if opts['edit']:
2121 message = ui.edit(message, user or ui.username())
2122 message = ui.edit(message, user or ui.username())
2122
2123
2123 diffopts = q.patchopts(q.diffopts(), *patches)
2124 diffopts = q.patchopts(q.diffopts(), *patches)
2124 q.refresh(repo, msg=message, git=diffopts.git)
2125 q.refresh(repo, msg=message, git=diffopts.git)
2125 q.delete(repo, patches, opts)
2126 q.delete(repo, patches, opts)
2126 q.save_dirty()
2127 q.save_dirty()
2127
2128
2128 def goto(ui, repo, patch, **opts):
2129 def goto(ui, repo, patch, **opts):
2129 '''push or pop patches until named patch is at top of stack'''
2130 '''push or pop patches until named patch is at top of stack'''
2130 q = repo.mq
2131 q = repo.mq
2131 patch = q.lookup(patch)
2132 patch = q.lookup(patch)
2132 if q.isapplied(patch):
2133 if q.isapplied(patch):
2133 ret = q.pop(repo, patch, force=opts['force'])
2134 ret = q.pop(repo, patch, force=opts['force'])
2134 else:
2135 else:
2135 ret = q.push(repo, patch, force=opts['force'])
2136 ret = q.push(repo, patch, force=opts['force'])
2136 q.save_dirty()
2137 q.save_dirty()
2137 return ret
2138 return ret
2138
2139
2139 def guard(ui, repo, *args, **opts):
2140 def guard(ui, repo, *args, **opts):
2140 '''set or print guards for a patch
2141 '''set or print guards for a patch
2141
2142
2142 Guards control whether a patch can be pushed. A patch with no
2143 Guards control whether a patch can be pushed. A patch with no
2143 guards is always pushed. A patch with a positive guard ("+foo") is
2144 guards is always pushed. A patch with a positive guard ("+foo") is
2144 pushed only if the qselect command has activated it. A patch with
2145 pushed only if the qselect command has activated it. A patch with
2145 a negative guard ("-foo") is never pushed if the qselect command
2146 a negative guard ("-foo") is never pushed if the qselect command
2146 has activated it.
2147 has activated it.
2147
2148
2148 With no arguments, print the currently active guards.
2149 With no arguments, print the currently active guards.
2149 With arguments, set guards for the named patch.
2150 With arguments, set guards for the named patch.
2150 NOTE: Specifying negative guards now requires '--'.
2151 NOTE: Specifying negative guards now requires '--'.
2151
2152
2152 To set guards on another patch::
2153 To set guards on another patch::
2153
2154
2154 hg qguard -- other.patch +2.6.17 -stable
2155 hg qguard -- other.patch +2.6.17 -stable
2155 '''
2156 '''
2156 def status(idx):
2157 def status(idx):
2157 guards = q.series_guards[idx] or ['unguarded']
2158 guards = q.series_guards[idx] or ['unguarded']
2158 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2159 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2159 q = repo.mq
2160 q = repo.mq
2160 patch = None
2161 patch = None
2161 args = list(args)
2162 args = list(args)
2162 if opts['list']:
2163 if opts['list']:
2163 if args or opts['none']:
2164 if args or opts['none']:
2164 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2165 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2165 for i in xrange(len(q.series)):
2166 for i in xrange(len(q.series)):
2166 status(i)
2167 status(i)
2167 return
2168 return
2168 if not args or args[0][0:1] in '-+':
2169 if not args or args[0][0:1] in '-+':
2169 if not q.applied:
2170 if not q.applied:
2170 raise util.Abort(_('no patches applied'))
2171 raise util.Abort(_('no patches applied'))
2171 patch = q.applied[-1].name
2172 patch = q.applied[-1].name
2172 if patch is None and args[0][0:1] not in '-+':
2173 if patch is None and args[0][0:1] not in '-+':
2173 patch = args.pop(0)
2174 patch = args.pop(0)
2174 if patch is None:
2175 if patch is None:
2175 raise util.Abort(_('no patch to work with'))
2176 raise util.Abort(_('no patch to work with'))
2176 if args or opts['none']:
2177 if args or opts['none']:
2177 idx = q.find_series(patch)
2178 idx = q.find_series(patch)
2178 if idx is None:
2179 if idx is None:
2179 raise util.Abort(_('no patch named %s') % patch)
2180 raise util.Abort(_('no patch named %s') % patch)
2180 q.set_guards(idx, args)
2181 q.set_guards(idx, args)
2181 q.save_dirty()
2182 q.save_dirty()
2182 else:
2183 else:
2183 status(q.series.index(q.lookup(patch)))
2184 status(q.series.index(q.lookup(patch)))
2184
2185
2185 def header(ui, repo, patch=None):
2186 def header(ui, repo, patch=None):
2186 """print the header of the topmost or specified patch"""
2187 """print the header of the topmost or specified patch"""
2187 q = repo.mq
2188 q = repo.mq
2188
2189
2189 if patch:
2190 if patch:
2190 patch = q.lookup(patch)
2191 patch = q.lookup(patch)
2191 else:
2192 else:
2192 if not q.applied:
2193 if not q.applied:
2193 ui.write('no patches applied\n')
2194 ui.write('no patches applied\n')
2194 return 1
2195 return 1
2195 patch = q.lookup('qtip')
2196 patch = q.lookup('qtip')
2196 ph = patchheader(q.join(patch), q.plainmode)
2197 ph = patchheader(q.join(patch), q.plainmode)
2197
2198
2198 ui.write('\n'.join(ph.message) + '\n')
2199 ui.write('\n'.join(ph.message) + '\n')
2199
2200
2200 def lastsavename(path):
2201 def lastsavename(path):
2201 (directory, base) = os.path.split(path)
2202 (directory, base) = os.path.split(path)
2202 names = os.listdir(directory)
2203 names = os.listdir(directory)
2203 namere = re.compile("%s.([0-9]+)" % base)
2204 namere = re.compile("%s.([0-9]+)" % base)
2204 maxindex = None
2205 maxindex = None
2205 maxname = None
2206 maxname = None
2206 for f in names:
2207 for f in names:
2207 m = namere.match(f)
2208 m = namere.match(f)
2208 if m:
2209 if m:
2209 index = int(m.group(1))
2210 index = int(m.group(1))
2210 if maxindex is None or index > maxindex:
2211 if maxindex is None or index > maxindex:
2211 maxindex = index
2212 maxindex = index
2212 maxname = f
2213 maxname = f
2213 if maxname:
2214 if maxname:
2214 return (os.path.join(directory, maxname), maxindex)
2215 return (os.path.join(directory, maxname), maxindex)
2215 return (None, None)
2216 return (None, None)
2216
2217
2217 def savename(path):
2218 def savename(path):
2218 (last, index) = lastsavename(path)
2219 (last, index) = lastsavename(path)
2219 if last is None:
2220 if last is None:
2220 index = 0
2221 index = 0
2221 newpath = path + ".%d" % (index + 1)
2222 newpath = path + ".%d" % (index + 1)
2222 return newpath
2223 return newpath
2223
2224
2224 def push(ui, repo, patch=None, **opts):
2225 def push(ui, repo, patch=None, **opts):
2225 """push the next patch onto the stack
2226 """push the next patch onto the stack
2226
2227
2227 When -f/--force is applied, all local changes in patched files
2228 When -f/--force is applied, all local changes in patched files
2228 will be lost.
2229 will be lost.
2229 """
2230 """
2230 q = repo.mq
2231 q = repo.mq
2231 mergeq = None
2232 mergeq = None
2232
2233
2233 if opts['merge']:
2234 if opts['merge']:
2234 if opts['name']:
2235 if opts['name']:
2235 newpath = repo.join(opts['name'])
2236 newpath = repo.join(opts['name'])
2236 else:
2237 else:
2237 newpath, i = lastsavename(q.path)
2238 newpath, i = lastsavename(q.path)
2238 if not newpath:
2239 if not newpath:
2239 ui.warn(_("no saved queues found, please use -n\n"))
2240 ui.warn(_("no saved queues found, please use -n\n"))
2240 return 1
2241 return 1
2241 mergeq = queue(ui, repo.join(""), newpath)
2242 mergeq = queue(ui, repo.join(""), newpath)
2242 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2243 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2243 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2244 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2244 mergeq=mergeq, all=opts.get('all'))
2245 mergeq=mergeq, all=opts.get('all'))
2245 return ret
2246 return ret
2246
2247
2247 def pop(ui, repo, patch=None, **opts):
2248 def pop(ui, repo, patch=None, **opts):
2248 """pop the current patch off the stack
2249 """pop the current patch off the stack
2249
2250
2250 By default, pops off the top of the patch stack. If given a patch
2251 By default, pops off the top of the patch stack. If given a patch
2251 name, keeps popping off patches until the named patch is at the
2252 name, keeps popping off patches until the named patch is at the
2252 top of the stack.
2253 top of the stack.
2253 """
2254 """
2254 localupdate = True
2255 localupdate = True
2255 if opts['name']:
2256 if opts['name']:
2256 q = queue(ui, repo.join(""), repo.join(opts['name']))
2257 q = queue(ui, repo.join(""), repo.join(opts['name']))
2257 ui.warn(_('using patch queue: %s\n') % q.path)
2258 ui.warn(_('using patch queue: %s\n') % q.path)
2258 localupdate = False
2259 localupdate = False
2259 else:
2260 else:
2260 q = repo.mq
2261 q = repo.mq
2261 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2262 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2262 all=opts['all'])
2263 all=opts['all'])
2263 q.save_dirty()
2264 q.save_dirty()
2264 return ret
2265 return ret
2265
2266
2266 def rename(ui, repo, patch, name=None, **opts):
2267 def rename(ui, repo, patch, name=None, **opts):
2267 """rename a patch
2268 """rename a patch
2268
2269
2269 With one argument, renames the current patch to PATCH1.
2270 With one argument, renames the current patch to PATCH1.
2270 With two arguments, renames PATCH1 to PATCH2."""
2271 With two arguments, renames PATCH1 to PATCH2."""
2271
2272
2272 q = repo.mq
2273 q = repo.mq
2273
2274
2274 if not name:
2275 if not name:
2275 name = patch
2276 name = patch
2276 patch = None
2277 patch = None
2277
2278
2278 if patch:
2279 if patch:
2279 patch = q.lookup(patch)
2280 patch = q.lookup(patch)
2280 else:
2281 else:
2281 if not q.applied:
2282 if not q.applied:
2282 ui.write(_('no patches applied\n'))
2283 ui.write(_('no patches applied\n'))
2283 return
2284 return
2284 patch = q.lookup('qtip')
2285 patch = q.lookup('qtip')
2285 absdest = q.join(name)
2286 absdest = q.join(name)
2286 if os.path.isdir(absdest):
2287 if os.path.isdir(absdest):
2287 name = normname(os.path.join(name, os.path.basename(patch)))
2288 name = normname(os.path.join(name, os.path.basename(patch)))
2288 absdest = q.join(name)
2289 absdest = q.join(name)
2289 if os.path.exists(absdest):
2290 if os.path.exists(absdest):
2290 raise util.Abort(_('%s already exists') % absdest)
2291 raise util.Abort(_('%s already exists') % absdest)
2291
2292
2292 if name in q.series:
2293 if name in q.series:
2293 raise util.Abort(
2294 raise util.Abort(
2294 _('A patch named %s already exists in the series file') % name)
2295 _('A patch named %s already exists in the series file') % name)
2295
2296
2296 if ui.verbose:
2297 if ui.verbose:
2297 ui.write('renaming %s to %s\n' % (patch, name))
2298 ui.write('renaming %s to %s\n' % (patch, name))
2298 i = q.find_series(patch)
2299 i = q.find_series(patch)
2299 guards = q.guard_re.findall(q.full_series[i])
2300 guards = q.guard_re.findall(q.full_series[i])
2300 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2301 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2301 q.parse_series()
2302 q.parse_series()
2302 q.series_dirty = 1
2303 q.series_dirty = 1
2303
2304
2304 info = q.isapplied(patch)
2305 info = q.isapplied(patch)
2305 if info:
2306 if info:
2306 q.applied[info[0]] = statusentry(info[1], name)
2307 q.applied[info[0]] = statusentry(info[1], name)
2307 q.applied_dirty = 1
2308 q.applied_dirty = 1
2308
2309
2309 util.rename(q.join(patch), absdest)
2310 util.rename(q.join(patch), absdest)
2310 r = q.qrepo()
2311 r = q.qrepo()
2311 if r:
2312 if r:
2312 wlock = r.wlock()
2313 wlock = r.wlock()
2313 try:
2314 try:
2314 if r.dirstate[patch] == 'a':
2315 if r.dirstate[patch] == 'a':
2315 r.dirstate.forget(patch)
2316 r.dirstate.forget(patch)
2316 r.dirstate.add(name)
2317 r.dirstate.add(name)
2317 else:
2318 else:
2318 if r.dirstate[name] == 'r':
2319 if r.dirstate[name] == 'r':
2319 r.undelete([name])
2320 r.undelete([name])
2320 r.copy(patch, name)
2321 r.copy(patch, name)
2321 r.remove([patch], False)
2322 r.remove([patch], False)
2322 finally:
2323 finally:
2323 wlock.release()
2324 wlock.release()
2324
2325
2325 q.save_dirty()
2326 q.save_dirty()
2326
2327
2327 def restore(ui, repo, rev, **opts):
2328 def restore(ui, repo, rev, **opts):
2328 """restore the queue state saved by a revision (DEPRECATED)
2329 """restore the queue state saved by a revision (DEPRECATED)
2329
2330
2330 This command is deprecated, use rebase --mq instead."""
2331 This command is deprecated, use rebase --mq instead."""
2331 rev = repo.lookup(rev)
2332 rev = repo.lookup(rev)
2332 q = repo.mq
2333 q = repo.mq
2333 q.restore(repo, rev, delete=opts['delete'],
2334 q.restore(repo, rev, delete=opts['delete'],
2334 qupdate=opts['update'])
2335 qupdate=opts['update'])
2335 q.save_dirty()
2336 q.save_dirty()
2336 return 0
2337 return 0
2337
2338
2338 def save(ui, repo, **opts):
2339 def save(ui, repo, **opts):
2339 """save current queue state (DEPRECATED)
2340 """save current queue state (DEPRECATED)
2340
2341
2341 This command is deprecated, use rebase --mq instead."""
2342 This command is deprecated, use rebase --mq instead."""
2342 q = repo.mq
2343 q = repo.mq
2343 message = cmdutil.logmessage(opts)
2344 message = cmdutil.logmessage(opts)
2344 ret = q.save(repo, msg=message)
2345 ret = q.save(repo, msg=message)
2345 if ret:
2346 if ret:
2346 return ret
2347 return ret
2347 q.save_dirty()
2348 q.save_dirty()
2348 if opts['copy']:
2349 if opts['copy']:
2349 path = q.path
2350 path = q.path
2350 if opts['name']:
2351 if opts['name']:
2351 newpath = os.path.join(q.basepath, opts['name'])
2352 newpath = os.path.join(q.basepath, opts['name'])
2352 if os.path.exists(newpath):
2353 if os.path.exists(newpath):
2353 if not os.path.isdir(newpath):
2354 if not os.path.isdir(newpath):
2354 raise util.Abort(_('destination %s exists and is not '
2355 raise util.Abort(_('destination %s exists and is not '
2355 'a directory') % newpath)
2356 'a directory') % newpath)
2356 if not opts['force']:
2357 if not opts['force']:
2357 raise util.Abort(_('destination %s exists, '
2358 raise util.Abort(_('destination %s exists, '
2358 'use -f to force') % newpath)
2359 'use -f to force') % newpath)
2359 else:
2360 else:
2360 newpath = savename(path)
2361 newpath = savename(path)
2361 ui.warn(_("copy %s to %s\n") % (path, newpath))
2362 ui.warn(_("copy %s to %s\n") % (path, newpath))
2362 util.copyfiles(path, newpath)
2363 util.copyfiles(path, newpath)
2363 if opts['empty']:
2364 if opts['empty']:
2364 try:
2365 try:
2365 os.unlink(q.join(q.status_path))
2366 os.unlink(q.join(q.status_path))
2366 except:
2367 except:
2367 pass
2368 pass
2368 return 0
2369 return 0
2369
2370
2370 def strip(ui, repo, rev, **opts):
2371 def strip(ui, repo, rev, **opts):
2371 """strip a revision and all its descendants from the repository
2372 """strip a revision and all its descendants from the repository
2372
2373
2373 If one of the working directory's parent revisions is stripped, the
2374 If one of the working directory's parent revisions is stripped, the
2374 working directory will be updated to the parent of the stripped
2375 working directory will be updated to the parent of the stripped
2375 revision.
2376 revision.
2376 """
2377 """
2377 backup = 'all'
2378 backup = 'all'
2378 if opts['backup']:
2379 if opts['backup']:
2379 backup = 'strip'
2380 backup = 'strip'
2380 elif opts['nobackup']:
2381 elif opts['nobackup']:
2381 backup = 'none'
2382 backup = 'none'
2382
2383
2383 rev = repo.lookup(rev)
2384 rev = repo.lookup(rev)
2384 p = repo.dirstate.parents()
2385 p = repo.dirstate.parents()
2385 cl = repo.changelog
2386 cl = repo.changelog
2386 update = True
2387 update = True
2387 if p[0] == nullid:
2388 if p[0] == nullid:
2388 update = False
2389 update = False
2389 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2390 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2390 update = False
2391 update = False
2391 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2392 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2392 update = False
2393 update = False
2393
2394
2394 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2395 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2395 return 0
2396 return 0
2396
2397
2397 def select(ui, repo, *args, **opts):
2398 def select(ui, repo, *args, **opts):
2398 '''set or print guarded patches to push
2399 '''set or print guarded patches to push
2399
2400
2400 Use the qguard command to set or print guards on patch, then use
2401 Use the qguard command to set or print guards on patch, then use
2401 qselect to tell mq which guards to use. A patch will be pushed if
2402 qselect to tell mq which guards to use. A patch will be pushed if
2402 it has no guards or any positive guards match the currently
2403 it has no guards or any positive guards match the currently
2403 selected guard, but will not be pushed if any negative guards
2404 selected guard, but will not be pushed if any negative guards
2404 match the current guard. For example::
2405 match the current guard. For example::
2405
2406
2406 qguard foo.patch -stable (negative guard)
2407 qguard foo.patch -stable (negative guard)
2407 qguard bar.patch +stable (positive guard)
2408 qguard bar.patch +stable (positive guard)
2408 qselect stable
2409 qselect stable
2409
2410
2410 This activates the "stable" guard. mq will skip foo.patch (because
2411 This activates the "stable" guard. mq will skip foo.patch (because
2411 it has a negative match) but push bar.patch (because it has a
2412 it has a negative match) but push bar.patch (because it has a
2412 positive match).
2413 positive match).
2413
2414
2414 With no arguments, prints the currently active guards.
2415 With no arguments, prints the currently active guards.
2415 With one argument, sets the active guard.
2416 With one argument, sets the active guard.
2416
2417
2417 Use -n/--none to deactivate guards (no other arguments needed).
2418 Use -n/--none to deactivate guards (no other arguments needed).
2418 When no guards are active, patches with positive guards are
2419 When no guards are active, patches with positive guards are
2419 skipped and patches with negative guards are pushed.
2420 skipped and patches with negative guards are pushed.
2420
2421
2421 qselect can change the guards on applied patches. It does not pop
2422 qselect can change the guards on applied patches. It does not pop
2422 guarded patches by default. Use --pop to pop back to the last
2423 guarded patches by default. Use --pop to pop back to the last
2423 applied patch that is not guarded. Use --reapply (which implies
2424 applied patch that is not guarded. Use --reapply (which implies
2424 --pop) to push back to the current patch afterwards, but skip
2425 --pop) to push back to the current patch afterwards, but skip
2425 guarded patches.
2426 guarded patches.
2426
2427
2427 Use -s/--series to print a list of all guards in the series file
2428 Use -s/--series to print a list of all guards in the series file
2428 (no other arguments needed). Use -v for more information.'''
2429 (no other arguments needed). Use -v for more information.'''
2429
2430
2430 q = repo.mq
2431 q = repo.mq
2431 guards = q.active()
2432 guards = q.active()
2432 if args or opts['none']:
2433 if args or opts['none']:
2433 old_unapplied = q.unapplied(repo)
2434 old_unapplied = q.unapplied(repo)
2434 old_guarded = [i for i in xrange(len(q.applied)) if
2435 old_guarded = [i for i in xrange(len(q.applied)) if
2435 not q.pushable(i)[0]]
2436 not q.pushable(i)[0]]
2436 q.set_active(args)
2437 q.set_active(args)
2437 q.save_dirty()
2438 q.save_dirty()
2438 if not args:
2439 if not args:
2439 ui.status(_('guards deactivated\n'))
2440 ui.status(_('guards deactivated\n'))
2440 if not opts['pop'] and not opts['reapply']:
2441 if not opts['pop'] and not opts['reapply']:
2441 unapplied = q.unapplied(repo)
2442 unapplied = q.unapplied(repo)
2442 guarded = [i for i in xrange(len(q.applied))
2443 guarded = [i for i in xrange(len(q.applied))
2443 if not q.pushable(i)[0]]
2444 if not q.pushable(i)[0]]
2444 if len(unapplied) != len(old_unapplied):
2445 if len(unapplied) != len(old_unapplied):
2445 ui.status(_('number of unguarded, unapplied patches has '
2446 ui.status(_('number of unguarded, unapplied patches has '
2446 'changed from %d to %d\n') %
2447 'changed from %d to %d\n') %
2447 (len(old_unapplied), len(unapplied)))
2448 (len(old_unapplied), len(unapplied)))
2448 if len(guarded) != len(old_guarded):
2449 if len(guarded) != len(old_guarded):
2449 ui.status(_('number of guarded, applied patches has changed '
2450 ui.status(_('number of guarded, applied patches has changed '
2450 'from %d to %d\n') %
2451 'from %d to %d\n') %
2451 (len(old_guarded), len(guarded)))
2452 (len(old_guarded), len(guarded)))
2452 elif opts['series']:
2453 elif opts['series']:
2453 guards = {}
2454 guards = {}
2454 noguards = 0
2455 noguards = 0
2455 for gs in q.series_guards:
2456 for gs in q.series_guards:
2456 if not gs:
2457 if not gs:
2457 noguards += 1
2458 noguards += 1
2458 for g in gs:
2459 for g in gs:
2459 guards.setdefault(g, 0)
2460 guards.setdefault(g, 0)
2460 guards[g] += 1
2461 guards[g] += 1
2461 if ui.verbose:
2462 if ui.verbose:
2462 guards['NONE'] = noguards
2463 guards['NONE'] = noguards
2463 guards = guards.items()
2464 guards = guards.items()
2464 guards.sort(key=lambda x: x[0][1:])
2465 guards.sort(key=lambda x: x[0][1:])
2465 if guards:
2466 if guards:
2466 ui.note(_('guards in series file:\n'))
2467 ui.note(_('guards in series file:\n'))
2467 for guard, count in guards:
2468 for guard, count in guards:
2468 ui.note('%2d ' % count)
2469 ui.note('%2d ' % count)
2469 ui.write(guard, '\n')
2470 ui.write(guard, '\n')
2470 else:
2471 else:
2471 ui.note(_('no guards in series file\n'))
2472 ui.note(_('no guards in series file\n'))
2472 else:
2473 else:
2473 if guards:
2474 if guards:
2474 ui.note(_('active guards:\n'))
2475 ui.note(_('active guards:\n'))
2475 for g in guards:
2476 for g in guards:
2476 ui.write(g, '\n')
2477 ui.write(g, '\n')
2477 else:
2478 else:
2478 ui.write(_('no active guards\n'))
2479 ui.write(_('no active guards\n'))
2479 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2480 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2480 popped = False
2481 popped = False
2481 if opts['pop'] or opts['reapply']:
2482 if opts['pop'] or opts['reapply']:
2482 for i in xrange(len(q.applied)):
2483 for i in xrange(len(q.applied)):
2483 pushable, reason = q.pushable(i)
2484 pushable, reason = q.pushable(i)
2484 if not pushable:
2485 if not pushable:
2485 ui.status(_('popping guarded patches\n'))
2486 ui.status(_('popping guarded patches\n'))
2486 popped = True
2487 popped = True
2487 if i == 0:
2488 if i == 0:
2488 q.pop(repo, all=True)
2489 q.pop(repo, all=True)
2489 else:
2490 else:
2490 q.pop(repo, i - 1)
2491 q.pop(repo, i - 1)
2491 break
2492 break
2492 if popped:
2493 if popped:
2493 try:
2494 try:
2494 if reapply:
2495 if reapply:
2495 ui.status(_('reapplying unguarded patches\n'))
2496 ui.status(_('reapplying unguarded patches\n'))
2496 q.push(repo, reapply)
2497 q.push(repo, reapply)
2497 finally:
2498 finally:
2498 q.save_dirty()
2499 q.save_dirty()
2499
2500
2500 def finish(ui, repo, *revrange, **opts):
2501 def finish(ui, repo, *revrange, **opts):
2501 """move applied patches into repository history
2502 """move applied patches into repository history
2502
2503
2503 Finishes the specified revisions (corresponding to applied
2504 Finishes the specified revisions (corresponding to applied
2504 patches) by moving them out of mq control into regular repository
2505 patches) by moving them out of mq control into regular repository
2505 history.
2506 history.
2506
2507
2507 Accepts a revision range or the -a/--applied option. If --applied
2508 Accepts a revision range or the -a/--applied option. If --applied
2508 is specified, all applied mq revisions are removed from mq
2509 is specified, all applied mq revisions are removed from mq
2509 control. Otherwise, the given revisions must be at the base of the
2510 control. Otherwise, the given revisions must be at the base of the
2510 stack of applied patches.
2511 stack of applied patches.
2511
2512
2512 This can be especially useful if your changes have been applied to
2513 This can be especially useful if your changes have been applied to
2513 an upstream repository, or if you are about to push your changes
2514 an upstream repository, or if you are about to push your changes
2514 to upstream.
2515 to upstream.
2515 """
2516 """
2516 if not opts['applied'] and not revrange:
2517 if not opts['applied'] and not revrange:
2517 raise util.Abort(_('no revisions specified'))
2518 raise util.Abort(_('no revisions specified'))
2518 elif opts['applied']:
2519 elif opts['applied']:
2519 revrange = ('qbase:qtip',) + revrange
2520 revrange = ('qbase:qtip',) + revrange
2520
2521
2521 q = repo.mq
2522 q = repo.mq
2522 if not q.applied:
2523 if not q.applied:
2523 ui.status(_('no patches applied\n'))
2524 ui.status(_('no patches applied\n'))
2524 return 0
2525 return 0
2525
2526
2526 revs = cmdutil.revrange(repo, revrange)
2527 revs = cmdutil.revrange(repo, revrange)
2527 q.finish(repo, revs)
2528 q.finish(repo, revs)
2528 q.save_dirty()
2529 q.save_dirty()
2529 return 0
2530 return 0
2530
2531
2531 def reposetup(ui, repo):
2532 def reposetup(ui, repo):
2532 class mqrepo(repo.__class__):
2533 class mqrepo(repo.__class__):
2533 @util.propertycache
2534 @util.propertycache
2534 def mq(self):
2535 def mq(self):
2535 return queue(self.ui, self.join(""))
2536 return queue(self.ui, self.join(""))
2536
2537
2537 def abort_if_wdir_patched(self, errmsg, force=False):
2538 def abort_if_wdir_patched(self, errmsg, force=False):
2538 if self.mq.applied and not force:
2539 if self.mq.applied and not force:
2539 parent = hex(self.dirstate.parents()[0])
2540 parent = hex(self.dirstate.parents()[0])
2540 if parent in [s.rev for s in self.mq.applied]:
2541 if parent in [s.rev for s in self.mq.applied]:
2541 raise util.Abort(errmsg)
2542 raise util.Abort(errmsg)
2542
2543
2543 def commit(self, text="", user=None, date=None, match=None,
2544 def commit(self, text="", user=None, date=None, match=None,
2544 force=False, editor=False, extra={}):
2545 force=False, editor=False, extra={}):
2545 self.abort_if_wdir_patched(
2546 self.abort_if_wdir_patched(
2546 _('cannot commit over an applied mq patch'),
2547 _('cannot commit over an applied mq patch'),
2547 force)
2548 force)
2548
2549
2549 return super(mqrepo, self).commit(text, user, date, match, force,
2550 return super(mqrepo, self).commit(text, user, date, match, force,
2550 editor, extra)
2551 editor, extra)
2551
2552
2552 def push(self, remote, force=False, revs=None):
2553 def push(self, remote, force=False, revs=None):
2553 if self.mq.applied and not force and not revs:
2554 if self.mq.applied and not force and not revs:
2554 raise util.Abort(_('source has mq patches applied'))
2555 raise util.Abort(_('source has mq patches applied'))
2555 return super(mqrepo, self).push(remote, force, revs)
2556 return super(mqrepo, self).push(remote, force, revs)
2556
2557
2557 def _findtags(self):
2558 def _findtags(self):
2558 '''augment tags from base class with patch tags'''
2559 '''augment tags from base class with patch tags'''
2559 result = super(mqrepo, self)._findtags()
2560 result = super(mqrepo, self)._findtags()
2560
2561
2561 q = self.mq
2562 q = self.mq
2562 if not q.applied:
2563 if not q.applied:
2563 return result
2564 return result
2564
2565
2565 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2566 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2566
2567
2567 if mqtags[-1][0] not in self.changelog.nodemap:
2568 if mqtags[-1][0] not in self.changelog.nodemap:
2568 self.ui.warn(_('mq status file refers to unknown node %s\n')
2569 self.ui.warn(_('mq status file refers to unknown node %s\n')
2569 % short(mqtags[-1][0]))
2570 % short(mqtags[-1][0]))
2570 return result
2571 return result
2571
2572
2572 mqtags.append((mqtags[-1][0], 'qtip'))
2573 mqtags.append((mqtags[-1][0], 'qtip'))
2573 mqtags.append((mqtags[0][0], 'qbase'))
2574 mqtags.append((mqtags[0][0], 'qbase'))
2574 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2575 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2575 tags = result[0]
2576 tags = result[0]
2576 for patch in mqtags:
2577 for patch in mqtags:
2577 if patch[1] in tags:
2578 if patch[1] in tags:
2578 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2579 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2579 % patch[1])
2580 % patch[1])
2580 else:
2581 else:
2581 tags[patch[1]] = patch[0]
2582 tags[patch[1]] = patch[0]
2582
2583
2583 return result
2584 return result
2584
2585
2585 def _branchtags(self, partial, lrev):
2586 def _branchtags(self, partial, lrev):
2586 q = self.mq
2587 q = self.mq
2587 if not q.applied:
2588 if not q.applied:
2588 return super(mqrepo, self)._branchtags(partial, lrev)
2589 return super(mqrepo, self)._branchtags(partial, lrev)
2589
2590
2590 cl = self.changelog
2591 cl = self.changelog
2591 qbasenode = bin(q.applied[0].rev)
2592 qbasenode = bin(q.applied[0].rev)
2592 if qbasenode not in cl.nodemap:
2593 if qbasenode not in cl.nodemap:
2593 self.ui.warn(_('mq status file refers to unknown node %s\n')
2594 self.ui.warn(_('mq status file refers to unknown node %s\n')
2594 % short(qbasenode))
2595 % short(qbasenode))
2595 return super(mqrepo, self)._branchtags(partial, lrev)
2596 return super(mqrepo, self)._branchtags(partial, lrev)
2596
2597
2597 qbase = cl.rev(qbasenode)
2598 qbase = cl.rev(qbasenode)
2598 start = lrev + 1
2599 start = lrev + 1
2599 if start < qbase:
2600 if start < qbase:
2600 # update the cache (excluding the patches) and save it
2601 # update the cache (excluding the patches) and save it
2601 self._updatebranchcache(partial, lrev + 1, qbase)
2602 self._updatebranchcache(partial, lrev + 1, qbase)
2602 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2603 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2603 start = qbase
2604 start = qbase
2604 # if start = qbase, the cache is as updated as it should be.
2605 # if start = qbase, the cache is as updated as it should be.
2605 # if start > qbase, the cache includes (part of) the patches.
2606 # if start > qbase, the cache includes (part of) the patches.
2606 # we might as well use it, but we won't save it.
2607 # we might as well use it, but we won't save it.
2607
2608
2608 # update the cache up to the tip
2609 # update the cache up to the tip
2609 self._updatebranchcache(partial, start, len(cl))
2610 self._updatebranchcache(partial, start, len(cl))
2610
2611
2611 return partial
2612 return partial
2612
2613
2613 if repo.local():
2614 if repo.local():
2614 repo.__class__ = mqrepo
2615 repo.__class__ = mqrepo
2615
2616
2616 def mqimport(orig, ui, repo, *args, **kwargs):
2617 def mqimport(orig, ui, repo, *args, **kwargs):
2617 if (hasattr(repo, 'abort_if_wdir_patched')
2618 if (hasattr(repo, 'abort_if_wdir_patched')
2618 and not kwargs.get('no_commit', False)):
2619 and not kwargs.get('no_commit', False)):
2619 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2620 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2620 kwargs.get('force'))
2621 kwargs.get('force'))
2621 return orig(ui, repo, *args, **kwargs)
2622 return orig(ui, repo, *args, **kwargs)
2622
2623
2623 def mqinit(orig, ui, *args, **kwargs):
2624 def mqinit(orig, ui, *args, **kwargs):
2624 mq = kwargs['mq']
2625 mq = kwargs['mq']
2625 del kwargs['mq']
2626 del kwargs['mq']
2626
2627
2627 if not mq:
2628 if not mq:
2628 return orig(ui, *args, **kwargs)
2629 return orig(ui, *args, **kwargs)
2629
2630
2630 repopath = cmdutil.findrepo(os.getcwd())
2631 repopath = cmdutil.findrepo(os.getcwd())
2631 repo = hg.repository(ui, repopath)
2632 repo = hg.repository(ui, repopath)
2632 q = repo.mq
2633 q = repo.mq
2633 r = q.init(repo, create=True)
2634 r = q.init(repo, create=True)
2634 q.save_dirty()
2635 q.save_dirty()
2635
2636
2636 if not os.path.exists(r.wjoin('.hgignore')):
2637 if not os.path.exists(r.wjoin('.hgignore')):
2637 fp = r.wopener('.hgignore', 'w')
2638 fp = r.wopener('.hgignore', 'w')
2638 fp.write('^\\.hg\n')
2639 fp.write('^\\.hg\n')
2639 fp.write('^\\.mq\n')
2640 fp.write('^\\.mq\n')
2640 fp.write('syntax: glob\n')
2641 fp.write('syntax: glob\n')
2641 fp.write('status\n')
2642 fp.write('status\n')
2642 fp.write('guards\n')
2643 fp.write('guards\n')
2643 fp.close()
2644 fp.close()
2644 if not os.path.exists(r.wjoin('series')):
2645 if not os.path.exists(r.wjoin('series')):
2645 r.wopener('series', 'w').close()
2646 r.wopener('series', 'w').close()
2646 r.add(['.hgignore', 'series'])
2647 r.add(['.hgignore', 'series'])
2647 commands.add(ui, r)
2648 commands.add(ui, r)
2648
2649
2649 def mqcommand(orig, ui, repo, *args, **kwargs):
2650 def mqcommand(orig, ui, repo, *args, **kwargs):
2650 """Add --mq option to operate on patch repository instead of main"""
2651 """Add --mq option to operate on patch repository instead of main"""
2651
2652
2652 # some commands do not like getting unknown options
2653 # some commands do not like getting unknown options
2653 mq = kwargs['mq']
2654 mq = kwargs['mq']
2654 del kwargs['mq']
2655 del kwargs['mq']
2655
2656
2656 if not mq:
2657 if not mq:
2657 return orig(ui, repo, *args, **kwargs)
2658 return orig(ui, repo, *args, **kwargs)
2658
2659
2659 q = repo.mq
2660 q = repo.mq
2660 r = q.qrepo()
2661 r = q.qrepo()
2661 if not r:
2662 if not r:
2662 raise util.Abort('no queue repository')
2663 raise util.Abort('no queue repository')
2663 return orig(r.ui, r, *args, **kwargs)
2664 return orig(r.ui, r, *args, **kwargs)
2664
2665
2665 def uisetup(ui):
2666 def uisetup(ui):
2666 mqopt = [('Q', 'mq', None, _("operate on patch repository"))]
2667 mqopt = [('Q', 'mq', None, _("operate on patch repository"))]
2667
2668
2668 extensions.wrapcommand(commands.table, 'import', mqimport)
2669 extensions.wrapcommand(commands.table, 'import', mqimport)
2669
2670
2670 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2671 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2671 entry[1].extend(mqopt)
2672 entry[1].extend(mqopt)
2672
2673
2673 for cmd in commands.table:
2674 for cmd in commands.table:
2674 cmd = cmdutil.parsealiases(cmd)[0]
2675 cmd = cmdutil.parsealiases(cmd)[0]
2675 if cmd in commands.norepo:
2676 if cmd in commands.norepo:
2676 continue
2677 continue
2677 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2678 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2678 entry[1].extend(mqopt)
2679 entry[1].extend(mqopt)
2679
2680
2680 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2681 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2681
2682
2682 cmdtable = {
2683 cmdtable = {
2683 "qapplied":
2684 "qapplied":
2684 (applied,
2685 (applied,
2685 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2686 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2686 _('hg qapplied [-1] [-s] [PATCH]')),
2687 _('hg qapplied [-1] [-s] [PATCH]')),
2687 "qclone":
2688 "qclone":
2688 (clone,
2689 (clone,
2689 [('', 'pull', None, _('use pull protocol to copy metadata')),
2690 [('', 'pull', None, _('use pull protocol to copy metadata')),
2690 ('U', 'noupdate', None, _('do not update the new working directories')),
2691 ('U', 'noupdate', None, _('do not update the new working directories')),
2691 ('', 'uncompressed', None,
2692 ('', 'uncompressed', None,
2692 _('use uncompressed transfer (fast over LAN)')),
2693 _('use uncompressed transfer (fast over LAN)')),
2693 ('p', 'patches', '', _('location of source patch repository')),
2694 ('p', 'patches', '', _('location of source patch repository')),
2694 ] + commands.remoteopts,
2695 ] + commands.remoteopts,
2695 _('hg qclone [OPTION]... SOURCE [DEST]')),
2696 _('hg qclone [OPTION]... SOURCE [DEST]')),
2696 "qcommit|qci":
2697 "qcommit|qci":
2697 (commit,
2698 (commit,
2698 commands.table["^commit|ci"][1],
2699 commands.table["^commit|ci"][1],
2699 _('hg qcommit [OPTION]... [FILE]...')),
2700 _('hg qcommit [OPTION]... [FILE]...')),
2700 "^qdiff":
2701 "^qdiff":
2701 (diff,
2702 (diff,
2702 commands.diffopts + commands.diffopts2 + commands.walkopts,
2703 commands.diffopts + commands.diffopts2 + commands.walkopts,
2703 _('hg qdiff [OPTION]... [FILE]...')),
2704 _('hg qdiff [OPTION]... [FILE]...')),
2704 "qdelete|qremove|qrm":
2705 "qdelete|qremove|qrm":
2705 (delete,
2706 (delete,
2706 [('k', 'keep', None, _('keep patch file')),
2707 [('k', 'keep', None, _('keep patch file')),
2707 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2708 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2708 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2709 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2709 'qfold':
2710 'qfold':
2710 (fold,
2711 (fold,
2711 [('e', 'edit', None, _('edit patch header')),
2712 [('e', 'edit', None, _('edit patch header')),
2712 ('k', 'keep', None, _('keep folded patch files')),
2713 ('k', 'keep', None, _('keep folded patch files')),
2713 ] + commands.commitopts,
2714 ] + commands.commitopts,
2714 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2715 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2715 'qgoto':
2716 'qgoto':
2716 (goto,
2717 (goto,
2717 [('f', 'force', None, _('overwrite any local changes'))],
2718 [('f', 'force', None, _('overwrite any local changes'))],
2718 _('hg qgoto [OPTION]... PATCH')),
2719 _('hg qgoto [OPTION]... PATCH')),
2719 'qguard':
2720 'qguard':
2720 (guard,
2721 (guard,
2721 [('l', 'list', None, _('list all patches and guards')),
2722 [('l', 'list', None, _('list all patches and guards')),
2722 ('n', 'none', None, _('drop all guards'))],
2723 ('n', 'none', None, _('drop all guards'))],
2723 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2724 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2724 'qheader': (header, [], _('hg qheader [PATCH]')),
2725 'qheader': (header, [], _('hg qheader [PATCH]')),
2725 "^qimport":
2726 "^qimport":
2726 (qimport,
2727 (qimport,
2727 [('e', 'existing', None, _('import file in patch directory')),
2728 [('e', 'existing', None, _('import file in patch directory')),
2728 ('n', 'name', '', _('name of patch file')),
2729 ('n', 'name', '', _('name of patch file')),
2729 ('f', 'force', None, _('overwrite existing files')),
2730 ('f', 'force', None, _('overwrite existing files')),
2730 ('r', 'rev', [], _('place existing revisions under mq control')),
2731 ('r', 'rev', [], _('place existing revisions under mq control')),
2731 ('g', 'git', None, _('use git extended diff format')),
2732 ('g', 'git', None, _('use git extended diff format')),
2732 ('P', 'push', None, _('qpush after importing'))],
2733 ('P', 'push', None, _('qpush after importing'))],
2733 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2734 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2734 "^qinit":
2735 "^qinit":
2735 (init,
2736 (init,
2736 [('c', 'create-repo', None, _('create queue repository'))],
2737 [('c', 'create-repo', None, _('create queue repository'))],
2737 _('hg qinit [-c]')),
2738 _('hg qinit [-c]')),
2738 "qnew":
2739 "qnew":
2739 (new,
2740 (new,
2740 [('e', 'edit', None, _('edit commit message')),
2741 [('e', 'edit', None, _('edit commit message')),
2741 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2742 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2742 ('g', 'git', None, _('use git extended diff format')),
2743 ('g', 'git', None, _('use git extended diff format')),
2743 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2744 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2744 ('u', 'user', '', _('add "From: <given user>" to patch')),
2745 ('u', 'user', '', _('add "From: <given user>" to patch')),
2745 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2746 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2746 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2747 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2747 ] + commands.walkopts + commands.commitopts,
2748 ] + commands.walkopts + commands.commitopts,
2748 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2749 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2749 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2750 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2750 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2751 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2751 "^qpop":
2752 "^qpop":
2752 (pop,
2753 (pop,
2753 [('a', 'all', None, _('pop all patches')),
2754 [('a', 'all', None, _('pop all patches')),
2754 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2755 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2755 ('f', 'force', None, _('forget any local changes to patched files'))],
2756 ('f', 'force', None, _('forget any local changes to patched files'))],
2756 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2757 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2757 "^qpush":
2758 "^qpush":
2758 (push,
2759 (push,
2759 [('f', 'force', None, _('apply if the patch has rejects')),
2760 [('f', 'force', None, _('apply if the patch has rejects')),
2760 ('l', 'list', None, _('list patch name in commit text')),
2761 ('l', 'list', None, _('list patch name in commit text')),
2761 ('a', 'all', None, _('apply all patches')),
2762 ('a', 'all', None, _('apply all patches')),
2762 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2763 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2763 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2764 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2764 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2765 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2765 "^qrefresh":
2766 "^qrefresh":
2766 (refresh,
2767 (refresh,
2767 [('e', 'edit', None, _('edit commit message')),
2768 [('e', 'edit', None, _('edit commit message')),
2768 ('g', 'git', None, _('use git extended diff format')),
2769 ('g', 'git', None, _('use git extended diff format')),
2769 ('s', 'short', None,
2770 ('s', 'short', None,
2770 _('refresh only files already in the patch and specified files')),
2771 _('refresh only files already in the patch and specified files')),
2771 ('U', 'currentuser', None,
2772 ('U', 'currentuser', None,
2772 _('add/update author field in patch with current user')),
2773 _('add/update author field in patch with current user')),
2773 ('u', 'user', '',
2774 ('u', 'user', '',
2774 _('add/update author field in patch with given user')),
2775 _('add/update author field in patch with given user')),
2775 ('D', 'currentdate', None,
2776 ('D', 'currentdate', None,
2776 _('add/update date field in patch with current date')),
2777 _('add/update date field in patch with current date')),
2777 ('d', 'date', '',
2778 ('d', 'date', '',
2778 _('add/update date field in patch with given date'))
2779 _('add/update date field in patch with given date'))
2779 ] + commands.walkopts + commands.commitopts,
2780 ] + commands.walkopts + commands.commitopts,
2780 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2781 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2781 'qrename|qmv':
2782 'qrename|qmv':
2782 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2783 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2783 "qrestore":
2784 "qrestore":
2784 (restore,
2785 (restore,
2785 [('d', 'delete', None, _('delete save entry')),
2786 [('d', 'delete', None, _('delete save entry')),
2786 ('u', 'update', None, _('update queue working directory'))],
2787 ('u', 'update', None, _('update queue working directory'))],
2787 _('hg qrestore [-d] [-u] REV')),
2788 _('hg qrestore [-d] [-u] REV')),
2788 "qsave":
2789 "qsave":
2789 (save,
2790 (save,
2790 [('c', 'copy', None, _('copy patch directory')),
2791 [('c', 'copy', None, _('copy patch directory')),
2791 ('n', 'name', '', _('copy directory name')),
2792 ('n', 'name', '', _('copy directory name')),
2792 ('e', 'empty', None, _('clear queue status file')),
2793 ('e', 'empty', None, _('clear queue status file')),
2793 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2794 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2794 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2795 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2795 "qselect":
2796 "qselect":
2796 (select,
2797 (select,
2797 [('n', 'none', None, _('disable all guards')),
2798 [('n', 'none', None, _('disable all guards')),
2798 ('s', 'series', None, _('list all guards in series file')),
2799 ('s', 'series', None, _('list all guards in series file')),
2799 ('', 'pop', None, _('pop to before first guarded applied patch')),
2800 ('', 'pop', None, _('pop to before first guarded applied patch')),
2800 ('', 'reapply', None, _('pop, then reapply patches'))],
2801 ('', 'reapply', None, _('pop, then reapply patches'))],
2801 _('hg qselect [OPTION]... [GUARD]...')),
2802 _('hg qselect [OPTION]... [GUARD]...')),
2802 "qseries":
2803 "qseries":
2803 (series,
2804 (series,
2804 [('m', 'missing', None, _('print patches not in series')),
2805 [('m', 'missing', None, _('print patches not in series')),
2805 ] + seriesopts,
2806 ] + seriesopts,
2806 _('hg qseries [-ms]')),
2807 _('hg qseries [-ms]')),
2807 "^strip":
2808 "^strip":
2808 (strip,
2809 (strip,
2809 [('f', 'force', None, _('force removal with local changes')),
2810 [('f', 'force', None, _('force removal with local changes')),
2810 ('b', 'backup', None, _('bundle unrelated changesets')),
2811 ('b', 'backup', None, _('bundle unrelated changesets')),
2811 ('n', 'nobackup', None, _('no backups'))],
2812 ('n', 'nobackup', None, _('no backups'))],
2812 _('hg strip [-f] [-b] [-n] REV')),
2813 _('hg strip [-f] [-b] [-n] REV')),
2813 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2814 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2814 "qunapplied":
2815 "qunapplied":
2815 (unapplied,
2816 (unapplied,
2816 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2817 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2817 _('hg qunapplied [-1] [-s] [PATCH]')),
2818 _('hg qunapplied [-1] [-s] [PATCH]')),
2818 "qfinish":
2819 "qfinish":
2819 (finish,
2820 (finish,
2820 [('a', 'applied', None, _('finish all applied changesets'))],
2821 [('a', 'applied', None, _('finish all applied changesets'))],
2821 _('hg qfinish [-a] [REV]...')),
2822 _('hg qfinish [-a] [REV]...')),
2822 }
2823 }
@@ -1,506 +1,506 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import util, repair, merge, cmdutil, commands, error
17 from mercurial import util, repair, merge, cmdutil, commands, error
18 from mercurial import extensions, ancestor, copies, patch
18 from mercurial import extensions, ancestor, copies, patch
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26
26
27 def rebase(ui, repo, **opts):
27 def rebase(ui, repo, **opts):
28 """move changeset (and descendants) to a different branch
28 """move changeset (and descendants) to a different branch
29
29
30 Rebase uses repeated merging to graft changesets from one part of
30 Rebase uses repeated merging to graft changesets from one part of
31 history onto another. This can be useful for linearizing local
31 history onto another. This can be useful for linearizing local
32 changes relative to a master development tree.
32 changes relative to a master development tree.
33
33
34 If a rebase is interrupted to manually resolve a merge, it can be
34 If a rebase is interrupted to manually resolve a merge, it can be
35 continued with --continue/-c or aborted with --abort/-a.
35 continued with --continue/-c or aborted with --abort/-a.
36 """
36 """
37 originalwd = target = None
37 originalwd = target = None
38 external = nullrev
38 external = nullrev
39 state = {}
39 state = {}
40 skipped = set()
40 skipped = set()
41 targetancestors = set()
41 targetancestors = set()
42
42
43 lock = wlock = None
43 lock = wlock = None
44 try:
44 try:
45 lock = repo.lock()
45 lock = repo.lock()
46 wlock = repo.wlock()
46 wlock = repo.wlock()
47
47
48 # Validate input and define rebasing points
48 # Validate input and define rebasing points
49 destf = opts.get('dest', None)
49 destf = opts.get('dest', None)
50 srcf = opts.get('source', None)
50 srcf = opts.get('source', None)
51 basef = opts.get('base', None)
51 basef = opts.get('base', None)
52 contf = opts.get('continue')
52 contf = opts.get('continue')
53 abortf = opts.get('abort')
53 abortf = opts.get('abort')
54 collapsef = opts.get('collapse', False)
54 collapsef = opts.get('collapse', False)
55 extrafn = opts.get('extrafn')
55 extrafn = opts.get('extrafn')
56 keepf = opts.get('keep', False)
56 keepf = opts.get('keep', False)
57 keepbranchesf = opts.get('keepbranches', False)
57 keepbranchesf = opts.get('keepbranches', False)
58 detachf = opts.get('detach', False)
58 detachf = opts.get('detach', False)
59
59
60 if contf or abortf:
60 if contf or abortf:
61 if contf and abortf:
61 if contf and abortf:
62 raise error.ParseError('rebase',
62 raise error.ParseError('rebase',
63 _('cannot use both abort and continue'))
63 _('cannot use both abort and continue'))
64 if collapsef:
64 if collapsef:
65 raise error.ParseError(
65 raise error.ParseError(
66 'rebase', _('cannot use collapse with continue or abort'))
66 'rebase', _('cannot use collapse with continue or abort'))
67
67
68 if detachf:
68 if detachf:
69 raise error.ParseError(
69 raise error.ParseError(
70 'rebase', _('cannot use detach with continue or abort'))
70 'rebase', _('cannot use detach with continue or abort'))
71
71
72 if srcf or basef or destf:
72 if srcf or basef or destf:
73 raise error.ParseError('rebase',
73 raise error.ParseError('rebase',
74 _('abort and continue do not allow specifying revisions'))
74 _('abort and continue do not allow specifying revisions'))
75
75
76 (originalwd, target, state, collapsef, keepf,
76 (originalwd, target, state, collapsef, keepf,
77 keepbranchesf, external) = restorestatus(repo)
77 keepbranchesf, external) = restorestatus(repo)
78 if abortf:
78 if abortf:
79 abort(repo, originalwd, target, state)
79 abort(repo, originalwd, target, state)
80 return
80 return
81 else:
81 else:
82 if srcf and basef:
82 if srcf and basef:
83 raise error.ParseError('rebase', _('cannot specify both a '
83 raise error.ParseError('rebase', _('cannot specify both a '
84 'revision and a base'))
84 'revision and a base'))
85 if detachf:
85 if detachf:
86 if not srcf:
86 if not srcf:
87 raise error.ParseError(
87 raise error.ParseError(
88 'rebase', _('detach requires a revision to be specified'))
88 'rebase', _('detach requires a revision to be specified'))
89 if basef:
89 if basef:
90 raise error.ParseError(
90 raise error.ParseError(
91 'rebase', _('cannot specify a base with detach'))
91 'rebase', _('cannot specify a base with detach'))
92
92
93 cmdutil.bail_if_changed(repo)
93 cmdutil.bail_if_changed(repo)
94 result = buildstate(repo, destf, srcf, basef, detachf)
94 result = buildstate(repo, destf, srcf, basef, detachf)
95 if not result:
95 if not result:
96 # Empty state built, nothing to rebase
96 # Empty state built, nothing to rebase
97 ui.status(_('nothing to rebase\n'))
97 ui.status(_('nothing to rebase\n'))
98 return
98 return
99 else:
99 else:
100 originalwd, target, state = result
100 originalwd, target, state = result
101 if collapsef:
101 if collapsef:
102 targetancestors = set(repo.changelog.ancestors(target))
102 targetancestors = set(repo.changelog.ancestors(target))
103 external = checkexternal(repo, state, targetancestors)
103 external = checkexternal(repo, state, targetancestors)
104
104
105 if keepbranchesf:
105 if keepbranchesf:
106 if extrafn:
106 if extrafn:
107 raise error.ParseError(
107 raise error.ParseError(
108 'rebase', _('cannot use both keepbranches and extrafn'))
108 'rebase', _('cannot use both keepbranches and extrafn'))
109 def extrafn(ctx, extra):
109 def extrafn(ctx, extra):
110 extra['branch'] = ctx.branch()
110 extra['branch'] = ctx.branch()
111
111
112 # Rebase
112 # Rebase
113 if not targetancestors:
113 if not targetancestors:
114 targetancestors = set(repo.changelog.ancestors(target))
114 targetancestors = set(repo.changelog.ancestors(target))
115 targetancestors.add(target)
115 targetancestors.add(target)
116
116
117 for rev in sorted(state):
117 for rev in sorted(state):
118 if state[rev] == -1:
118 if state[rev] == -1:
119 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
119 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
120 storestatus(repo, originalwd, target, state, collapsef, keepf,
120 storestatus(repo, originalwd, target, state, collapsef, keepf,
121 keepbranchesf, external)
121 keepbranchesf, external)
122 p1, p2 = defineparents(repo, rev, target, state,
122 p1, p2 = defineparents(repo, rev, target, state,
123 targetancestors)
123 targetancestors)
124 if len(repo.parents()) == 2:
124 if len(repo.parents()) == 2:
125 repo.ui.debug('resuming interrupted rebase\n')
125 repo.ui.debug('resuming interrupted rebase\n')
126 else:
126 else:
127 stats = rebasenode(repo, rev, p1, p2, state)
127 stats = rebasenode(repo, rev, p1, p2, state)
128 if stats and stats[3] > 0:
128 if stats and stats[3] > 0:
129 raise util.Abort(_('fix unresolved conflicts with hg '
129 raise util.Abort(_('fix unresolved conflicts with hg '
130 'resolve then run hg rebase --continue'))
130 'resolve then run hg rebase --continue'))
131 updatedirstate(repo, rev, target, p2)
131 updatedirstate(repo, rev, target, p2)
132 if not collapsef:
132 if not collapsef:
133 extra = {'rebase_source': repo[rev].hex()}
133 extra = {'rebase_source': repo[rev].hex()}
134 if extrafn:
134 if extrafn:
135 extrafn(repo[rev], extra)
135 extrafn(repo[rev], extra)
136 newrev = concludenode(repo, rev, p1, p2, extra=extra)
136 newrev = concludenode(repo, rev, p1, p2, extra=extra)
137 else:
137 else:
138 # Skip commit if we are collapsing
138 # Skip commit if we are collapsing
139 repo.dirstate.setparents(repo[p1].node())
139 repo.dirstate.setparents(repo[p1].node())
140 newrev = None
140 newrev = None
141 # Update the state
141 # Update the state
142 if newrev is not None:
142 if newrev is not None:
143 state[rev] = repo[newrev].rev()
143 state[rev] = repo[newrev].rev()
144 else:
144 else:
145 if not collapsef:
145 if not collapsef:
146 ui.note(_('no changes, revision %d skipped\n') % rev)
146 ui.note(_('no changes, revision %d skipped\n') % rev)
147 ui.debug('next revision set to %s\n' % p1)
147 ui.debug('next revision set to %s\n' % p1)
148 skipped.add(rev)
148 skipped.add(rev)
149 state[rev] = p1
149 state[rev] = p1
150
150
151 ui.note(_('rebase merging completed\n'))
151 ui.note(_('rebase merging completed\n'))
152
152
153 if collapsef:
153 if collapsef:
154 p1, p2 = defineparents(repo, min(state), target,
154 p1, p2 = defineparents(repo, min(state), target,
155 state, targetancestors)
155 state, targetancestors)
156 commitmsg = 'Collapsed revision'
156 commitmsg = 'Collapsed revision'
157 for rebased in state:
157 for rebased in state:
158 if rebased not in skipped and state[rebased] != nullmerge:
158 if rebased not in skipped and state[rebased] != nullmerge:
159 commitmsg += '\n* %s' % repo[rebased].description()
159 commitmsg += '\n* %s' % repo[rebased].description()
160 commitmsg = ui.edit(commitmsg, repo.ui.username())
160 commitmsg = ui.edit(commitmsg, repo.ui.username())
161 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
161 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
162 extra=extrafn)
162 extra=extrafn)
163
163
164 if 'qtip' in repo.tags():
164 if 'qtip' in repo.tags():
165 updatemq(repo, state, skipped, **opts)
165 updatemq(repo, state, skipped, **opts)
166
166
167 if not keepf:
167 if not keepf:
168 # Remove no more useful revisions
168 # Remove no more useful revisions
169 rebased = [rev for rev in state if state[rev] != nullmerge]
169 rebased = [rev for rev in state if state[rev] != nullmerge]
170 if rebased:
170 if rebased:
171 if set(repo.changelog.descendants(min(rebased))) - set(state):
171 if set(repo.changelog.descendants(min(rebased))) - set(state):
172 ui.warn(_("warning: new changesets detected on source branch, "
172 ui.warn(_("warning: new changesets detected on source branch, "
173 "not stripping\n"))
173 "not stripping\n"))
174 else:
174 else:
175 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
175 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
176
176
177 clearstatus(repo)
177 clearstatus(repo)
178 ui.status(_("rebase completed\n"))
178 ui.status(_("rebase completed\n"))
179 if os.path.exists(repo.sjoin('undo')):
179 if os.path.exists(repo.sjoin('undo')):
180 util.unlink(repo.sjoin('undo'))
180 util.unlink(repo.sjoin('undo'))
181 if skipped:
181 if skipped:
182 ui.note(_("%d revisions have been skipped\n") % len(skipped))
182 ui.note(_("%d revisions have been skipped\n") % len(skipped))
183 finally:
183 finally:
184 release(lock, wlock)
184 release(lock, wlock)
185
185
186 def rebasemerge(repo, rev, first=False):
186 def rebasemerge(repo, rev, first=False):
187 'return the correct ancestor'
187 'return the correct ancestor'
188 oldancestor = ancestor.ancestor
188 oldancestor = ancestor.ancestor
189
189
190 def newancestor(a, b, pfunc):
190 def newancestor(a, b, pfunc):
191 if b == rev:
191 if b == rev:
192 return repo[rev].parents()[0].rev()
192 return repo[rev].parents()[0].rev()
193 return oldancestor(a, b, pfunc)
193 return oldancestor(a, b, pfunc)
194
194
195 if not first:
195 if not first:
196 ancestor.ancestor = newancestor
196 ancestor.ancestor = newancestor
197 else:
197 else:
198 repo.ui.debug("first revision, do not change ancestor\n")
198 repo.ui.debug("first revision, do not change ancestor\n")
199 try:
199 try:
200 stats = merge.update(repo, rev, True, True, False)
200 stats = merge.update(repo, rev, True, True, False)
201 return stats
201 return stats
202 finally:
202 finally:
203 ancestor.ancestor = oldancestor
203 ancestor.ancestor = oldancestor
204
204
205 def checkexternal(repo, state, targetancestors):
205 def checkexternal(repo, state, targetancestors):
206 """Check whether one or more external revisions need to be taken in
206 """Check whether one or more external revisions need to be taken in
207 consideration. In the latter case, abort.
207 consideration. In the latter case, abort.
208 """
208 """
209 external = nullrev
209 external = nullrev
210 source = min(state)
210 source = min(state)
211 for rev in state:
211 for rev in state:
212 if rev == source:
212 if rev == source:
213 continue
213 continue
214 # Check externals and fail if there are more than one
214 # Check externals and fail if there are more than one
215 for p in repo[rev].parents():
215 for p in repo[rev].parents():
216 if (p.rev() not in state
216 if (p.rev() not in state
217 and p.rev() not in targetancestors):
217 and p.rev() not in targetancestors):
218 if external != nullrev:
218 if external != nullrev:
219 raise util.Abort(_('unable to collapse, there is more '
219 raise util.Abort(_('unable to collapse, there is more '
220 'than one external parent'))
220 'than one external parent'))
221 external = p.rev()
221 external = p.rev()
222 return external
222 return external
223
223
224 def updatedirstate(repo, rev, p1, p2):
224 def updatedirstate(repo, rev, p1, p2):
225 """Keep track of renamed files in the revision that is going to be rebased
225 """Keep track of renamed files in the revision that is going to be rebased
226 """
226 """
227 # Here we simulate the copies and renames in the source changeset
227 # Here we simulate the copies and renames in the source changeset
228 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
228 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
229 m1 = repo[rev].manifest()
229 m1 = repo[rev].manifest()
230 m2 = repo[p1].manifest()
230 m2 = repo[p1].manifest()
231 for k, v in cop.iteritems():
231 for k, v in cop.iteritems():
232 if k in m1:
232 if k in m1:
233 if v in m1 or v in m2:
233 if v in m1 or v in m2:
234 repo.dirstate.copy(v, k)
234 repo.dirstate.copy(v, k)
235 if v in m2 and v not in m1:
235 if v in m2 and v not in m1:
236 repo.dirstate.remove(v)
236 repo.dirstate.remove(v)
237
237
238 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
238 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
239 'Commit the changes and store useful information in extra'
239 'Commit the changes and store useful information in extra'
240 try:
240 try:
241 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
241 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
242 if commitmsg is None:
242 if commitmsg is None:
243 commitmsg = repo[rev].description()
243 commitmsg = repo[rev].description()
244 if extra is None:
244 if extra is None:
245 extra = {}
245 extra = {}
246 # Commit might fail if unresolved files exist
246 # Commit might fail if unresolved files exist
247 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
247 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
248 date=repo[rev].date(), extra=extra)
248 date=repo[rev].date(), extra=extra)
249 repo.dirstate.setbranch(repo[newrev].branch())
249 repo.dirstate.setbranch(repo[newrev].branch())
250 return newrev
250 return newrev
251 except util.Abort:
251 except util.Abort:
252 # Invalidate the previous setparents
252 # Invalidate the previous setparents
253 repo.dirstate.invalidate()
253 repo.dirstate.invalidate()
254 raise
254 raise
255
255
256 def rebasenode(repo, rev, p1, p2, state):
256 def rebasenode(repo, rev, p1, p2, state):
257 'Rebase a single revision'
257 'Rebase a single revision'
258 # Merge phase
258 # Merge phase
259 # Update to target and merge it with local
259 # Update to target and merge it with local
260 if repo['.'].rev() != repo[p1].rev():
260 if repo['.'].rev() != repo[p1].rev():
261 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
261 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
262 merge.update(repo, p1, False, True, False)
262 merge.update(repo, p1, False, True, False)
263 else:
263 else:
264 repo.ui.debug(" already in target\n")
264 repo.ui.debug(" already in target\n")
265 repo.dirstate.write()
265 repo.dirstate.write()
266 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
266 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
267 first = repo[rev].rev() == repo[min(state)].rev()
267 first = repo[rev].rev() == repo[min(state)].rev()
268 stats = rebasemerge(repo, rev, first)
268 stats = rebasemerge(repo, rev, first)
269 return stats
269 return stats
270
270
271 def defineparents(repo, rev, target, state, targetancestors):
271 def defineparents(repo, rev, target, state, targetancestors):
272 'Return the new parent relationship of the revision that will be rebased'
272 'Return the new parent relationship of the revision that will be rebased'
273 parents = repo[rev].parents()
273 parents = repo[rev].parents()
274 p1 = p2 = nullrev
274 p1 = p2 = nullrev
275
275
276 P1n = parents[0].rev()
276 P1n = parents[0].rev()
277 if P1n in targetancestors:
277 if P1n in targetancestors:
278 p1 = target
278 p1 = target
279 elif P1n in state:
279 elif P1n in state:
280 if state[P1n] == nullmerge:
280 if state[P1n] == nullmerge:
281 p1 = target
281 p1 = target
282 else:
282 else:
283 p1 = state[P1n]
283 p1 = state[P1n]
284 else: # P1n external
284 else: # P1n external
285 p1 = target
285 p1 = target
286 p2 = P1n
286 p2 = P1n
287
287
288 if len(parents) == 2 and parents[1].rev() not in targetancestors:
288 if len(parents) == 2 and parents[1].rev() not in targetancestors:
289 P2n = parents[1].rev()
289 P2n = parents[1].rev()
290 # interesting second parent
290 # interesting second parent
291 if P2n in state:
291 if P2n in state:
292 if p1 == target: # P1n in targetancestors or external
292 if p1 == target: # P1n in targetancestors or external
293 p1 = state[P2n]
293 p1 = state[P2n]
294 else:
294 else:
295 p2 = state[P2n]
295 p2 = state[P2n]
296 else: # P2n external
296 else: # P2n external
297 if p2 != nullrev: # P1n external too => rev is a merged revision
297 if p2 != nullrev: # P1n external too => rev is a merged revision
298 raise util.Abort(_('cannot use revision %d as base, result '
298 raise util.Abort(_('cannot use revision %d as base, result '
299 'would have 3 parents') % rev)
299 'would have 3 parents') % rev)
300 p2 = P2n
300 p2 = P2n
301 repo.ui.debug(" future parents are %d and %d\n" %
301 repo.ui.debug(" future parents are %d and %d\n" %
302 (repo[p1].rev(), repo[p2].rev()))
302 (repo[p1].rev(), repo[p2].rev()))
303 return p1, p2
303 return p1, p2
304
304
305 def isagitpatch(repo, patchname):
305 def isagitpatch(repo, patchname):
306 'Return true if the given patch is in git format'
306 'Return true if the given patch is in git format'
307 mqpatch = os.path.join(repo.mq.path, patchname)
307 mqpatch = os.path.join(repo.mq.path, patchname)
308 for line in patch.linereader(file(mqpatch, 'rb')):
308 for line in patch.linereader(file(mqpatch, 'rb')):
309 if line.startswith('diff --git'):
309 if line.startswith('diff --git'):
310 return True
310 return True
311 return False
311 return False
312
312
313 def updatemq(repo, state, skipped, **opts):
313 def updatemq(repo, state, skipped, **opts):
314 'Update rebased mq patches - finalize and then import them'
314 'Update rebased mq patches - finalize and then import them'
315 mqrebase = {}
315 mqrebase = {}
316 for p in repo.mq.applied:
316 for p in repo.mq.applied:
317 if repo[p.rev].rev() in state:
317 if repo[p.rev].rev() in state:
318 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
318 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
319 (repo[p.rev].rev(), p.name))
319 (repo[p.rev].rev(), p.name))
320 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
320 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
321
321
322 if mqrebase:
322 if mqrebase:
323 repo.mq.finish(repo, mqrebase.keys())
323 repo.mq.finish(repo, mqrebase.keys())
324
324
325 # We must start import from the newest revision
325 # We must start import from the newest revision
326 for rev in sorted(mqrebase, reverse=True):
326 for rev in sorted(mqrebase, reverse=True):
327 if rev not in skipped:
327 if rev not in skipped:
328 repo.ui.debug('import mq patch %d (%s)\n'
328 repo.ui.debug('import mq patch %d (%s)\n'
329 % (state[rev], mqrebase[rev][0]))
329 % (state[rev], mqrebase[rev][0]))
330 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
330 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
331 git=mqrebase[rev][1],rev=[str(state[rev])])
331 git=mqrebase[rev][1],rev=[str(state[rev])])
332 repo.mq.save_dirty()
332 repo.mq.save_dirty()
333
333
334 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
334 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
335 external):
335 external):
336 'Store the current status to allow recovery'
336 'Store the current status to allow recovery'
337 f = repo.opener("rebasestate", "w")
337 f = repo.opener("rebasestate", "w")
338 f.write(repo[originalwd].hex() + '\n')
338 f.write(repo[originalwd].hex() + '\n')
339 f.write(repo[target].hex() + '\n')
339 f.write(repo[target].hex() + '\n')
340 f.write(repo[external].hex() + '\n')
340 f.write(repo[external].hex() + '\n')
341 f.write('%d\n' % int(collapse))
341 f.write('%d\n' % int(collapse))
342 f.write('%d\n' % int(keep))
342 f.write('%d\n' % int(keep))
343 f.write('%d\n' % int(keepbranches))
343 f.write('%d\n' % int(keepbranches))
344 for d, v in state.iteritems():
344 for d, v in state.iteritems():
345 oldrev = repo[d].hex()
345 oldrev = repo[d].hex()
346 newrev = repo[v].hex()
346 newrev = repo[v].hex()
347 f.write("%s:%s\n" % (oldrev, newrev))
347 f.write("%s:%s\n" % (oldrev, newrev))
348 f.close()
348 f.close()
349 repo.ui.debug('rebase status stored\n')
349 repo.ui.debug('rebase status stored\n')
350
350
351 def clearstatus(repo):
351 def clearstatus(repo):
352 'Remove the status files'
352 'Remove the status files'
353 if os.path.exists(repo.join("rebasestate")):
353 if os.path.exists(repo.join("rebasestate")):
354 util.unlink(repo.join("rebasestate"))
354 util.unlink(repo.join("rebasestate"))
355
355
356 def restorestatus(repo):
356 def restorestatus(repo):
357 'Restore a previously stored status'
357 'Restore a previously stored status'
358 try:
358 try:
359 target = None
359 target = None
360 collapse = False
360 collapse = False
361 external = nullrev
361 external = nullrev
362 state = {}
362 state = {}
363 f = repo.opener("rebasestate")
363 f = repo.opener("rebasestate")
364 for i, l in enumerate(f.read().splitlines()):
364 for i, l in enumerate(f.read().splitlines()):
365 if i == 0:
365 if i == 0:
366 originalwd = repo[l].rev()
366 originalwd = repo[l].rev()
367 elif i == 1:
367 elif i == 1:
368 target = repo[l].rev()
368 target = repo[l].rev()
369 elif i == 2:
369 elif i == 2:
370 external = repo[l].rev()
370 external = repo[l].rev()
371 elif i == 3:
371 elif i == 3:
372 collapse = bool(int(l))
372 collapse = bool(int(l))
373 elif i == 4:
373 elif i == 4:
374 keep = bool(int(l))
374 keep = bool(int(l))
375 elif i == 5:
375 elif i == 5:
376 keepbranches = bool(int(l))
376 keepbranches = bool(int(l))
377 else:
377 else:
378 oldrev, newrev = l.split(':')
378 oldrev, newrev = l.split(':')
379 state[repo[oldrev].rev()] = repo[newrev].rev()
379 state[repo[oldrev].rev()] = repo[newrev].rev()
380 repo.ui.debug('rebase status resumed\n')
380 repo.ui.debug('rebase status resumed\n')
381 return originalwd, target, state, collapse, keep, keepbranches, external
381 return originalwd, target, state, collapse, keep, keepbranches, external
382 except IOError, err:
382 except IOError, err:
383 if err.errno != errno.ENOENT:
383 if err.errno != errno.ENOENT:
384 raise
384 raise
385 raise util.Abort(_('no rebase in progress'))
385 raise util.Abort(_('no rebase in progress'))
386
386
387 def abort(repo, originalwd, target, state):
387 def abort(repo, originalwd, target, state):
388 'Restore the repository to its original state'
388 'Restore the repository to its original state'
389 if set(repo.changelog.descendants(target)) - set(state.values()):
389 if set(repo.changelog.descendants(target)) - set(state.values()):
390 repo.ui.warn(_("warning: new changesets detected on target branch, "
390 repo.ui.warn(_("warning: new changesets detected on target branch, "
391 "not stripping\n"))
391 "not stripping\n"))
392 else:
392 else:
393 # Strip from the first rebased revision
393 # Strip from the first rebased revision
394 merge.update(repo, repo[originalwd].rev(), False, True, False)
394 merge.update(repo, repo[originalwd].rev(), False, True, False)
395 rebased = filter(lambda x: x > -1, state.values())
395 rebased = filter(lambda x: x > -1, state.values())
396 if rebased:
396 if rebased:
397 strippoint = min(rebased)
397 strippoint = min(rebased)
398 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
398 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
399 clearstatus(repo)
399 clearstatus(repo)
400 repo.ui.status(_('rebase aborted\n'))
400 repo.ui.status(_('rebase aborted\n'))
401
401
402 def buildstate(repo, dest, src, base, detach):
402 def buildstate(repo, dest, src, base, detach):
403 'Define which revisions are going to be rebased and where'
403 'Define which revisions are going to be rebased and where'
404 targetancestors = set()
404 targetancestors = set()
405 detachset = set()
405 detachset = set()
406
406
407 if not dest:
407 if not dest:
408 # Destination defaults to the latest revision in the current branch
408 # Destination defaults to the latest revision in the current branch
409 branch = repo[None].branch()
409 branch = repo[None].branch()
410 dest = repo[branch].rev()
410 dest = repo[branch].rev()
411 else:
411 else:
412 if 'qtip' in repo.tags() and (repo[dest].hex() in
412 if 'qtip' in repo.tags() and (repo[dest].hex() in
413 [s.rev for s in repo.mq.applied]):
413 [s.rev for s in repo.mq.applied]):
414 raise util.Abort(_('cannot rebase onto an applied mq patch'))
414 raise util.Abort(_('cannot rebase onto an applied mq patch'))
415 dest = repo[dest].rev()
415 dest = repo[dest].rev()
416
416
417 if src:
417 if src:
418 commonbase = repo[src].ancestor(repo[dest])
418 commonbase = repo[src].ancestor(repo[dest])
419 if commonbase == repo[src]:
419 if commonbase == repo[src]:
420 raise util.Abort(_('source is ancestor of destination'))
420 raise util.Abort(_('source is ancestor of destination'))
421 if commonbase == repo[dest]:
421 if commonbase == repo[dest]:
422 raise util.Abort(_('source is descendant of destination'))
422 raise util.Abort(_('source is descendant of destination'))
423 source = repo[src].rev()
423 source = repo[src].rev()
424 if detach:
424 if detach:
425 # We need to keep track of source's ancestors up to the common base
425 # We need to keep track of source's ancestors up to the common base
426 srcancestors = set(repo.changelog.ancestors(source))
426 srcancestors = set(repo.changelog.ancestors(source))
427 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
427 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
428 detachset = srcancestors - baseancestors
428 detachset = srcancestors - baseancestors
429 detachset.remove(commonbase.rev())
429 detachset.remove(commonbase.rev())
430 else:
430 else:
431 if base:
431 if base:
432 cwd = repo[base].rev()
432 cwd = repo[base].rev()
433 else:
433 else:
434 cwd = repo['.'].rev()
434 cwd = repo['.'].rev()
435
435
436 if cwd == dest:
436 if cwd == dest:
437 repo.ui.debug('source and destination are the same\n')
437 repo.ui.debug('source and destination are the same\n')
438 return None
438 return None
439
439
440 targetancestors = set(repo.changelog.ancestors(dest))
440 targetancestors = set(repo.changelog.ancestors(dest))
441 if cwd in targetancestors:
441 if cwd in targetancestors:
442 repo.ui.debug('source is ancestor of destination\n')
442 repo.ui.debug('source is ancestor of destination\n')
443 return None
443 return None
444
444
445 cwdancestors = set(repo.changelog.ancestors(cwd))
445 cwdancestors = set(repo.changelog.ancestors(cwd))
446 if dest in cwdancestors:
446 if dest in cwdancestors:
447 repo.ui.debug('source is descendant of destination\n')
447 repo.ui.debug('source is descendant of destination\n')
448 return None
448 return None
449
449
450 cwdancestors.add(cwd)
450 cwdancestors.add(cwd)
451 rebasingbranch = cwdancestors - targetancestors
451 rebasingbranch = cwdancestors - targetancestors
452 source = min(rebasingbranch)
452 source = min(rebasingbranch)
453
453
454 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
454 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
455 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
455 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
456 state.update(dict.fromkeys(detachset, nullmerge))
456 state.update(dict.fromkeys(detachset, nullmerge))
457 state[source] = nullrev
457 state[source] = nullrev
458 return repo['.'].rev(), repo[dest].rev(), state
458 return repo['.'].rev(), repo[dest].rev(), state
459
459
460 def pullrebase(orig, ui, repo, *args, **opts):
460 def pullrebase(orig, ui, repo, *args, **opts):
461 'Call rebase after pull if the latter has been invoked with --rebase'
461 'Call rebase after pull if the latter has been invoked with --rebase'
462 if opts.get('rebase'):
462 if opts.get('rebase'):
463 if opts.get('update'):
463 if opts.get('update'):
464 del opts['update']
464 del opts['update']
465 ui.debug('--update and --rebase are not compatible, ignoring '
465 ui.debug('--update and --rebase are not compatible, ignoring '
466 'the update flag\n')
466 'the update flag\n')
467
467
468 cmdutil.bail_if_changed(repo)
468 cmdutil.bail_if_changed(repo)
469 revsprepull = len(repo)
469 revsprepull = len(repo)
470 orig(ui, repo, *args, **opts)
470 orig(ui, repo, *args, **opts)
471 revspostpull = len(repo)
471 revspostpull = len(repo)
472 if revspostpull > revsprepull:
472 if revspostpull > revsprepull:
473 rebase(ui, repo, **opts)
473 rebase(ui, repo, **opts)
474 branch = repo[None].branch()
474 branch = repo[None].branch()
475 dest = repo[branch].rev()
475 dest = repo[branch].rev()
476 if dest != repo['.'].rev():
476 if dest != repo['.'].rev():
477 # there was nothing to rebase we force an update
477 # there was nothing to rebase we force an update
478 merge.update(repo, dest, False, False, False)
478 merge.update(repo, dest, False, False, False)
479 else:
479 else:
480 orig(ui, repo, *args, **opts)
480 orig(ui, repo, *args, **opts)
481
481
482 def uisetup(ui):
482 def uisetup(ui):
483 'Replace pull with a decorator to provide --rebase option'
483 'Replace pull with a decorator to provide --rebase option'
484 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
484 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
485 entry[1].append(('', 'rebase', None,
485 entry[1].append(('', 'rebase', None,
486 _("rebase working directory to branch head"))
486 _("rebase working directory to branch head"))
487 )
487 )
488
488
489 cmdtable = {
489 cmdtable = {
490 "rebase":
490 "rebase":
491 (rebase,
491 (rebase,
492 [
492 [
493 ('s', 'source', '', _('rebase from a given revision')),
493 ('s', 'source', '', _('rebase from a given revision')),
494 ('b', 'base', '', _('rebase from the base of a given revision')),
494 ('b', 'base', '', _('rebase from the base of a given revision')),
495 ('d', 'dest', '', _('rebase onto a given revision')),
495 ('d', 'dest', '', _('rebase onto a given revision')),
496 ('', 'collapse', False, _('collapse the rebased changesets')),
496 ('', 'collapse', False, _('collapse the rebased changesets')),
497 ('', 'keep', False, _('keep original changesets')),
497 ('', 'keep', False, _('keep original changesets')),
498 ('', 'keepbranches', False, _('keep original branch names')),
498 ('', 'keepbranches', False, _('keep original branch names')),
499 ('', 'detach', False, _('force detaching of source from its original '
499 ('', 'detach', False, _('force detaching of source from its original '
500 'branch')),
500 'branch')),
501 ('c', 'continue', False, _('continue an interrupted rebase')),
501 ('c', 'continue', False, _('continue an interrupted rebase')),
502 ('a', 'abort', False, _('abort an interrupted rebase')),] +
502 ('a', 'abort', False, _('abort an interrupted rebase'))] +
503 templateopts,
503 templateopts,
504 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] '
504 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] '
505 '[--keep] [--keepbranches] | [-c] | [-a]')),
505 '[--keep] [--keepbranches] | [-c] | [-a]')),
506 }
506 }
@@ -1,3808 +1,3809 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, bundlerepo, extensions, copies, error
12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 import patch, help, mdiff, url, encoding, templatekw
13 import patch, help, mdiff, url, encoding, templatekw
14 import archival, changegroup, cmdutil, sshserver, hbisect
14 import archival, changegroup, cmdutil, sshserver, hbisect
15 from hgweb import server
15 from hgweb import server
16 import merge as merge_
16 import merge as merge_
17 import minirst
17 import minirst
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
32
33 bad = []
33 bad = []
34 names = []
34 names = []
35 m = cmdutil.match(repo, pats, opts)
35 m = cmdutil.match(repo, pats, opts)
36 oldbad = m.bad
36 oldbad = m.bad
37 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
37 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
38
38
39 for f in repo.walk(m):
39 for f in repo.walk(m):
40 exact = m.exact(f)
40 exact = m.exact(f)
41 if exact or f not in repo.dirstate:
41 if exact or f not in repo.dirstate:
42 names.append(f)
42 names.append(f)
43 if ui.verbose or not exact:
43 if ui.verbose or not exact:
44 ui.status(_('adding %s\n') % m.rel(f))
44 ui.status(_('adding %s\n') % m.rel(f))
45 if not opts.get('dry_run'):
45 if not opts.get('dry_run'):
46 bad += [f for f in repo.add(names) if f in m.files()]
46 bad += [f for f in repo.add(names) if f in m.files()]
47 return bad and 1 or 0
47 return bad and 1 or 0
48
48
49 def addremove(ui, repo, *pats, **opts):
49 def addremove(ui, repo, *pats, **opts):
50 """add all new files, delete all missing files
50 """add all new files, delete all missing files
51
51
52 Add all new files and remove all missing files from the
52 Add all new files and remove all missing files from the
53 repository.
53 repository.
54
54
55 New files are ignored if they match any of the patterns in
55 New files are ignored if they match any of the patterns in
56 .hgignore. As with add, these changes take effect at the next
56 .hgignore. As with add, these changes take effect at the next
57 commit.
57 commit.
58
58
59 Use the -s/--similarity option to detect renamed files. With a
59 Use the -s/--similarity option to detect renamed files. With a
60 parameter greater than 0, this compares every removed file with
60 parameter greater than 0, this compares every removed file with
61 every added file and records those similar enough as renames. This
61 every added file and records those similar enough as renames. This
62 option takes a percentage between 0 (disabled) and 100 (files must
62 option takes a percentage between 0 (disabled) and 100 (files must
63 be identical) as its parameter. Detecting renamed files this way
63 be identical) as its parameter. Detecting renamed files this way
64 can be expensive.
64 can be expensive.
65 """
65 """
66 try:
66 try:
67 sim = float(opts.get('similarity') or 0)
67 sim = float(opts.get('similarity') or 0)
68 except ValueError:
68 except ValueError:
69 raise util.Abort(_('similarity must be a number'))
69 raise util.Abort(_('similarity must be a number'))
70 if sim < 0 or sim > 100:
70 if sim < 0 or sim > 100:
71 raise util.Abort(_('similarity must be between 0 and 100'))
71 raise util.Abort(_('similarity must be between 0 and 100'))
72 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
72 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
73
73
74 def annotate(ui, repo, *pats, **opts):
74 def annotate(ui, repo, *pats, **opts):
75 """show changeset information by line for each file
75 """show changeset information by line for each file
76
76
77 List changes in files, showing the revision id responsible for
77 List changes in files, showing the revision id responsible for
78 each line
78 each line
79
79
80 This command is useful for discovering when a change was made and
80 This command is useful for discovering when a change was made and
81 by whom.
81 by whom.
82
82
83 Without the -a/--text option, annotate will avoid processing files
83 Without the -a/--text option, annotate will avoid processing files
84 it detects as binary. With -a, annotate will annotate the file
84 it detects as binary. With -a, annotate will annotate the file
85 anyway, although the results will probably be neither useful
85 anyway, although the results will probably be neither useful
86 nor desirable.
86 nor desirable.
87 """
87 """
88 datefunc = ui.quiet and util.shortdate or util.datestr
88 datefunc = ui.quiet and util.shortdate or util.datestr
89 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
89 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
90
90
91 if not pats:
91 if not pats:
92 raise util.Abort(_('at least one filename or pattern is required'))
92 raise util.Abort(_('at least one filename or pattern is required'))
93
93
94 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
94 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
95 ('number', lambda x: str(x[0].rev())),
95 ('number', lambda x: str(x[0].rev())),
96 ('changeset', lambda x: short(x[0].node())),
96 ('changeset', lambda x: short(x[0].node())),
97 ('date', getdate),
97 ('date', getdate),
98 ('file', lambda x: x[0].path()),
98 ('file', lambda x: x[0].path()),
99 ]
99 ]
100
100
101 if (not opts.get('user') and not opts.get('changeset')
101 if (not opts.get('user') and not opts.get('changeset')
102 and not opts.get('date') and not opts.get('file')):
102 and not opts.get('date') and not opts.get('file')):
103 opts['number'] = 1
103 opts['number'] = 1
104
104
105 linenumber = opts.get('line_number') is not None
105 linenumber = opts.get('line_number') is not None
106 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
106 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
107 raise util.Abort(_('at least one of -n/-c is required for -l'))
107 raise util.Abort(_('at least one of -n/-c is required for -l'))
108
108
109 funcmap = [func for op, func in opmap if opts.get(op)]
109 funcmap = [func for op, func in opmap if opts.get(op)]
110 if linenumber:
110 if linenumber:
111 lastfunc = funcmap[-1]
111 lastfunc = funcmap[-1]
112 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
112 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
113
113
114 ctx = repo[opts.get('rev')]
114 ctx = repo[opts.get('rev')]
115 m = cmdutil.match(repo, pats, opts)
115 m = cmdutil.match(repo, pats, opts)
116 follow = not opts.get('no_follow')
116 follow = not opts.get('no_follow')
117 for abs in ctx.walk(m):
117 for abs in ctx.walk(m):
118 fctx = ctx[abs]
118 fctx = ctx[abs]
119 if not opts.get('text') and util.binary(fctx.data()):
119 if not opts.get('text') and util.binary(fctx.data()):
120 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
120 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
121 continue
121 continue
122
122
123 lines = fctx.annotate(follow=follow, linenumber=linenumber)
123 lines = fctx.annotate(follow=follow, linenumber=linenumber)
124 pieces = []
124 pieces = []
125
125
126 for f in funcmap:
126 for f in funcmap:
127 l = [f(n) for n, dummy in lines]
127 l = [f(n) for n, dummy in lines]
128 if l:
128 if l:
129 ml = max(map(len, l))
129 ml = max(map(len, l))
130 pieces.append(["%*s" % (ml, x) for x in l])
130 pieces.append(["%*s" % (ml, x) for x in l])
131
131
132 if pieces:
132 if pieces:
133 for p, l in zip(zip(*pieces), lines):
133 for p, l in zip(zip(*pieces), lines):
134 ui.write("%s: %s" % (" ".join(p), l[1]))
134 ui.write("%s: %s" % (" ".join(p), l[1]))
135
135
136 def archive(ui, repo, dest, **opts):
136 def archive(ui, repo, dest, **opts):
137 '''create an unversioned archive of a repository revision
137 '''create an unversioned archive of a repository revision
138
138
139 By default, the revision used is the parent of the working
139 By default, the revision used is the parent of the working
140 directory; use -r/--rev to specify a different revision.
140 directory; use -r/--rev to specify a different revision.
141
141
142 To specify the type of archive to create, use -t/--type. Valid
142 To specify the type of archive to create, use -t/--type. Valid
143 types are:
143 types are:
144
144
145 :``files``: a directory full of files (default)
145 :``files``: a directory full of files (default)
146 :``tar``: tar archive, uncompressed
146 :``tar``: tar archive, uncompressed
147 :``tbz2``: tar archive, compressed using bzip2
147 :``tbz2``: tar archive, compressed using bzip2
148 :``tgz``: tar archive, compressed using gzip
148 :``tgz``: tar archive, compressed using gzip
149 :``uzip``: zip archive, uncompressed
149 :``uzip``: zip archive, uncompressed
150 :``zip``: zip archive, compressed using deflate
150 :``zip``: zip archive, compressed using deflate
151
151
152 The exact name of the destination archive or directory is given
152 The exact name of the destination archive or directory is given
153 using a format string; see 'hg help export' for details.
153 using a format string; see 'hg help export' for details.
154
154
155 Each member added to an archive file has a directory prefix
155 Each member added to an archive file has a directory prefix
156 prepended. Use -p/--prefix to specify a format string for the
156 prepended. Use -p/--prefix to specify a format string for the
157 prefix. The default is the basename of the archive, with suffixes
157 prefix. The default is the basename of the archive, with suffixes
158 removed.
158 removed.
159 '''
159 '''
160
160
161 ctx = repo[opts.get('rev')]
161 ctx = repo[opts.get('rev')]
162 if not ctx:
162 if not ctx:
163 raise util.Abort(_('no working directory: please specify a revision'))
163 raise util.Abort(_('no working directory: please specify a revision'))
164 node = ctx.node()
164 node = ctx.node()
165 dest = cmdutil.make_filename(repo, dest, node)
165 dest = cmdutil.make_filename(repo, dest, node)
166 if os.path.realpath(dest) == repo.root:
166 if os.path.realpath(dest) == repo.root:
167 raise util.Abort(_('repository root cannot be destination'))
167 raise util.Abort(_('repository root cannot be destination'))
168 matchfn = cmdutil.match(repo, [], opts)
168 matchfn = cmdutil.match(repo, [], opts)
169 kind = opts.get('type') or 'files'
169 kind = opts.get('type') or 'files'
170 prefix = opts.get('prefix')
170 prefix = opts.get('prefix')
171 if dest == '-':
171 if dest == '-':
172 if kind == 'files':
172 if kind == 'files':
173 raise util.Abort(_('cannot archive plain files to stdout'))
173 raise util.Abort(_('cannot archive plain files to stdout'))
174 dest = sys.stdout
174 dest = sys.stdout
175 if not prefix:
175 if not prefix:
176 prefix = os.path.basename(repo.root) + '-%h'
176 prefix = os.path.basename(repo.root) + '-%h'
177 prefix = cmdutil.make_filename(repo, prefix, node)
177 prefix = cmdutil.make_filename(repo, prefix, node)
178 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
178 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
179 matchfn, prefix)
179 matchfn, prefix)
180
180
181 def backout(ui, repo, node=None, rev=None, **opts):
181 def backout(ui, repo, node=None, rev=None, **opts):
182 '''reverse effect of earlier changeset
182 '''reverse effect of earlier changeset
183
183
184 Commit the backed out changes as a new changeset. The new
184 Commit the backed out changes as a new changeset. The new
185 changeset is a child of the backed out changeset.
185 changeset is a child of the backed out changeset.
186
186
187 If you backout a changeset other than the tip, a new head is
187 If you backout a changeset other than the tip, a new head is
188 created. This head will be the new tip and you should merge this
188 created. This head will be the new tip and you should merge this
189 backout changeset with another head.
189 backout changeset with another head.
190
190
191 The --merge option remembers the parent of the working directory
191 The --merge option remembers the parent of the working directory
192 before starting the backout, then merges the new head with that
192 before starting the backout, then merges the new head with that
193 changeset afterwards. This saves you from doing the merge by hand.
193 changeset afterwards. This saves you from doing the merge by hand.
194 The result of this merge is not committed, as with a normal merge.
194 The result of this merge is not committed, as with a normal merge.
195
195
196 See 'hg help dates' for a list of formats valid for -d/--date.
196 See 'hg help dates' for a list of formats valid for -d/--date.
197 '''
197 '''
198 if rev and node:
198 if rev and node:
199 raise util.Abort(_("please specify just one revision"))
199 raise util.Abort(_("please specify just one revision"))
200
200
201 if not rev:
201 if not rev:
202 rev = node
202 rev = node
203
203
204 if not rev:
204 if not rev:
205 raise util.Abort(_("please specify a revision to backout"))
205 raise util.Abort(_("please specify a revision to backout"))
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 cmdutil.bail_if_changed(repo)
211 cmdutil.bail_if_changed(repo)
212 node = repo.lookup(rev)
212 node = repo.lookup(rev)
213
213
214 op1, op2 = repo.dirstate.parents()
214 op1, op2 = repo.dirstate.parents()
215 a = repo.changelog.ancestor(op1, node)
215 a = repo.changelog.ancestor(op1, node)
216 if a != node:
216 if a != node:
217 raise util.Abort(_('cannot backout change on a different branch'))
217 raise util.Abort(_('cannot backout change on a different branch'))
218
218
219 p1, p2 = repo.changelog.parents(node)
219 p1, p2 = repo.changelog.parents(node)
220 if p1 == nullid:
220 if p1 == nullid:
221 raise util.Abort(_('cannot backout a change with no parents'))
221 raise util.Abort(_('cannot backout a change with no parents'))
222 if p2 != nullid:
222 if p2 != nullid:
223 if not opts.get('parent'):
223 if not opts.get('parent'):
224 raise util.Abort(_('cannot backout a merge changeset without '
224 raise util.Abort(_('cannot backout a merge changeset without '
225 '--parent'))
225 '--parent'))
226 p = repo.lookup(opts['parent'])
226 p = repo.lookup(opts['parent'])
227 if p not in (p1, p2):
227 if p not in (p1, p2):
228 raise util.Abort(_('%s is not a parent of %s') %
228 raise util.Abort(_('%s is not a parent of %s') %
229 (short(p), short(node)))
229 (short(p), short(node)))
230 parent = p
230 parent = p
231 else:
231 else:
232 if opts.get('parent'):
232 if opts.get('parent'):
233 raise util.Abort(_('cannot use --parent on non-merge changeset'))
233 raise util.Abort(_('cannot use --parent on non-merge changeset'))
234 parent = p1
234 parent = p1
235
235
236 # the backout should appear on the same branch
236 # the backout should appear on the same branch
237 branch = repo.dirstate.branch()
237 branch = repo.dirstate.branch()
238 hg.clean(repo, node, show_stats=False)
238 hg.clean(repo, node, show_stats=False)
239 repo.dirstate.setbranch(branch)
239 repo.dirstate.setbranch(branch)
240 revert_opts = opts.copy()
240 revert_opts = opts.copy()
241 revert_opts['date'] = None
241 revert_opts['date'] = None
242 revert_opts['all'] = True
242 revert_opts['all'] = True
243 revert_opts['rev'] = hex(parent)
243 revert_opts['rev'] = hex(parent)
244 revert_opts['no_backup'] = None
244 revert_opts['no_backup'] = None
245 revert(ui, repo, **revert_opts)
245 revert(ui, repo, **revert_opts)
246 commit_opts = opts.copy()
246 commit_opts = opts.copy()
247 commit_opts['addremove'] = False
247 commit_opts['addremove'] = False
248 if not commit_opts['message'] and not commit_opts['logfile']:
248 if not commit_opts['message'] and not commit_opts['logfile']:
249 # we don't translate commit messages
249 # we don't translate commit messages
250 commit_opts['message'] = "Backed out changeset %s" % short(node)
250 commit_opts['message'] = "Backed out changeset %s" % short(node)
251 commit_opts['force_editor'] = True
251 commit_opts['force_editor'] = True
252 commit(ui, repo, **commit_opts)
252 commit(ui, repo, **commit_opts)
253 def nice(node):
253 def nice(node):
254 return '%d:%s' % (repo.changelog.rev(node), short(node))
254 return '%d:%s' % (repo.changelog.rev(node), short(node))
255 ui.status(_('changeset %s backs out changeset %s\n') %
255 ui.status(_('changeset %s backs out changeset %s\n') %
256 (nice(repo.changelog.tip()), nice(node)))
256 (nice(repo.changelog.tip()), nice(node)))
257 if op1 != node:
257 if op1 != node:
258 hg.clean(repo, op1, show_stats=False)
258 hg.clean(repo, op1, show_stats=False)
259 if opts.get('merge'):
259 if opts.get('merge'):
260 ui.status(_('merging with changeset %s\n')
260 ui.status(_('merging with changeset %s\n')
261 % nice(repo.changelog.tip()))
261 % nice(repo.changelog.tip()))
262 hg.merge(repo, hex(repo.changelog.tip()))
262 hg.merge(repo, hex(repo.changelog.tip()))
263 else:
263 else:
264 ui.status(_('the backout changeset is a new head - '
264 ui.status(_('the backout changeset is a new head - '
265 'do not forget to merge\n'))
265 'do not forget to merge\n'))
266 ui.status(_('(use "backout --merge" '
266 ui.status(_('(use "backout --merge" '
267 'if you want to auto-merge)\n'))
267 'if you want to auto-merge)\n'))
268
268
269 def bisect(ui, repo, rev=None, extra=None, command=None,
269 def bisect(ui, repo, rev=None, extra=None, command=None,
270 reset=None, good=None, bad=None, skip=None, noupdate=None):
270 reset=None, good=None, bad=None, skip=None, noupdate=None):
271 """subdivision search of changesets
271 """subdivision search of changesets
272
272
273 This command helps to find changesets which introduce problems. To
273 This command helps to find changesets which introduce problems. To
274 use, mark the earliest changeset you know exhibits the problem as
274 use, mark the earliest changeset you know exhibits the problem as
275 bad, then mark the latest changeset which is free from the problem
275 bad, then mark the latest changeset which is free from the problem
276 as good. Bisect will update your working directory to a revision
276 as good. Bisect will update your working directory to a revision
277 for testing (unless the -U/--noupdate option is specified). Once
277 for testing (unless the -U/--noupdate option is specified). Once
278 you have performed tests, mark the working directory as good or
278 you have performed tests, mark the working directory as good or
279 bad, and bisect will either update to another candidate changeset
279 bad, and bisect will either update to another candidate changeset
280 or announce that it has found the bad revision.
280 or announce that it has found the bad revision.
281
281
282 As a shortcut, you can also use the revision argument to mark a
282 As a shortcut, you can also use the revision argument to mark a
283 revision as good or bad without checking it out first.
283 revision as good or bad without checking it out first.
284
284
285 If you supply a command, it will be used for automatic bisection.
285 If you supply a command, it will be used for automatic bisection.
286 Its exit status will be used to mark revisions as good or bad:
286 Its exit status will be used to mark revisions as good or bad:
287 status 0 means good, 125 means to skip the revision, 127
287 status 0 means good, 125 means to skip the revision, 127
288 (command not found) will abort the bisection, and any other
288 (command not found) will abort the bisection, and any other
289 non-zero exit status means the revision is bad.
289 non-zero exit status means the revision is bad.
290 """
290 """
291 def print_result(nodes, good):
291 def print_result(nodes, good):
292 displayer = cmdutil.show_changeset(ui, repo, {})
292 displayer = cmdutil.show_changeset(ui, repo, {})
293 if len(nodes) == 1:
293 if len(nodes) == 1:
294 # narrowed it down to a single revision
294 # narrowed it down to a single revision
295 if good:
295 if good:
296 ui.write(_("The first good revision is:\n"))
296 ui.write(_("The first good revision is:\n"))
297 else:
297 else:
298 ui.write(_("The first bad revision is:\n"))
298 ui.write(_("The first bad revision is:\n"))
299 displayer.show(repo[nodes[0]])
299 displayer.show(repo[nodes[0]])
300 else:
300 else:
301 # multiple possible revisions
301 # multiple possible revisions
302 if good:
302 if good:
303 ui.write(_("Due to skipped revisions, the first "
303 ui.write(_("Due to skipped revisions, the first "
304 "good revision could be any of:\n"))
304 "good revision could be any of:\n"))
305 else:
305 else:
306 ui.write(_("Due to skipped revisions, the first "
306 ui.write(_("Due to skipped revisions, the first "
307 "bad revision could be any of:\n"))
307 "bad revision could be any of:\n"))
308 for n in nodes:
308 for n in nodes:
309 displayer.show(repo[n])
309 displayer.show(repo[n])
310 displayer.close()
310 displayer.close()
311
311
312 def check_state(state, interactive=True):
312 def check_state(state, interactive=True):
313 if not state['good'] or not state['bad']:
313 if not state['good'] or not state['bad']:
314 if (good or bad or skip or reset) and interactive:
314 if (good or bad or skip or reset) and interactive:
315 return
315 return
316 if not state['good']:
316 if not state['good']:
317 raise util.Abort(_('cannot bisect (no known good revisions)'))
317 raise util.Abort(_('cannot bisect (no known good revisions)'))
318 else:
318 else:
319 raise util.Abort(_('cannot bisect (no known bad revisions)'))
319 raise util.Abort(_('cannot bisect (no known bad revisions)'))
320 return True
320 return True
321
321
322 # backward compatibility
322 # backward compatibility
323 if rev in "good bad reset init".split():
323 if rev in "good bad reset init".split():
324 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
324 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
325 cmd, rev, extra = rev, extra, None
325 cmd, rev, extra = rev, extra, None
326 if cmd == "good":
326 if cmd == "good":
327 good = True
327 good = True
328 elif cmd == "bad":
328 elif cmd == "bad":
329 bad = True
329 bad = True
330 else:
330 else:
331 reset = True
331 reset = True
332 elif extra or good + bad + skip + reset + bool(command) > 1:
332 elif extra or good + bad + skip + reset + bool(command) > 1:
333 raise util.Abort(_('incompatible arguments'))
333 raise util.Abort(_('incompatible arguments'))
334
334
335 if reset:
335 if reset:
336 p = repo.join("bisect.state")
336 p = repo.join("bisect.state")
337 if os.path.exists(p):
337 if os.path.exists(p):
338 os.unlink(p)
338 os.unlink(p)
339 return
339 return
340
340
341 state = hbisect.load_state(repo)
341 state = hbisect.load_state(repo)
342
342
343 if command:
343 if command:
344 changesets = 1
344 changesets = 1
345 try:
345 try:
346 while changesets:
346 while changesets:
347 # update state
347 # update state
348 status = util.system(command)
348 status = util.system(command)
349 if status == 125:
349 if status == 125:
350 transition = "skip"
350 transition = "skip"
351 elif status == 0:
351 elif status == 0:
352 transition = "good"
352 transition = "good"
353 # status < 0 means process was killed
353 # status < 0 means process was killed
354 elif status == 127:
354 elif status == 127:
355 raise util.Abort(_("failed to execute %s") % command)
355 raise util.Abort(_("failed to execute %s") % command)
356 elif status < 0:
356 elif status < 0:
357 raise util.Abort(_("%s killed") % command)
357 raise util.Abort(_("%s killed") % command)
358 else:
358 else:
359 transition = "bad"
359 transition = "bad"
360 ctx = repo[rev or '.']
360 ctx = repo[rev or '.']
361 state[transition].append(ctx.node())
361 state[transition].append(ctx.node())
362 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
362 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
363 check_state(state, interactive=False)
363 check_state(state, interactive=False)
364 # bisect
364 # bisect
365 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
365 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
366 # update to next check
366 # update to next check
367 cmdutil.bail_if_changed(repo)
367 cmdutil.bail_if_changed(repo)
368 hg.clean(repo, nodes[0], show_stats=False)
368 hg.clean(repo, nodes[0], show_stats=False)
369 finally:
369 finally:
370 hbisect.save_state(repo, state)
370 hbisect.save_state(repo, state)
371 return print_result(nodes, good)
371 return print_result(nodes, good)
372
372
373 # update state
373 # update state
374 node = repo.lookup(rev or '.')
374 node = repo.lookup(rev or '.')
375 if good or bad or skip:
375 if good or bad or skip:
376 if good:
376 if good:
377 state['good'].append(node)
377 state['good'].append(node)
378 elif bad:
378 elif bad:
379 state['bad'].append(node)
379 state['bad'].append(node)
380 elif skip:
380 elif skip:
381 state['skip'].append(node)
381 state['skip'].append(node)
382 hbisect.save_state(repo, state)
382 hbisect.save_state(repo, state)
383
383
384 if not check_state(state):
384 if not check_state(state):
385 return
385 return
386
386
387 # actually bisect
387 # actually bisect
388 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
388 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
389 if changesets == 0:
389 if changesets == 0:
390 print_result(nodes, good)
390 print_result(nodes, good)
391 else:
391 else:
392 assert len(nodes) == 1 # only a single node can be tested next
392 assert len(nodes) == 1 # only a single node can be tested next
393 node = nodes[0]
393 node = nodes[0]
394 # compute the approximate number of remaining tests
394 # compute the approximate number of remaining tests
395 tests, size = 0, 2
395 tests, size = 0, 2
396 while size <= changesets:
396 while size <= changesets:
397 tests, size = tests + 1, size * 2
397 tests, size = tests + 1, size * 2
398 rev = repo.changelog.rev(node)
398 rev = repo.changelog.rev(node)
399 ui.write(_("Testing changeset %d:%s "
399 ui.write(_("Testing changeset %d:%s "
400 "(%d changesets remaining, ~%d tests)\n")
400 "(%d changesets remaining, ~%d tests)\n")
401 % (rev, short(node), changesets, tests))
401 % (rev, short(node), changesets, tests))
402 if not noupdate:
402 if not noupdate:
403 cmdutil.bail_if_changed(repo)
403 cmdutil.bail_if_changed(repo)
404 return hg.clean(repo, node)
404 return hg.clean(repo, node)
405
405
406 def branch(ui, repo, label=None, **opts):
406 def branch(ui, repo, label=None, **opts):
407 """set or show the current branch name
407 """set or show the current branch name
408
408
409 With no argument, show the current branch name. With one argument,
409 With no argument, show the current branch name. With one argument,
410 set the working directory branch name (the branch will not exist
410 set the working directory branch name (the branch will not exist
411 in the repository until the next commit). Standard practice
411 in the repository until the next commit). Standard practice
412 recommends that primary development take place on the 'default'
412 recommends that primary development take place on the 'default'
413 branch.
413 branch.
414
414
415 Unless -f/--force is specified, branch will not let you set a
415 Unless -f/--force is specified, branch will not let you set a
416 branch name that already exists, even if it's inactive.
416 branch name that already exists, even if it's inactive.
417
417
418 Use -C/--clean to reset the working directory branch to that of
418 Use -C/--clean to reset the working directory branch to that of
419 the parent of the working directory, negating a previous branch
419 the parent of the working directory, negating a previous branch
420 change.
420 change.
421
421
422 Use the command 'hg update' to switch to an existing branch. Use
422 Use the command 'hg update' to switch to an existing branch. Use
423 'hg commit --close-branch' to mark this branch as closed.
423 'hg commit --close-branch' to mark this branch as closed.
424 """
424 """
425
425
426 if opts.get('clean'):
426 if opts.get('clean'):
427 label = repo[None].parents()[0].branch()
427 label = repo[None].parents()[0].branch()
428 repo.dirstate.setbranch(label)
428 repo.dirstate.setbranch(label)
429 ui.status(_('reset working directory to branch %s\n') % label)
429 ui.status(_('reset working directory to branch %s\n') % label)
430 elif label:
430 elif label:
431 utflabel = encoding.fromlocal(label)
431 utflabel = encoding.fromlocal(label)
432 if not opts.get('force') and utflabel in repo.branchtags():
432 if not opts.get('force') and utflabel in repo.branchtags():
433 if label not in [p.branch() for p in repo.parents()]:
433 if label not in [p.branch() for p in repo.parents()]:
434 raise util.Abort(_('a branch of the same name already exists'
434 raise util.Abort(_('a branch of the same name already exists'
435 ' (use --force to override)'))
435 ' (use --force to override)'))
436 repo.dirstate.setbranch(utflabel)
436 repo.dirstate.setbranch(utflabel)
437 ui.status(_('marked working directory as branch %s\n') % label)
437 ui.status(_('marked working directory as branch %s\n') % label)
438 else:
438 else:
439 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
439 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
440
440
441 def branches(ui, repo, active=False, closed=False):
441 def branches(ui, repo, active=False, closed=False):
442 """list repository named branches
442 """list repository named branches
443
443
444 List the repository's named branches, indicating which ones are
444 List the repository's named branches, indicating which ones are
445 inactive. If -c/--closed is specified, also list branches which have
445 inactive. If -c/--closed is specified, also list branches which have
446 been marked closed (see hg commit --close-branch).
446 been marked closed (see hg commit --close-branch).
447
447
448 If -a/--active is specified, only show active branches. A branch
448 If -a/--active is specified, only show active branches. A branch
449 is considered active if it contains repository heads.
449 is considered active if it contains repository heads.
450
450
451 Use the command 'hg update' to switch to an existing branch.
451 Use the command 'hg update' to switch to an existing branch.
452 """
452 """
453
453
454 hexfunc = ui.debugflag and hex or short
454 hexfunc = ui.debugflag and hex or short
455 activebranches = [repo[n].branch() for n in repo.heads()]
455 activebranches = [repo[n].branch() for n in repo.heads()]
456 def testactive(tag, node):
456 def testactive(tag, node):
457 realhead = tag in activebranches
457 realhead = tag in activebranches
458 open = node in repo.branchheads(tag, closed=False)
458 open = node in repo.branchheads(tag, closed=False)
459 return realhead and open
459 return realhead and open
460 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
460 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
461 for tag, node in repo.branchtags().items()],
461 for tag, node in repo.branchtags().items()],
462 reverse=True)
462 reverse=True)
463
463
464 for isactive, node, tag in branches:
464 for isactive, node, tag in branches:
465 if (not active) or isactive:
465 if (not active) or isactive:
466 encodedtag = encoding.tolocal(tag)
466 encodedtag = encoding.tolocal(tag)
467 if ui.quiet:
467 if ui.quiet:
468 ui.write("%s\n" % encodedtag)
468 ui.write("%s\n" % encodedtag)
469 else:
469 else:
470 hn = repo.lookup(node)
470 hn = repo.lookup(node)
471 if isactive:
471 if isactive:
472 notice = ''
472 notice = ''
473 elif hn not in repo.branchheads(tag, closed=False):
473 elif hn not in repo.branchheads(tag, closed=False):
474 if not closed:
474 if not closed:
475 continue
475 continue
476 notice = _(' (closed)')
476 notice = _(' (closed)')
477 else:
477 else:
478 notice = _(' (inactive)')
478 notice = _(' (inactive)')
479 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
479 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
480 data = encodedtag, rev, hexfunc(hn), notice
480 data = encodedtag, rev, hexfunc(hn), notice
481 ui.write("%s %s:%s%s\n" % data)
481 ui.write("%s %s:%s%s\n" % data)
482
482
483 def bundle(ui, repo, fname, dest=None, **opts):
483 def bundle(ui, repo, fname, dest=None, **opts):
484 """create a changegroup file
484 """create a changegroup file
485
485
486 Generate a compressed changegroup file collecting changesets not
486 Generate a compressed changegroup file collecting changesets not
487 known to be in another repository.
487 known to be in another repository.
488
488
489 If you omit the destination repository, then hg assumes the
489 If you omit the destination repository, then hg assumes the
490 destination will have all the nodes you specify with --base
490 destination will have all the nodes you specify with --base
491 parameters. To create a bundle containing all changesets, use
491 parameters. To create a bundle containing all changesets, use
492 -a/--all (or --base null).
492 -a/--all (or --base null).
493
493
494 You can change compression method with the -t/--type option.
494 You can change compression method with the -t/--type option.
495 The available compression methods are: none, bzip2, and
495 The available compression methods are: none, bzip2, and
496 gzip (by default, bundles are compressed using bzip2).
496 gzip (by default, bundles are compressed using bzip2).
497
497
498 The bundle file can then be transferred using conventional means
498 The bundle file can then be transferred using conventional means
499 and applied to another repository with the unbundle or pull
499 and applied to another repository with the unbundle or pull
500 command. This is useful when direct push and pull are not
500 command. This is useful when direct push and pull are not
501 available or when exporting an entire repository is undesirable.
501 available or when exporting an entire repository is undesirable.
502
502
503 Applying bundles preserves all changeset contents including
503 Applying bundles preserves all changeset contents including
504 permissions, copy/rename information, and revision history.
504 permissions, copy/rename information, and revision history.
505 """
505 """
506 revs = opts.get('rev') or None
506 revs = opts.get('rev') or None
507 if revs:
507 if revs:
508 revs = [repo.lookup(rev) for rev in revs]
508 revs = [repo.lookup(rev) for rev in revs]
509 if opts.get('all'):
509 if opts.get('all'):
510 base = ['null']
510 base = ['null']
511 else:
511 else:
512 base = opts.get('base')
512 base = opts.get('base')
513 if base:
513 if base:
514 if dest:
514 if dest:
515 raise util.Abort(_("--base is incompatible with specifying "
515 raise util.Abort(_("--base is incompatible with specifying "
516 "a destination"))
516 "a destination"))
517 base = [repo.lookup(rev) for rev in base]
517 base = [repo.lookup(rev) for rev in base]
518 # create the right base
518 # create the right base
519 # XXX: nodesbetween / changegroup* should be "fixed" instead
519 # XXX: nodesbetween / changegroup* should be "fixed" instead
520 o = []
520 o = []
521 has = set((nullid,))
521 has = set((nullid,))
522 for n in base:
522 for n in base:
523 has.update(repo.changelog.reachable(n))
523 has.update(repo.changelog.reachable(n))
524 if revs:
524 if revs:
525 visit = list(revs)
525 visit = list(revs)
526 else:
526 else:
527 visit = repo.changelog.heads()
527 visit = repo.changelog.heads()
528 seen = {}
528 seen = {}
529 while visit:
529 while visit:
530 n = visit.pop(0)
530 n = visit.pop(0)
531 parents = [p for p in repo.changelog.parents(n) if p not in has]
531 parents = [p for p in repo.changelog.parents(n) if p not in has]
532 if len(parents) == 0:
532 if len(parents) == 0:
533 o.insert(0, n)
533 o.insert(0, n)
534 else:
534 else:
535 for p in parents:
535 for p in parents:
536 if p not in seen:
536 if p not in seen:
537 seen[p] = 1
537 seen[p] = 1
538 visit.append(p)
538 visit.append(p)
539 else:
539 else:
540 dest = ui.expandpath(dest or 'default-push', dest or 'default')
540 dest = ui.expandpath(dest or 'default-push', dest or 'default')
541 dest, branches = hg.parseurl(dest, opts.get('branch'))
541 dest, branches = hg.parseurl(dest, opts.get('branch'))
542 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
542 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
543 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
543 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
544 o = repo.findoutgoing(other, force=opts.get('force'))
544 o = repo.findoutgoing(other, force=opts.get('force'))
545
545
546 if revs:
546 if revs:
547 cg = repo.changegroupsubset(o, revs, 'bundle')
547 cg = repo.changegroupsubset(o, revs, 'bundle')
548 else:
548 else:
549 cg = repo.changegroup(o, 'bundle')
549 cg = repo.changegroup(o, 'bundle')
550
550
551 bundletype = opts.get('type', 'bzip2').lower()
551 bundletype = opts.get('type', 'bzip2').lower()
552 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
552 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
553 bundletype = btypes.get(bundletype)
553 bundletype = btypes.get(bundletype)
554 if bundletype not in changegroup.bundletypes:
554 if bundletype not in changegroup.bundletypes:
555 raise util.Abort(_('unknown bundle type specified with --type'))
555 raise util.Abort(_('unknown bundle type specified with --type'))
556
556
557 changegroup.writebundle(cg, fname, bundletype)
557 changegroup.writebundle(cg, fname, bundletype)
558
558
559 def cat(ui, repo, file1, *pats, **opts):
559 def cat(ui, repo, file1, *pats, **opts):
560 """output the current or given revision of files
560 """output the current or given revision of files
561
561
562 Print the specified files as they were at the given revision. If
562 Print the specified files as they were at the given revision. If
563 no revision is given, the parent of the working directory is used,
563 no revision is given, the parent of the working directory is used,
564 or tip if no revision is checked out.
564 or tip if no revision is checked out.
565
565
566 Output may be to a file, in which case the name of the file is
566 Output may be to a file, in which case the name of the file is
567 given using a format string. The formatting rules are the same as
567 given using a format string. The formatting rules are the same as
568 for the export command, with the following additions:
568 for the export command, with the following additions:
569
569
570 :``%s``: basename of file being printed
570 :``%s``: basename of file being printed
571 :``%d``: dirname of file being printed, or '.' if in repository root
571 :``%d``: dirname of file being printed, or '.' if in repository root
572 :``%p``: root-relative path name of file being printed
572 :``%p``: root-relative path name of file being printed
573 """
573 """
574 ctx = repo[opts.get('rev')]
574 ctx = repo[opts.get('rev')]
575 err = 1
575 err = 1
576 m = cmdutil.match(repo, (file1,) + pats, opts)
576 m = cmdutil.match(repo, (file1,) + pats, opts)
577 for abs in ctx.walk(m):
577 for abs in ctx.walk(m):
578 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
578 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
579 data = ctx[abs].data()
579 data = ctx[abs].data()
580 if opts.get('decode'):
580 if opts.get('decode'):
581 data = repo.wwritedata(abs, data)
581 data = repo.wwritedata(abs, data)
582 fp.write(data)
582 fp.write(data)
583 err = 0
583 err = 0
584 return err
584 return err
585
585
586 def clone(ui, source, dest=None, **opts):
586 def clone(ui, source, dest=None, **opts):
587 """make a copy of an existing repository
587 """make a copy of an existing repository
588
588
589 Create a copy of an existing repository in a new directory.
589 Create a copy of an existing repository in a new directory.
590
590
591 If no destination directory name is specified, it defaults to the
591 If no destination directory name is specified, it defaults to the
592 basename of the source.
592 basename of the source.
593
593
594 The location of the source is added to the new repository's
594 The location of the source is added to the new repository's
595 .hg/hgrc file, as the default to be used for future pulls.
595 .hg/hgrc file, as the default to be used for future pulls.
596
596
597 See 'hg help urls' for valid source format details.
597 See 'hg help urls' for valid source format details.
598
598
599 It is possible to specify an ``ssh://`` URL as the destination, but no
599 It is possible to specify an ``ssh://`` URL as the destination, but no
600 .hg/hgrc and working directory will be created on the remote side.
600 .hg/hgrc and working directory will be created on the remote side.
601 Please see 'hg help urls' for important details about ``ssh://`` URLs.
601 Please see 'hg help urls' for important details about ``ssh://`` URLs.
602
602
603 If the -U/--noupdate option is specified, the new clone will contain
603 If the -U/--noupdate option is specified, the new clone will contain
604 only a repository (.hg) and no working copy (the working copy parent
604 only a repository (.hg) and no working copy (the working copy parent
605 will be the null changeset). Otherwise, clone will initially check
605 will be the null changeset). Otherwise, clone will initially check
606 out (in order of precedence):
606 out (in order of precedence):
607
607
608 a) the changeset, tag or branch specified with -u/--updaterev
608 a) the changeset, tag or branch specified with -u/--updaterev
609 b) the changeset, tag or branch given with the first -r/--rev
609 b) the changeset, tag or branch given with the first -r/--rev
610 c) the branch given with the first -b/--branch
610 c) the branch given with the first -b/--branch
611 d) the branch given with the url#branch source syntax
611 d) the branch given with the url#branch source syntax
612 e) the head of the default branch
612 e) the head of the default branch
613
613
614 Use 'hg clone -u . src dst' to checkout the source repository's
614 Use 'hg clone -u . src dst' to checkout the source repository's
615 parent changeset (applicable for local source repositories only).
615 parent changeset (applicable for local source repositories only).
616
616
617 A set of changesets (tags, or branch names) to pull may be specified
617 A set of changesets (tags, or branch names) to pull may be specified
618 by listing each changeset (tag, or branch name) with -r/--rev.
618 by listing each changeset (tag, or branch name) with -r/--rev.
619 If -r/--rev is used, the cloned repository will contain only a subset
619 If -r/--rev is used, the cloned repository will contain only a subset
620 of the changesets of the source repository. Only the set of changesets
620 of the changesets of the source repository. Only the set of changesets
621 defined by all -r/--rev options (including all their ancestors)
621 defined by all -r/--rev options (including all their ancestors)
622 will be pulled into the destination repository.
622 will be pulled into the destination repository.
623 No subsequent changesets (including subsequent tags) will be present
623 No subsequent changesets (including subsequent tags) will be present
624 in the destination.
624 in the destination.
625
625
626 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
626 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
627 local source repositories.
627 local source repositories.
628
628
629 For efficiency, hardlinks are used for cloning whenever the source
629 For efficiency, hardlinks are used for cloning whenever the source
630 and destination are on the same filesystem (note this applies only
630 and destination are on the same filesystem (note this applies only
631 to the repository data, not to the checked out files). Some
631 to the repository data, not to the checked out files). Some
632 filesystems, such as AFS, implement hardlinking incorrectly, but
632 filesystems, such as AFS, implement hardlinking incorrectly, but
633 do not report errors. In these cases, use the --pull option to
633 do not report errors. In these cases, use the --pull option to
634 avoid hardlinking.
634 avoid hardlinking.
635
635
636 In some cases, you can clone repositories and checked out files
636 In some cases, you can clone repositories and checked out files
637 using full hardlinks with ::
637 using full hardlinks with ::
638
638
639 $ cp -al REPO REPOCLONE
639 $ cp -al REPO REPOCLONE
640
640
641 This is the fastest way to clone, but it is not always safe. The
641 This is the fastest way to clone, but it is not always safe. The
642 operation is not atomic (making sure REPO is not modified during
642 operation is not atomic (making sure REPO is not modified during
643 the operation is up to you) and you have to make sure your editor
643 the operation is up to you) and you have to make sure your editor
644 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
644 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
645 this is not compatible with certain extensions that place their
645 this is not compatible with certain extensions that place their
646 metadata under the .hg directory, such as mq.
646 metadata under the .hg directory, such as mq.
647 """
647 """
648 if opts.get('noupdate') and opts.get('updaterev'):
648 if opts.get('noupdate') and opts.get('updaterev'):
649 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
649 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
650
650
651 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
651 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
652 pull=opts.get('pull'),
652 pull=opts.get('pull'),
653 stream=opts.get('uncompressed'),
653 stream=opts.get('uncompressed'),
654 rev=opts.get('rev'),
654 rev=opts.get('rev'),
655 update=opts.get('updaterev') or not opts.get('noupdate'),
655 update=opts.get('updaterev') or not opts.get('noupdate'),
656 branch=opts.get('branch'))
656 branch=opts.get('branch'))
657
657
658 def commit(ui, repo, *pats, **opts):
658 def commit(ui, repo, *pats, **opts):
659 """commit the specified files or all outstanding changes
659 """commit the specified files or all outstanding changes
660
660
661 Commit changes to the given files into the repository. Unlike a
661 Commit changes to the given files into the repository. Unlike a
662 centralized RCS, this operation is a local operation. See hg push
662 centralized RCS, this operation is a local operation. See hg push
663 for a way to actively distribute your changes.
663 for a way to actively distribute your changes.
664
664
665 If a list of files is omitted, all changes reported by "hg status"
665 If a list of files is omitted, all changes reported by "hg status"
666 will be committed.
666 will be committed.
667
667
668 If you are committing the result of a merge, do not provide any
668 If you are committing the result of a merge, do not provide any
669 filenames or -I/-X filters.
669 filenames or -I/-X filters.
670
670
671 If no commit message is specified, the configured editor is
671 If no commit message is specified, the configured editor is
672 started to prompt you for a message.
672 started to prompt you for a message.
673
673
674 See 'hg help dates' for a list of formats valid for -d/--date.
674 See 'hg help dates' for a list of formats valid for -d/--date.
675 """
675 """
676 extra = {}
676 extra = {}
677 if opts.get('close_branch'):
677 if opts.get('close_branch'):
678 extra['close'] = 1
678 extra['close'] = 1
679 e = cmdutil.commiteditor
679 e = cmdutil.commiteditor
680 if opts.get('force_editor'):
680 if opts.get('force_editor'):
681 e = cmdutil.commitforceeditor
681 e = cmdutil.commitforceeditor
682
682
683 def commitfunc(ui, repo, message, match, opts):
683 def commitfunc(ui, repo, message, match, opts):
684 return repo.commit(message, opts.get('user'), opts.get('date'), match,
684 return repo.commit(message, opts.get('user'), opts.get('date'), match,
685 editor=e, extra=extra)
685 editor=e, extra=extra)
686
686
687 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
687 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
688 if not node:
688 if not node:
689 ui.status(_("nothing changed\n"))
689 ui.status(_("nothing changed\n"))
690 return
690 return
691 cl = repo.changelog
691 cl = repo.changelog
692 rev = cl.rev(node)
692 rev = cl.rev(node)
693 parents = cl.parentrevs(rev)
693 parents = cl.parentrevs(rev)
694 if rev - 1 in parents:
694 if rev - 1 in parents:
695 # one of the parents was the old tip
695 # one of the parents was the old tip
696 pass
696 pass
697 elif (parents == (nullrev, nullrev) or
697 elif (parents == (nullrev, nullrev) or
698 len(cl.heads(cl.node(parents[0]))) > 1 and
698 len(cl.heads(cl.node(parents[0]))) > 1 and
699 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
699 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
700 ui.status(_('created new head\n'))
700 ui.status(_('created new head\n'))
701
701
702 if ui.debugflag:
702 if ui.debugflag:
703 ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
703 ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
704 elif ui.verbose:
704 elif ui.verbose:
705 ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
705 ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
706
706
707 def copy(ui, repo, *pats, **opts):
707 def copy(ui, repo, *pats, **opts):
708 """mark files as copied for the next commit
708 """mark files as copied for the next commit
709
709
710 Mark dest as having copies of source files. If dest is a
710 Mark dest as having copies of source files. If dest is a
711 directory, copies are put in that directory. If dest is a file,
711 directory, copies are put in that directory. If dest is a file,
712 the source must be a single file.
712 the source must be a single file.
713
713
714 By default, this command copies the contents of files as they
714 By default, this command copies the contents of files as they
715 exist in the working directory. If invoked with -A/--after, the
715 exist in the working directory. If invoked with -A/--after, the
716 operation is recorded, but no copying is performed.
716 operation is recorded, but no copying is performed.
717
717
718 This command takes effect with the next commit. To undo a copy
718 This command takes effect with the next commit. To undo a copy
719 before that, see hg revert.
719 before that, see hg revert.
720 """
720 """
721 wlock = repo.wlock(False)
721 wlock = repo.wlock(False)
722 try:
722 try:
723 return cmdutil.copy(ui, repo, pats, opts)
723 return cmdutil.copy(ui, repo, pats, opts)
724 finally:
724 finally:
725 wlock.release()
725 wlock.release()
726
726
727 def debugancestor(ui, repo, *args):
727 def debugancestor(ui, repo, *args):
728 """find the ancestor revision of two revisions in a given index"""
728 """find the ancestor revision of two revisions in a given index"""
729 if len(args) == 3:
729 if len(args) == 3:
730 index, rev1, rev2 = args
730 index, rev1, rev2 = args
731 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
731 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
732 lookup = r.lookup
732 lookup = r.lookup
733 elif len(args) == 2:
733 elif len(args) == 2:
734 if not repo:
734 if not repo:
735 raise util.Abort(_("There is no Mercurial repository here "
735 raise util.Abort(_("There is no Mercurial repository here "
736 "(.hg not found)"))
736 "(.hg not found)"))
737 rev1, rev2 = args
737 rev1, rev2 = args
738 r = repo.changelog
738 r = repo.changelog
739 lookup = repo.lookup
739 lookup = repo.lookup
740 else:
740 else:
741 raise util.Abort(_('either two or three arguments required'))
741 raise util.Abort(_('either two or three arguments required'))
742 a = r.ancestor(lookup(rev1), lookup(rev2))
742 a = r.ancestor(lookup(rev1), lookup(rev2))
743 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
743 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
744
744
745 def debugcommands(ui, cmd='', *args):
745 def debugcommands(ui, cmd='', *args):
746 for cmd, vals in sorted(table.iteritems()):
746 for cmd, vals in sorted(table.iteritems()):
747 cmd = cmd.split('|')[0].strip('^')
747 cmd = cmd.split('|')[0].strip('^')
748 opts = ', '.join([i[1] for i in vals[1]])
748 opts = ', '.join([i[1] for i in vals[1]])
749 ui.write('%s: %s\n' % (cmd, opts))
749 ui.write('%s: %s\n' % (cmd, opts))
750
750
751 def debugcomplete(ui, cmd='', **opts):
751 def debugcomplete(ui, cmd='', **opts):
752 """returns the completion list associated with the given command"""
752 """returns the completion list associated with the given command"""
753
753
754 if opts.get('options'):
754 if opts.get('options'):
755 options = []
755 options = []
756 otables = [globalopts]
756 otables = [globalopts]
757 if cmd:
757 if cmd:
758 aliases, entry = cmdutil.findcmd(cmd, table, False)
758 aliases, entry = cmdutil.findcmd(cmd, table, False)
759 otables.append(entry[1])
759 otables.append(entry[1])
760 for t in otables:
760 for t in otables:
761 for o in t:
761 for o in t:
762 if o[0]:
762 if o[0]:
763 options.append('-%s' % o[0])
763 options.append('-%s' % o[0])
764 options.append('--%s' % o[1])
764 options.append('--%s' % o[1])
765 ui.write("%s\n" % "\n".join(options))
765 ui.write("%s\n" % "\n".join(options))
766 return
766 return
767
767
768 cmdlist = cmdutil.findpossible(cmd, table)
768 cmdlist = cmdutil.findpossible(cmd, table)
769 if ui.verbose:
769 if ui.verbose:
770 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
770 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
771 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
771 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
772
772
773 def debugfsinfo(ui, path = "."):
773 def debugfsinfo(ui, path = "."):
774 open('.debugfsinfo', 'w').write('')
774 open('.debugfsinfo', 'w').write('')
775 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
775 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
776 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
776 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
777 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
777 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
778 and 'yes' or 'no'))
778 and 'yes' or 'no'))
779 os.unlink('.debugfsinfo')
779 os.unlink('.debugfsinfo')
780
780
781 def debugrebuildstate(ui, repo, rev="tip"):
781 def debugrebuildstate(ui, repo, rev="tip"):
782 """rebuild the dirstate as it would look like for the given revision"""
782 """rebuild the dirstate as it would look like for the given revision"""
783 ctx = repo[rev]
783 ctx = repo[rev]
784 wlock = repo.wlock()
784 wlock = repo.wlock()
785 try:
785 try:
786 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
786 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
787 finally:
787 finally:
788 wlock.release()
788 wlock.release()
789
789
790 def debugcheckstate(ui, repo):
790 def debugcheckstate(ui, repo):
791 """validate the correctness of the current dirstate"""
791 """validate the correctness of the current dirstate"""
792 parent1, parent2 = repo.dirstate.parents()
792 parent1, parent2 = repo.dirstate.parents()
793 m1 = repo[parent1].manifest()
793 m1 = repo[parent1].manifest()
794 m2 = repo[parent2].manifest()
794 m2 = repo[parent2].manifest()
795 errors = 0
795 errors = 0
796 for f in repo.dirstate:
796 for f in repo.dirstate:
797 state = repo.dirstate[f]
797 state = repo.dirstate[f]
798 if state in "nr" and f not in m1:
798 if state in "nr" and f not in m1:
799 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
799 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
800 errors += 1
800 errors += 1
801 if state in "a" and f in m1:
801 if state in "a" and f in m1:
802 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
802 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
803 errors += 1
803 errors += 1
804 if state in "m" and f not in m1 and f not in m2:
804 if state in "m" and f not in m1 and f not in m2:
805 ui.warn(_("%s in state %s, but not in either manifest\n") %
805 ui.warn(_("%s in state %s, but not in either manifest\n") %
806 (f, state))
806 (f, state))
807 errors += 1
807 errors += 1
808 for f in m1:
808 for f in m1:
809 state = repo.dirstate[f]
809 state = repo.dirstate[f]
810 if state not in "nrm":
810 if state not in "nrm":
811 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
811 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
812 errors += 1
812 errors += 1
813 if errors:
813 if errors:
814 error = _(".hg/dirstate inconsistent with current parent's manifest")
814 error = _(".hg/dirstate inconsistent with current parent's manifest")
815 raise util.Abort(error)
815 raise util.Abort(error)
816
816
817 def showconfig(ui, repo, *values, **opts):
817 def showconfig(ui, repo, *values, **opts):
818 """show combined config settings from all hgrc files
818 """show combined config settings from all hgrc files
819
819
820 With no arguments, print names and values of all config items.
820 With no arguments, print names and values of all config items.
821
821
822 With one argument of the form section.name, print just the value
822 With one argument of the form section.name, print just the value
823 of that config item.
823 of that config item.
824
824
825 With multiple arguments, print names and values of all config
825 With multiple arguments, print names and values of all config
826 items with matching section names.
826 items with matching section names.
827
827
828 With --debug, the source (filename and line number) is printed
828 With --debug, the source (filename and line number) is printed
829 for each config item.
829 for each config item.
830 """
830 """
831
831
832 untrusted = bool(opts.get('untrusted'))
832 untrusted = bool(opts.get('untrusted'))
833 if values:
833 if values:
834 if len([v for v in values if '.' in v]) > 1:
834 if len([v for v in values if '.' in v]) > 1:
835 raise util.Abort(_('only one config item permitted'))
835 raise util.Abort(_('only one config item permitted'))
836 for section, name, value in ui.walkconfig(untrusted=untrusted):
836 for section, name, value in ui.walkconfig(untrusted=untrusted):
837 sectname = section + '.' + name
837 sectname = section + '.' + name
838 if values:
838 if values:
839 for v in values:
839 for v in values:
840 if v == section:
840 if v == section:
841 ui.debug('%s: ' %
841 ui.debug('%s: ' %
842 ui.configsource(section, name, untrusted))
842 ui.configsource(section, name, untrusted))
843 ui.write('%s=%s\n' % (sectname, value))
843 ui.write('%s=%s\n' % (sectname, value))
844 elif v == sectname:
844 elif v == sectname:
845 ui.debug('%s: ' %
845 ui.debug('%s: ' %
846 ui.configsource(section, name, untrusted))
846 ui.configsource(section, name, untrusted))
847 ui.write(value, '\n')
847 ui.write(value, '\n')
848 else:
848 else:
849 ui.debug('%s: ' %
849 ui.debug('%s: ' %
850 ui.configsource(section, name, untrusted))
850 ui.configsource(section, name, untrusted))
851 ui.write('%s=%s\n' % (sectname, value))
851 ui.write('%s=%s\n' % (sectname, value))
852
852
853 def debugsetparents(ui, repo, rev1, rev2=None):
853 def debugsetparents(ui, repo, rev1, rev2=None):
854 """manually set the parents of the current working directory
854 """manually set the parents of the current working directory
855
855
856 This is useful for writing repository conversion tools, but should
856 This is useful for writing repository conversion tools, but should
857 be used with care.
857 be used with care.
858 """
858 """
859
859
860 if not rev2:
860 if not rev2:
861 rev2 = hex(nullid)
861 rev2 = hex(nullid)
862
862
863 wlock = repo.wlock()
863 wlock = repo.wlock()
864 try:
864 try:
865 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
865 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
866 finally:
866 finally:
867 wlock.release()
867 wlock.release()
868
868
869 def debugstate(ui, repo, nodates=None):
869 def debugstate(ui, repo, nodates=None):
870 """show the contents of the current dirstate"""
870 """show the contents of the current dirstate"""
871 timestr = ""
871 timestr = ""
872 showdate = not nodates
872 showdate = not nodates
873 for file_, ent in sorted(repo.dirstate._map.iteritems()):
873 for file_, ent in sorted(repo.dirstate._map.iteritems()):
874 if showdate:
874 if showdate:
875 if ent[3] == -1:
875 if ent[3] == -1:
876 # Pad or slice to locale representation
876 # Pad or slice to locale representation
877 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
877 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
878 time.localtime(0)))
878 time.localtime(0)))
879 timestr = 'unset'
879 timestr = 'unset'
880 timestr = (timestr[:locale_len] +
880 timestr = (timestr[:locale_len] +
881 ' ' * (locale_len - len(timestr)))
881 ' ' * (locale_len - len(timestr)))
882 else:
882 else:
883 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
883 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
884 time.localtime(ent[3]))
884 time.localtime(ent[3]))
885 if ent[1] & 020000:
885 if ent[1] & 020000:
886 mode = 'lnk'
886 mode = 'lnk'
887 else:
887 else:
888 mode = '%3o' % (ent[1] & 0777)
888 mode = '%3o' % (ent[1] & 0777)
889 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
889 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
890 for f in repo.dirstate.copies():
890 for f in repo.dirstate.copies():
891 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
891 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
892
892
893 def debugsub(ui, repo, rev=None):
893 def debugsub(ui, repo, rev=None):
894 if rev == '':
894 if rev == '':
895 rev = None
895 rev = None
896 for k, v in sorted(repo[rev].substate.items()):
896 for k, v in sorted(repo[rev].substate.items()):
897 ui.write('path %s\n' % k)
897 ui.write('path %s\n' % k)
898 ui.write(' source %s\n' % v[0])
898 ui.write(' source %s\n' % v[0])
899 ui.write(' revision %s\n' % v[1])
899 ui.write(' revision %s\n' % v[1])
900
900
901 def debugdata(ui, file_, rev):
901 def debugdata(ui, file_, rev):
902 """dump the contents of a data file revision"""
902 """dump the contents of a data file revision"""
903 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
903 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
904 try:
904 try:
905 ui.write(r.revision(r.lookup(rev)))
905 ui.write(r.revision(r.lookup(rev)))
906 except KeyError:
906 except KeyError:
907 raise util.Abort(_('invalid revision identifier %s') % rev)
907 raise util.Abort(_('invalid revision identifier %s') % rev)
908
908
909 def debugdate(ui, date, range=None, **opts):
909 def debugdate(ui, date, range=None, **opts):
910 """parse and display a date"""
910 """parse and display a date"""
911 if opts["extended"]:
911 if opts["extended"]:
912 d = util.parsedate(date, util.extendeddateformats)
912 d = util.parsedate(date, util.extendeddateformats)
913 else:
913 else:
914 d = util.parsedate(date)
914 d = util.parsedate(date)
915 ui.write("internal: %s %s\n" % d)
915 ui.write("internal: %s %s\n" % d)
916 ui.write("standard: %s\n" % util.datestr(d))
916 ui.write("standard: %s\n" % util.datestr(d))
917 if range:
917 if range:
918 m = util.matchdate(range)
918 m = util.matchdate(range)
919 ui.write("match: %s\n" % m(d[0]))
919 ui.write("match: %s\n" % m(d[0]))
920
920
921 def debugindex(ui, file_):
921 def debugindex(ui, file_):
922 """dump the contents of an index file"""
922 """dump the contents of an index file"""
923 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
923 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
924 ui.write(" rev offset length base linkrev"
924 ui.write(" rev offset length base linkrev"
925 " nodeid p1 p2\n")
925 " nodeid p1 p2\n")
926 for i in r:
926 for i in r:
927 node = r.node(i)
927 node = r.node(i)
928 try:
928 try:
929 pp = r.parents(node)
929 pp = r.parents(node)
930 except:
930 except:
931 pp = [nullid, nullid]
931 pp = [nullid, nullid]
932 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
932 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
933 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
933 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
934 short(node), short(pp[0]), short(pp[1])))
934 short(node), short(pp[0]), short(pp[1])))
935
935
936 def debugindexdot(ui, file_):
936 def debugindexdot(ui, file_):
937 """dump an index DAG as a graphviz dot file"""
937 """dump an index DAG as a graphviz dot file"""
938 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
938 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
939 ui.write("digraph G {\n")
939 ui.write("digraph G {\n")
940 for i in r:
940 for i in r:
941 node = r.node(i)
941 node = r.node(i)
942 pp = r.parents(node)
942 pp = r.parents(node)
943 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
943 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
944 if pp[1] != nullid:
944 if pp[1] != nullid:
945 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
945 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
946 ui.write("}\n")
946 ui.write("}\n")
947
947
948 def debuginstall(ui):
948 def debuginstall(ui):
949 '''test Mercurial installation'''
949 '''test Mercurial installation'''
950
950
951 def writetemp(contents):
951 def writetemp(contents):
952 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
952 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
953 f = os.fdopen(fd, "wb")
953 f = os.fdopen(fd, "wb")
954 f.write(contents)
954 f.write(contents)
955 f.close()
955 f.close()
956 return name
956 return name
957
957
958 problems = 0
958 problems = 0
959
959
960 # encoding
960 # encoding
961 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
961 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
962 try:
962 try:
963 encoding.fromlocal("test")
963 encoding.fromlocal("test")
964 except util.Abort, inst:
964 except util.Abort, inst:
965 ui.write(" %s\n" % inst)
965 ui.write(" %s\n" % inst)
966 ui.write(_(" (check that your locale is properly set)\n"))
966 ui.write(_(" (check that your locale is properly set)\n"))
967 problems += 1
967 problems += 1
968
968
969 # compiled modules
969 # compiled modules
970 ui.status(_("Checking extensions...\n"))
970 ui.status(_("Checking extensions...\n"))
971 try:
971 try:
972 import bdiff, mpatch, base85
972 import bdiff, mpatch, base85
973 except Exception, inst:
973 except Exception, inst:
974 ui.write(" %s\n" % inst)
974 ui.write(" %s\n" % inst)
975 ui.write(_(" One or more extensions could not be found"))
975 ui.write(_(" One or more extensions could not be found"))
976 ui.write(_(" (check that you compiled the extensions)\n"))
976 ui.write(_(" (check that you compiled the extensions)\n"))
977 problems += 1
977 problems += 1
978
978
979 # templates
979 # templates
980 ui.status(_("Checking templates...\n"))
980 ui.status(_("Checking templates...\n"))
981 try:
981 try:
982 import templater
982 import templater
983 templater.templater(templater.templatepath("map-cmdline.default"))
983 templater.templater(templater.templatepath("map-cmdline.default"))
984 except Exception, inst:
984 except Exception, inst:
985 ui.write(" %s\n" % inst)
985 ui.write(" %s\n" % inst)
986 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
986 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
987 problems += 1
987 problems += 1
988
988
989 # patch
989 # patch
990 ui.status(_("Checking patch...\n"))
990 ui.status(_("Checking patch...\n"))
991 patchproblems = 0
991 patchproblems = 0
992 a = "1\n2\n3\n4\n"
992 a = "1\n2\n3\n4\n"
993 b = "1\n2\n3\ninsert\n4\n"
993 b = "1\n2\n3\ninsert\n4\n"
994 fa = writetemp(a)
994 fa = writetemp(a)
995 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
995 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
996 os.path.basename(fa))
996 os.path.basename(fa))
997 fd = writetemp(d)
997 fd = writetemp(d)
998
998
999 files = {}
999 files = {}
1000 try:
1000 try:
1001 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1001 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1002 except util.Abort, e:
1002 except util.Abort, e:
1003 ui.write(_(" patch call failed:\n"))
1003 ui.write(_(" patch call failed:\n"))
1004 ui.write(" " + str(e) + "\n")
1004 ui.write(" " + str(e) + "\n")
1005 patchproblems += 1
1005 patchproblems += 1
1006 else:
1006 else:
1007 if list(files) != [os.path.basename(fa)]:
1007 if list(files) != [os.path.basename(fa)]:
1008 ui.write(_(" unexpected patch output!\n"))
1008 ui.write(_(" unexpected patch output!\n"))
1009 patchproblems += 1
1009 patchproblems += 1
1010 a = open(fa).read()
1010 a = open(fa).read()
1011 if a != b:
1011 if a != b:
1012 ui.write(_(" patch test failed!\n"))
1012 ui.write(_(" patch test failed!\n"))
1013 patchproblems += 1
1013 patchproblems += 1
1014
1014
1015 if patchproblems:
1015 if patchproblems:
1016 if ui.config('ui', 'patch'):
1016 if ui.config('ui', 'patch'):
1017 ui.write(_(" (Current patch tool may be incompatible with patch,"
1017 ui.write(_(" (Current patch tool may be incompatible with patch,"
1018 " or misconfigured. Please check your .hgrc file)\n"))
1018 " or misconfigured. Please check your .hgrc file)\n"))
1019 else:
1019 else:
1020 ui.write(_(" Internal patcher failure, please report this error"
1020 ui.write(_(" Internal patcher failure, please report this error"
1021 " to http://mercurial.selenic.com/bts/\n"))
1021 " to http://mercurial.selenic.com/bts/\n"))
1022 problems += patchproblems
1022 problems += patchproblems
1023
1023
1024 os.unlink(fa)
1024 os.unlink(fa)
1025 os.unlink(fd)
1025 os.unlink(fd)
1026
1026
1027 # editor
1027 # editor
1028 ui.status(_("Checking commit editor...\n"))
1028 ui.status(_("Checking commit editor...\n"))
1029 editor = ui.geteditor()
1029 editor = ui.geteditor()
1030 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1030 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1031 if not cmdpath:
1031 if not cmdpath:
1032 if editor == 'vi':
1032 if editor == 'vi':
1033 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1033 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1034 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1034 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1035 else:
1035 else:
1036 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1036 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1037 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1037 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1038 problems += 1
1038 problems += 1
1039
1039
1040 # check username
1040 # check username
1041 ui.status(_("Checking username...\n"))
1041 ui.status(_("Checking username...\n"))
1042 try:
1042 try:
1043 user = ui.username()
1043 user = ui.username()
1044 except util.Abort, e:
1044 except util.Abort, e:
1045 ui.write(" %s\n" % e)
1045 ui.write(" %s\n" % e)
1046 ui.write(_(" (specify a username in your .hgrc file)\n"))
1046 ui.write(_(" (specify a username in your .hgrc file)\n"))
1047 problems += 1
1047 problems += 1
1048
1048
1049 if not problems:
1049 if not problems:
1050 ui.status(_("No problems detected\n"))
1050 ui.status(_("No problems detected\n"))
1051 else:
1051 else:
1052 ui.write(_("%s problems detected,"
1052 ui.write(_("%s problems detected,"
1053 " please check your install!\n") % problems)
1053 " please check your install!\n") % problems)
1054
1054
1055 return problems
1055 return problems
1056
1056
1057 def debugrename(ui, repo, file1, *pats, **opts):
1057 def debugrename(ui, repo, file1, *pats, **opts):
1058 """dump rename information"""
1058 """dump rename information"""
1059
1059
1060 ctx = repo[opts.get('rev')]
1060 ctx = repo[opts.get('rev')]
1061 m = cmdutil.match(repo, (file1,) + pats, opts)
1061 m = cmdutil.match(repo, (file1,) + pats, opts)
1062 for abs in ctx.walk(m):
1062 for abs in ctx.walk(m):
1063 fctx = ctx[abs]
1063 fctx = ctx[abs]
1064 o = fctx.filelog().renamed(fctx.filenode())
1064 o = fctx.filelog().renamed(fctx.filenode())
1065 rel = m.rel(abs)
1065 rel = m.rel(abs)
1066 if o:
1066 if o:
1067 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1067 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1068 else:
1068 else:
1069 ui.write(_("%s not renamed\n") % rel)
1069 ui.write(_("%s not renamed\n") % rel)
1070
1070
1071 def debugwalk(ui, repo, *pats, **opts):
1071 def debugwalk(ui, repo, *pats, **opts):
1072 """show how files match on given patterns"""
1072 """show how files match on given patterns"""
1073 m = cmdutil.match(repo, pats, opts)
1073 m = cmdutil.match(repo, pats, opts)
1074 items = list(repo.walk(m))
1074 items = list(repo.walk(m))
1075 if not items:
1075 if not items:
1076 return
1076 return
1077 fmt = 'f %%-%ds %%-%ds %%s' % (
1077 fmt = 'f %%-%ds %%-%ds %%s' % (
1078 max([len(abs) for abs in items]),
1078 max([len(abs) for abs in items]),
1079 max([len(m.rel(abs)) for abs in items]))
1079 max([len(m.rel(abs)) for abs in items]))
1080 for abs in items:
1080 for abs in items:
1081 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1081 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1082 ui.write("%s\n" % line.rstrip())
1082 ui.write("%s\n" % line.rstrip())
1083
1083
1084 def diff(ui, repo, *pats, **opts):
1084 def diff(ui, repo, *pats, **opts):
1085 """diff repository (or selected files)
1085 """diff repository (or selected files)
1086
1086
1087 Show differences between revisions for the specified files.
1087 Show differences between revisions for the specified files.
1088
1088
1089 Differences between files are shown using the unified diff format.
1089 Differences between files are shown using the unified diff format.
1090
1090
1091 NOTE: diff may generate unexpected results for merges, as it will
1091 NOTE: diff may generate unexpected results for merges, as it will
1092 default to comparing against the working directory's first parent
1092 default to comparing against the working directory's first parent
1093 changeset if no revisions are specified.
1093 changeset if no revisions are specified.
1094
1094
1095 When two revision arguments are given, then changes are shown
1095 When two revision arguments are given, then changes are shown
1096 between those revisions. If only one revision is specified then
1096 between those revisions. If only one revision is specified then
1097 that revision is compared to the working directory, and, when no
1097 that revision is compared to the working directory, and, when no
1098 revisions are specified, the working directory files are compared
1098 revisions are specified, the working directory files are compared
1099 to its parent.
1099 to its parent.
1100
1100
1101 Without the -a/--text option, diff will avoid generating diffs of
1101 Without the -a/--text option, diff will avoid generating diffs of
1102 files it detects as binary. With -a, diff will generate a diff
1102 files it detects as binary. With -a, diff will generate a diff
1103 anyway, probably with undesirable results.
1103 anyway, probably with undesirable results.
1104
1104
1105 Use the -g/--git option to generate diffs in the git extended diff
1105 Use the -g/--git option to generate diffs in the git extended diff
1106 format. For more information, read 'hg help diffs'.
1106 format. For more information, read 'hg help diffs'.
1107 """
1107 """
1108
1108
1109 revs = opts.get('rev')
1109 revs = opts.get('rev')
1110 change = opts.get('change')
1110 change = opts.get('change')
1111 stat = opts.get('stat')
1111 stat = opts.get('stat')
1112 reverse = opts.get('reverse')
1112 reverse = opts.get('reverse')
1113
1113
1114 if revs and change:
1114 if revs and change:
1115 msg = _('cannot specify --rev and --change at the same time')
1115 msg = _('cannot specify --rev and --change at the same time')
1116 raise util.Abort(msg)
1116 raise util.Abort(msg)
1117 elif change:
1117 elif change:
1118 node2 = repo.lookup(change)
1118 node2 = repo.lookup(change)
1119 node1 = repo[node2].parents()[0].node()
1119 node1 = repo[node2].parents()[0].node()
1120 else:
1120 else:
1121 node1, node2 = cmdutil.revpair(repo, revs)
1121 node1, node2 = cmdutil.revpair(repo, revs)
1122
1122
1123 if reverse:
1123 if reverse:
1124 node1, node2 = node2, node1
1124 node1, node2 = node2, node1
1125
1125
1126 if stat:
1126 if stat:
1127 opts['unified'] = '0'
1127 opts['unified'] = '0'
1128 diffopts = patch.diffopts(ui, opts)
1128 diffopts = patch.diffopts(ui, opts)
1129
1129
1130 m = cmdutil.match(repo, pats, opts)
1130 m = cmdutil.match(repo, pats, opts)
1131 it = patch.diff(repo, node1, node2, match=m, opts=diffopts)
1131 it = patch.diff(repo, node1, node2, match=m, opts=diffopts)
1132 if stat:
1132 if stat:
1133 width = ui.interactive() and util.termwidth() or 80
1133 width = ui.interactive() and util.termwidth() or 80
1134 ui.write(patch.diffstat(util.iterlines(it), width=width,
1134 ui.write(patch.diffstat(util.iterlines(it), width=width,
1135 git=diffopts.git))
1135 git=diffopts.git))
1136 else:
1136 else:
1137 for chunk in it:
1137 for chunk in it:
1138 ui.write(chunk)
1138 ui.write(chunk)
1139
1139
1140 def export(ui, repo, *changesets, **opts):
1140 def export(ui, repo, *changesets, **opts):
1141 """dump the header and diffs for one or more changesets
1141 """dump the header and diffs for one or more changesets
1142
1142
1143 Print the changeset header and diffs for one or more revisions.
1143 Print the changeset header and diffs for one or more revisions.
1144
1144
1145 The information shown in the changeset header is: author, date,
1145 The information shown in the changeset header is: author, date,
1146 branch name (if non-default), changeset hash, parent(s) and commit
1146 branch name (if non-default), changeset hash, parent(s) and commit
1147 comment.
1147 comment.
1148
1148
1149 NOTE: export may generate unexpected diff output for merge
1149 NOTE: export may generate unexpected diff output for merge
1150 changesets, as it will compare the merge changeset against its
1150 changesets, as it will compare the merge changeset against its
1151 first parent only.
1151 first parent only.
1152
1152
1153 Output may be to a file, in which case the name of the file is
1153 Output may be to a file, in which case the name of the file is
1154 given using a format string. The formatting rules are as follows:
1154 given using a format string. The formatting rules are as follows:
1155
1155
1156 :``%%``: literal "%" character
1156 :``%%``: literal "%" character
1157 :``%H``: changeset hash (40 bytes of hexadecimal)
1157 :``%H``: changeset hash (40 bytes of hexadecimal)
1158 :``%N``: number of patches being generated
1158 :``%N``: number of patches being generated
1159 :``%R``: changeset revision number
1159 :``%R``: changeset revision number
1160 :``%b``: basename of the exporting repository
1160 :``%b``: basename of the exporting repository
1161 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1161 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1162 :``%n``: zero-padded sequence number, starting at 1
1162 :``%n``: zero-padded sequence number, starting at 1
1163 :``%r``: zero-padded changeset revision number
1163 :``%r``: zero-padded changeset revision number
1164
1164
1165 Without the -a/--text option, export will avoid generating diffs
1165 Without the -a/--text option, export will avoid generating diffs
1166 of files it detects as binary. With -a, export will generate a
1166 of files it detects as binary. With -a, export will generate a
1167 diff anyway, probably with undesirable results.
1167 diff anyway, probably with undesirable results.
1168
1168
1169 Use the -g/--git option to generate diffs in the git extended diff
1169 Use the -g/--git option to generate diffs in the git extended diff
1170 format. See 'hg help diffs' for more information.
1170 format. See 'hg help diffs' for more information.
1171
1171
1172 With the --switch-parent option, the diff will be against the
1172 With the --switch-parent option, the diff will be against the
1173 second parent. It can be useful to review a merge.
1173 second parent. It can be useful to review a merge.
1174 """
1174 """
1175 changesets += tuple(opts.get('rev', []))
1175 changesets += tuple(opts.get('rev', []))
1176 if not changesets:
1176 if not changesets:
1177 raise util.Abort(_("export requires at least one changeset"))
1177 raise util.Abort(_("export requires at least one changeset"))
1178 revs = cmdutil.revrange(repo, changesets)
1178 revs = cmdutil.revrange(repo, changesets)
1179 if len(revs) > 1:
1179 if len(revs) > 1:
1180 ui.note(_('exporting patches:\n'))
1180 ui.note(_('exporting patches:\n'))
1181 else:
1181 else:
1182 ui.note(_('exporting patch:\n'))
1182 ui.note(_('exporting patch:\n'))
1183 patch.export(repo, revs, template=opts.get('output'),
1183 patch.export(repo, revs, template=opts.get('output'),
1184 switch_parent=opts.get('switch_parent'),
1184 switch_parent=opts.get('switch_parent'),
1185 opts=patch.diffopts(ui, opts))
1185 opts=patch.diffopts(ui, opts))
1186
1186
1187 def forget(ui, repo, *pats, **opts):
1187 def forget(ui, repo, *pats, **opts):
1188 """forget the specified files on the next commit
1188 """forget the specified files on the next commit
1189
1189
1190 Mark the specified files so they will no longer be tracked
1190 Mark the specified files so they will no longer be tracked
1191 after the next commit.
1191 after the next commit.
1192
1192
1193 This only removes files from the current branch, not from the
1193 This only removes files from the current branch, not from the
1194 entire project history, and it does not delete them from the
1194 entire project history, and it does not delete them from the
1195 working directory.
1195 working directory.
1196
1196
1197 To undo a forget before the next commit, see hg add.
1197 To undo a forget before the next commit, see hg add.
1198 """
1198 """
1199
1199
1200 if not pats:
1200 if not pats:
1201 raise util.Abort(_('no files specified'))
1201 raise util.Abort(_('no files specified'))
1202
1202
1203 m = cmdutil.match(repo, pats, opts)
1203 m = cmdutil.match(repo, pats, opts)
1204 s = repo.status(match=m, clean=True)
1204 s = repo.status(match=m, clean=True)
1205 forget = sorted(s[0] + s[1] + s[3] + s[6])
1205 forget = sorted(s[0] + s[1] + s[3] + s[6])
1206
1206
1207 for f in m.files():
1207 for f in m.files():
1208 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1208 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1209 ui.warn(_('not removing %s: file is already untracked\n')
1209 ui.warn(_('not removing %s: file is already untracked\n')
1210 % m.rel(f))
1210 % m.rel(f))
1211
1211
1212 for f in forget:
1212 for f in forget:
1213 if ui.verbose or not m.exact(f):
1213 if ui.verbose or not m.exact(f):
1214 ui.status(_('removing %s\n') % m.rel(f))
1214 ui.status(_('removing %s\n') % m.rel(f))
1215
1215
1216 repo.remove(forget, unlink=False)
1216 repo.remove(forget, unlink=False)
1217
1217
1218 def grep(ui, repo, pattern, *pats, **opts):
1218 def grep(ui, repo, pattern, *pats, **opts):
1219 """search for a pattern in specified files and revisions
1219 """search for a pattern in specified files and revisions
1220
1220
1221 Search revisions of files for a regular expression.
1221 Search revisions of files for a regular expression.
1222
1222
1223 This command behaves differently than Unix grep. It only accepts
1223 This command behaves differently than Unix grep. It only accepts
1224 Python/Perl regexps. It searches repository history, not the
1224 Python/Perl regexps. It searches repository history, not the
1225 working directory. It always prints the revision number in which a
1225 working directory. It always prints the revision number in which a
1226 match appears.
1226 match appears.
1227
1227
1228 By default, grep only prints output for the first revision of a
1228 By default, grep only prints output for the first revision of a
1229 file in which it finds a match. To get it to print every revision
1229 file in which it finds a match. To get it to print every revision
1230 that contains a change in match status ("-" for a match that
1230 that contains a change in match status ("-" for a match that
1231 becomes a non-match, or "+" for a non-match that becomes a match),
1231 becomes a non-match, or "+" for a non-match that becomes a match),
1232 use the --all flag.
1232 use the --all flag.
1233 """
1233 """
1234 reflags = 0
1234 reflags = 0
1235 if opts.get('ignore_case'):
1235 if opts.get('ignore_case'):
1236 reflags |= re.I
1236 reflags |= re.I
1237 try:
1237 try:
1238 regexp = re.compile(pattern, reflags)
1238 regexp = re.compile(pattern, reflags)
1239 except Exception, inst:
1239 except Exception, inst:
1240 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1240 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1241 return None
1241 return None
1242 sep, eol = ':', '\n'
1242 sep, eol = ':', '\n'
1243 if opts.get('print0'):
1243 if opts.get('print0'):
1244 sep = eol = '\0'
1244 sep = eol = '\0'
1245
1245
1246 getfile = util.lrucachefunc(repo.file)
1246 getfile = util.lrucachefunc(repo.file)
1247
1247
1248 def matchlines(body):
1248 def matchlines(body):
1249 begin = 0
1249 begin = 0
1250 linenum = 0
1250 linenum = 0
1251 while True:
1251 while True:
1252 match = regexp.search(body, begin)
1252 match = regexp.search(body, begin)
1253 if not match:
1253 if not match:
1254 break
1254 break
1255 mstart, mend = match.span()
1255 mstart, mend = match.span()
1256 linenum += body.count('\n', begin, mstart) + 1
1256 linenum += body.count('\n', begin, mstart) + 1
1257 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1257 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1258 begin = body.find('\n', mend) + 1 or len(body)
1258 begin = body.find('\n', mend) + 1 or len(body)
1259 lend = begin - 1
1259 lend = begin - 1
1260 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1260 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1261
1261
1262 class linestate(object):
1262 class linestate(object):
1263 def __init__(self, line, linenum, colstart, colend):
1263 def __init__(self, line, linenum, colstart, colend):
1264 self.line = line
1264 self.line = line
1265 self.linenum = linenum
1265 self.linenum = linenum
1266 self.colstart = colstart
1266 self.colstart = colstart
1267 self.colend = colend
1267 self.colend = colend
1268
1268
1269 def __hash__(self):
1269 def __hash__(self):
1270 return hash((self.linenum, self.line))
1270 return hash((self.linenum, self.line))
1271
1271
1272 def __eq__(self, other):
1272 def __eq__(self, other):
1273 return self.line == other.line
1273 return self.line == other.line
1274
1274
1275 matches = {}
1275 matches = {}
1276 copies = {}
1276 copies = {}
1277 def grepbody(fn, rev, body):
1277 def grepbody(fn, rev, body):
1278 matches[rev].setdefault(fn, [])
1278 matches[rev].setdefault(fn, [])
1279 m = matches[rev][fn]
1279 m = matches[rev][fn]
1280 for lnum, cstart, cend, line in matchlines(body):
1280 for lnum, cstart, cend, line in matchlines(body):
1281 s = linestate(line, lnum, cstart, cend)
1281 s = linestate(line, lnum, cstart, cend)
1282 m.append(s)
1282 m.append(s)
1283
1283
1284 def difflinestates(a, b):
1284 def difflinestates(a, b):
1285 sm = difflib.SequenceMatcher(None, a, b)
1285 sm = difflib.SequenceMatcher(None, a, b)
1286 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1286 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1287 if tag == 'insert':
1287 if tag == 'insert':
1288 for i in xrange(blo, bhi):
1288 for i in xrange(blo, bhi):
1289 yield ('+', b[i])
1289 yield ('+', b[i])
1290 elif tag == 'delete':
1290 elif tag == 'delete':
1291 for i in xrange(alo, ahi):
1291 for i in xrange(alo, ahi):
1292 yield ('-', a[i])
1292 yield ('-', a[i])
1293 elif tag == 'replace':
1293 elif tag == 'replace':
1294 for i in xrange(alo, ahi):
1294 for i in xrange(alo, ahi):
1295 yield ('-', a[i])
1295 yield ('-', a[i])
1296 for i in xrange(blo, bhi):
1296 for i in xrange(blo, bhi):
1297 yield ('+', b[i])
1297 yield ('+', b[i])
1298
1298
1299 def display(fn, ctx, pstates, states):
1299 def display(fn, ctx, pstates, states):
1300 rev = ctx.rev()
1300 rev = ctx.rev()
1301 datefunc = ui.quiet and util.shortdate or util.datestr
1301 datefunc = ui.quiet and util.shortdate or util.datestr
1302 found = False
1302 found = False
1303 filerevmatches = {}
1303 filerevmatches = {}
1304 if opts.get('all'):
1304 if opts.get('all'):
1305 iter = difflinestates(pstates, states)
1305 iter = difflinestates(pstates, states)
1306 else:
1306 else:
1307 iter = [('', l) for l in states]
1307 iter = [('', l) for l in states]
1308 for change, l in iter:
1308 for change, l in iter:
1309 cols = [fn, str(rev)]
1309 cols = [fn, str(rev)]
1310 if opts.get('line_number'):
1310 if opts.get('line_number'):
1311 cols.append(str(l.linenum))
1311 cols.append(str(l.linenum))
1312 if opts.get('all'):
1312 if opts.get('all'):
1313 cols.append(change)
1313 cols.append(change)
1314 if opts.get('user'):
1314 if opts.get('user'):
1315 cols.append(ui.shortuser(ctx.user()))
1315 cols.append(ui.shortuser(ctx.user()))
1316 if opts.get('date'):
1316 if opts.get('date'):
1317 cols.append(datefunc(ctx.date()))
1317 cols.append(datefunc(ctx.date()))
1318 if opts.get('files_with_matches'):
1318 if opts.get('files_with_matches'):
1319 c = (fn, rev)
1319 c = (fn, rev)
1320 if c in filerevmatches:
1320 if c in filerevmatches:
1321 continue
1321 continue
1322 filerevmatches[c] = 1
1322 filerevmatches[c] = 1
1323 else:
1323 else:
1324 cols.append(l.line)
1324 cols.append(l.line)
1325 ui.write(sep.join(cols), eol)
1325 ui.write(sep.join(cols), eol)
1326 found = True
1326 found = True
1327 return found
1327 return found
1328
1328
1329 skip = {}
1329 skip = {}
1330 revfiles = {}
1330 revfiles = {}
1331 matchfn = cmdutil.match(repo, pats, opts)
1331 matchfn = cmdutil.match(repo, pats, opts)
1332 found = False
1332 found = False
1333 follow = opts.get('follow')
1333 follow = opts.get('follow')
1334
1334
1335 def prep(ctx, fns):
1335 def prep(ctx, fns):
1336 rev = ctx.rev()
1336 rev = ctx.rev()
1337 pctx = ctx.parents()[0]
1337 pctx = ctx.parents()[0]
1338 parent = pctx.rev()
1338 parent = pctx.rev()
1339 matches.setdefault(rev, {})
1339 matches.setdefault(rev, {})
1340 matches.setdefault(parent, {})
1340 matches.setdefault(parent, {})
1341 files = revfiles.setdefault(rev, [])
1341 files = revfiles.setdefault(rev, [])
1342 for fn in fns:
1342 for fn in fns:
1343 flog = getfile(fn)
1343 flog = getfile(fn)
1344 try:
1344 try:
1345 fnode = ctx.filenode(fn)
1345 fnode = ctx.filenode(fn)
1346 except error.LookupError:
1346 except error.LookupError:
1347 continue
1347 continue
1348
1348
1349 copied = flog.renamed(fnode)
1349 copied = flog.renamed(fnode)
1350 copy = follow and copied and copied[0]
1350 copy = follow and copied and copied[0]
1351 if copy:
1351 if copy:
1352 copies.setdefault(rev, {})[fn] = copy
1352 copies.setdefault(rev, {})[fn] = copy
1353 if fn in skip:
1353 if fn in skip:
1354 if copy:
1354 if copy:
1355 skip[copy] = True
1355 skip[copy] = True
1356 continue
1356 continue
1357 files.append(fn)
1357 files.append(fn)
1358
1358
1359 if fn not in matches[rev]:
1359 if fn not in matches[rev]:
1360 grepbody(fn, rev, flog.read(fnode))
1360 grepbody(fn, rev, flog.read(fnode))
1361
1361
1362 pfn = copy or fn
1362 pfn = copy or fn
1363 if pfn not in matches[parent]:
1363 if pfn not in matches[parent]:
1364 try:
1364 try:
1365 fnode = pctx.filenode(pfn)
1365 fnode = pctx.filenode(pfn)
1366 grepbody(pfn, parent, flog.read(fnode))
1366 grepbody(pfn, parent, flog.read(fnode))
1367 except error.LookupError:
1367 except error.LookupError:
1368 pass
1368 pass
1369
1369
1370 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1370 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1371 rev = ctx.rev()
1371 rev = ctx.rev()
1372 parent = ctx.parents()[0].rev()
1372 parent = ctx.parents()[0].rev()
1373 for fn in sorted(revfiles.get(rev, [])):
1373 for fn in sorted(revfiles.get(rev, [])):
1374 states = matches[rev][fn]
1374 states = matches[rev][fn]
1375 copy = copies.get(rev, {}).get(fn)
1375 copy = copies.get(rev, {}).get(fn)
1376 if fn in skip:
1376 if fn in skip:
1377 if copy:
1377 if copy:
1378 skip[copy] = True
1378 skip[copy] = True
1379 continue
1379 continue
1380 pstates = matches.get(parent, {}).get(copy or fn, [])
1380 pstates = matches.get(parent, {}).get(copy or fn, [])
1381 if pstates or states:
1381 if pstates or states:
1382 r = display(fn, ctx, pstates, states)
1382 r = display(fn, ctx, pstates, states)
1383 found = found or r
1383 found = found or r
1384 if r and not opts.get('all'):
1384 if r and not opts.get('all'):
1385 skip[fn] = True
1385 skip[fn] = True
1386 if copy:
1386 if copy:
1387 skip[copy] = True
1387 skip[copy] = True
1388 del matches[rev]
1388 del matches[rev]
1389 del revfiles[rev]
1389 del revfiles[rev]
1390
1390
1391 def heads(ui, repo, *branchrevs, **opts):
1391 def heads(ui, repo, *branchrevs, **opts):
1392 """show current repository heads or show branch heads
1392 """show current repository heads or show branch heads
1393
1393
1394 With no arguments, show all repository branch heads.
1394 With no arguments, show all repository branch heads.
1395
1395
1396 Repository "heads" are changesets with no child changesets. They are
1396 Repository "heads" are changesets with no child changesets. They are
1397 where development generally takes place and are the usual targets
1397 where development generally takes place and are the usual targets
1398 for update and merge operations. Branch heads are changesets that have
1398 for update and merge operations. Branch heads are changesets that have
1399 no child changeset on the same branch.
1399 no child changeset on the same branch.
1400
1400
1401 If one or more REVs are given, only branch heads on the branches
1401 If one or more REVs are given, only branch heads on the branches
1402 associated with the specified changesets are shown.
1402 associated with the specified changesets are shown.
1403
1403
1404 If -c/--closed is specified, also show branch heads marked closed
1404 If -c/--closed is specified, also show branch heads marked closed
1405 (see hg commit --close-branch).
1405 (see hg commit --close-branch).
1406
1406
1407 If STARTREV is specified, only those heads that are descendants of
1407 If STARTREV is specified, only those heads that are descendants of
1408 STARTREV will be displayed.
1408 STARTREV will be displayed.
1409
1409
1410 If -t/--topo is specified, named branch mechanics will be ignored and only
1410 If -t/--topo is specified, named branch mechanics will be ignored and only
1411 changesets without children will be shown.
1411 changesets without children will be shown.
1412 """
1412 """
1413
1413
1414 if opts.get('rev'):
1414 if opts.get('rev'):
1415 start = repo.lookup(opts['rev'])
1415 start = repo.lookup(opts['rev'])
1416 else:
1416 else:
1417 start = None
1417 start = None
1418
1418
1419 if opts.get('topo'):
1419 if opts.get('topo'):
1420 heads = [repo[h] for h in repo.heads(start)]
1420 heads = [repo[h] for h in repo.heads(start)]
1421 else:
1421 else:
1422 heads = []
1422 heads = []
1423 for b, ls in repo.branchmap().iteritems():
1423 for b, ls in repo.branchmap().iteritems():
1424 if start is None:
1424 if start is None:
1425 heads += [repo[h] for h in ls]
1425 heads += [repo[h] for h in ls]
1426 continue
1426 continue
1427 startrev = repo.changelog.rev(start)
1427 startrev = repo.changelog.rev(start)
1428 descendants = set(repo.changelog.descendants(startrev))
1428 descendants = set(repo.changelog.descendants(startrev))
1429 descendants.add(startrev)
1429 descendants.add(startrev)
1430 rev = repo.changelog.rev
1430 rev = repo.changelog.rev
1431 heads += [repo[h] for h in ls if rev(h) in descendants]
1431 heads += [repo[h] for h in ls if rev(h) in descendants]
1432
1432
1433 if branchrevs:
1433 if branchrevs:
1434 decode, encode = encoding.fromlocal, encoding.tolocal
1434 decode, encode = encoding.fromlocal, encoding.tolocal
1435 branches = set(repo[decode(br)].branch() for br in branchrevs)
1435 branches = set(repo[decode(br)].branch() for br in branchrevs)
1436 heads = [h for h in heads if h.branch() in branches]
1436 heads = [h for h in heads if h.branch() in branches]
1437
1437
1438 if not opts.get('closed'):
1438 if not opts.get('closed'):
1439 heads = [h for h in heads if not h.extra().get('close')]
1439 heads = [h for h in heads if not h.extra().get('close')]
1440
1440
1441 if opts.get('active') and branchrevs:
1441 if opts.get('active') and branchrevs:
1442 dagheads = repo.heads(start)
1442 dagheads = repo.heads(start)
1443 heads = [h for h in heads if h.node() in dagheads]
1443 heads = [h for h in heads if h.node() in dagheads]
1444
1444
1445 if branchrevs:
1445 if branchrevs:
1446 haveheads = set(h.branch() for h in heads)
1446 haveheads = set(h.branch() for h in heads)
1447 if branches - haveheads:
1447 if branches - haveheads:
1448 headless = ', '.join(encode(b) for b in branches - haveheads)
1448 headless = ', '.join(encode(b) for b in branches - haveheads)
1449 msg = _('no open branch heads found on branches %s')
1449 msg = _('no open branch heads found on branches %s')
1450 if opts.get('rev'):
1450 if opts.get('rev'):
1451 msg += _(' (started at %s)' % opts['rev'])
1451 msg += _(' (started at %s)' % opts['rev'])
1452 ui.warn((msg + '\n') % headless)
1452 ui.warn((msg + '\n') % headless)
1453
1453
1454 if not heads:
1454 if not heads:
1455 return 1
1455 return 1
1456
1456
1457 heads = sorted(heads, key=lambda x: -x.rev())
1457 heads = sorted(heads, key=lambda x: -x.rev())
1458 displayer = cmdutil.show_changeset(ui, repo, opts)
1458 displayer = cmdutil.show_changeset(ui, repo, opts)
1459 for ctx in heads:
1459 for ctx in heads:
1460 displayer.show(ctx)
1460 displayer.show(ctx)
1461 displayer.close()
1461 displayer.close()
1462
1462
1463 def help_(ui, name=None, with_version=False, unknowncmd=False):
1463 def help_(ui, name=None, with_version=False, unknowncmd=False):
1464 """show help for a given topic or a help overview
1464 """show help for a given topic or a help overview
1465
1465
1466 With no arguments, print a list of commands with short help messages.
1466 With no arguments, print a list of commands with short help messages.
1467
1467
1468 Given a topic, extension, or command name, print help for that
1468 Given a topic, extension, or command name, print help for that
1469 topic."""
1469 topic."""
1470 option_lists = []
1470 option_lists = []
1471 textwidth = util.termwidth() - 2
1471 textwidth = util.termwidth() - 2
1472
1472
1473 def addglobalopts(aliases):
1473 def addglobalopts(aliases):
1474 if ui.verbose:
1474 if ui.verbose:
1475 option_lists.append((_("global options:"), globalopts))
1475 option_lists.append((_("global options:"), globalopts))
1476 if name == 'shortlist':
1476 if name == 'shortlist':
1477 option_lists.append((_('use "hg help" for the full list '
1477 option_lists.append((_('use "hg help" for the full list '
1478 'of commands'), ()))
1478 'of commands'), ()))
1479 else:
1479 else:
1480 if name == 'shortlist':
1480 if name == 'shortlist':
1481 msg = _('use "hg help" for the full list of commands '
1481 msg = _('use "hg help" for the full list of commands '
1482 'or "hg -v" for details')
1482 'or "hg -v" for details')
1483 elif aliases:
1483 elif aliases:
1484 msg = _('use "hg -v help%s" to show aliases and '
1484 msg = _('use "hg -v help%s" to show aliases and '
1485 'global options') % (name and " " + name or "")
1485 'global options') % (name and " " + name or "")
1486 else:
1486 else:
1487 msg = _('use "hg -v help %s" to show global options') % name
1487 msg = _('use "hg -v help %s" to show global options') % name
1488 option_lists.append((msg, ()))
1488 option_lists.append((msg, ()))
1489
1489
1490 def helpcmd(name):
1490 def helpcmd(name):
1491 if with_version:
1491 if with_version:
1492 version_(ui)
1492 version_(ui)
1493 ui.write('\n')
1493 ui.write('\n')
1494
1494
1495 try:
1495 try:
1496 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1496 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1497 except error.AmbiguousCommand, inst:
1497 except error.AmbiguousCommand, inst:
1498 # py3k fix: except vars can't be used outside the scope of the
1498 # py3k fix: except vars can't be used outside the scope of the
1499 # except block, nor can be used inside a lambda. python issue4617
1499 # except block, nor can be used inside a lambda. python issue4617
1500 prefix = inst.args[0]
1500 prefix = inst.args[0]
1501 select = lambda c: c.lstrip('^').startswith(prefix)
1501 select = lambda c: c.lstrip('^').startswith(prefix)
1502 helplist(_('list of commands:\n\n'), select)
1502 helplist(_('list of commands:\n\n'), select)
1503 return
1503 return
1504
1504
1505 # check if it's an invalid alias and display its error if it is
1505 # check if it's an invalid alias and display its error if it is
1506 if getattr(entry[0], 'badalias', False):
1506 if getattr(entry[0], 'badalias', False):
1507 if not unknowncmd:
1507 if not unknowncmd:
1508 entry[0](ui)
1508 entry[0](ui)
1509 return
1509 return
1510
1510
1511 # synopsis
1511 # synopsis
1512 if len(entry) > 2:
1512 if len(entry) > 2:
1513 if entry[2].startswith('hg'):
1513 if entry[2].startswith('hg'):
1514 ui.write("%s\n" % entry[2])
1514 ui.write("%s\n" % entry[2])
1515 else:
1515 else:
1516 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1516 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1517 else:
1517 else:
1518 ui.write('hg %s\n' % aliases[0])
1518 ui.write('hg %s\n' % aliases[0])
1519
1519
1520 # aliases
1520 # aliases
1521 if not ui.quiet and len(aliases) > 1:
1521 if not ui.quiet and len(aliases) > 1:
1522 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1522 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1523
1523
1524 # description
1524 # description
1525 doc = gettext(entry[0].__doc__)
1525 doc = gettext(entry[0].__doc__)
1526 if not doc:
1526 if not doc:
1527 doc = _("(no help text available)")
1527 doc = _("(no help text available)")
1528 if ui.quiet:
1528 if ui.quiet:
1529 doc = doc.splitlines()[0]
1529 doc = doc.splitlines()[0]
1530 ui.write("\n%s\n" % minirst.format(doc, textwidth))
1530 ui.write("\n%s\n" % minirst.format(doc, textwidth))
1531
1531
1532 if not ui.quiet:
1532 if not ui.quiet:
1533 # options
1533 # options
1534 if entry[1]:
1534 if entry[1]:
1535 option_lists.append((_("options:\n"), entry[1]))
1535 option_lists.append((_("options:\n"), entry[1]))
1536
1536
1537 addglobalopts(False)
1537 addglobalopts(False)
1538
1538
1539 def helplist(header, select=None):
1539 def helplist(header, select=None):
1540 h = {}
1540 h = {}
1541 cmds = {}
1541 cmds = {}
1542 for c, e in table.iteritems():
1542 for c, e in table.iteritems():
1543 f = c.split("|", 1)[0]
1543 f = c.split("|", 1)[0]
1544 if select and not select(f):
1544 if select and not select(f):
1545 continue
1545 continue
1546 if (not select and name != 'shortlist' and
1546 if (not select and name != 'shortlist' and
1547 e[0].__module__ != __name__):
1547 e[0].__module__ != __name__):
1548 continue
1548 continue
1549 if name == "shortlist" and not f.startswith("^"):
1549 if name == "shortlist" and not f.startswith("^"):
1550 continue
1550 continue
1551 f = f.lstrip("^")
1551 f = f.lstrip("^")
1552 if not ui.debugflag and f.startswith("debug"):
1552 if not ui.debugflag and f.startswith("debug"):
1553 continue
1553 continue
1554 doc = e[0].__doc__
1554 doc = e[0].__doc__
1555 if doc and 'DEPRECATED' in doc and not ui.verbose:
1555 if doc and 'DEPRECATED' in doc and not ui.verbose:
1556 continue
1556 continue
1557 doc = gettext(doc)
1557 doc = gettext(doc)
1558 if not doc:
1558 if not doc:
1559 doc = _("(no help text available)")
1559 doc = _("(no help text available)")
1560 h[f] = doc.splitlines()[0].rstrip()
1560 h[f] = doc.splitlines()[0].rstrip()
1561 cmds[f] = c.lstrip("^")
1561 cmds[f] = c.lstrip("^")
1562
1562
1563 if not h:
1563 if not h:
1564 ui.status(_('no commands defined\n'))
1564 ui.status(_('no commands defined\n'))
1565 return
1565 return
1566
1566
1567 ui.status(header)
1567 ui.status(header)
1568 fns = sorted(h)
1568 fns = sorted(h)
1569 m = max(map(len, fns))
1569 m = max(map(len, fns))
1570 for f in fns:
1570 for f in fns:
1571 if ui.verbose:
1571 if ui.verbose:
1572 commands = cmds[f].replace("|",", ")
1572 commands = cmds[f].replace("|",", ")
1573 ui.write(" %s:\n %s\n"%(commands, h[f]))
1573 ui.write(" %s:\n %s\n"%(commands, h[f]))
1574 else:
1574 else:
1575 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1575 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1576
1576
1577 if not ui.quiet:
1577 if not ui.quiet:
1578 addglobalopts(True)
1578 addglobalopts(True)
1579
1579
1580 def helptopic(name):
1580 def helptopic(name):
1581 for names, header, doc in help.helptable:
1581 for names, header, doc in help.helptable:
1582 if name in names:
1582 if name in names:
1583 break
1583 break
1584 else:
1584 else:
1585 raise error.UnknownCommand(name)
1585 raise error.UnknownCommand(name)
1586
1586
1587 # description
1587 # description
1588 if not doc:
1588 if not doc:
1589 doc = _("(no help text available)")
1589 doc = _("(no help text available)")
1590 if hasattr(doc, '__call__'):
1590 if hasattr(doc, '__call__'):
1591 doc = doc()
1591 doc = doc()
1592
1592
1593 ui.write("%s\n\n" % header)
1593 ui.write("%s\n\n" % header)
1594 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1594 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1595
1595
1596 def helpext(name):
1596 def helpext(name):
1597 try:
1597 try:
1598 mod = extensions.find(name)
1598 mod = extensions.find(name)
1599 doc = gettext(mod.__doc__) or _('no help text available')
1599 doc = gettext(mod.__doc__) or _('no help text available')
1600 except KeyError:
1600 except KeyError:
1601 mod = None
1601 mod = None
1602 doc = extensions.disabledext(name)
1602 doc = extensions.disabledext(name)
1603 if not doc:
1603 if not doc:
1604 raise error.UnknownCommand(name)
1604 raise error.UnknownCommand(name)
1605
1605
1606 if '\n' not in doc:
1606 if '\n' not in doc:
1607 head, tail = doc, ""
1607 head, tail = doc, ""
1608 else:
1608 else:
1609 head, tail = doc.split('\n', 1)
1609 head, tail = doc.split('\n', 1)
1610 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1610 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1611 if tail:
1611 if tail:
1612 ui.write(minirst.format(tail, textwidth))
1612 ui.write(minirst.format(tail, textwidth))
1613 ui.status('\n\n')
1613 ui.status('\n\n')
1614
1614
1615 if mod:
1615 if mod:
1616 try:
1616 try:
1617 ct = mod.cmdtable
1617 ct = mod.cmdtable
1618 except AttributeError:
1618 except AttributeError:
1619 ct = {}
1619 ct = {}
1620 modcmds = set([c.split('|', 1)[0] for c in ct])
1620 modcmds = set([c.split('|', 1)[0] for c in ct])
1621 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1621 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1622 else:
1622 else:
1623 ui.write(_('use "hg help extensions" for information on enabling '
1623 ui.write(_('use "hg help extensions" for information on enabling '
1624 'extensions\n'))
1624 'extensions\n'))
1625
1625
1626 def helpextcmd(name):
1626 def helpextcmd(name):
1627 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1627 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1628 doc = gettext(mod.__doc__).splitlines()[0]
1628 doc = gettext(mod.__doc__).splitlines()[0]
1629
1629
1630 msg = help.listexts(_("'%s' is provided by the following "
1630 msg = help.listexts(_("'%s' is provided by the following "
1631 "extension:") % cmd, {ext: doc}, len(ext),
1631 "extension:") % cmd, {ext: doc}, len(ext),
1632 indent=4)
1632 indent=4)
1633 ui.write(minirst.format(msg, textwidth))
1633 ui.write(minirst.format(msg, textwidth))
1634 ui.write('\n\n')
1634 ui.write('\n\n')
1635 ui.write(_('use "hg help extensions" for information on enabling '
1635 ui.write(_('use "hg help extensions" for information on enabling '
1636 'extensions\n'))
1636 'extensions\n'))
1637
1637
1638 if name and name != 'shortlist':
1638 if name and name != 'shortlist':
1639 i = None
1639 i = None
1640 if unknowncmd:
1640 if unknowncmd:
1641 queries = (helpextcmd,)
1641 queries = (helpextcmd,)
1642 else:
1642 else:
1643 queries = (helptopic, helpcmd, helpext, helpextcmd)
1643 queries = (helptopic, helpcmd, helpext, helpextcmd)
1644 for f in queries:
1644 for f in queries:
1645 try:
1645 try:
1646 f(name)
1646 f(name)
1647 i = None
1647 i = None
1648 break
1648 break
1649 except error.UnknownCommand, inst:
1649 except error.UnknownCommand, inst:
1650 i = inst
1650 i = inst
1651 if i:
1651 if i:
1652 raise i
1652 raise i
1653
1653
1654 else:
1654 else:
1655 # program name
1655 # program name
1656 if ui.verbose or with_version:
1656 if ui.verbose or with_version:
1657 version_(ui)
1657 version_(ui)
1658 else:
1658 else:
1659 ui.status(_("Mercurial Distributed SCM\n"))
1659 ui.status(_("Mercurial Distributed SCM\n"))
1660 ui.status('\n')
1660 ui.status('\n')
1661
1661
1662 # list of commands
1662 # list of commands
1663 if name == "shortlist":
1663 if name == "shortlist":
1664 header = _('basic commands:\n\n')
1664 header = _('basic commands:\n\n')
1665 else:
1665 else:
1666 header = _('list of commands:\n\n')
1666 header = _('list of commands:\n\n')
1667
1667
1668 helplist(header)
1668 helplist(header)
1669 if name != 'shortlist':
1669 if name != 'shortlist':
1670 exts, maxlength = extensions.enabled()
1670 exts, maxlength = extensions.enabled()
1671 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1671 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1672 if text:
1672 if text:
1673 ui.write("\n%s\n" % minirst.format(text, textwidth))
1673 ui.write("\n%s\n" % minirst.format(text, textwidth))
1674
1674
1675 # list all option lists
1675 # list all option lists
1676 opt_output = []
1676 opt_output = []
1677 for title, options in option_lists:
1677 for title, options in option_lists:
1678 opt_output.append(("\n%s" % title, None))
1678 opt_output.append(("\n%s" % title, None))
1679 for shortopt, longopt, default, desc in options:
1679 for shortopt, longopt, default, desc in options:
1680 if _("DEPRECATED") in desc and not ui.verbose:
1680 if _("DEPRECATED") in desc and not ui.verbose:
1681 continue
1681 continue
1682 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1682 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1683 longopt and " --%s" % longopt),
1683 longopt and " --%s" % longopt),
1684 "%s%s" % (desc,
1684 "%s%s" % (desc,
1685 default
1685 default
1686 and _(" (default: %s)") % default
1686 and _(" (default: %s)") % default
1687 or "")))
1687 or "")))
1688
1688
1689 if not name:
1689 if not name:
1690 ui.write(_("\nadditional help topics:\n\n"))
1690 ui.write(_("\nadditional help topics:\n\n"))
1691 topics = []
1691 topics = []
1692 for names, header, doc in help.helptable:
1692 for names, header, doc in help.helptable:
1693 topics.append((sorted(names, key=len, reverse=True)[0], header))
1693 topics.append((sorted(names, key=len, reverse=True)[0], header))
1694 topics_len = max([len(s[0]) for s in topics])
1694 topics_len = max([len(s[0]) for s in topics])
1695 for t, desc in topics:
1695 for t, desc in topics:
1696 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1696 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1697
1697
1698 if opt_output:
1698 if opt_output:
1699 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1699 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1700 for first, second in opt_output:
1700 for first, second in opt_output:
1701 if second:
1701 if second:
1702 second = util.wrap(second, opts_len + 3)
1702 second = util.wrap(second, opts_len + 3)
1703 ui.write(" %-*s %s\n" % (opts_len, first, second))
1703 ui.write(" %-*s %s\n" % (opts_len, first, second))
1704 else:
1704 else:
1705 ui.write("%s\n" % first)
1705 ui.write("%s\n" % first)
1706
1706
1707 def identify(ui, repo, source=None,
1707 def identify(ui, repo, source=None,
1708 rev=None, num=None, id=None, branch=None, tags=None):
1708 rev=None, num=None, id=None, branch=None, tags=None):
1709 """identify the working copy or specified revision
1709 """identify the working copy or specified revision
1710
1710
1711 With no revision, print a summary of the current state of the
1711 With no revision, print a summary of the current state of the
1712 repository.
1712 repository.
1713
1713
1714 Specifying a path to a repository root or Mercurial bundle will
1714 Specifying a path to a repository root or Mercurial bundle will
1715 cause lookup to operate on that repository/bundle.
1715 cause lookup to operate on that repository/bundle.
1716
1716
1717 This summary identifies the repository state using one or two
1717 This summary identifies the repository state using one or two
1718 parent hash identifiers, followed by a "+" if there are
1718 parent hash identifiers, followed by a "+" if there are
1719 uncommitted changes in the working directory, a list of tags for
1719 uncommitted changes in the working directory, a list of tags for
1720 this revision and a branch name for non-default branches.
1720 this revision and a branch name for non-default branches.
1721 """
1721 """
1722
1722
1723 if not repo and not source:
1723 if not repo and not source:
1724 raise util.Abort(_("There is no Mercurial repository here "
1724 raise util.Abort(_("There is no Mercurial repository here "
1725 "(.hg not found)"))
1725 "(.hg not found)"))
1726
1726
1727 hexfunc = ui.debugflag and hex or short
1727 hexfunc = ui.debugflag and hex or short
1728 default = not (num or id or branch or tags)
1728 default = not (num or id or branch or tags)
1729 output = []
1729 output = []
1730
1730
1731 revs = []
1731 revs = []
1732 if source:
1732 if source:
1733 source, branches = hg.parseurl(ui.expandpath(source))
1733 source, branches = hg.parseurl(ui.expandpath(source))
1734 repo = hg.repository(ui, source)
1734 repo = hg.repository(ui, source)
1735 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1735 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1736
1736
1737 if not repo.local():
1737 if not repo.local():
1738 if not rev and revs:
1738 if not rev and revs:
1739 rev = revs[0]
1739 rev = revs[0]
1740 if not rev:
1740 if not rev:
1741 rev = "tip"
1741 rev = "tip"
1742 if num or branch or tags:
1742 if num or branch or tags:
1743 raise util.Abort(
1743 raise util.Abort(
1744 "can't query remote revision number, branch, or tags")
1744 "can't query remote revision number, branch, or tags")
1745 output = [hexfunc(repo.lookup(rev))]
1745 output = [hexfunc(repo.lookup(rev))]
1746 elif not rev:
1746 elif not rev:
1747 ctx = repo[None]
1747 ctx = repo[None]
1748 parents = ctx.parents()
1748 parents = ctx.parents()
1749 changed = False
1749 changed = False
1750 if default or id or num:
1750 if default or id or num:
1751 changed = ctx.files() + ctx.deleted()
1751 changed = ctx.files() + ctx.deleted()
1752 if default or id:
1752 if default or id:
1753 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1753 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1754 (changed) and "+" or "")]
1754 (changed) and "+" or "")]
1755 if num:
1755 if num:
1756 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1756 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1757 (changed) and "+" or ""))
1757 (changed) and "+" or ""))
1758 else:
1758 else:
1759 ctx = repo[rev]
1759 ctx = repo[rev]
1760 if default or id:
1760 if default or id:
1761 output = [hexfunc(ctx.node())]
1761 output = [hexfunc(ctx.node())]
1762 if num:
1762 if num:
1763 output.append(str(ctx.rev()))
1763 output.append(str(ctx.rev()))
1764
1764
1765 if repo.local() and default and not ui.quiet:
1765 if repo.local() and default and not ui.quiet:
1766 b = encoding.tolocal(ctx.branch())
1766 b = encoding.tolocal(ctx.branch())
1767 if b != 'default':
1767 if b != 'default':
1768 output.append("(%s)" % b)
1768 output.append("(%s)" % b)
1769
1769
1770 # multiple tags for a single parent separated by '/'
1770 # multiple tags for a single parent separated by '/'
1771 t = "/".join(ctx.tags())
1771 t = "/".join(ctx.tags())
1772 if t:
1772 if t:
1773 output.append(t)
1773 output.append(t)
1774
1774
1775 if branch:
1775 if branch:
1776 output.append(encoding.tolocal(ctx.branch()))
1776 output.append(encoding.tolocal(ctx.branch()))
1777
1777
1778 if tags:
1778 if tags:
1779 output.extend(ctx.tags())
1779 output.extend(ctx.tags())
1780
1780
1781 ui.write("%s\n" % ' '.join(output))
1781 ui.write("%s\n" % ' '.join(output))
1782
1782
1783 def import_(ui, repo, patch1, *patches, **opts):
1783 def import_(ui, repo, patch1, *patches, **opts):
1784 """import an ordered set of patches
1784 """import an ordered set of patches
1785
1785
1786 Import a list of patches and commit them individually (unless
1786 Import a list of patches and commit them individually (unless
1787 --no-commit is specified).
1787 --no-commit is specified).
1788
1788
1789 If there are outstanding changes in the working directory, import
1789 If there are outstanding changes in the working directory, import
1790 will abort unless given the -f/--force flag.
1790 will abort unless given the -f/--force flag.
1791
1791
1792 You can import a patch straight from a mail message. Even patches
1792 You can import a patch straight from a mail message. Even patches
1793 as attachments work (to use the body part, it must have type
1793 as attachments work (to use the body part, it must have type
1794 text/plain or text/x-patch). From and Subject headers of email
1794 text/plain or text/x-patch). From and Subject headers of email
1795 message are used as default committer and commit message. All
1795 message are used as default committer and commit message. All
1796 text/plain body parts before first diff are added to commit
1796 text/plain body parts before first diff are added to commit
1797 message.
1797 message.
1798
1798
1799 If the imported patch was generated by hg export, user and
1799 If the imported patch was generated by hg export, user and
1800 description from patch override values from message headers and
1800 description from patch override values from message headers and
1801 body. Values given on command line with -m/--message and -u/--user
1801 body. Values given on command line with -m/--message and -u/--user
1802 override these.
1802 override these.
1803
1803
1804 If --exact is specified, import will set the working directory to
1804 If --exact is specified, import will set the working directory to
1805 the parent of each patch before applying it, and will abort if the
1805 the parent of each patch before applying it, and will abort if the
1806 resulting changeset has a different ID than the one recorded in
1806 resulting changeset has a different ID than the one recorded in
1807 the patch. This may happen due to character set problems or other
1807 the patch. This may happen due to character set problems or other
1808 deficiencies in the text patch format.
1808 deficiencies in the text patch format.
1809
1809
1810 With -s/--similarity, hg will attempt to discover renames and
1810 With -s/--similarity, hg will attempt to discover renames and
1811 copies in the patch in the same way as 'addremove'.
1811 copies in the patch in the same way as 'addremove'.
1812
1812
1813 To read a patch from standard input, use "-" as the patch name. If
1813 To read a patch from standard input, use "-" as the patch name. If
1814 a URL is specified, the patch will be downloaded from it.
1814 a URL is specified, the patch will be downloaded from it.
1815 See 'hg help dates' for a list of formats valid for -d/--date.
1815 See 'hg help dates' for a list of formats valid for -d/--date.
1816 """
1816 """
1817 patches = (patch1,) + patches
1817 patches = (patch1,) + patches
1818
1818
1819 date = opts.get('date')
1819 date = opts.get('date')
1820 if date:
1820 if date:
1821 opts['date'] = util.parsedate(date)
1821 opts['date'] = util.parsedate(date)
1822
1822
1823 try:
1823 try:
1824 sim = float(opts.get('similarity') or 0)
1824 sim = float(opts.get('similarity') or 0)
1825 except ValueError:
1825 except ValueError:
1826 raise util.Abort(_('similarity must be a number'))
1826 raise util.Abort(_('similarity must be a number'))
1827 if sim < 0 or sim > 100:
1827 if sim < 0 or sim > 100:
1828 raise util.Abort(_('similarity must be between 0 and 100'))
1828 raise util.Abort(_('similarity must be between 0 and 100'))
1829
1829
1830 if opts.get('exact') or not opts.get('force'):
1830 if opts.get('exact') or not opts.get('force'):
1831 cmdutil.bail_if_changed(repo)
1831 cmdutil.bail_if_changed(repo)
1832
1832
1833 d = opts["base"]
1833 d = opts["base"]
1834 strip = opts["strip"]
1834 strip = opts["strip"]
1835 wlock = lock = None
1835 wlock = lock = None
1836
1836
1837 def tryone(ui, hunk):
1837 def tryone(ui, hunk):
1838 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, hunk)
1838 tmpname, message, user, date, branch, nodeid, p1, p2 = \
1839 patch.extract(ui, hunk)
1839
1840
1840 if not tmpname:
1841 if not tmpname:
1841 return None
1842 return None
1842 commitid = _('to working directory')
1843 commitid = _('to working directory')
1843
1844
1844 try:
1845 try:
1845 cmdline_message = cmdutil.logmessage(opts)
1846 cmdline_message = cmdutil.logmessage(opts)
1846 if cmdline_message:
1847 if cmdline_message:
1847 # pickup the cmdline msg
1848 # pickup the cmdline msg
1848 message = cmdline_message
1849 message = cmdline_message
1849 elif message:
1850 elif message:
1850 # pickup the patch msg
1851 # pickup the patch msg
1851 message = message.strip()
1852 message = message.strip()
1852 else:
1853 else:
1853 # launch the editor
1854 # launch the editor
1854 message = None
1855 message = None
1855 ui.debug('message:\n%s\n' % message)
1856 ui.debug('message:\n%s\n' % message)
1856
1857
1857 wp = repo.parents()
1858 wp = repo.parents()
1858 if opts.get('exact'):
1859 if opts.get('exact'):
1859 if not nodeid or not p1:
1860 if not nodeid or not p1:
1860 raise util.Abort(_('not a Mercurial patch'))
1861 raise util.Abort(_('not a Mercurial patch'))
1861 p1 = repo.lookup(p1)
1862 p1 = repo.lookup(p1)
1862 p2 = repo.lookup(p2 or hex(nullid))
1863 p2 = repo.lookup(p2 or hex(nullid))
1863
1864
1864 if p1 != wp[0].node():
1865 if p1 != wp[0].node():
1865 hg.clean(repo, p1)
1866 hg.clean(repo, p1)
1866 repo.dirstate.setparents(p1, p2)
1867 repo.dirstate.setparents(p1, p2)
1867 elif p2:
1868 elif p2:
1868 try:
1869 try:
1869 p1 = repo.lookup(p1)
1870 p1 = repo.lookup(p1)
1870 p2 = repo.lookup(p2)
1871 p2 = repo.lookup(p2)
1871 if p1 == wp[0].node():
1872 if p1 == wp[0].node():
1872 repo.dirstate.setparents(p1, p2)
1873 repo.dirstate.setparents(p1, p2)
1873 except error.RepoError:
1874 except error.RepoError:
1874 pass
1875 pass
1875 if opts.get('exact') or opts.get('import_branch'):
1876 if opts.get('exact') or opts.get('import_branch'):
1876 repo.dirstate.setbranch(branch or 'default')
1877 repo.dirstate.setbranch(branch or 'default')
1877
1878
1878 files = {}
1879 files = {}
1879 try:
1880 try:
1880 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1881 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1881 files=files, eolmode=None)
1882 files=files, eolmode=None)
1882 finally:
1883 finally:
1883 files = patch.updatedir(ui, repo, files,
1884 files = patch.updatedir(ui, repo, files,
1884 similarity=sim / 100.0)
1885 similarity=sim / 100.0)
1885 if not opts.get('no_commit'):
1886 if not opts.get('no_commit'):
1886 if opts.get('exact'):
1887 if opts.get('exact'):
1887 m = None
1888 m = None
1888 else:
1889 else:
1889 m = cmdutil.matchfiles(repo, files or [])
1890 m = cmdutil.matchfiles(repo, files or [])
1890 n = repo.commit(message, opts.get('user') or user,
1891 n = repo.commit(message, opts.get('user') or user,
1891 opts.get('date') or date, match=m,
1892 opts.get('date') or date, match=m,
1892 editor=cmdutil.commiteditor)
1893 editor=cmdutil.commiteditor)
1893 if opts.get('exact'):
1894 if opts.get('exact'):
1894 if hex(n) != nodeid:
1895 if hex(n) != nodeid:
1895 repo.rollback()
1896 repo.rollback()
1896 raise util.Abort(_('patch is damaged'
1897 raise util.Abort(_('patch is damaged'
1897 ' or loses information'))
1898 ' or loses information'))
1898 # Force a dirstate write so that the next transaction
1899 # Force a dirstate write so that the next transaction
1899 # backups an up-do-date file.
1900 # backups an up-do-date file.
1900 repo.dirstate.write()
1901 repo.dirstate.write()
1901 if n:
1902 if n:
1902 commitid = short(n)
1903 commitid = short(n)
1903
1904
1904 return commitid
1905 return commitid
1905 finally:
1906 finally:
1906 os.unlink(tmpname)
1907 os.unlink(tmpname)
1907
1908
1908 try:
1909 try:
1909 wlock = repo.wlock()
1910 wlock = repo.wlock()
1910 lock = repo.lock()
1911 lock = repo.lock()
1911 lastcommit = None
1912 lastcommit = None
1912 for p in patches:
1913 for p in patches:
1913 pf = os.path.join(d, p)
1914 pf = os.path.join(d, p)
1914
1915
1915 if pf == '-':
1916 if pf == '-':
1916 ui.status(_("applying patch from stdin\n"))
1917 ui.status(_("applying patch from stdin\n"))
1917 pf = sys.stdin
1918 pf = sys.stdin
1918 else:
1919 else:
1919 ui.status(_("applying %s\n") % p)
1920 ui.status(_("applying %s\n") % p)
1920 pf = url.open(ui, pf)
1921 pf = url.open(ui, pf)
1921
1922
1922 haspatch = False
1923 haspatch = False
1923 for hunk in patch.split(pf):
1924 for hunk in patch.split(pf):
1924 commitid = tryone(ui, hunk)
1925 commitid = tryone(ui, hunk)
1925 if commitid:
1926 if commitid:
1926 haspatch = True
1927 haspatch = True
1927 if lastcommit:
1928 if lastcommit:
1928 ui.status(_('applied %s\n') % lastcommit)
1929 ui.status(_('applied %s\n') % lastcommit)
1929 lastcommit = commitid
1930 lastcommit = commitid
1930
1931
1931 if not haspatch:
1932 if not haspatch:
1932 raise util.Abort(_('no diffs found'))
1933 raise util.Abort(_('no diffs found'))
1933
1934
1934 finally:
1935 finally:
1935 release(lock, wlock)
1936 release(lock, wlock)
1936
1937
1937 def incoming(ui, repo, source="default", **opts):
1938 def incoming(ui, repo, source="default", **opts):
1938 """show new changesets found in source
1939 """show new changesets found in source
1939
1940
1940 Show new changesets found in the specified path/URL or the default
1941 Show new changesets found in the specified path/URL or the default
1941 pull location. These are the changesets that would have been pulled
1942 pull location. These are the changesets that would have been pulled
1942 if a pull at the time you issued this command.
1943 if a pull at the time you issued this command.
1943
1944
1944 For remote repository, using --bundle avoids downloading the
1945 For remote repository, using --bundle avoids downloading the
1945 changesets twice if the incoming is followed by a pull.
1946 changesets twice if the incoming is followed by a pull.
1946
1947
1947 See pull for valid source format details.
1948 See pull for valid source format details.
1948 """
1949 """
1949 limit = cmdutil.loglimit(opts)
1950 limit = cmdutil.loglimit(opts)
1950 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
1951 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
1951 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1952 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1952 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1953 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1953 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
1954 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
1954 if revs:
1955 if revs:
1955 revs = [other.lookup(rev) for rev in revs]
1956 revs = [other.lookup(rev) for rev in revs]
1956 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1957 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1957 force=opts["force"])
1958 force=opts["force"])
1958 if not incoming:
1959 if not incoming:
1959 try:
1960 try:
1960 os.unlink(opts["bundle"])
1961 os.unlink(opts["bundle"])
1961 except:
1962 except:
1962 pass
1963 pass
1963 ui.status(_("no changes found\n"))
1964 ui.status(_("no changes found\n"))
1964 return 1
1965 return 1
1965
1966
1966 cleanup = None
1967 cleanup = None
1967 try:
1968 try:
1968 fname = opts["bundle"]
1969 fname = opts["bundle"]
1969 if fname or not other.local():
1970 if fname or not other.local():
1970 # create a bundle (uncompressed if other repo is not local)
1971 # create a bundle (uncompressed if other repo is not local)
1971
1972
1972 if revs is None and other.capable('changegroupsubset'):
1973 if revs is None and other.capable('changegroupsubset'):
1973 revs = rheads
1974 revs = rheads
1974
1975
1975 if revs is None:
1976 if revs is None:
1976 cg = other.changegroup(incoming, "incoming")
1977 cg = other.changegroup(incoming, "incoming")
1977 else:
1978 else:
1978 cg = other.changegroupsubset(incoming, revs, 'incoming')
1979 cg = other.changegroupsubset(incoming, revs, 'incoming')
1979 bundletype = other.local() and "HG10BZ" or "HG10UN"
1980 bundletype = other.local() and "HG10BZ" or "HG10UN"
1980 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1981 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1981 # keep written bundle?
1982 # keep written bundle?
1982 if opts["bundle"]:
1983 if opts["bundle"]:
1983 cleanup = None
1984 cleanup = None
1984 if not other.local():
1985 if not other.local():
1985 # use the created uncompressed bundlerepo
1986 # use the created uncompressed bundlerepo
1986 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1987 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1987
1988
1988 o = other.changelog.nodesbetween(incoming, revs)[0]
1989 o = other.changelog.nodesbetween(incoming, revs)[0]
1989 if opts.get('newest_first'):
1990 if opts.get('newest_first'):
1990 o.reverse()
1991 o.reverse()
1991 displayer = cmdutil.show_changeset(ui, other, opts)
1992 displayer = cmdutil.show_changeset(ui, other, opts)
1992 count = 0
1993 count = 0
1993 for n in o:
1994 for n in o:
1994 if limit is not None and count >= limit:
1995 if limit is not None and count >= limit:
1995 break
1996 break
1996 parents = [p for p in other.changelog.parents(n) if p != nullid]
1997 parents = [p for p in other.changelog.parents(n) if p != nullid]
1997 if opts.get('no_merges') and len(parents) == 2:
1998 if opts.get('no_merges') and len(parents) == 2:
1998 continue
1999 continue
1999 count += 1
2000 count += 1
2000 displayer.show(other[n])
2001 displayer.show(other[n])
2001 displayer.close()
2002 displayer.close()
2002 finally:
2003 finally:
2003 if hasattr(other, 'close'):
2004 if hasattr(other, 'close'):
2004 other.close()
2005 other.close()
2005 if cleanup:
2006 if cleanup:
2006 os.unlink(cleanup)
2007 os.unlink(cleanup)
2007
2008
2008 def init(ui, dest=".", **opts):
2009 def init(ui, dest=".", **opts):
2009 """create a new repository in the given directory
2010 """create a new repository in the given directory
2010
2011
2011 Initialize a new repository in the given directory. If the given
2012 Initialize a new repository in the given directory. If the given
2012 directory does not exist, it will be created.
2013 directory does not exist, it will be created.
2013
2014
2014 If no directory is given, the current directory is used.
2015 If no directory is given, the current directory is used.
2015
2016
2016 It is possible to specify an ``ssh://`` URL as the destination.
2017 It is possible to specify an ``ssh://`` URL as the destination.
2017 See 'hg help urls' for more information.
2018 See 'hg help urls' for more information.
2018 """
2019 """
2019 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
2020 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
2020
2021
2021 def locate(ui, repo, *pats, **opts):
2022 def locate(ui, repo, *pats, **opts):
2022 """locate files matching specific patterns
2023 """locate files matching specific patterns
2023
2024
2024 Print files under Mercurial control in the working directory whose
2025 Print files under Mercurial control in the working directory whose
2025 names match the given patterns.
2026 names match the given patterns.
2026
2027
2027 By default, this command searches all directories in the working
2028 By default, this command searches all directories in the working
2028 directory. To search just the current directory and its
2029 directory. To search just the current directory and its
2029 subdirectories, use "--include .".
2030 subdirectories, use "--include .".
2030
2031
2031 If no patterns are given to match, this command prints the names
2032 If no patterns are given to match, this command prints the names
2032 of all files under Mercurial control in the working directory.
2033 of all files under Mercurial control in the working directory.
2033
2034
2034 If you want to feed the output of this command into the "xargs"
2035 If you want to feed the output of this command into the "xargs"
2035 command, use the -0 option to both this command and "xargs". This
2036 command, use the -0 option to both this command and "xargs". This
2036 will avoid the problem of "xargs" treating single filenames that
2037 will avoid the problem of "xargs" treating single filenames that
2037 contain whitespace as multiple filenames.
2038 contain whitespace as multiple filenames.
2038 """
2039 """
2039 end = opts.get('print0') and '\0' or '\n'
2040 end = opts.get('print0') and '\0' or '\n'
2040 rev = opts.get('rev') or None
2041 rev = opts.get('rev') or None
2041
2042
2042 ret = 1
2043 ret = 1
2043 m = cmdutil.match(repo, pats, opts, default='relglob')
2044 m = cmdutil.match(repo, pats, opts, default='relglob')
2044 m.bad = lambda x, y: False
2045 m.bad = lambda x, y: False
2045 for abs in repo[rev].walk(m):
2046 for abs in repo[rev].walk(m):
2046 if not rev and abs not in repo.dirstate:
2047 if not rev and abs not in repo.dirstate:
2047 continue
2048 continue
2048 if opts.get('fullpath'):
2049 if opts.get('fullpath'):
2049 ui.write(repo.wjoin(abs), end)
2050 ui.write(repo.wjoin(abs), end)
2050 else:
2051 else:
2051 ui.write(((pats and m.rel(abs)) or abs), end)
2052 ui.write(((pats and m.rel(abs)) or abs), end)
2052 ret = 0
2053 ret = 0
2053
2054
2054 return ret
2055 return ret
2055
2056
2056 def log(ui, repo, *pats, **opts):
2057 def log(ui, repo, *pats, **opts):
2057 """show revision history of entire repository or files
2058 """show revision history of entire repository or files
2058
2059
2059 Print the revision history of the specified files or the entire
2060 Print the revision history of the specified files or the entire
2060 project.
2061 project.
2061
2062
2062 File history is shown without following rename or copy history of
2063 File history is shown without following rename or copy history of
2063 files. Use -f/--follow with a filename to follow history across
2064 files. Use -f/--follow with a filename to follow history across
2064 renames and copies. --follow without a filename will only show
2065 renames and copies. --follow without a filename will only show
2065 ancestors or descendants of the starting revision. --follow-first
2066 ancestors or descendants of the starting revision. --follow-first
2066 only follows the first parent of merge revisions.
2067 only follows the first parent of merge revisions.
2067
2068
2068 If no revision range is specified, the default is tip:0 unless
2069 If no revision range is specified, the default is tip:0 unless
2069 --follow is set, in which case the working directory parent is
2070 --follow is set, in which case the working directory parent is
2070 used as the starting revision.
2071 used as the starting revision.
2071
2072
2072 See 'hg help dates' for a list of formats valid for -d/--date.
2073 See 'hg help dates' for a list of formats valid for -d/--date.
2073
2074
2074 By default this command prints revision number and changeset id,
2075 By default this command prints revision number and changeset id,
2075 tags, non-trivial parents, user, date and time, and a summary for
2076 tags, non-trivial parents, user, date and time, and a summary for
2076 each commit. When the -v/--verbose switch is used, the list of
2077 each commit. When the -v/--verbose switch is used, the list of
2077 changed files and full commit message are shown.
2078 changed files and full commit message are shown.
2078
2079
2079 NOTE: log -p/--patch may generate unexpected diff output for merge
2080 NOTE: log -p/--patch may generate unexpected diff output for merge
2080 changesets, as it will only compare the merge changeset against
2081 changesets, as it will only compare the merge changeset against
2081 its first parent. Also, only files different from BOTH parents
2082 its first parent. Also, only files different from BOTH parents
2082 will appear in files:.
2083 will appear in files:.
2083 """
2084 """
2084
2085
2085 matchfn = cmdutil.match(repo, pats, opts)
2086 matchfn = cmdutil.match(repo, pats, opts)
2086 limit = cmdutil.loglimit(opts)
2087 limit = cmdutil.loglimit(opts)
2087 count = 0
2088 count = 0
2088
2089
2089 endrev = None
2090 endrev = None
2090 if opts.get('copies') and opts.get('rev'):
2091 if opts.get('copies') and opts.get('rev'):
2091 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2092 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2092
2093
2093 df = False
2094 df = False
2094 if opts["date"]:
2095 if opts["date"]:
2095 df = util.matchdate(opts["date"])
2096 df = util.matchdate(opts["date"])
2096
2097
2097 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2098 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2098 def prep(ctx, fns):
2099 def prep(ctx, fns):
2099 rev = ctx.rev()
2100 rev = ctx.rev()
2100 parents = [p for p in repo.changelog.parentrevs(rev)
2101 parents = [p for p in repo.changelog.parentrevs(rev)
2101 if p != nullrev]
2102 if p != nullrev]
2102 if opts.get('no_merges') and len(parents) == 2:
2103 if opts.get('no_merges') and len(parents) == 2:
2103 return
2104 return
2104 if opts.get('only_merges') and len(parents) != 2:
2105 if opts.get('only_merges') and len(parents) != 2:
2105 return
2106 return
2106 if opts.get('only_branch') and ctx.branch() not in opts['only_branch']:
2107 if opts.get('only_branch') and ctx.branch() not in opts['only_branch']:
2107 return
2108 return
2108 if df and not df(ctx.date()[0]):
2109 if df and not df(ctx.date()[0]):
2109 return
2110 return
2110 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2111 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2111 return
2112 return
2112 if opts.get('keyword'):
2113 if opts.get('keyword'):
2113 for k in [kw.lower() for kw in opts['keyword']]:
2114 for k in [kw.lower() for kw in opts['keyword']]:
2114 if (k in ctx.user().lower() or
2115 if (k in ctx.user().lower() or
2115 k in ctx.description().lower() or
2116 k in ctx.description().lower() or
2116 k in " ".join(ctx.files()).lower()):
2117 k in " ".join(ctx.files()).lower()):
2117 break
2118 break
2118 else:
2119 else:
2119 return
2120 return
2120
2121
2121 copies = None
2122 copies = None
2122 if opts.get('copies') and rev:
2123 if opts.get('copies') and rev:
2123 copies = []
2124 copies = []
2124 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2125 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2125 for fn in ctx.files():
2126 for fn in ctx.files():
2126 rename = getrenamed(fn, rev)
2127 rename = getrenamed(fn, rev)
2127 if rename:
2128 if rename:
2128 copies.append((fn, rename[0]))
2129 copies.append((fn, rename[0]))
2129
2130
2130 displayer.show(ctx, copies=copies)
2131 displayer.show(ctx, copies=copies)
2131
2132
2132 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2133 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2133 if count == limit:
2134 if count == limit:
2134 break
2135 break
2135 if displayer.flush(ctx.rev()):
2136 if displayer.flush(ctx.rev()):
2136 count += 1
2137 count += 1
2137 displayer.close()
2138 displayer.close()
2138
2139
2139 def manifest(ui, repo, node=None, rev=None):
2140 def manifest(ui, repo, node=None, rev=None):
2140 """output the current or given revision of the project manifest
2141 """output the current or given revision of the project manifest
2141
2142
2142 Print a list of version controlled files for the given revision.
2143 Print a list of version controlled files for the given revision.
2143 If no revision is given, the first parent of the working directory
2144 If no revision is given, the first parent of the working directory
2144 is used, or the null revision if no revision is checked out.
2145 is used, or the null revision if no revision is checked out.
2145
2146
2146 With -v, print file permissions, symlink and executable bits.
2147 With -v, print file permissions, symlink and executable bits.
2147 With --debug, print file revision hashes.
2148 With --debug, print file revision hashes.
2148 """
2149 """
2149
2150
2150 if rev and node:
2151 if rev and node:
2151 raise util.Abort(_("please specify just one revision"))
2152 raise util.Abort(_("please specify just one revision"))
2152
2153
2153 if not node:
2154 if not node:
2154 node = rev
2155 node = rev
2155
2156
2156 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2157 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2157 ctx = repo[node]
2158 ctx = repo[node]
2158 for f in ctx:
2159 for f in ctx:
2159 if ui.debugflag:
2160 if ui.debugflag:
2160 ui.write("%40s " % hex(ctx.manifest()[f]))
2161 ui.write("%40s " % hex(ctx.manifest()[f]))
2161 if ui.verbose:
2162 if ui.verbose:
2162 ui.write(decor[ctx.flags(f)])
2163 ui.write(decor[ctx.flags(f)])
2163 ui.write("%s\n" % f)
2164 ui.write("%s\n" % f)
2164
2165
2165 def merge(ui, repo, node=None, **opts):
2166 def merge(ui, repo, node=None, **opts):
2166 """merge working directory with another revision
2167 """merge working directory with another revision
2167
2168
2168 The current working directory is updated with all changes made in
2169 The current working directory is updated with all changes made in
2169 the requested revision since the last common predecessor revision.
2170 the requested revision since the last common predecessor revision.
2170
2171
2171 Files that changed between either parent are marked as changed for
2172 Files that changed between either parent are marked as changed for
2172 the next commit and a commit must be performed before any further
2173 the next commit and a commit must be performed before any further
2173 updates to the repository are allowed. The next commit will have
2174 updates to the repository are allowed. The next commit will have
2174 two parents.
2175 two parents.
2175
2176
2176 If no revision is specified, the working directory's parent is a
2177 If no revision is specified, the working directory's parent is a
2177 head revision, and the current branch contains exactly one other
2178 head revision, and the current branch contains exactly one other
2178 head, the other head is merged with by default. Otherwise, an
2179 head, the other head is merged with by default. Otherwise, an
2179 explicit revision with which to merge with must be provided.
2180 explicit revision with which to merge with must be provided.
2180 """
2181 """
2181
2182
2182 if opts.get('rev') and node:
2183 if opts.get('rev') and node:
2183 raise util.Abort(_("please specify just one revision"))
2184 raise util.Abort(_("please specify just one revision"))
2184 if not node:
2185 if not node:
2185 node = opts.get('rev')
2186 node = opts.get('rev')
2186
2187
2187 if not node:
2188 if not node:
2188 branch = repo.changectx(None).branch()
2189 branch = repo.changectx(None).branch()
2189 bheads = repo.branchheads(branch)
2190 bheads = repo.branchheads(branch)
2190 if len(bheads) > 2:
2191 if len(bheads) > 2:
2191 ui.warn(_("abort: branch '%s' has %d heads - "
2192 ui.warn(_("abort: branch '%s' has %d heads - "
2192 "please merge with an explicit rev\n")
2193 "please merge with an explicit rev\n")
2193 % (branch, len(bheads)))
2194 % (branch, len(bheads)))
2194 ui.status(_("(run 'hg heads .' to see heads)\n"))
2195 ui.status(_("(run 'hg heads .' to see heads)\n"))
2195 return False
2196 return False
2196
2197
2197 parent = repo.dirstate.parents()[0]
2198 parent = repo.dirstate.parents()[0]
2198 if len(bheads) == 1:
2199 if len(bheads) == 1:
2199 if len(repo.heads()) > 1:
2200 if len(repo.heads()) > 1:
2200 ui.warn(_("abort: branch '%s' has one head - "
2201 ui.warn(_("abort: branch '%s' has one head - "
2201 "please merge with an explicit rev\n" % branch))
2202 "please merge with an explicit rev\n" % branch))
2202 ui.status(_("(run 'hg heads' to see all heads)\n"))
2203 ui.status(_("(run 'hg heads' to see all heads)\n"))
2203 return False
2204 return False
2204 msg = _('there is nothing to merge')
2205 msg = _('there is nothing to merge')
2205 if parent != repo.lookup(repo[None].branch()):
2206 if parent != repo.lookup(repo[None].branch()):
2206 msg = _('%s - use "hg update" instead') % msg
2207 msg = _('%s - use "hg update" instead') % msg
2207 raise util.Abort(msg)
2208 raise util.Abort(msg)
2208
2209
2209 if parent not in bheads:
2210 if parent not in bheads:
2210 raise util.Abort(_('working dir not at a head rev - '
2211 raise util.Abort(_('working dir not at a head rev - '
2211 'use "hg update" or merge with an explicit rev'))
2212 'use "hg update" or merge with an explicit rev'))
2212 node = parent == bheads[0] and bheads[-1] or bheads[0]
2213 node = parent == bheads[0] and bheads[-1] or bheads[0]
2213
2214
2214 if opts.get('preview'):
2215 if opts.get('preview'):
2215 p1 = repo['.']
2216 p1 = repo['.']
2216 p2 = repo[node]
2217 p2 = repo[node]
2217 common = p1.ancestor(p2)
2218 common = p1.ancestor(p2)
2218 roots, heads = [common.node()], [p2.node()]
2219 roots, heads = [common.node()], [p2.node()]
2219 displayer = cmdutil.show_changeset(ui, repo, opts)
2220 displayer = cmdutil.show_changeset(ui, repo, opts)
2220 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2221 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2221 if node not in roots:
2222 if node not in roots:
2222 displayer.show(repo[node])
2223 displayer.show(repo[node])
2223 displayer.close()
2224 displayer.close()
2224 return 0
2225 return 0
2225
2226
2226 return hg.merge(repo, node, force=opts.get('force'))
2227 return hg.merge(repo, node, force=opts.get('force'))
2227
2228
2228 def outgoing(ui, repo, dest=None, **opts):
2229 def outgoing(ui, repo, dest=None, **opts):
2229 """show changesets not found in the destination
2230 """show changesets not found in the destination
2230
2231
2231 Show changesets not found in the specified destination repository
2232 Show changesets not found in the specified destination repository
2232 or the default push location. These are the changesets that would
2233 or the default push location. These are the changesets that would
2233 be pushed if a push was requested.
2234 be pushed if a push was requested.
2234
2235
2235 See pull for details of valid destination formats.
2236 See pull for details of valid destination formats.
2236 """
2237 """
2237 limit = cmdutil.loglimit(opts)
2238 limit = cmdutil.loglimit(opts)
2238 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2239 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2239 dest, branches = hg.parseurl(dest, opts.get('branch'))
2240 dest, branches = hg.parseurl(dest, opts.get('branch'))
2240 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2241 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2241 if revs:
2242 if revs:
2242 revs = [repo.lookup(rev) for rev in revs]
2243 revs = [repo.lookup(rev) for rev in revs]
2243
2244
2244 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2245 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2245 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2246 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2246 o = repo.findoutgoing(other, force=opts.get('force'))
2247 o = repo.findoutgoing(other, force=opts.get('force'))
2247 if not o:
2248 if not o:
2248 ui.status(_("no changes found\n"))
2249 ui.status(_("no changes found\n"))
2249 return 1
2250 return 1
2250 o = repo.changelog.nodesbetween(o, revs)[0]
2251 o = repo.changelog.nodesbetween(o, revs)[0]
2251 if opts.get('newest_first'):
2252 if opts.get('newest_first'):
2252 o.reverse()
2253 o.reverse()
2253 displayer = cmdutil.show_changeset(ui, repo, opts)
2254 displayer = cmdutil.show_changeset(ui, repo, opts)
2254 count = 0
2255 count = 0
2255 for n in o:
2256 for n in o:
2256 if limit is not None and count >= limit:
2257 if limit is not None and count >= limit:
2257 break
2258 break
2258 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2259 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2259 if opts.get('no_merges') and len(parents) == 2:
2260 if opts.get('no_merges') and len(parents) == 2:
2260 continue
2261 continue
2261 count += 1
2262 count += 1
2262 displayer.show(repo[n])
2263 displayer.show(repo[n])
2263 displayer.close()
2264 displayer.close()
2264
2265
2265 def parents(ui, repo, file_=None, **opts):
2266 def parents(ui, repo, file_=None, **opts):
2266 """show the parents of the working directory or revision
2267 """show the parents of the working directory or revision
2267
2268
2268 Print the working directory's parent revisions. If a revision is
2269 Print the working directory's parent revisions. If a revision is
2269 given via -r/--rev, the parent of that revision will be printed.
2270 given via -r/--rev, the parent of that revision will be printed.
2270 If a file argument is given, the revision in which the file was
2271 If a file argument is given, the revision in which the file was
2271 last changed (before the working directory revision or the
2272 last changed (before the working directory revision or the
2272 argument to --rev if given) is printed.
2273 argument to --rev if given) is printed.
2273 """
2274 """
2274 rev = opts.get('rev')
2275 rev = opts.get('rev')
2275 if rev:
2276 if rev:
2276 ctx = repo[rev]
2277 ctx = repo[rev]
2277 else:
2278 else:
2278 ctx = repo[None]
2279 ctx = repo[None]
2279
2280
2280 if file_:
2281 if file_:
2281 m = cmdutil.match(repo, (file_,), opts)
2282 m = cmdutil.match(repo, (file_,), opts)
2282 if m.anypats() or len(m.files()) != 1:
2283 if m.anypats() or len(m.files()) != 1:
2283 raise util.Abort(_('can only specify an explicit filename'))
2284 raise util.Abort(_('can only specify an explicit filename'))
2284 file_ = m.files()[0]
2285 file_ = m.files()[0]
2285 filenodes = []
2286 filenodes = []
2286 for cp in ctx.parents():
2287 for cp in ctx.parents():
2287 if not cp:
2288 if not cp:
2288 continue
2289 continue
2289 try:
2290 try:
2290 filenodes.append(cp.filenode(file_))
2291 filenodes.append(cp.filenode(file_))
2291 except error.LookupError:
2292 except error.LookupError:
2292 pass
2293 pass
2293 if not filenodes:
2294 if not filenodes:
2294 raise util.Abort(_("'%s' not found in manifest!") % file_)
2295 raise util.Abort(_("'%s' not found in manifest!") % file_)
2295 fl = repo.file(file_)
2296 fl = repo.file(file_)
2296 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2297 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2297 else:
2298 else:
2298 p = [cp.node() for cp in ctx.parents()]
2299 p = [cp.node() for cp in ctx.parents()]
2299
2300
2300 displayer = cmdutil.show_changeset(ui, repo, opts)
2301 displayer = cmdutil.show_changeset(ui, repo, opts)
2301 for n in p:
2302 for n in p:
2302 if n != nullid:
2303 if n != nullid:
2303 displayer.show(repo[n])
2304 displayer.show(repo[n])
2304 displayer.close()
2305 displayer.close()
2305
2306
2306 def paths(ui, repo, search=None):
2307 def paths(ui, repo, search=None):
2307 """show aliases for remote repositories
2308 """show aliases for remote repositories
2308
2309
2309 Show definition of symbolic path name NAME. If no name is given,
2310 Show definition of symbolic path name NAME. If no name is given,
2310 show definition of all available names.
2311 show definition of all available names.
2311
2312
2312 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2313 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2313 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2314 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2314
2315
2315 See 'hg help urls' for more information.
2316 See 'hg help urls' for more information.
2316 """
2317 """
2317 if search:
2318 if search:
2318 for name, path in ui.configitems("paths"):
2319 for name, path in ui.configitems("paths"):
2319 if name == search:
2320 if name == search:
2320 ui.write("%s\n" % url.hidepassword(path))
2321 ui.write("%s\n" % url.hidepassword(path))
2321 return
2322 return
2322 ui.warn(_("not found!\n"))
2323 ui.warn(_("not found!\n"))
2323 return 1
2324 return 1
2324 else:
2325 else:
2325 for name, path in ui.configitems("paths"):
2326 for name, path in ui.configitems("paths"):
2326 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2327 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2327
2328
2328 def postincoming(ui, repo, modheads, optupdate, checkout):
2329 def postincoming(ui, repo, modheads, optupdate, checkout):
2329 if modheads == 0:
2330 if modheads == 0:
2330 return
2331 return
2331 if optupdate:
2332 if optupdate:
2332 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2333 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2333 return hg.update(repo, checkout)
2334 return hg.update(repo, checkout)
2334 else:
2335 else:
2335 ui.status(_("not updating, since new heads added\n"))
2336 ui.status(_("not updating, since new heads added\n"))
2336 if modheads > 1:
2337 if modheads > 1:
2337 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2338 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2338 else:
2339 else:
2339 ui.status(_("(run 'hg update' to get a working copy)\n"))
2340 ui.status(_("(run 'hg update' to get a working copy)\n"))
2340
2341
2341 def pull(ui, repo, source="default", **opts):
2342 def pull(ui, repo, source="default", **opts):
2342 """pull changes from the specified source
2343 """pull changes from the specified source
2343
2344
2344 Pull changes from a remote repository to a local one.
2345 Pull changes from a remote repository to a local one.
2345
2346
2346 This finds all changes from the repository at the specified path
2347 This finds all changes from the repository at the specified path
2347 or URL and adds them to a local repository (the current one unless
2348 or URL and adds them to a local repository (the current one unless
2348 -R is specified). By default, this does not update the copy of the
2349 -R is specified). By default, this does not update the copy of the
2349 project in the working directory.
2350 project in the working directory.
2350
2351
2351 Use hg incoming if you want to see what would have been added by a
2352 Use hg incoming if you want to see what would have been added by a
2352 pull at the time you issued this command. If you then decide to
2353 pull at the time you issued this command. If you then decide to
2353 added those changes to the repository, you should use pull -r X
2354 added those changes to the repository, you should use pull -r X
2354 where X is the last changeset listed by hg incoming.
2355 where X is the last changeset listed by hg incoming.
2355
2356
2356 If SOURCE is omitted, the 'default' path will be used.
2357 If SOURCE is omitted, the 'default' path will be used.
2357 See 'hg help urls' for more information.
2358 See 'hg help urls' for more information.
2358 """
2359 """
2359 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2360 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2360 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2361 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2361 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2362 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2362 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2363 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2363 if revs:
2364 if revs:
2364 try:
2365 try:
2365 revs = [other.lookup(rev) for rev in revs]
2366 revs = [other.lookup(rev) for rev in revs]
2366 except error.CapabilityError:
2367 except error.CapabilityError:
2367 err = _("Other repository doesn't support revision lookup, "
2368 err = _("Other repository doesn't support revision lookup, "
2368 "so a rev cannot be specified.")
2369 "so a rev cannot be specified.")
2369 raise util.Abort(err)
2370 raise util.Abort(err)
2370
2371
2371 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2372 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2372 if checkout:
2373 if checkout:
2373 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2374 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2374 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2375 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2375
2376
2376 def push(ui, repo, dest=None, **opts):
2377 def push(ui, repo, dest=None, **opts):
2377 """push changes to the specified destination
2378 """push changes to the specified destination
2378
2379
2379 Push changes from the local repository to the specified destination.
2380 Push changes from the local repository to the specified destination.
2380
2381
2381 This is the symmetrical operation for pull. It moves changes from
2382 This is the symmetrical operation for pull. It moves changes from
2382 the current repository to a different one. If the destination is
2383 the current repository to a different one. If the destination is
2383 local this is identical to a pull in that directory from the
2384 local this is identical to a pull in that directory from the
2384 current one.
2385 current one.
2385
2386
2386 By default, push will refuse to run if it detects the result would
2387 By default, push will refuse to run if it detects the result would
2387 increase the number of remote heads. This generally indicates the
2388 increase the number of remote heads. This generally indicates the
2388 user forgot to pull and merge before pushing.
2389 user forgot to pull and merge before pushing.
2389
2390
2390 If -r/--rev is used, the named revision and all its ancestors will
2391 If -r/--rev is used, the named revision and all its ancestors will
2391 be pushed to the remote repository.
2392 be pushed to the remote repository.
2392
2393
2393 Please see 'hg help urls' for important details about ``ssh://``
2394 Please see 'hg help urls' for important details about ``ssh://``
2394 URLs. If DESTINATION is omitted, a default path will be used.
2395 URLs. If DESTINATION is omitted, a default path will be used.
2395 """
2396 """
2396 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2397 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2397 dest, branches = hg.parseurl(dest, opts.get('branch'))
2398 dest, branches = hg.parseurl(dest, opts.get('branch'))
2398 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2399 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2399 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2400 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2400 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2401 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2401 if revs:
2402 if revs:
2402 revs = [repo.lookup(rev) for rev in revs]
2403 revs = [repo.lookup(rev) for rev in revs]
2403
2404
2404 # push subrepos depth-first for coherent ordering
2405 # push subrepos depth-first for coherent ordering
2405 c = repo['']
2406 c = repo['']
2406 subs = c.substate # only repos that are committed
2407 subs = c.substate # only repos that are committed
2407 for s in sorted(subs):
2408 for s in sorted(subs):
2408 c.sub(s).push(opts.get('force'))
2409 c.sub(s).push(opts.get('force'))
2409
2410
2410 r = repo.push(other, opts.get('force'), revs=revs)
2411 r = repo.push(other, opts.get('force'), revs=revs)
2411 return r == 0
2412 return r == 0
2412
2413
2413 def recover(ui, repo):
2414 def recover(ui, repo):
2414 """roll back an interrupted transaction
2415 """roll back an interrupted transaction
2415
2416
2416 Recover from an interrupted commit or pull.
2417 Recover from an interrupted commit or pull.
2417
2418
2418 This command tries to fix the repository status after an
2419 This command tries to fix the repository status after an
2419 interrupted operation. It should only be necessary when Mercurial
2420 interrupted operation. It should only be necessary when Mercurial
2420 suggests it.
2421 suggests it.
2421 """
2422 """
2422 if repo.recover():
2423 if repo.recover():
2423 return hg.verify(repo)
2424 return hg.verify(repo)
2424 return 1
2425 return 1
2425
2426
2426 def remove(ui, repo, *pats, **opts):
2427 def remove(ui, repo, *pats, **opts):
2427 """remove the specified files on the next commit
2428 """remove the specified files on the next commit
2428
2429
2429 Schedule the indicated files for removal from the repository.
2430 Schedule the indicated files for removal from the repository.
2430
2431
2431 This only removes files from the current branch, not from the
2432 This only removes files from the current branch, not from the
2432 entire project history. -A/--after can be used to remove only
2433 entire project history. -A/--after can be used to remove only
2433 files that have already been deleted, -f/--force can be used to
2434 files that have already been deleted, -f/--force can be used to
2434 force deletion, and -Af can be used to remove files from the next
2435 force deletion, and -Af can be used to remove files from the next
2435 revision without deleting them from the working directory.
2436 revision without deleting them from the working directory.
2436
2437
2437 The following table details the behavior of remove for different
2438 The following table details the behavior of remove for different
2438 file states (columns) and option combinations (rows). The file
2439 file states (columns) and option combinations (rows). The file
2439 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2440 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2440 reported by hg status). The actions are Warn, Remove (from branch)
2441 reported by hg status). The actions are Warn, Remove (from branch)
2441 and Delete (from disk)::
2442 and Delete (from disk)::
2442
2443
2443 A C M !
2444 A C M !
2444 none W RD W R
2445 none W RD W R
2445 -f R RD RD R
2446 -f R RD RD R
2446 -A W W W R
2447 -A W W W R
2447 -Af R R R R
2448 -Af R R R R
2448
2449
2449 This command schedules the files to be removed at the next commit.
2450 This command schedules the files to be removed at the next commit.
2450 To undo a remove before that, see hg revert.
2451 To undo a remove before that, see hg revert.
2451 """
2452 """
2452
2453
2453 after, force = opts.get('after'), opts.get('force')
2454 after, force = opts.get('after'), opts.get('force')
2454 if not pats and not after:
2455 if not pats and not after:
2455 raise util.Abort(_('no files specified'))
2456 raise util.Abort(_('no files specified'))
2456
2457
2457 m = cmdutil.match(repo, pats, opts)
2458 m = cmdutil.match(repo, pats, opts)
2458 s = repo.status(match=m, clean=True)
2459 s = repo.status(match=m, clean=True)
2459 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2460 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2460
2461
2461 for f in m.files():
2462 for f in m.files():
2462 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2463 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2463 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2464 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2464
2465
2465 def warn(files, reason):
2466 def warn(files, reason):
2466 for f in files:
2467 for f in files:
2467 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2468 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2468 % (m.rel(f), reason))
2469 % (m.rel(f), reason))
2469
2470
2470 if force:
2471 if force:
2471 remove, forget = modified + deleted + clean, added
2472 remove, forget = modified + deleted + clean, added
2472 elif after:
2473 elif after:
2473 remove, forget = deleted, []
2474 remove, forget = deleted, []
2474 warn(modified + added + clean, _('still exists'))
2475 warn(modified + added + clean, _('still exists'))
2475 else:
2476 else:
2476 remove, forget = deleted + clean, []
2477 remove, forget = deleted + clean, []
2477 warn(modified, _('is modified'))
2478 warn(modified, _('is modified'))
2478 warn(added, _('has been marked for add'))
2479 warn(added, _('has been marked for add'))
2479
2480
2480 for f in sorted(remove + forget):
2481 for f in sorted(remove + forget):
2481 if ui.verbose or not m.exact(f):
2482 if ui.verbose or not m.exact(f):
2482 ui.status(_('removing %s\n') % m.rel(f))
2483 ui.status(_('removing %s\n') % m.rel(f))
2483
2484
2484 repo.forget(forget)
2485 repo.forget(forget)
2485 repo.remove(remove, unlink=not after)
2486 repo.remove(remove, unlink=not after)
2486
2487
2487 def rename(ui, repo, *pats, **opts):
2488 def rename(ui, repo, *pats, **opts):
2488 """rename files; equivalent of copy + remove
2489 """rename files; equivalent of copy + remove
2489
2490
2490 Mark dest as copies of sources; mark sources for deletion. If dest
2491 Mark dest as copies of sources; mark sources for deletion. If dest
2491 is a directory, copies are put in that directory. If dest is a
2492 is a directory, copies are put in that directory. If dest is a
2492 file, there can only be one source.
2493 file, there can only be one source.
2493
2494
2494 By default, this command copies the contents of files as they
2495 By default, this command copies the contents of files as they
2495 exist in the working directory. If invoked with -A/--after, the
2496 exist in the working directory. If invoked with -A/--after, the
2496 operation is recorded, but no copying is performed.
2497 operation is recorded, but no copying is performed.
2497
2498
2498 This command takes effect at the next commit. To undo a rename
2499 This command takes effect at the next commit. To undo a rename
2499 before that, see hg revert.
2500 before that, see hg revert.
2500 """
2501 """
2501 wlock = repo.wlock(False)
2502 wlock = repo.wlock(False)
2502 try:
2503 try:
2503 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2504 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2504 finally:
2505 finally:
2505 wlock.release()
2506 wlock.release()
2506
2507
2507 def resolve(ui, repo, *pats, **opts):
2508 def resolve(ui, repo, *pats, **opts):
2508 """retry file merges from a merge or update
2509 """retry file merges from a merge or update
2509
2510
2510 This command can cleanly retry unresolved file merges using file
2511 This command can cleanly retry unresolved file merges using file
2511 revisions preserved from the last update or merge.
2512 revisions preserved from the last update or merge.
2512
2513
2513 If a conflict is resolved manually, please note that the changes
2514 If a conflict is resolved manually, please note that the changes
2514 will be overwritten if the merge is retried with resolve. The
2515 will be overwritten if the merge is retried with resolve. The
2515 -m/--mark switch should be used to mark the file as resolved.
2516 -m/--mark switch should be used to mark the file as resolved.
2516
2517
2517 You can specify a set of files to operate on, or use the -a/--all
2518 You can specify a set of files to operate on, or use the -a/--all
2518 switch to select all unresolved files.
2519 switch to select all unresolved files.
2519
2520
2520 This command also allows listing resolved files and manually
2521 This command also allows listing resolved files and manually
2521 indicating whether or not files are resolved. All files must be
2522 indicating whether or not files are resolved. All files must be
2522 marked as resolved before a commit is permitted.
2523 marked as resolved before a commit is permitted.
2523
2524
2524 The codes used to show the status of files are::
2525 The codes used to show the status of files are::
2525
2526
2526 U = unresolved
2527 U = unresolved
2527 R = resolved
2528 R = resolved
2528 """
2529 """
2529
2530
2530 all, mark, unmark, show, nostatus = \
2531 all, mark, unmark, show, nostatus = \
2531 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2532 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2532
2533
2533 if (show and (mark or unmark)) or (mark and unmark):
2534 if (show and (mark or unmark)) or (mark and unmark):
2534 raise util.Abort(_("too many options specified"))
2535 raise util.Abort(_("too many options specified"))
2535 if pats and all:
2536 if pats and all:
2536 raise util.Abort(_("can't specify --all and patterns"))
2537 raise util.Abort(_("can't specify --all and patterns"))
2537 if not (all or pats or show or mark or unmark):
2538 if not (all or pats or show or mark or unmark):
2538 raise util.Abort(_('no files or directories specified; '
2539 raise util.Abort(_('no files or directories specified; '
2539 'use --all to remerge all files'))
2540 'use --all to remerge all files'))
2540
2541
2541 ms = merge_.mergestate(repo)
2542 ms = merge_.mergestate(repo)
2542 m = cmdutil.match(repo, pats, opts)
2543 m = cmdutil.match(repo, pats, opts)
2543
2544
2544 for f in ms:
2545 for f in ms:
2545 if m(f):
2546 if m(f):
2546 if show:
2547 if show:
2547 if nostatus:
2548 if nostatus:
2548 ui.write("%s\n" % f)
2549 ui.write("%s\n" % f)
2549 else:
2550 else:
2550 ui.write("%s %s\n" % (ms[f].upper(), f))
2551 ui.write("%s %s\n" % (ms[f].upper(), f))
2551 elif mark:
2552 elif mark:
2552 ms.mark(f, "r")
2553 ms.mark(f, "r")
2553 elif unmark:
2554 elif unmark:
2554 ms.mark(f, "u")
2555 ms.mark(f, "u")
2555 else:
2556 else:
2556 wctx = repo[None]
2557 wctx = repo[None]
2557 mctx = wctx.parents()[-1]
2558 mctx = wctx.parents()[-1]
2558
2559
2559 # backup pre-resolve (merge uses .orig for its own purposes)
2560 # backup pre-resolve (merge uses .orig for its own purposes)
2560 a = repo.wjoin(f)
2561 a = repo.wjoin(f)
2561 util.copyfile(a, a + ".resolve")
2562 util.copyfile(a, a + ".resolve")
2562
2563
2563 # resolve file
2564 # resolve file
2564 ms.resolve(f, wctx, mctx)
2565 ms.resolve(f, wctx, mctx)
2565
2566
2566 # replace filemerge's .orig file with our resolve file
2567 # replace filemerge's .orig file with our resolve file
2567 util.rename(a + ".resolve", a + ".orig")
2568 util.rename(a + ".resolve", a + ".orig")
2568
2569
2569 def revert(ui, repo, *pats, **opts):
2570 def revert(ui, repo, *pats, **opts):
2570 """restore individual files or directories to an earlier state
2571 """restore individual files or directories to an earlier state
2571
2572
2572 (Use update -r to check out earlier revisions, revert does not
2573 (Use update -r to check out earlier revisions, revert does not
2573 change the working directory parents.)
2574 change the working directory parents.)
2574
2575
2575 With no revision specified, revert the named files or directories
2576 With no revision specified, revert the named files or directories
2576 to the contents they had in the parent of the working directory.
2577 to the contents they had in the parent of the working directory.
2577 This restores the contents of the affected files to an unmodified
2578 This restores the contents of the affected files to an unmodified
2578 state and unschedules adds, removes, copies, and renames. If the
2579 state and unschedules adds, removes, copies, and renames. If the
2579 working directory has two parents, you must explicitly specify a
2580 working directory has two parents, you must explicitly specify a
2580 revision.
2581 revision.
2581
2582
2582 Using the -r/--rev option, revert the given files or directories
2583 Using the -r/--rev option, revert the given files or directories
2583 to their contents as of a specific revision. This can be helpful
2584 to their contents as of a specific revision. This can be helpful
2584 to "roll back" some or all of an earlier change. See 'hg help
2585 to "roll back" some or all of an earlier change. See 'hg help
2585 dates' for a list of formats valid for -d/--date.
2586 dates' for a list of formats valid for -d/--date.
2586
2587
2587 Revert modifies the working directory. It does not commit any
2588 Revert modifies the working directory. It does not commit any
2588 changes, or change the parent of the working directory. If you
2589 changes, or change the parent of the working directory. If you
2589 revert to a revision other than the parent of the working
2590 revert to a revision other than the parent of the working
2590 directory, the reverted files will thus appear modified
2591 directory, the reverted files will thus appear modified
2591 afterwards.
2592 afterwards.
2592
2593
2593 If a file has been deleted, it is restored. If the executable mode
2594 If a file has been deleted, it is restored. If the executable mode
2594 of a file was changed, it is reset.
2595 of a file was changed, it is reset.
2595
2596
2596 If names are given, all files matching the names are reverted.
2597 If names are given, all files matching the names are reverted.
2597 If no arguments are given, no files are reverted.
2598 If no arguments are given, no files are reverted.
2598
2599
2599 Modified files are saved with a .orig suffix before reverting.
2600 Modified files are saved with a .orig suffix before reverting.
2600 To disable these backups, use --no-backup.
2601 To disable these backups, use --no-backup.
2601 """
2602 """
2602
2603
2603 if opts["date"]:
2604 if opts["date"]:
2604 if opts["rev"]:
2605 if opts["rev"]:
2605 raise util.Abort(_("you can't specify a revision and a date"))
2606 raise util.Abort(_("you can't specify a revision and a date"))
2606 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2607 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2607
2608
2608 if not pats and not opts.get('all'):
2609 if not pats and not opts.get('all'):
2609 raise util.Abort(_('no files or directories specified; '
2610 raise util.Abort(_('no files or directories specified; '
2610 'use --all to revert the whole repo'))
2611 'use --all to revert the whole repo'))
2611
2612
2612 parent, p2 = repo.dirstate.parents()
2613 parent, p2 = repo.dirstate.parents()
2613 if not opts.get('rev') and p2 != nullid:
2614 if not opts.get('rev') and p2 != nullid:
2614 raise util.Abort(_('uncommitted merge - please provide a '
2615 raise util.Abort(_('uncommitted merge - please provide a '
2615 'specific revision'))
2616 'specific revision'))
2616 ctx = repo[opts.get('rev')]
2617 ctx = repo[opts.get('rev')]
2617 node = ctx.node()
2618 node = ctx.node()
2618 mf = ctx.manifest()
2619 mf = ctx.manifest()
2619 if node == parent:
2620 if node == parent:
2620 pmf = mf
2621 pmf = mf
2621 else:
2622 else:
2622 pmf = None
2623 pmf = None
2623
2624
2624 # need all matching names in dirstate and manifest of target rev,
2625 # need all matching names in dirstate and manifest of target rev,
2625 # so have to walk both. do not print errors if files exist in one
2626 # so have to walk both. do not print errors if files exist in one
2626 # but not other.
2627 # but not other.
2627
2628
2628 names = {}
2629 names = {}
2629
2630
2630 wlock = repo.wlock()
2631 wlock = repo.wlock()
2631 try:
2632 try:
2632 # walk dirstate.
2633 # walk dirstate.
2633
2634
2634 m = cmdutil.match(repo, pats, opts)
2635 m = cmdutil.match(repo, pats, opts)
2635 m.bad = lambda x, y: False
2636 m.bad = lambda x, y: False
2636 for abs in repo.walk(m):
2637 for abs in repo.walk(m):
2637 names[abs] = m.rel(abs), m.exact(abs)
2638 names[abs] = m.rel(abs), m.exact(abs)
2638
2639
2639 # walk target manifest.
2640 # walk target manifest.
2640
2641
2641 def badfn(path, msg):
2642 def badfn(path, msg):
2642 if path in names:
2643 if path in names:
2643 return
2644 return
2644 path_ = path + '/'
2645 path_ = path + '/'
2645 for f in names:
2646 for f in names:
2646 if f.startswith(path_):
2647 if f.startswith(path_):
2647 return
2648 return
2648 ui.warn("%s: %s\n" % (m.rel(path), msg))
2649 ui.warn("%s: %s\n" % (m.rel(path), msg))
2649
2650
2650 m = cmdutil.match(repo, pats, opts)
2651 m = cmdutil.match(repo, pats, opts)
2651 m.bad = badfn
2652 m.bad = badfn
2652 for abs in repo[node].walk(m):
2653 for abs in repo[node].walk(m):
2653 if abs not in names:
2654 if abs not in names:
2654 names[abs] = m.rel(abs), m.exact(abs)
2655 names[abs] = m.rel(abs), m.exact(abs)
2655
2656
2656 m = cmdutil.matchfiles(repo, names)
2657 m = cmdutil.matchfiles(repo, names)
2657 changes = repo.status(match=m)[:4]
2658 changes = repo.status(match=m)[:4]
2658 modified, added, removed, deleted = map(set, changes)
2659 modified, added, removed, deleted = map(set, changes)
2659
2660
2660 # if f is a rename, also revert the source
2661 # if f is a rename, also revert the source
2661 cwd = repo.getcwd()
2662 cwd = repo.getcwd()
2662 for f in added:
2663 for f in added:
2663 src = repo.dirstate.copied(f)
2664 src = repo.dirstate.copied(f)
2664 if src and src not in names and repo.dirstate[src] == 'r':
2665 if src and src not in names and repo.dirstate[src] == 'r':
2665 removed.add(src)
2666 removed.add(src)
2666 names[src] = (repo.pathto(src, cwd), True)
2667 names[src] = (repo.pathto(src, cwd), True)
2667
2668
2668 def removeforget(abs):
2669 def removeforget(abs):
2669 if repo.dirstate[abs] == 'a':
2670 if repo.dirstate[abs] == 'a':
2670 return _('forgetting %s\n')
2671 return _('forgetting %s\n')
2671 return _('removing %s\n')
2672 return _('removing %s\n')
2672
2673
2673 revert = ([], _('reverting %s\n'))
2674 revert = ([], _('reverting %s\n'))
2674 add = ([], _('adding %s\n'))
2675 add = ([], _('adding %s\n'))
2675 remove = ([], removeforget)
2676 remove = ([], removeforget)
2676 undelete = ([], _('undeleting %s\n'))
2677 undelete = ([], _('undeleting %s\n'))
2677
2678
2678 disptable = (
2679 disptable = (
2679 # dispatch table:
2680 # dispatch table:
2680 # file state
2681 # file state
2681 # action if in target manifest
2682 # action if in target manifest
2682 # action if not in target manifest
2683 # action if not in target manifest
2683 # make backup if in target manifest
2684 # make backup if in target manifest
2684 # make backup if not in target manifest
2685 # make backup if not in target manifest
2685 (modified, revert, remove, True, True),
2686 (modified, revert, remove, True, True),
2686 (added, revert, remove, True, False),
2687 (added, revert, remove, True, False),
2687 (removed, undelete, None, False, False),
2688 (removed, undelete, None, False, False),
2688 (deleted, revert, remove, False, False),
2689 (deleted, revert, remove, False, False),
2689 )
2690 )
2690
2691
2691 for abs, (rel, exact) in sorted(names.items()):
2692 for abs, (rel, exact) in sorted(names.items()):
2692 mfentry = mf.get(abs)
2693 mfentry = mf.get(abs)
2693 target = repo.wjoin(abs)
2694 target = repo.wjoin(abs)
2694 def handle(xlist, dobackup):
2695 def handle(xlist, dobackup):
2695 xlist[0].append(abs)
2696 xlist[0].append(abs)
2696 if dobackup and not opts.get('no_backup') and util.lexists(target):
2697 if dobackup and not opts.get('no_backup') and util.lexists(target):
2697 bakname = "%s.orig" % rel
2698 bakname = "%s.orig" % rel
2698 ui.note(_('saving current version of %s as %s\n') %
2699 ui.note(_('saving current version of %s as %s\n') %
2699 (rel, bakname))
2700 (rel, bakname))
2700 if not opts.get('dry_run'):
2701 if not opts.get('dry_run'):
2701 util.copyfile(target, bakname)
2702 util.copyfile(target, bakname)
2702 if ui.verbose or not exact:
2703 if ui.verbose or not exact:
2703 msg = xlist[1]
2704 msg = xlist[1]
2704 if not isinstance(msg, basestring):
2705 if not isinstance(msg, basestring):
2705 msg = msg(abs)
2706 msg = msg(abs)
2706 ui.status(msg % rel)
2707 ui.status(msg % rel)
2707 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2708 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2708 if abs not in table:
2709 if abs not in table:
2709 continue
2710 continue
2710 # file has changed in dirstate
2711 # file has changed in dirstate
2711 if mfentry:
2712 if mfentry:
2712 handle(hitlist, backuphit)
2713 handle(hitlist, backuphit)
2713 elif misslist is not None:
2714 elif misslist is not None:
2714 handle(misslist, backupmiss)
2715 handle(misslist, backupmiss)
2715 break
2716 break
2716 else:
2717 else:
2717 if abs not in repo.dirstate:
2718 if abs not in repo.dirstate:
2718 if mfentry:
2719 if mfentry:
2719 handle(add, True)
2720 handle(add, True)
2720 elif exact:
2721 elif exact:
2721 ui.warn(_('file not managed: %s\n') % rel)
2722 ui.warn(_('file not managed: %s\n') % rel)
2722 continue
2723 continue
2723 # file has not changed in dirstate
2724 # file has not changed in dirstate
2724 if node == parent:
2725 if node == parent:
2725 if exact:
2726 if exact:
2726 ui.warn(_('no changes needed to %s\n') % rel)
2727 ui.warn(_('no changes needed to %s\n') % rel)
2727 continue
2728 continue
2728 if pmf is None:
2729 if pmf is None:
2729 # only need parent manifest in this unlikely case,
2730 # only need parent manifest in this unlikely case,
2730 # so do not read by default
2731 # so do not read by default
2731 pmf = repo[parent].manifest()
2732 pmf = repo[parent].manifest()
2732 if abs in pmf:
2733 if abs in pmf:
2733 if mfentry:
2734 if mfentry:
2734 # if version of file is same in parent and target
2735 # if version of file is same in parent and target
2735 # manifests, do nothing
2736 # manifests, do nothing
2736 if (pmf[abs] != mfentry or
2737 if (pmf[abs] != mfentry or
2737 pmf.flags(abs) != mf.flags(abs)):
2738 pmf.flags(abs) != mf.flags(abs)):
2738 handle(revert, False)
2739 handle(revert, False)
2739 else:
2740 else:
2740 handle(remove, False)
2741 handle(remove, False)
2741
2742
2742 if not opts.get('dry_run'):
2743 if not opts.get('dry_run'):
2743 def checkout(f):
2744 def checkout(f):
2744 fc = ctx[f]
2745 fc = ctx[f]
2745 repo.wwrite(f, fc.data(), fc.flags())
2746 repo.wwrite(f, fc.data(), fc.flags())
2746
2747
2747 audit_path = util.path_auditor(repo.root)
2748 audit_path = util.path_auditor(repo.root)
2748 for f in remove[0]:
2749 for f in remove[0]:
2749 if repo.dirstate[f] == 'a':
2750 if repo.dirstate[f] == 'a':
2750 repo.dirstate.forget(f)
2751 repo.dirstate.forget(f)
2751 continue
2752 continue
2752 audit_path(f)
2753 audit_path(f)
2753 try:
2754 try:
2754 util.unlink(repo.wjoin(f))
2755 util.unlink(repo.wjoin(f))
2755 except OSError:
2756 except OSError:
2756 pass
2757 pass
2757 repo.dirstate.remove(f)
2758 repo.dirstate.remove(f)
2758
2759
2759 normal = None
2760 normal = None
2760 if node == parent:
2761 if node == parent:
2761 # We're reverting to our parent. If possible, we'd like status
2762 # We're reverting to our parent. If possible, we'd like status
2762 # to report the file as clean. We have to use normallookup for
2763 # to report the file as clean. We have to use normallookup for
2763 # merges to avoid losing information about merged/dirty files.
2764 # merges to avoid losing information about merged/dirty files.
2764 if p2 != nullid:
2765 if p2 != nullid:
2765 normal = repo.dirstate.normallookup
2766 normal = repo.dirstate.normallookup
2766 else:
2767 else:
2767 normal = repo.dirstate.normal
2768 normal = repo.dirstate.normal
2768 for f in revert[0]:
2769 for f in revert[0]:
2769 checkout(f)
2770 checkout(f)
2770 if normal:
2771 if normal:
2771 normal(f)
2772 normal(f)
2772
2773
2773 for f in add[0]:
2774 for f in add[0]:
2774 checkout(f)
2775 checkout(f)
2775 repo.dirstate.add(f)
2776 repo.dirstate.add(f)
2776
2777
2777 normal = repo.dirstate.normallookup
2778 normal = repo.dirstate.normallookup
2778 if node == parent and p2 == nullid:
2779 if node == parent and p2 == nullid:
2779 normal = repo.dirstate.normal
2780 normal = repo.dirstate.normal
2780 for f in undelete[0]:
2781 for f in undelete[0]:
2781 checkout(f)
2782 checkout(f)
2782 normal(f)
2783 normal(f)
2783
2784
2784 finally:
2785 finally:
2785 wlock.release()
2786 wlock.release()
2786
2787
2787 def rollback(ui, repo):
2788 def rollback(ui, repo):
2788 """roll back the last transaction
2789 """roll back the last transaction
2789
2790
2790 This command should be used with care. There is only one level of
2791 This command should be used with care. There is only one level of
2791 rollback, and there is no way to undo a rollback. It will also
2792 rollback, and there is no way to undo a rollback. It will also
2792 restore the dirstate at the time of the last transaction, losing
2793 restore the dirstate at the time of the last transaction, losing
2793 any dirstate changes since that time. This command does not alter
2794 any dirstate changes since that time. This command does not alter
2794 the working directory.
2795 the working directory.
2795
2796
2796 Transactions are used to encapsulate the effects of all commands
2797 Transactions are used to encapsulate the effects of all commands
2797 that create new changesets or propagate existing changesets into a
2798 that create new changesets or propagate existing changesets into a
2798 repository. For example, the following commands are transactional,
2799 repository. For example, the following commands are transactional,
2799 and their effects can be rolled back:
2800 and their effects can be rolled back:
2800
2801
2801 - commit
2802 - commit
2802 - import
2803 - import
2803 - pull
2804 - pull
2804 - push (with this repository as the destination)
2805 - push (with this repository as the destination)
2805 - unbundle
2806 - unbundle
2806
2807
2807 This command is not intended for use on public repositories. Once
2808 This command is not intended for use on public repositories. Once
2808 changes are visible for pull by other users, rolling a transaction
2809 changes are visible for pull by other users, rolling a transaction
2809 back locally is ineffective (someone else may already have pulled
2810 back locally is ineffective (someone else may already have pulled
2810 the changes). Furthermore, a race is possible with readers of the
2811 the changes). Furthermore, a race is possible with readers of the
2811 repository; for example an in-progress pull from the repository
2812 repository; for example an in-progress pull from the repository
2812 may fail if a rollback is performed.
2813 may fail if a rollback is performed.
2813 """
2814 """
2814 repo.rollback()
2815 repo.rollback()
2815
2816
2816 def root(ui, repo):
2817 def root(ui, repo):
2817 """print the root (top) of the current working directory
2818 """print the root (top) of the current working directory
2818
2819
2819 Print the root directory of the current repository.
2820 Print the root directory of the current repository.
2820 """
2821 """
2821 ui.write(repo.root + "\n")
2822 ui.write(repo.root + "\n")
2822
2823
2823 def serve(ui, repo, **opts):
2824 def serve(ui, repo, **opts):
2824 """export the repository via HTTP
2825 """export the repository via HTTP
2825
2826
2826 Start a local HTTP repository browser and pull server.
2827 Start a local HTTP repository browser and pull server.
2827
2828
2828 By default, the server logs accesses to stdout and errors to
2829 By default, the server logs accesses to stdout and errors to
2829 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2830 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2830 files.
2831 files.
2831 """
2832 """
2832
2833
2833 if opts["stdio"]:
2834 if opts["stdio"]:
2834 if repo is None:
2835 if repo is None:
2835 raise error.RepoError(_("There is no Mercurial repository here"
2836 raise error.RepoError(_("There is no Mercurial repository here"
2836 " (.hg not found)"))
2837 " (.hg not found)"))
2837 s = sshserver.sshserver(ui, repo)
2838 s = sshserver.sshserver(ui, repo)
2838 s.serve_forever()
2839 s.serve_forever()
2839
2840
2840 baseui = repo and repo.baseui or ui
2841 baseui = repo and repo.baseui or ui
2841 optlist = ("name templates style address port prefix ipv6"
2842 optlist = ("name templates style address port prefix ipv6"
2842 " accesslog errorlog webdir_conf certificate encoding")
2843 " accesslog errorlog webdir_conf certificate encoding")
2843 for o in optlist.split():
2844 for o in optlist.split():
2844 if opts.get(o, None):
2845 if opts.get(o, None):
2845 baseui.setconfig("web", o, str(opts[o]))
2846 baseui.setconfig("web", o, str(opts[o]))
2846 if (repo is not None) and (repo.ui != baseui):
2847 if (repo is not None) and (repo.ui != baseui):
2847 repo.ui.setconfig("web", o, str(opts[o]))
2848 repo.ui.setconfig("web", o, str(opts[o]))
2848
2849
2849 if repo is None and not ui.config("web", "webdir_conf"):
2850 if repo is None and not ui.config("web", "webdir_conf"):
2850 raise error.RepoError(_("There is no Mercurial repository here"
2851 raise error.RepoError(_("There is no Mercurial repository here"
2851 " (.hg not found)"))
2852 " (.hg not found)"))
2852
2853
2853 class service(object):
2854 class service(object):
2854 def init(self):
2855 def init(self):
2855 util.set_signal_handler()
2856 util.set_signal_handler()
2856 self.httpd = server.create_server(baseui, repo)
2857 self.httpd = server.create_server(baseui, repo)
2857
2858
2858 if not ui.verbose:
2859 if not ui.verbose:
2859 return
2860 return
2860
2861
2861 if self.httpd.prefix:
2862 if self.httpd.prefix:
2862 prefix = self.httpd.prefix.strip('/') + '/'
2863 prefix = self.httpd.prefix.strip('/') + '/'
2863 else:
2864 else:
2864 prefix = ''
2865 prefix = ''
2865
2866
2866 port = ':%d' % self.httpd.port
2867 port = ':%d' % self.httpd.port
2867 if port == ':80':
2868 if port == ':80':
2868 port = ''
2869 port = ''
2869
2870
2870 bindaddr = self.httpd.addr
2871 bindaddr = self.httpd.addr
2871 if bindaddr == '0.0.0.0':
2872 if bindaddr == '0.0.0.0':
2872 bindaddr = '*'
2873 bindaddr = '*'
2873 elif ':' in bindaddr: # IPv6
2874 elif ':' in bindaddr: # IPv6
2874 bindaddr = '[%s]' % bindaddr
2875 bindaddr = '[%s]' % bindaddr
2875
2876
2876 fqaddr = self.httpd.fqaddr
2877 fqaddr = self.httpd.fqaddr
2877 if ':' in fqaddr:
2878 if ':' in fqaddr:
2878 fqaddr = '[%s]' % fqaddr
2879 fqaddr = '[%s]' % fqaddr
2879 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2880 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2880 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2881 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2881
2882
2882 def run(self):
2883 def run(self):
2883 self.httpd.serve_forever()
2884 self.httpd.serve_forever()
2884
2885
2885 service = service()
2886 service = service()
2886
2887
2887 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2888 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2888
2889
2889 def status(ui, repo, *pats, **opts):
2890 def status(ui, repo, *pats, **opts):
2890 """show changed files in the working directory
2891 """show changed files in the working directory
2891
2892
2892 Show status of files in the repository. If names are given, only
2893 Show status of files in the repository. If names are given, only
2893 files that match are shown. Files that are clean or ignored or
2894 files that match are shown. Files that are clean or ignored or
2894 the source of a copy/move operation, are not listed unless
2895 the source of a copy/move operation, are not listed unless
2895 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
2896 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
2896 Unless options described with "show only ..." are given, the
2897 Unless options described with "show only ..." are given, the
2897 options -mardu are used.
2898 options -mardu are used.
2898
2899
2899 Option -q/--quiet hides untracked (unknown and ignored) files
2900 Option -q/--quiet hides untracked (unknown and ignored) files
2900 unless explicitly requested with -u/--unknown or -i/--ignored.
2901 unless explicitly requested with -u/--unknown or -i/--ignored.
2901
2902
2902 NOTE: status may appear to disagree with diff if permissions have
2903 NOTE: status may appear to disagree with diff if permissions have
2903 changed or a merge has occurred. The standard diff format does not
2904 changed or a merge has occurred. The standard diff format does not
2904 report permission changes and diff only reports changes relative
2905 report permission changes and diff only reports changes relative
2905 to one merge parent.
2906 to one merge parent.
2906
2907
2907 If one revision is given, it is used as the base revision.
2908 If one revision is given, it is used as the base revision.
2908 If two revisions are given, the differences between them are
2909 If two revisions are given, the differences between them are
2909 shown. The --change option can also be used as a shortcut to list
2910 shown. The --change option can also be used as a shortcut to list
2910 the changed files of a revision from its first parent.
2911 the changed files of a revision from its first parent.
2911
2912
2912 The codes used to show the status of files are::
2913 The codes used to show the status of files are::
2913
2914
2914 M = modified
2915 M = modified
2915 A = added
2916 A = added
2916 R = removed
2917 R = removed
2917 C = clean
2918 C = clean
2918 ! = missing (deleted by non-hg command, but still tracked)
2919 ! = missing (deleted by non-hg command, but still tracked)
2919 ? = not tracked
2920 ? = not tracked
2920 I = ignored
2921 I = ignored
2921 = origin of the previous file listed as A (added)
2922 = origin of the previous file listed as A (added)
2922 """
2923 """
2923
2924
2924 revs = opts.get('rev')
2925 revs = opts.get('rev')
2925 change = opts.get('change')
2926 change = opts.get('change')
2926
2927
2927 if revs and change:
2928 if revs and change:
2928 msg = _('cannot specify --rev and --change at the same time')
2929 msg = _('cannot specify --rev and --change at the same time')
2929 raise util.Abort(msg)
2930 raise util.Abort(msg)
2930 elif change:
2931 elif change:
2931 node2 = repo.lookup(change)
2932 node2 = repo.lookup(change)
2932 node1 = repo[node2].parents()[0].node()
2933 node1 = repo[node2].parents()[0].node()
2933 else:
2934 else:
2934 node1, node2 = cmdutil.revpair(repo, revs)
2935 node1, node2 = cmdutil.revpair(repo, revs)
2935
2936
2936 cwd = (pats and repo.getcwd()) or ''
2937 cwd = (pats and repo.getcwd()) or ''
2937 end = opts.get('print0') and '\0' or '\n'
2938 end = opts.get('print0') and '\0' or '\n'
2938 copy = {}
2939 copy = {}
2939 states = 'modified added removed deleted unknown ignored clean'.split()
2940 states = 'modified added removed deleted unknown ignored clean'.split()
2940 show = [k for k in states if opts.get(k)]
2941 show = [k for k in states if opts.get(k)]
2941 if opts.get('all'):
2942 if opts.get('all'):
2942 show += ui.quiet and (states[:4] + ['clean']) or states
2943 show += ui.quiet and (states[:4] + ['clean']) or states
2943 if not show:
2944 if not show:
2944 show = ui.quiet and states[:4] or states[:5]
2945 show = ui.quiet and states[:4] or states[:5]
2945
2946
2946 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2947 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2947 'ignored' in show, 'clean' in show, 'unknown' in show)
2948 'ignored' in show, 'clean' in show, 'unknown' in show)
2948 changestates = zip(states, 'MAR!?IC', stat)
2949 changestates = zip(states, 'MAR!?IC', stat)
2949
2950
2950 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2951 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2951 ctxn = repo[nullid]
2952 ctxn = repo[nullid]
2952 ctx1 = repo[node1]
2953 ctx1 = repo[node1]
2953 ctx2 = repo[node2]
2954 ctx2 = repo[node2]
2954 added = stat[1]
2955 added = stat[1]
2955 if node2 is None:
2956 if node2 is None:
2956 added = stat[0] + stat[1] # merged?
2957 added = stat[0] + stat[1] # merged?
2957
2958
2958 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2959 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2959 if k in added:
2960 if k in added:
2960 copy[k] = v
2961 copy[k] = v
2961 elif v in added:
2962 elif v in added:
2962 copy[v] = k
2963 copy[v] = k
2963
2964
2964 for state, char, files in changestates:
2965 for state, char, files in changestates:
2965 if state in show:
2966 if state in show:
2966 format = "%s %%s%s" % (char, end)
2967 format = "%s %%s%s" % (char, end)
2967 if opts.get('no_status'):
2968 if opts.get('no_status'):
2968 format = "%%s%s" % end
2969 format = "%%s%s" % end
2969
2970
2970 for f in files:
2971 for f in files:
2971 ui.write(format % repo.pathto(f, cwd))
2972 ui.write(format % repo.pathto(f, cwd))
2972 if f in copy:
2973 if f in copy:
2973 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2974 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2974
2975
2975 def summary(ui, repo, **opts):
2976 def summary(ui, repo, **opts):
2976 """summarize working directory state
2977 """summarize working directory state
2977
2978
2978 This generates a brief summary of the working directory state,
2979 This generates a brief summary of the working directory state,
2979 including parents, branch, commit status, and available updates.
2980 including parents, branch, commit status, and available updates.
2980
2981
2981 With the --remote option, this will check the default paths for
2982 With the --remote option, this will check the default paths for
2982 incoming and outgoing changes. This can be time-consuming.
2983 incoming and outgoing changes. This can be time-consuming.
2983 """
2984 """
2984
2985
2985 ctx = repo[None]
2986 ctx = repo[None]
2986 parents = ctx.parents()
2987 parents = ctx.parents()
2987 pnode = parents[0].node()
2988 pnode = parents[0].node()
2988 tags = repo.tags()
2989 tags = repo.tags()
2989
2990
2990 for p in parents:
2991 for p in parents:
2991 t = ' '.join([t for t in tags if tags[t] == p.node()])
2992 t = ' '.join([t for t in tags if tags[t] == p.node()])
2992 if p.rev() == -1:
2993 if p.rev() == -1:
2993 if not len(repo):
2994 if not len(repo):
2994 t += _(' (empty repository)')
2995 t += _(' (empty repository)')
2995 else:
2996 else:
2996 t += _(' (no revision checked out)')
2997 t += _(' (no revision checked out)')
2997 ui.write(_('parent: %d:%s %s\n') % (p.rev(), str(p), t))
2998 ui.write(_('parent: %d:%s %s\n') % (p.rev(), str(p), t))
2998 if p.description():
2999 if p.description():
2999 ui.status(' ' + p.description().splitlines()[0].strip() + '\n')
3000 ui.status(' ' + p.description().splitlines()[0].strip() + '\n')
3000
3001
3001 branch = ctx.branch()
3002 branch = ctx.branch()
3002 bheads = repo.branchheads(branch)
3003 bheads = repo.branchheads(branch)
3003 m = _('branch: %s\n') % branch
3004 m = _('branch: %s\n') % branch
3004 if branch != 'default':
3005 if branch != 'default':
3005 ui.write(m)
3006 ui.write(m)
3006 else:
3007 else:
3007 ui.status(m)
3008 ui.status(m)
3008
3009
3009 st = list(repo.status(unknown=True))[:6]
3010 st = list(repo.status(unknown=True))[:6]
3010 ms = merge_.mergestate(repo)
3011 ms = merge_.mergestate(repo)
3011 st.append([f for f in ms if ms[f] == 'u'])
3012 st.append([f for f in ms if ms[f] == 'u'])
3012 labels = [_('%d modified'), _('%d added'), _('%d removed'),
3013 labels = [_('%d modified'), _('%d added'), _('%d removed'),
3013 _('%d deleted'), _('%d unknown'), _('%d ignored'),
3014 _('%d deleted'), _('%d unknown'), _('%d ignored'),
3014 _('%d unresolved')]
3015 _('%d unresolved')]
3015 t = []
3016 t = []
3016 for s, l in zip(st, labels):
3017 for s, l in zip(st, labels):
3017 if s:
3018 if s:
3018 t.append(l % len(s))
3019 t.append(l % len(s))
3019
3020
3020 t = ', '.join(t)
3021 t = ', '.join(t)
3021 cleanworkdir = False
3022 cleanworkdir = False
3022
3023
3023 if len(parents) > 1:
3024 if len(parents) > 1:
3024 t += _(' (merge)')
3025 t += _(' (merge)')
3025 elif branch != parents[0].branch():
3026 elif branch != parents[0].branch():
3026 t += _(' (new branch)')
3027 t += _(' (new branch)')
3027 elif (not st[0] and not st[1] and not st[2]):
3028 elif (not st[0] and not st[1] and not st[2]):
3028 t += _(' (clean)')
3029 t += _(' (clean)')
3029 cleanworkdir = True
3030 cleanworkdir = True
3030 elif pnode not in bheads:
3031 elif pnode not in bheads:
3031 t += _(' (new branch head)')
3032 t += _(' (new branch head)')
3032
3033
3033 if cleanworkdir:
3034 if cleanworkdir:
3034 ui.status(_('commit: %s\n') % t.strip())
3035 ui.status(_('commit: %s\n') % t.strip())
3035 else:
3036 else:
3036 ui.write(_('commit: %s\n') % t.strip())
3037 ui.write(_('commit: %s\n') % t.strip())
3037
3038
3038 # all ancestors of branch heads - all ancestors of parent = new csets
3039 # all ancestors of branch heads - all ancestors of parent = new csets
3039 new = [0] * len(repo)
3040 new = [0] * len(repo)
3040 cl = repo.changelog
3041 cl = repo.changelog
3041 for a in [cl.rev(n) for n in bheads]:
3042 for a in [cl.rev(n) for n in bheads]:
3042 new[a] = 1
3043 new[a] = 1
3043 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3044 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3044 new[a] = 1
3045 new[a] = 1
3045 for a in [p.rev() for p in parents]:
3046 for a in [p.rev() for p in parents]:
3046 if a >= 0:
3047 if a >= 0:
3047 new[a] = 0
3048 new[a] = 0
3048 for a in cl.ancestors(*[p.rev() for p in parents]):
3049 for a in cl.ancestors(*[p.rev() for p in parents]):
3049 new[a] = 0
3050 new[a] = 0
3050 new = sum(new)
3051 new = sum(new)
3051
3052
3052 if new == 0:
3053 if new == 0:
3053 ui.status(_('update: (current)\n'))
3054 ui.status(_('update: (current)\n'))
3054 elif pnode not in bheads:
3055 elif pnode not in bheads:
3055 ui.write(_('update: %d new changesets (update)\n') % new)
3056 ui.write(_('update: %d new changesets (update)\n') % new)
3056 else:
3057 else:
3057 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3058 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3058 (new, len(bheads)))
3059 (new, len(bheads)))
3059
3060
3060 if opts.get('remote'):
3061 if opts.get('remote'):
3061 t = []
3062 t = []
3062 source, branches = hg.parseurl(ui.expandpath('default'))
3063 source, branches = hg.parseurl(ui.expandpath('default'))
3063 other = hg.repository(cmdutil.remoteui(repo, {}), source)
3064 other = hg.repository(cmdutil.remoteui(repo, {}), source)
3064 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3065 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3065 ui.debug('comparing with %s\n' % url.hidepassword(source))
3066 ui.debug('comparing with %s\n' % url.hidepassword(source))
3066 repo.ui.pushbuffer()
3067 repo.ui.pushbuffer()
3067 common, incoming, rheads = repo.findcommonincoming(other)
3068 common, incoming, rheads = repo.findcommonincoming(other)
3068 repo.ui.popbuffer()
3069 repo.ui.popbuffer()
3069 if incoming:
3070 if incoming:
3070 t.append(_('1 or more incoming'))
3071 t.append(_('1 or more incoming'))
3071
3072
3072 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3073 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3073 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3074 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3074 other = hg.repository(cmdutil.remoteui(repo, {}), dest)
3075 other = hg.repository(cmdutil.remoteui(repo, {}), dest)
3075 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3076 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3076 repo.ui.pushbuffer()
3077 repo.ui.pushbuffer()
3077 o = repo.findoutgoing(other)
3078 o = repo.findoutgoing(other)
3078 repo.ui.popbuffer()
3079 repo.ui.popbuffer()
3079 o = repo.changelog.nodesbetween(o, None)[0]
3080 o = repo.changelog.nodesbetween(o, None)[0]
3080 if o:
3081 if o:
3081 t.append(_('%d outgoing') % len(o))
3082 t.append(_('%d outgoing') % len(o))
3082
3083
3083 if t:
3084 if t:
3084 ui.write(_('remote: %s\n') % (', '.join(t)))
3085 ui.write(_('remote: %s\n') % (', '.join(t)))
3085 else:
3086 else:
3086 ui.status(_('remote: (synced)\n'))
3087 ui.status(_('remote: (synced)\n'))
3087
3088
3088 def tag(ui, repo, name1, *names, **opts):
3089 def tag(ui, repo, name1, *names, **opts):
3089 """add one or more tags for the current or given revision
3090 """add one or more tags for the current or given revision
3090
3091
3091 Name a particular revision using <name>.
3092 Name a particular revision using <name>.
3092
3093
3093 Tags are used to name particular revisions of the repository and are
3094 Tags are used to name particular revisions of the repository and are
3094 very useful to compare different revisions, to go back to significant
3095 very useful to compare different revisions, to go back to significant
3095 earlier versions or to mark branch points as releases, etc.
3096 earlier versions or to mark branch points as releases, etc.
3096
3097
3097 If no revision is given, the parent of the working directory is
3098 If no revision is given, the parent of the working directory is
3098 used, or tip if no revision is checked out.
3099 used, or tip if no revision is checked out.
3099
3100
3100 To facilitate version control, distribution, and merging of tags,
3101 To facilitate version control, distribution, and merging of tags,
3101 they are stored as a file named ".hgtags" which is managed
3102 they are stored as a file named ".hgtags" which is managed
3102 similarly to other project files and can be hand-edited if
3103 similarly to other project files and can be hand-edited if
3103 necessary. The file '.hg/localtags' is used for local tags (not
3104 necessary. The file '.hg/localtags' is used for local tags (not
3104 shared among repositories).
3105 shared among repositories).
3105
3106
3106 See 'hg help dates' for a list of formats valid for -d/--date.
3107 See 'hg help dates' for a list of formats valid for -d/--date.
3107 """
3108 """
3108
3109
3109 rev_ = "."
3110 rev_ = "."
3110 names = (name1,) + names
3111 names = (name1,) + names
3111 if len(names) != len(set(names)):
3112 if len(names) != len(set(names)):
3112 raise util.Abort(_('tag names must be unique'))
3113 raise util.Abort(_('tag names must be unique'))
3113 for n in names:
3114 for n in names:
3114 if n in ['tip', '.', 'null']:
3115 if n in ['tip', '.', 'null']:
3115 raise util.Abort(_('the name \'%s\' is reserved') % n)
3116 raise util.Abort(_('the name \'%s\' is reserved') % n)
3116 if opts.get('rev') and opts.get('remove'):
3117 if opts.get('rev') and opts.get('remove'):
3117 raise util.Abort(_("--rev and --remove are incompatible"))
3118 raise util.Abort(_("--rev and --remove are incompatible"))
3118 if opts.get('rev'):
3119 if opts.get('rev'):
3119 rev_ = opts['rev']
3120 rev_ = opts['rev']
3120 message = opts.get('message')
3121 message = opts.get('message')
3121 if opts.get('remove'):
3122 if opts.get('remove'):
3122 expectedtype = opts.get('local') and 'local' or 'global'
3123 expectedtype = opts.get('local') and 'local' or 'global'
3123 for n in names:
3124 for n in names:
3124 if not repo.tagtype(n):
3125 if not repo.tagtype(n):
3125 raise util.Abort(_('tag \'%s\' does not exist') % n)
3126 raise util.Abort(_('tag \'%s\' does not exist') % n)
3126 if repo.tagtype(n) != expectedtype:
3127 if repo.tagtype(n) != expectedtype:
3127 if expectedtype == 'global':
3128 if expectedtype == 'global':
3128 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3129 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3129 else:
3130 else:
3130 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3131 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3131 rev_ = nullid
3132 rev_ = nullid
3132 if not message:
3133 if not message:
3133 # we don't translate commit messages
3134 # we don't translate commit messages
3134 message = 'Removed tag %s' % ', '.join(names)
3135 message = 'Removed tag %s' % ', '.join(names)
3135 elif not opts.get('force'):
3136 elif not opts.get('force'):
3136 for n in names:
3137 for n in names:
3137 if n in repo.tags():
3138 if n in repo.tags():
3138 raise util.Abort(_('tag \'%s\' already exists '
3139 raise util.Abort(_('tag \'%s\' already exists '
3139 '(use -f to force)') % n)
3140 '(use -f to force)') % n)
3140 if not rev_ and repo.dirstate.parents()[1] != nullid:
3141 if not rev_ and repo.dirstate.parents()[1] != nullid:
3141 raise util.Abort(_('uncommitted merge - please provide a '
3142 raise util.Abort(_('uncommitted merge - please provide a '
3142 'specific revision'))
3143 'specific revision'))
3143 r = repo[rev_].node()
3144 r = repo[rev_].node()
3144
3145
3145 if not message:
3146 if not message:
3146 # we don't translate commit messages
3147 # we don't translate commit messages
3147 message = ('Added tag %s for changeset %s' %
3148 message = ('Added tag %s for changeset %s' %
3148 (', '.join(names), short(r)))
3149 (', '.join(names), short(r)))
3149
3150
3150 date = opts.get('date')
3151 date = opts.get('date')
3151 if date:
3152 if date:
3152 date = util.parsedate(date)
3153 date = util.parsedate(date)
3153
3154
3154 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3155 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3155
3156
3156 def tags(ui, repo):
3157 def tags(ui, repo):
3157 """list repository tags
3158 """list repository tags
3158
3159
3159 This lists both regular and local tags. When the -v/--verbose
3160 This lists both regular and local tags. When the -v/--verbose
3160 switch is used, a third column "local" is printed for local tags.
3161 switch is used, a third column "local" is printed for local tags.
3161 """
3162 """
3162
3163
3163 hexfunc = ui.debugflag and hex or short
3164 hexfunc = ui.debugflag and hex or short
3164 tagtype = ""
3165 tagtype = ""
3165
3166
3166 for t, n in reversed(repo.tagslist()):
3167 for t, n in reversed(repo.tagslist()):
3167 if ui.quiet:
3168 if ui.quiet:
3168 ui.write("%s\n" % t)
3169 ui.write("%s\n" % t)
3169 continue
3170 continue
3170
3171
3171 try:
3172 try:
3172 hn = hexfunc(n)
3173 hn = hexfunc(n)
3173 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3174 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3174 except error.LookupError:
3175 except error.LookupError:
3175 r = " ?:%s" % hn
3176 r = " ?:%s" % hn
3176 else:
3177 else:
3177 spaces = " " * (30 - encoding.colwidth(t))
3178 spaces = " " * (30 - encoding.colwidth(t))
3178 if ui.verbose:
3179 if ui.verbose:
3179 if repo.tagtype(t) == 'local':
3180 if repo.tagtype(t) == 'local':
3180 tagtype = " local"
3181 tagtype = " local"
3181 else:
3182 else:
3182 tagtype = ""
3183 tagtype = ""
3183 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3184 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3184
3185
3185 def tip(ui, repo, **opts):
3186 def tip(ui, repo, **opts):
3186 """show the tip revision
3187 """show the tip revision
3187
3188
3188 The tip revision (usually just called the tip) is the changeset
3189 The tip revision (usually just called the tip) is the changeset
3189 most recently added to the repository (and therefore the most
3190 most recently added to the repository (and therefore the most
3190 recently changed head).
3191 recently changed head).
3191
3192
3192 If you have just made a commit, that commit will be the tip. If
3193 If you have just made a commit, that commit will be the tip. If
3193 you have just pulled changes from another repository, the tip of
3194 you have just pulled changes from another repository, the tip of
3194 that repository becomes the current tip. The "tip" tag is special
3195 that repository becomes the current tip. The "tip" tag is special
3195 and cannot be renamed or assigned to a different changeset.
3196 and cannot be renamed or assigned to a different changeset.
3196 """
3197 """
3197 displayer = cmdutil.show_changeset(ui, repo, opts)
3198 displayer = cmdutil.show_changeset(ui, repo, opts)
3198 displayer.show(repo[len(repo) - 1])
3199 displayer.show(repo[len(repo) - 1])
3199 displayer.close()
3200 displayer.close()
3200
3201
3201 def unbundle(ui, repo, fname1, *fnames, **opts):
3202 def unbundle(ui, repo, fname1, *fnames, **opts):
3202 """apply one or more changegroup files
3203 """apply one or more changegroup files
3203
3204
3204 Apply one or more compressed changegroup files generated by the
3205 Apply one or more compressed changegroup files generated by the
3205 bundle command.
3206 bundle command.
3206 """
3207 """
3207 fnames = (fname1,) + fnames
3208 fnames = (fname1,) + fnames
3208
3209
3209 lock = repo.lock()
3210 lock = repo.lock()
3210 try:
3211 try:
3211 for fname in fnames:
3212 for fname in fnames:
3212 f = url.open(ui, fname)
3213 f = url.open(ui, fname)
3213 gen = changegroup.readbundle(f, fname)
3214 gen = changegroup.readbundle(f, fname)
3214 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3215 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3215 finally:
3216 finally:
3216 lock.release()
3217 lock.release()
3217
3218
3218 return postincoming(ui, repo, modheads, opts.get('update'), None)
3219 return postincoming(ui, repo, modheads, opts.get('update'), None)
3219
3220
3220 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3221 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3221 """update working directory
3222 """update working directory
3222
3223
3223 Update the repository's working directory to the specified
3224 Update the repository's working directory to the specified
3224 changeset.
3225 changeset.
3225
3226
3226 If no changeset is specified, attempt to update to the head of the
3227 If no changeset is specified, attempt to update to the head of the
3227 current branch. If this head is a descendant of the working
3228 current branch. If this head is a descendant of the working
3228 directory's parent, update to it, otherwise abort.
3229 directory's parent, update to it, otherwise abort.
3229
3230
3230 The following rules apply when the working directory contains
3231 The following rules apply when the working directory contains
3231 uncommitted changes:
3232 uncommitted changes:
3232
3233
3233 1. If neither -c/--check nor -C/--clean is specified, and if
3234 1. If neither -c/--check nor -C/--clean is specified, and if
3234 the requested changeset is an ancestor or descendant of
3235 the requested changeset is an ancestor or descendant of
3235 the working directory's parent, the uncommitted changes
3236 the working directory's parent, the uncommitted changes
3236 are merged into the requested changeset and the merged
3237 are merged into the requested changeset and the merged
3237 result is left uncommitted. If the requested changeset is
3238 result is left uncommitted. If the requested changeset is
3238 not an ancestor or descendant (that is, it is on another
3239 not an ancestor or descendant (that is, it is on another
3239 branch), the update is aborted and the uncommitted changes
3240 branch), the update is aborted and the uncommitted changes
3240 are preserved.
3241 are preserved.
3241
3242
3242 2. With the -c/--check option, the update is aborted and the
3243 2. With the -c/--check option, the update is aborted and the
3243 uncommitted changes are preserved.
3244 uncommitted changes are preserved.
3244
3245
3245 3. With the -C/--clean option, uncommitted changes are discarded and
3246 3. With the -C/--clean option, uncommitted changes are discarded and
3246 the working directory is updated to the requested changeset.
3247 the working directory is updated to the requested changeset.
3247
3248
3248 Use null as the changeset to remove the working directory (like 'hg
3249 Use null as the changeset to remove the working directory (like 'hg
3249 clone -U').
3250 clone -U').
3250
3251
3251 If you want to update just one file to an older changeset, use 'hg revert'.
3252 If you want to update just one file to an older changeset, use 'hg revert'.
3252
3253
3253 See 'hg help dates' for a list of formats valid for -d/--date.
3254 See 'hg help dates' for a list of formats valid for -d/--date.
3254 """
3255 """
3255 if rev and node:
3256 if rev and node:
3256 raise util.Abort(_("please specify just one revision"))
3257 raise util.Abort(_("please specify just one revision"))
3257
3258
3258 if not rev:
3259 if not rev:
3259 rev = node
3260 rev = node
3260
3261
3261 if check and clean:
3262 if check and clean:
3262 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3263 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3263
3264
3264 if check:
3265 if check:
3265 # we could use dirty() but we can ignore merge and branch trivia
3266 # we could use dirty() but we can ignore merge and branch trivia
3266 c = repo[None]
3267 c = repo[None]
3267 if c.modified() or c.added() or c.removed():
3268 if c.modified() or c.added() or c.removed():
3268 raise util.Abort(_("uncommitted local changes"))
3269 raise util.Abort(_("uncommitted local changes"))
3269
3270
3270 if date:
3271 if date:
3271 if rev:
3272 if rev:
3272 raise util.Abort(_("you can't specify a revision and a date"))
3273 raise util.Abort(_("you can't specify a revision and a date"))
3273 rev = cmdutil.finddate(ui, repo, date)
3274 rev = cmdutil.finddate(ui, repo, date)
3274
3275
3275 if clean or check:
3276 if clean or check:
3276 return hg.clean(repo, rev)
3277 return hg.clean(repo, rev)
3277 else:
3278 else:
3278 return hg.update(repo, rev)
3279 return hg.update(repo, rev)
3279
3280
3280 def verify(ui, repo):
3281 def verify(ui, repo):
3281 """verify the integrity of the repository
3282 """verify the integrity of the repository
3282
3283
3283 Verify the integrity of the current repository.
3284 Verify the integrity of the current repository.
3284
3285
3285 This will perform an extensive check of the repository's
3286 This will perform an extensive check of the repository's
3286 integrity, validating the hashes and checksums of each entry in
3287 integrity, validating the hashes and checksums of each entry in
3287 the changelog, manifest, and tracked files, as well as the
3288 the changelog, manifest, and tracked files, as well as the
3288 integrity of their crosslinks and indices.
3289 integrity of their crosslinks and indices.
3289 """
3290 """
3290 return hg.verify(repo)
3291 return hg.verify(repo)
3291
3292
3292 def version_(ui):
3293 def version_(ui):
3293 """output version and copyright information"""
3294 """output version and copyright information"""
3294 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3295 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3295 % util.version())
3296 % util.version())
3296 ui.status(_(
3297 ui.status(_(
3297 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3298 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3298 "This is free software; see the source for copying conditions. "
3299 "This is free software; see the source for copying conditions. "
3299 "There is NO\nwarranty; "
3300 "There is NO\nwarranty; "
3300 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3301 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3301 ))
3302 ))
3302
3303
3303 # Command options and aliases are listed here, alphabetically
3304 # Command options and aliases are listed here, alphabetically
3304
3305
3305 globalopts = [
3306 globalopts = [
3306 ('R', 'repository', '',
3307 ('R', 'repository', '',
3307 _('repository root directory or name of overlay bundle file')),
3308 _('repository root directory or name of overlay bundle file')),
3308 ('', 'cwd', '', _('change working directory')),
3309 ('', 'cwd', '', _('change working directory')),
3309 ('y', 'noninteractive', None,
3310 ('y', 'noninteractive', None,
3310 _('do not prompt, assume \'yes\' for any required answers')),
3311 _('do not prompt, assume \'yes\' for any required answers')),
3311 ('q', 'quiet', None, _('suppress output')),
3312 ('q', 'quiet', None, _('suppress output')),
3312 ('v', 'verbose', None, _('enable additional output')),
3313 ('v', 'verbose', None, _('enable additional output')),
3313 ('', 'config', [], _('set/override config option')),
3314 ('', 'config', [], _('set/override config option')),
3314 ('', 'debug', None, _('enable debugging output')),
3315 ('', 'debug', None, _('enable debugging output')),
3315 ('', 'debugger', None, _('start debugger')),
3316 ('', 'debugger', None, _('start debugger')),
3316 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3317 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3317 ('', 'encodingmode', encoding.encodingmode,
3318 ('', 'encodingmode', encoding.encodingmode,
3318 _('set the charset encoding mode')),
3319 _('set the charset encoding mode')),
3319 ('', 'traceback', None, _('always print a traceback on exception')),
3320 ('', 'traceback', None, _('always print a traceback on exception')),
3320 ('', 'time', None, _('time how long the command takes')),
3321 ('', 'time', None, _('time how long the command takes')),
3321 ('', 'profile', None, _('print command execution profile')),
3322 ('', 'profile', None, _('print command execution profile')),
3322 ('', 'version', None, _('output version information and exit')),
3323 ('', 'version', None, _('output version information and exit')),
3323 ('h', 'help', None, _('display help and exit')),
3324 ('h', 'help', None, _('display help and exit')),
3324 ]
3325 ]
3325
3326
3326 dryrunopts = [('n', 'dry-run', None,
3327 dryrunopts = [('n', 'dry-run', None,
3327 _('do not perform actions, just print output'))]
3328 _('do not perform actions, just print output'))]
3328
3329
3329 remoteopts = [
3330 remoteopts = [
3330 ('e', 'ssh', '', _('specify ssh command to use')),
3331 ('e', 'ssh', '', _('specify ssh command to use')),
3331 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3332 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3332 ]
3333 ]
3333
3334
3334 walkopts = [
3335 walkopts = [
3335 ('I', 'include', [], _('include names matching the given patterns')),
3336 ('I', 'include', [], _('include names matching the given patterns')),
3336 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3337 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3337 ]
3338 ]
3338
3339
3339 commitopts = [
3340 commitopts = [
3340 ('m', 'message', '', _('use <text> as commit message')),
3341 ('m', 'message', '', _('use <text> as commit message')),
3341 ('l', 'logfile', '', _('read commit message from <file>')),
3342 ('l', 'logfile', '', _('read commit message from <file>')),
3342 ]
3343 ]
3343
3344
3344 commitopts2 = [
3345 commitopts2 = [
3345 ('d', 'date', '', _('record datecode as commit date')),
3346 ('d', 'date', '', _('record datecode as commit date')),
3346 ('u', 'user', '', _('record the specified user as committer')),
3347 ('u', 'user', '', _('record the specified user as committer')),
3347 ]
3348 ]
3348
3349
3349 templateopts = [
3350 templateopts = [
3350 ('', 'style', '', _('display using template map file')),
3351 ('', 'style', '', _('display using template map file')),
3351 ('', 'template', '', _('display with template')),
3352 ('', 'template', '', _('display with template')),
3352 ]
3353 ]
3353
3354
3354 logopts = [
3355 logopts = [
3355 ('p', 'patch', None, _('show patch')),
3356 ('p', 'patch', None, _('show patch')),
3356 ('g', 'git', None, _('use git extended diff format')),
3357 ('g', 'git', None, _('use git extended diff format')),
3357 ('l', 'limit', '', _('limit number of changes displayed')),
3358 ('l', 'limit', '', _('limit number of changes displayed')),
3358 ('M', 'no-merges', None, _('do not show merges')),
3359 ('M', 'no-merges', None, _('do not show merges')),
3359 ] + templateopts
3360 ] + templateopts
3360
3361
3361 diffopts = [
3362 diffopts = [
3362 ('a', 'text', None, _('treat all files as text')),
3363 ('a', 'text', None, _('treat all files as text')),
3363 ('g', 'git', None, _('use git extended diff format')),
3364 ('g', 'git', None, _('use git extended diff format')),
3364 ('', 'nodates', None, _('omit dates from diff headers'))
3365 ('', 'nodates', None, _('omit dates from diff headers'))
3365 ]
3366 ]
3366
3367
3367 diffopts2 = [
3368 diffopts2 = [
3368 ('p', 'show-function', None, _('show which function each change is in')),
3369 ('p', 'show-function', None, _('show which function each change is in')),
3369 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3370 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3370 ('w', 'ignore-all-space', None,
3371 ('w', 'ignore-all-space', None,
3371 _('ignore white space when comparing lines')),
3372 _('ignore white space when comparing lines')),
3372 ('b', 'ignore-space-change', None,
3373 ('b', 'ignore-space-change', None,
3373 _('ignore changes in the amount of white space')),
3374 _('ignore changes in the amount of white space')),
3374 ('B', 'ignore-blank-lines', None,
3375 ('B', 'ignore-blank-lines', None,
3375 _('ignore changes whose lines are all blank')),
3376 _('ignore changes whose lines are all blank')),
3376 ('U', 'unified', '', _('number of lines of context to show')),
3377 ('U', 'unified', '', _('number of lines of context to show')),
3377 ('', 'stat', None, _('output diffstat-style summary of changes')),
3378 ('', 'stat', None, _('output diffstat-style summary of changes')),
3378 ]
3379 ]
3379
3380
3380 similarityopts = [
3381 similarityopts = [
3381 ('s', 'similarity', '',
3382 ('s', 'similarity', '',
3382 _('guess renamed files by similarity (0<=s<=100)'))
3383 _('guess renamed files by similarity (0<=s<=100)'))
3383 ]
3384 ]
3384
3385
3385 table = {
3386 table = {
3386 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3387 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3387 "addremove":
3388 "addremove":
3388 (addremove, similarityopts + walkopts + dryrunopts,
3389 (addremove, similarityopts + walkopts + dryrunopts,
3389 _('[OPTION]... [FILE]...')),
3390 _('[OPTION]... [FILE]...')),
3390 "^annotate|blame":
3391 "^annotate|blame":
3391 (annotate,
3392 (annotate,
3392 [('r', 'rev', '', _('annotate the specified revision')),
3393 [('r', 'rev', '', _('annotate the specified revision')),
3393 ('', 'follow', None, _('follow copies and renames (DEPRECATED)')),
3394 ('', 'follow', None, _('follow copies and renames (DEPRECATED)')),
3394 ('', 'no-follow', None, _("don't follow copies and renames")),
3395 ('', 'no-follow', None, _("don't follow copies and renames")),
3395 ('a', 'text', None, _('treat all files as text')),
3396 ('a', 'text', None, _('treat all files as text')),
3396 ('u', 'user', None, _('list the author (long with -v)')),
3397 ('u', 'user', None, _('list the author (long with -v)')),
3397 ('f', 'file', None, _('list the filename')),
3398 ('f', 'file', None, _('list the filename')),
3398 ('d', 'date', None, _('list the date (short with -q)')),
3399 ('d', 'date', None, _('list the date (short with -q)')),
3399 ('n', 'number', None, _('list the revision number (default)')),
3400 ('n', 'number', None, _('list the revision number (default)')),
3400 ('c', 'changeset', None, _('list the changeset')),
3401 ('c', 'changeset', None, _('list the changeset')),
3401 ('l', 'line-number', None,
3402 ('l', 'line-number', None,
3402 _('show line number at the first appearance'))
3403 _('show line number at the first appearance'))
3403 ] + walkopts,
3404 ] + walkopts,
3404 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3405 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3405 "archive":
3406 "archive":
3406 (archive,
3407 (archive,
3407 [('', 'no-decode', None, _('do not pass files through decoders')),
3408 [('', 'no-decode', None, _('do not pass files through decoders')),
3408 ('p', 'prefix', '', _('directory prefix for files in archive')),
3409 ('p', 'prefix', '', _('directory prefix for files in archive')),
3409 ('r', 'rev', '', _('revision to distribute')),
3410 ('r', 'rev', '', _('revision to distribute')),
3410 ('t', 'type', '', _('type of distribution to create')),
3411 ('t', 'type', '', _('type of distribution to create')),
3411 ] + walkopts,
3412 ] + walkopts,
3412 _('[OPTION]... DEST')),
3413 _('[OPTION]... DEST')),
3413 "backout":
3414 "backout":
3414 (backout,
3415 (backout,
3415 [('', 'merge', None,
3416 [('', 'merge', None,
3416 _('merge with old dirstate parent after backout')),
3417 _('merge with old dirstate parent after backout')),
3417 ('', 'parent', '', _('parent to choose when backing out merge')),
3418 ('', 'parent', '', _('parent to choose when backing out merge')),
3418 ('r', 'rev', '', _('revision to backout')),
3419 ('r', 'rev', '', _('revision to backout')),
3419 ] + walkopts + commitopts + commitopts2,
3420 ] + walkopts + commitopts + commitopts2,
3420 _('[OPTION]... [-r] REV')),
3421 _('[OPTION]... [-r] REV')),
3421 "bisect":
3422 "bisect":
3422 (bisect,
3423 (bisect,
3423 [('r', 'reset', False, _('reset bisect state')),
3424 [('r', 'reset', False, _('reset bisect state')),
3424 ('g', 'good', False, _('mark changeset good')),
3425 ('g', 'good', False, _('mark changeset good')),
3425 ('b', 'bad', False, _('mark changeset bad')),
3426 ('b', 'bad', False, _('mark changeset bad')),
3426 ('s', 'skip', False, _('skip testing changeset')),
3427 ('s', 'skip', False, _('skip testing changeset')),
3427 ('c', 'command', '', _('use command to check changeset state')),
3428 ('c', 'command', '', _('use command to check changeset state')),
3428 ('U', 'noupdate', False, _('do not update to target'))],
3429 ('U', 'noupdate', False, _('do not update to target'))],
3429 _("[-gbsr] [-U] [-c CMD] [REV]")),
3430 _("[-gbsr] [-U] [-c CMD] [REV]")),
3430 "branch":
3431 "branch":
3431 (branch,
3432 (branch,
3432 [('f', 'force', None,
3433 [('f', 'force', None,
3433 _('set branch name even if it shadows an existing branch')),
3434 _('set branch name even if it shadows an existing branch')),
3434 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3435 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3435 _('[-fC] [NAME]')),
3436 _('[-fC] [NAME]')),
3436 "branches":
3437 "branches":
3437 (branches,
3438 (branches,
3438 [('a', 'active', False,
3439 [('a', 'active', False,
3439 _('show only branches that have unmerged heads')),
3440 _('show only branches that have unmerged heads')),
3440 ('c', 'closed', False,
3441 ('c', 'closed', False,
3441 _('show normal and closed branches'))],
3442 _('show normal and closed branches'))],
3442 _('[-ac]')),
3443 _('[-ac]')),
3443 "bundle":
3444 "bundle":
3444 (bundle,
3445 (bundle,
3445 [('f', 'force', None,
3446 [('f', 'force', None,
3446 _('run even when the destination is unrelated')),
3447 _('run even when the destination is unrelated')),
3447 ('r', 'rev', [],
3448 ('r', 'rev', [],
3448 _('a changeset intended to be added to the destination')),
3449 _('a changeset intended to be added to the destination')),
3449 ('b', 'branch', [],
3450 ('b', 'branch', [],
3450 _('a specific branch you would like to bundle')),
3451 _('a specific branch you would like to bundle')),
3451 ('', 'base', [],
3452 ('', 'base', [],
3452 _('a base changeset assumed to be available at the destination')),
3453 _('a base changeset assumed to be available at the destination')),
3453 ('a', 'all', None, _('bundle all changesets in the repository')),
3454 ('a', 'all', None, _('bundle all changesets in the repository')),
3454 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3455 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3455 ] + remoteopts,
3456 ] + remoteopts,
3456 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3457 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3457 "cat":
3458 "cat":
3458 (cat,
3459 (cat,
3459 [('o', 'output', '', _('print output to file with formatted name')),
3460 [('o', 'output', '', _('print output to file with formatted name')),
3460 ('r', 'rev', '', _('print the given revision')),
3461 ('r', 'rev', '', _('print the given revision')),
3461 ('', 'decode', None, _('apply any matching decode filter')),
3462 ('', 'decode', None, _('apply any matching decode filter')),
3462 ] + walkopts,
3463 ] + walkopts,
3463 _('[OPTION]... FILE...')),
3464 _('[OPTION]... FILE...')),
3464 "^clone":
3465 "^clone":
3465 (clone,
3466 (clone,
3466 [('U', 'noupdate', None,
3467 [('U', 'noupdate', None,
3467 _('the clone will include an empty working copy (only a repository)')),
3468 _('the clone will include an empty working copy (only a repository)')),
3468 ('u', 'updaterev', '',
3469 ('u', 'updaterev', '',
3469 _('revision, tag or branch to check out')),
3470 _('revision, tag or branch to check out')),
3470 ('r', 'rev', [],
3471 ('r', 'rev', [],
3471 _('include the specified changeset')),
3472 _('include the specified changeset')),
3472 ('b', 'branch', [],
3473 ('b', 'branch', [],
3473 _('clone only the specified branch')),
3474 _('clone only the specified branch')),
3474 ('', 'pull', None, _('use pull protocol to copy metadata')),
3475 ('', 'pull', None, _('use pull protocol to copy metadata')),
3475 ('', 'uncompressed', None,
3476 ('', 'uncompressed', None,
3476 _('use uncompressed transfer (fast over LAN)')),
3477 _('use uncompressed transfer (fast over LAN)')),
3477 ] + remoteopts,
3478 ] + remoteopts,
3478 _('[OPTION]... SOURCE [DEST]')),
3479 _('[OPTION]... SOURCE [DEST]')),
3479 "^commit|ci":
3480 "^commit|ci":
3480 (commit,
3481 (commit,
3481 [('A', 'addremove', None,
3482 [('A', 'addremove', None,
3482 _('mark new/missing files as added/removed before committing')),
3483 _('mark new/missing files as added/removed before committing')),
3483 ('', 'close-branch', None,
3484 ('', 'close-branch', None,
3484 _('mark a branch as closed, hiding it from the branch list')),
3485 _('mark a branch as closed, hiding it from the branch list')),
3485 ] + walkopts + commitopts + commitopts2,
3486 ] + walkopts + commitopts + commitopts2,
3486 _('[OPTION]... [FILE]...')),
3487 _('[OPTION]... [FILE]...')),
3487 "copy|cp":
3488 "copy|cp":
3488 (copy,
3489 (copy,
3489 [('A', 'after', None, _('record a copy that has already occurred')),
3490 [('A', 'after', None, _('record a copy that has already occurred')),
3490 ('f', 'force', None,
3491 ('f', 'force', None,
3491 _('forcibly copy over an existing managed file')),
3492 _('forcibly copy over an existing managed file')),
3492 ] + walkopts + dryrunopts,
3493 ] + walkopts + dryrunopts,
3493 _('[OPTION]... [SOURCE]... DEST')),
3494 _('[OPTION]... [SOURCE]... DEST')),
3494 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3495 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3495 "debugcheckstate": (debugcheckstate, [], ''),
3496 "debugcheckstate": (debugcheckstate, [], ''),
3496 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3497 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3497 "debugcomplete":
3498 "debugcomplete":
3498 (debugcomplete,
3499 (debugcomplete,
3499 [('o', 'options', None, _('show the command options'))],
3500 [('o', 'options', None, _('show the command options'))],
3500 _('[-o] CMD')),
3501 _('[-o] CMD')),
3501 "debugdate":
3502 "debugdate":
3502 (debugdate,
3503 (debugdate,
3503 [('e', 'extended', None, _('try extended date formats'))],
3504 [('e', 'extended', None, _('try extended date formats'))],
3504 _('[-e] DATE [RANGE]')),
3505 _('[-e] DATE [RANGE]')),
3505 "debugdata": (debugdata, [], _('FILE REV')),
3506 "debugdata": (debugdata, [], _('FILE REV')),
3506 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3507 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3507 "debugindex": (debugindex, [], _('FILE')),
3508 "debugindex": (debugindex, [], _('FILE')),
3508 "debugindexdot": (debugindexdot, [], _('FILE')),
3509 "debugindexdot": (debugindexdot, [], _('FILE')),
3509 "debuginstall": (debuginstall, [], ''),
3510 "debuginstall": (debuginstall, [], ''),
3510 "debugrebuildstate":
3511 "debugrebuildstate":
3511 (debugrebuildstate,
3512 (debugrebuildstate,
3512 [('r', 'rev', '', _('revision to rebuild to'))],
3513 [('r', 'rev', '', _('revision to rebuild to'))],
3513 _('[-r REV] [REV]')),
3514 _('[-r REV] [REV]')),
3514 "debugrename":
3515 "debugrename":
3515 (debugrename,
3516 (debugrename,
3516 [('r', 'rev', '', _('revision to debug'))],
3517 [('r', 'rev', '', _('revision to debug'))],
3517 _('[-r REV] FILE')),
3518 _('[-r REV] FILE')),
3518 "debugsetparents":
3519 "debugsetparents":
3519 (debugsetparents, [], _('REV1 [REV2]')),
3520 (debugsetparents, [], _('REV1 [REV2]')),
3520 "debugstate":
3521 "debugstate":
3521 (debugstate,
3522 (debugstate,
3522 [('', 'nodates', None, _('do not display the saved mtime'))],
3523 [('', 'nodates', None, _('do not display the saved mtime'))],
3523 _('[OPTION]...')),
3524 _('[OPTION]...')),
3524 "debugsub":
3525 "debugsub":
3525 (debugsub,
3526 (debugsub,
3526 [('r', 'rev', '', _('revision to check'))],
3527 [('r', 'rev', '', _('revision to check'))],
3527 _('[-r REV] [REV]')),
3528 _('[-r REV] [REV]')),
3528 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3529 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3529 "^diff":
3530 "^diff":
3530 (diff,
3531 (diff,
3531 [('r', 'rev', [], _('revision')),
3532 [('r', 'rev', [], _('revision')),
3532 ('c', 'change', '', _('change made by revision'))
3533 ('c', 'change', '', _('change made by revision'))
3533 ] + diffopts + diffopts2 + walkopts,
3534 ] + diffopts + diffopts2 + walkopts,
3534 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3535 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3535 "^export":
3536 "^export":
3536 (export,
3537 (export,
3537 [('o', 'output', '', _('print output to file with formatted name')),
3538 [('o', 'output', '', _('print output to file with formatted name')),
3538 ('', 'switch-parent', None, _('diff against the second parent')),
3539 ('', 'switch-parent', None, _('diff against the second parent')),
3539 ('r', 'rev', [], _('revisions to export')),
3540 ('r', 'rev', [], _('revisions to export')),
3540 ] + diffopts,
3541 ] + diffopts,
3541 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3542 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3542 "^forget":
3543 "^forget":
3543 (forget,
3544 (forget,
3544 [] + walkopts,
3545 [] + walkopts,
3545 _('[OPTION]... FILE...')),
3546 _('[OPTION]... FILE...')),
3546 "grep":
3547 "grep":
3547 (grep,
3548 (grep,
3548 [('0', 'print0', None, _('end fields with NUL')),
3549 [('0', 'print0', None, _('end fields with NUL')),
3549 ('', 'all', None, _('print all revisions that match')),
3550 ('', 'all', None, _('print all revisions that match')),
3550 ('f', 'follow', None,
3551 ('f', 'follow', None,
3551 _('follow changeset history,'
3552 _('follow changeset history,'
3552 ' or file history across copies and renames')),
3553 ' or file history across copies and renames')),
3553 ('i', 'ignore-case', None, _('ignore case when matching')),
3554 ('i', 'ignore-case', None, _('ignore case when matching')),
3554 ('l', 'files-with-matches', None,
3555 ('l', 'files-with-matches', None,
3555 _('print only filenames and revisions that match')),
3556 _('print only filenames and revisions that match')),
3556 ('n', 'line-number', None, _('print matching line numbers')),
3557 ('n', 'line-number', None, _('print matching line numbers')),
3557 ('r', 'rev', [], _('search in given revision range')),
3558 ('r', 'rev', [], _('search in given revision range')),
3558 ('u', 'user', None, _('list the author (long with -v)')),
3559 ('u', 'user', None, _('list the author (long with -v)')),
3559 ('d', 'date', None, _('list the date (short with -q)')),
3560 ('d', 'date', None, _('list the date (short with -q)')),
3560 ] + walkopts,
3561 ] + walkopts,
3561 _('[OPTION]... PATTERN [FILE]...')),
3562 _('[OPTION]... PATTERN [FILE]...')),
3562 "heads":
3563 "heads":
3563 (heads,
3564 (heads,
3564 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3565 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3565 ('t', 'topo', False, _('show topological heads only')),
3566 ('t', 'topo', False, _('show topological heads only')),
3566 ('a', 'active', False,
3567 ('a', 'active', False,
3567 _('show active branchheads only [DEPRECATED]')),
3568 _('show active branchheads only [DEPRECATED]')),
3568 ('c', 'closed', False,
3569 ('c', 'closed', False,
3569 _('show normal and closed branch heads')),
3570 _('show normal and closed branch heads')),
3570 ] + templateopts,
3571 ] + templateopts,
3571 _('[-ac] [-r STARTREV] [REV]...')),
3572 _('[-ac] [-r STARTREV] [REV]...')),
3572 "help": (help_, [], _('[TOPIC]')),
3573 "help": (help_, [], _('[TOPIC]')),
3573 "identify|id":
3574 "identify|id":
3574 (identify,
3575 (identify,
3575 [('r', 'rev', '', _('identify the specified revision')),
3576 [('r', 'rev', '', _('identify the specified revision')),
3576 ('n', 'num', None, _('show local revision number')),
3577 ('n', 'num', None, _('show local revision number')),
3577 ('i', 'id', None, _('show global revision id')),
3578 ('i', 'id', None, _('show global revision id')),
3578 ('b', 'branch', None, _('show branch')),
3579 ('b', 'branch', None, _('show branch')),
3579 ('t', 'tags', None, _('show tags'))],
3580 ('t', 'tags', None, _('show tags'))],
3580 _('[-nibt] [-r REV] [SOURCE]')),
3581 _('[-nibt] [-r REV] [SOURCE]')),
3581 "import|patch":
3582 "import|patch":
3582 (import_,
3583 (import_,
3583 [('p', 'strip', 1,
3584 [('p', 'strip', 1,
3584 _('directory strip option for patch. This has the same '
3585 _('directory strip option for patch. This has the same '
3585 'meaning as the corresponding patch option')),
3586 'meaning as the corresponding patch option')),
3586 ('b', 'base', '', _('base path')),
3587 ('b', 'base', '', _('base path')),
3587 ('f', 'force', None,
3588 ('f', 'force', None,
3588 _('skip check for outstanding uncommitted changes')),
3589 _('skip check for outstanding uncommitted changes')),
3589 ('', 'no-commit', None,
3590 ('', 'no-commit', None,
3590 _("don't commit, just update the working directory")),
3591 _("don't commit, just update the working directory")),
3591 ('', 'exact', None,
3592 ('', 'exact', None,
3592 _('apply patch to the nodes from which it was generated')),
3593 _('apply patch to the nodes from which it was generated')),
3593 ('', 'import-branch', None,
3594 ('', 'import-branch', None,
3594 _('use any branch information in patch (implied by --exact)'))] +
3595 _('use any branch information in patch (implied by --exact)'))] +
3595 commitopts + commitopts2 + similarityopts,
3596 commitopts + commitopts2 + similarityopts,
3596 _('[OPTION]... PATCH...')),
3597 _('[OPTION]... PATCH...')),
3597 "incoming|in":
3598 "incoming|in":
3598 (incoming,
3599 (incoming,
3599 [('f', 'force', None,
3600 [('f', 'force', None,
3600 _('run even if remote repository is unrelated')),
3601 _('run even if remote repository is unrelated')),
3601 ('n', 'newest-first', None, _('show newest record first')),
3602 ('n', 'newest-first', None, _('show newest record first')),
3602 ('', 'bundle', '', _('file to store the bundles into')),
3603 ('', 'bundle', '', _('file to store the bundles into')),
3603 ('r', 'rev', [],
3604 ('r', 'rev', [],
3604 _('a remote changeset intended to be added')),
3605 _('a remote changeset intended to be added')),
3605 ('b', 'branch', [],
3606 ('b', 'branch', [],
3606 _('a specific branch you would like to pull')),
3607 _('a specific branch you would like to pull')),
3607 ] + logopts + remoteopts,
3608 ] + logopts + remoteopts,
3608 _('[-p] [-n] [-M] [-f] [-r REV]...'
3609 _('[-p] [-n] [-M] [-f] [-r REV]...'
3609 ' [--bundle FILENAME] [SOURCE]')),
3610 ' [--bundle FILENAME] [SOURCE]')),
3610 "^init":
3611 "^init":
3611 (init,
3612 (init,
3612 remoteopts,
3613 remoteopts,
3613 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3614 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3614 "locate":
3615 "locate":
3615 (locate,
3616 (locate,
3616 [('r', 'rev', '', _('search the repository as it is in REV')),
3617 [('r', 'rev', '', _('search the repository as it is in REV')),
3617 ('0', 'print0', None,
3618 ('0', 'print0', None,
3618 _('end filenames with NUL, for use with xargs')),
3619 _('end filenames with NUL, for use with xargs')),
3619 ('f', 'fullpath', None,
3620 ('f', 'fullpath', None,
3620 _('print complete paths from the filesystem root')),
3621 _('print complete paths from the filesystem root')),
3621 ] + walkopts,
3622 ] + walkopts,
3622 _('[OPTION]... [PATTERN]...')),
3623 _('[OPTION]... [PATTERN]...')),
3623 "^log|history":
3624 "^log|history":
3624 (log,
3625 (log,
3625 [('f', 'follow', None,
3626 [('f', 'follow', None,
3626 _('follow changeset history,'
3627 _('follow changeset history,'
3627 ' or file history across copies and renames')),
3628 ' or file history across copies and renames')),
3628 ('', 'follow-first', None,
3629 ('', 'follow-first', None,
3629 _('only follow the first parent of merge changesets')),
3630 _('only follow the first parent of merge changesets')),
3630 ('d', 'date', '', _('show revisions matching date spec')),
3631 ('d', 'date', '', _('show revisions matching date spec')),
3631 ('C', 'copies', None, _('show copied files')),
3632 ('C', 'copies', None, _('show copied files')),
3632 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3633 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3633 ('r', 'rev', [], _('show the specified revision or range')),
3634 ('r', 'rev', [], _('show the specified revision or range')),
3634 ('', 'removed', None, _('include revisions where files were removed')),
3635 ('', 'removed', None, _('include revisions where files were removed')),
3635 ('m', 'only-merges', None, _('show only merges')),
3636 ('m', 'only-merges', None, _('show only merges')),
3636 ('u', 'user', [], _('revisions committed by user')),
3637 ('u', 'user', [], _('revisions committed by user')),
3637 ('b', 'only-branch', [],
3638 ('b', 'only-branch', [],
3638 _('show only changesets within the given named branch')),
3639 _('show only changesets within the given named branch')),
3639 ('P', 'prune', [],
3640 ('P', 'prune', [],
3640 _('do not display revision or any of its ancestors')),
3641 _('do not display revision or any of its ancestors')),
3641 ] + logopts + walkopts,
3642 ] + logopts + walkopts,
3642 _('[OPTION]... [FILE]')),
3643 _('[OPTION]... [FILE]')),
3643 "manifest":
3644 "manifest":
3644 (manifest,
3645 (manifest,
3645 [('r', 'rev', '', _('revision to display'))],
3646 [('r', 'rev', '', _('revision to display'))],
3646 _('[-r REV]')),
3647 _('[-r REV]')),
3647 "^merge":
3648 "^merge":
3648 (merge,
3649 (merge,
3649 [('f', 'force', None, _('force a merge with outstanding changes')),
3650 [('f', 'force', None, _('force a merge with outstanding changes')),
3650 ('r', 'rev', '', _('revision to merge')),
3651 ('r', 'rev', '', _('revision to merge')),
3651 ('P', 'preview', None,
3652 ('P', 'preview', None,
3652 _('review revisions to merge (no merge is performed)'))],
3653 _('review revisions to merge (no merge is performed)'))],
3653 _('[-P] [-f] [[-r] REV]')),
3654 _('[-P] [-f] [[-r] REV]')),
3654 "outgoing|out":
3655 "outgoing|out":
3655 (outgoing,
3656 (outgoing,
3656 [('f', 'force', None,
3657 [('f', 'force', None,
3657 _('run even when the destination is unrelated')),
3658 _('run even when the destination is unrelated')),
3658 ('r', 'rev', [],
3659 ('r', 'rev', [],
3659 _('a changeset intended to be included in the destination')),
3660 _('a changeset intended to be included in the destination')),
3660 ('n', 'newest-first', None, _('show newest record first')),
3661 ('n', 'newest-first', None, _('show newest record first')),
3661 ('b', 'branch', [],
3662 ('b', 'branch', [],
3662 _('a specific branch you would like to push')),
3663 _('a specific branch you would like to push')),
3663 ] + logopts + remoteopts,
3664 ] + logopts + remoteopts,
3664 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3665 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3665 "parents":
3666 "parents":
3666 (parents,
3667 (parents,
3667 [('r', 'rev', '', _('show parents of the specified revision')),
3668 [('r', 'rev', '', _('show parents of the specified revision')),
3668 ] + templateopts,
3669 ] + templateopts,
3669 _('[-r REV] [FILE]')),
3670 _('[-r REV] [FILE]')),
3670 "paths": (paths, [], _('[NAME]')),
3671 "paths": (paths, [], _('[NAME]')),
3671 "^pull":
3672 "^pull":
3672 (pull,
3673 (pull,
3673 [('u', 'update', None,
3674 [('u', 'update', None,
3674 _('update to new branch head if changesets were pulled')),
3675 _('update to new branch head if changesets were pulled')),
3675 ('f', 'force', None,
3676 ('f', 'force', None,
3676 _('run even when remote repository is unrelated')),
3677 _('run even when remote repository is unrelated')),
3677 ('r', 'rev', [],
3678 ('r', 'rev', [],
3678 _('a remote changeset intended to be added')),
3679 _('a remote changeset intended to be added')),
3679 ('b', 'branch', [],
3680 ('b', 'branch', [],
3680 _('a specific branch you would like to pull')),
3681 _('a specific branch you would like to pull')),
3681 ] + remoteopts,
3682 ] + remoteopts,
3682 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3683 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3683 "^push":
3684 "^push":
3684 (push,
3685 (push,
3685 [('f', 'force', None, _('force push')),
3686 [('f', 'force', None, _('force push')),
3686 ('r', 'rev', [],
3687 ('r', 'rev', [],
3687 _('a changeset intended to be included in the destination')),
3688 _('a changeset intended to be included in the destination')),
3688 ('b', 'branch', [],
3689 ('b', 'branch', [],
3689 _('a specific branch you would like to push')),
3690 _('a specific branch you would like to push')),
3690 ] + remoteopts,
3691 ] + remoteopts,
3691 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3692 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3692 "recover": (recover, []),
3693 "recover": (recover, []),
3693 "^remove|rm":
3694 "^remove|rm":
3694 (remove,
3695 (remove,
3695 [('A', 'after', None, _('record delete for missing files')),
3696 [('A', 'after', None, _('record delete for missing files')),
3696 ('f', 'force', None,
3697 ('f', 'force', None,
3697 _('remove (and delete) file even if added or modified')),
3698 _('remove (and delete) file even if added or modified')),
3698 ] + walkopts,
3699 ] + walkopts,
3699 _('[OPTION]... FILE...')),
3700 _('[OPTION]... FILE...')),
3700 "rename|mv":
3701 "rename|mv":
3701 (rename,
3702 (rename,
3702 [('A', 'after', None, _('record a rename that has already occurred')),
3703 [('A', 'after', None, _('record a rename that has already occurred')),
3703 ('f', 'force', None,
3704 ('f', 'force', None,
3704 _('forcibly copy over an existing managed file')),
3705 _('forcibly copy over an existing managed file')),
3705 ] + walkopts + dryrunopts,
3706 ] + walkopts + dryrunopts,
3706 _('[OPTION]... SOURCE... DEST')),
3707 _('[OPTION]... SOURCE... DEST')),
3707 "resolve":
3708 "resolve":
3708 (resolve,
3709 (resolve,
3709 [('a', 'all', None, _('select all unresolved files')),
3710 [('a', 'all', None, _('select all unresolved files')),
3710 ('l', 'list', None, _('list state of files needing merge')),
3711 ('l', 'list', None, _('list state of files needing merge')),
3711 ('m', 'mark', None, _('mark files as resolved')),
3712 ('m', 'mark', None, _('mark files as resolved')),
3712 ('u', 'unmark', None, _('unmark files as resolved')),
3713 ('u', 'unmark', None, _('unmark files as resolved')),
3713 ('n', 'no-status', None, _('hide status prefix'))]
3714 ('n', 'no-status', None, _('hide status prefix'))]
3714 + walkopts,
3715 + walkopts,
3715 _('[OPTION]... [FILE]...')),
3716 _('[OPTION]... [FILE]...')),
3716 "revert":
3717 "revert":
3717 (revert,
3718 (revert,
3718 [('a', 'all', None, _('revert all changes when no arguments given')),
3719 [('a', 'all', None, _('revert all changes when no arguments given')),
3719 ('d', 'date', '', _('tipmost revision matching date')),
3720 ('d', 'date', '', _('tipmost revision matching date')),
3720 ('r', 'rev', '', _('revert to the specified revision')),
3721 ('r', 'rev', '', _('revert to the specified revision')),
3721 ('', 'no-backup', None, _('do not save backup copies of files')),
3722 ('', 'no-backup', None, _('do not save backup copies of files')),
3722 ] + walkopts + dryrunopts,
3723 ] + walkopts + dryrunopts,
3723 _('[OPTION]... [-r REV] [NAME]...')),
3724 _('[OPTION]... [-r REV] [NAME]...')),
3724 "rollback": (rollback, []),
3725 "rollback": (rollback, []),
3725 "root": (root, []),
3726 "root": (root, []),
3726 "^serve":
3727 "^serve":
3727 (serve,
3728 (serve,
3728 [('A', 'accesslog', '', _('name of access log file to write to')),
3729 [('A', 'accesslog', '', _('name of access log file to write to')),
3729 ('d', 'daemon', None, _('run server in background')),
3730 ('d', 'daemon', None, _('run server in background')),
3730 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3731 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3731 ('E', 'errorlog', '', _('name of error log file to write to')),
3732 ('E', 'errorlog', '', _('name of error log file to write to')),
3732 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3733 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3733 ('a', 'address', '',
3734 ('a', 'address', '',
3734 _('address to listen on (default: all interfaces)')),
3735 _('address to listen on (default: all interfaces)')),
3735 ('', 'prefix', '',
3736 ('', 'prefix', '',
3736 _('prefix path to serve from (default: server root)')),
3737 _('prefix path to serve from (default: server root)')),
3737 ('n', 'name', '',
3738 ('n', 'name', '',
3738 _('name to show in web pages (default: working directory)')),
3739 _('name to show in web pages (default: working directory)')),
3739 ('', 'webdir-conf', '', _('name of the webdir config file'
3740 ('', 'webdir-conf', '', _('name of the webdir config file'
3740 ' (serve more than one repository)')),
3741 ' (serve more than one repository)')),
3741 ('', 'pid-file', '', _('name of file to write process ID to')),
3742 ('', 'pid-file', '', _('name of file to write process ID to')),
3742 ('', 'stdio', None, _('for remote clients')),
3743 ('', 'stdio', None, _('for remote clients')),
3743 ('t', 'templates', '', _('web templates to use')),
3744 ('t', 'templates', '', _('web templates to use')),
3744 ('', 'style', '', _('template style to use')),
3745 ('', 'style', '', _('template style to use')),
3745 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3746 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3746 ('', 'certificate', '', _('SSL certificate file'))],
3747 ('', 'certificate', '', _('SSL certificate file'))],
3747 _('[OPTION]...')),
3748 _('[OPTION]...')),
3748 "showconfig|debugconfig":
3749 "showconfig|debugconfig":
3749 (showconfig,
3750 (showconfig,
3750 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3751 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3751 _('[-u] [NAME]...')),
3752 _('[-u] [NAME]...')),
3752 "^summary|sum":
3753 "^summary|sum":
3753 (summary,
3754 (summary,
3754 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
3755 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
3755 "^status|st":
3756 "^status|st":
3756 (status,
3757 (status,
3757 [('A', 'all', None, _('show status of all files')),
3758 [('A', 'all', None, _('show status of all files')),
3758 ('m', 'modified', None, _('show only modified files')),
3759 ('m', 'modified', None, _('show only modified files')),
3759 ('a', 'added', None, _('show only added files')),
3760 ('a', 'added', None, _('show only added files')),
3760 ('r', 'removed', None, _('show only removed files')),
3761 ('r', 'removed', None, _('show only removed files')),
3761 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3762 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3762 ('c', 'clean', None, _('show only files without changes')),
3763 ('c', 'clean', None, _('show only files without changes')),
3763 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3764 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3764 ('i', 'ignored', None, _('show only ignored files')),
3765 ('i', 'ignored', None, _('show only ignored files')),
3765 ('n', 'no-status', None, _('hide status prefix')),
3766 ('n', 'no-status', None, _('hide status prefix')),
3766 ('C', 'copies', None, _('show source of copied files')),
3767 ('C', 'copies', None, _('show source of copied files')),
3767 ('0', 'print0', None,
3768 ('0', 'print0', None,
3768 _('end filenames with NUL, for use with xargs')),
3769 _('end filenames with NUL, for use with xargs')),
3769 ('', 'rev', [], _('show difference from revision')),
3770 ('', 'rev', [], _('show difference from revision')),
3770 ('', 'change', '', _('list the changed files of a revision')),
3771 ('', 'change', '', _('list the changed files of a revision')),
3771 ] + walkopts,
3772 ] + walkopts,
3772 _('[OPTION]... [FILE]...')),
3773 _('[OPTION]... [FILE]...')),
3773 "tag":
3774 "tag":
3774 (tag,
3775 (tag,
3775 [('f', 'force', None, _('replace existing tag')),
3776 [('f', 'force', None, _('replace existing tag')),
3776 ('l', 'local', None, _('make the tag local')),
3777 ('l', 'local', None, _('make the tag local')),
3777 ('r', 'rev', '', _('revision to tag')),
3778 ('r', 'rev', '', _('revision to tag')),
3778 ('', 'remove', None, _('remove a tag')),
3779 ('', 'remove', None, _('remove a tag')),
3779 # -l/--local is already there, commitopts cannot be used
3780 # -l/--local is already there, commitopts cannot be used
3780 ('m', 'message', '', _('use <text> as commit message')),
3781 ('m', 'message', '', _('use <text> as commit message')),
3781 ] + commitopts2,
3782 ] + commitopts2,
3782 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3783 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3783 "tags": (tags, [], ''),
3784 "tags": (tags, [], ''),
3784 "tip":
3785 "tip":
3785 (tip,
3786 (tip,
3786 [('p', 'patch', None, _('show patch')),
3787 [('p', 'patch', None, _('show patch')),
3787 ('g', 'git', None, _('use git extended diff format')),
3788 ('g', 'git', None, _('use git extended diff format')),
3788 ] + templateopts,
3789 ] + templateopts,
3789 _('[-p] [-g]')),
3790 _('[-p] [-g]')),
3790 "unbundle":
3791 "unbundle":
3791 (unbundle,
3792 (unbundle,
3792 [('u', 'update', None,
3793 [('u', 'update', None,
3793 _('update to new branch head if changesets were unbundled'))],
3794 _('update to new branch head if changesets were unbundled'))],
3794 _('[-u] FILE...')),
3795 _('[-u] FILE...')),
3795 "^update|up|checkout|co":
3796 "^update|up|checkout|co":
3796 (update,
3797 (update,
3797 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
3798 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
3798 ('c', 'check', None, _('check for uncommitted changes')),
3799 ('c', 'check', None, _('check for uncommitted changes')),
3799 ('d', 'date', '', _('tipmost revision matching date')),
3800 ('d', 'date', '', _('tipmost revision matching date')),
3800 ('r', 'rev', '', _('revision'))],
3801 ('r', 'rev', '', _('revision'))],
3801 _('[-c] [-C] [-d DATE] [[-r] REV]')),
3802 _('[-c] [-C] [-d DATE] [[-r] REV]')),
3802 "verify": (verify, []),
3803 "verify": (verify, []),
3803 "version": (version_, []),
3804 "version": (version_, []),
3804 }
3805 }
3805
3806
3806 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3807 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3807 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3808 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3808 optionalrepo = ("identify paths serve showconfig debugancestor")
3809 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,1166 +1,1166 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # Perforce Defect Tracking Integration Project
3 # Perforce Defect Tracking Integration Project
4 # <http://www.ravenbrook.com/project/p4dti/>
4 # <http://www.ravenbrook.com/project/p4dti/>
5 #
5 #
6 # COVERAGE.PY -- COVERAGE TESTING
6 # COVERAGE.PY -- COVERAGE TESTING
7 #
7 #
8 # Gareth Rees, Ravenbrook Limited, 2001-12-04
8 # Gareth Rees, Ravenbrook Limited, 2001-12-04
9 # Ned Batchelder, 2004-12-12
9 # Ned Batchelder, 2004-12-12
10 # http://nedbatchelder.com/code/modules/coverage.html
10 # http://nedbatchelder.com/code/modules/coverage.html
11 #
11 #
12 #
12 #
13 # 1. INTRODUCTION
13 # 1. INTRODUCTION
14 #
14 #
15 # This module provides coverage testing for Python code.
15 # This module provides coverage testing for Python code.
16 #
16 #
17 # The intended readership is all Python developers.
17 # The intended readership is all Python developers.
18 #
18 #
19 # This document is not confidential.
19 # This document is not confidential.
20 #
20 #
21 # See [GDR 2001-12-04a] for the command-line interface, programmatic
21 # See [GDR 2001-12-04a] for the command-line interface, programmatic
22 # interface and limitations. See [GDR 2001-12-04b] for requirements and
22 # interface and limitations. See [GDR 2001-12-04b] for requirements and
23 # design.
23 # design.
24
24
25 r"""Usage:
25 r"""Usage:
26
26
27 coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
27 coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
28 Execute module, passing the given command-line arguments, collecting
28 Execute module, passing the given command-line arguments, collecting
29 coverage data. With the -p option, write to a temporary file containing
29 coverage data. With the -p option, write to a temporary file containing
30 the machine name and process ID.
30 the machine name and process ID.
31
31
32 coverage.py -e
32 coverage.py -e
33 Erase collected coverage data.
33 Erase collected coverage data.
34
34
35 coverage.py -c
35 coverage.py -c
36 Collect data from multiple coverage files (as created by -p option above)
36 Collect data from multiple coverage files (as created by -p option above)
37 and store it into a single file representing the union of the coverage.
37 and store it into a single file representing the union of the coverage.
38
38
39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
40 Report on the statement coverage for the given files. With the -m
40 Report on the statement coverage for the given files. With the -m
41 option, show line numbers of the statements that weren't executed.
41 option, show line numbers of the statements that weren't executed.
42
42
43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
44 Make annotated copies of the given files, marking statements that
44 Make annotated copies of the given files, marking statements that
45 are executed with > and statements that are missed with !. With
45 are executed with > and statements that are missed with !. With
46 the -d option, make the copies in that directory. Without the -d
46 the -d option, make the copies in that directory. Without the -d
47 option, make each copy in the same directory as the original.
47 option, make each copy in the same directory as the original.
48
48
49 -o dir,dir2,...
49 -o dir,dir2,...
50 Omit reporting or annotating files when their filename path starts with
50 Omit reporting or annotating files when their filename path starts with
51 a directory listed in the omit list.
51 a directory listed in the omit list.
52 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
52 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
53
53
54 Coverage data is saved in the file .coverage by default. Set the
54 Coverage data is saved in the file .coverage by default. Set the
55 COVERAGE_FILE environment variable to save it somewhere else."""
55 COVERAGE_FILE environment variable to save it somewhere else."""
56
56
57 __version__ = "2.85.20080914" # see detailed history at the end of this file.
57 __version__ = "2.85.20080914" # see detailed history at the end of this file.
58
58
59 import compiler
59 import compiler
60 import compiler.visitor
60 import compiler.visitor
61 import glob
61 import glob
62 import os
62 import os
63 import re
63 import re
64 import string
64 import string
65 import symbol
65 import symbol
66 import sys
66 import sys
67 import threading
67 import threading
68 import token
68 import token
69 import types
69 import types
70 import zipimport
70 import zipimport
71 from socket import gethostname
71 from socket import gethostname
72
72
73 # Python version compatibility
73 # Python version compatibility
74 try:
74 try:
75 strclass = basestring # new to 2.3
75 strclass = basestring # new to 2.3
76 except:
76 except:
77 strclass = str
77 strclass = str
78
78
79 # 2. IMPLEMENTATION
79 # 2. IMPLEMENTATION
80 #
80 #
81 # This uses the "singleton" pattern.
81 # This uses the "singleton" pattern.
82 #
82 #
83 # The word "morf" means a module object (from which the source file can
83 # The word "morf" means a module object (from which the source file can
84 # be deduced by suitable manipulation of the __file__ attribute) or a
84 # be deduced by suitable manipulation of the __file__ attribute) or a
85 # filename.
85 # filename.
86 #
86 #
87 # When we generate a coverage report we have to canonicalize every
87 # When we generate a coverage report we have to canonicalize every
88 # filename in the coverage dictionary just in case it refers to the
88 # filename in the coverage dictionary just in case it refers to the
89 # module we are reporting on. It seems a shame to throw away this
89 # module we are reporting on. It seems a shame to throw away this
90 # information so the data in the coverage dictionary is transferred to
90 # information so the data in the coverage dictionary is transferred to
91 # the 'cexecuted' dictionary under the canonical filenames.
91 # the 'cexecuted' dictionary under the canonical filenames.
92 #
92 #
93 # The coverage dictionary is called "c" and the trace function "t". The
93 # The coverage dictionary is called "c" and the trace function "t". The
94 # reason for these short names is that Python looks up variables by name
94 # reason for these short names is that Python looks up variables by name
95 # at runtime and so execution time depends on the length of variables!
95 # at runtime and so execution time depends on the length of variables!
96 # In the bottleneck of this application it's appropriate to abbreviate
96 # In the bottleneck of this application it's appropriate to abbreviate
97 # names to increase speed.
97 # names to increase speed.
98
98
99 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
99 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
100 """ A visitor for a parsed Abstract Syntax Tree which finds executable
100 """ A visitor for a parsed Abstract Syntax Tree which finds executable
101 statements.
101 statements.
102 """
102 """
103 def __init__(self, statements, excluded, suite_spots):
103 def __init__(self, statements, excluded, suite_spots):
104 compiler.visitor.ASTVisitor.__init__(self)
104 compiler.visitor.ASTVisitor.__init__(self)
105 self.statements = statements
105 self.statements = statements
106 self.excluded = excluded
106 self.excluded = excluded
107 self.suite_spots = suite_spots
107 self.suite_spots = suite_spots
108 self.excluding_suite = 0
108 self.excluding_suite = 0
109
109
110 def doRecursive(self, node):
110 def doRecursive(self, node):
111 for n in node.getChildNodes():
111 for n in node.getChildNodes():
112 self.dispatch(n)
112 self.dispatch(n)
113
113
114 visitStmt = visitModule = doRecursive
114 visitStmt = visitModule = doRecursive
115
115
116 def doCode(self, node):
116 def doCode(self, node):
117 if hasattr(node, 'decorators') and node.decorators:
117 if hasattr(node, 'decorators') and node.decorators:
118 self.dispatch(node.decorators)
118 self.dispatch(node.decorators)
119 self.recordAndDispatch(node.code)
119 self.recordAndDispatch(node.code)
120 else:
120 else:
121 self.doSuite(node, node.code)
121 self.doSuite(node, node.code)
122
122
123 visitFunction = visitClass = doCode
123 visitFunction = visitClass = doCode
124
124
125 def getFirstLine(self, node):
125 def getFirstLine(self, node):
126 # Find the first line in the tree node.
126 # Find the first line in the tree node.
127 lineno = node.lineno
127 lineno = node.lineno
128 for n in node.getChildNodes():
128 for n in node.getChildNodes():
129 f = self.getFirstLine(n)
129 f = self.getFirstLine(n)
130 if lineno and f:
130 if lineno and f:
131 lineno = min(lineno, f)
131 lineno = min(lineno, f)
132 else:
132 else:
133 lineno = lineno or f
133 lineno = lineno or f
134 return lineno
134 return lineno
135
135
136 def getLastLine(self, node):
136 def getLastLine(self, node):
137 # Find the first line in the tree node.
137 # Find the first line in the tree node.
138 lineno = node.lineno
138 lineno = node.lineno
139 for n in node.getChildNodes():
139 for n in node.getChildNodes():
140 lineno = max(lineno, self.getLastLine(n))
140 lineno = max(lineno, self.getLastLine(n))
141 return lineno
141 return lineno
142
142
143 def doStatement(self, node):
143 def doStatement(self, node):
144 self.recordLine(self.getFirstLine(node))
144 self.recordLine(self.getFirstLine(node))
145
145
146 visitAssert = visitAssign = visitAssTuple = visitPrint = \
146 visitAssert = visitAssign = visitAssTuple = visitPrint = \
147 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
147 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
148 doStatement
148 doStatement
149
149
150 def visitPass(self, node):
150 def visitPass(self, node):
151 # Pass statements have weird interactions with docstrings. If
151 # Pass statements have weird interactions with docstrings. If
152 # this pass statement is part of one of those pairs, claim
152 # this pass statement is part of one of those pairs, claim
153 # that the statement is on the later of the two lines.
153 # that the statement is on the later of the two lines.
154 l = node.lineno
154 l = node.lineno
155 if l:
155 if l:
156 lines = self.suite_spots.get(l, [l, l])
156 lines = self.suite_spots.get(l, [l, l])
157 self.statements[lines[1]] = 1
157 self.statements[lines[1]] = 1
158
158
159 def visitDiscard(self, node):
159 def visitDiscard(self, node):
160 # Discard nodes are statements that execute an expression, but then
160 # Discard nodes are statements that execute an expression, but then
161 # discard the results. This includes function calls, so we can't
161 # discard the results. This includes function calls, so we can't
162 # ignore them all. But if the expression is a constant, the statement
162 # ignore them all. But if the expression is a constant, the statement
163 # won't be "executed", so don't count it now.
163 # won't be "executed", so don't count it now.
164 if node.expr.__class__.__name__ != 'Const':
164 if node.expr.__class__.__name__ != 'Const':
165 self.doStatement(node)
165 self.doStatement(node)
166
166
167 def recordNodeLine(self, node):
167 def recordNodeLine(self, node):
168 # Stmt nodes often have None, but shouldn't claim the first line of
168 # Stmt nodes often have None, but shouldn't claim the first line of
169 # their children (because the first child might be an ignorable line
169 # their children (because the first child might be an ignorable line
170 # like "global a").
170 # like "global a").
171 if node.__class__.__name__ != 'Stmt':
171 if node.__class__.__name__ != 'Stmt':
172 return self.recordLine(self.getFirstLine(node))
172 return self.recordLine(self.getFirstLine(node))
173 else:
173 else:
174 return 0
174 return 0
175
175
176 def recordLine(self, lineno):
176 def recordLine(self, lineno):
177 # Returns a bool, whether the line is included or excluded.
177 # Returns a bool, whether the line is included or excluded.
178 if lineno:
178 if lineno:
179 # Multi-line tests introducing suites have to get charged to their
179 # Multi-line tests introducing suites have to get charged to their
180 # keyword.
180 # keyword.
181 if lineno in self.suite_spots:
181 if lineno in self.suite_spots:
182 lineno = self.suite_spots[lineno][0]
182 lineno = self.suite_spots[lineno][0]
183 # If we're inside an excluded suite, record that this line was
183 # If we're inside an excluded suite, record that this line was
184 # excluded.
184 # excluded.
185 if self.excluding_suite:
185 if self.excluding_suite:
186 self.excluded[lineno] = 1
186 self.excluded[lineno] = 1
187 return 0
187 return 0
188 # If this line is excluded, or suite_spots maps this line to
188 # If this line is excluded, or suite_spots maps this line to
189 # another line that is exlcuded, then we're excluded.
189 # another line that is exlcuded, then we're excluded.
190 elif self.excluded.has_key(lineno) or \
190 elif self.excluded.has_key(lineno) or \
191 self.suite_spots.has_key(lineno) and \
191 self.suite_spots.has_key(lineno) and \
192 self.excluded.has_key(self.suite_spots[lineno][1]):
192 self.excluded.has_key(self.suite_spots[lineno][1]):
193 return 0
193 return 0
194 # Otherwise, this is an executable line.
194 # Otherwise, this is an executable line.
195 else:
195 else:
196 self.statements[lineno] = 1
196 self.statements[lineno] = 1
197 return 1
197 return 1
198 return 0
198 return 0
199
199
200 default = recordNodeLine
200 default = recordNodeLine
201
201
202 def recordAndDispatch(self, node):
202 def recordAndDispatch(self, node):
203 self.recordNodeLine(node)
203 self.recordNodeLine(node)
204 self.dispatch(node)
204 self.dispatch(node)
205
205
206 def doSuite(self, intro, body, exclude=0):
206 def doSuite(self, intro, body, exclude=0):
207 exsuite = self.excluding_suite
207 exsuite = self.excluding_suite
208 if exclude or (intro and not self.recordNodeLine(intro)):
208 if exclude or (intro and not self.recordNodeLine(intro)):
209 self.excluding_suite = 1
209 self.excluding_suite = 1
210 self.recordAndDispatch(body)
210 self.recordAndDispatch(body)
211 self.excluding_suite = exsuite
211 self.excluding_suite = exsuite
212
212
213 def doPlainWordSuite(self, prevsuite, suite):
213 def doPlainWordSuite(self, prevsuite, suite):
214 # Finding the exclude lines for else's is tricky, because they aren't
214 # Finding the exclude lines for else's is tricky, because they aren't
215 # present in the compiler parse tree. Look at the previous suite,
215 # present in the compiler parse tree. Look at the previous suite,
216 # and find its last line. If any line between there and the else's
216 # and find its last line. If any line between there and the else's
217 # first line are excluded, then we exclude the else.
217 # first line are excluded, then we exclude the else.
218 lastprev = self.getLastLine(prevsuite)
218 lastprev = self.getLastLine(prevsuite)
219 firstelse = self.getFirstLine(suite)
219 firstelse = self.getFirstLine(suite)
220 for l in range(lastprev + 1, firstelse):
220 for l in range(lastprev + 1, firstelse):
221 if self.suite_spots.has_key(l):
221 if self.suite_spots.has_key(l):
222 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
222 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
223 break
223 break
224 else:
224 else:
225 self.doSuite(None, suite)
225 self.doSuite(None, suite)
226
226
227 def doElse(self, prevsuite, node):
227 def doElse(self, prevsuite, node):
228 if node.else_:
228 if node.else_:
229 self.doPlainWordSuite(prevsuite, node.else_)
229 self.doPlainWordSuite(prevsuite, node.else_)
230
230
231 def visitFor(self, node):
231 def visitFor(self, node):
232 self.doSuite(node, node.body)
232 self.doSuite(node, node.body)
233 self.doElse(node.body, node)
233 self.doElse(node.body, node)
234
234
235 visitWhile = visitFor
235 visitWhile = visitFor
236
236
237 def visitIf(self, node):
237 def visitIf(self, node):
238 # The first test has to be handled separately from the rest.
238 # The first test has to be handled separately from the rest.
239 # The first test is credited to the line with the "if", but the others
239 # The first test is credited to the line with the "if", but the others
240 # are credited to the line with the test for the elif.
240 # are credited to the line with the test for the elif.
241 self.doSuite(node, node.tests[0][1])
241 self.doSuite(node, node.tests[0][1])
242 for t, n in node.tests[1:]:
242 for t, n in node.tests[1:]:
243 self.doSuite(t, n)
243 self.doSuite(t, n)
244 self.doElse(node.tests[-1][1], node)
244 self.doElse(node.tests[-1][1], node)
245
245
246 def visitTryExcept(self, node):
246 def visitTryExcept(self, node):
247 self.doSuite(node, node.body)
247 self.doSuite(node, node.body)
248 for i in range(len(node.handlers)):
248 for i in range(len(node.handlers)):
249 a, b, h = node.handlers[i]
249 a, b, h = node.handlers[i]
250 if not a:
250 if not a:
251 # It's a plain "except:". Find the previous suite.
251 # It's a plain "except:". Find the previous suite.
252 if i > 0:
252 if i > 0:
253 prev = node.handlers[i - 1][2]
253 prev = node.handlers[i - 1][2]
254 else:
254 else:
255 prev = node.body
255 prev = node.body
256 self.doPlainWordSuite(prev, h)
256 self.doPlainWordSuite(prev, h)
257 else:
257 else:
258 self.doSuite(a, h)
258 self.doSuite(a, h)
259 self.doElse(node.handlers[-1][2], node)
259 self.doElse(node.handlers[-1][2], node)
260
260
261 def visitTryFinally(self, node):
261 def visitTryFinally(self, node):
262 self.doSuite(node, node.body)
262 self.doSuite(node, node.body)
263 self.doPlainWordSuite(node.body, node.final)
263 self.doPlainWordSuite(node.body, node.final)
264
264
265 def visitWith(self, node):
265 def visitWith(self, node):
266 self.doSuite(node, node.body)
266 self.doSuite(node, node.body)
267
267
268 def visitGlobal(self, node):
268 def visitGlobal(self, node):
269 # "global" statements don't execute like others (they don't call the
269 # "global" statements don't execute like others (they don't call the
270 # trace function), so don't record their line numbers.
270 # trace function), so don't record their line numbers.
271 pass
271 pass
272
272
273 the_coverage = None
273 the_coverage = None
274
274
275 class CoverageException(Exception):
275 class CoverageException(Exception):
276 pass
276 pass
277
277
278 class coverage:
278 class coverage:
279 # Name of the cache file (unless environment variable is set).
279 # Name of the cache file (unless environment variable is set).
280 cache_default = ".coverage"
280 cache_default = ".coverage"
281
281
282 # Environment variable naming the cache file.
282 # Environment variable naming the cache file.
283 cache_env = "COVERAGE_FILE"
283 cache_env = "COVERAGE_FILE"
284
284
285 # A dictionary with an entry for (Python source file name, line number
285 # A dictionary with an entry for (Python source file name, line number
286 # in that file) if that line has been executed.
286 # in that file) if that line has been executed.
287 c = {}
287 c = {}
288
288
289 # A map from canonical Python source file name to a dictionary in
289 # A map from canonical Python source file name to a dictionary in
290 # which there's an entry for each line number that has been
290 # which there's an entry for each line number that has been
291 # executed.
291 # executed.
292 cexecuted = {}
292 cexecuted = {}
293
293
294 # Cache of results of calling the analysis2() method, so that you can
294 # Cache of results of calling the analysis2() method, so that you can
295 # specify both -r and -a without doing double work.
295 # specify both -r and -a without doing double work.
296 analysis_cache = {}
296 analysis_cache = {}
297
297
298 # Cache of results of calling the canonical_filename() method, to
298 # Cache of results of calling the canonical_filename() method, to
299 # avoid duplicating work.
299 # avoid duplicating work.
300 canonical_filename_cache = {}
300 canonical_filename_cache = {}
301
301
302 def __init__(self):
302 def __init__(self):
303 global the_coverage
303 global the_coverage
304 if the_coverage:
304 if the_coverage:
305 raise CoverageException("Only one coverage object allowed.")
305 raise CoverageException("Only one coverage object allowed.")
306 self.usecache = 1
306 self.usecache = 1
307 self.cache = None
307 self.cache = None
308 self.parallel_mode = False
308 self.parallel_mode = False
309 self.exclude_re = ''
309 self.exclude_re = ''
310 self.nesting = 0
310 self.nesting = 0
311 self.cstack = []
311 self.cstack = []
312 self.xstack = []
312 self.xstack = []
313 self.relative_dir = self.abs_file(os.curdir)+os.sep
313 self.relative_dir = self.abs_file(os.curdir)+os.sep
314 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
314 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
315
315
316 # t(f, x, y). This method is passed to sys.settrace as a trace function.
316 # t(f, x, y). This method is passed to sys.settrace as a trace function.
317 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
317 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
318 # the arguments and return value of the trace function.
318 # the arguments and return value of the trace function.
319 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
319 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
320 # objects.
320 # objects.
321 def t(self, f, w, unused): #pragma: no cover
321 def t(self, f, w, unused): #pragma: no cover
322 if w == 'line':
322 if w == 'line':
323 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
323 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
324 #-for c in self.cstack:
324 #-for c in self.cstack:
325 #- c[(f.f_code.co_filename, f.f_lineno)] = 1
325 #- c[(f.f_code.co_filename, f.f_lineno)] = 1
326 return self.t
326 return self.t
327
327
328 def help(self, error=None): #pragma: no cover
328 def help(self, error=None): #pragma: no cover
329 if error:
329 if error:
330 print error
330 print error
331 print
331 print
332 print __doc__
332 print __doc__
333 sys.exit(1)
333 sys.exit(1)
334
334
335 def command_line(self, argv, help_fn=None):
335 def command_line(self, argv, help_fn=None):
336 import getopt
336 import getopt
337 help_fn = help_fn or self.help
337 help_fn = help_fn or self.help
338 settings = {}
338 settings = {}
339 optmap = {
339 optmap = {
340 '-a': 'annotate',
340 '-a': 'annotate',
341 '-c': 'collect',
341 '-c': 'collect',
342 '-d:': 'directory=',
342 '-d:': 'directory=',
343 '-e': 'erase',
343 '-e': 'erase',
344 '-h': 'help',
344 '-h': 'help',
345 '-i': 'ignore-errors',
345 '-i': 'ignore-errors',
346 '-m': 'show-missing',
346 '-m': 'show-missing',
347 '-p': 'parallel-mode',
347 '-p': 'parallel-mode',
348 '-r': 'report',
348 '-r': 'report',
349 '-x': 'execute',
349 '-x': 'execute',
350 '-o:': 'omit=',
350 '-o:': 'omit=',
351 }
351 }
352 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
352 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
353 long_opts = optmap.values()
353 long_opts = optmap.values()
354 options, args = getopt.getopt(argv, short_opts, long_opts)
354 options, args = getopt.getopt(argv, short_opts, long_opts)
355 for o, a in options:
355 for o, a in options:
356 if optmap.has_key(o):
356 if optmap.has_key(o):
357 settings[optmap[o]] = 1
357 settings[optmap[o]] = 1
358 elif optmap.has_key(o + ':'):
358 elif optmap.has_key(o + ':'):
359 settings[optmap[o + ':']] = a
359 settings[optmap[o + ':']] = a
360 elif o[2:] in long_opts:
360 elif o[2:] in long_opts:
361 settings[o[2:]] = 1
361 settings[o[2:]] = 1
362 elif o[2:] + '=' in long_opts:
362 elif o[2:] + '=' in long_opts:
363 settings[o[2:]+'='] = a
363 settings[o[2:]+'='] = a
364 else: #pragma: no cover
364 else: #pragma: no cover
365 # Can't get here, because getopt won't return anything unknown.
365 # Can't get here, because getopt won't return anything unknown.
366 pass
366 pass
367
367
368 if settings.get('help'):
368 if settings.get('help'):
369 help_fn()
369 help_fn()
370
370
371 for i in ['erase', 'execute']:
371 for i in ['erase', 'execute']:
372 for j in ['annotate', 'report', 'collect']:
372 for j in ['annotate', 'report', 'collect']:
373 if settings.get(i) and settings.get(j):
373 if settings.get(i) and settings.get(j):
374 help_fn("You can't specify the '%s' and '%s' "
374 help_fn("You can't specify the '%s' and '%s' "
375 "options at the same time." % (i, j))
375 "options at the same time." % (i, j))
376
376
377 args_needed = (settings.get('execute')
377 args_needed = (settings.get('execute')
378 or settings.get('annotate')
378 or settings.get('annotate')
379 or settings.get('report'))
379 or settings.get('report'))
380 action = (settings.get('erase')
380 action = (settings.get('erase')
381 or settings.get('collect')
381 or settings.get('collect')
382 or args_needed)
382 or args_needed)
383 if not action:
383 if not action:
384 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
384 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
385 if not args_needed and args:
385 if not args_needed and args:
386 help_fn("Unexpected arguments: %s" % " ".join(args))
386 help_fn("Unexpected arguments: %s" % " ".join(args))
387
387
388 self.parallel_mode = settings.get('parallel-mode')
388 self.parallel_mode = settings.get('parallel-mode')
389 self.get_ready()
389 self.get_ready()
390
390
391 if settings.get('erase'):
391 if settings.get('erase'):
392 self.erase()
392 self.erase()
393 if settings.get('execute'):
393 if settings.get('execute'):
394 if not args:
394 if not args:
395 help_fn("Nothing to do.")
395 help_fn("Nothing to do.")
396 sys.argv = args
396 sys.argv = args
397 self.start()
397 self.start()
398 import __main__
398 import __main__
399 sys.path[0] = os.path.dirname(sys.argv[0])
399 sys.path[0] = os.path.dirname(sys.argv[0])
400 execfile(sys.argv[0], __main__.__dict__)
400 execfile(sys.argv[0], __main__.__dict__)
401 if settings.get('collect'):
401 if settings.get('collect'):
402 self.collect()
402 self.collect()
403 if not args:
403 if not args:
404 args = self.cexecuted.keys()
404 args = self.cexecuted.keys()
405
405
406 ignore_errors = settings.get('ignore-errors')
406 ignore_errors = settings.get('ignore-errors')
407 show_missing = settings.get('show-missing')
407 show_missing = settings.get('show-missing')
408 directory = settings.get('directory=')
408 directory = settings.get('directory=')
409
409
410 omit = settings.get('omit=')
410 omit = settings.get('omit=')
411 if omit is not None:
411 if omit is not None:
412 omit = [self.abs_file(p) for p in omit.split(',')]
412 omit = [self.abs_file(p) for p in omit.split(',')]
413 else:
413 else:
414 omit = []
414 omit = []
415
415
416 if settings.get('report'):
416 if settings.get('report'):
417 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
417 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
418 if settings.get('annotate'):
418 if settings.get('annotate'):
419 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
419 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
420
420
421 def use_cache(self, usecache, cache_file=None):
421 def use_cache(self, usecache, cache_file=None):
422 self.usecache = usecache
422 self.usecache = usecache
423 if cache_file and not self.cache:
423 if cache_file and not self.cache:
424 self.cache_default = cache_file
424 self.cache_default = cache_file
425
425
426 def get_ready(self, parallel_mode=False):
426 def get_ready(self, parallel_mode=False):
427 if self.usecache and not self.cache:
427 if self.usecache and not self.cache:
428 self.cache = os.environ.get(self.cache_env, self.cache_default)
428 self.cache = os.environ.get(self.cache_env, self.cache_default)
429 if self.parallel_mode:
429 if self.parallel_mode:
430 self.cache += "." + gethostname() + "." + str(os.getpid())
430 self.cache += "." + gethostname() + "." + str(os.getpid())
431 self.restore()
431 self.restore()
432 self.analysis_cache = {}
432 self.analysis_cache = {}
433
433
434 def start(self, parallel_mode=False):
434 def start(self, parallel_mode=False):
435 self.get_ready()
435 self.get_ready()
436 if self.nesting == 0: #pragma: no cover
436 if self.nesting == 0: #pragma: no cover
437 sys.settrace(self.t)
437 sys.settrace(self.t)
438 if hasattr(threading, 'settrace'):
438 if hasattr(threading, 'settrace'):
439 threading.settrace(self.t)
439 threading.settrace(self.t)
440 self.nesting += 1
440 self.nesting += 1
441
441
442 def stop(self):
442 def stop(self):
443 self.nesting -= 1
443 self.nesting -= 1
444 if self.nesting == 0: #pragma: no cover
444 if self.nesting == 0: #pragma: no cover
445 sys.settrace(None)
445 sys.settrace(None)
446 if hasattr(threading, 'settrace'):
446 if hasattr(threading, 'settrace'):
447 threading.settrace(None)
447 threading.settrace(None)
448
448
449 def erase(self):
449 def erase(self):
450 self.get_ready()
450 self.get_ready()
451 self.c = {}
451 self.c = {}
452 self.analysis_cache = {}
452 self.analysis_cache = {}
453 self.cexecuted = {}
453 self.cexecuted = {}
454 if self.cache and os.path.exists(self.cache):
454 if self.cache and os.path.exists(self.cache):
455 os.remove(self.cache)
455 os.remove(self.cache)
456
456
457 def exclude(self, re):
457 def exclude(self, re):
458 if self.exclude_re:
458 if self.exclude_re:
459 self.exclude_re += "|"
459 self.exclude_re += "|"
460 self.exclude_re += "(" + re + ")"
460 self.exclude_re += "(" + re + ")"
461
461
462 def begin_recursive(self):
462 def begin_recursive(self):
463 self.cstack.append(self.c)
463 self.cstack.append(self.c)
464 self.xstack.append(self.exclude_re)
464 self.xstack.append(self.exclude_re)
465
465
466 def end_recursive(self):
466 def end_recursive(self):
467 self.c = self.cstack.pop()
467 self.c = self.cstack.pop()
468 self.exclude_re = self.xstack.pop()
468 self.exclude_re = self.xstack.pop()
469
469
470 # save(). Save coverage data to the coverage cache.
470 # save(). Save coverage data to the coverage cache.
471
471
472 def save(self):
472 def save(self):
473 if self.usecache and self.cache:
473 if self.usecache and self.cache:
474 self.canonicalize_filenames()
474 self.canonicalize_filenames()
475 cache = open(self.cache, 'wb')
475 cache = open(self.cache, 'wb')
476 import marshal
476 import marshal
477 marshal.dump(self.cexecuted, cache)
477 marshal.dump(self.cexecuted, cache)
478 cache.close()
478 cache.close()
479
479
480 # restore(). Restore coverage data from the coverage cache (if it exists).
480 # restore(). Restore coverage data from the coverage cache (if it exists).
481
481
482 def restore(self):
482 def restore(self):
483 self.c = {}
483 self.c = {}
484 self.cexecuted = {}
484 self.cexecuted = {}
485 assert self.usecache
485 assert self.usecache
486 if os.path.exists(self.cache):
486 if os.path.exists(self.cache):
487 self.cexecuted = self.restore_file(self.cache)
487 self.cexecuted = self.restore_file(self.cache)
488
488
489 def restore_file(self, file_name):
489 def restore_file(self, file_name):
490 try:
490 try:
491 cache = open(file_name, 'rb')
491 cache = open(file_name, 'rb')
492 import marshal
492 import marshal
493 cexecuted = marshal.load(cache)
493 cexecuted = marshal.load(cache)
494 cache.close()
494 cache.close()
495 if isinstance(cexecuted, types.DictType):
495 if isinstance(cexecuted, types.DictType):
496 return cexecuted
496 return cexecuted
497 else:
497 else:
498 return {}
498 return {}
499 except:
499 except:
500 return {}
500 return {}
501
501
502 # collect(). Collect data in multiple files produced by parallel mode
502 # collect(). Collect data in multiple files produced by parallel mode
503
503
504 def collect(self):
504 def collect(self):
505 cache_dir, local = os.path.split(self.cache)
505 cache_dir, local = os.path.split(self.cache)
506 for f in os.listdir(cache_dir or '.'):
506 for f in os.listdir(cache_dir or '.'):
507 if not f.startswith(local):
507 if not f.startswith(local):
508 continue
508 continue
509
509
510 full_path = os.path.join(cache_dir, f)
510 full_path = os.path.join(cache_dir, f)
511 cexecuted = self.restore_file(full_path)
511 cexecuted = self.restore_file(full_path)
512 self.merge_data(cexecuted)
512 self.merge_data(cexecuted)
513
513
514 def merge_data(self, new_data):
514 def merge_data(self, new_data):
515 for file_name, file_data in new_data.items():
515 for file_name, file_data in new_data.items():
516 if self.cexecuted.has_key(file_name):
516 if self.cexecuted.has_key(file_name):
517 self.merge_file_data(self.cexecuted[file_name], file_data)
517 self.merge_file_data(self.cexecuted[file_name], file_data)
518 else:
518 else:
519 self.cexecuted[file_name] = file_data
519 self.cexecuted[file_name] = file_data
520
520
521 def merge_file_data(self, cache_data, new_data):
521 def merge_file_data(self, cache_data, new_data):
522 for line_number in new_data.keys():
522 for line_number in new_data.keys():
523 if not cache_data.has_key(line_number):
523 if not cache_data.has_key(line_number):
524 cache_data[line_number] = new_data[line_number]
524 cache_data[line_number] = new_data[line_number]
525
525
526 def abs_file(self, filename):
526 def abs_file(self, filename):
527 """ Helper function to turn a filename into an absolute normalized
527 """ Helper function to turn a filename into an absolute normalized
528 filename.
528 filename.
529 """
529 """
530 return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
530 return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
531
531
532 def get_zip_data(self, filename):
532 def get_zip_data(self, filename):
533 """ Get data from `filename` if it is a zip file path, or return None
533 """ Get data from `filename` if it is a zip file path, or return None
534 if it is not.
534 if it is not.
535 """
535 """
536 markers = ['.zip'+os.sep, '.egg'+os.sep]
536 markers = ['.zip'+os.sep, '.egg'+os.sep]
537 for marker in markers:
537 for marker in markers:
538 if marker in filename:
538 if marker in filename:
539 parts = filename.split(marker)
539 parts = filename.split(marker)
540 try:
540 try:
541 zi = zipimport.zipimporter(parts[0]+marker[:-1])
541 zi = zipimport.zipimporter(parts[0]+marker[:-1])
542 except zipimport.ZipImportError:
542 except zipimport.ZipImportError:
543 continue
543 continue
544 try:
544 try:
545 data = zi.get_data(parts[1])
545 data = zi.get_data(parts[1])
546 except IOError:
546 except IOError:
547 continue
547 continue
548 return data
548 return data
549 return None
549 return None
550
550
551 # canonical_filename(filename). Return a canonical filename for the
551 # canonical_filename(filename). Return a canonical filename for the
552 # file (that is, an absolute path with no redundant components and
552 # file (that is, an absolute path with no redundant components and
553 # normalized case). See [GDR 2001-12-04b, 3.3].
553 # normalized case). See [GDR 2001-12-04b, 3.3].
554
554
555 def canonical_filename(self, filename):
555 def canonical_filename(self, filename):
556 if not self.canonical_filename_cache.has_key(filename):
556 if not self.canonical_filename_cache.has_key(filename):
557 f = filename
557 f = filename
558 if os.path.isabs(f) and not os.path.exists(f):
558 if os.path.isabs(f) and not os.path.exists(f):
559 if not self.get_zip_data(f):
559 if not self.get_zip_data(f):
560 f = os.path.basename(f)
560 f = os.path.basename(f)
561 if not os.path.isabs(f):
561 if not os.path.isabs(f):
562 for path in [os.curdir] + sys.path:
562 for path in [os.curdir] + sys.path:
563 g = os.path.join(path, f)
563 g = os.path.join(path, f)
564 if os.path.exists(g):
564 if os.path.exists(g):
565 f = g
565 f = g
566 break
566 break
567 cf = self.abs_file(f)
567 cf = self.abs_file(f)
568 self.canonical_filename_cache[filename] = cf
568 self.canonical_filename_cache[filename] = cf
569 return self.canonical_filename_cache[filename]
569 return self.canonical_filename_cache[filename]
570
570
571 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
571 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
572 # canonicalizing filenames on the way. Clear the "c" map.
572 # canonicalizing filenames on the way. Clear the "c" map.
573
573
574 def canonicalize_filenames(self):
574 def canonicalize_filenames(self):
575 for filename, lineno in self.c.keys():
575 for filename, lineno in self.c.keys():
576 if filename == '<string>':
576 if filename == '<string>':
577 # Can't do anything useful with exec'd strings, so skip them.
577 # Can't do anything useful with exec'd strings, so skip them.
578 continue
578 continue
579 f = self.canonical_filename(filename)
579 f = self.canonical_filename(filename)
580 if not self.cexecuted.has_key(f):
580 if not self.cexecuted.has_key(f):
581 self.cexecuted[f] = {}
581 self.cexecuted[f] = {}
582 self.cexecuted[f][lineno] = 1
582 self.cexecuted[f][lineno] = 1
583 self.c = {}
583 self.c = {}
584
584
585 # morf_filename(morf). Return the filename for a module or file.
585 # morf_filename(morf). Return the filename for a module or file.
586
586
587 def morf_filename(self, morf):
587 def morf_filename(self, morf):
588 if hasattr(morf, '__file__'):
588 if hasattr(morf, '__file__'):
589 f = morf.__file__
589 f = morf.__file__
590 else:
590 else:
591 f = morf
591 f = morf
592 return self.canonical_filename(f)
592 return self.canonical_filename(f)
593
593
594 # analyze_morf(morf). Analyze the module or filename passed as
594 # analyze_morf(morf). Analyze the module or filename passed as
595 # the argument. If the source code can't be found, raise an error.
595 # the argument. If the source code can't be found, raise an error.
596 # Otherwise, return a tuple of (1) the canonical filename of the
596 # Otherwise, return a tuple of (1) the canonical filename of the
597 # source code for the module, (2) a list of lines of statements
597 # source code for the module, (2) a list of lines of statements
598 # in the source code, (3) a list of lines of excluded statements,
598 # in the source code, (3) a list of lines of excluded statements,
599 # and (4), a map of line numbers to multi-line line number ranges, for
599 # and (4), a map of line numbers to multi-line line number ranges, for
600 # statements that cross lines.
600 # statements that cross lines.
601 def analyze_morf(self, morf):
601 def analyze_morf(self, morf):
602 if self.analysis_cache.has_key(morf):
602 if self.analysis_cache.has_key(morf):
603 return self.analysis_cache[morf]
603 return self.analysis_cache[morf]
604 filename = self.morf_filename(morf)
604 filename = self.morf_filename(morf)
605 ext = os.path.splitext(filename)[1]
605 ext = os.path.splitext(filename)[1]
606 source, sourcef = None, None
606 source, sourcef = None, None
607 if ext == '.pyc':
607 if ext == '.pyc':
608 if not os.path.exists(filename[:-1]):
608 if not os.path.exists(filename[:-1]):
609 source = self.get_zip_data(filename[:-1])
609 source = self.get_zip_data(filename[:-1])
610 if not source:
610 if not source:
611 raise CoverageException(
611 raise CoverageException(
612 "No source for compiled code '%s'." % filename
612 "No source for compiled code '%s'." % filename
613 )
613 )
614 filename = filename[:-1]
614 filename = filename[:-1]
615 if not source:
615 if not source:
616 sourcef = open(filename, 'rU')
616 sourcef = open(filename, 'rU')
617 source = sourcef.read()
617 source = sourcef.read()
618 try:
618 try:
619 lines, excluded_lines, line_map = self.find_executable_statements(
619 lines, excluded_lines, line_map = self.find_executable_statements(
620 source, exclude=self.exclude_re
620 source, exclude=self.exclude_re
621 )
621 )
622 except SyntaxError, synerr:
622 except SyntaxError, synerr:
623 raise CoverageException(
623 raise CoverageException(
624 "Couldn't parse '%s' as Python source: '%s' at line %d" %
624 "Couldn't parse '%s' as Python source: '%s' at line %d" %
625 (filename, synerr.msg, synerr.lineno)
625 (filename, synerr.msg, synerr.lineno)
626 )
626 )
627 if sourcef:
627 if sourcef:
628 sourcef.close()
628 sourcef.close()
629 result = filename, lines, excluded_lines, line_map
629 result = filename, lines, excluded_lines, line_map
630 self.analysis_cache[morf] = result
630 self.analysis_cache[morf] = result
631 return result
631 return result
632
632
633 def first_line_of_tree(self, tree):
633 def first_line_of_tree(self, tree):
634 while True:
634 while True:
635 if len(tree) == 3 and type(tree[2]) == type(1):
635 if len(tree) == 3 and type(tree[2]) == type(1):
636 return tree[2]
636 return tree[2]
637 tree = tree[1]
637 tree = tree[1]
638
638
639 def last_line_of_tree(self, tree):
639 def last_line_of_tree(self, tree):
640 while True:
640 while True:
641 if len(tree) == 3 and type(tree[2]) == type(1):
641 if len(tree) == 3 and type(tree[2]) == type(1):
642 return tree[2]
642 return tree[2]
643 tree = tree[-1]
643 tree = tree[-1]
644
644
645 def find_docstring_pass_pair(self, tree, spots):
645 def find_docstring_pass_pair(self, tree, spots):
646 for i in range(1, len(tree)):
646 for i in range(1, len(tree)):
647 if (self.is_string_constant(tree[i]) and
647 if (self.is_string_constant(tree[i]) and
648 self.is_pass_stmt(tree[i + 1])):
648 self.is_pass_stmt(tree[i + 1])):
649 first_line = self.first_line_of_tree(tree[i])
649 first_line = self.first_line_of_tree(tree[i])
650 last_line = self.last_line_of_tree(tree[i + 1])
650 last_line = self.last_line_of_tree(tree[i + 1])
651 self.record_multiline(spots, first_line, last_line)
651 self.record_multiline(spots, first_line, last_line)
652
652
653 def is_string_constant(self, tree):
653 def is_string_constant(self, tree):
654 try:
654 try:
655 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
655 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
656 except:
656 except:
657 return False
657 return False
658
658
659 def is_pass_stmt(self, tree):
659 def is_pass_stmt(self, tree):
660 try:
660 try:
661 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
661 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
662 except:
662 except:
663 return False
663 return False
664
664
665 def record_multiline(self, spots, i, j):
665 def record_multiline(self, spots, i, j):
666 for l in range(i, j + 1):
666 for l in range(i, j + 1):
667 spots[l] = (i, j)
667 spots[l] = (i, j)
668
668
669 def get_suite_spots(self, tree, spots):
669 def get_suite_spots(self, tree, spots):
670 """ Analyze a parse tree to find suite introducers which span a number
670 """ Analyze a parse tree to find suite introducers which span a number
671 of lines.
671 of lines.
672 """
672 """
673 for i in range(1, len(tree)):
673 for i in range(1, len(tree)):
674 if type(tree[i]) == type(()):
674 if type(tree[i]) == type(()):
675 if tree[i][0] == symbol.suite:
675 if tree[i][0] == symbol.suite:
676 # Found a suite, look back for the colon and keyword.
676 # Found a suite, look back for the colon and keyword.
677 lineno_colon = lineno_word = None
677 lineno_colon = lineno_word = None
678 for j in range(i - 1, 0, -1):
678 for j in range(i - 1, 0, -1):
679 if tree[j][0] == token.COLON:
679 if tree[j][0] == token.COLON:
680 # Colons are never executed themselves: we want the
680 # Colons are never executed themselves: we want the
681 # line number of the last token before the colon.
681 # line number of the last token before the colon.
682 lineno_colon = self.last_line_of_tree(tree[j - 1])
682 lineno_colon = self.last_line_of_tree(tree[j - 1])
683 elif tree[j][0] == token.NAME:
683 elif tree[j][0] == token.NAME:
684 if tree[j][1] == 'elif':
684 if tree[j][1] == 'elif':
685 # Find the line number of the first
685 # Find the line number of the first
686 # non-terminal after the keyword.
686 # non-terminal after the keyword.
687 t = tree[j + 1]
687 t = tree[j + 1]
688 while t and token.ISNONTERMINAL(t[0]):
688 while t and token.ISNONTERMINAL(t[0]):
689 t = t[1]
689 t = t[1]
690 if t:
690 if t:
691 lineno_word = t[2]
691 lineno_word = t[2]
692 else:
692 else:
693 lineno_word = tree[j][2]
693 lineno_word = tree[j][2]
694 break
694 break
695 elif tree[j][0] == symbol.except_clause:
695 elif tree[j][0] == symbol.except_clause:
696 # "except" clauses look like:
696 # "except" clauses look like:
697 # ('except_clause', ('NAME', 'except', lineno),...)
697 # ('except_clause', ('NAME', 'except', lineno),...)
698 if tree[j][1][0] == token.NAME:
698 if tree[j][1][0] == token.NAME:
699 lineno_word = tree[j][1][2]
699 lineno_word = tree[j][1][2]
700 break
700 break
701 if lineno_colon and lineno_word:
701 if lineno_colon and lineno_word:
702 # Found colon and keyword, mark all the lines
702 # Found colon and keyword, mark all the lines
703 # between the two with the two line numbers.
703 # between the two with the two line numbers.
704 self.record_multiline(spots, lineno_word, lineno_colon)
704 self.record_multiline(spots, lineno_word, lineno_colon)
705
705
706 # "pass" statements are tricky: different versions
706 # "pass" statements are tricky: different versions
707 # of Python treat them differently, especially in
707 # of Python treat them differently, especially in
708 # the common case of a function with a doc string
708 # the common case of a function with a doc string
709 # and a single pass statement.
709 # and a single pass statement.
710 self.find_docstring_pass_pair(tree[i], spots)
710 self.find_docstring_pass_pair(tree[i], spots)
711 elif tree[i][0] == symbol.simple_stmt:
711 elif tree[i][0] == symbol.simple_stmt:
712 first_line = self.first_line_of_tree(tree[i])
712 first_line = self.first_line_of_tree(tree[i])
713 last_line = self.last_line_of_tree(tree[i])
713 last_line = self.last_line_of_tree(tree[i])
714 if first_line != last_line:
714 if first_line != last_line:
715 self.record_multiline(spots, first_line, last_line)
715 self.record_multiline(spots, first_line, last_line)
716 self.get_suite_spots(tree[i], spots)
716 self.get_suite_spots(tree[i], spots)
717
717
718 def find_executable_statements(self, text, exclude=None):
718 def find_executable_statements(self, text, exclude=None):
719 # Find lines which match an exclusion pattern.
719 # Find lines which match an exclusion pattern.
720 excluded = {}
720 excluded = {}
721 suite_spots = {}
721 suite_spots = {}
722 if exclude:
722 if exclude:
723 reExclude = re.compile(exclude)
723 reExclude = re.compile(exclude)
724 lines = text.split('\n')
724 lines = text.split('\n')
725 for i in range(len(lines)):
725 for i in range(len(lines)):
726 if reExclude.search(lines[i]):
726 if reExclude.search(lines[i]):
727 excluded[i + 1] = 1
727 excluded[i + 1] = 1
728
728
729 # Parse the code and analyze the parse tree to find out which statements
729 # Parse the code and analyze the parse tree to find out which statements
730 # are multiline, and where suites begin and end.
730 # are multiline, and where suites begin and end.
731 import parser
731 import parser
732 tree = parser.suite(text+'\n\n').totuple(1)
732 tree = parser.suite(text+'\n\n').totuple(1)
733 self.get_suite_spots(tree, suite_spots)
733 self.get_suite_spots(tree, suite_spots)
734 #print "Suite spots:", suite_spots
734 #print "Suite spots:", suite_spots
735
735
736 # Use the compiler module to parse the text and find the executable
736 # Use the compiler module to parse the text and find the executable
737 # statements. We add newlines to be impervious to final partial lines.
737 # statements. We add newlines to be impervious to final partial lines.
738 statements = {}
738 statements = {}
739 ast = compiler.parse(text+'\n\n')
739 ast = compiler.parse(text+'\n\n')
740 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
740 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
741 compiler.walk(ast, visitor, walker=visitor)
741 compiler.walk(ast, visitor, walker=visitor)
742
742
743 lines = statements.keys()
743 lines = statements.keys()
744 lines.sort()
744 lines.sort()
745 excluded_lines = excluded.keys()
745 excluded_lines = excluded.keys()
746 excluded_lines.sort()
746 excluded_lines.sort()
747 return lines, excluded_lines, suite_spots
747 return lines, excluded_lines, suite_spots
748
748
749 # format_lines(statements, lines). Format a list of line numbers
749 # format_lines(statements, lines). Format a list of line numbers
750 # for printing by coalescing groups of lines as long as the lines
750 # for printing by coalescing groups of lines as long as the lines
751 # represent consecutive statements. This will coalesce even if
751 # represent consecutive statements. This will coalesce even if
752 # there are gaps between statements, so if statements =
752 # there are gaps between statements, so if statements =
753 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
753 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
754 # format_lines will return "1-2, 5-11, 13-14".
754 # format_lines will return "1-2, 5-11, 13-14".
755
755
756 def format_lines(self, statements, lines):
756 def format_lines(self, statements, lines):
757 pairs = []
757 pairs = []
758 i = 0
758 i = 0
759 j = 0
759 j = 0
760 start = None
760 start = None
761 pairs = []
761 pairs = []
762 while i < len(statements) and j < len(lines):
762 while i < len(statements) and j < len(lines):
763 if statements[i] == lines[j]:
763 if statements[i] == lines[j]:
764 if start == None:
764 if start == None:
765 start = lines[j]
765 start = lines[j]
766 end = lines[j]
766 end = lines[j]
767 j = j + 1
767 j = j + 1
768 elif start:
768 elif start:
769 pairs.append((start, end))
769 pairs.append((start, end))
770 start = None
770 start = None
771 i = i + 1
771 i = i + 1
772 if start:
772 if start:
773 pairs.append((start, end))
773 pairs.append((start, end))
774 def stringify(pair):
774 def stringify(pair):
775 start, end = pair
775 start, end = pair
776 if start == end:
776 if start == end:
777 return "%d" % start
777 return "%d" % start
778 else:
778 else:
779 return "%d-%d" % (start, end)
779 return "%d-%d" % (start, end)
780 ret = string.join(map(stringify, pairs), ", ")
780 ret = string.join(map(stringify, pairs), ", ")
781 return ret
781 return ret
782
782
783 # Backward compatibility with version 1.
783 # Backward compatibility with version 1.
784 def analysis(self, morf):
784 def analysis(self, morf):
785 f, s, _, m, mf = self.analysis2(morf)
785 f, s, _, m, mf = self.analysis2(morf)
786 return f, s, m, mf
786 return f, s, m, mf
787
787
788 def analysis2(self, morf):
788 def analysis2(self, morf):
789 filename, statements, excluded, line_map = self.analyze_morf(morf)
789 filename, statements, excluded, line_map = self.analyze_morf(morf)
790 self.canonicalize_filenames()
790 self.canonicalize_filenames()
791 if not self.cexecuted.has_key(filename):
791 if not self.cexecuted.has_key(filename):
792 self.cexecuted[filename] = {}
792 self.cexecuted[filename] = {}
793 missing = []
793 missing = []
794 for line in statements:
794 for line in statements:
795 lines = line_map.get(line, [line, line])
795 lines = line_map.get(line, [line, line])
796 for l in range(lines[0], lines[1]+1):
796 for l in range(lines[0], lines[1]+1):
797 if self.cexecuted[filename].has_key(l):
797 if self.cexecuted[filename].has_key(l):
798 break
798 break
799 else:
799 else:
800 missing.append(line)
800 missing.append(line)
801 return (filename, statements, excluded, missing,
801 return (filename, statements, excluded, missing,
802 self.format_lines(statements, missing))
802 self.format_lines(statements, missing))
803
803
804 def relative_filename(self, filename):
804 def relative_filename(self, filename):
805 """ Convert filename to relative filename from self.relative_dir.
805 """ Convert filename to relative filename from self.relative_dir.
806 """
806 """
807 return filename.replace(self.relative_dir, "")
807 return filename.replace(self.relative_dir, "")
808
808
809 def morf_name(self, morf):
809 def morf_name(self, morf):
810 """ Return the name of morf as used in report.
810 """ Return the name of morf as used in report.
811 """
811 """
812 if hasattr(morf, '__name__'):
812 if hasattr(morf, '__name__'):
813 return morf.__name__
813 return morf.__name__
814 else:
814 else:
815 return self.relative_filename(os.path.splitext(morf)[0])
815 return self.relative_filename(os.path.splitext(morf)[0])
816
816
817 def filter_by_prefix(self, morfs, omit_prefixes):
817 def filter_by_prefix(self, morfs, omit_prefixes):
818 """ Return list of morfs where the morf name does not begin
818 """ Return list of morfs where the morf name does not begin
819 with any one of the omit_prefixes.
819 with any one of the omit_prefixes.
820 """
820 """
821 filtered_morfs = []
821 filtered_morfs = []
822 for morf in morfs:
822 for morf in morfs:
823 for prefix in omit_prefixes:
823 for prefix in omit_prefixes:
824 if self.morf_name(morf).startswith(prefix):
824 if self.morf_name(morf).startswith(prefix):
825 break
825 break
826 else:
826 else:
827 filtered_morfs.append(morf)
827 filtered_morfs.append(morf)
828
828
829 return filtered_morfs
829 return filtered_morfs
830
830
831 def morf_name_compare(self, x, y):
831 def morf_name_compare(self, x, y):
832 return cmp(self.morf_name(x), self.morf_name(y))
832 return cmp(self.morf_name(x), self.morf_name(y))
833
833
834 def report(self, morfs, show_missing=1, ignore_errors=0, file=None,
834 def report(self, morfs, show_missing=1, ignore_errors=0, file=None,
835 omit_prefixes=[]):
835 omit_prefixes=[]):
836 if not isinstance(morfs, types.ListType):
836 if not isinstance(morfs, types.ListType):
837 morfs = [morfs]
837 morfs = [morfs]
838 # On windows, the shell doesn't expand wildcards. Do it here.
838 # On windows, the shell doesn't expand wildcards. Do it here.
839 globbed = []
839 globbed = []
840 for morf in morfs:
840 for morf in morfs:
841 if isinstance(morf, strclass):
841 if isinstance(morf, strclass):
842 globbed.extend(glob.glob(morf))
842 globbed.extend(glob.glob(morf))
843 else:
843 else:
844 globbed.append(morf)
844 globbed.append(morf)
845 morfs = globbed
845 morfs = globbed
846
846
847 morfs = self.filter_by_prefix(morfs, omit_prefixes)
847 morfs = self.filter_by_prefix(morfs, omit_prefixes)
848 morfs.sort(self.morf_name_compare)
848 morfs.sort(self.morf_name_compare)
849
849
850 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
850 max_name = max([5] + map(len, map(self.morf_name, morfs)))
851 fmt_name = "%%- %ds " % max_name
851 fmt_name = "%%- %ds " % max_name
852 fmt_err = fmt_name + "%s: %s"
852 fmt_err = fmt_name + "%s: %s"
853 header = fmt_name % "Name" + " Stmts Exec Cover"
853 header = fmt_name % "Name" + " Stmts Exec Cover"
854 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
854 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
855 if show_missing:
855 if show_missing:
856 header = header + " Missing"
856 header = header + " Missing"
857 fmt_coverage = fmt_coverage + " %s"
857 fmt_coverage = fmt_coverage + " %s"
858 if not file:
858 if not file:
859 file = sys.stdout
859 file = sys.stdout
860 print >> file, header
860 print >> file, header
861 print >> file, "-" * len(header)
861 print >> file, "-" * len(header)
862 total_statements = 0
862 total_statements = 0
863 total_executed = 0
863 total_executed = 0
864 for morf in morfs:
864 for morf in morfs:
865 name = self.morf_name(morf)
865 name = self.morf_name(morf)
866 try:
866 try:
867 _, statements, _, missing, readable = self.analysis2(morf)
867 _, statements, _, missing, readable = self.analysis2(morf)
868 n = len(statements)
868 n = len(statements)
869 m = n - len(missing)
869 m = n - len(missing)
870 if n > 0:
870 if n > 0:
871 pc = 100.0 * m / n
871 pc = 100.0 * m / n
872 else:
872 else:
873 pc = 100.0
873 pc = 100.0
874 args = (name, n, m, pc)
874 args = (name, n, m, pc)
875 if show_missing:
875 if show_missing:
876 args = args + (readable,)
876 args = args + (readable,)
877 print >>file, fmt_coverage % args
877 print >>file, fmt_coverage % args
878 total_statements = total_statements + n
878 total_statements = total_statements + n
879 total_executed = total_executed + m
879 total_executed = total_executed + m
880 except KeyboardInterrupt: #pragma: no cover
880 except KeyboardInterrupt: #pragma: no cover
881 raise
881 raise
882 except:
882 except:
883 if not ignore_errors:
883 if not ignore_errors:
884 typ, msg = sys.exc_info()[:2]
884 typ, msg = sys.exc_info()[:2]
885 print >>file, fmt_err % (name, typ, msg)
885 print >>file, fmt_err % (name, typ, msg)
886 if len(morfs) > 1:
886 if len(morfs) > 1:
887 print >>file, "-" * len(header)
887 print >>file, "-" * len(header)
888 if total_statements > 0:
888 if total_statements > 0:
889 pc = 100.0 * total_executed / total_statements
889 pc = 100.0 * total_executed / total_statements
890 else:
890 else:
891 pc = 100.0
891 pc = 100.0
892 args = ("TOTAL", total_statements, total_executed, pc)
892 args = ("TOTAL", total_statements, total_executed, pc)
893 if show_missing:
893 if show_missing:
894 args = args + ("",)
894 args = args + ("",)
895 print >>file, fmt_coverage % args
895 print >>file, fmt_coverage % args
896
896
897 # annotate(morfs, ignore_errors).
897 # annotate(morfs, ignore_errors).
898
898
899 blank_re = re.compile(r"\s*(#|$)")
899 blank_re = re.compile(r"\s*(#|$)")
900 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
900 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
901
901
902 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
902 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
903 morfs = self.filter_by_prefix(morfs, omit_prefixes)
903 morfs = self.filter_by_prefix(morfs, omit_prefixes)
904 for morf in morfs:
904 for morf in morfs:
905 try:
905 try:
906 filename, statements, excluded, missing, _ = self.analysis2(morf)
906 filename, statements, excluded, missing, _ = self.analysis2(morf)
907 self.annotate_file(filename, statements, excluded, missing,
907 self.annotate_file(filename, statements, excluded, missing,
908 directory)
908 directory)
909 except KeyboardInterrupt:
909 except KeyboardInterrupt:
910 raise
910 raise
911 except:
911 except:
912 if not ignore_errors:
912 if not ignore_errors:
913 raise
913 raise
914
914
915 def annotate_file(self, filename, statements, excluded, missing,
915 def annotate_file(self, filename, statements, excluded, missing,
916 directory=None):
916 directory=None):
917 source = open(filename, 'r')
917 source = open(filename, 'r')
918 if directory:
918 if directory:
919 dest_file = os.path.join(directory,
919 dest_file = os.path.join(directory,
920 os.path.basename(filename)
920 os.path.basename(filename)
921 + ',cover')
921 + ',cover')
922 else:
922 else:
923 dest_file = filename + ',cover'
923 dest_file = filename + ',cover'
924 dest = open(dest_file, 'w')
924 dest = open(dest_file, 'w')
925 lineno = 0
925 lineno = 0
926 i = 0
926 i = 0
927 j = 0
927 j = 0
928 covered = 1
928 covered = 1
929 while 1:
929 while 1:
930 line = source.readline()
930 line = source.readline()
931 if line == '':
931 if line == '':
932 break
932 break
933 lineno = lineno + 1
933 lineno = lineno + 1
934 while i < len(statements) and statements[i] < lineno:
934 while i < len(statements) and statements[i] < lineno:
935 i = i + 1
935 i = i + 1
936 while j < len(missing) and missing[j] < lineno:
936 while j < len(missing) and missing[j] < lineno:
937 j = j + 1
937 j = j + 1
938 if i < len(statements) and statements[i] == lineno:
938 if i < len(statements) and statements[i] == lineno:
939 covered = j >= len(missing) or missing[j] > lineno
939 covered = j >= len(missing) or missing[j] > lineno
940 if self.blank_re.match(line):
940 if self.blank_re.match(line):
941 dest.write(' ')
941 dest.write(' ')
942 elif self.else_re.match(line):
942 elif self.else_re.match(line):
943 # Special logic for lines containing only 'else:'.
943 # Special logic for lines containing only 'else:'.
944 # See [GDR 2001-12-04b, 3.2].
944 # See [GDR 2001-12-04b, 3.2].
945 if i >= len(statements) and j >= len(missing):
945 if i >= len(statements) and j >= len(missing):
946 dest.write('! ')
946 dest.write('! ')
947 elif i >= len(statements) or j >= len(missing):
947 elif i >= len(statements) or j >= len(missing):
948 dest.write('> ')
948 dest.write('> ')
949 elif statements[i] == missing[j]:
949 elif statements[i] == missing[j]:
950 dest.write('! ')
950 dest.write('! ')
951 else:
951 else:
952 dest.write('> ')
952 dest.write('> ')
953 elif lineno in excluded:
953 elif lineno in excluded:
954 dest.write('- ')
954 dest.write('- ')
955 elif covered:
955 elif covered:
956 dest.write('> ')
956 dest.write('> ')
957 else:
957 else:
958 dest.write('! ')
958 dest.write('! ')
959 dest.write(line)
959 dest.write(line)
960 source.close()
960 source.close()
961 dest.close()
961 dest.close()
962
962
963 # Singleton object.
963 # Singleton object.
964 the_coverage = coverage()
964 the_coverage = coverage()
965
965
966 # Module functions call methods in the singleton object.
966 # Module functions call methods in the singleton object.
967 def use_cache(*args, **kw):
967 def use_cache(*args, **kw):
968 return the_coverage.use_cache(*args, **kw)
968 return the_coverage.use_cache(*args, **kw)
969
969
970 def start(*args, **kw):
970 def start(*args, **kw):
971 return the_coverage.start(*args, **kw)
971 return the_coverage.start(*args, **kw)
972
972
973 def stop(*args, **kw):
973 def stop(*args, **kw):
974 return the_coverage.stop(*args, **kw)
974 return the_coverage.stop(*args, **kw)
975
975
976 def erase(*args, **kw):
976 def erase(*args, **kw):
977 return the_coverage.erase(*args, **kw)
977 return the_coverage.erase(*args, **kw)
978
978
979 def begin_recursive(*args, **kw):
979 def begin_recursive(*args, **kw):
980 return the_coverage.begin_recursive(*args, **kw)
980 return the_coverage.begin_recursive(*args, **kw)
981
981
982 def end_recursive(*args, **kw):
982 def end_recursive(*args, **kw):
983 return the_coverage.end_recursive(*args, **kw)
983 return the_coverage.end_recursive(*args, **kw)
984
984
985 def exclude(*args, **kw):
985 def exclude(*args, **kw):
986 return the_coverage.exclude(*args, **kw)
986 return the_coverage.exclude(*args, **kw)
987
987
988 def analysis(*args, **kw):
988 def analysis(*args, **kw):
989 return the_coverage.analysis(*args, **kw)
989 return the_coverage.analysis(*args, **kw)
990
990
991 def analysis2(*args, **kw):
991 def analysis2(*args, **kw):
992 return the_coverage.analysis2(*args, **kw)
992 return the_coverage.analysis2(*args, **kw)
993
993
994 def report(*args, **kw):
994 def report(*args, **kw):
995 return the_coverage.report(*args, **kw)
995 return the_coverage.report(*args, **kw)
996
996
997 def annotate(*args, **kw):
997 def annotate(*args, **kw):
998 return the_coverage.annotate(*args, **kw)
998 return the_coverage.annotate(*args, **kw)
999
999
1000 def annotate_file(*args, **kw):
1000 def annotate_file(*args, **kw):
1001 return the_coverage.annotate_file(*args, **kw)
1001 return the_coverage.annotate_file(*args, **kw)
1002
1002
1003 # Save coverage data when Python exits. (The atexit module wasn't
1003 # Save coverage data when Python exits. (The atexit module wasn't
1004 # introduced until Python 2.0, so use sys.exitfunc when it's not
1004 # introduced until Python 2.0, so use sys.exitfunc when it's not
1005 # available.)
1005 # available.)
1006 try:
1006 try:
1007 import atexit
1007 import atexit
1008 atexit.register(the_coverage.save)
1008 atexit.register(the_coverage.save)
1009 except ImportError:
1009 except ImportError:
1010 sys.exitfunc = the_coverage.save
1010 sys.exitfunc = the_coverage.save
1011
1011
1012 def main():
1012 def main():
1013 the_coverage.command_line(sys.argv[1:])
1013 the_coverage.command_line(sys.argv[1:])
1014
1014
1015 # Command-line interface.
1015 # Command-line interface.
1016 if __name__ == '__main__':
1016 if __name__ == '__main__':
1017 main()
1017 main()
1018
1018
1019
1019
1020 # A. REFERENCES
1020 # A. REFERENCES
1021 #
1021 #
1022 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
1022 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
1023 # Ravenbrook Limited; 2001-12-04;
1023 # Ravenbrook Limited; 2001-12-04;
1024 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
1024 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
1025 #
1025 #
1026 # [GDR 2001-12-04b] "Statement coverage for Python: design and
1026 # [GDR 2001-12-04b] "Statement coverage for Python: design and
1027 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
1027 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
1028 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
1028 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
1029 #
1029 #
1030 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
1030 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
1031 # Guide van Rossum; 2001-07-20;
1031 # Guide van Rossum; 2001-07-20;
1032 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
1032 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
1033 #
1033 #
1034 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
1034 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
1035 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
1035 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
1036 #
1036 #
1037 #
1037 #
1038 # B. DOCUMENT HISTORY
1038 # B. DOCUMENT HISTORY
1039 #
1039 #
1040 # 2001-12-04 GDR Created.
1040 # 2001-12-04 GDR Created.
1041 #
1041 #
1042 # 2001-12-06 GDR Added command-line interface and source code
1042 # 2001-12-06 GDR Added command-line interface and source code
1043 # annotation.
1043 # annotation.
1044 #
1044 #
1045 # 2001-12-09 GDR Moved design and interface to separate documents.
1045 # 2001-12-09 GDR Moved design and interface to separate documents.
1046 #
1046 #
1047 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
1047 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
1048 # simultaneous -e and -x, or -a and -r.
1048 # simultaneous -e and -x, or -a and -r.
1049 #
1049 #
1050 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
1050 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
1051 # only needs to be done once when you specify -a and -r.
1051 # only needs to be done once when you specify -a and -r.
1052 #
1052 #
1053 # 2001-12-13 GDR Improved speed while recording. Portable between
1053 # 2001-12-13 GDR Improved speed while recording. Portable between
1054 # Python 1.5.2 and 2.1.1.
1054 # Python 1.5.2 and 2.1.1.
1055 #
1055 #
1056 # 2002-01-03 GDR Module-level functions work correctly.
1056 # 2002-01-03 GDR Module-level functions work correctly.
1057 #
1057 #
1058 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
1058 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
1059 # so that it matches the value the program would get if it were run on
1059 # so that it matches the value the program would get if it were run on
1060 # its own.
1060 # its own.
1061 #
1061 #
1062 # 2004-12-12 NMB Significant code changes.
1062 # 2004-12-12 NMB Significant code changes.
1063 # - Finding executable statements has been rewritten so that docstrings and
1063 # - Finding executable statements has been rewritten so that docstrings and
1064 # other quirks of Python execution aren't mistakenly identified as missing
1064 # other quirks of Python execution aren't mistakenly identified as missing
1065 # lines.
1065 # lines.
1066 # - Lines can be excluded from consideration, even entire suites of lines.
1066 # - Lines can be excluded from consideration, even entire suites of lines.
1067 # - The filesystem cache of covered lines can be disabled programmatically.
1067 # - The filesystem cache of covered lines can be disabled programmatically.
1068 # - Modernized the code.
1068 # - Modernized the code.
1069 #
1069 #
1070 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
1070 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
1071 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
1071 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
1072 # 'annotate_file'.
1072 # 'annotate_file'.
1073 #
1073 #
1074 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1074 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1075 # Thanks, Allen.
1075 # Thanks, Allen.
1076 #
1076 #
1077 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
1077 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
1078 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1078 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1079 # captured to a different destination.
1079 # captured to a different destination.
1080 #
1080 #
1081 # 2005-12-03 NMB coverage.py can now measure itself.
1081 # 2005-12-03 NMB coverage.py can now measure itself.
1082 #
1082 #
1083 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1083 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1084 # and sorting and omitting files to report on.
1084 # and sorting and omitting files to report on.
1085 #
1085 #
1086 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1086 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1087 #
1087 #
1088 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1088 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1089 # handling.
1089 # handling.
1090 #
1090 #
1091 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1091 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1092 #
1092 #
1093 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
1093 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
1094 # logic for parallel mode and collect.
1094 # logic for parallel mode and collect.
1095 #
1095 #
1096 # 2006-08-25 NMB "#pragma: nocover" is excluded by default.
1096 # 2006-08-25 NMB "#pragma: nocover" is excluded by default.
1097 #
1097 #
1098 # 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
1098 # 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
1099 # appear in the middle of a function, a problem reported by Tim Leslie.
1099 # appear in the middle of a function, a problem reported by Tim Leslie.
1100 # Minor changes to avoid lint warnings.
1100 # Minor changes to avoid lint warnings.
1101 #
1101 #
1102 # 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
1102 # 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
1103 # Change how parallel mode is invoked, and fix erase() so that it erases the
1103 # Change how parallel mode is invoked, and fix erase() so that it erases the
1104 # cache when called programmatically.
1104 # cache when called programmatically.
1105 #
1105 #
1106 # 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
1106 # 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
1107 # do anything useful with it anyway.
1107 # do anything useful with it anyway.
1108 # Better file handling on Linux, thanks Guillaume Chazarain.
1108 # Better file handling on Linux, thanks Guillaume Chazarain.
1109 # Better shell support on Windows, thanks Noel O'Boyle.
1109 # Better shell support on Windows, thanks Noel O'Boyle.
1110 # Python 2.2 support maintained, thanks Catherine Proulx.
1110 # Python 2.2 support maintained, thanks Catherine Proulx.
1111 #
1111 #
1112 # 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
1112 # 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
1113 # multi-line statements is now less sensitive to the exact line that Python
1113 # multi-line statements is now less sensitive to the exact line that Python
1114 # reports during execution. Pass statements are handled specially so that their
1114 # reports during execution. Pass statements are handled specially so that their
1115 # disappearance during execution won't throw off the measurement.
1115 # disappearance during execution won't throw off the measurement.
1116 #
1116 #
1117 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1117 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1118 # new with statement is counted as executable.
1118 # new with statement is counted as executable.
1119 #
1119 #
1120 # 2007-07-29 NMB Better packaging.
1120 # 2007-07-29 NMB Better packaging.
1121 #
1121 #
1122 # 2007-09-30 NMB Don't try to predict whether a file is Python source based on
1122 # 2007-09-30 NMB Don't try to predict whether a file is Python source based on
1123 # the extension. Extensionless files are often Pythons scripts. Instead, simply
1123 # the extension. Extensionless files are often Pythons scripts. Instead, simply
1124 # parse the file and catch the syntax errors. Hat tip to Ben Finney.
1124 # parse the file and catch the syntax errors. Hat tip to Ben Finney.
1125 #
1125 #
1126 # 2008-05-25 NMB Open files in rU mode to avoid line ending craziness.
1126 # 2008-05-25 NMB Open files in rU mode to avoid line ending craziness.
1127 # Thanks, Edward Loper.
1127 # Thanks, Edward Loper.
1128 #
1128 #
1129 # 2008-09-14 NMB Add support for finding source files in eggs.
1129 # 2008-09-14 NMB Add support for finding source files in eggs.
1130 # Don't check for morf's being instances of ModuleType, instead use duck typing
1130 # Don't check for morf's being instances of ModuleType, instead use duck typing
1131 # so that pseudo-modules can participate. Thanks, Imri Goldberg.
1131 # so that pseudo-modules can participate. Thanks, Imri Goldberg.
1132 # Use os.realpath as part of the fixing of filenames so that symlinks won't
1132 # Use os.realpath as part of the fixing of filenames so that symlinks won't
1133 # confuse things. Thanks, Patrick Mezard.
1133 # confuse things. Thanks, Patrick Mezard.
1134 #
1134 #
1135 #
1135 #
1136 # C. COPYRIGHT AND LICENCE
1136 # C. COPYRIGHT AND LICENCE
1137 #
1137 #
1138 # Copyright 2001 Gareth Rees. All rights reserved.
1138 # Copyright 2001 Gareth Rees. All rights reserved.
1139 # Copyright 2004-2008 Ned Batchelder. All rights reserved.
1139 # Copyright 2004-2008 Ned Batchelder. All rights reserved.
1140 #
1140 #
1141 # Redistribution and use in source and binary forms, with or without
1141 # Redistribution and use in source and binary forms, with or without
1142 # modification, are permitted provided that the following conditions are
1142 # modification, are permitted provided that the following conditions are
1143 # met:
1143 # met:
1144 #
1144 #
1145 # 1. Redistributions of source code must retain the above copyright
1145 # 1. Redistributions of source code must retain the above copyright
1146 # notice, this list of conditions and the following disclaimer.
1146 # notice, this list of conditions and the following disclaimer.
1147 #
1147 #
1148 # 2. Redistributions in binary form must reproduce the above copyright
1148 # 2. Redistributions in binary form must reproduce the above copyright
1149 # notice, this list of conditions and the following disclaimer in the
1149 # notice, this list of conditions and the following disclaimer in the
1150 # documentation and/or other materials provided with the
1150 # documentation and/or other materials provided with the
1151 # distribution.
1151 # distribution.
1152 #
1152 #
1153 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1153 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1154 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1154 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1155 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1155 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1156 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1156 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1157 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1157 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1158 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1158 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1159 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1159 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1160 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1160 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1161 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1161 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1162 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1162 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1163 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1163 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1164 # DAMAGE.
1164 # DAMAGE.
1165 #
1165 #
1166 # $Id: coverage.py 96 2008-09-14 18:34:13Z nedbat $
1166 # $Id: coverage.py 96 2008-09-14 18:34:13Z nedbat $
@@ -1,948 +1,948 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 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 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 import difflib
44 import difflib
45 import errno
45 import errno
46 import optparse
46 import optparse
47 import os
47 import os
48 import signal
48 import signal
49 import subprocess
49 import subprocess
50 import shutil
50 import shutil
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55
55
56 closefds = os.name == 'posix'
56 closefds = os.name == 'posix'
57 def Popen4(cmd, bufsize=-1):
57 def Popen4(cmd, bufsize=-1):
58 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
59 close_fds=closefds,
59 close_fds=closefds,
60 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
60 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stderr=subprocess.STDOUT)
61 stderr=subprocess.STDOUT)
62 p.fromchild = p.stdout
62 p.fromchild = p.stdout
63 p.tochild = p.stdin
63 p.tochild = p.stdin
64 p.childerr = p.stderr
64 p.childerr = p.stderr
65 return p
65 return p
66
66
67 # reserved exit code to skip test (used by hghave)
67 # reserved exit code to skip test (used by hghave)
68 SKIPPED_STATUS = 80
68 SKIPPED_STATUS = 80
69 SKIPPED_PREFIX = 'skipped: '
69 SKIPPED_PREFIX = 'skipped: '
70 FAILED_PREFIX = 'hghave check failed: '
70 FAILED_PREFIX = 'hghave check failed: '
71 PYTHON = sys.executable
71 PYTHON = sys.executable
72
72
73 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
73 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
74
74
75 defaults = {
75 defaults = {
76 'jobs': ('HGTEST_JOBS', 1),
76 'jobs': ('HGTEST_JOBS', 1),
77 'timeout': ('HGTEST_TIMEOUT', 180),
77 'timeout': ('HGTEST_TIMEOUT', 180),
78 'port': ('HGTEST_PORT', 20059),
78 'port': ('HGTEST_PORT', 20059),
79 }
79 }
80
80
81 def parseargs():
81 def parseargs():
82 parser = optparse.OptionParser("%prog [options] [tests]")
82 parser = optparse.OptionParser("%prog [options] [tests]")
83 parser.add_option("-C", "--annotate", action="store_true",
83 parser.add_option("-C", "--annotate", action="store_true",
84 help="output files annotated with coverage")
84 help="output files annotated with coverage")
85 parser.add_option("--child", type="int",
85 parser.add_option("--child", type="int",
86 help="run as child process, summary to given fd")
86 help="run as child process, summary to given fd")
87 parser.add_option("-c", "--cover", action="store_true",
87 parser.add_option("-c", "--cover", action="store_true",
88 help="print a test coverage report")
88 help="print a test coverage report")
89 parser.add_option("-f", "--first", action="store_true",
89 parser.add_option("-f", "--first", action="store_true",
90 help="exit on the first test failure")
90 help="exit on the first test failure")
91 parser.add_option("-i", "--interactive", action="store_true",
91 parser.add_option("-i", "--interactive", action="store_true",
92 help="prompt to accept changed output")
92 help="prompt to accept changed output")
93 parser.add_option("-j", "--jobs", type="int",
93 parser.add_option("-j", "--jobs", type="int",
94 help="number of jobs to run in parallel"
94 help="number of jobs to run in parallel"
95 " (default: $%s or %d)" % defaults['jobs'])
95 " (default: $%s or %d)" % defaults['jobs'])
96 parser.add_option("-k", "--keywords",
96 parser.add_option("-k", "--keywords",
97 help="run tests matching keywords")
97 help="run tests matching keywords")
98 parser.add_option("--keep-tmpdir", action="store_true",
98 parser.add_option("--keep-tmpdir", action="store_true",
99 help="keep temporary directory after running tests")
99 help="keep temporary directory after running tests")
100 parser.add_option("--tmpdir", type="string",
100 parser.add_option("--tmpdir", type="string",
101 help="run tests in the given temporary directory"
101 help="run tests in the given temporary directory"
102 " (implies --keep-tmpdir)")
102 " (implies --keep-tmpdir)")
103 parser.add_option("-d", "--debug", action="store_true",
103 parser.add_option("-d", "--debug", action="store_true",
104 help="debug mode: write output of test scripts to console"
104 help="debug mode: write output of test scripts to console"
105 " rather than capturing and diff'ing it (disables timeout)")
105 " rather than capturing and diff'ing it (disables timeout)")
106 parser.add_option("-R", "--restart", action="store_true",
106 parser.add_option("-R", "--restart", action="store_true",
107 help="restart at last error")
107 help="restart at last error")
108 parser.add_option("-p", "--port", type="int",
108 parser.add_option("-p", "--port", type="int",
109 help="port on which servers should listen"
109 help="port on which servers should listen"
110 " (default: $%s or %d)" % defaults['port'])
110 " (default: $%s or %d)" % defaults['port'])
111 parser.add_option("-r", "--retest", action="store_true",
111 parser.add_option("-r", "--retest", action="store_true",
112 help="retest failed tests")
112 help="retest failed tests")
113 parser.add_option("-s", "--cover_stdlib", action="store_true",
113 parser.add_option("-s", "--cover_stdlib", action="store_true",
114 help="print a test coverage report inc. standard libraries")
114 help="print a test coverage report inc. standard libraries")
115 parser.add_option("-S", "--noskips", action="store_true",
115 parser.add_option("-S", "--noskips", action="store_true",
116 help="don't report skip tests verbosely")
116 help="don't report skip tests verbosely")
117 parser.add_option("-t", "--timeout", type="int",
117 parser.add_option("-t", "--timeout", type="int",
118 help="kill errant tests after TIMEOUT seconds"
118 help="kill errant tests after TIMEOUT seconds"
119 " (default: $%s or %d)" % defaults['timeout'])
119 " (default: $%s or %d)" % defaults['timeout'])
120 parser.add_option("-v", "--verbose", action="store_true",
120 parser.add_option("-v", "--verbose", action="store_true",
121 help="output verbose messages")
121 help="output verbose messages")
122 parser.add_option("-n", "--nodiff", action="store_true",
122 parser.add_option("-n", "--nodiff", action="store_true",
123 help="skip showing test changes")
123 help="skip showing test changes")
124 parser.add_option("--with-hg", type="string",
124 parser.add_option("--with-hg", type="string",
125 metavar="HG",
125 metavar="HG",
126 help="test using specified hg script rather than a "
126 help="test using specified hg script rather than a "
127 "temporary installation")
127 "temporary installation")
128 parser.add_option("--local", action="store_true",
128 parser.add_option("--local", action="store_true",
129 help="shortcut for --with-hg=<testdir>/../hg")
129 help="shortcut for --with-hg=<testdir>/../hg")
130 parser.add_option("--pure", action="store_true",
130 parser.add_option("--pure", action="store_true",
131 help="use pure Python code instead of C extensions")
131 help="use pure Python code instead of C extensions")
132 parser.add_option("-3", "--py3k-warnings", action="store_true",
132 parser.add_option("-3", "--py3k-warnings", action="store_true",
133 help="enable Py3k warnings on Python 2.6+")
133 help="enable Py3k warnings on Python 2.6+")
134 parser.add_option("--inotify", action="store_true",
134 parser.add_option("--inotify", action="store_true",
135 help="enable inotify extension when running tests")
135 help="enable inotify extension when running tests")
136 parser.add_option("--blacklist", action="append",
136 parser.add_option("--blacklist", action="append",
137 help="skip tests listed in the specified blacklist file")
137 help="skip tests listed in the specified blacklist file")
138
138
139 for option, default in defaults.items():
139 for option, default in defaults.items():
140 defaults[option] = int(os.environ.get(*default))
140 defaults[option] = int(os.environ.get(*default))
141 parser.set_defaults(**defaults)
141 parser.set_defaults(**defaults)
142 (options, args) = parser.parse_args()
142 (options, args) = parser.parse_args()
143
143
144 if options.with_hg:
144 if options.with_hg:
145 if not (os.path.isfile(options.with_hg) and
145 if not (os.path.isfile(options.with_hg) and
146 os.access(options.with_hg, os.X_OK)):
146 os.access(options.with_hg, os.X_OK)):
147 parser.error('--with-hg must specify an executable hg script')
147 parser.error('--with-hg must specify an executable hg script')
148 if not os.path.basename(options.with_hg) == 'hg':
148 if not os.path.basename(options.with_hg) == 'hg':
149 sys.stderr.write('warning: --with-hg should specify an hg script')
149 sys.stderr.write('warning: --with-hg should specify an hg script')
150 if options.local:
150 if options.local:
151 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
151 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
152 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
152 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
153 if not os.access(hgbin, os.X_OK):
153 if not os.access(hgbin, os.X_OK):
154 parser.error('--local specified, but %r not found or not executable'
154 parser.error('--local specified, but %r not found or not executable'
155 % hgbin)
155 % hgbin)
156 options.with_hg = hgbin
156 options.with_hg = hgbin
157
157
158 options.anycoverage = (options.cover or
158 options.anycoverage = (options.cover or
159 options.cover_stdlib or
159 options.cover_stdlib or
160 options.annotate)
160 options.annotate)
161
161
162 if options.anycoverage and options.with_hg:
162 if options.anycoverage and options.with_hg:
163 # I'm not sure if this is a fundamental limitation or just a
163 # I'm not sure if this is a fundamental limitation or just a
164 # bug. But I don't want to waste people's time and energy doing
164 # bug. But I don't want to waste people's time and energy doing
165 # test runs that don't give the results they want.
165 # test runs that don't give the results they want.
166 parser.error("sorry, coverage options do not work when --with-hg "
166 parser.error("sorry, coverage options do not work when --with-hg "
167 "or --local specified")
167 "or --local specified")
168
168
169 global vlog
169 global vlog
170 if options.verbose:
170 if options.verbose:
171 if options.jobs > 1 or options.child is not None:
171 if options.jobs > 1 or options.child is not None:
172 pid = "[%d]" % os.getpid()
172 pid = "[%d]" % os.getpid()
173 else:
173 else:
174 pid = None
174 pid = None
175 def vlog(*msg):
175 def vlog(*msg):
176 if pid:
176 if pid:
177 print pid,
177 print pid,
178 for m in msg:
178 for m in msg:
179 print m,
179 print m,
180 print
180 print
181 sys.stdout.flush()
181 sys.stdout.flush()
182 else:
182 else:
183 vlog = lambda *msg: None
183 vlog = lambda *msg: None
184
184
185 if options.tmpdir:
185 if options.tmpdir:
186 options.tmpdir = os.path.expanduser(options.tmpdir)
186 options.tmpdir = os.path.expanduser(options.tmpdir)
187
187
188 if options.jobs < 1:
188 if options.jobs < 1:
189 parser.error('--jobs must be positive')
189 parser.error('--jobs must be positive')
190 if options.interactive and options.jobs > 1:
190 if options.interactive and options.jobs > 1:
191 print '(--interactive overrides --jobs)'
191 print '(--interactive overrides --jobs)'
192 options.jobs = 1
192 options.jobs = 1
193 if options.interactive and options.debug:
193 if options.interactive and options.debug:
194 parser.error("-i/--interactive and -d/--debug are incompatible")
194 parser.error("-i/--interactive and -d/--debug are incompatible")
195 if options.debug:
195 if options.debug:
196 if options.timeout != defaults['timeout']:
196 if options.timeout != defaults['timeout']:
197 sys.stderr.write(
197 sys.stderr.write(
198 'warning: --timeout option ignored with --debug\n')
198 'warning: --timeout option ignored with --debug\n')
199 options.timeout = 0
199 options.timeout = 0
200 if options.py3k_warnings:
200 if options.py3k_warnings:
201 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
201 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
202 parser.error('--py3k-warnings can only be used on Python 2.6+')
202 parser.error('--py3k-warnings can only be used on Python 2.6+')
203 if options.blacklist:
203 if options.blacklist:
204 blacklist = dict()
204 blacklist = dict()
205 for filename in options.blacklist:
205 for filename in options.blacklist:
206 try:
206 try:
207 path = os.path.expanduser(os.path.expandvars(filename))
207 path = os.path.expanduser(os.path.expandvars(filename))
208 f = open(path, "r")
208 f = open(path, "r")
209 except IOError, err:
209 except IOError, err:
210 if err.errno != errno.ENOENT:
210 if err.errno != errno.ENOENT:
211 raise
211 raise
212 print "warning: no such blacklist file: %s" % filename
212 print "warning: no such blacklist file: %s" % filename
213 continue
213 continue
214
214
215 for line in f.readlines():
215 for line in f.readlines():
216 line = line.strip()
216 line = line.strip()
217 if line and not line.startswith('#'):
217 if line and not line.startswith('#'):
218 blacklist[line] = filename
218 blacklist[line] = filename
219
219
220 options.blacklist = blacklist
220 options.blacklist = blacklist
221
221
222 return (options, args)
222 return (options, args)
223
223
224 def rename(src, dst):
224 def rename(src, dst):
225 """Like os.rename(), trade atomicity and opened files friendliness
225 """Like os.rename(), trade atomicity and opened files friendliness
226 for existing destination support.
226 for existing destination support.
227 """
227 """
228 shutil.copy(src, dst)
228 shutil.copy(src, dst)
229 os.remove(src)
229 os.remove(src)
230
230
231 def splitnewlines(text):
231 def splitnewlines(text):
232 '''like str.splitlines, but only split on newlines.
232 '''like str.splitlines, but only split on newlines.
233 keep line endings.'''
233 keep line endings.'''
234 i = 0
234 i = 0
235 lines = []
235 lines = []
236 while True:
236 while True:
237 n = text.find('\n', i)
237 n = text.find('\n', i)
238 if n == -1:
238 if n == -1:
239 last = text[i:]
239 last = text[i:]
240 if last:
240 if last:
241 lines.append(last)
241 lines.append(last)
242 return lines
242 return lines
243 lines.append(text[i:n + 1])
243 lines.append(text[i:n + 1])
244 i = n + 1
244 i = n + 1
245
245
246 def parsehghaveoutput(lines):
246 def parsehghaveoutput(lines):
247 '''Parse hghave log lines.
247 '''Parse hghave log lines.
248 Return tuple of lists (missing, failed):
248 Return tuple of lists (missing, failed):
249 * the missing/unknown features
249 * the missing/unknown features
250 * the features for which existence check failed'''
250 * the features for which existence check failed'''
251 missing = []
251 missing = []
252 failed = []
252 failed = []
253 for line in lines:
253 for line in lines:
254 if line.startswith(SKIPPED_PREFIX):
254 if line.startswith(SKIPPED_PREFIX):
255 line = line.splitlines()[0]
255 line = line.splitlines()[0]
256 missing.append(line[len(SKIPPED_PREFIX):])
256 missing.append(line[len(SKIPPED_PREFIX):])
257 elif line.startswith(FAILED_PREFIX):
257 elif line.startswith(FAILED_PREFIX):
258 line = line.splitlines()[0]
258 line = line.splitlines()[0]
259 failed.append(line[len(FAILED_PREFIX):])
259 failed.append(line[len(FAILED_PREFIX):])
260
260
261 return missing, failed
261 return missing, failed
262
262
263 def showdiff(expected, output, ref, err):
263 def showdiff(expected, output, ref, err):
264 for line in difflib.unified_diff(expected, output, ref, err):
264 for line in difflib.unified_diff(expected, output, ref, err):
265 sys.stdout.write(line)
265 sys.stdout.write(line)
266
266
267 def findprogram(program):
267 def findprogram(program):
268 """Search PATH for a executable program"""
268 """Search PATH for a executable program"""
269 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
269 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
270 name = os.path.join(p, program)
270 name = os.path.join(p, program)
271 if os.access(name, os.X_OK):
271 if os.access(name, os.X_OK):
272 return name
272 return name
273 return None
273 return None
274
274
275 def checktools():
275 def checktools():
276 # Before we go any further, check for pre-requisite tools
276 # Before we go any further, check for pre-requisite tools
277 # stuff from coreutils (cat, rm, etc) are not tested
277 # stuff from coreutils (cat, rm, etc) are not tested
278 for p in requiredtools:
278 for p in requiredtools:
279 if os.name == 'nt':
279 if os.name == 'nt':
280 p += '.exe'
280 p += '.exe'
281 found = findprogram(p)
281 found = findprogram(p)
282 if found:
282 if found:
283 vlog("# Found prerequisite", p, "at", found)
283 vlog("# Found prerequisite", p, "at", found)
284 else:
284 else:
285 print "WARNING: Did not find prerequisite tool: "+p
285 print "WARNING: Did not find prerequisite tool: "+p
286
286
287 def killdaemons():
287 def killdaemons():
288 # Kill off any leftover daemon processes
288 # Kill off any leftover daemon processes
289 try:
289 try:
290 fp = open(DAEMON_PIDS)
290 fp = open(DAEMON_PIDS)
291 for line in fp:
291 for line in fp:
292 try:
292 try:
293 pid = int(line)
293 pid = int(line)
294 except ValueError:
294 except ValueError:
295 continue
295 continue
296 try:
296 try:
297 os.kill(pid, 0)
297 os.kill(pid, 0)
298 vlog('# Killing daemon process %d' % pid)
298 vlog('# Killing daemon process %d' % pid)
299 os.kill(pid, signal.SIGTERM)
299 os.kill(pid, signal.SIGTERM)
300 time.sleep(0.25)
300 time.sleep(0.25)
301 os.kill(pid, 0)
301 os.kill(pid, 0)
302 vlog('# Daemon process %d is stuck - really killing it' % pid)
302 vlog('# Daemon process %d is stuck - really killing it' % pid)
303 os.kill(pid, signal.SIGKILL)
303 os.kill(pid, signal.SIGKILL)
304 except OSError, err:
304 except OSError, err:
305 if err.errno != errno.ESRCH:
305 if err.errno != errno.ESRCH:
306 raise
306 raise
307 fp.close()
307 fp.close()
308 os.unlink(DAEMON_PIDS)
308 os.unlink(DAEMON_PIDS)
309 except IOError:
309 except IOError:
310 pass
310 pass
311
311
312 def cleanup(options):
312 def cleanup(options):
313 if not options.keep_tmpdir:
313 if not options.keep_tmpdir:
314 vlog("# Cleaning up HGTMP", HGTMP)
314 vlog("# Cleaning up HGTMP", HGTMP)
315 shutil.rmtree(HGTMP, True)
315 shutil.rmtree(HGTMP, True)
316
316
317 def usecorrectpython():
317 def usecorrectpython():
318 # some tests run python interpreter. they must use same
318 # some tests run python interpreter. they must use same
319 # interpreter we use or bad things will happen.
319 # interpreter we use or bad things will happen.
320 exedir, exename = os.path.split(sys.executable)
320 exedir, exename = os.path.split(sys.executable)
321 if exename == 'python':
321 if exename == 'python':
322 path = findprogram('python')
322 path = findprogram('python')
323 if os.path.dirname(path) == exedir:
323 if os.path.dirname(path) == exedir:
324 return
324 return
325 vlog('# Making python executable in test path use correct Python')
325 vlog('# Making python executable in test path use correct Python')
326 mypython = os.path.join(BINDIR, 'python')
326 mypython = os.path.join(BINDIR, 'python')
327 try:
327 try:
328 os.symlink(sys.executable, mypython)
328 os.symlink(sys.executable, mypython)
329 except AttributeError:
329 except AttributeError:
330 # windows fallback
330 # windows fallback
331 shutil.copyfile(sys.executable, mypython)
331 shutil.copyfile(sys.executable, mypython)
332 shutil.copymode(sys.executable, mypython)
332 shutil.copymode(sys.executable, mypython)
333
333
334 def installhg(options):
334 def installhg(options):
335 vlog("# Performing temporary installation of HG")
335 vlog("# Performing temporary installation of HG")
336 installerrs = os.path.join("tests", "install.err")
336 installerrs = os.path.join("tests", "install.err")
337 pure = options.pure and "--pure" or ""
337 pure = options.pure and "--pure" or ""
338
338
339 # Run installer in hg root
339 # Run installer in hg root
340 script = os.path.realpath(sys.argv[0])
340 script = os.path.realpath(sys.argv[0])
341 hgroot = os.path.dirname(os.path.dirname(script))
341 hgroot = os.path.dirname(os.path.dirname(script))
342 os.chdir(hgroot)
342 os.chdir(hgroot)
343 nohome = '--home=""'
343 nohome = '--home=""'
344 if os.name == 'nt':
344 if os.name == 'nt':
345 # The --home="" trick works only on OS where os.sep == '/'
345 # The --home="" trick works only on OS where os.sep == '/'
346 # because of a distutils convert_path() fast-path. Avoid it at
346 # because of a distutils convert_path() fast-path. Avoid it at
347 # least on Windows for now, deal with .pydistutils.cfg bugs
347 # least on Windows for now, deal with .pydistutils.cfg bugs
348 # when they happen.
348 # when they happen.
349 nohome = ''
349 nohome = ''
350 cmd = ('%s setup.py %s clean --all'
350 cmd = ('%s setup.py %s clean --all'
351 ' install --force --prefix="%s" --install-lib="%s"'
351 ' install --force --prefix="%s" --install-lib="%s"'
352 ' --install-scripts="%s" %s >%s 2>&1'
352 ' --install-scripts="%s" %s >%s 2>&1'
353 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
353 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
354 installerrs))
354 installerrs))
355 vlog("# Running", cmd)
355 vlog("# Running", cmd)
356 if os.system(cmd) == 0:
356 if os.system(cmd) == 0:
357 if not options.verbose:
357 if not options.verbose:
358 os.remove(installerrs)
358 os.remove(installerrs)
359 else:
359 else:
360 f = open(installerrs)
360 f = open(installerrs)
361 for line in f:
361 for line in f:
362 print line,
362 print line,
363 f.close()
363 f.close()
364 sys.exit(1)
364 sys.exit(1)
365 os.chdir(TESTDIR)
365 os.chdir(TESTDIR)
366
366
367 usecorrectpython()
367 usecorrectpython()
368
368
369 vlog("# Installing dummy diffstat")
369 vlog("# Installing dummy diffstat")
370 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
370 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
371 f.write('#!' + sys.executable + '\n'
371 f.write('#!' + sys.executable + '\n'
372 'import sys\n'
372 'import sys\n'
373 'files = 0\n'
373 'files = 0\n'
374 'for line in sys.stdin:\n'
374 'for line in sys.stdin:\n'
375 ' if line.startswith("diff "):\n'
375 ' if line.startswith("diff "):\n'
376 ' files += 1\n'
376 ' files += 1\n'
377 'sys.stdout.write("files patched: %d\\n" % files)\n')
377 'sys.stdout.write("files patched: %d\\n" % files)\n')
378 f.close()
378 f.close()
379 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
379 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
380
380
381 if options.py3k_warnings and not options.anycoverage:
381 if options.py3k_warnings and not options.anycoverage:
382 vlog("# Updating hg command to enable Py3k Warnings switch")
382 vlog("# Updating hg command to enable Py3k Warnings switch")
383 f = open(os.path.join(BINDIR, 'hg'), 'r')
383 f = open(os.path.join(BINDIR, 'hg'), 'r')
384 lines = [line.rstrip() for line in f]
384 lines = [line.rstrip() for line in f]
385 lines[0] += ' -3'
385 lines[0] += ' -3'
386 f.close()
386 f.close()
387 f = open(os.path.join(BINDIR, 'hg'), 'w')
387 f = open(os.path.join(BINDIR, 'hg'), 'w')
388 for line in lines:
388 for line in lines:
389 f.write(line + '\n')
389 f.write(line + '\n')
390 f.close()
390 f.close()
391
391
392 if options.anycoverage:
392 if options.anycoverage:
393 vlog("# Installing coverage wrapper")
393 vlog("# Installing coverage wrapper")
394 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
394 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
395 if os.path.exists(COVERAGE_FILE):
395 if os.path.exists(COVERAGE_FILE):
396 os.unlink(COVERAGE_FILE)
396 os.unlink(COVERAGE_FILE)
397 # Create a wrapper script to invoke hg via coverage.py
397 # Create a wrapper script to invoke hg via coverage.py
398 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
398 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
399 f = open(os.path.join(BINDIR, 'hg'), 'w')
399 f = open(os.path.join(BINDIR, 'hg'), 'w')
400 f.write('#!' + sys.executable + '\n')
400 f.write('#!' + sys.executable + '\n')
401 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
401 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
402 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
402 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
403 (os.path.join(TESTDIR, 'coverage.py'),
403 (os.path.join(TESTDIR, 'coverage.py'),
404 os.path.join(BINDIR, '_hg.py')))
404 os.path.join(BINDIR, '_hg.py')))
405 f.close()
405 f.close()
406 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
406 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
407
407
408 def outputcoverage(options):
408 def outputcoverage(options):
409
409
410 vlog('# Producing coverage report')
410 vlog('# Producing coverage report')
411 os.chdir(PYTHONDIR)
411 os.chdir(PYTHONDIR)
412
412
413 def covrun(*args):
413 def covrun(*args):
414 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
414 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
415 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
415 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
416 vlog('# Running: %s' % cmd)
416 vlog('# Running: %s' % cmd)
417 os.system(cmd)
417 os.system(cmd)
418
418
419 omit = [BINDIR, TESTDIR, PYTHONDIR]
419 omit = [BINDIR, TESTDIR, PYTHONDIR]
420 if not options.cover_stdlib:
420 if not options.cover_stdlib:
421 # Exclude as system paths (ignoring empty strings seen on win)
421 # Exclude as system paths (ignoring empty strings seen on win)
422 omit += [x for x in sys.path if x != '']
422 omit += [x for x in sys.path if x != '']
423 omit = ','.join(omit)
423 omit = ','.join(omit)
424
424
425 covrun('-c') # combine from parallel processes
425 covrun('-c') # combine from parallel processes
426 for fn in os.listdir(TESTDIR):
426 for fn in os.listdir(TESTDIR):
427 if fn.startswith('.coverage.'):
427 if fn.startswith('.coverage.'):
428 os.unlink(os.path.join(TESTDIR, fn))
428 os.unlink(os.path.join(TESTDIR, fn))
429
429
430 covrun('-i', '-r', '"--omit=%s"' % omit) # report
430 covrun('-i', '-r', '"--omit=%s"' % omit) # report
431 if options.annotate:
431 if options.annotate:
432 adir = os.path.join(TESTDIR, 'annotated')
432 adir = os.path.join(TESTDIR, 'annotated')
433 if not os.path.isdir(adir):
433 if not os.path.isdir(adir):
434 os.mkdir(adir)
434 os.mkdir(adir)
435 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
435 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
436
436
437 class Timeout(Exception):
437 class Timeout(Exception):
438 pass
438 pass
439
439
440 def alarmed(signum, frame):
440 def alarmed(signum, frame):
441 raise Timeout
441 raise Timeout
442
442
443 def run(cmd, options):
443 def run(cmd, options):
444 """Run command in a sub-process, capturing the output (stdout and stderr).
444 """Run command in a sub-process, capturing the output (stdout and stderr).
445 Return a tuple (exitcode, output). output is None in debug mode."""
445 Return a tuple (exitcode, output). output is None in debug mode."""
446 # TODO: Use subprocess.Popen if we're running on Python 2.4
446 # TODO: Use subprocess.Popen if we're running on Python 2.4
447 if options.debug:
447 if options.debug:
448 proc = subprocess.Popen(cmd, shell=True)
448 proc = subprocess.Popen(cmd, shell=True)
449 ret = proc.wait()
449 ret = proc.wait()
450 return (ret, None)
450 return (ret, None)
451
451
452 if os.name == 'nt' or sys.platform.startswith('java'):
452 if os.name == 'nt' or sys.platform.startswith('java'):
453 tochild, fromchild = os.popen4(cmd)
453 tochild, fromchild = os.popen4(cmd)
454 tochild.close()
454 tochild.close()
455 output = fromchild.read()
455 output = fromchild.read()
456 ret = fromchild.close()
456 ret = fromchild.close()
457 if ret == None:
457 if ret == None:
458 ret = 0
458 ret = 0
459 else:
459 else:
460 proc = Popen4(cmd)
460 proc = Popen4(cmd)
461 def cleanup():
461 def cleanup():
462 os.kill(proc.pid, signal.SIGTERM)
462 os.kill(proc.pid, signal.SIGTERM)
463 ret = proc.wait()
463 ret = proc.wait()
464 if ret == 0:
464 if ret == 0:
465 ret = signal.SIGTERM << 8
465 ret = signal.SIGTERM << 8
466 killdaemons()
466 killdaemons()
467 return ret
467 return ret
468
468
469 try:
469 try:
470 output = ''
470 output = ''
471 proc.tochild.close()
471 proc.tochild.close()
472 output = proc.fromchild.read()
472 output = proc.fromchild.read()
473 ret = proc.wait()
473 ret = proc.wait()
474 if os.WIFEXITED(ret):
474 if os.WIFEXITED(ret):
475 ret = os.WEXITSTATUS(ret)
475 ret = os.WEXITSTATUS(ret)
476 except Timeout:
476 except Timeout:
477 vlog('# Process %d timed out - killing it' % proc.pid)
477 vlog('# Process %d timed out - killing it' % proc.pid)
478 ret = cleanup()
478 ret = cleanup()
479 output += ("\n### Abort: timeout after %d seconds.\n"
479 output += ("\n### Abort: timeout after %d seconds.\n"
480 % options.timeout)
480 % options.timeout)
481 except KeyboardInterrupt:
481 except KeyboardInterrupt:
482 vlog('# Handling keyboard interrupt')
482 vlog('# Handling keyboard interrupt')
483 cleanup()
483 cleanup()
484 raise
484 raise
485
485
486 return ret, splitnewlines(output)
486 return ret, splitnewlines(output)
487
487
488 def runone(options, test, skips, fails):
488 def runone(options, test, skips, fails):
489 '''tristate output:
489 '''tristate output:
490 None -> skipped
490 None -> skipped
491 True -> passed
491 True -> passed
492 False -> failed'''
492 False -> failed'''
493
493
494 def skip(msg):
494 def skip(msg):
495 if not options.verbose:
495 if not options.verbose:
496 skips.append((test, msg))
496 skips.append((test, msg))
497 else:
497 else:
498 print "\nSkipping %s: %s" % (testpath, msg)
498 print "\nSkipping %s: %s" % (testpath, msg)
499 return None
499 return None
500
500
501 def fail(msg):
501 def fail(msg):
502 fails.append((test, msg))
502 fails.append((test, msg))
503 if not options.nodiff:
503 if not options.nodiff:
504 print "\nERROR: %s %s" % (testpath, msg)
504 print "\nERROR: %s %s" % (testpath, msg)
505 return None
505 return None
506
506
507 vlog("# Test", test)
507 vlog("# Test", test)
508
508
509 # create a fresh hgrc
509 # create a fresh hgrc
510 hgrc = open(HGRCPATH, 'w+')
510 hgrc = open(HGRCPATH, 'w+')
511 hgrc.write('[ui]\n')
511 hgrc.write('[ui]\n')
512 hgrc.write('slash = True\n')
512 hgrc.write('slash = True\n')
513 hgrc.write('[defaults]\n')
513 hgrc.write('[defaults]\n')
514 hgrc.write('backout = -d "0 0"\n')
514 hgrc.write('backout = -d "0 0"\n')
515 hgrc.write('commit = -d "0 0"\n')
515 hgrc.write('commit = -d "0 0"\n')
516 hgrc.write('tag = -d "0 0"\n')
516 hgrc.write('tag = -d "0 0"\n')
517 if options.inotify:
517 if options.inotify:
518 hgrc.write('[extensions]\n')
518 hgrc.write('[extensions]\n')
519 hgrc.write('inotify=\n')
519 hgrc.write('inotify=\n')
520 hgrc.write('[inotify]\n')
520 hgrc.write('[inotify]\n')
521 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
521 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
522 hgrc.write('appendpid=True\n')
522 hgrc.write('appendpid=True\n')
523 hgrc.close()
523 hgrc.close()
524
524
525 testpath = os.path.join(TESTDIR, test)
525 testpath = os.path.join(TESTDIR, test)
526 ref = os.path.join(TESTDIR, test+".out")
526 ref = os.path.join(TESTDIR, test+".out")
527 err = os.path.join(TESTDIR, test+".err")
527 err = os.path.join(TESTDIR, test+".err")
528 if os.path.exists(err):
528 if os.path.exists(err):
529 os.remove(err) # Remove any previous output files
529 os.remove(err) # Remove any previous output files
530 try:
530 try:
531 tf = open(testpath)
531 tf = open(testpath)
532 firstline = tf.readline().rstrip()
532 firstline = tf.readline().rstrip()
533 tf.close()
533 tf.close()
534 except:
534 except:
535 firstline = ''
535 firstline = ''
536 lctest = test.lower()
536 lctest = test.lower()
537
537
538 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
538 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
539 py3kswitch = options.py3k_warnings and ' -3' or ''
539 py3kswitch = options.py3k_warnings and ' -3' or ''
540 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
540 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
541 elif lctest.endswith('.bat'):
541 elif lctest.endswith('.bat'):
542 # do not run batch scripts on non-windows
542 # do not run batch scripts on non-windows
543 if os.name != 'nt':
543 if os.name != 'nt':
544 return skip("batch script")
544 return skip("batch script")
545 # To reliably get the error code from batch files on WinXP,
545 # To reliably get the error code from batch files on WinXP,
546 # the "cmd /c call" prefix is needed. Grrr
546 # the "cmd /c call" prefix is needed. Grrr
547 cmd = 'cmd /c call "%s"' % testpath
547 cmd = 'cmd /c call "%s"' % testpath
548 else:
548 else:
549 # do not run shell scripts on windows
549 # do not run shell scripts on windows
550 if os.name == 'nt':
550 if os.name == 'nt':
551 return skip("shell script")
551 return skip("shell script")
552 # do not try to run non-executable programs
552 # do not try to run non-executable programs
553 if not os.path.exists(testpath):
553 if not os.path.exists(testpath):
554 return fail("does not exist")
554 return fail("does not exist")
555 elif not os.access(testpath, os.X_OK):
555 elif not os.access(testpath, os.X_OK):
556 return skip("not executable")
556 return skip("not executable")
557 cmd = '"%s"' % testpath
557 cmd = '"%s"' % testpath
558
558
559 # Make a tmp subdirectory to work in
559 # Make a tmp subdirectory to work in
560 tmpd = os.path.join(HGTMP, test)
560 tmpd = os.path.join(HGTMP, test)
561 os.mkdir(tmpd)
561 os.mkdir(tmpd)
562 os.chdir(tmpd)
562 os.chdir(tmpd)
563
563
564 if options.timeout > 0:
564 if options.timeout > 0:
565 signal.alarm(options.timeout)
565 signal.alarm(options.timeout)
566
566
567 vlog("# Running", cmd)
567 vlog("# Running", cmd)
568 ret, out = run(cmd, options)
568 ret, out = run(cmd, options)
569 vlog("# Ret was:", ret)
569 vlog("# Ret was:", ret)
570
570
571 if options.timeout > 0:
571 if options.timeout > 0:
572 signal.alarm(0)
572 signal.alarm(0)
573
573
574 mark = '.'
574 mark = '.'
575
575
576 skipped = (ret == SKIPPED_STATUS)
576 skipped = (ret == SKIPPED_STATUS)
577 # If we're not in --debug mode and reference output file exists,
577 # If we're not in --debug mode and reference output file exists,
578 # check test output against it.
578 # check test output against it.
579 if options.debug:
579 if options.debug:
580 refout = None # to match out == None
580 refout = None # to match out == None
581 elif os.path.exists(ref):
581 elif os.path.exists(ref):
582 f = open(ref, "r")
582 f = open(ref, "r")
583 refout = splitnewlines(f.read())
583 refout = splitnewlines(f.read())
584 f.close()
584 f.close()
585 else:
585 else:
586 refout = []
586 refout = []
587
587
588 if skipped:
588 if skipped:
589 mark = 's'
589 mark = 's'
590 if out is None: # debug mode: nothing to parse
590 if out is None: # debug mode: nothing to parse
591 missing = ['unknown']
591 missing = ['unknown']
592 failed = None
592 failed = None
593 else:
593 else:
594 missing, failed = parsehghaveoutput(out)
594 missing, failed = parsehghaveoutput(out)
595 if not missing:
595 if not missing:
596 missing = ['irrelevant']
596 missing = ['irrelevant']
597 if failed:
597 if failed:
598 fail("hghave failed checking for %s" % failed[-1])
598 fail("hghave failed checking for %s" % failed[-1])
599 skipped = False
599 skipped = False
600 else:
600 else:
601 skip(missing[-1])
601 skip(missing[-1])
602 elif out != refout:
602 elif out != refout:
603 mark = '!'
603 mark = '!'
604 if ret:
604 if ret:
605 fail("output changed and returned error code %d" % ret)
605 fail("output changed and returned error code %d" % ret)
606 else:
606 else:
607 fail("output changed")
607 fail("output changed")
608 if not options.nodiff:
608 if not options.nodiff:
609 showdiff(refout, out, ref, err)
609 showdiff(refout, out, ref, err)
610 ret = 1
610 ret = 1
611 elif ret:
611 elif ret:
612 mark = '!'
612 mark = '!'
613 fail("returned error code %d" % ret)
613 fail("returned error code %d" % ret)
614
614
615 if not options.verbose:
615 if not options.verbose:
616 sys.stdout.write(mark)
616 sys.stdout.write(mark)
617 sys.stdout.flush()
617 sys.stdout.flush()
618
618
619 if ret != 0 and not skipped and not options.debug:
619 if ret != 0 and not skipped and not options.debug:
620 # Save errors to a file for diagnosis
620 # Save errors to a file for diagnosis
621 f = open(err, "wb")
621 f = open(err, "wb")
622 for line in out:
622 for line in out:
623 f.write(line)
623 f.write(line)
624 f.close()
624 f.close()
625
625
626 killdaemons()
626 killdaemons()
627
627
628 os.chdir(TESTDIR)
628 os.chdir(TESTDIR)
629 if not options.keep_tmpdir:
629 if not options.keep_tmpdir:
630 shutil.rmtree(tmpd, True)
630 shutil.rmtree(tmpd, True)
631 if skipped:
631 if skipped:
632 return None
632 return None
633 return ret == 0
633 return ret == 0
634
634
635 _hgpath = None
635 _hgpath = None
636
636
637 def _gethgpath():
637 def _gethgpath():
638 """Return the path to the mercurial package that is actually found by
638 """Return the path to the mercurial package that is actually found by
639 the current Python interpreter."""
639 the current Python interpreter."""
640 global _hgpath
640 global _hgpath
641 if _hgpath is not None:
641 if _hgpath is not None:
642 return _hgpath
642 return _hgpath
643
643
644 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
644 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
645 pipe = os.popen(cmd % PYTHON)
645 pipe = os.popen(cmd % PYTHON)
646 try:
646 try:
647 _hgpath = pipe.read().strip()
647 _hgpath = pipe.read().strip()
648 finally:
648 finally:
649 pipe.close()
649 pipe.close()
650 return _hgpath
650 return _hgpath
651
651
652 def _checkhglib(verb):
652 def _checkhglib(verb):
653 """Ensure that the 'mercurial' package imported by python is
653 """Ensure that the 'mercurial' package imported by python is
654 the one we expect it to be. If not, print a warning to stderr."""
654 the one we expect it to be. If not, print a warning to stderr."""
655 expecthg = os.path.join(PYTHONDIR, 'mercurial')
655 expecthg = os.path.join(PYTHONDIR, 'mercurial')
656 actualhg = _gethgpath()
656 actualhg = _gethgpath()
657 if actualhg != expecthg:
657 if actualhg != expecthg:
658 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
658 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
659 ' (expected %s)\n'
659 ' (expected %s)\n'
660 % (verb, actualhg, expecthg))
660 % (verb, actualhg, expecthg))
661
661
662 def runchildren(options, tests):
662 def runchildren(options, tests):
663 if INST:
663 if INST:
664 installhg(options)
664 installhg(options)
665 _checkhglib("Testing")
665 _checkhglib("Testing")
666
666
667 optcopy = dict(options.__dict__)
667 optcopy = dict(options.__dict__)
668 optcopy['jobs'] = 1
668 optcopy['jobs'] = 1
669 if optcopy['with_hg'] is None:
669 if optcopy['with_hg'] is None:
670 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
670 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
671 opts = []
671 opts = []
672 for opt, value in optcopy.iteritems():
672 for opt, value in optcopy.iteritems():
673 name = '--' + opt.replace('_', '-')
673 name = '--' + opt.replace('_', '-')
674 if value is True:
674 if value is True:
675 opts.append(name)
675 opts.append(name)
676 elif value is not None:
676 elif value is not None:
677 opts.append(name + '=' + str(value))
677 opts.append(name + '=' + str(value))
678
678
679 tests.reverse()
679 tests.reverse()
680 jobs = [[] for j in xrange(options.jobs)]
680 jobs = [[] for j in xrange(options.jobs)]
681 while tests:
681 while tests:
682 for job in jobs:
682 for job in jobs:
683 if not tests:
683 if not tests:
684 break
684 break
685 job.append(tests.pop())
685 job.append(tests.pop())
686 fps = {}
686 fps = {}
687
687
688 for j, job in enumerate(jobs):
688 for j, job in enumerate(jobs):
689 if not job:
689 if not job:
690 continue
690 continue
691 rfd, wfd = os.pipe()
691 rfd, wfd = os.pipe()
692 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
692 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
693 childtmp = os.path.join(HGTMP, 'child%d' % j)
693 childtmp = os.path.join(HGTMP, 'child%d' % j)
694 childopts += ['--tmpdir', childtmp]
694 childopts += ['--tmpdir', childtmp]
695 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
695 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
696 vlog(' '.join(cmdline))
696 vlog(' '.join(cmdline))
697 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
697 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
698 os.close(wfd)
698 os.close(wfd)
699 signal.signal(signal.SIGINT, signal.SIG_IGN)
699 signal.signal(signal.SIGINT, signal.SIG_IGN)
700 failures = 0
700 failures = 0
701 tested, skipped, failed = 0, 0, 0
701 tested, skipped, failed = 0, 0, 0
702 skips = []
702 skips = []
703 fails = []
703 fails = []
704 while fps:
704 while fps:
705 pid, status = os.wait()
705 pid, status = os.wait()
706 fp = fps.pop(pid)
706 fp = fps.pop(pid)
707 l = fp.read().splitlines()
707 l = fp.read().splitlines()
708 try:
708 try:
709 test, skip, fail = map(int, l[:3])
709 test, skip, fail = map(int, l[:3])
710 except ValueError:
710 except ValueError:
711 test, skip, fail = 0, 0, 0
711 test, skip, fail = 0, 0, 0
712 split = -fail or len(l)
712 split = -fail or len(l)
713 for s in l[3:split]:
713 for s in l[3:split]:
714 skips.append(s.split(" ", 1))
714 skips.append(s.split(" ", 1))
715 for s in l[split:]:
715 for s in l[split:]:
716 fails.append(s.split(" ", 1))
716 fails.append(s.split(" ", 1))
717 tested += test
717 tested += test
718 skipped += skip
718 skipped += skip
719 failed += fail
719 failed += fail
720 vlog('pid %d exited, status %d' % (pid, status))
720 vlog('pid %d exited, status %d' % (pid, status))
721 failures |= status
721 failures |= status
722 print
722 print
723 if not options.noskips:
723 if not options.noskips:
724 for s in skips:
724 for s in skips:
725 print "Skipped %s: %s" % (s[0], s[1])
725 print "Skipped %s: %s" % (s[0], s[1])
726 for s in fails:
726 for s in fails:
727 print "Failed %s: %s" % (s[0], s[1])
727 print "Failed %s: %s" % (s[0], s[1])
728
728
729 _checkhglib("Tested")
729 _checkhglib("Tested")
730 print "# Ran %d tests, %d skipped, %d failed." % (
730 print "# Ran %d tests, %d skipped, %d failed." % (
731 tested, skipped, failed)
731 tested, skipped, failed)
732 sys.exit(failures != 0)
732 sys.exit(failures != 0)
733
733
734 def runtests(options, tests):
734 def runtests(options, tests):
735 global DAEMON_PIDS, HGRCPATH
735 global DAEMON_PIDS, HGRCPATH
736 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
736 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
737 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
737 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
738
738
739 try:
739 try:
740 if INST:
740 if INST:
741 installhg(options)
741 installhg(options)
742 _checkhglib("Testing")
742 _checkhglib("Testing")
743
743
744 if options.timeout > 0:
744 if options.timeout > 0:
745 try:
745 try:
746 signal.signal(signal.SIGALRM, alarmed)
746 signal.signal(signal.SIGALRM, alarmed)
747 vlog('# Running each test with %d second timeout' %
747 vlog('# Running each test with %d second timeout' %
748 options.timeout)
748 options.timeout)
749 except AttributeError:
749 except AttributeError:
750 print 'WARNING: cannot run tests with timeouts'
750 print 'WARNING: cannot run tests with timeouts'
751 options.timeout = 0
751 options.timeout = 0
752
752
753 tested = 0
753 tested = 0
754 failed = 0
754 failed = 0
755 skipped = 0
755 skipped = 0
756
756
757 if options.restart:
757 if options.restart:
758 orig = list(tests)
758 orig = list(tests)
759 while tests:
759 while tests:
760 if os.path.exists(tests[0] + ".err"):
760 if os.path.exists(tests[0] + ".err"):
761 break
761 break
762 tests.pop(0)
762 tests.pop(0)
763 if not tests:
763 if not tests:
764 print "running all tests"
764 print "running all tests"
765 tests = orig
765 tests = orig
766
766
767 skips = []
767 skips = []
768 fails = []
768 fails = []
769
769
770 for test in tests:
770 for test in tests:
771 if options.blacklist:
771 if options.blacklist:
772 filename = options.blacklist.get(test)
772 filename = options.blacklist.get(test)
773 if filename is not None:
773 if filename is not None:
774 skips.append((test, "blacklisted (%s)" % filename))
774 skips.append((test, "blacklisted (%s)" % filename))
775 skipped += 1
775 skipped += 1
776 continue
776 continue
777
777
778 if options.retest and not os.path.exists(test + ".err"):
778 if options.retest and not os.path.exists(test + ".err"):
779 skipped += 1
779 skipped += 1
780 continue
780 continue
781
781
782 if options.keywords:
782 if options.keywords:
783 t = open(test).read().lower() + test.lower()
783 t = open(test).read().lower() + test.lower()
784 for k in options.keywords.lower().split():
784 for k in options.keywords.lower().split():
785 if k in t:
785 if k in t:
786 break
786 break
787 else:
787 else:
788 skipped +=1
788 skipped += 1
789 continue
789 continue
790
790
791 ret = runone(options, test, skips, fails)
791 ret = runone(options, test, skips, fails)
792 if ret is None:
792 if ret is None:
793 skipped += 1
793 skipped += 1
794 elif not ret:
794 elif not ret:
795 if options.interactive:
795 if options.interactive:
796 print "Accept this change? [n] ",
796 print "Accept this change? [n] ",
797 answer = sys.stdin.readline().strip()
797 answer = sys.stdin.readline().strip()
798 if answer.lower() in "y yes".split():
798 if answer.lower() in "y yes".split():
799 rename(test + ".err", test + ".out")
799 rename(test + ".err", test + ".out")
800 tested += 1
800 tested += 1
801 fails.pop()
801 fails.pop()
802 continue
802 continue
803 failed += 1
803 failed += 1
804 if options.first:
804 if options.first:
805 break
805 break
806 tested += 1
806 tested += 1
807
807
808 if options.child:
808 if options.child:
809 fp = os.fdopen(options.child, 'w')
809 fp = os.fdopen(options.child, 'w')
810 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
810 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
811 for s in skips:
811 for s in skips:
812 fp.write("%s %s\n" % s)
812 fp.write("%s %s\n" % s)
813 for s in fails:
813 for s in fails:
814 fp.write("%s %s\n" % s)
814 fp.write("%s %s\n" % s)
815 fp.close()
815 fp.close()
816 else:
816 else:
817 print
817 print
818 for s in skips:
818 for s in skips:
819 print "Skipped %s: %s" % s
819 print "Skipped %s: %s" % s
820 for s in fails:
820 for s in fails:
821 print "Failed %s: %s" % s
821 print "Failed %s: %s" % s
822 _checkhglib("Tested")
822 _checkhglib("Tested")
823 print "# Ran %d tests, %d skipped, %d failed." % (
823 print "# Ran %d tests, %d skipped, %d failed." % (
824 tested, skipped, failed)
824 tested, skipped, failed)
825
825
826 if options.anycoverage:
826 if options.anycoverage:
827 outputcoverage(options)
827 outputcoverage(options)
828 except KeyboardInterrupt:
828 except KeyboardInterrupt:
829 failed = True
829 failed = True
830 print "\ninterrupted!"
830 print "\ninterrupted!"
831
831
832 if failed:
832 if failed:
833 sys.exit(1)
833 sys.exit(1)
834
834
835 def main():
835 def main():
836 (options, args) = parseargs()
836 (options, args) = parseargs()
837 if not options.child:
837 if not options.child:
838 os.umask(022)
838 os.umask(022)
839
839
840 checktools()
840 checktools()
841
841
842 # Reset some environment variables to well-known values so that
842 # Reset some environment variables to well-known values so that
843 # the tests produce repeatable output.
843 # the tests produce repeatable output.
844 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
844 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
845 os.environ['TZ'] = 'GMT'
845 os.environ['TZ'] = 'GMT'
846 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
846 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
847 os.environ['CDPATH'] = ''
847 os.environ['CDPATH'] = ''
848 os.environ['COLUMNS'] = '80'
848 os.environ['COLUMNS'] = '80'
849 os.environ['http_proxy'] = ''
849 os.environ['http_proxy'] = ''
850
850
851 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
851 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
852 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
852 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
853 if options.tmpdir:
853 if options.tmpdir:
854 options.keep_tmpdir = True
854 options.keep_tmpdir = True
855 tmpdir = options.tmpdir
855 tmpdir = options.tmpdir
856 if os.path.exists(tmpdir):
856 if os.path.exists(tmpdir):
857 # Meaning of tmpdir has changed since 1.3: we used to create
857 # Meaning of tmpdir has changed since 1.3: we used to create
858 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
858 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
859 # tmpdir already exists.
859 # tmpdir already exists.
860 sys.exit("error: temp dir %r already exists" % tmpdir)
860 sys.exit("error: temp dir %r already exists" % tmpdir)
861
861
862 # Automatically removing tmpdir sounds convenient, but could
862 # Automatically removing tmpdir sounds convenient, but could
863 # really annoy anyone in the habit of using "--tmpdir=/tmp"
863 # really annoy anyone in the habit of using "--tmpdir=/tmp"
864 # or "--tmpdir=$HOME".
864 # or "--tmpdir=$HOME".
865 #vlog("# Removing temp dir", tmpdir)
865 #vlog("# Removing temp dir", tmpdir)
866 #shutil.rmtree(tmpdir)
866 #shutil.rmtree(tmpdir)
867 os.makedirs(tmpdir)
867 os.makedirs(tmpdir)
868 else:
868 else:
869 tmpdir = tempfile.mkdtemp('', 'hgtests.')
869 tmpdir = tempfile.mkdtemp('', 'hgtests.')
870 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
870 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
871 DAEMON_PIDS = None
871 DAEMON_PIDS = None
872 HGRCPATH = None
872 HGRCPATH = None
873
873
874 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
874 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
875 os.environ["HGMERGE"] = "internal:merge"
875 os.environ["HGMERGE"] = "internal:merge"
876 os.environ["HGUSER"] = "test"
876 os.environ["HGUSER"] = "test"
877 os.environ["HGENCODING"] = "ascii"
877 os.environ["HGENCODING"] = "ascii"
878 os.environ["HGENCODINGMODE"] = "strict"
878 os.environ["HGENCODINGMODE"] = "strict"
879 os.environ["HGPORT"] = str(options.port)
879 os.environ["HGPORT"] = str(options.port)
880 os.environ["HGPORT1"] = str(options.port + 1)
880 os.environ["HGPORT1"] = str(options.port + 1)
881 os.environ["HGPORT2"] = str(options.port + 2)
881 os.environ["HGPORT2"] = str(options.port + 2)
882
882
883 if options.with_hg:
883 if options.with_hg:
884 INST = None
884 INST = None
885 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
885 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
886
886
887 # This looks redundant with how Python initializes sys.path from
887 # This looks redundant with how Python initializes sys.path from
888 # the location of the script being executed. Needed because the
888 # the location of the script being executed. Needed because the
889 # "hg" specified by --with-hg is not the only Python script
889 # "hg" specified by --with-hg is not the only Python script
890 # executed in the test suite that needs to import 'mercurial'
890 # executed in the test suite that needs to import 'mercurial'
891 # ... which means it's not really redundant at all.
891 # ... which means it's not really redundant at all.
892 PYTHONDIR = BINDIR
892 PYTHONDIR = BINDIR
893 else:
893 else:
894 INST = os.path.join(HGTMP, "install")
894 INST = os.path.join(HGTMP, "install")
895 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
895 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
896 PYTHONDIR = os.path.join(INST, "lib", "python")
896 PYTHONDIR = os.path.join(INST, "lib", "python")
897
897
898 os.environ["BINDIR"] = BINDIR
898 os.environ["BINDIR"] = BINDIR
899 os.environ["PYTHON"] = PYTHON
899 os.environ["PYTHON"] = PYTHON
900
900
901 if not options.child:
901 if not options.child:
902 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
902 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
903 os.environ["PATH"] = os.pathsep.join(path)
903 os.environ["PATH"] = os.pathsep.join(path)
904
904
905 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
905 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
906 # can run .../tests/run-tests.py test-foo where test-foo
906 # can run .../tests/run-tests.py test-foo where test-foo
907 # adds an extension to HGRC
907 # adds an extension to HGRC
908 pypath = [PYTHONDIR, TESTDIR]
908 pypath = [PYTHONDIR, TESTDIR]
909 # We have to augment PYTHONPATH, rather than simply replacing
909 # We have to augment PYTHONPATH, rather than simply replacing
910 # it, in case external libraries are only available via current
910 # it, in case external libraries are only available via current
911 # PYTHONPATH. (In particular, the Subversion bindings on OS X
911 # PYTHONPATH. (In particular, the Subversion bindings on OS X
912 # are in /opt/subversion.)
912 # are in /opt/subversion.)
913 oldpypath = os.environ.get('PYTHONPATH')
913 oldpypath = os.environ.get('PYTHONPATH')
914 if oldpypath:
914 if oldpypath:
915 pypath.append(oldpypath)
915 pypath.append(oldpypath)
916 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
916 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
917
917
918 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
918 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
919
919
920 if len(args) == 0:
920 if len(args) == 0:
921 args = os.listdir(".")
921 args = os.listdir(".")
922 args.sort()
922 args.sort()
923
923
924 tests = []
924 tests = []
925 for test in args:
925 for test in args:
926 if (test.startswith("test-") and '~' not in test and
926 if (test.startswith("test-") and '~' not in test and
927 ('.' not in test or test.endswith('.py') or
927 ('.' not in test or test.endswith('.py') or
928 test.endswith('.bat'))):
928 test.endswith('.bat'))):
929 tests.append(test)
929 tests.append(test)
930 if not tests:
930 if not tests:
931 print "# Ran 0 tests, 0 skipped, 0 failed."
931 print "# Ran 0 tests, 0 skipped, 0 failed."
932 return
932 return
933
933
934 vlog("# Using TESTDIR", TESTDIR)
934 vlog("# Using TESTDIR", TESTDIR)
935 vlog("# Using HGTMP", HGTMP)
935 vlog("# Using HGTMP", HGTMP)
936 vlog("# Using PATH", os.environ["PATH"])
936 vlog("# Using PATH", os.environ["PATH"])
937 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
937 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
938
938
939 try:
939 try:
940 if len(tests) > 1 and options.jobs > 1:
940 if len(tests) > 1 and options.jobs > 1:
941 runchildren(options, tests)
941 runchildren(options, tests)
942 else:
942 else:
943 runtests(options, tests)
943 runtests(options, tests)
944 finally:
944 finally:
945 time.sleep(1)
945 time.sleep(1)
946 cleanup(options)
946 cleanup(options)
947
947
948 main()
948 main()
@@ -1,19 +1,19 b''
1 # this is hack to make sure no escape characters are inserted into the output
1 # this is hack to make sure no escape characters are inserted into the output
2 import os;
2 import os
3 if 'TERM' in os.environ:
3 if 'TERM' in os.environ:
4 del os.environ['TERM']
4 del os.environ['TERM']
5 import doctest
5 import doctest
6
6
7 import mercurial.changelog
7 import mercurial.changelog
8 # test doctest from changelog
8 # test doctest from changelog
9
9
10 doctest.testmod(mercurial.changelog)
10 doctest.testmod(mercurial.changelog)
11
11
12 import mercurial.httprepo
12 import mercurial.httprepo
13 doctest.testmod(mercurial.httprepo)
13 doctest.testmod(mercurial.httprepo)
14
14
15 import mercurial.util
15 import mercurial.util
16 doctest.testmod(mercurial.util)
16 doctest.testmod(mercurial.util)
17
17
18 import hgext.convert.cvsps
18 import hgext.convert.cvsps
19 doctest.testmod(hgext.convert.cvsps)
19 doctest.testmod(hgext.convert.cvsps)
@@ -1,151 +1,151 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "mq=" >> $HGRCPATH
4 echo "mq=" >> $HGRCPATH
5 echo "[diff]" >> $HGRCPATH
5 echo "[diff]" >> $HGRCPATH
6 echo "nodates=true" >> $HGRCPATH
6 echo "nodates=true" >> $HGRCPATH
7
7
8
8
9 catlog() {
9 catlog() {
10 cat .hg/patches/$1.patch | sed -e "s/^diff \-r [0-9a-f]* /diff -r ... /" \
10 cat .hg/patches/$1.patch | sed -e "s/^diff \-r [0-9a-f]* /diff -r ... /" \
11 -e "s/^\(# Parent \).*/\1/"
11 -e "s/^\(# Parent \).*/\1/"
12 hg log --template "{rev}: {desc} - {author}\n"
12 hg log --template "{rev}: {desc} - {author}\n"
13 }
13 }
14
14
15 runtest() {
15 runtest() {
16 echo ==== init
16 echo ==== init
17 hg init a
17 hg init a
18 cd a
18 cd a
19 hg qinit
19 hg qinit
20
20
21
21
22 echo ==== qnew -U
22 echo ==== qnew -U
23 hg qnew -U 1.patch
23 hg qnew -U 1.patch
24 catlog 1
24 catlog 1
25
25
26 echo ==== qref
26 echo ==== qref
27 echo "1" >1
27 echo "1" >1
28 hg add
28 hg add
29 hg qref
29 hg qref
30 catlog 1
30 catlog 1
31
31
32 echo ==== qref -u
32 echo ==== qref -u
33 hg qref -u mary
33 hg qref -u mary
34 catlog 1
34 catlog 1
35
35
36 echo ==== qnew
36 echo ==== qnew
37 hg qnew 2.patch
37 hg qnew 2.patch
38 echo "2" >2
38 echo "2" >2
39 hg add
39 hg add
40 hg qref
40 hg qref
41 catlog 2
41 catlog 2
42
42
43 echo ==== qref -u
43 echo ==== qref -u
44 hg qref -u jane
44 hg qref -u jane
45 catlog 2
45 catlog 2
46
46
47
47
48 echo ==== qnew -U -m
48 echo ==== qnew -U -m
49 hg qnew -U -m "Three" 3.patch
49 hg qnew -U -m "Three" 3.patch
50 catlog 3
50 catlog 3
51
51
52 echo ==== qref
52 echo ==== qref
53 echo "3" >3
53 echo "3" >3
54 hg add
54 hg add
55 hg qref
55 hg qref
56 catlog 3
56 catlog 3
57
57
58 echo ==== qref -m
58 echo ==== qref -m
59 hg qref -m "Drei"
59 hg qref -m "Drei"
60 catlog 3
60 catlog 3
61
61
62 echo ==== qref -u
62 echo ==== qref -u
63 hg qref -u mary
63 hg qref -u mary
64 catlog 3
64 catlog 3
65
65
66 echo ==== qref -u -m
66 echo ==== qref -u -m
67 hg qref -u maria -m "Three (again)"
67 hg qref -u maria -m "Three (again)"
68 catlog 3
68 catlog 3
69
69
70 echo ==== qnew -m
70 echo ==== qnew -m
71 hg qnew -m "Four" 4.patch
71 hg qnew -m "Four" 4.patch
72 echo "4" >4of t
72 echo "4" >4of t
73 hg add
73 hg add
74 hg qref
74 hg qref
75 catlog 4
75 catlog 4
76
76
77 echo ==== qref -u
77 echo ==== qref -u
78 hg qref -u jane
78 hg qref -u jane
79 catlog 4
79 catlog 4
80
80
81
81
82 echo ==== qnew with HG header
82 echo ==== qnew with HG header
83 hg qnew --config 'mq.plain=true' 5.patch
83 hg qnew --config 'mq.plain=true' 5.patch
84 hg qpop
84 hg qpop
85 echo "# HG changeset patch" >>.hg/patches/5.patch
85 echo "# HG changeset patch" >>.hg/patches/5.patch
86 echo "# User johndoe" >>.hg/patches/5.patch
86 echo "# User johndoe" >>.hg/patches/5.patch
87 hg qpush 2>&1 | grep 'now at'
87 hg qpush 2>&1 | grep 'now at'
88 catlog 5
88 catlog 5
89
89
90 echo ==== hg qref
90 echo ==== hg qref
91 echo "5" >5
91 echo "5" >5
92 hg add
92 hg add
93 hg qref
93 hg qref
94 catlog 5
94 catlog 5
95
95
96 echo ==== hg qref -U
96 echo ==== hg qref -U
97 hg qref -U
97 hg qref -U
98 catlog 5
98 catlog 5
99
99
100 echo ==== hg qref -u
100 echo ==== hg qref -u
101 hg qref -u johndeere
101 hg qref -u johndeere
102 catlog 5
102 catlog 5
103
103
104
104
105 echo ==== qnew with plain header
105 echo ==== qnew with plain header
106 hg qnew --config 'mq.plain=true' -U 6.patch
106 hg qnew --config 'mq.plain=true' -U 6.patch
107 hg qpop
107 hg qpop
108 hg qpush 2>&1 | grep 'now at'
108 hg qpush 2>&1 | grep 'now at'
109 catlog 6
109 catlog 6
110
110
111 echo ==== hg qref
111 echo ==== hg qref
112 echo "6" >6
112 echo "6" >6
113 hg add
113 hg add
114 hg qref
114 hg qref
115 catlog 6
115 catlog 6
116
116
117 echo ==== hg qref -U
117 echo ==== hg qref -U
118 hg qref -U
118 hg qref -U
119 catlog 6
119 catlog 6
120
120
121 echo ==== hg qref -u
121 echo ==== hg qref -u
122 hg qref -u johndeere
122 hg qref -u johndeere
123 catlog 6
123 catlog 6
124
124
125
125
126 echo ==== "qpop -a / qpush -a"
126 echo ==== "qpop -a / qpush -a"
127 hg qpop -a
127 hg qpop -a
128 hg qpush -a
128 hg qpush -a
129 hg log --template "{rev}: {desc} - {author}\n"
129 hg log --template "{rev}: {desc} - {author}\n"
130 }
130 }
131
131
132
132
133 echo ======= plain headers
133 echo ======= plain headers
134
134
135 echo "[mq]" >> $HGRCPATH
135 echo "[mq]" >> $HGRCPATH
136 echo "plain=true" >> $HGRCPATH
136 echo "plain=true" >> $HGRCPATH
137
137
138 mkdir sandbox
138 mkdir sandbox
139 (cd sandbox ; runtest)
139 (cd sandbox ; runtest)
140 rm -r sandbox
140 rm -r sandbox
141
141
142
142
143 echo ======= hg headers
143 echo ======= hg headers
144
144
145 echo "plain=false" >> $HGRCPATH
145 echo "plain=false" >> $HGRCPATH
146
146
147 mkdir sandbox
147 mkdir sandbox
148 (cd sandbox ; runtest)
148 (cd sandbox ; runtest)
149 rm -r sandbox
149 rm -r sandbox
150
150
151 runtest No newline at end of file
151 runtest
@@ -1,416 +1,416 b''
1 # Copyright (C) 2004, 2005 Canonical Ltd
1 # Copyright (C) 2004, 2005 Canonical Ltd
2 #
2 #
3 # This program is free software; you can redistribute it and/or modify
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
6 # (at your option) any later version.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU General Public License
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
16
17 import unittest
17 import unittest
18 from unittest import TestCase
18 from unittest import TestCase
19 from mercurial import util, simplemerge
19 from mercurial import util, simplemerge
20
20
21 # bzr compatible interface, for the tests
21 # bzr compatible interface, for the tests
22 class Merge3(simplemerge.Merge3Text):
22 class Merge3(simplemerge.Merge3Text):
23 """3-way merge of texts.
23 """3-way merge of texts.
24
24
25 Given BASE, OTHER, THIS, tries to produce a combined text
25 Given BASE, OTHER, THIS, tries to produce a combined text
26 incorporating the changes from both BASE->OTHER and BASE->THIS.
26 incorporating the changes from both BASE->OTHER and BASE->THIS.
27 All three will typically be sequences of lines."""
27 All three will typically be sequences of lines."""
28 def __init__(self, base, a, b):
28 def __init__(self, base, a, b):
29 basetext = '\n'.join([i.strip('\n') for i in base] + [''])
29 basetext = '\n'.join([i.strip('\n') for i in base] + [''])
30 atext = '\n'.join([i.strip('\n') for i in a] + [''])
30 atext = '\n'.join([i.strip('\n') for i in a] + [''])
31 btext = '\n'.join([i.strip('\n') for i in b] + [''])
31 btext = '\n'.join([i.strip('\n') for i in b] + [''])
32 if util.binary(basetext) or util.binary(atext) or util.binary(btext):
32 if util.binary(basetext) or util.binary(atext) or util.binary(btext):
33 raise util.Abort("don't know how to merge binary files")
33 raise util.Abort("don't know how to merge binary files")
34 simplemerge.Merge3Text.__init__(self, basetext, atext, btext,
34 simplemerge.Merge3Text.__init__(self, basetext, atext, btext,
35 base, a, b)
35 base, a, b)
36
36
37 CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
37 CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
38
38
39 def split_lines(t):
39 def split_lines(t):
40 from cStringIO import StringIO
40 from cStringIO import StringIO
41 return StringIO(t).readlines()
41 return StringIO(t).readlines()
42
42
43 ############################################################
43 ############################################################
44 # test case data from the gnu diffutils manual
44 # test case data from the gnu diffutils manual
45 # common base
45 # common base
46 TZU = split_lines(""" The Nameless is the origin of Heaven and Earth;
46 TZU = split_lines(""" The Nameless is the origin of Heaven and Earth;
47 The named is the mother of all things.
47 The named is the mother of all things.
48
48
49 Therefore let there always be non-being,
49 Therefore let there always be non-being,
50 so we may see their subtlety,
50 so we may see their subtlety,
51 And let there always be being,
51 And let there always be being,
52 so we may see their outcome.
52 so we may see their outcome.
53 The two are the same,
53 The two are the same,
54 But after they are produced,
54 But after they are produced,
55 they have different names.
55 they have different names.
56 They both may be called deep and profound.
56 They both may be called deep and profound.
57 Deeper and more profound,
57 Deeper and more profound,
58 The door of all subtleties!
58 The door of all subtleties!
59 """)
59 """)
60
60
61 LAO = split_lines(""" The Way that can be told of is not the eternal Way;
61 LAO = split_lines(""" The Way that can be told of is not the eternal Way;
62 The name that can be named is not the eternal name.
62 The name that can be named is not the eternal name.
63 The Nameless is the origin of Heaven and Earth;
63 The Nameless is the origin of Heaven and Earth;
64 The Named is the mother of all things.
64 The Named is the mother of all things.
65 Therefore let there always be non-being,
65 Therefore let there always be non-being,
66 so we may see their subtlety,
66 so we may see their subtlety,
67 And let there always be being,
67 And let there always be being,
68 so we may see their outcome.
68 so we may see their outcome.
69 The two are the same,
69 The two are the same,
70 But after they are produced,
70 But after they are produced,
71 they have different names.
71 they have different names.
72 """)
72 """)
73
73
74
74
75 TAO = split_lines(""" The Way that can be told of is not the eternal Way;
75 TAO = split_lines(""" The Way that can be told of is not the eternal Way;
76 The name that can be named is not the eternal name.
76 The name that can be named is not the eternal name.
77 The Nameless is the origin of Heaven and Earth;
77 The Nameless is the origin of Heaven and Earth;
78 The named is the mother of all things.
78 The named is the mother of all things.
79
79
80 Therefore let there always be non-being,
80 Therefore let there always be non-being,
81 so we may see their subtlety,
81 so we may see their subtlety,
82 And let there always be being,
82 And let there always be being,
83 so we may see their result.
83 so we may see their result.
84 The two are the same,
84 The two are the same,
85 But after they are produced,
85 But after they are produced,
86 they have different names.
86 they have different names.
87
87
88 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
88 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
89
89
90 """)
90 """)
91
91
92 MERGED_RESULT = split_lines("""\
92 MERGED_RESULT = split_lines("""\
93 The Way that can be told of is not the eternal Way;
93 The Way that can be told of is not the eternal Way;
94 The name that can be named is not the eternal name.
94 The name that can be named is not the eternal name.
95 The Nameless is the origin of Heaven and Earth;
95 The Nameless is the origin of Heaven and Earth;
96 The Named is the mother of all things.
96 The Named is the mother of all things.
97 Therefore let there always be non-being,
97 Therefore let there always be non-being,
98 so we may see their subtlety,
98 so we may see their subtlety,
99 And let there always be being,
99 And let there always be being,
100 so we may see their result.
100 so we may see their result.
101 The two are the same,
101 The two are the same,
102 But after they are produced,
102 But after they are produced,
103 they have different names.
103 they have different names.
104 <<<<<<< LAO
104 <<<<<<< LAO
105 =======
105 =======
106
106
107 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
107 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
108
108
109 >>>>>>> TAO
109 >>>>>>> TAO
110 """)
110 """)
111
111
112 class TestMerge3(TestCase):
112 class TestMerge3(TestCase):
113 def log(self, msg):
113 def log(self, msg):
114 pass
114 pass
115
115
116 def test_no_changes(self):
116 def test_no_changes(self):
117 """No conflicts because nothing changed"""
117 """No conflicts because nothing changed"""
118 m3 = Merge3(['aaa', 'bbb'],
118 m3 = Merge3(['aaa', 'bbb'],
119 ['aaa', 'bbb'],
119 ['aaa', 'bbb'],
120 ['aaa', 'bbb'])
120 ['aaa', 'bbb'])
121
121
122 self.assertEquals(m3.find_unconflicted(),
122 self.assertEquals(m3.find_unconflicted(),
123 [(0, 2)])
123 [(0, 2)])
124
124
125 self.assertEquals(list(m3.find_sync_regions()),
125 self.assertEquals(list(m3.find_sync_regions()),
126 [(0, 2,
126 [(0, 2,
127 0, 2,
127 0, 2,
128 0, 2),
128 0, 2),
129 (2, 2, 2, 2, 2, 2)])
129 (2, 2, 2, 2, 2, 2)])
130
130
131 self.assertEquals(list(m3.merge_regions()),
131 self.assertEquals(list(m3.merge_regions()),
132 [('unchanged', 0, 2)])
132 [('unchanged', 0, 2)])
133
133
134 self.assertEquals(list(m3.merge_groups()),
134 self.assertEquals(list(m3.merge_groups()),
135 [('unchanged', ['aaa', 'bbb'])])
135 [('unchanged', ['aaa', 'bbb'])])
136
136
137 def test_front_insert(self):
137 def test_front_insert(self):
138 m3 = Merge3(['zz'],
138 m3 = Merge3(['zz'],
139 ['aaa', 'bbb', 'zz'],
139 ['aaa', 'bbb', 'zz'],
140 ['zz'])
140 ['zz'])
141
141
142 # todo: should use a sentinal at end as from get_matching_blocks
142 # todo: should use a sentinal at end as from get_matching_blocks
143 # to match without zz
143 # to match without zz
144 self.assertEquals(list(m3.find_sync_regions()),
144 self.assertEquals(list(m3.find_sync_regions()),
145 [(0, 1, 2, 3, 0, 1),
145 [(0, 1, 2, 3, 0, 1),
146 (1, 1, 3, 3, 1, 1),])
146 (1, 1, 3, 3, 1, 1)])
147
147
148 self.assertEquals(list(m3.merge_regions()),
148 self.assertEquals(list(m3.merge_regions()),
149 [('a', 0, 2),
149 [('a', 0, 2),
150 ('unchanged', 0, 1)])
150 ('unchanged', 0, 1)])
151
151
152 self.assertEquals(list(m3.merge_groups()),
152 self.assertEquals(list(m3.merge_groups()),
153 [('a', ['aaa', 'bbb']),
153 [('a', ['aaa', 'bbb']),
154 ('unchanged', ['zz'])])
154 ('unchanged', ['zz'])])
155
155
156 def test_null_insert(self):
156 def test_null_insert(self):
157 m3 = Merge3([],
157 m3 = Merge3([],
158 ['aaa', 'bbb'],
158 ['aaa', 'bbb'],
159 [])
159 [])
160 # todo: should use a sentinal at end as from get_matching_blocks
160 # todo: should use a sentinal at end as from get_matching_blocks
161 # to match without zz
161 # to match without zz
162 self.assertEquals(list(m3.find_sync_regions()),
162 self.assertEquals(list(m3.find_sync_regions()),
163 [(0, 0, 2, 2, 0, 0)])
163 [(0, 0, 2, 2, 0, 0)])
164
164
165 self.assertEquals(list(m3.merge_regions()),
165 self.assertEquals(list(m3.merge_regions()),
166 [('a', 0, 2)])
166 [('a', 0, 2)])
167
167
168 self.assertEquals(list(m3.merge_lines()),
168 self.assertEquals(list(m3.merge_lines()),
169 ['aaa', 'bbb'])
169 ['aaa', 'bbb'])
170
170
171 def test_no_conflicts(self):
171 def test_no_conflicts(self):
172 """No conflicts because only one side changed"""
172 """No conflicts because only one side changed"""
173 m3 = Merge3(['aaa', 'bbb'],
173 m3 = Merge3(['aaa', 'bbb'],
174 ['aaa', '111', 'bbb'],
174 ['aaa', '111', 'bbb'],
175 ['aaa', 'bbb'])
175 ['aaa', 'bbb'])
176
176
177 self.assertEquals(m3.find_unconflicted(),
177 self.assertEquals(m3.find_unconflicted(),
178 [(0, 1), (1, 2)])
178 [(0, 1), (1, 2)])
179
179
180 self.assertEquals(list(m3.find_sync_regions()),
180 self.assertEquals(list(m3.find_sync_regions()),
181 [(0, 1, 0, 1, 0, 1),
181 [(0, 1, 0, 1, 0, 1),
182 (1, 2, 2, 3, 1, 2),
182 (1, 2, 2, 3, 1, 2),
183 (2, 2, 3, 3, 2, 2),])
183 (2, 2, 3, 3, 2, 2)])
184
184
185 self.assertEquals(list(m3.merge_regions()),
185 self.assertEquals(list(m3.merge_regions()),
186 [('unchanged', 0, 1),
186 [('unchanged', 0, 1),
187 ('a', 1, 2),
187 ('a', 1, 2),
188 ('unchanged', 1, 2),])
188 ('unchanged', 1, 2)])
189
189
190 def test_append_a(self):
190 def test_append_a(self):
191 m3 = Merge3(['aaa\n', 'bbb\n'],
191 m3 = Merge3(['aaa\n', 'bbb\n'],
192 ['aaa\n', 'bbb\n', '222\n'],
192 ['aaa\n', 'bbb\n', '222\n'],
193 ['aaa\n', 'bbb\n'])
193 ['aaa\n', 'bbb\n'])
194
194
195 self.assertEquals(''.join(m3.merge_lines()),
195 self.assertEquals(''.join(m3.merge_lines()),
196 'aaa\nbbb\n222\n')
196 'aaa\nbbb\n222\n')
197
197
198 def test_append_b(self):
198 def test_append_b(self):
199 m3 = Merge3(['aaa\n', 'bbb\n'],
199 m3 = Merge3(['aaa\n', 'bbb\n'],
200 ['aaa\n', 'bbb\n'],
200 ['aaa\n', 'bbb\n'],
201 ['aaa\n', 'bbb\n', '222\n'])
201 ['aaa\n', 'bbb\n', '222\n'])
202
202
203 self.assertEquals(''.join(m3.merge_lines()),
203 self.assertEquals(''.join(m3.merge_lines()),
204 'aaa\nbbb\n222\n')
204 'aaa\nbbb\n222\n')
205
205
206 def test_append_agreement(self):
206 def test_append_agreement(self):
207 m3 = Merge3(['aaa\n', 'bbb\n'],
207 m3 = Merge3(['aaa\n', 'bbb\n'],
208 ['aaa\n', 'bbb\n', '222\n'],
208 ['aaa\n', 'bbb\n', '222\n'],
209 ['aaa\n', 'bbb\n', '222\n'])
209 ['aaa\n', 'bbb\n', '222\n'])
210
210
211 self.assertEquals(''.join(m3.merge_lines()),
211 self.assertEquals(''.join(m3.merge_lines()),
212 'aaa\nbbb\n222\n')
212 'aaa\nbbb\n222\n')
213
213
214 def test_append_clash(self):
214 def test_append_clash(self):
215 m3 = Merge3(['aaa\n', 'bbb\n'],
215 m3 = Merge3(['aaa\n', 'bbb\n'],
216 ['aaa\n', 'bbb\n', '222\n'],
216 ['aaa\n', 'bbb\n', '222\n'],
217 ['aaa\n', 'bbb\n', '333\n'])
217 ['aaa\n', 'bbb\n', '333\n'])
218
218
219 ml = m3.merge_lines(name_a='a',
219 ml = m3.merge_lines(name_a='a',
220 name_b='b',
220 name_b='b',
221 start_marker='<<',
221 start_marker='<<',
222 mid_marker='--',
222 mid_marker='--',
223 end_marker='>>')
223 end_marker='>>')
224 self.assertEquals(''.join(ml),
224 self.assertEquals(''.join(ml),
225 'aaa\n'
225 'aaa\n'
226 'bbb\n'
226 'bbb\n'
227 '<< a\n'
227 '<< a\n'
228 '222\n'
228 '222\n'
229 '--\n'
229 '--\n'
230 '333\n'
230 '333\n'
231 '>> b\n'
231 '>> b\n'
232 )
232 )
233
233
234 def test_insert_agreement(self):
234 def test_insert_agreement(self):
235 m3 = Merge3(['aaa\n', 'bbb\n'],
235 m3 = Merge3(['aaa\n', 'bbb\n'],
236 ['aaa\n', '222\n', 'bbb\n'],
236 ['aaa\n', '222\n', 'bbb\n'],
237 ['aaa\n', '222\n', 'bbb\n'])
237 ['aaa\n', '222\n', 'bbb\n'])
238
238
239 ml = m3.merge_lines(name_a='a',
239 ml = m3.merge_lines(name_a='a',
240 name_b='b',
240 name_b='b',
241 start_marker='<<',
241 start_marker='<<',
242 mid_marker='--',
242 mid_marker='--',
243 end_marker='>>')
243 end_marker='>>')
244 self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
244 self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
245
245
246
246
247 def test_insert_clash(self):
247 def test_insert_clash(self):
248 """Both try to insert lines in the same place."""
248 """Both try to insert lines in the same place."""
249 m3 = Merge3(['aaa\n', 'bbb\n'],
249 m3 = Merge3(['aaa\n', 'bbb\n'],
250 ['aaa\n', '111\n', 'bbb\n'],
250 ['aaa\n', '111\n', 'bbb\n'],
251 ['aaa\n', '222\n', 'bbb\n'])
251 ['aaa\n', '222\n', 'bbb\n'])
252
252
253 self.assertEquals(m3.find_unconflicted(),
253 self.assertEquals(m3.find_unconflicted(),
254 [(0, 1), (1, 2)])
254 [(0, 1), (1, 2)])
255
255
256 self.assertEquals(list(m3.find_sync_regions()),
256 self.assertEquals(list(m3.find_sync_regions()),
257 [(0, 1, 0, 1, 0, 1),
257 [(0, 1, 0, 1, 0, 1),
258 (1, 2, 2, 3, 2, 3),
258 (1, 2, 2, 3, 2, 3),
259 (2, 2, 3, 3, 3, 3),])
259 (2, 2, 3, 3, 3, 3)])
260
260
261 self.assertEquals(list(m3.merge_regions()),
261 self.assertEquals(list(m3.merge_regions()),
262 [('unchanged', 0, 1),
262 [('unchanged', 0, 1),
263 ('conflict', 1, 1, 1, 2, 1, 2),
263 ('conflict', 1, 1, 1, 2, 1, 2),
264 ('unchanged', 1, 2)])
264 ('unchanged', 1, 2)])
265
265
266 self.assertEquals(list(m3.merge_groups()),
266 self.assertEquals(list(m3.merge_groups()),
267 [('unchanged', ['aaa\n']),
267 [('unchanged', ['aaa\n']),
268 ('conflict', [], ['111\n'], ['222\n']),
268 ('conflict', [], ['111\n'], ['222\n']),
269 ('unchanged', ['bbb\n']),
269 ('unchanged', ['bbb\n']),
270 ])
270 ])
271
271
272 ml = m3.merge_lines(name_a='a',
272 ml = m3.merge_lines(name_a='a',
273 name_b='b',
273 name_b='b',
274 start_marker='<<',
274 start_marker='<<',
275 mid_marker='--',
275 mid_marker='--',
276 end_marker='>>')
276 end_marker='>>')
277 self.assertEquals(''.join(ml),
277 self.assertEquals(''.join(ml),
278 '''aaa
278 '''aaa
279 << a
279 << a
280 111
280 111
281 --
281 --
282 222
282 222
283 >> b
283 >> b
284 bbb
284 bbb
285 ''')
285 ''')
286
286
287 def test_replace_clash(self):
287 def test_replace_clash(self):
288 """Both try to insert lines in the same place."""
288 """Both try to insert lines in the same place."""
289 m3 = Merge3(['aaa', '000', 'bbb'],
289 m3 = Merge3(['aaa', '000', 'bbb'],
290 ['aaa', '111', 'bbb'],
290 ['aaa', '111', 'bbb'],
291 ['aaa', '222', 'bbb'])
291 ['aaa', '222', 'bbb'])
292
292
293 self.assertEquals(m3.find_unconflicted(),
293 self.assertEquals(m3.find_unconflicted(),
294 [(0, 1), (2, 3)])
294 [(0, 1), (2, 3)])
295
295
296 self.assertEquals(list(m3.find_sync_regions()),
296 self.assertEquals(list(m3.find_sync_regions()),
297 [(0, 1, 0, 1, 0, 1),
297 [(0, 1, 0, 1, 0, 1),
298 (2, 3, 2, 3, 2, 3),
298 (2, 3, 2, 3, 2, 3),
299 (3, 3, 3, 3, 3, 3),])
299 (3, 3, 3, 3, 3, 3)])
300
300
301 def test_replace_multi(self):
301 def test_replace_multi(self):
302 """Replacement with regions of different size."""
302 """Replacement with regions of different size."""
303 m3 = Merge3(['aaa', '000', '000', 'bbb'],
303 m3 = Merge3(['aaa', '000', '000', 'bbb'],
304 ['aaa', '111', '111', '111', 'bbb'],
304 ['aaa', '111', '111', '111', 'bbb'],
305 ['aaa', '222', '222', '222', '222', 'bbb'])
305 ['aaa', '222', '222', '222', '222', 'bbb'])
306
306
307 self.assertEquals(m3.find_unconflicted(),
307 self.assertEquals(m3.find_unconflicted(),
308 [(0, 1), (3, 4)])
308 [(0, 1), (3, 4)])
309
309
310
310
311 self.assertEquals(list(m3.find_sync_regions()),
311 self.assertEquals(list(m3.find_sync_regions()),
312 [(0, 1, 0, 1, 0, 1),
312 [(0, 1, 0, 1, 0, 1),
313 (3, 4, 4, 5, 5, 6),
313 (3, 4, 4, 5, 5, 6),
314 (4, 4, 5, 5, 6, 6)])
314 (4, 4, 5, 5, 6, 6)])
315
315
316 def test_merge_poem(self):
316 def test_merge_poem(self):
317 """Test case from diff3 manual"""
317 """Test case from diff3 manual"""
318 m3 = Merge3(TZU, LAO, TAO)
318 m3 = Merge3(TZU, LAO, TAO)
319 ml = list(m3.merge_lines('LAO', 'TAO'))
319 ml = list(m3.merge_lines('LAO', 'TAO'))
320 self.log('merge result:')
320 self.log('merge result:')
321 self.log(''.join(ml))
321 self.log(''.join(ml))
322 self.assertEquals(ml, MERGED_RESULT)
322 self.assertEquals(ml, MERGED_RESULT)
323
323
324 def test_minimal_conflicts_common(self):
324 def test_minimal_conflicts_common(self):
325 """Reprocessing"""
325 """Reprocessing"""
326 base_text = ("a\n" * 20).splitlines(True)
326 base_text = ("a\n" * 20).splitlines(True)
327 this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
327 this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
328 other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
328 other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
329 m3 = Merge3(base_text, other_text, this_text)
329 m3 = Merge3(base_text, other_text, this_text)
330 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
330 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
331 merged_text = "".join(list(m_lines))
331 merged_text = "".join(list(m_lines))
332 optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n"
332 optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n"
333 + ">>>>>>> THIS\n"
333 + ">>>>>>> THIS\n"
334 + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n"
334 + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n"
335 + 2* "b\n" + ">>>>>>> THIS\n")
335 + 2* "b\n" + ">>>>>>> THIS\n")
336 self.assertEquals(optimal_text, merged_text)
336 self.assertEquals(optimal_text, merged_text)
337
337
338 def test_minimal_conflicts_unique(self):
338 def test_minimal_conflicts_unique(self):
339 def add_newline(s):
339 def add_newline(s):
340 """Add a newline to each entry in the string"""
340 """Add a newline to each entry in the string"""
341 return [(x+'\n') for x in s]
341 return [(x+'\n') for x in s]
342
342
343 base_text = add_newline("abcdefghijklm")
343 base_text = add_newline("abcdefghijklm")
344 this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ")
344 this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ")
345 other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2")
345 other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2")
346 m3 = Merge3(base_text, other_text, this_text)
346 m3 = Merge3(base_text, other_text, this_text)
347 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
347 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
348 merged_text = "".join(list(m_lines))
348 merged_text = "".join(list(m_lines))
349 optimal_text = ''.join(add_newline("abcdefghijklm")
349 optimal_text = ''.join(add_newline("abcdefghijklm")
350 + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
350 + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
351 + add_newline('OPQRSTUVWXY')
351 + add_newline('OPQRSTUVWXY')
352 + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
352 + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
353 )
353 )
354 self.assertEquals(optimal_text, merged_text)
354 self.assertEquals(optimal_text, merged_text)
355
355
356 def test_minimal_conflicts_nonunique(self):
356 def test_minimal_conflicts_nonunique(self):
357 def add_newline(s):
357 def add_newline(s):
358 """Add a newline to each entry in the string"""
358 """Add a newline to each entry in the string"""
359 return [(x+'\n') for x in s]
359 return [(x+'\n') for x in s]
360
360
361 base_text = add_newline("abacddefgghij")
361 base_text = add_newline("abacddefgghij")
362 this_text = add_newline("abacddefgghijkalmontfprz")
362 this_text = add_newline("abacddefgghijkalmontfprz")
363 other_text = add_newline("abacddefgghijknlmontfprd")
363 other_text = add_newline("abacddefgghijknlmontfprd")
364 m3 = Merge3(base_text, other_text, this_text)
364 m3 = Merge3(base_text, other_text, this_text)
365 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
365 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
366 merged_text = "".join(list(m_lines))
366 merged_text = "".join(list(m_lines))
367 optimal_text = ''.join(add_newline("abacddefgghijk")
367 optimal_text = ''.join(add_newline("abacddefgghijk")
368 + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
368 + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
369 + add_newline('lmontfpr')
369 + add_newline('lmontfpr')
370 + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
370 + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
371 )
371 )
372 self.assertEquals(optimal_text, merged_text)
372 self.assertEquals(optimal_text, merged_text)
373
373
374 def test_reprocess_and_base(self):
374 def test_reprocess_and_base(self):
375 """Reprocessing and showing base breaks correctly"""
375 """Reprocessing and showing base breaks correctly"""
376 base_text = ("a\n" * 20).splitlines(True)
376 base_text = ("a\n" * 20).splitlines(True)
377 this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
377 this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
378 other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
378 other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
379 m3 = Merge3(base_text, other_text, this_text)
379 m3 = Merge3(base_text, other_text, this_text)
380 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True,
380 m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True,
381 base_marker='|||||||')
381 base_marker='|||||||')
382 self.assertRaises(CantReprocessAndShowBase, list, m_lines)
382 self.assertRaises(CantReprocessAndShowBase, list, m_lines)
383
383
384 def test_binary(self):
384 def test_binary(self):
385 self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b'])
385 self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b'])
386
386
387 def test_dos_text(self):
387 def test_dos_text(self):
388 base_text = 'a\r\n'
388 base_text = 'a\r\n'
389 this_text = 'b\r\n'
389 this_text = 'b\r\n'
390 other_text = 'c\r\n'
390 other_text = 'c\r\n'
391 m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
391 m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
392 this_text.splitlines(True))
392 this_text.splitlines(True))
393 m_lines = m3.merge_lines('OTHER', 'THIS')
393 m_lines = m3.merge_lines('OTHER', 'THIS')
394 self.assertEqual('<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
394 self.assertEqual('<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
395 '>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
395 '>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
396
396
397 def test_mac_text(self):
397 def test_mac_text(self):
398 base_text = 'a\r'
398 base_text = 'a\r'
399 this_text = 'b\r'
399 this_text = 'b\r'
400 other_text = 'c\r'
400 other_text = 'c\r'
401 m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
401 m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
402 this_text.splitlines(True))
402 this_text.splitlines(True))
403 m_lines = m3.merge_lines('OTHER', 'THIS')
403 m_lines = m3.merge_lines('OTHER', 'THIS')
404 self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r'
404 self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r'
405 '>>>>>>> THIS\r'.splitlines(True), list(m_lines))
405 '>>>>>>> THIS\r'.splitlines(True), list(m_lines))
406
406
407 if __name__ == '__main__':
407 if __name__ == '__main__':
408 # hide the timer
408 # hide the timer
409 import time
409 import time
410 orig = time.time
410 orig = time.time
411 try:
411 try:
412 time.time = lambda: 0
412 time.time = lambda: 0
413 unittest.main()
413 unittest.main()
414 finally:
414 finally:
415 time.time = orig
415 time.time = orig
416
416
@@ -1,186 +1,186 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 rm -rf sub
3 rm -rf sub
4 mkdir sub
4 mkdir sub
5 cd sub
5 cd sub
6 hg init t
6 hg init t
7 cd t
7 cd t
8
8
9 echo % first revision, no sub
9 echo % first revision, no sub
10 echo a > a
10 echo a > a
11 hg ci -Am0
11 hg ci -Am0
12
12
13 echo % add first sub
13 echo % add first sub
14 echo s = s > .hgsub
14 echo s = s > .hgsub
15 hg add .hgsub
15 hg add .hgsub
16 hg init s
16 hg init s
17 echo a > s/a
17 echo a > s/a
18 hg -R s ci -Ams0
18 hg -R s ci -Ams0
19 hg ci -m1
19 hg ci -m1
20
20
21 echo % add sub sub
21 echo % add sub sub
22 echo ss = ss > s/.hgsub
22 echo ss = ss > s/.hgsub
23 hg init s/ss
23 hg init s/ss
24 echo a > s/ss/a
24 echo a > s/ss/a
25 hg -R s add s/.hgsub
25 hg -R s add s/.hgsub
26 hg -R s/ss add s/ss/a
26 hg -R s/ss add s/ss/a
27 hg ci -m2
27 hg ci -m2
28
28
29 echo % bump sub rev
29 echo % bump sub rev
30 echo b > s/a
30 echo b > s/a
31 hg -R s ci -ms1
31 hg -R s ci -ms1
32 hg ci -m3
32 hg ci -m3
33
33
34 echo % leave sub dirty
34 echo % leave sub dirty
35 echo c > s/a
35 echo c > s/a
36 hg ci -m4
36 hg ci -m4
37 hg tip -R s
37 hg tip -R s
38
38
39 echo % check caching
39 echo % check caching
40 hg co 0
40 hg co 0
41 hg debugsub
41 hg debugsub
42 echo % restore
42 echo % restore
43 hg co
43 hg co
44 hg debugsub
44 hg debugsub
45
45
46 echo % new branch for merge tests
46 echo % new branch for merge tests
47 hg co 1
47 hg co 1
48 echo t = t >> .hgsub
48 echo t = t >> .hgsub
49 hg init t
49 hg init t
50 echo t > t/t
50 echo t > t/t
51 hg -R t add t
51 hg -R t add t
52 echo % 5
52 echo % 5
53 hg ci -m5 # add sub
53 hg ci -m5 # add sub
54 echo t2 > t/t
54 echo t2 > t/t
55 echo % 6
55 echo % 6
56 hg st -R s
56 hg st -R s
57 hg ci -m6 # change sub
57 hg ci -m6 # change sub
58 hg debugsub
58 hg debugsub
59 echo t3 > t/t
59 echo t3 > t/t
60 echo % 7
60 echo % 7
61 hg ci -m7 # change sub again for conflict test
61 hg ci -m7 # change sub again for conflict test
62 hg rm .hgsub
62 hg rm .hgsub
63 echo % 8
63 echo % 8
64 hg ci -m8 # remove sub
64 hg ci -m8 # remove sub
65
65
66 echo % merge tests
66 echo % merge tests
67 hg co -C 3
67 hg co -C 3
68 hg merge 5 # test adding
68 hg merge 5 # test adding
69 hg debugsub
69 hg debugsub
70 hg ci -m9
70 hg ci -m9
71 hg merge 6 --debug # test change
71 hg merge 6 --debug # test change
72 hg debugsub
72 hg debugsub
73 echo conflict > t/t
73 echo conflict > t/t
74 hg ci -m10
74 hg ci -m10
75 HGMERGE=internal:merge hg merge --debug 7 # test conflict
75 HGMERGE=internal:merge hg merge --debug 7 # test conflict
76 echo % should conflict
76 echo % should conflict
77 cat t/t
77 cat t/t
78
78
79 echo % clone
79 echo % clone
80 cd ..
80 cd ..
81 hg clone t tc
81 hg clone t tc
82 cd tc
82 cd tc
83 hg debugsub
83 hg debugsub
84
84
85 echo % push
85 echo % push
86 echo bah > t/t
86 echo bah > t/t
87 hg ci -m11
87 hg ci -m11
88 hg push | sed 's/ .*sub/ ...sub/g'
88 hg push | sed 's/ .*sub/ ...sub/g'
89
89
90 echo % push -f
90 echo % push -f
91 echo bah > s/a
91 echo bah > s/a
92 hg ci -m12
92 hg ci -m12
93 hg push | sed 's/ .*sub/ ...sub/g'
93 hg push | sed 's/ .*sub/ ...sub/g'
94 hg push -f | sed 's/ .*sub/ ...sub/g'
94 hg push -f | sed 's/ .*sub/ ...sub/g'
95
95
96 echo % update
96 echo % update
97 cd ../t
97 cd ../t
98 hg up -C # discard our earlier merge
98 hg up -C # discard our earlier merge
99 echo blah > t/t
99 echo blah > t/t
100 hg ci -m13
100 hg ci -m13
101
101
102 echo % pull
102 echo % pull
103 cd ../tc
103 cd ../tc
104 hg pull | sed 's/ .*sub/ ...sub/g'
104 hg pull | sed 's/ .*sub/ ...sub/g'
105 hg up # should pull t
105 hg up # should pull t
106 cat t/t
106 cat t/t
107
107
108 echo % bogus subrepo path aborts
108 echo % bogus subrepo path aborts
109 echo 'bogus=[boguspath' >> .hgsub
109 echo 'bogus=[boguspath' >> .hgsub
110 hg ci -m 'bogus subrepo path'
110 hg ci -m 'bogus subrepo path'
111
111
112 echo % issue 1986
112 echo % issue 1986
113 cd ..
113 cd ..
114 rm -rf sub
114 rm -rf sub
115 hg init main
115 hg init main
116 cd main
116 cd main
117
117
118 hg init s # subrepo layout
118 hg init s # subrepo layout
119 cd s #
119 cd s #
120 echo a > a # o 5 br
120 echo a > a # o 5 br
121 hg ci -Am1 # /|
121 hg ci -Am1 # /|
122 hg branch br # o | 4 default
122 hg branch br # o | 4 default
123 echo a >> a # | |
123 echo a >> a # | |
124 hg ci -m1 # | o 3 br
124 hg ci -m1 # | o 3 br
125 hg up default # |/|
125 hg up default # |/|
126 echo b > b # o | 2 default
126 echo b > b # o | 2 default
127 hg ci -Am1 # | |
127 hg ci -Am1 # | |
128 hg up br # | o 1 br
128 hg up br # | o 1 br
129 hg merge tip # |/
129 hg merge tip # |/
130 hg ci -m1 # o 0 default
130 hg ci -m1 # o 0 default
131 hg up 2
131 hg up 2
132 echo c > c
132 echo c > c
133 hg ci -Am1
133 hg ci -Am1
134 hg up 3
134 hg up 3
135 hg merge 4
135 hg merge 4
136 hg ci -m1
136 hg ci -m1
137
137
138 cd .. # main repo layout:
138 cd .. # main repo layout:
139 echo 's = s' > .hgsub #
139 echo 's = s' > .hgsub #
140 hg -R s up 2 # * <-- try to merge default into br again
140 hg -R s up 2 # * <-- try to merge default into br again
141 hg ci -Am1 # .`|
141 hg ci -Am1 # .`|
142 hg branch br # . o 5 br --> substate = 5
142 hg branch br # . o 5 br --> substate = 5
143 echo b > b # . |
143 echo b > b # . |
144 hg -R s up 3 # o | 4 default --> substate = 4
144 hg -R s up 3 # o | 4 default --> substate = 4
145 hg ci -Am1 # | |
145 hg ci -Am1 # | |
146 hg up default # | o 3 br --> substate = 2
146 hg up default # | o 3 br --> substate = 2
147 echo c > c # |/|
147 echo c > c # |/|
148 hg ci -Am1 # o | 2 default --> substate = 2
148 hg ci -Am1 # o | 2 default --> substate = 2
149 hg up 1 # | |
149 hg up 1 # | |
150 hg merge 2 # | o 1 br --> substate = 3
150 hg merge 2 # | o 1 br --> substate = 3
151 hg ci -m1 # |/
151 hg ci -m1 # |/
152 hg up 2 # o 0 default --> substate = 2
152 hg up 2 # o 0 default --> substate = 2
153 hg -R s up 4
153 hg -R s up 4
154 echo d > d
154 echo d > d
155 hg ci -Am1
155 hg ci -Am1
156 hg up 3
156 hg up 3
157 hg -R s up 5
157 hg -R s up 5
158 echo e > e
158 echo e > e
159 hg ci -Am1
159 hg ci -Am1
160
160
161 hg up 5
161 hg up 5
162 hg merge 4 # try to merge default into br again
162 hg merge 4 # try to merge default into br again
163 cd ..
163 cd ..
164
164
165 echo % test repository cloning
165 echo % test repository cloning
166 mkdir mercurial mercurial2
166 mkdir mercurial mercurial2
167 hg init nested_absolute
167 hg init nested_absolute
168 echo test > nested_absolute/foo
168 echo test > nested_absolute/foo
169 hg -R nested_absolute add
169 hg -R nested_absolute add
170 hg -R nested_absolute ci -mtest
170 hg -R nested_absolute ci -mtest
171 cd mercurial
171 cd mercurial
172 hg init nested_relative
172 hg init nested_relative
173 echo test2 > nested_relative/foo2
173 echo test2 > nested_relative/foo2
174 hg -R nested_relative add
174 hg -R nested_relative add
175 hg -R nested_relative ci -mtest2
175 hg -R nested_relative ci -mtest2
176 hg init main
176 hg init main
177 echo nested_relative = ../nested_relative > main/.hgsub
177 echo nested_relative = ../nested_relative > main/.hgsub
178 echo nested_absolute = $PWD/nested_absolute >> main/.hgsub
178 echo nested_absolute = `pwd`/nested_absolute >> main/.hgsub
179 hg -R main add
179 hg -R main add
180 hg -R main ci -m "add subrepos"
180 hg -R main ci -m "add subrepos"
181 cd ..
181 cd ..
182 hg clone mercurial/main mercurial2/main
182 hg clone mercurial/main mercurial2/main
183 cat mercurial2/main/nested_absolute/.hg/hgrc mercurial2/main/nested_relative/.hg/hgrc | sed "s:${PWD}:/tmp:"
183 cat mercurial2/main/nested_absolute/.hg/hgrc mercurial2/main/nested_relative/.hg/hgrc | sed "s:${PWD}:/tmp:"
184 rm -rf mercurial mercurial2
184 rm -rf mercurial mercurial2
185
185
186 exit 0
186 exit 0
General Comments 0
You need to be logged in to leave comments. Login now