##// END OF EJS Templates
revset: split language services to revsetlang module (API)...
Yuya Nishihara -
r31024:0b835670 default
parent child Browse files
Show More

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

@@ -1,3610 +1,3610
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help COMMAND` for more details)::
17 Common tasks (use :hg:`help COMMAND` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behavior can be configured with::
31 files creations or deletions. This behavior can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60
60
61 This extension used to provide a strip command. This command now lives
61 This extension used to provide a strip command. This command now lives
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65 from __future__ import absolute_import
65 from __future__ import absolute_import
66
66
67 import errno
67 import errno
68 import os
68 import os
69 import re
69 import re
70 import shutil
70 import shutil
71 from mercurial.i18n import _
71 from mercurial.i18n import _
72 from mercurial.node import (
72 from mercurial.node import (
73 bin,
73 bin,
74 hex,
74 hex,
75 nullid,
75 nullid,
76 nullrev,
76 nullrev,
77 short,
77 short,
78 )
78 )
79 from mercurial import (
79 from mercurial import (
80 cmdutil,
80 cmdutil,
81 commands,
81 commands,
82 dirstateguard,
82 dirstateguard,
83 error,
83 error,
84 extensions,
84 extensions,
85 hg,
85 hg,
86 localrepo,
86 localrepo,
87 lock as lockmod,
87 lock as lockmod,
88 patch as patchmod,
88 patch as patchmod,
89 phases,
89 phases,
90 pycompat,
90 pycompat,
91 registrar,
91 registrar,
92 revset,
92 revsetlang,
93 scmutil,
93 scmutil,
94 smartset,
94 smartset,
95 subrepo,
95 subrepo,
96 util,
96 util,
97 )
97 )
98
98
99 release = lockmod.release
99 release = lockmod.release
100 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
100 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
101
101
102 cmdtable = {}
102 cmdtable = {}
103 command = cmdutil.command(cmdtable)
103 command = cmdutil.command(cmdtable)
104 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
104 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
105 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
105 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
106 # be specifying the version(s) of Mercurial they are tested with, or
106 # be specifying the version(s) of Mercurial they are tested with, or
107 # leave the attribute unspecified.
107 # leave the attribute unspecified.
108 testedwith = 'ships-with-hg-core'
108 testedwith = 'ships-with-hg-core'
109
109
110 # force load strip extension formerly included in mq and import some utility
110 # force load strip extension formerly included in mq and import some utility
111 try:
111 try:
112 stripext = extensions.find('strip')
112 stripext = extensions.find('strip')
113 except KeyError:
113 except KeyError:
114 # note: load is lazy so we could avoid the try-except,
114 # note: load is lazy so we could avoid the try-except,
115 # but I (marmoute) prefer this explicit code.
115 # but I (marmoute) prefer this explicit code.
116 class dummyui(object):
116 class dummyui(object):
117 def debug(self, msg):
117 def debug(self, msg):
118 pass
118 pass
119 stripext = extensions.load(dummyui(), 'strip', '')
119 stripext = extensions.load(dummyui(), 'strip', '')
120
120
121 strip = stripext.strip
121 strip = stripext.strip
122 checksubstate = stripext.checksubstate
122 checksubstate = stripext.checksubstate
123 checklocalchanges = stripext.checklocalchanges
123 checklocalchanges = stripext.checklocalchanges
124
124
125
125
126 # Patch names looks like unix-file names.
126 # Patch names looks like unix-file names.
127 # They must be joinable with queue directory and result in the patch path.
127 # They must be joinable with queue directory and result in the patch path.
128 normname = util.normpath
128 normname = util.normpath
129
129
130 class statusentry(object):
130 class statusentry(object):
131 def __init__(self, node, name):
131 def __init__(self, node, name):
132 self.node, self.name = node, name
132 self.node, self.name = node, name
133 def __repr__(self):
133 def __repr__(self):
134 return hex(self.node) + ':' + self.name
134 return hex(self.node) + ':' + self.name
135
135
136 # The order of the headers in 'hg export' HG patches:
136 # The order of the headers in 'hg export' HG patches:
137 HGHEADERS = [
137 HGHEADERS = [
138 # '# HG changeset patch',
138 # '# HG changeset patch',
139 '# User ',
139 '# User ',
140 '# Date ',
140 '# Date ',
141 '# ',
141 '# ',
142 '# Branch ',
142 '# Branch ',
143 '# Node ID ',
143 '# Node ID ',
144 '# Parent ', # can occur twice for merges - but that is not relevant for mq
144 '# Parent ', # can occur twice for merges - but that is not relevant for mq
145 ]
145 ]
146 # The order of headers in plain 'mail style' patches:
146 # The order of headers in plain 'mail style' patches:
147 PLAINHEADERS = {
147 PLAINHEADERS = {
148 'from': 0,
148 'from': 0,
149 'date': 1,
149 'date': 1,
150 'subject': 2,
150 'subject': 2,
151 }
151 }
152
152
153 def inserthgheader(lines, header, value):
153 def inserthgheader(lines, header, value):
154 """Assuming lines contains a HG patch header, add a header line with value.
154 """Assuming lines contains a HG patch header, add a header line with value.
155 >>> try: inserthgheader([], '# Date ', 'z')
155 >>> try: inserthgheader([], '# Date ', 'z')
156 ... except ValueError, inst: print "oops"
156 ... except ValueError, inst: print "oops"
157 oops
157 oops
158 >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
158 >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
159 ['# HG changeset patch', '# Date z']
159 ['# HG changeset patch', '# Date z']
160 >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
160 >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
161 ['# HG changeset patch', '# Date z', '']
161 ['# HG changeset patch', '# Date z', '']
162 >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
162 >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
163 ['# HG changeset patch', '# User y', '# Date z']
163 ['# HG changeset patch', '# User y', '# Date z']
164 >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
164 >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
165 ... '# User ', 'z')
165 ... '# User ', 'z')
166 ['# HG changeset patch', '# Date x', '# User z']
166 ['# HG changeset patch', '# Date x', '# User z']
167 >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
167 >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
168 ['# HG changeset patch', '# Date z']
168 ['# HG changeset patch', '# Date z']
169 >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
169 >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
170 ['# HG changeset patch', '# Date z', '', '# Date y']
170 ['# HG changeset patch', '# Date z', '', '# Date y']
171 >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z')
171 >>> inserthgheader(['# HG changeset patch', '# Parent y'], '# Date ', 'z')
172 ['# HG changeset patch', '# Date z', '# Parent y']
172 ['# HG changeset patch', '# Date z', '# Parent y']
173 """
173 """
174 start = lines.index('# HG changeset patch') + 1
174 start = lines.index('# HG changeset patch') + 1
175 newindex = HGHEADERS.index(header)
175 newindex = HGHEADERS.index(header)
176 bestpos = len(lines)
176 bestpos = len(lines)
177 for i in range(start, len(lines)):
177 for i in range(start, len(lines)):
178 line = lines[i]
178 line = lines[i]
179 if not line.startswith('# '):
179 if not line.startswith('# '):
180 bestpos = min(bestpos, i)
180 bestpos = min(bestpos, i)
181 break
181 break
182 for lineindex, h in enumerate(HGHEADERS):
182 for lineindex, h in enumerate(HGHEADERS):
183 if line.startswith(h):
183 if line.startswith(h):
184 if lineindex == newindex:
184 if lineindex == newindex:
185 lines[i] = header + value
185 lines[i] = header + value
186 return lines
186 return lines
187 if lineindex > newindex:
187 if lineindex > newindex:
188 bestpos = min(bestpos, i)
188 bestpos = min(bestpos, i)
189 break # next line
189 break # next line
190 lines.insert(bestpos, header + value)
190 lines.insert(bestpos, header + value)
191 return lines
191 return lines
192
192
193 def insertplainheader(lines, header, value):
193 def insertplainheader(lines, header, value):
194 """For lines containing a plain patch header, add a header line with value.
194 """For lines containing a plain patch header, add a header line with value.
195 >>> insertplainheader([], 'Date', 'z')
195 >>> insertplainheader([], 'Date', 'z')
196 ['Date: z']
196 ['Date: z']
197 >>> insertplainheader([''], 'Date', 'z')
197 >>> insertplainheader([''], 'Date', 'z')
198 ['Date: z', '']
198 ['Date: z', '']
199 >>> insertplainheader(['x'], 'Date', 'z')
199 >>> insertplainheader(['x'], 'Date', 'z')
200 ['Date: z', '', 'x']
200 ['Date: z', '', 'x']
201 >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
201 >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
202 ['From: y', 'Date: z', '', 'x']
202 ['From: y', 'Date: z', '', 'x']
203 >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
203 >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
204 [' date : x', 'From: z', '']
204 [' date : x', 'From: z', '']
205 >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
205 >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
206 ['Date: z', '', 'Date: y']
206 ['Date: z', '', 'Date: y']
207 >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
207 >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
208 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
208 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
209 """
209 """
210 newprio = PLAINHEADERS[header.lower()]
210 newprio = PLAINHEADERS[header.lower()]
211 bestpos = len(lines)
211 bestpos = len(lines)
212 for i, line in enumerate(lines):
212 for i, line in enumerate(lines):
213 if ':' in line:
213 if ':' in line:
214 lheader = line.split(':', 1)[0].strip().lower()
214 lheader = line.split(':', 1)[0].strip().lower()
215 lprio = PLAINHEADERS.get(lheader, newprio + 1)
215 lprio = PLAINHEADERS.get(lheader, newprio + 1)
216 if lprio == newprio:
216 if lprio == newprio:
217 lines[i] = '%s: %s' % (header, value)
217 lines[i] = '%s: %s' % (header, value)
218 return lines
218 return lines
219 if lprio > newprio and i < bestpos:
219 if lprio > newprio and i < bestpos:
220 bestpos = i
220 bestpos = i
221 else:
221 else:
222 if line:
222 if line:
223 lines.insert(i, '')
223 lines.insert(i, '')
224 if i < bestpos:
224 if i < bestpos:
225 bestpos = i
225 bestpos = i
226 break
226 break
227 lines.insert(bestpos, '%s: %s' % (header, value))
227 lines.insert(bestpos, '%s: %s' % (header, value))
228 return lines
228 return lines
229
229
230 class patchheader(object):
230 class patchheader(object):
231 def __init__(self, pf, plainmode=False):
231 def __init__(self, pf, plainmode=False):
232 def eatdiff(lines):
232 def eatdiff(lines):
233 while lines:
233 while lines:
234 l = lines[-1]
234 l = lines[-1]
235 if (l.startswith("diff -") or
235 if (l.startswith("diff -") or
236 l.startswith("Index:") or
236 l.startswith("Index:") or
237 l.startswith("===========")):
237 l.startswith("===========")):
238 del lines[-1]
238 del lines[-1]
239 else:
239 else:
240 break
240 break
241 def eatempty(lines):
241 def eatempty(lines):
242 while lines:
242 while lines:
243 if not lines[-1].strip():
243 if not lines[-1].strip():
244 del lines[-1]
244 del lines[-1]
245 else:
245 else:
246 break
246 break
247
247
248 message = []
248 message = []
249 comments = []
249 comments = []
250 user = None
250 user = None
251 date = None
251 date = None
252 parent = None
252 parent = None
253 format = None
253 format = None
254 subject = None
254 subject = None
255 branch = None
255 branch = None
256 nodeid = None
256 nodeid = None
257 diffstart = 0
257 diffstart = 0
258
258
259 for line in file(pf):
259 for line in file(pf):
260 line = line.rstrip()
260 line = line.rstrip()
261 if (line.startswith('diff --git')
261 if (line.startswith('diff --git')
262 or (diffstart and line.startswith('+++ '))):
262 or (diffstart and line.startswith('+++ '))):
263 diffstart = 2
263 diffstart = 2
264 break
264 break
265 diffstart = 0 # reset
265 diffstart = 0 # reset
266 if line.startswith("--- "):
266 if line.startswith("--- "):
267 diffstart = 1
267 diffstart = 1
268 continue
268 continue
269 elif format == "hgpatch":
269 elif format == "hgpatch":
270 # parse values when importing the result of an hg export
270 # parse values when importing the result of an hg export
271 if line.startswith("# User "):
271 if line.startswith("# User "):
272 user = line[7:]
272 user = line[7:]
273 elif line.startswith("# Date "):
273 elif line.startswith("# Date "):
274 date = line[7:]
274 date = line[7:]
275 elif line.startswith("# Parent "):
275 elif line.startswith("# Parent "):
276 parent = line[9:].lstrip() # handle double trailing space
276 parent = line[9:].lstrip() # handle double trailing space
277 elif line.startswith("# Branch "):
277 elif line.startswith("# Branch "):
278 branch = line[9:]
278 branch = line[9:]
279 elif line.startswith("# Node ID "):
279 elif line.startswith("# Node ID "):
280 nodeid = line[10:]
280 nodeid = line[10:]
281 elif not line.startswith("# ") and line:
281 elif not line.startswith("# ") and line:
282 message.append(line)
282 message.append(line)
283 format = None
283 format = None
284 elif line == '# HG changeset patch':
284 elif line == '# HG changeset patch':
285 message = []
285 message = []
286 format = "hgpatch"
286 format = "hgpatch"
287 elif (format != "tagdone" and (line.startswith("Subject: ") or
287 elif (format != "tagdone" and (line.startswith("Subject: ") or
288 line.startswith("subject: "))):
288 line.startswith("subject: "))):
289 subject = line[9:]
289 subject = line[9:]
290 format = "tag"
290 format = "tag"
291 elif (format != "tagdone" and (line.startswith("From: ") or
291 elif (format != "tagdone" and (line.startswith("From: ") or
292 line.startswith("from: "))):
292 line.startswith("from: "))):
293 user = line[6:]
293 user = line[6:]
294 format = "tag"
294 format = "tag"
295 elif (format != "tagdone" and (line.startswith("Date: ") or
295 elif (format != "tagdone" and (line.startswith("Date: ") or
296 line.startswith("date: "))):
296 line.startswith("date: "))):
297 date = line[6:]
297 date = line[6:]
298 format = "tag"
298 format = "tag"
299 elif format == "tag" and line == "":
299 elif format == "tag" and line == "":
300 # when looking for tags (subject: from: etc) they
300 # when looking for tags (subject: from: etc) they
301 # end once you find a blank line in the source
301 # end once you find a blank line in the source
302 format = "tagdone"
302 format = "tagdone"
303 elif message or line:
303 elif message or line:
304 message.append(line)
304 message.append(line)
305 comments.append(line)
305 comments.append(line)
306
306
307 eatdiff(message)
307 eatdiff(message)
308 eatdiff(comments)
308 eatdiff(comments)
309 # Remember the exact starting line of the patch diffs before consuming
309 # Remember the exact starting line of the patch diffs before consuming
310 # empty lines, for external use by TortoiseHg and others
310 # empty lines, for external use by TortoiseHg and others
311 self.diffstartline = len(comments)
311 self.diffstartline = len(comments)
312 eatempty(message)
312 eatempty(message)
313 eatempty(comments)
313 eatempty(comments)
314
314
315 # make sure message isn't empty
315 # make sure message isn't empty
316 if format and format.startswith("tag") and subject:
316 if format and format.startswith("tag") and subject:
317 message.insert(0, subject)
317 message.insert(0, subject)
318
318
319 self.message = message
319 self.message = message
320 self.comments = comments
320 self.comments = comments
321 self.user = user
321 self.user = user
322 self.date = date
322 self.date = date
323 self.parent = parent
323 self.parent = parent
324 # nodeid and branch are for external use by TortoiseHg and others
324 # nodeid and branch are for external use by TortoiseHg and others
325 self.nodeid = nodeid
325 self.nodeid = nodeid
326 self.branch = branch
326 self.branch = branch
327 self.haspatch = diffstart > 1
327 self.haspatch = diffstart > 1
328 self.plainmode = (plainmode or
328 self.plainmode = (plainmode or
329 '# HG changeset patch' not in self.comments and
329 '# HG changeset patch' not in self.comments and
330 any(c.startswith('Date: ') or
330 any(c.startswith('Date: ') or
331 c.startswith('From: ')
331 c.startswith('From: ')
332 for c in self.comments))
332 for c in self.comments))
333
333
334 def setuser(self, user):
334 def setuser(self, user):
335 try:
335 try:
336 inserthgheader(self.comments, '# User ', user)
336 inserthgheader(self.comments, '# User ', user)
337 except ValueError:
337 except ValueError:
338 if self.plainmode:
338 if self.plainmode:
339 insertplainheader(self.comments, 'From', user)
339 insertplainheader(self.comments, 'From', user)
340 else:
340 else:
341 tmp = ['# HG changeset patch', '# User ' + user]
341 tmp = ['# HG changeset patch', '# User ' + user]
342 self.comments = tmp + self.comments
342 self.comments = tmp + self.comments
343 self.user = user
343 self.user = user
344
344
345 def setdate(self, date):
345 def setdate(self, date):
346 try:
346 try:
347 inserthgheader(self.comments, '# Date ', date)
347 inserthgheader(self.comments, '# Date ', date)
348 except ValueError:
348 except ValueError:
349 if self.plainmode:
349 if self.plainmode:
350 insertplainheader(self.comments, 'Date', date)
350 insertplainheader(self.comments, 'Date', date)
351 else:
351 else:
352 tmp = ['# HG changeset patch', '# Date ' + date]
352 tmp = ['# HG changeset patch', '# Date ' + date]
353 self.comments = tmp + self.comments
353 self.comments = tmp + self.comments
354 self.date = date
354 self.date = date
355
355
356 def setparent(self, parent):
356 def setparent(self, parent):
357 try:
357 try:
358 inserthgheader(self.comments, '# Parent ', parent)
358 inserthgheader(self.comments, '# Parent ', parent)
359 except ValueError:
359 except ValueError:
360 if not self.plainmode:
360 if not self.plainmode:
361 tmp = ['# HG changeset patch', '# Parent ' + parent]
361 tmp = ['# HG changeset patch', '# Parent ' + parent]
362 self.comments = tmp + self.comments
362 self.comments = tmp + self.comments
363 self.parent = parent
363 self.parent = parent
364
364
365 def setmessage(self, message):
365 def setmessage(self, message):
366 if self.comments:
366 if self.comments:
367 self._delmsg()
367 self._delmsg()
368 self.message = [message]
368 self.message = [message]
369 if message:
369 if message:
370 if self.plainmode and self.comments and self.comments[-1]:
370 if self.plainmode and self.comments and self.comments[-1]:
371 self.comments.append('')
371 self.comments.append('')
372 self.comments.append(message)
372 self.comments.append(message)
373
373
374 def __str__(self):
374 def __str__(self):
375 s = '\n'.join(self.comments).rstrip()
375 s = '\n'.join(self.comments).rstrip()
376 if not s:
376 if not s:
377 return ''
377 return ''
378 return s + '\n\n'
378 return s + '\n\n'
379
379
380 def _delmsg(self):
380 def _delmsg(self):
381 '''Remove existing message, keeping the rest of the comments fields.
381 '''Remove existing message, keeping the rest of the comments fields.
382 If comments contains 'subject: ', message will prepend
382 If comments contains 'subject: ', message will prepend
383 the field and a blank line.'''
383 the field and a blank line.'''
384 if self.message:
384 if self.message:
385 subj = 'subject: ' + self.message[0].lower()
385 subj = 'subject: ' + self.message[0].lower()
386 for i in xrange(len(self.comments)):
386 for i in xrange(len(self.comments)):
387 if subj == self.comments[i].lower():
387 if subj == self.comments[i].lower():
388 del self.comments[i]
388 del self.comments[i]
389 self.message = self.message[2:]
389 self.message = self.message[2:]
390 break
390 break
391 ci = 0
391 ci = 0
392 for mi in self.message:
392 for mi in self.message:
393 while mi != self.comments[ci]:
393 while mi != self.comments[ci]:
394 ci += 1
394 ci += 1
395 del self.comments[ci]
395 del self.comments[ci]
396
396
397 def newcommit(repo, phase, *args, **kwargs):
397 def newcommit(repo, phase, *args, **kwargs):
398 """helper dedicated to ensure a commit respect mq.secret setting
398 """helper dedicated to ensure a commit respect mq.secret setting
399
399
400 It should be used instead of repo.commit inside the mq source for operation
400 It should be used instead of repo.commit inside the mq source for operation
401 creating new changeset.
401 creating new changeset.
402 """
402 """
403 repo = repo.unfiltered()
403 repo = repo.unfiltered()
404 if phase is None:
404 if phase is None:
405 if repo.ui.configbool('mq', 'secret', False):
405 if repo.ui.configbool('mq', 'secret', False):
406 phase = phases.secret
406 phase = phases.secret
407 if phase is not None:
407 if phase is not None:
408 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
408 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
409 allowemptybackup = repo.ui.backupconfig('ui', 'allowemptycommit')
409 allowemptybackup = repo.ui.backupconfig('ui', 'allowemptycommit')
410 try:
410 try:
411 if phase is not None:
411 if phase is not None:
412 repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
412 repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
413 repo.ui.setconfig('ui', 'allowemptycommit', True)
413 repo.ui.setconfig('ui', 'allowemptycommit', True)
414 return repo.commit(*args, **kwargs)
414 return repo.commit(*args, **kwargs)
415 finally:
415 finally:
416 repo.ui.restoreconfig(allowemptybackup)
416 repo.ui.restoreconfig(allowemptybackup)
417 if phase is not None:
417 if phase is not None:
418 repo.ui.restoreconfig(phasebackup)
418 repo.ui.restoreconfig(phasebackup)
419
419
420 class AbortNoCleanup(error.Abort):
420 class AbortNoCleanup(error.Abort):
421 pass
421 pass
422
422
423 class queue(object):
423 class queue(object):
424 def __init__(self, ui, baseui, path, patchdir=None):
424 def __init__(self, ui, baseui, path, patchdir=None):
425 self.basepath = path
425 self.basepath = path
426 try:
426 try:
427 fh = open(os.path.join(path, 'patches.queue'))
427 fh = open(os.path.join(path, 'patches.queue'))
428 cur = fh.read().rstrip()
428 cur = fh.read().rstrip()
429 fh.close()
429 fh.close()
430 if not cur:
430 if not cur:
431 curpath = os.path.join(path, 'patches')
431 curpath = os.path.join(path, 'patches')
432 else:
432 else:
433 curpath = os.path.join(path, 'patches-' + cur)
433 curpath = os.path.join(path, 'patches-' + cur)
434 except IOError:
434 except IOError:
435 curpath = os.path.join(path, 'patches')
435 curpath = os.path.join(path, 'patches')
436 self.path = patchdir or curpath
436 self.path = patchdir or curpath
437 self.opener = scmutil.opener(self.path)
437 self.opener = scmutil.opener(self.path)
438 self.ui = ui
438 self.ui = ui
439 self.baseui = baseui
439 self.baseui = baseui
440 self.applieddirty = False
440 self.applieddirty = False
441 self.seriesdirty = False
441 self.seriesdirty = False
442 self.added = []
442 self.added = []
443 self.seriespath = "series"
443 self.seriespath = "series"
444 self.statuspath = "status"
444 self.statuspath = "status"
445 self.guardspath = "guards"
445 self.guardspath = "guards"
446 self.activeguards = None
446 self.activeguards = None
447 self.guardsdirty = False
447 self.guardsdirty = False
448 # Handle mq.git as a bool with extended values
448 # Handle mq.git as a bool with extended values
449 try:
449 try:
450 gitmode = ui.configbool('mq', 'git', None)
450 gitmode = ui.configbool('mq', 'git', None)
451 if gitmode is None:
451 if gitmode is None:
452 raise error.ConfigError
452 raise error.ConfigError
453 if gitmode:
453 if gitmode:
454 self.gitmode = 'yes'
454 self.gitmode = 'yes'
455 else:
455 else:
456 self.gitmode = 'no'
456 self.gitmode = 'no'
457 except error.ConfigError:
457 except error.ConfigError:
458 # let's have check-config ignore the type mismatch
458 # let's have check-config ignore the type mismatch
459 self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
459 self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
460 # deprecated config: mq.plain
460 # deprecated config: mq.plain
461 self.plainmode = ui.configbool('mq', 'plain', False)
461 self.plainmode = ui.configbool('mq', 'plain', False)
462 self.checkapplied = True
462 self.checkapplied = True
463
463
464 @util.propertycache
464 @util.propertycache
465 def applied(self):
465 def applied(self):
466 def parselines(lines):
466 def parselines(lines):
467 for l in lines:
467 for l in lines:
468 entry = l.split(':', 1)
468 entry = l.split(':', 1)
469 if len(entry) > 1:
469 if len(entry) > 1:
470 n, name = entry
470 n, name = entry
471 yield statusentry(bin(n), name)
471 yield statusentry(bin(n), name)
472 elif l.strip():
472 elif l.strip():
473 self.ui.warn(_('malformated mq status line: %s\n') % entry)
473 self.ui.warn(_('malformated mq status line: %s\n') % entry)
474 # else we ignore empty lines
474 # else we ignore empty lines
475 try:
475 try:
476 lines = self.opener.read(self.statuspath).splitlines()
476 lines = self.opener.read(self.statuspath).splitlines()
477 return list(parselines(lines))
477 return list(parselines(lines))
478 except IOError as e:
478 except IOError as e:
479 if e.errno == errno.ENOENT:
479 if e.errno == errno.ENOENT:
480 return []
480 return []
481 raise
481 raise
482
482
483 @util.propertycache
483 @util.propertycache
484 def fullseries(self):
484 def fullseries(self):
485 try:
485 try:
486 return self.opener.read(self.seriespath).splitlines()
486 return self.opener.read(self.seriespath).splitlines()
487 except IOError as e:
487 except IOError as e:
488 if e.errno == errno.ENOENT:
488 if e.errno == errno.ENOENT:
489 return []
489 return []
490 raise
490 raise
491
491
492 @util.propertycache
492 @util.propertycache
493 def series(self):
493 def series(self):
494 self.parseseries()
494 self.parseseries()
495 return self.series
495 return self.series
496
496
497 @util.propertycache
497 @util.propertycache
498 def seriesguards(self):
498 def seriesguards(self):
499 self.parseseries()
499 self.parseseries()
500 return self.seriesguards
500 return self.seriesguards
501
501
502 def invalidate(self):
502 def invalidate(self):
503 for a in 'applied fullseries series seriesguards'.split():
503 for a in 'applied fullseries series seriesguards'.split():
504 if a in self.__dict__:
504 if a in self.__dict__:
505 delattr(self, a)
505 delattr(self, a)
506 self.applieddirty = False
506 self.applieddirty = False
507 self.seriesdirty = False
507 self.seriesdirty = False
508 self.guardsdirty = False
508 self.guardsdirty = False
509 self.activeguards = None
509 self.activeguards = None
510
510
511 def diffopts(self, opts=None, patchfn=None):
511 def diffopts(self, opts=None, patchfn=None):
512 diffopts = patchmod.diffopts(self.ui, opts)
512 diffopts = patchmod.diffopts(self.ui, opts)
513 if self.gitmode == 'auto':
513 if self.gitmode == 'auto':
514 diffopts.upgrade = True
514 diffopts.upgrade = True
515 elif self.gitmode == 'keep':
515 elif self.gitmode == 'keep':
516 pass
516 pass
517 elif self.gitmode in ('yes', 'no'):
517 elif self.gitmode in ('yes', 'no'):
518 diffopts.git = self.gitmode == 'yes'
518 diffopts.git = self.gitmode == 'yes'
519 else:
519 else:
520 raise error.Abort(_('mq.git option can be auto/keep/yes/no'
520 raise error.Abort(_('mq.git option can be auto/keep/yes/no'
521 ' got %s') % self.gitmode)
521 ' got %s') % self.gitmode)
522 if patchfn:
522 if patchfn:
523 diffopts = self.patchopts(diffopts, patchfn)
523 diffopts = self.patchopts(diffopts, patchfn)
524 return diffopts
524 return diffopts
525
525
526 def patchopts(self, diffopts, *patches):
526 def patchopts(self, diffopts, *patches):
527 """Return a copy of input diff options with git set to true if
527 """Return a copy of input diff options with git set to true if
528 referenced patch is a git patch and should be preserved as such.
528 referenced patch is a git patch and should be preserved as such.
529 """
529 """
530 diffopts = diffopts.copy()
530 diffopts = diffopts.copy()
531 if not diffopts.git and self.gitmode == 'keep':
531 if not diffopts.git and self.gitmode == 'keep':
532 for patchfn in patches:
532 for patchfn in patches:
533 patchf = self.opener(patchfn, 'r')
533 patchf = self.opener(patchfn, 'r')
534 # if the patch was a git patch, refresh it as a git patch
534 # if the patch was a git patch, refresh it as a git patch
535 for line in patchf:
535 for line in patchf:
536 if line.startswith('diff --git'):
536 if line.startswith('diff --git'):
537 diffopts.git = True
537 diffopts.git = True
538 break
538 break
539 patchf.close()
539 patchf.close()
540 return diffopts
540 return diffopts
541
541
542 def join(self, *p):
542 def join(self, *p):
543 return os.path.join(self.path, *p)
543 return os.path.join(self.path, *p)
544
544
545 def findseries(self, patch):
545 def findseries(self, patch):
546 def matchpatch(l):
546 def matchpatch(l):
547 l = l.split('#', 1)[0]
547 l = l.split('#', 1)[0]
548 return l.strip() == patch
548 return l.strip() == patch
549 for index, l in enumerate(self.fullseries):
549 for index, l in enumerate(self.fullseries):
550 if matchpatch(l):
550 if matchpatch(l):
551 return index
551 return index
552 return None
552 return None
553
553
554 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
554 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
555
555
556 def parseseries(self):
556 def parseseries(self):
557 self.series = []
557 self.series = []
558 self.seriesguards = []
558 self.seriesguards = []
559 for l in self.fullseries:
559 for l in self.fullseries:
560 h = l.find('#')
560 h = l.find('#')
561 if h == -1:
561 if h == -1:
562 patch = l
562 patch = l
563 comment = ''
563 comment = ''
564 elif h == 0:
564 elif h == 0:
565 continue
565 continue
566 else:
566 else:
567 patch = l[:h]
567 patch = l[:h]
568 comment = l[h:]
568 comment = l[h:]
569 patch = patch.strip()
569 patch = patch.strip()
570 if patch:
570 if patch:
571 if patch in self.series:
571 if patch in self.series:
572 raise error.Abort(_('%s appears more than once in %s') %
572 raise error.Abort(_('%s appears more than once in %s') %
573 (patch, self.join(self.seriespath)))
573 (patch, self.join(self.seriespath)))
574 self.series.append(patch)
574 self.series.append(patch)
575 self.seriesguards.append(self.guard_re.findall(comment))
575 self.seriesguards.append(self.guard_re.findall(comment))
576
576
577 def checkguard(self, guard):
577 def checkguard(self, guard):
578 if not guard:
578 if not guard:
579 return _('guard cannot be an empty string')
579 return _('guard cannot be an empty string')
580 bad_chars = '# \t\r\n\f'
580 bad_chars = '# \t\r\n\f'
581 first = guard[0]
581 first = guard[0]
582 if first in '-+':
582 if first in '-+':
583 return (_('guard %r starts with invalid character: %r') %
583 return (_('guard %r starts with invalid character: %r') %
584 (guard, first))
584 (guard, first))
585 for c in bad_chars:
585 for c in bad_chars:
586 if c in guard:
586 if c in guard:
587 return _('invalid character in guard %r: %r') % (guard, c)
587 return _('invalid character in guard %r: %r') % (guard, c)
588
588
589 def setactive(self, guards):
589 def setactive(self, guards):
590 for guard in guards:
590 for guard in guards:
591 bad = self.checkguard(guard)
591 bad = self.checkguard(guard)
592 if bad:
592 if bad:
593 raise error.Abort(bad)
593 raise error.Abort(bad)
594 guards = sorted(set(guards))
594 guards = sorted(set(guards))
595 self.ui.debug('active guards: %s\n' % ' '.join(guards))
595 self.ui.debug('active guards: %s\n' % ' '.join(guards))
596 self.activeguards = guards
596 self.activeguards = guards
597 self.guardsdirty = True
597 self.guardsdirty = True
598
598
599 def active(self):
599 def active(self):
600 if self.activeguards is None:
600 if self.activeguards is None:
601 self.activeguards = []
601 self.activeguards = []
602 try:
602 try:
603 guards = self.opener.read(self.guardspath).split()
603 guards = self.opener.read(self.guardspath).split()
604 except IOError as err:
604 except IOError as err:
605 if err.errno != errno.ENOENT:
605 if err.errno != errno.ENOENT:
606 raise
606 raise
607 guards = []
607 guards = []
608 for i, guard in enumerate(guards):
608 for i, guard in enumerate(guards):
609 bad = self.checkguard(guard)
609 bad = self.checkguard(guard)
610 if bad:
610 if bad:
611 self.ui.warn('%s:%d: %s\n' %
611 self.ui.warn('%s:%d: %s\n' %
612 (self.join(self.guardspath), i + 1, bad))
612 (self.join(self.guardspath), i + 1, bad))
613 else:
613 else:
614 self.activeguards.append(guard)
614 self.activeguards.append(guard)
615 return self.activeguards
615 return self.activeguards
616
616
617 def setguards(self, idx, guards):
617 def setguards(self, idx, guards):
618 for g in guards:
618 for g in guards:
619 if len(g) < 2:
619 if len(g) < 2:
620 raise error.Abort(_('guard %r too short') % g)
620 raise error.Abort(_('guard %r too short') % g)
621 if g[0] not in '-+':
621 if g[0] not in '-+':
622 raise error.Abort(_('guard %r starts with invalid char') % g)
622 raise error.Abort(_('guard %r starts with invalid char') % g)
623 bad = self.checkguard(g[1:])
623 bad = self.checkguard(g[1:])
624 if bad:
624 if bad:
625 raise error.Abort(bad)
625 raise error.Abort(bad)
626 drop = self.guard_re.sub('', self.fullseries[idx])
626 drop = self.guard_re.sub('', self.fullseries[idx])
627 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
627 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
628 self.parseseries()
628 self.parseseries()
629 self.seriesdirty = True
629 self.seriesdirty = True
630
630
631 def pushable(self, idx):
631 def pushable(self, idx):
632 if isinstance(idx, str):
632 if isinstance(idx, str):
633 idx = self.series.index(idx)
633 idx = self.series.index(idx)
634 patchguards = self.seriesguards[idx]
634 patchguards = self.seriesguards[idx]
635 if not patchguards:
635 if not patchguards:
636 return True, None
636 return True, None
637 guards = self.active()
637 guards = self.active()
638 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
638 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
639 if exactneg:
639 if exactneg:
640 return False, repr(exactneg[0])
640 return False, repr(exactneg[0])
641 pos = [g for g in patchguards if g[0] == '+']
641 pos = [g for g in patchguards if g[0] == '+']
642 exactpos = [g for g in pos if g[1:] in guards]
642 exactpos = [g for g in pos if g[1:] in guards]
643 if pos:
643 if pos:
644 if exactpos:
644 if exactpos:
645 return True, repr(exactpos[0])
645 return True, repr(exactpos[0])
646 return False, ' '.join(map(repr, pos))
646 return False, ' '.join(map(repr, pos))
647 return True, ''
647 return True, ''
648
648
649 def explainpushable(self, idx, all_patches=False):
649 def explainpushable(self, idx, all_patches=False):
650 if all_patches:
650 if all_patches:
651 write = self.ui.write
651 write = self.ui.write
652 else:
652 else:
653 write = self.ui.warn
653 write = self.ui.warn
654
654
655 if all_patches or self.ui.verbose:
655 if all_patches or self.ui.verbose:
656 if isinstance(idx, str):
656 if isinstance(idx, str):
657 idx = self.series.index(idx)
657 idx = self.series.index(idx)
658 pushable, why = self.pushable(idx)
658 pushable, why = self.pushable(idx)
659 if all_patches and pushable:
659 if all_patches and pushable:
660 if why is None:
660 if why is None:
661 write(_('allowing %s - no guards in effect\n') %
661 write(_('allowing %s - no guards in effect\n') %
662 self.series[idx])
662 self.series[idx])
663 else:
663 else:
664 if not why:
664 if not why:
665 write(_('allowing %s - no matching negative guards\n') %
665 write(_('allowing %s - no matching negative guards\n') %
666 self.series[idx])
666 self.series[idx])
667 else:
667 else:
668 write(_('allowing %s - guarded by %s\n') %
668 write(_('allowing %s - guarded by %s\n') %
669 (self.series[idx], why))
669 (self.series[idx], why))
670 if not pushable:
670 if not pushable:
671 if why:
671 if why:
672 write(_('skipping %s - guarded by %s\n') %
672 write(_('skipping %s - guarded by %s\n') %
673 (self.series[idx], why))
673 (self.series[idx], why))
674 else:
674 else:
675 write(_('skipping %s - no matching guards\n') %
675 write(_('skipping %s - no matching guards\n') %
676 self.series[idx])
676 self.series[idx])
677
677
678 def savedirty(self):
678 def savedirty(self):
679 def writelist(items, path):
679 def writelist(items, path):
680 fp = self.opener(path, 'w')
680 fp = self.opener(path, 'w')
681 for i in items:
681 for i in items:
682 fp.write("%s\n" % i)
682 fp.write("%s\n" % i)
683 fp.close()
683 fp.close()
684 if self.applieddirty:
684 if self.applieddirty:
685 writelist(map(str, self.applied), self.statuspath)
685 writelist(map(str, self.applied), self.statuspath)
686 self.applieddirty = False
686 self.applieddirty = False
687 if self.seriesdirty:
687 if self.seriesdirty:
688 writelist(self.fullseries, self.seriespath)
688 writelist(self.fullseries, self.seriespath)
689 self.seriesdirty = False
689 self.seriesdirty = False
690 if self.guardsdirty:
690 if self.guardsdirty:
691 writelist(self.activeguards, self.guardspath)
691 writelist(self.activeguards, self.guardspath)
692 self.guardsdirty = False
692 self.guardsdirty = False
693 if self.added:
693 if self.added:
694 qrepo = self.qrepo()
694 qrepo = self.qrepo()
695 if qrepo:
695 if qrepo:
696 qrepo[None].add(f for f in self.added if f not in qrepo[None])
696 qrepo[None].add(f for f in self.added if f not in qrepo[None])
697 self.added = []
697 self.added = []
698
698
699 def removeundo(self, repo):
699 def removeundo(self, repo):
700 undo = repo.sjoin('undo')
700 undo = repo.sjoin('undo')
701 if not os.path.exists(undo):
701 if not os.path.exists(undo):
702 return
702 return
703 try:
703 try:
704 os.unlink(undo)
704 os.unlink(undo)
705 except OSError as inst:
705 except OSError as inst:
706 self.ui.warn(_('error removing undo: %s\n') % str(inst))
706 self.ui.warn(_('error removing undo: %s\n') % str(inst))
707
707
708 def backup(self, repo, files, copy=False):
708 def backup(self, repo, files, copy=False):
709 # backup local changes in --force case
709 # backup local changes in --force case
710 for f in sorted(files):
710 for f in sorted(files):
711 absf = repo.wjoin(f)
711 absf = repo.wjoin(f)
712 if os.path.lexists(absf):
712 if os.path.lexists(absf):
713 self.ui.note(_('saving current version of %s as %s\n') %
713 self.ui.note(_('saving current version of %s as %s\n') %
714 (f, scmutil.origpath(self.ui, repo, f)))
714 (f, scmutil.origpath(self.ui, repo, f)))
715
715
716 absorig = scmutil.origpath(self.ui, repo, absf)
716 absorig = scmutil.origpath(self.ui, repo, absf)
717 if copy:
717 if copy:
718 util.copyfile(absf, absorig)
718 util.copyfile(absf, absorig)
719 else:
719 else:
720 util.rename(absf, absorig)
720 util.rename(absf, absorig)
721
721
722 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
722 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
723 fp=None, changes=None, opts={}):
723 fp=None, changes=None, opts={}):
724 stat = opts.get('stat')
724 stat = opts.get('stat')
725 m = scmutil.match(repo[node1], files, opts)
725 m = scmutil.match(repo[node1], files, opts)
726 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
726 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
727 changes, stat, fp)
727 changes, stat, fp)
728
728
729 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
729 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
730 # first try just applying the patch
730 # first try just applying the patch
731 (err, n) = self.apply(repo, [patch], update_status=False,
731 (err, n) = self.apply(repo, [patch], update_status=False,
732 strict=True, merge=rev)
732 strict=True, merge=rev)
733
733
734 if err == 0:
734 if err == 0:
735 return (err, n)
735 return (err, n)
736
736
737 if n is None:
737 if n is None:
738 raise error.Abort(_("apply failed for patch %s") % patch)
738 raise error.Abort(_("apply failed for patch %s") % patch)
739
739
740 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
740 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
741
741
742 # apply failed, strip away that rev and merge.
742 # apply failed, strip away that rev and merge.
743 hg.clean(repo, head)
743 hg.clean(repo, head)
744 strip(self.ui, repo, [n], update=False, backup=False)
744 strip(self.ui, repo, [n], update=False, backup=False)
745
745
746 ctx = repo[rev]
746 ctx = repo[rev]
747 ret = hg.merge(repo, rev)
747 ret = hg.merge(repo, rev)
748 if ret:
748 if ret:
749 raise error.Abort(_("update returned %d") % ret)
749 raise error.Abort(_("update returned %d") % ret)
750 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
750 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
751 if n is None:
751 if n is None:
752 raise error.Abort(_("repo commit failed"))
752 raise error.Abort(_("repo commit failed"))
753 try:
753 try:
754 ph = patchheader(mergeq.join(patch), self.plainmode)
754 ph = patchheader(mergeq.join(patch), self.plainmode)
755 except Exception:
755 except Exception:
756 raise error.Abort(_("unable to read %s") % patch)
756 raise error.Abort(_("unable to read %s") % patch)
757
757
758 diffopts = self.patchopts(diffopts, patch)
758 diffopts = self.patchopts(diffopts, patch)
759 patchf = self.opener(patch, "w")
759 patchf = self.opener(patch, "w")
760 comments = str(ph)
760 comments = str(ph)
761 if comments:
761 if comments:
762 patchf.write(comments)
762 patchf.write(comments)
763 self.printdiff(repo, diffopts, head, n, fp=patchf)
763 self.printdiff(repo, diffopts, head, n, fp=patchf)
764 patchf.close()
764 patchf.close()
765 self.removeundo(repo)
765 self.removeundo(repo)
766 return (0, n)
766 return (0, n)
767
767
768 def qparents(self, repo, rev=None):
768 def qparents(self, repo, rev=None):
769 """return the mq handled parent or p1
769 """return the mq handled parent or p1
770
770
771 In some case where mq get himself in being the parent of a merge the
771 In some case where mq get himself in being the parent of a merge the
772 appropriate parent may be p2.
772 appropriate parent may be p2.
773 (eg: an in progress merge started with mq disabled)
773 (eg: an in progress merge started with mq disabled)
774
774
775 If no parent are managed by mq, p1 is returned.
775 If no parent are managed by mq, p1 is returned.
776 """
776 """
777 if rev is None:
777 if rev is None:
778 (p1, p2) = repo.dirstate.parents()
778 (p1, p2) = repo.dirstate.parents()
779 if p2 == nullid:
779 if p2 == nullid:
780 return p1
780 return p1
781 if not self.applied:
781 if not self.applied:
782 return None
782 return None
783 return self.applied[-1].node
783 return self.applied[-1].node
784 p1, p2 = repo.changelog.parents(rev)
784 p1, p2 = repo.changelog.parents(rev)
785 if p2 != nullid and p2 in [x.node for x in self.applied]:
785 if p2 != nullid and p2 in [x.node for x in self.applied]:
786 return p2
786 return p2
787 return p1
787 return p1
788
788
789 def mergepatch(self, repo, mergeq, series, diffopts):
789 def mergepatch(self, repo, mergeq, series, diffopts):
790 if not self.applied:
790 if not self.applied:
791 # each of the patches merged in will have two parents. This
791 # each of the patches merged in will have two parents. This
792 # can confuse the qrefresh, qdiff, and strip code because it
792 # can confuse the qrefresh, qdiff, and strip code because it
793 # needs to know which parent is actually in the patch queue.
793 # needs to know which parent is actually in the patch queue.
794 # so, we insert a merge marker with only one parent. This way
794 # so, we insert a merge marker with only one parent. This way
795 # the first patch in the queue is never a merge patch
795 # the first patch in the queue is never a merge patch
796 #
796 #
797 pname = ".hg.patches.merge.marker"
797 pname = ".hg.patches.merge.marker"
798 n = newcommit(repo, None, '[mq]: merge marker', force=True)
798 n = newcommit(repo, None, '[mq]: merge marker', force=True)
799 self.removeundo(repo)
799 self.removeundo(repo)
800 self.applied.append(statusentry(n, pname))
800 self.applied.append(statusentry(n, pname))
801 self.applieddirty = True
801 self.applieddirty = True
802
802
803 head = self.qparents(repo)
803 head = self.qparents(repo)
804
804
805 for patch in series:
805 for patch in series:
806 patch = mergeq.lookup(patch, strict=True)
806 patch = mergeq.lookup(patch, strict=True)
807 if not patch:
807 if not patch:
808 self.ui.warn(_("patch %s does not exist\n") % patch)
808 self.ui.warn(_("patch %s does not exist\n") % patch)
809 return (1, None)
809 return (1, None)
810 pushable, reason = self.pushable(patch)
810 pushable, reason = self.pushable(patch)
811 if not pushable:
811 if not pushable:
812 self.explainpushable(patch, all_patches=True)
812 self.explainpushable(patch, all_patches=True)
813 continue
813 continue
814 info = mergeq.isapplied(patch)
814 info = mergeq.isapplied(patch)
815 if not info:
815 if not info:
816 self.ui.warn(_("patch %s is not applied\n") % patch)
816 self.ui.warn(_("patch %s is not applied\n") % patch)
817 return (1, None)
817 return (1, None)
818 rev = info[1]
818 rev = info[1]
819 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
819 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
820 if head:
820 if head:
821 self.applied.append(statusentry(head, patch))
821 self.applied.append(statusentry(head, patch))
822 self.applieddirty = True
822 self.applieddirty = True
823 if err:
823 if err:
824 return (err, head)
824 return (err, head)
825 self.savedirty()
825 self.savedirty()
826 return (0, head)
826 return (0, head)
827
827
828 def patch(self, repo, patchfile):
828 def patch(self, repo, patchfile):
829 '''Apply patchfile to the working directory.
829 '''Apply patchfile to the working directory.
830 patchfile: name of patch file'''
830 patchfile: name of patch file'''
831 files = set()
831 files = set()
832 try:
832 try:
833 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
833 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
834 files=files, eolmode=None)
834 files=files, eolmode=None)
835 return (True, list(files), fuzz)
835 return (True, list(files), fuzz)
836 except Exception as inst:
836 except Exception as inst:
837 self.ui.note(str(inst) + '\n')
837 self.ui.note(str(inst) + '\n')
838 if not self.ui.verbose:
838 if not self.ui.verbose:
839 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
839 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
840 self.ui.traceback()
840 self.ui.traceback()
841 return (False, list(files), False)
841 return (False, list(files), False)
842
842
843 def apply(self, repo, series, list=False, update_status=True,
843 def apply(self, repo, series, list=False, update_status=True,
844 strict=False, patchdir=None, merge=None, all_files=None,
844 strict=False, patchdir=None, merge=None, all_files=None,
845 tobackup=None, keepchanges=False):
845 tobackup=None, keepchanges=False):
846 wlock = lock = tr = None
846 wlock = lock = tr = None
847 try:
847 try:
848 wlock = repo.wlock()
848 wlock = repo.wlock()
849 lock = repo.lock()
849 lock = repo.lock()
850 tr = repo.transaction("qpush")
850 tr = repo.transaction("qpush")
851 try:
851 try:
852 ret = self._apply(repo, series, list, update_status,
852 ret = self._apply(repo, series, list, update_status,
853 strict, patchdir, merge, all_files=all_files,
853 strict, patchdir, merge, all_files=all_files,
854 tobackup=tobackup, keepchanges=keepchanges)
854 tobackup=tobackup, keepchanges=keepchanges)
855 tr.close()
855 tr.close()
856 self.savedirty()
856 self.savedirty()
857 return ret
857 return ret
858 except AbortNoCleanup:
858 except AbortNoCleanup:
859 tr.close()
859 tr.close()
860 self.savedirty()
860 self.savedirty()
861 raise
861 raise
862 except: # re-raises
862 except: # re-raises
863 try:
863 try:
864 tr.abort()
864 tr.abort()
865 finally:
865 finally:
866 self.invalidate()
866 self.invalidate()
867 raise
867 raise
868 finally:
868 finally:
869 release(tr, lock, wlock)
869 release(tr, lock, wlock)
870 self.removeundo(repo)
870 self.removeundo(repo)
871
871
872 def _apply(self, repo, series, list=False, update_status=True,
872 def _apply(self, repo, series, list=False, update_status=True,
873 strict=False, patchdir=None, merge=None, all_files=None,
873 strict=False, patchdir=None, merge=None, all_files=None,
874 tobackup=None, keepchanges=False):
874 tobackup=None, keepchanges=False):
875 """returns (error, hash)
875 """returns (error, hash)
876
876
877 error = 1 for unable to read, 2 for patch failed, 3 for patch
877 error = 1 for unable to read, 2 for patch failed, 3 for patch
878 fuzz. tobackup is None or a set of files to backup before they
878 fuzz. tobackup is None or a set of files to backup before they
879 are modified by a patch.
879 are modified by a patch.
880 """
880 """
881 # TODO unify with commands.py
881 # TODO unify with commands.py
882 if not patchdir:
882 if not patchdir:
883 patchdir = self.path
883 patchdir = self.path
884 err = 0
884 err = 0
885 n = None
885 n = None
886 for patchname in series:
886 for patchname in series:
887 pushable, reason = self.pushable(patchname)
887 pushable, reason = self.pushable(patchname)
888 if not pushable:
888 if not pushable:
889 self.explainpushable(patchname, all_patches=True)
889 self.explainpushable(patchname, all_patches=True)
890 continue
890 continue
891 self.ui.status(_("applying %s\n") % patchname)
891 self.ui.status(_("applying %s\n") % patchname)
892 pf = os.path.join(patchdir, patchname)
892 pf = os.path.join(patchdir, patchname)
893
893
894 try:
894 try:
895 ph = patchheader(self.join(patchname), self.plainmode)
895 ph = patchheader(self.join(patchname), self.plainmode)
896 except IOError:
896 except IOError:
897 self.ui.warn(_("unable to read %s\n") % patchname)
897 self.ui.warn(_("unable to read %s\n") % patchname)
898 err = 1
898 err = 1
899 break
899 break
900
900
901 message = ph.message
901 message = ph.message
902 if not message:
902 if not message:
903 # The commit message should not be translated
903 # The commit message should not be translated
904 message = "imported patch %s\n" % patchname
904 message = "imported patch %s\n" % patchname
905 else:
905 else:
906 if list:
906 if list:
907 # The commit message should not be translated
907 # The commit message should not be translated
908 message.append("\nimported patch %s" % patchname)
908 message.append("\nimported patch %s" % patchname)
909 message = '\n'.join(message)
909 message = '\n'.join(message)
910
910
911 if ph.haspatch:
911 if ph.haspatch:
912 if tobackup:
912 if tobackup:
913 touched = patchmod.changedfiles(self.ui, repo, pf)
913 touched = patchmod.changedfiles(self.ui, repo, pf)
914 touched = set(touched) & tobackup
914 touched = set(touched) & tobackup
915 if touched and keepchanges:
915 if touched and keepchanges:
916 raise AbortNoCleanup(
916 raise AbortNoCleanup(
917 _("conflicting local changes found"),
917 _("conflicting local changes found"),
918 hint=_("did you forget to qrefresh?"))
918 hint=_("did you forget to qrefresh?"))
919 self.backup(repo, touched, copy=True)
919 self.backup(repo, touched, copy=True)
920 tobackup = tobackup - touched
920 tobackup = tobackup - touched
921 (patcherr, files, fuzz) = self.patch(repo, pf)
921 (patcherr, files, fuzz) = self.patch(repo, pf)
922 if all_files is not None:
922 if all_files is not None:
923 all_files.update(files)
923 all_files.update(files)
924 patcherr = not patcherr
924 patcherr = not patcherr
925 else:
925 else:
926 self.ui.warn(_("patch %s is empty\n") % patchname)
926 self.ui.warn(_("patch %s is empty\n") % patchname)
927 patcherr, files, fuzz = 0, [], 0
927 patcherr, files, fuzz = 0, [], 0
928
928
929 if merge and files:
929 if merge and files:
930 # Mark as removed/merged and update dirstate parent info
930 # Mark as removed/merged and update dirstate parent info
931 removed = []
931 removed = []
932 merged = []
932 merged = []
933 for f in files:
933 for f in files:
934 if os.path.lexists(repo.wjoin(f)):
934 if os.path.lexists(repo.wjoin(f)):
935 merged.append(f)
935 merged.append(f)
936 else:
936 else:
937 removed.append(f)
937 removed.append(f)
938 repo.dirstate.beginparentchange()
938 repo.dirstate.beginparentchange()
939 for f in removed:
939 for f in removed:
940 repo.dirstate.remove(f)
940 repo.dirstate.remove(f)
941 for f in merged:
941 for f in merged:
942 repo.dirstate.merge(f)
942 repo.dirstate.merge(f)
943 p1, p2 = repo.dirstate.parents()
943 p1, p2 = repo.dirstate.parents()
944 repo.setparents(p1, merge)
944 repo.setparents(p1, merge)
945 repo.dirstate.endparentchange()
945 repo.dirstate.endparentchange()
946
946
947 if all_files and '.hgsubstate' in all_files:
947 if all_files and '.hgsubstate' in all_files:
948 wctx = repo[None]
948 wctx = repo[None]
949 pctx = repo['.']
949 pctx = repo['.']
950 overwrite = False
950 overwrite = False
951 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
951 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
952 overwrite)
952 overwrite)
953 files += mergedsubstate.keys()
953 files += mergedsubstate.keys()
954
954
955 match = scmutil.matchfiles(repo, files or [])
955 match = scmutil.matchfiles(repo, files or [])
956 oldtip = repo['tip']
956 oldtip = repo['tip']
957 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
957 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
958 force=True)
958 force=True)
959 if repo['tip'] == oldtip:
959 if repo['tip'] == oldtip:
960 raise error.Abort(_("qpush exactly duplicates child changeset"))
960 raise error.Abort(_("qpush exactly duplicates child changeset"))
961 if n is None:
961 if n is None:
962 raise error.Abort(_("repository commit failed"))
962 raise error.Abort(_("repository commit failed"))
963
963
964 if update_status:
964 if update_status:
965 self.applied.append(statusentry(n, patchname))
965 self.applied.append(statusentry(n, patchname))
966
966
967 if patcherr:
967 if patcherr:
968 self.ui.warn(_("patch failed, rejects left in working "
968 self.ui.warn(_("patch failed, rejects left in working "
969 "directory\n"))
969 "directory\n"))
970 err = 2
970 err = 2
971 break
971 break
972
972
973 if fuzz and strict:
973 if fuzz and strict:
974 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
974 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
975 err = 3
975 err = 3
976 break
976 break
977 return (err, n)
977 return (err, n)
978
978
979 def _cleanup(self, patches, numrevs, keep=False):
979 def _cleanup(self, patches, numrevs, keep=False):
980 if not keep:
980 if not keep:
981 r = self.qrepo()
981 r = self.qrepo()
982 if r:
982 if r:
983 r[None].forget(patches)
983 r[None].forget(patches)
984 for p in patches:
984 for p in patches:
985 try:
985 try:
986 os.unlink(self.join(p))
986 os.unlink(self.join(p))
987 except OSError as inst:
987 except OSError as inst:
988 if inst.errno != errno.ENOENT:
988 if inst.errno != errno.ENOENT:
989 raise
989 raise
990
990
991 qfinished = []
991 qfinished = []
992 if numrevs:
992 if numrevs:
993 qfinished = self.applied[:numrevs]
993 qfinished = self.applied[:numrevs]
994 del self.applied[:numrevs]
994 del self.applied[:numrevs]
995 self.applieddirty = True
995 self.applieddirty = True
996
996
997 unknown = []
997 unknown = []
998
998
999 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
999 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
1000 reverse=True):
1000 reverse=True):
1001 if i is not None:
1001 if i is not None:
1002 del self.fullseries[i]
1002 del self.fullseries[i]
1003 else:
1003 else:
1004 unknown.append(p)
1004 unknown.append(p)
1005
1005
1006 if unknown:
1006 if unknown:
1007 if numrevs:
1007 if numrevs:
1008 rev = dict((entry.name, entry.node) for entry in qfinished)
1008 rev = dict((entry.name, entry.node) for entry in qfinished)
1009 for p in unknown:
1009 for p in unknown:
1010 msg = _('revision %s refers to unknown patches: %s\n')
1010 msg = _('revision %s refers to unknown patches: %s\n')
1011 self.ui.warn(msg % (short(rev[p]), p))
1011 self.ui.warn(msg % (short(rev[p]), p))
1012 else:
1012 else:
1013 msg = _('unknown patches: %s\n')
1013 msg = _('unknown patches: %s\n')
1014 raise error.Abort(''.join(msg % p for p in unknown))
1014 raise error.Abort(''.join(msg % p for p in unknown))
1015
1015
1016 self.parseseries()
1016 self.parseseries()
1017 self.seriesdirty = True
1017 self.seriesdirty = True
1018 return [entry.node for entry in qfinished]
1018 return [entry.node for entry in qfinished]
1019
1019
1020 def _revpatches(self, repo, revs):
1020 def _revpatches(self, repo, revs):
1021 firstrev = repo[self.applied[0].node].rev()
1021 firstrev = repo[self.applied[0].node].rev()
1022 patches = []
1022 patches = []
1023 for i, rev in enumerate(revs):
1023 for i, rev in enumerate(revs):
1024
1024
1025 if rev < firstrev:
1025 if rev < firstrev:
1026 raise error.Abort(_('revision %d is not managed') % rev)
1026 raise error.Abort(_('revision %d is not managed') % rev)
1027
1027
1028 ctx = repo[rev]
1028 ctx = repo[rev]
1029 base = self.applied[i].node
1029 base = self.applied[i].node
1030 if ctx.node() != base:
1030 if ctx.node() != base:
1031 msg = _('cannot delete revision %d above applied patches')
1031 msg = _('cannot delete revision %d above applied patches')
1032 raise error.Abort(msg % rev)
1032 raise error.Abort(msg % rev)
1033
1033
1034 patch = self.applied[i].name
1034 patch = self.applied[i].name
1035 for fmt in ('[mq]: %s', 'imported patch %s'):
1035 for fmt in ('[mq]: %s', 'imported patch %s'):
1036 if ctx.description() == fmt % patch:
1036 if ctx.description() == fmt % patch:
1037 msg = _('patch %s finalized without changeset message\n')
1037 msg = _('patch %s finalized without changeset message\n')
1038 repo.ui.status(msg % patch)
1038 repo.ui.status(msg % patch)
1039 break
1039 break
1040
1040
1041 patches.append(patch)
1041 patches.append(patch)
1042 return patches
1042 return patches
1043
1043
1044 def finish(self, repo, revs):
1044 def finish(self, repo, revs):
1045 # Manually trigger phase computation to ensure phasedefaults is
1045 # Manually trigger phase computation to ensure phasedefaults is
1046 # executed before we remove the patches.
1046 # executed before we remove the patches.
1047 repo._phasecache
1047 repo._phasecache
1048 patches = self._revpatches(repo, sorted(revs))
1048 patches = self._revpatches(repo, sorted(revs))
1049 qfinished = self._cleanup(patches, len(patches))
1049 qfinished = self._cleanup(patches, len(patches))
1050 if qfinished and repo.ui.configbool('mq', 'secret', False):
1050 if qfinished and repo.ui.configbool('mq', 'secret', False):
1051 # only use this logic when the secret option is added
1051 # only use this logic when the secret option is added
1052 oldqbase = repo[qfinished[0]]
1052 oldqbase = repo[qfinished[0]]
1053 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
1053 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
1054 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1054 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1055 with repo.transaction('qfinish') as tr:
1055 with repo.transaction('qfinish') as tr:
1056 phases.advanceboundary(repo, tr, tphase, qfinished)
1056 phases.advanceboundary(repo, tr, tphase, qfinished)
1057
1057
1058 def delete(self, repo, patches, opts):
1058 def delete(self, repo, patches, opts):
1059 if not patches and not opts.get('rev'):
1059 if not patches and not opts.get('rev'):
1060 raise error.Abort(_('qdelete requires at least one revision or '
1060 raise error.Abort(_('qdelete requires at least one revision or '
1061 'patch name'))
1061 'patch name'))
1062
1062
1063 realpatches = []
1063 realpatches = []
1064 for patch in patches:
1064 for patch in patches:
1065 patch = self.lookup(patch, strict=True)
1065 patch = self.lookup(patch, strict=True)
1066 info = self.isapplied(patch)
1066 info = self.isapplied(patch)
1067 if info:
1067 if info:
1068 raise error.Abort(_("cannot delete applied patch %s") % patch)
1068 raise error.Abort(_("cannot delete applied patch %s") % patch)
1069 if patch not in self.series:
1069 if patch not in self.series:
1070 raise error.Abort(_("patch %s not in series file") % patch)
1070 raise error.Abort(_("patch %s not in series file") % patch)
1071 if patch not in realpatches:
1071 if patch not in realpatches:
1072 realpatches.append(patch)
1072 realpatches.append(patch)
1073
1073
1074 numrevs = 0
1074 numrevs = 0
1075 if opts.get('rev'):
1075 if opts.get('rev'):
1076 if not self.applied:
1076 if not self.applied:
1077 raise error.Abort(_('no patches applied'))
1077 raise error.Abort(_('no patches applied'))
1078 revs = scmutil.revrange(repo, opts.get('rev'))
1078 revs = scmutil.revrange(repo, opts.get('rev'))
1079 revs.sort()
1079 revs.sort()
1080 revpatches = self._revpatches(repo, revs)
1080 revpatches = self._revpatches(repo, revs)
1081 realpatches += revpatches
1081 realpatches += revpatches
1082 numrevs = len(revpatches)
1082 numrevs = len(revpatches)
1083
1083
1084 self._cleanup(realpatches, numrevs, opts.get('keep'))
1084 self._cleanup(realpatches, numrevs, opts.get('keep'))
1085
1085
1086 def checktoppatch(self, repo):
1086 def checktoppatch(self, repo):
1087 '''check that working directory is at qtip'''
1087 '''check that working directory is at qtip'''
1088 if self.applied:
1088 if self.applied:
1089 top = self.applied[-1].node
1089 top = self.applied[-1].node
1090 patch = self.applied[-1].name
1090 patch = self.applied[-1].name
1091 if repo.dirstate.p1() != top:
1091 if repo.dirstate.p1() != top:
1092 raise error.Abort(_("working directory revision is not qtip"))
1092 raise error.Abort(_("working directory revision is not qtip"))
1093 return top, patch
1093 return top, patch
1094 return None, None
1094 return None, None
1095
1095
1096 def putsubstate2changes(self, substatestate, changes):
1096 def putsubstate2changes(self, substatestate, changes):
1097 for files in changes[:3]:
1097 for files in changes[:3]:
1098 if '.hgsubstate' in files:
1098 if '.hgsubstate' in files:
1099 return # already listed up
1099 return # already listed up
1100 # not yet listed up
1100 # not yet listed up
1101 if substatestate in 'a?':
1101 if substatestate in 'a?':
1102 changes[1].append('.hgsubstate')
1102 changes[1].append('.hgsubstate')
1103 elif substatestate in 'r':
1103 elif substatestate in 'r':
1104 changes[2].append('.hgsubstate')
1104 changes[2].append('.hgsubstate')
1105 else: # modified
1105 else: # modified
1106 changes[0].append('.hgsubstate')
1106 changes[0].append('.hgsubstate')
1107
1107
1108 def checklocalchanges(self, repo, force=False, refresh=True):
1108 def checklocalchanges(self, repo, force=False, refresh=True):
1109 excsuffix = ''
1109 excsuffix = ''
1110 if refresh:
1110 if refresh:
1111 excsuffix = ', qrefresh first'
1111 excsuffix = ', qrefresh first'
1112 # plain versions for i18n tool to detect them
1112 # plain versions for i18n tool to detect them
1113 _("local changes found, qrefresh first")
1113 _("local changes found, qrefresh first")
1114 _("local changed subrepos found, qrefresh first")
1114 _("local changed subrepos found, qrefresh first")
1115 return checklocalchanges(repo, force, excsuffix)
1115 return checklocalchanges(repo, force, excsuffix)
1116
1116
1117 _reserved = ('series', 'status', 'guards', '.', '..')
1117 _reserved = ('series', 'status', 'guards', '.', '..')
1118 def checkreservedname(self, name):
1118 def checkreservedname(self, name):
1119 if name in self._reserved:
1119 if name in self._reserved:
1120 raise error.Abort(_('"%s" cannot be used as the name of a patch')
1120 raise error.Abort(_('"%s" cannot be used as the name of a patch')
1121 % name)
1121 % name)
1122 for prefix in ('.hg', '.mq'):
1122 for prefix in ('.hg', '.mq'):
1123 if name.startswith(prefix):
1123 if name.startswith(prefix):
1124 raise error.Abort(_('patch name cannot begin with "%s"')
1124 raise error.Abort(_('patch name cannot begin with "%s"')
1125 % prefix)
1125 % prefix)
1126 for c in ('#', ':', '\r', '\n'):
1126 for c in ('#', ':', '\r', '\n'):
1127 if c in name:
1127 if c in name:
1128 raise error.Abort(_('%r cannot be used in the name of a patch')
1128 raise error.Abort(_('%r cannot be used in the name of a patch')
1129 % c)
1129 % c)
1130
1130
1131 def checkpatchname(self, name, force=False):
1131 def checkpatchname(self, name, force=False):
1132 self.checkreservedname(name)
1132 self.checkreservedname(name)
1133 if not force and os.path.exists(self.join(name)):
1133 if not force and os.path.exists(self.join(name)):
1134 if os.path.isdir(self.join(name)):
1134 if os.path.isdir(self.join(name)):
1135 raise error.Abort(_('"%s" already exists as a directory')
1135 raise error.Abort(_('"%s" already exists as a directory')
1136 % name)
1136 % name)
1137 else:
1137 else:
1138 raise error.Abort(_('patch "%s" already exists') % name)
1138 raise error.Abort(_('patch "%s" already exists') % name)
1139
1139
1140 def makepatchname(self, title, fallbackname):
1140 def makepatchname(self, title, fallbackname):
1141 """Return a suitable filename for title, adding a suffix to make
1141 """Return a suitable filename for title, adding a suffix to make
1142 it unique in the existing list"""
1142 it unique in the existing list"""
1143 namebase = re.sub('[\s\W_]+', '_', title.lower()).strip('_')
1143 namebase = re.sub('[\s\W_]+', '_', title.lower()).strip('_')
1144 namebase = namebase[:75] # avoid too long name (issue5117)
1144 namebase = namebase[:75] # avoid too long name (issue5117)
1145 if namebase:
1145 if namebase:
1146 try:
1146 try:
1147 self.checkreservedname(namebase)
1147 self.checkreservedname(namebase)
1148 except error.Abort:
1148 except error.Abort:
1149 namebase = fallbackname
1149 namebase = fallbackname
1150 else:
1150 else:
1151 namebase = fallbackname
1151 namebase = fallbackname
1152 name = namebase
1152 name = namebase
1153 i = 0
1153 i = 0
1154 while True:
1154 while True:
1155 if name not in self.fullseries:
1155 if name not in self.fullseries:
1156 try:
1156 try:
1157 self.checkpatchname(name)
1157 self.checkpatchname(name)
1158 break
1158 break
1159 except error.Abort:
1159 except error.Abort:
1160 pass
1160 pass
1161 i += 1
1161 i += 1
1162 name = '%s__%s' % (namebase, i)
1162 name = '%s__%s' % (namebase, i)
1163 return name
1163 return name
1164
1164
1165 def checkkeepchanges(self, keepchanges, force):
1165 def checkkeepchanges(self, keepchanges, force):
1166 if force and keepchanges:
1166 if force and keepchanges:
1167 raise error.Abort(_('cannot use both --force and --keep-changes'))
1167 raise error.Abort(_('cannot use both --force and --keep-changes'))
1168
1168
1169 def new(self, repo, patchfn, *pats, **opts):
1169 def new(self, repo, patchfn, *pats, **opts):
1170 """options:
1170 """options:
1171 msg: a string or a no-argument function returning a string
1171 msg: a string or a no-argument function returning a string
1172 """
1172 """
1173 msg = opts.get('msg')
1173 msg = opts.get('msg')
1174 edit = opts.get('edit')
1174 edit = opts.get('edit')
1175 editform = opts.get('editform', 'mq.qnew')
1175 editform = opts.get('editform', 'mq.qnew')
1176 user = opts.get('user')
1176 user = opts.get('user')
1177 date = opts.get('date')
1177 date = opts.get('date')
1178 if date:
1178 if date:
1179 date = util.parsedate(date)
1179 date = util.parsedate(date)
1180 diffopts = self.diffopts({'git': opts.get('git')})
1180 diffopts = self.diffopts({'git': opts.get('git')})
1181 if opts.get('checkname', True):
1181 if opts.get('checkname', True):
1182 self.checkpatchname(patchfn)
1182 self.checkpatchname(patchfn)
1183 inclsubs = checksubstate(repo)
1183 inclsubs = checksubstate(repo)
1184 if inclsubs:
1184 if inclsubs:
1185 substatestate = repo.dirstate['.hgsubstate']
1185 substatestate = repo.dirstate['.hgsubstate']
1186 if opts.get('include') or opts.get('exclude') or pats:
1186 if opts.get('include') or opts.get('exclude') or pats:
1187 # detect missing files in pats
1187 # detect missing files in pats
1188 def badfn(f, msg):
1188 def badfn(f, msg):
1189 if f != '.hgsubstate': # .hgsubstate is auto-created
1189 if f != '.hgsubstate': # .hgsubstate is auto-created
1190 raise error.Abort('%s: %s' % (f, msg))
1190 raise error.Abort('%s: %s' % (f, msg))
1191 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1191 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1192 changes = repo.status(match=match)
1192 changes = repo.status(match=match)
1193 else:
1193 else:
1194 changes = self.checklocalchanges(repo, force=True)
1194 changes = self.checklocalchanges(repo, force=True)
1195 commitfiles = list(inclsubs)
1195 commitfiles = list(inclsubs)
1196 for files in changes[:3]:
1196 for files in changes[:3]:
1197 commitfiles.extend(files)
1197 commitfiles.extend(files)
1198 match = scmutil.matchfiles(repo, commitfiles)
1198 match = scmutil.matchfiles(repo, commitfiles)
1199 if len(repo[None].parents()) > 1:
1199 if len(repo[None].parents()) > 1:
1200 raise error.Abort(_('cannot manage merge changesets'))
1200 raise error.Abort(_('cannot manage merge changesets'))
1201 self.checktoppatch(repo)
1201 self.checktoppatch(repo)
1202 insert = self.fullseriesend()
1202 insert = self.fullseriesend()
1203 with repo.wlock():
1203 with repo.wlock():
1204 try:
1204 try:
1205 # if patch file write fails, abort early
1205 # if patch file write fails, abort early
1206 p = self.opener(patchfn, "w")
1206 p = self.opener(patchfn, "w")
1207 except IOError as e:
1207 except IOError as e:
1208 raise error.Abort(_('cannot write patch "%s": %s')
1208 raise error.Abort(_('cannot write patch "%s": %s')
1209 % (patchfn, e.strerror))
1209 % (patchfn, e.strerror))
1210 try:
1210 try:
1211 defaultmsg = "[mq]: %s" % patchfn
1211 defaultmsg = "[mq]: %s" % patchfn
1212 editor = cmdutil.getcommiteditor(editform=editform)
1212 editor = cmdutil.getcommiteditor(editform=editform)
1213 if edit:
1213 if edit:
1214 def finishdesc(desc):
1214 def finishdesc(desc):
1215 if desc.rstrip():
1215 if desc.rstrip():
1216 return desc
1216 return desc
1217 else:
1217 else:
1218 return defaultmsg
1218 return defaultmsg
1219 # i18n: this message is shown in editor with "HG: " prefix
1219 # i18n: this message is shown in editor with "HG: " prefix
1220 extramsg = _('Leave message empty to use default message.')
1220 extramsg = _('Leave message empty to use default message.')
1221 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1221 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1222 extramsg=extramsg,
1222 extramsg=extramsg,
1223 editform=editform)
1223 editform=editform)
1224 commitmsg = msg
1224 commitmsg = msg
1225 else:
1225 else:
1226 commitmsg = msg or defaultmsg
1226 commitmsg = msg or defaultmsg
1227
1227
1228 n = newcommit(repo, None, commitmsg, user, date, match=match,
1228 n = newcommit(repo, None, commitmsg, user, date, match=match,
1229 force=True, editor=editor)
1229 force=True, editor=editor)
1230 if n is None:
1230 if n is None:
1231 raise error.Abort(_("repo commit failed"))
1231 raise error.Abort(_("repo commit failed"))
1232 try:
1232 try:
1233 self.fullseries[insert:insert] = [patchfn]
1233 self.fullseries[insert:insert] = [patchfn]
1234 self.applied.append(statusentry(n, patchfn))
1234 self.applied.append(statusentry(n, patchfn))
1235 self.parseseries()
1235 self.parseseries()
1236 self.seriesdirty = True
1236 self.seriesdirty = True
1237 self.applieddirty = True
1237 self.applieddirty = True
1238 nctx = repo[n]
1238 nctx = repo[n]
1239 ph = patchheader(self.join(patchfn), self.plainmode)
1239 ph = patchheader(self.join(patchfn), self.plainmode)
1240 if user:
1240 if user:
1241 ph.setuser(user)
1241 ph.setuser(user)
1242 if date:
1242 if date:
1243 ph.setdate('%s %s' % date)
1243 ph.setdate('%s %s' % date)
1244 ph.setparent(hex(nctx.p1().node()))
1244 ph.setparent(hex(nctx.p1().node()))
1245 msg = nctx.description().strip()
1245 msg = nctx.description().strip()
1246 if msg == defaultmsg.strip():
1246 if msg == defaultmsg.strip():
1247 msg = ''
1247 msg = ''
1248 ph.setmessage(msg)
1248 ph.setmessage(msg)
1249 p.write(str(ph))
1249 p.write(str(ph))
1250 if commitfiles:
1250 if commitfiles:
1251 parent = self.qparents(repo, n)
1251 parent = self.qparents(repo, n)
1252 if inclsubs:
1252 if inclsubs:
1253 self.putsubstate2changes(substatestate, changes)
1253 self.putsubstate2changes(substatestate, changes)
1254 chunks = patchmod.diff(repo, node1=parent, node2=n,
1254 chunks = patchmod.diff(repo, node1=parent, node2=n,
1255 changes=changes, opts=diffopts)
1255 changes=changes, opts=diffopts)
1256 for chunk in chunks:
1256 for chunk in chunks:
1257 p.write(chunk)
1257 p.write(chunk)
1258 p.close()
1258 p.close()
1259 r = self.qrepo()
1259 r = self.qrepo()
1260 if r:
1260 if r:
1261 r[None].add([patchfn])
1261 r[None].add([patchfn])
1262 except: # re-raises
1262 except: # re-raises
1263 repo.rollback()
1263 repo.rollback()
1264 raise
1264 raise
1265 except Exception:
1265 except Exception:
1266 patchpath = self.join(patchfn)
1266 patchpath = self.join(patchfn)
1267 try:
1267 try:
1268 os.unlink(patchpath)
1268 os.unlink(patchpath)
1269 except OSError:
1269 except OSError:
1270 self.ui.warn(_('error unlinking %s\n') % patchpath)
1270 self.ui.warn(_('error unlinking %s\n') % patchpath)
1271 raise
1271 raise
1272 self.removeundo(repo)
1272 self.removeundo(repo)
1273
1273
1274 def isapplied(self, patch):
1274 def isapplied(self, patch):
1275 """returns (index, rev, patch)"""
1275 """returns (index, rev, patch)"""
1276 for i, a in enumerate(self.applied):
1276 for i, a in enumerate(self.applied):
1277 if a.name == patch:
1277 if a.name == patch:
1278 return (i, a.node, a.name)
1278 return (i, a.node, a.name)
1279 return None
1279 return None
1280
1280
1281 # if the exact patch name does not exist, we try a few
1281 # if the exact patch name does not exist, we try a few
1282 # variations. If strict is passed, we try only #1
1282 # variations. If strict is passed, we try only #1
1283 #
1283 #
1284 # 1) a number (as string) to indicate an offset in the series file
1284 # 1) a number (as string) to indicate an offset in the series file
1285 # 2) a unique substring of the patch name was given
1285 # 2) a unique substring of the patch name was given
1286 # 3) patchname[-+]num to indicate an offset in the series file
1286 # 3) patchname[-+]num to indicate an offset in the series file
1287 def lookup(self, patch, strict=False):
1287 def lookup(self, patch, strict=False):
1288 def partialname(s):
1288 def partialname(s):
1289 if s in self.series:
1289 if s in self.series:
1290 return s
1290 return s
1291 matches = [x for x in self.series if s in x]
1291 matches = [x for x in self.series if s in x]
1292 if len(matches) > 1:
1292 if len(matches) > 1:
1293 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1293 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1294 for m in matches:
1294 for m in matches:
1295 self.ui.warn(' %s\n' % m)
1295 self.ui.warn(' %s\n' % m)
1296 return None
1296 return None
1297 if matches:
1297 if matches:
1298 return matches[0]
1298 return matches[0]
1299 if self.series and self.applied:
1299 if self.series and self.applied:
1300 if s == 'qtip':
1300 if s == 'qtip':
1301 return self.series[self.seriesend(True) - 1]
1301 return self.series[self.seriesend(True) - 1]
1302 if s == 'qbase':
1302 if s == 'qbase':
1303 return self.series[0]
1303 return self.series[0]
1304 return None
1304 return None
1305
1305
1306 if patch in self.series:
1306 if patch in self.series:
1307 return patch
1307 return patch
1308
1308
1309 if not os.path.isfile(self.join(patch)):
1309 if not os.path.isfile(self.join(patch)):
1310 try:
1310 try:
1311 sno = int(patch)
1311 sno = int(patch)
1312 except (ValueError, OverflowError):
1312 except (ValueError, OverflowError):
1313 pass
1313 pass
1314 else:
1314 else:
1315 if -len(self.series) <= sno < len(self.series):
1315 if -len(self.series) <= sno < len(self.series):
1316 return self.series[sno]
1316 return self.series[sno]
1317
1317
1318 if not strict:
1318 if not strict:
1319 res = partialname(patch)
1319 res = partialname(patch)
1320 if res:
1320 if res:
1321 return res
1321 return res
1322 minus = patch.rfind('-')
1322 minus = patch.rfind('-')
1323 if minus >= 0:
1323 if minus >= 0:
1324 res = partialname(patch[:minus])
1324 res = partialname(patch[:minus])
1325 if res:
1325 if res:
1326 i = self.series.index(res)
1326 i = self.series.index(res)
1327 try:
1327 try:
1328 off = int(patch[minus + 1:] or 1)
1328 off = int(patch[minus + 1:] or 1)
1329 except (ValueError, OverflowError):
1329 except (ValueError, OverflowError):
1330 pass
1330 pass
1331 else:
1331 else:
1332 if i - off >= 0:
1332 if i - off >= 0:
1333 return self.series[i - off]
1333 return self.series[i - off]
1334 plus = patch.rfind('+')
1334 plus = patch.rfind('+')
1335 if plus >= 0:
1335 if plus >= 0:
1336 res = partialname(patch[:plus])
1336 res = partialname(patch[:plus])
1337 if res:
1337 if res:
1338 i = self.series.index(res)
1338 i = self.series.index(res)
1339 try:
1339 try:
1340 off = int(patch[plus + 1:] or 1)
1340 off = int(patch[plus + 1:] or 1)
1341 except (ValueError, OverflowError):
1341 except (ValueError, OverflowError):
1342 pass
1342 pass
1343 else:
1343 else:
1344 if i + off < len(self.series):
1344 if i + off < len(self.series):
1345 return self.series[i + off]
1345 return self.series[i + off]
1346 raise error.Abort(_("patch %s not in series") % patch)
1346 raise error.Abort(_("patch %s not in series") % patch)
1347
1347
1348 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1348 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1349 all=False, move=False, exact=False, nobackup=False,
1349 all=False, move=False, exact=False, nobackup=False,
1350 keepchanges=False):
1350 keepchanges=False):
1351 self.checkkeepchanges(keepchanges, force)
1351 self.checkkeepchanges(keepchanges, force)
1352 diffopts = self.diffopts()
1352 diffopts = self.diffopts()
1353 with repo.wlock():
1353 with repo.wlock():
1354 heads = []
1354 heads = []
1355 for hs in repo.branchmap().itervalues():
1355 for hs in repo.branchmap().itervalues():
1356 heads.extend(hs)
1356 heads.extend(hs)
1357 if not heads:
1357 if not heads:
1358 heads = [nullid]
1358 heads = [nullid]
1359 if repo.dirstate.p1() not in heads and not exact:
1359 if repo.dirstate.p1() not in heads and not exact:
1360 self.ui.status(_("(working directory not at a head)\n"))
1360 self.ui.status(_("(working directory not at a head)\n"))
1361
1361
1362 if not self.series:
1362 if not self.series:
1363 self.ui.warn(_('no patches in series\n'))
1363 self.ui.warn(_('no patches in series\n'))
1364 return 0
1364 return 0
1365
1365
1366 # Suppose our series file is: A B C and the current 'top'
1366 # Suppose our series file is: A B C and the current 'top'
1367 # patch is B. qpush C should be performed (moving forward)
1367 # patch is B. qpush C should be performed (moving forward)
1368 # qpush B is a NOP (no change) qpush A is an error (can't
1368 # qpush B is a NOP (no change) qpush A is an error (can't
1369 # go backwards with qpush)
1369 # go backwards with qpush)
1370 if patch:
1370 if patch:
1371 patch = self.lookup(patch)
1371 patch = self.lookup(patch)
1372 info = self.isapplied(patch)
1372 info = self.isapplied(patch)
1373 if info and info[0] >= len(self.applied) - 1:
1373 if info and info[0] >= len(self.applied) - 1:
1374 self.ui.warn(
1374 self.ui.warn(
1375 _('qpush: %s is already at the top\n') % patch)
1375 _('qpush: %s is already at the top\n') % patch)
1376 return 0
1376 return 0
1377
1377
1378 pushable, reason = self.pushable(patch)
1378 pushable, reason = self.pushable(patch)
1379 if pushable:
1379 if pushable:
1380 if self.series.index(patch) < self.seriesend():
1380 if self.series.index(patch) < self.seriesend():
1381 raise error.Abort(
1381 raise error.Abort(
1382 _("cannot push to a previous patch: %s") % patch)
1382 _("cannot push to a previous patch: %s") % patch)
1383 else:
1383 else:
1384 if reason:
1384 if reason:
1385 reason = _('guarded by %s') % reason
1385 reason = _('guarded by %s') % reason
1386 else:
1386 else:
1387 reason = _('no matching guards')
1387 reason = _('no matching guards')
1388 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1388 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1389 return 1
1389 return 1
1390 elif all:
1390 elif all:
1391 patch = self.series[-1]
1391 patch = self.series[-1]
1392 if self.isapplied(patch):
1392 if self.isapplied(patch):
1393 self.ui.warn(_('all patches are currently applied\n'))
1393 self.ui.warn(_('all patches are currently applied\n'))
1394 return 0
1394 return 0
1395
1395
1396 # Following the above example, starting at 'top' of B:
1396 # Following the above example, starting at 'top' of B:
1397 # qpush should be performed (pushes C), but a subsequent
1397 # qpush should be performed (pushes C), but a subsequent
1398 # qpush without an argument is an error (nothing to
1398 # qpush without an argument is an error (nothing to
1399 # apply). This allows a loop of "...while hg qpush..." to
1399 # apply). This allows a loop of "...while hg qpush..." to
1400 # work as it detects an error when done
1400 # work as it detects an error when done
1401 start = self.seriesend()
1401 start = self.seriesend()
1402 if start == len(self.series):
1402 if start == len(self.series):
1403 self.ui.warn(_('patch series already fully applied\n'))
1403 self.ui.warn(_('patch series already fully applied\n'))
1404 return 1
1404 return 1
1405 if not force and not keepchanges:
1405 if not force and not keepchanges:
1406 self.checklocalchanges(repo, refresh=self.applied)
1406 self.checklocalchanges(repo, refresh=self.applied)
1407
1407
1408 if exact:
1408 if exact:
1409 if keepchanges:
1409 if keepchanges:
1410 raise error.Abort(
1410 raise error.Abort(
1411 _("cannot use --exact and --keep-changes together"))
1411 _("cannot use --exact and --keep-changes together"))
1412 if move:
1412 if move:
1413 raise error.Abort(_('cannot use --exact and --move '
1413 raise error.Abort(_('cannot use --exact and --move '
1414 'together'))
1414 'together'))
1415 if self.applied:
1415 if self.applied:
1416 raise error.Abort(_('cannot push --exact with applied '
1416 raise error.Abort(_('cannot push --exact with applied '
1417 'patches'))
1417 'patches'))
1418 root = self.series[start]
1418 root = self.series[start]
1419 target = patchheader(self.join(root), self.plainmode).parent
1419 target = patchheader(self.join(root), self.plainmode).parent
1420 if not target:
1420 if not target:
1421 raise error.Abort(
1421 raise error.Abort(
1422 _("%s does not have a parent recorded") % root)
1422 _("%s does not have a parent recorded") % root)
1423 if not repo[target] == repo['.']:
1423 if not repo[target] == repo['.']:
1424 hg.update(repo, target)
1424 hg.update(repo, target)
1425
1425
1426 if move:
1426 if move:
1427 if not patch:
1427 if not patch:
1428 raise error.Abort(_("please specify the patch to move"))
1428 raise error.Abort(_("please specify the patch to move"))
1429 for fullstart, rpn in enumerate(self.fullseries):
1429 for fullstart, rpn in enumerate(self.fullseries):
1430 # strip markers for patch guards
1430 # strip markers for patch guards
1431 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1431 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1432 break
1432 break
1433 for i, rpn in enumerate(self.fullseries[fullstart:]):
1433 for i, rpn in enumerate(self.fullseries[fullstart:]):
1434 # strip markers for patch guards
1434 # strip markers for patch guards
1435 if self.guard_re.split(rpn, 1)[0] == patch:
1435 if self.guard_re.split(rpn, 1)[0] == patch:
1436 break
1436 break
1437 index = fullstart + i
1437 index = fullstart + i
1438 assert index < len(self.fullseries)
1438 assert index < len(self.fullseries)
1439 fullpatch = self.fullseries[index]
1439 fullpatch = self.fullseries[index]
1440 del self.fullseries[index]
1440 del self.fullseries[index]
1441 self.fullseries.insert(fullstart, fullpatch)
1441 self.fullseries.insert(fullstart, fullpatch)
1442 self.parseseries()
1442 self.parseseries()
1443 self.seriesdirty = True
1443 self.seriesdirty = True
1444
1444
1445 self.applieddirty = True
1445 self.applieddirty = True
1446 if start > 0:
1446 if start > 0:
1447 self.checktoppatch(repo)
1447 self.checktoppatch(repo)
1448 if not patch:
1448 if not patch:
1449 patch = self.series[start]
1449 patch = self.series[start]
1450 end = start + 1
1450 end = start + 1
1451 else:
1451 else:
1452 end = self.series.index(patch, start) + 1
1452 end = self.series.index(patch, start) + 1
1453
1453
1454 tobackup = set()
1454 tobackup = set()
1455 if (not nobackup and force) or keepchanges:
1455 if (not nobackup and force) or keepchanges:
1456 status = self.checklocalchanges(repo, force=True)
1456 status = self.checklocalchanges(repo, force=True)
1457 if keepchanges:
1457 if keepchanges:
1458 tobackup.update(status.modified + status.added +
1458 tobackup.update(status.modified + status.added +
1459 status.removed + status.deleted)
1459 status.removed + status.deleted)
1460 else:
1460 else:
1461 tobackup.update(status.modified + status.added)
1461 tobackup.update(status.modified + status.added)
1462
1462
1463 s = self.series[start:end]
1463 s = self.series[start:end]
1464 all_files = set()
1464 all_files = set()
1465 try:
1465 try:
1466 if mergeq:
1466 if mergeq:
1467 ret = self.mergepatch(repo, mergeq, s, diffopts)
1467 ret = self.mergepatch(repo, mergeq, s, diffopts)
1468 else:
1468 else:
1469 ret = self.apply(repo, s, list, all_files=all_files,
1469 ret = self.apply(repo, s, list, all_files=all_files,
1470 tobackup=tobackup, keepchanges=keepchanges)
1470 tobackup=tobackup, keepchanges=keepchanges)
1471 except AbortNoCleanup:
1471 except AbortNoCleanup:
1472 raise
1472 raise
1473 except: # re-raises
1473 except: # re-raises
1474 self.ui.warn(_('cleaning up working directory...\n'))
1474 self.ui.warn(_('cleaning up working directory...\n'))
1475 cmdutil.revert(self.ui, repo, repo['.'],
1475 cmdutil.revert(self.ui, repo, repo['.'],
1476 repo.dirstate.parents(), no_backup=True)
1476 repo.dirstate.parents(), no_backup=True)
1477 # only remove unknown files that we know we touched or
1477 # only remove unknown files that we know we touched or
1478 # created while patching
1478 # created while patching
1479 for f in all_files:
1479 for f in all_files:
1480 if f not in repo.dirstate:
1480 if f not in repo.dirstate:
1481 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1481 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1482 self.ui.warn(_('done\n'))
1482 self.ui.warn(_('done\n'))
1483 raise
1483 raise
1484
1484
1485 if not self.applied:
1485 if not self.applied:
1486 return ret[0]
1486 return ret[0]
1487 top = self.applied[-1].name
1487 top = self.applied[-1].name
1488 if ret[0] and ret[0] > 1:
1488 if ret[0] and ret[0] > 1:
1489 msg = _("errors during apply, please fix and qrefresh %s\n")
1489 msg = _("errors during apply, please fix and qrefresh %s\n")
1490 self.ui.write(msg % top)
1490 self.ui.write(msg % top)
1491 else:
1491 else:
1492 self.ui.write(_("now at: %s\n") % top)
1492 self.ui.write(_("now at: %s\n") % top)
1493 return ret[0]
1493 return ret[0]
1494
1494
1495 def pop(self, repo, patch=None, force=False, update=True, all=False,
1495 def pop(self, repo, patch=None, force=False, update=True, all=False,
1496 nobackup=False, keepchanges=False):
1496 nobackup=False, keepchanges=False):
1497 self.checkkeepchanges(keepchanges, force)
1497 self.checkkeepchanges(keepchanges, force)
1498 with repo.wlock():
1498 with repo.wlock():
1499 if patch:
1499 if patch:
1500 # index, rev, patch
1500 # index, rev, patch
1501 info = self.isapplied(patch)
1501 info = self.isapplied(patch)
1502 if not info:
1502 if not info:
1503 patch = self.lookup(patch)
1503 patch = self.lookup(patch)
1504 info = self.isapplied(patch)
1504 info = self.isapplied(patch)
1505 if not info:
1505 if not info:
1506 raise error.Abort(_("patch %s is not applied") % patch)
1506 raise error.Abort(_("patch %s is not applied") % patch)
1507
1507
1508 if not self.applied:
1508 if not self.applied:
1509 # Allow qpop -a to work repeatedly,
1509 # Allow qpop -a to work repeatedly,
1510 # but not qpop without an argument
1510 # but not qpop without an argument
1511 self.ui.warn(_("no patches applied\n"))
1511 self.ui.warn(_("no patches applied\n"))
1512 return not all
1512 return not all
1513
1513
1514 if all:
1514 if all:
1515 start = 0
1515 start = 0
1516 elif patch:
1516 elif patch:
1517 start = info[0] + 1
1517 start = info[0] + 1
1518 else:
1518 else:
1519 start = len(self.applied) - 1
1519 start = len(self.applied) - 1
1520
1520
1521 if start >= len(self.applied):
1521 if start >= len(self.applied):
1522 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1522 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1523 return
1523 return
1524
1524
1525 if not update:
1525 if not update:
1526 parents = repo.dirstate.parents()
1526 parents = repo.dirstate.parents()
1527 rr = [x.node for x in self.applied]
1527 rr = [x.node for x in self.applied]
1528 for p in parents:
1528 for p in parents:
1529 if p in rr:
1529 if p in rr:
1530 self.ui.warn(_("qpop: forcing dirstate update\n"))
1530 self.ui.warn(_("qpop: forcing dirstate update\n"))
1531 update = True
1531 update = True
1532 else:
1532 else:
1533 parents = [p.node() for p in repo[None].parents()]
1533 parents = [p.node() for p in repo[None].parents()]
1534 needupdate = False
1534 needupdate = False
1535 for entry in self.applied[start:]:
1535 for entry in self.applied[start:]:
1536 if entry.node in parents:
1536 if entry.node in parents:
1537 needupdate = True
1537 needupdate = True
1538 break
1538 break
1539 update = needupdate
1539 update = needupdate
1540
1540
1541 tobackup = set()
1541 tobackup = set()
1542 if update:
1542 if update:
1543 s = self.checklocalchanges(repo, force=force or keepchanges)
1543 s = self.checklocalchanges(repo, force=force or keepchanges)
1544 if force:
1544 if force:
1545 if not nobackup:
1545 if not nobackup:
1546 tobackup.update(s.modified + s.added)
1546 tobackup.update(s.modified + s.added)
1547 elif keepchanges:
1547 elif keepchanges:
1548 tobackup.update(s.modified + s.added +
1548 tobackup.update(s.modified + s.added +
1549 s.removed + s.deleted)
1549 s.removed + s.deleted)
1550
1550
1551 self.applieddirty = True
1551 self.applieddirty = True
1552 end = len(self.applied)
1552 end = len(self.applied)
1553 rev = self.applied[start].node
1553 rev = self.applied[start].node
1554
1554
1555 try:
1555 try:
1556 heads = repo.changelog.heads(rev)
1556 heads = repo.changelog.heads(rev)
1557 except error.LookupError:
1557 except error.LookupError:
1558 node = short(rev)
1558 node = short(rev)
1559 raise error.Abort(_('trying to pop unknown node %s') % node)
1559 raise error.Abort(_('trying to pop unknown node %s') % node)
1560
1560
1561 if heads != [self.applied[-1].node]:
1561 if heads != [self.applied[-1].node]:
1562 raise error.Abort(_("popping would remove a revision not "
1562 raise error.Abort(_("popping would remove a revision not "
1563 "managed by this patch queue"))
1563 "managed by this patch queue"))
1564 if not repo[self.applied[-1].node].mutable():
1564 if not repo[self.applied[-1].node].mutable():
1565 raise error.Abort(
1565 raise error.Abort(
1566 _("popping would remove a public revision"),
1566 _("popping would remove a public revision"),
1567 hint=_("see 'hg help phases' for details"))
1567 hint=_("see 'hg help phases' for details"))
1568
1568
1569 # we know there are no local changes, so we can make a simplified
1569 # we know there are no local changes, so we can make a simplified
1570 # form of hg.update.
1570 # form of hg.update.
1571 if update:
1571 if update:
1572 qp = self.qparents(repo, rev)
1572 qp = self.qparents(repo, rev)
1573 ctx = repo[qp]
1573 ctx = repo[qp]
1574 m, a, r, d = repo.status(qp, '.')[:4]
1574 m, a, r, d = repo.status(qp, '.')[:4]
1575 if d:
1575 if d:
1576 raise error.Abort(_("deletions found between repo revs"))
1576 raise error.Abort(_("deletions found between repo revs"))
1577
1577
1578 tobackup = set(a + m + r) & tobackup
1578 tobackup = set(a + m + r) & tobackup
1579 if keepchanges and tobackup:
1579 if keepchanges and tobackup:
1580 raise error.Abort(_("local changes found, qrefresh first"))
1580 raise error.Abort(_("local changes found, qrefresh first"))
1581 self.backup(repo, tobackup)
1581 self.backup(repo, tobackup)
1582 repo.dirstate.beginparentchange()
1582 repo.dirstate.beginparentchange()
1583 for f in a:
1583 for f in a:
1584 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1584 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1585 repo.dirstate.drop(f)
1585 repo.dirstate.drop(f)
1586 for f in m + r:
1586 for f in m + r:
1587 fctx = ctx[f]
1587 fctx = ctx[f]
1588 repo.wwrite(f, fctx.data(), fctx.flags())
1588 repo.wwrite(f, fctx.data(), fctx.flags())
1589 repo.dirstate.normal(f)
1589 repo.dirstate.normal(f)
1590 repo.setparents(qp, nullid)
1590 repo.setparents(qp, nullid)
1591 repo.dirstate.endparentchange()
1591 repo.dirstate.endparentchange()
1592 for patch in reversed(self.applied[start:end]):
1592 for patch in reversed(self.applied[start:end]):
1593 self.ui.status(_("popping %s\n") % patch.name)
1593 self.ui.status(_("popping %s\n") % patch.name)
1594 del self.applied[start:end]
1594 del self.applied[start:end]
1595 strip(self.ui, repo, [rev], update=False, backup=False)
1595 strip(self.ui, repo, [rev], update=False, backup=False)
1596 for s, state in repo['.'].substate.items():
1596 for s, state in repo['.'].substate.items():
1597 repo['.'].sub(s).get(state)
1597 repo['.'].sub(s).get(state)
1598 if self.applied:
1598 if self.applied:
1599 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1599 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1600 else:
1600 else:
1601 self.ui.write(_("patch queue now empty\n"))
1601 self.ui.write(_("patch queue now empty\n"))
1602
1602
1603 def diff(self, repo, pats, opts):
1603 def diff(self, repo, pats, opts):
1604 top, patch = self.checktoppatch(repo)
1604 top, patch = self.checktoppatch(repo)
1605 if not top:
1605 if not top:
1606 self.ui.write(_("no patches applied\n"))
1606 self.ui.write(_("no patches applied\n"))
1607 return
1607 return
1608 qp = self.qparents(repo, top)
1608 qp = self.qparents(repo, top)
1609 if opts.get('reverse'):
1609 if opts.get('reverse'):
1610 node1, node2 = None, qp
1610 node1, node2 = None, qp
1611 else:
1611 else:
1612 node1, node2 = qp, None
1612 node1, node2 = qp, None
1613 diffopts = self.diffopts(opts, patch)
1613 diffopts = self.diffopts(opts, patch)
1614 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1614 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1615
1615
1616 def refresh(self, repo, pats=None, **opts):
1616 def refresh(self, repo, pats=None, **opts):
1617 if not self.applied:
1617 if not self.applied:
1618 self.ui.write(_("no patches applied\n"))
1618 self.ui.write(_("no patches applied\n"))
1619 return 1
1619 return 1
1620 msg = opts.get('msg', '').rstrip()
1620 msg = opts.get('msg', '').rstrip()
1621 edit = opts.get('edit')
1621 edit = opts.get('edit')
1622 editform = opts.get('editform', 'mq.qrefresh')
1622 editform = opts.get('editform', 'mq.qrefresh')
1623 newuser = opts.get('user')
1623 newuser = opts.get('user')
1624 newdate = opts.get('date')
1624 newdate = opts.get('date')
1625 if newdate:
1625 if newdate:
1626 newdate = '%d %d' % util.parsedate(newdate)
1626 newdate = '%d %d' % util.parsedate(newdate)
1627 wlock = repo.wlock()
1627 wlock = repo.wlock()
1628
1628
1629 try:
1629 try:
1630 self.checktoppatch(repo)
1630 self.checktoppatch(repo)
1631 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1631 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1632 if repo.changelog.heads(top) != [top]:
1632 if repo.changelog.heads(top) != [top]:
1633 raise error.Abort(_("cannot qrefresh a revision with children"))
1633 raise error.Abort(_("cannot qrefresh a revision with children"))
1634 if not repo[top].mutable():
1634 if not repo[top].mutable():
1635 raise error.Abort(_("cannot qrefresh public revision"),
1635 raise error.Abort(_("cannot qrefresh public revision"),
1636 hint=_("see 'hg help phases' for details"))
1636 hint=_("see 'hg help phases' for details"))
1637
1637
1638 cparents = repo.changelog.parents(top)
1638 cparents = repo.changelog.parents(top)
1639 patchparent = self.qparents(repo, top)
1639 patchparent = self.qparents(repo, top)
1640
1640
1641 inclsubs = checksubstate(repo, hex(patchparent))
1641 inclsubs = checksubstate(repo, hex(patchparent))
1642 if inclsubs:
1642 if inclsubs:
1643 substatestate = repo.dirstate['.hgsubstate']
1643 substatestate = repo.dirstate['.hgsubstate']
1644
1644
1645 ph = patchheader(self.join(patchfn), self.plainmode)
1645 ph = patchheader(self.join(patchfn), self.plainmode)
1646 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1646 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1647 if newuser:
1647 if newuser:
1648 ph.setuser(newuser)
1648 ph.setuser(newuser)
1649 if newdate:
1649 if newdate:
1650 ph.setdate(newdate)
1650 ph.setdate(newdate)
1651 ph.setparent(hex(patchparent))
1651 ph.setparent(hex(patchparent))
1652
1652
1653 # only commit new patch when write is complete
1653 # only commit new patch when write is complete
1654 patchf = self.opener(patchfn, 'w', atomictemp=True)
1654 patchf = self.opener(patchfn, 'w', atomictemp=True)
1655
1655
1656 # update the dirstate in place, strip off the qtip commit
1656 # update the dirstate in place, strip off the qtip commit
1657 # and then commit.
1657 # and then commit.
1658 #
1658 #
1659 # this should really read:
1659 # this should really read:
1660 # mm, dd, aa = repo.status(top, patchparent)[:3]
1660 # mm, dd, aa = repo.status(top, patchparent)[:3]
1661 # but we do it backwards to take advantage of manifest/changelog
1661 # but we do it backwards to take advantage of manifest/changelog
1662 # caching against the next repo.status call
1662 # caching against the next repo.status call
1663 mm, aa, dd = repo.status(patchparent, top)[:3]
1663 mm, aa, dd = repo.status(patchparent, top)[:3]
1664 changes = repo.changelog.read(top)
1664 changes = repo.changelog.read(top)
1665 man = repo.manifestlog[changes[0]].read()
1665 man = repo.manifestlog[changes[0]].read()
1666 aaa = aa[:]
1666 aaa = aa[:]
1667 matchfn = scmutil.match(repo[None], pats, opts)
1667 matchfn = scmutil.match(repo[None], pats, opts)
1668 # in short mode, we only diff the files included in the
1668 # in short mode, we only diff the files included in the
1669 # patch already plus specified files
1669 # patch already plus specified files
1670 if opts.get('short'):
1670 if opts.get('short'):
1671 # if amending a patch, we start with existing
1671 # if amending a patch, we start with existing
1672 # files plus specified files - unfiltered
1672 # files plus specified files - unfiltered
1673 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1673 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1674 # filter with include/exclude options
1674 # filter with include/exclude options
1675 matchfn = scmutil.match(repo[None], opts=opts)
1675 matchfn = scmutil.match(repo[None], opts=opts)
1676 else:
1676 else:
1677 match = scmutil.matchall(repo)
1677 match = scmutil.matchall(repo)
1678 m, a, r, d = repo.status(match=match)[:4]
1678 m, a, r, d = repo.status(match=match)[:4]
1679 mm = set(mm)
1679 mm = set(mm)
1680 aa = set(aa)
1680 aa = set(aa)
1681 dd = set(dd)
1681 dd = set(dd)
1682
1682
1683 # we might end up with files that were added between
1683 # we might end up with files that were added between
1684 # qtip and the dirstate parent, but then changed in the
1684 # qtip and the dirstate parent, but then changed in the
1685 # local dirstate. in this case, we want them to only
1685 # local dirstate. in this case, we want them to only
1686 # show up in the added section
1686 # show up in the added section
1687 for x in m:
1687 for x in m:
1688 if x not in aa:
1688 if x not in aa:
1689 mm.add(x)
1689 mm.add(x)
1690 # we might end up with files added by the local dirstate that
1690 # we might end up with files added by the local dirstate that
1691 # were deleted by the patch. In this case, they should only
1691 # were deleted by the patch. In this case, they should only
1692 # show up in the changed section.
1692 # show up in the changed section.
1693 for x in a:
1693 for x in a:
1694 if x in dd:
1694 if x in dd:
1695 dd.remove(x)
1695 dd.remove(x)
1696 mm.add(x)
1696 mm.add(x)
1697 else:
1697 else:
1698 aa.add(x)
1698 aa.add(x)
1699 # make sure any files deleted in the local dirstate
1699 # make sure any files deleted in the local dirstate
1700 # are not in the add or change column of the patch
1700 # are not in the add or change column of the patch
1701 forget = []
1701 forget = []
1702 for x in d + r:
1702 for x in d + r:
1703 if x in aa:
1703 if x in aa:
1704 aa.remove(x)
1704 aa.remove(x)
1705 forget.append(x)
1705 forget.append(x)
1706 continue
1706 continue
1707 else:
1707 else:
1708 mm.discard(x)
1708 mm.discard(x)
1709 dd.add(x)
1709 dd.add(x)
1710
1710
1711 m = list(mm)
1711 m = list(mm)
1712 r = list(dd)
1712 r = list(dd)
1713 a = list(aa)
1713 a = list(aa)
1714
1714
1715 # create 'match' that includes the files to be recommitted.
1715 # create 'match' that includes the files to be recommitted.
1716 # apply matchfn via repo.status to ensure correct case handling.
1716 # apply matchfn via repo.status to ensure correct case handling.
1717 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1717 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1718 allmatches = set(cm + ca + cr + cd)
1718 allmatches = set(cm + ca + cr + cd)
1719 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1719 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1720
1720
1721 files = set(inclsubs)
1721 files = set(inclsubs)
1722 for x in refreshchanges:
1722 for x in refreshchanges:
1723 files.update(x)
1723 files.update(x)
1724 match = scmutil.matchfiles(repo, files)
1724 match = scmutil.matchfiles(repo, files)
1725
1725
1726 bmlist = repo[top].bookmarks()
1726 bmlist = repo[top].bookmarks()
1727
1727
1728 dsguard = None
1728 dsguard = None
1729 try:
1729 try:
1730 dsguard = dirstateguard.dirstateguard(repo, 'mq.refresh')
1730 dsguard = dirstateguard.dirstateguard(repo, 'mq.refresh')
1731 if diffopts.git or diffopts.upgrade:
1731 if diffopts.git or diffopts.upgrade:
1732 copies = {}
1732 copies = {}
1733 for dst in a:
1733 for dst in a:
1734 src = repo.dirstate.copied(dst)
1734 src = repo.dirstate.copied(dst)
1735 # during qfold, the source file for copies may
1735 # during qfold, the source file for copies may
1736 # be removed. Treat this as a simple add.
1736 # be removed. Treat this as a simple add.
1737 if src is not None and src in repo.dirstate:
1737 if src is not None and src in repo.dirstate:
1738 copies.setdefault(src, []).append(dst)
1738 copies.setdefault(src, []).append(dst)
1739 repo.dirstate.add(dst)
1739 repo.dirstate.add(dst)
1740 # remember the copies between patchparent and qtip
1740 # remember the copies between patchparent and qtip
1741 for dst in aaa:
1741 for dst in aaa:
1742 f = repo.file(dst)
1742 f = repo.file(dst)
1743 src = f.renamed(man[dst])
1743 src = f.renamed(man[dst])
1744 if src:
1744 if src:
1745 copies.setdefault(src[0], []).extend(
1745 copies.setdefault(src[0], []).extend(
1746 copies.get(dst, []))
1746 copies.get(dst, []))
1747 if dst in a:
1747 if dst in a:
1748 copies[src[0]].append(dst)
1748 copies[src[0]].append(dst)
1749 # we can't copy a file created by the patch itself
1749 # we can't copy a file created by the patch itself
1750 if dst in copies:
1750 if dst in copies:
1751 del copies[dst]
1751 del copies[dst]
1752 for src, dsts in copies.iteritems():
1752 for src, dsts in copies.iteritems():
1753 for dst in dsts:
1753 for dst in dsts:
1754 repo.dirstate.copy(src, dst)
1754 repo.dirstate.copy(src, dst)
1755 else:
1755 else:
1756 for dst in a:
1756 for dst in a:
1757 repo.dirstate.add(dst)
1757 repo.dirstate.add(dst)
1758 # Drop useless copy information
1758 # Drop useless copy information
1759 for f in list(repo.dirstate.copies()):
1759 for f in list(repo.dirstate.copies()):
1760 repo.dirstate.copy(None, f)
1760 repo.dirstate.copy(None, f)
1761 for f in r:
1761 for f in r:
1762 repo.dirstate.remove(f)
1762 repo.dirstate.remove(f)
1763 # if the patch excludes a modified file, mark that
1763 # if the patch excludes a modified file, mark that
1764 # file with mtime=0 so status can see it.
1764 # file with mtime=0 so status can see it.
1765 mm = []
1765 mm = []
1766 for i in xrange(len(m) - 1, -1, -1):
1766 for i in xrange(len(m) - 1, -1, -1):
1767 if not matchfn(m[i]):
1767 if not matchfn(m[i]):
1768 mm.append(m[i])
1768 mm.append(m[i])
1769 del m[i]
1769 del m[i]
1770 for f in m:
1770 for f in m:
1771 repo.dirstate.normal(f)
1771 repo.dirstate.normal(f)
1772 for f in mm:
1772 for f in mm:
1773 repo.dirstate.normallookup(f)
1773 repo.dirstate.normallookup(f)
1774 for f in forget:
1774 for f in forget:
1775 repo.dirstate.drop(f)
1775 repo.dirstate.drop(f)
1776
1776
1777 user = ph.user or changes[1]
1777 user = ph.user or changes[1]
1778
1778
1779 oldphase = repo[top].phase()
1779 oldphase = repo[top].phase()
1780
1780
1781 # assumes strip can roll itself back if interrupted
1781 # assumes strip can roll itself back if interrupted
1782 repo.setparents(*cparents)
1782 repo.setparents(*cparents)
1783 self.applied.pop()
1783 self.applied.pop()
1784 self.applieddirty = True
1784 self.applieddirty = True
1785 strip(self.ui, repo, [top], update=False, backup=False)
1785 strip(self.ui, repo, [top], update=False, backup=False)
1786 dsguard.close()
1786 dsguard.close()
1787 finally:
1787 finally:
1788 release(dsguard)
1788 release(dsguard)
1789
1789
1790 try:
1790 try:
1791 # might be nice to attempt to roll back strip after this
1791 # might be nice to attempt to roll back strip after this
1792
1792
1793 defaultmsg = "[mq]: %s" % patchfn
1793 defaultmsg = "[mq]: %s" % patchfn
1794 editor = cmdutil.getcommiteditor(editform=editform)
1794 editor = cmdutil.getcommiteditor(editform=editform)
1795 if edit:
1795 if edit:
1796 def finishdesc(desc):
1796 def finishdesc(desc):
1797 if desc.rstrip():
1797 if desc.rstrip():
1798 ph.setmessage(desc)
1798 ph.setmessage(desc)
1799 return desc
1799 return desc
1800 return defaultmsg
1800 return defaultmsg
1801 # i18n: this message is shown in editor with "HG: " prefix
1801 # i18n: this message is shown in editor with "HG: " prefix
1802 extramsg = _('Leave message empty to use default message.')
1802 extramsg = _('Leave message empty to use default message.')
1803 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1803 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1804 extramsg=extramsg,
1804 extramsg=extramsg,
1805 editform=editform)
1805 editform=editform)
1806 message = msg or "\n".join(ph.message)
1806 message = msg or "\n".join(ph.message)
1807 elif not msg:
1807 elif not msg:
1808 if not ph.message:
1808 if not ph.message:
1809 message = defaultmsg
1809 message = defaultmsg
1810 else:
1810 else:
1811 message = "\n".join(ph.message)
1811 message = "\n".join(ph.message)
1812 else:
1812 else:
1813 message = msg
1813 message = msg
1814 ph.setmessage(msg)
1814 ph.setmessage(msg)
1815
1815
1816 # Ensure we create a new changeset in the same phase than
1816 # Ensure we create a new changeset in the same phase than
1817 # the old one.
1817 # the old one.
1818 lock = tr = None
1818 lock = tr = None
1819 try:
1819 try:
1820 lock = repo.lock()
1820 lock = repo.lock()
1821 tr = repo.transaction('mq')
1821 tr = repo.transaction('mq')
1822 n = newcommit(repo, oldphase, message, user, ph.date,
1822 n = newcommit(repo, oldphase, message, user, ph.date,
1823 match=match, force=True, editor=editor)
1823 match=match, force=True, editor=editor)
1824 # only write patch after a successful commit
1824 # only write patch after a successful commit
1825 c = [list(x) for x in refreshchanges]
1825 c = [list(x) for x in refreshchanges]
1826 if inclsubs:
1826 if inclsubs:
1827 self.putsubstate2changes(substatestate, c)
1827 self.putsubstate2changes(substatestate, c)
1828 chunks = patchmod.diff(repo, patchparent,
1828 chunks = patchmod.diff(repo, patchparent,
1829 changes=c, opts=diffopts)
1829 changes=c, opts=diffopts)
1830 comments = str(ph)
1830 comments = str(ph)
1831 if comments:
1831 if comments:
1832 patchf.write(comments)
1832 patchf.write(comments)
1833 for chunk in chunks:
1833 for chunk in chunks:
1834 patchf.write(chunk)
1834 patchf.write(chunk)
1835 patchf.close()
1835 patchf.close()
1836
1836
1837 marks = repo._bookmarks
1837 marks = repo._bookmarks
1838 for bm in bmlist:
1838 for bm in bmlist:
1839 marks[bm] = n
1839 marks[bm] = n
1840 marks.recordchange(tr)
1840 marks.recordchange(tr)
1841 tr.close()
1841 tr.close()
1842
1842
1843 self.applied.append(statusentry(n, patchfn))
1843 self.applied.append(statusentry(n, patchfn))
1844 finally:
1844 finally:
1845 lockmod.release(tr, lock)
1845 lockmod.release(tr, lock)
1846 except: # re-raises
1846 except: # re-raises
1847 ctx = repo[cparents[0]]
1847 ctx = repo[cparents[0]]
1848 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1848 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1849 self.savedirty()
1849 self.savedirty()
1850 self.ui.warn(_('qrefresh interrupted while patch was popped! '
1850 self.ui.warn(_('qrefresh interrupted while patch was popped! '
1851 '(revert --all, qpush to recover)\n'))
1851 '(revert --all, qpush to recover)\n'))
1852 raise
1852 raise
1853 finally:
1853 finally:
1854 wlock.release()
1854 wlock.release()
1855 self.removeundo(repo)
1855 self.removeundo(repo)
1856
1856
1857 def init(self, repo, create=False):
1857 def init(self, repo, create=False):
1858 if not create and os.path.isdir(self.path):
1858 if not create and os.path.isdir(self.path):
1859 raise error.Abort(_("patch queue directory already exists"))
1859 raise error.Abort(_("patch queue directory already exists"))
1860 try:
1860 try:
1861 os.mkdir(self.path)
1861 os.mkdir(self.path)
1862 except OSError as inst:
1862 except OSError as inst:
1863 if inst.errno != errno.EEXIST or not create:
1863 if inst.errno != errno.EEXIST or not create:
1864 raise
1864 raise
1865 if create:
1865 if create:
1866 return self.qrepo(create=True)
1866 return self.qrepo(create=True)
1867
1867
1868 def unapplied(self, repo, patch=None):
1868 def unapplied(self, repo, patch=None):
1869 if patch and patch not in self.series:
1869 if patch and patch not in self.series:
1870 raise error.Abort(_("patch %s is not in series file") % patch)
1870 raise error.Abort(_("patch %s is not in series file") % patch)
1871 if not patch:
1871 if not patch:
1872 start = self.seriesend()
1872 start = self.seriesend()
1873 else:
1873 else:
1874 start = self.series.index(patch) + 1
1874 start = self.series.index(patch) + 1
1875 unapplied = []
1875 unapplied = []
1876 for i in xrange(start, len(self.series)):
1876 for i in xrange(start, len(self.series)):
1877 pushable, reason = self.pushable(i)
1877 pushable, reason = self.pushable(i)
1878 if pushable:
1878 if pushable:
1879 unapplied.append((i, self.series[i]))
1879 unapplied.append((i, self.series[i]))
1880 self.explainpushable(i)
1880 self.explainpushable(i)
1881 return unapplied
1881 return unapplied
1882
1882
1883 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1883 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1884 summary=False):
1884 summary=False):
1885 def displayname(pfx, patchname, state):
1885 def displayname(pfx, patchname, state):
1886 if pfx:
1886 if pfx:
1887 self.ui.write(pfx)
1887 self.ui.write(pfx)
1888 if summary:
1888 if summary:
1889 ph = patchheader(self.join(patchname), self.plainmode)
1889 ph = patchheader(self.join(patchname), self.plainmode)
1890 if ph.message:
1890 if ph.message:
1891 msg = ph.message[0]
1891 msg = ph.message[0]
1892 else:
1892 else:
1893 msg = ''
1893 msg = ''
1894
1894
1895 if self.ui.formatted():
1895 if self.ui.formatted():
1896 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1896 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1897 if width > 0:
1897 if width > 0:
1898 msg = util.ellipsis(msg, width)
1898 msg = util.ellipsis(msg, width)
1899 else:
1899 else:
1900 msg = ''
1900 msg = ''
1901 self.ui.write(patchname, label='qseries.' + state)
1901 self.ui.write(patchname, label='qseries.' + state)
1902 self.ui.write(': ')
1902 self.ui.write(': ')
1903 self.ui.write(msg, label='qseries.message.' + state)
1903 self.ui.write(msg, label='qseries.message.' + state)
1904 else:
1904 else:
1905 self.ui.write(patchname, label='qseries.' + state)
1905 self.ui.write(patchname, label='qseries.' + state)
1906 self.ui.write('\n')
1906 self.ui.write('\n')
1907
1907
1908 applied = set([p.name for p in self.applied])
1908 applied = set([p.name for p in self.applied])
1909 if length is None:
1909 if length is None:
1910 length = len(self.series) - start
1910 length = len(self.series) - start
1911 if not missing:
1911 if not missing:
1912 if self.ui.verbose:
1912 if self.ui.verbose:
1913 idxwidth = len(str(start + length - 1))
1913 idxwidth = len(str(start + length - 1))
1914 for i in xrange(start, start + length):
1914 for i in xrange(start, start + length):
1915 patch = self.series[i]
1915 patch = self.series[i]
1916 if patch in applied:
1916 if patch in applied:
1917 char, state = 'A', 'applied'
1917 char, state = 'A', 'applied'
1918 elif self.pushable(i)[0]:
1918 elif self.pushable(i)[0]:
1919 char, state = 'U', 'unapplied'
1919 char, state = 'U', 'unapplied'
1920 else:
1920 else:
1921 char, state = 'G', 'guarded'
1921 char, state = 'G', 'guarded'
1922 pfx = ''
1922 pfx = ''
1923 if self.ui.verbose:
1923 if self.ui.verbose:
1924 pfx = '%*d %s ' % (idxwidth, i, char)
1924 pfx = '%*d %s ' % (idxwidth, i, char)
1925 elif status and status != char:
1925 elif status and status != char:
1926 continue
1926 continue
1927 displayname(pfx, patch, state)
1927 displayname(pfx, patch, state)
1928 else:
1928 else:
1929 msng_list = []
1929 msng_list = []
1930 for root, dirs, files in os.walk(self.path):
1930 for root, dirs, files in os.walk(self.path):
1931 d = root[len(self.path) + 1:]
1931 d = root[len(self.path) + 1:]
1932 for f in files:
1932 for f in files:
1933 fl = os.path.join(d, f)
1933 fl = os.path.join(d, f)
1934 if (fl not in self.series and
1934 if (fl not in self.series and
1935 fl not in (self.statuspath, self.seriespath,
1935 fl not in (self.statuspath, self.seriespath,
1936 self.guardspath)
1936 self.guardspath)
1937 and not fl.startswith('.')):
1937 and not fl.startswith('.')):
1938 msng_list.append(fl)
1938 msng_list.append(fl)
1939 for x in sorted(msng_list):
1939 for x in sorted(msng_list):
1940 pfx = self.ui.verbose and ('D ') or ''
1940 pfx = self.ui.verbose and ('D ') or ''
1941 displayname(pfx, x, 'missing')
1941 displayname(pfx, x, 'missing')
1942
1942
1943 def issaveline(self, l):
1943 def issaveline(self, l):
1944 if l.name == '.hg.patches.save.line':
1944 if l.name == '.hg.patches.save.line':
1945 return True
1945 return True
1946
1946
1947 def qrepo(self, create=False):
1947 def qrepo(self, create=False):
1948 ui = self.baseui.copy()
1948 ui = self.baseui.copy()
1949 if create or os.path.isdir(self.join(".hg")):
1949 if create or os.path.isdir(self.join(".hg")):
1950 return hg.repository(ui, path=self.path, create=create)
1950 return hg.repository(ui, path=self.path, create=create)
1951
1951
1952 def restore(self, repo, rev, delete=None, qupdate=None):
1952 def restore(self, repo, rev, delete=None, qupdate=None):
1953 desc = repo[rev].description().strip()
1953 desc = repo[rev].description().strip()
1954 lines = desc.splitlines()
1954 lines = desc.splitlines()
1955 i = 0
1955 i = 0
1956 datastart = None
1956 datastart = None
1957 series = []
1957 series = []
1958 applied = []
1958 applied = []
1959 qpp = None
1959 qpp = None
1960 for i, line in enumerate(lines):
1960 for i, line in enumerate(lines):
1961 if line == 'Patch Data:':
1961 if line == 'Patch Data:':
1962 datastart = i + 1
1962 datastart = i + 1
1963 elif line.startswith('Dirstate:'):
1963 elif line.startswith('Dirstate:'):
1964 l = line.rstrip()
1964 l = line.rstrip()
1965 l = l[10:].split(' ')
1965 l = l[10:].split(' ')
1966 qpp = [bin(x) for x in l]
1966 qpp = [bin(x) for x in l]
1967 elif datastart is not None:
1967 elif datastart is not None:
1968 l = line.rstrip()
1968 l = line.rstrip()
1969 n, name = l.split(':', 1)
1969 n, name = l.split(':', 1)
1970 if n:
1970 if n:
1971 applied.append(statusentry(bin(n), name))
1971 applied.append(statusentry(bin(n), name))
1972 else:
1972 else:
1973 series.append(l)
1973 series.append(l)
1974 if datastart is None:
1974 if datastart is None:
1975 self.ui.warn(_("no saved patch data found\n"))
1975 self.ui.warn(_("no saved patch data found\n"))
1976 return 1
1976 return 1
1977 self.ui.warn(_("restoring status: %s\n") % lines[0])
1977 self.ui.warn(_("restoring status: %s\n") % lines[0])
1978 self.fullseries = series
1978 self.fullseries = series
1979 self.applied = applied
1979 self.applied = applied
1980 self.parseseries()
1980 self.parseseries()
1981 self.seriesdirty = True
1981 self.seriesdirty = True
1982 self.applieddirty = True
1982 self.applieddirty = True
1983 heads = repo.changelog.heads()
1983 heads = repo.changelog.heads()
1984 if delete:
1984 if delete:
1985 if rev not in heads:
1985 if rev not in heads:
1986 self.ui.warn(_("save entry has children, leaving it alone\n"))
1986 self.ui.warn(_("save entry has children, leaving it alone\n"))
1987 else:
1987 else:
1988 self.ui.warn(_("removing save entry %s\n") % short(rev))
1988 self.ui.warn(_("removing save entry %s\n") % short(rev))
1989 pp = repo.dirstate.parents()
1989 pp = repo.dirstate.parents()
1990 if rev in pp:
1990 if rev in pp:
1991 update = True
1991 update = True
1992 else:
1992 else:
1993 update = False
1993 update = False
1994 strip(self.ui, repo, [rev], update=update, backup=False)
1994 strip(self.ui, repo, [rev], update=update, backup=False)
1995 if qpp:
1995 if qpp:
1996 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1996 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1997 (short(qpp[0]), short(qpp[1])))
1997 (short(qpp[0]), short(qpp[1])))
1998 if qupdate:
1998 if qupdate:
1999 self.ui.status(_("updating queue directory\n"))
1999 self.ui.status(_("updating queue directory\n"))
2000 r = self.qrepo()
2000 r = self.qrepo()
2001 if not r:
2001 if not r:
2002 self.ui.warn(_("unable to load queue repository\n"))
2002 self.ui.warn(_("unable to load queue repository\n"))
2003 return 1
2003 return 1
2004 hg.clean(r, qpp[0])
2004 hg.clean(r, qpp[0])
2005
2005
2006 def save(self, repo, msg=None):
2006 def save(self, repo, msg=None):
2007 if not self.applied:
2007 if not self.applied:
2008 self.ui.warn(_("save: no patches applied, exiting\n"))
2008 self.ui.warn(_("save: no patches applied, exiting\n"))
2009 return 1
2009 return 1
2010 if self.issaveline(self.applied[-1]):
2010 if self.issaveline(self.applied[-1]):
2011 self.ui.warn(_("status is already saved\n"))
2011 self.ui.warn(_("status is already saved\n"))
2012 return 1
2012 return 1
2013
2013
2014 if not msg:
2014 if not msg:
2015 msg = _("hg patches saved state")
2015 msg = _("hg patches saved state")
2016 else:
2016 else:
2017 msg = "hg patches: " + msg.rstrip('\r\n')
2017 msg = "hg patches: " + msg.rstrip('\r\n')
2018 r = self.qrepo()
2018 r = self.qrepo()
2019 if r:
2019 if r:
2020 pp = r.dirstate.parents()
2020 pp = r.dirstate.parents()
2021 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2021 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2022 msg += "\n\nPatch Data:\n"
2022 msg += "\n\nPatch Data:\n"
2023 msg += ''.join('%s\n' % x for x in self.applied)
2023 msg += ''.join('%s\n' % x for x in self.applied)
2024 msg += ''.join(':%s\n' % x for x in self.fullseries)
2024 msg += ''.join(':%s\n' % x for x in self.fullseries)
2025 n = repo.commit(msg, force=True)
2025 n = repo.commit(msg, force=True)
2026 if not n:
2026 if not n:
2027 self.ui.warn(_("repo commit failed\n"))
2027 self.ui.warn(_("repo commit failed\n"))
2028 return 1
2028 return 1
2029 self.applied.append(statusentry(n, '.hg.patches.save.line'))
2029 self.applied.append(statusentry(n, '.hg.patches.save.line'))
2030 self.applieddirty = True
2030 self.applieddirty = True
2031 self.removeundo(repo)
2031 self.removeundo(repo)
2032
2032
2033 def fullseriesend(self):
2033 def fullseriesend(self):
2034 if self.applied:
2034 if self.applied:
2035 p = self.applied[-1].name
2035 p = self.applied[-1].name
2036 end = self.findseries(p)
2036 end = self.findseries(p)
2037 if end is None:
2037 if end is None:
2038 return len(self.fullseries)
2038 return len(self.fullseries)
2039 return end + 1
2039 return end + 1
2040 return 0
2040 return 0
2041
2041
2042 def seriesend(self, all_patches=False):
2042 def seriesend(self, all_patches=False):
2043 """If all_patches is False, return the index of the next pushable patch
2043 """If all_patches is False, return the index of the next pushable patch
2044 in the series, or the series length. If all_patches is True, return the
2044 in the series, or the series length. If all_patches is True, return the
2045 index of the first patch past the last applied one.
2045 index of the first patch past the last applied one.
2046 """
2046 """
2047 end = 0
2047 end = 0
2048 def nextpatch(start):
2048 def nextpatch(start):
2049 if all_patches or start >= len(self.series):
2049 if all_patches or start >= len(self.series):
2050 return start
2050 return start
2051 for i in xrange(start, len(self.series)):
2051 for i in xrange(start, len(self.series)):
2052 p, reason = self.pushable(i)
2052 p, reason = self.pushable(i)
2053 if p:
2053 if p:
2054 return i
2054 return i
2055 self.explainpushable(i)
2055 self.explainpushable(i)
2056 return len(self.series)
2056 return len(self.series)
2057 if self.applied:
2057 if self.applied:
2058 p = self.applied[-1].name
2058 p = self.applied[-1].name
2059 try:
2059 try:
2060 end = self.series.index(p)
2060 end = self.series.index(p)
2061 except ValueError:
2061 except ValueError:
2062 return 0
2062 return 0
2063 return nextpatch(end + 1)
2063 return nextpatch(end + 1)
2064 return nextpatch(end)
2064 return nextpatch(end)
2065
2065
2066 def appliedname(self, index):
2066 def appliedname(self, index):
2067 pname = self.applied[index].name
2067 pname = self.applied[index].name
2068 if not self.ui.verbose:
2068 if not self.ui.verbose:
2069 p = pname
2069 p = pname
2070 else:
2070 else:
2071 p = str(self.series.index(pname)) + " " + pname
2071 p = str(self.series.index(pname)) + " " + pname
2072 return p
2072 return p
2073
2073
2074 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
2074 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
2075 force=None, git=False):
2075 force=None, git=False):
2076 def checkseries(patchname):
2076 def checkseries(patchname):
2077 if patchname in self.series:
2077 if patchname in self.series:
2078 raise error.Abort(_('patch %s is already in the series file')
2078 raise error.Abort(_('patch %s is already in the series file')
2079 % patchname)
2079 % patchname)
2080
2080
2081 if rev:
2081 if rev:
2082 if files:
2082 if files:
2083 raise error.Abort(_('option "-r" not valid when importing '
2083 raise error.Abort(_('option "-r" not valid when importing '
2084 'files'))
2084 'files'))
2085 rev = scmutil.revrange(repo, rev)
2085 rev = scmutil.revrange(repo, rev)
2086 rev.sort(reverse=True)
2086 rev.sort(reverse=True)
2087 elif not files:
2087 elif not files:
2088 raise error.Abort(_('no files or revisions specified'))
2088 raise error.Abort(_('no files or revisions specified'))
2089 if (len(files) > 1 or len(rev) > 1) and patchname:
2089 if (len(files) > 1 or len(rev) > 1) and patchname:
2090 raise error.Abort(_('option "-n" not valid when importing multiple '
2090 raise error.Abort(_('option "-n" not valid when importing multiple '
2091 'patches'))
2091 'patches'))
2092 imported = []
2092 imported = []
2093 if rev:
2093 if rev:
2094 # If mq patches are applied, we can only import revisions
2094 # If mq patches are applied, we can only import revisions
2095 # that form a linear path to qbase.
2095 # that form a linear path to qbase.
2096 # Otherwise, they should form a linear path to a head.
2096 # Otherwise, they should form a linear path to a head.
2097 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2097 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2098 if len(heads) > 1:
2098 if len(heads) > 1:
2099 raise error.Abort(_('revision %d is the root of more than one '
2099 raise error.Abort(_('revision %d is the root of more than one '
2100 'branch') % rev.last())
2100 'branch') % rev.last())
2101 if self.applied:
2101 if self.applied:
2102 base = repo.changelog.node(rev.first())
2102 base = repo.changelog.node(rev.first())
2103 if base in [n.node for n in self.applied]:
2103 if base in [n.node for n in self.applied]:
2104 raise error.Abort(_('revision %d is already managed')
2104 raise error.Abort(_('revision %d is already managed')
2105 % rev.first())
2105 % rev.first())
2106 if heads != [self.applied[-1].node]:
2106 if heads != [self.applied[-1].node]:
2107 raise error.Abort(_('revision %d is not the parent of '
2107 raise error.Abort(_('revision %d is not the parent of '
2108 'the queue') % rev.first())
2108 'the queue') % rev.first())
2109 base = repo.changelog.rev(self.applied[0].node)
2109 base = repo.changelog.rev(self.applied[0].node)
2110 lastparent = repo.changelog.parentrevs(base)[0]
2110 lastparent = repo.changelog.parentrevs(base)[0]
2111 else:
2111 else:
2112 if heads != [repo.changelog.node(rev.first())]:
2112 if heads != [repo.changelog.node(rev.first())]:
2113 raise error.Abort(_('revision %d has unmanaged children')
2113 raise error.Abort(_('revision %d has unmanaged children')
2114 % rev.first())
2114 % rev.first())
2115 lastparent = None
2115 lastparent = None
2116
2116
2117 diffopts = self.diffopts({'git': git})
2117 diffopts = self.diffopts({'git': git})
2118 with repo.transaction('qimport') as tr:
2118 with repo.transaction('qimport') as tr:
2119 for r in rev:
2119 for r in rev:
2120 if not repo[r].mutable():
2120 if not repo[r].mutable():
2121 raise error.Abort(_('revision %d is not mutable') % r,
2121 raise error.Abort(_('revision %d is not mutable') % r,
2122 hint=_("see 'hg help phases' "
2122 hint=_("see 'hg help phases' "
2123 'for details'))
2123 'for details'))
2124 p1, p2 = repo.changelog.parentrevs(r)
2124 p1, p2 = repo.changelog.parentrevs(r)
2125 n = repo.changelog.node(r)
2125 n = repo.changelog.node(r)
2126 if p2 != nullrev:
2126 if p2 != nullrev:
2127 raise error.Abort(_('cannot import merge revision %d')
2127 raise error.Abort(_('cannot import merge revision %d')
2128 % r)
2128 % r)
2129 if lastparent and lastparent != r:
2129 if lastparent and lastparent != r:
2130 raise error.Abort(_('revision %d is not the parent of '
2130 raise error.Abort(_('revision %d is not the parent of '
2131 '%d')
2131 '%d')
2132 % (r, lastparent))
2132 % (r, lastparent))
2133 lastparent = p1
2133 lastparent = p1
2134
2134
2135 if not patchname:
2135 if not patchname:
2136 patchname = self.makepatchname(
2136 patchname = self.makepatchname(
2137 repo[r].description().split('\n', 1)[0],
2137 repo[r].description().split('\n', 1)[0],
2138 '%d.diff' % r)
2138 '%d.diff' % r)
2139 checkseries(patchname)
2139 checkseries(patchname)
2140 self.checkpatchname(patchname, force)
2140 self.checkpatchname(patchname, force)
2141 self.fullseries.insert(0, patchname)
2141 self.fullseries.insert(0, patchname)
2142
2142
2143 patchf = self.opener(patchname, "w")
2143 patchf = self.opener(patchname, "w")
2144 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
2144 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
2145 patchf.close()
2145 patchf.close()
2146
2146
2147 se = statusentry(n, patchname)
2147 se = statusentry(n, patchname)
2148 self.applied.insert(0, se)
2148 self.applied.insert(0, se)
2149
2149
2150 self.added.append(patchname)
2150 self.added.append(patchname)
2151 imported.append(patchname)
2151 imported.append(patchname)
2152 patchname = None
2152 patchname = None
2153 if rev and repo.ui.configbool('mq', 'secret', False):
2153 if rev and repo.ui.configbool('mq', 'secret', False):
2154 # if we added anything with --rev, move the secret root
2154 # if we added anything with --rev, move the secret root
2155 phases.retractboundary(repo, tr, phases.secret, [n])
2155 phases.retractboundary(repo, tr, phases.secret, [n])
2156 self.parseseries()
2156 self.parseseries()
2157 self.applieddirty = True
2157 self.applieddirty = True
2158 self.seriesdirty = True
2158 self.seriesdirty = True
2159
2159
2160 for i, filename in enumerate(files):
2160 for i, filename in enumerate(files):
2161 if existing:
2161 if existing:
2162 if filename == '-':
2162 if filename == '-':
2163 raise error.Abort(_('-e is incompatible with import from -')
2163 raise error.Abort(_('-e is incompatible with import from -')
2164 )
2164 )
2165 filename = normname(filename)
2165 filename = normname(filename)
2166 self.checkreservedname(filename)
2166 self.checkreservedname(filename)
2167 if util.url(filename).islocal():
2167 if util.url(filename).islocal():
2168 originpath = self.join(filename)
2168 originpath = self.join(filename)
2169 if not os.path.isfile(originpath):
2169 if not os.path.isfile(originpath):
2170 raise error.Abort(
2170 raise error.Abort(
2171 _("patch %s does not exist") % filename)
2171 _("patch %s does not exist") % filename)
2172
2172
2173 if patchname:
2173 if patchname:
2174 self.checkpatchname(patchname, force)
2174 self.checkpatchname(patchname, force)
2175
2175
2176 self.ui.write(_('renaming %s to %s\n')
2176 self.ui.write(_('renaming %s to %s\n')
2177 % (filename, patchname))
2177 % (filename, patchname))
2178 util.rename(originpath, self.join(patchname))
2178 util.rename(originpath, self.join(patchname))
2179 else:
2179 else:
2180 patchname = filename
2180 patchname = filename
2181
2181
2182 else:
2182 else:
2183 if filename == '-' and not patchname:
2183 if filename == '-' and not patchname:
2184 raise error.Abort(_('need --name to import a patch from -'))
2184 raise error.Abort(_('need --name to import a patch from -'))
2185 elif not patchname:
2185 elif not patchname:
2186 patchname = normname(os.path.basename(filename.rstrip('/')))
2186 patchname = normname(os.path.basename(filename.rstrip('/')))
2187 self.checkpatchname(patchname, force)
2187 self.checkpatchname(patchname, force)
2188 try:
2188 try:
2189 if filename == '-':
2189 if filename == '-':
2190 text = self.ui.fin.read()
2190 text = self.ui.fin.read()
2191 else:
2191 else:
2192 fp = hg.openpath(self.ui, filename)
2192 fp = hg.openpath(self.ui, filename)
2193 text = fp.read()
2193 text = fp.read()
2194 fp.close()
2194 fp.close()
2195 except (OSError, IOError):
2195 except (OSError, IOError):
2196 raise error.Abort(_("unable to read file %s") % filename)
2196 raise error.Abort(_("unable to read file %s") % filename)
2197 patchf = self.opener(patchname, "w")
2197 patchf = self.opener(patchname, "w")
2198 patchf.write(text)
2198 patchf.write(text)
2199 patchf.close()
2199 patchf.close()
2200 if not force:
2200 if not force:
2201 checkseries(patchname)
2201 checkseries(patchname)
2202 if patchname not in self.series:
2202 if patchname not in self.series:
2203 index = self.fullseriesend() + i
2203 index = self.fullseriesend() + i
2204 self.fullseries[index:index] = [patchname]
2204 self.fullseries[index:index] = [patchname]
2205 self.parseseries()
2205 self.parseseries()
2206 self.seriesdirty = True
2206 self.seriesdirty = True
2207 self.ui.warn(_("adding %s to series file\n") % patchname)
2207 self.ui.warn(_("adding %s to series file\n") % patchname)
2208 self.added.append(patchname)
2208 self.added.append(patchname)
2209 imported.append(patchname)
2209 imported.append(patchname)
2210 patchname = None
2210 patchname = None
2211
2211
2212 self.removeundo(repo)
2212 self.removeundo(repo)
2213 return imported
2213 return imported
2214
2214
2215 def fixkeepchangesopts(ui, opts):
2215 def fixkeepchangesopts(ui, opts):
2216 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2216 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2217 or opts.get('exact')):
2217 or opts.get('exact')):
2218 return opts
2218 return opts
2219 opts = dict(opts)
2219 opts = dict(opts)
2220 opts['keep_changes'] = True
2220 opts['keep_changes'] = True
2221 return opts
2221 return opts
2222
2222
2223 @command("qdelete|qremove|qrm",
2223 @command("qdelete|qremove|qrm",
2224 [('k', 'keep', None, _('keep patch file')),
2224 [('k', 'keep', None, _('keep patch file')),
2225 ('r', 'rev', [],
2225 ('r', 'rev', [],
2226 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2226 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2227 _('hg qdelete [-k] [PATCH]...'))
2227 _('hg qdelete [-k] [PATCH]...'))
2228 def delete(ui, repo, *patches, **opts):
2228 def delete(ui, repo, *patches, **opts):
2229 """remove patches from queue
2229 """remove patches from queue
2230
2230
2231 The patches must not be applied, and at least one patch is required. Exact
2231 The patches must not be applied, and at least one patch is required. Exact
2232 patch identifiers must be given. With -k/--keep, the patch files are
2232 patch identifiers must be given. With -k/--keep, the patch files are
2233 preserved in the patch directory.
2233 preserved in the patch directory.
2234
2234
2235 To stop managing a patch and move it into permanent history,
2235 To stop managing a patch and move it into permanent history,
2236 use the :hg:`qfinish` command."""
2236 use the :hg:`qfinish` command."""
2237 q = repo.mq
2237 q = repo.mq
2238 q.delete(repo, patches, opts)
2238 q.delete(repo, patches, opts)
2239 q.savedirty()
2239 q.savedirty()
2240 return 0
2240 return 0
2241
2241
2242 @command("qapplied",
2242 @command("qapplied",
2243 [('1', 'last', None, _('show only the preceding applied patch'))
2243 [('1', 'last', None, _('show only the preceding applied patch'))
2244 ] + seriesopts,
2244 ] + seriesopts,
2245 _('hg qapplied [-1] [-s] [PATCH]'))
2245 _('hg qapplied [-1] [-s] [PATCH]'))
2246 def applied(ui, repo, patch=None, **opts):
2246 def applied(ui, repo, patch=None, **opts):
2247 """print the patches already applied
2247 """print the patches already applied
2248
2248
2249 Returns 0 on success."""
2249 Returns 0 on success."""
2250
2250
2251 q = repo.mq
2251 q = repo.mq
2252
2252
2253 if patch:
2253 if patch:
2254 if patch not in q.series:
2254 if patch not in q.series:
2255 raise error.Abort(_("patch %s is not in series file") % patch)
2255 raise error.Abort(_("patch %s is not in series file") % patch)
2256 end = q.series.index(patch) + 1
2256 end = q.series.index(patch) + 1
2257 else:
2257 else:
2258 end = q.seriesend(True)
2258 end = q.seriesend(True)
2259
2259
2260 if opts.get('last') and not end:
2260 if opts.get('last') and not end:
2261 ui.write(_("no patches applied\n"))
2261 ui.write(_("no patches applied\n"))
2262 return 1
2262 return 1
2263 elif opts.get('last') and end == 1:
2263 elif opts.get('last') and end == 1:
2264 ui.write(_("only one patch applied\n"))
2264 ui.write(_("only one patch applied\n"))
2265 return 1
2265 return 1
2266 elif opts.get('last'):
2266 elif opts.get('last'):
2267 start = end - 2
2267 start = end - 2
2268 end = 1
2268 end = 1
2269 else:
2269 else:
2270 start = 0
2270 start = 0
2271
2271
2272 q.qseries(repo, length=end, start=start, status='A',
2272 q.qseries(repo, length=end, start=start, status='A',
2273 summary=opts.get('summary'))
2273 summary=opts.get('summary'))
2274
2274
2275
2275
2276 @command("qunapplied",
2276 @command("qunapplied",
2277 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2277 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2278 _('hg qunapplied [-1] [-s] [PATCH]'))
2278 _('hg qunapplied [-1] [-s] [PATCH]'))
2279 def unapplied(ui, repo, patch=None, **opts):
2279 def unapplied(ui, repo, patch=None, **opts):
2280 """print the patches not yet applied
2280 """print the patches not yet applied
2281
2281
2282 Returns 0 on success."""
2282 Returns 0 on success."""
2283
2283
2284 q = repo.mq
2284 q = repo.mq
2285 if patch:
2285 if patch:
2286 if patch not in q.series:
2286 if patch not in q.series:
2287 raise error.Abort(_("patch %s is not in series file") % patch)
2287 raise error.Abort(_("patch %s is not in series file") % patch)
2288 start = q.series.index(patch) + 1
2288 start = q.series.index(patch) + 1
2289 else:
2289 else:
2290 start = q.seriesend(True)
2290 start = q.seriesend(True)
2291
2291
2292 if start == len(q.series) and opts.get('first'):
2292 if start == len(q.series) and opts.get('first'):
2293 ui.write(_("all patches applied\n"))
2293 ui.write(_("all patches applied\n"))
2294 return 1
2294 return 1
2295
2295
2296 if opts.get('first'):
2296 if opts.get('first'):
2297 length = 1
2297 length = 1
2298 else:
2298 else:
2299 length = None
2299 length = None
2300 q.qseries(repo, start=start, length=length, status='U',
2300 q.qseries(repo, start=start, length=length, status='U',
2301 summary=opts.get('summary'))
2301 summary=opts.get('summary'))
2302
2302
2303 @command("qimport",
2303 @command("qimport",
2304 [('e', 'existing', None, _('import file in patch directory')),
2304 [('e', 'existing', None, _('import file in patch directory')),
2305 ('n', 'name', '',
2305 ('n', 'name', '',
2306 _('name of patch file'), _('NAME')),
2306 _('name of patch file'), _('NAME')),
2307 ('f', 'force', None, _('overwrite existing files')),
2307 ('f', 'force', None, _('overwrite existing files')),
2308 ('r', 'rev', [],
2308 ('r', 'rev', [],
2309 _('place existing revisions under mq control'), _('REV')),
2309 _('place existing revisions under mq control'), _('REV')),
2310 ('g', 'git', None, _('use git extended diff format')),
2310 ('g', 'git', None, _('use git extended diff format')),
2311 ('P', 'push', None, _('qpush after importing'))],
2311 ('P', 'push', None, _('qpush after importing'))],
2312 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2312 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2313 def qimport(ui, repo, *filename, **opts):
2313 def qimport(ui, repo, *filename, **opts):
2314 """import a patch or existing changeset
2314 """import a patch or existing changeset
2315
2315
2316 The patch is inserted into the series after the last applied
2316 The patch is inserted into the series after the last applied
2317 patch. If no patches have been applied, qimport prepends the patch
2317 patch. If no patches have been applied, qimport prepends the patch
2318 to the series.
2318 to the series.
2319
2319
2320 The patch will have the same name as its source file unless you
2320 The patch will have the same name as its source file unless you
2321 give it a new one with -n/--name.
2321 give it a new one with -n/--name.
2322
2322
2323 You can register an existing patch inside the patch directory with
2323 You can register an existing patch inside the patch directory with
2324 the -e/--existing flag.
2324 the -e/--existing flag.
2325
2325
2326 With -f/--force, an existing patch of the same name will be
2326 With -f/--force, an existing patch of the same name will be
2327 overwritten.
2327 overwritten.
2328
2328
2329 An existing changeset may be placed under mq control with -r/--rev
2329 An existing changeset may be placed under mq control with -r/--rev
2330 (e.g. qimport --rev . -n patch will place the current revision
2330 (e.g. qimport --rev . -n patch will place the current revision
2331 under mq control). With -g/--git, patches imported with --rev will
2331 under mq control). With -g/--git, patches imported with --rev will
2332 use the git diff format. See the diffs help topic for information
2332 use the git diff format. See the diffs help topic for information
2333 on why this is important for preserving rename/copy information
2333 on why this is important for preserving rename/copy information
2334 and permission changes. Use :hg:`qfinish` to remove changesets
2334 and permission changes. Use :hg:`qfinish` to remove changesets
2335 from mq control.
2335 from mq control.
2336
2336
2337 To import a patch from standard input, pass - as the patch file.
2337 To import a patch from standard input, pass - as the patch file.
2338 When importing from standard input, a patch name must be specified
2338 When importing from standard input, a patch name must be specified
2339 using the --name flag.
2339 using the --name flag.
2340
2340
2341 To import an existing patch while renaming it::
2341 To import an existing patch while renaming it::
2342
2342
2343 hg qimport -e existing-patch -n new-name
2343 hg qimport -e existing-patch -n new-name
2344
2344
2345 Returns 0 if import succeeded.
2345 Returns 0 if import succeeded.
2346 """
2346 """
2347 with repo.lock(): # cause this may move phase
2347 with repo.lock(): # cause this may move phase
2348 q = repo.mq
2348 q = repo.mq
2349 try:
2349 try:
2350 imported = q.qimport(
2350 imported = q.qimport(
2351 repo, filename, patchname=opts.get('name'),
2351 repo, filename, patchname=opts.get('name'),
2352 existing=opts.get('existing'), force=opts.get('force'),
2352 existing=opts.get('existing'), force=opts.get('force'),
2353 rev=opts.get('rev'), git=opts.get('git'))
2353 rev=opts.get('rev'), git=opts.get('git'))
2354 finally:
2354 finally:
2355 q.savedirty()
2355 q.savedirty()
2356
2356
2357 if imported and opts.get('push') and not opts.get('rev'):
2357 if imported and opts.get('push') and not opts.get('rev'):
2358 return q.push(repo, imported[-1])
2358 return q.push(repo, imported[-1])
2359 return 0
2359 return 0
2360
2360
2361 def qinit(ui, repo, create):
2361 def qinit(ui, repo, create):
2362 """initialize a new queue repository
2362 """initialize a new queue repository
2363
2363
2364 This command also creates a series file for ordering patches, and
2364 This command also creates a series file for ordering patches, and
2365 an mq-specific .hgignore file in the queue repository, to exclude
2365 an mq-specific .hgignore file in the queue repository, to exclude
2366 the status and guards files (these contain mostly transient state).
2366 the status and guards files (these contain mostly transient state).
2367
2367
2368 Returns 0 if initialization succeeded."""
2368 Returns 0 if initialization succeeded."""
2369 q = repo.mq
2369 q = repo.mq
2370 r = q.init(repo, create)
2370 r = q.init(repo, create)
2371 q.savedirty()
2371 q.savedirty()
2372 if r:
2372 if r:
2373 if not os.path.exists(r.wjoin('.hgignore')):
2373 if not os.path.exists(r.wjoin('.hgignore')):
2374 fp = r.wvfs('.hgignore', 'w')
2374 fp = r.wvfs('.hgignore', 'w')
2375 fp.write('^\\.hg\n')
2375 fp.write('^\\.hg\n')
2376 fp.write('^\\.mq\n')
2376 fp.write('^\\.mq\n')
2377 fp.write('syntax: glob\n')
2377 fp.write('syntax: glob\n')
2378 fp.write('status\n')
2378 fp.write('status\n')
2379 fp.write('guards\n')
2379 fp.write('guards\n')
2380 fp.close()
2380 fp.close()
2381 if not os.path.exists(r.wjoin('series')):
2381 if not os.path.exists(r.wjoin('series')):
2382 r.wvfs('series', 'w').close()
2382 r.wvfs('series', 'w').close()
2383 r[None].add(['.hgignore', 'series'])
2383 r[None].add(['.hgignore', 'series'])
2384 commands.add(ui, r)
2384 commands.add(ui, r)
2385 return 0
2385 return 0
2386
2386
2387 @command("^qinit",
2387 @command("^qinit",
2388 [('c', 'create-repo', None, _('create queue repository'))],
2388 [('c', 'create-repo', None, _('create queue repository'))],
2389 _('hg qinit [-c]'))
2389 _('hg qinit [-c]'))
2390 def init(ui, repo, **opts):
2390 def init(ui, repo, **opts):
2391 """init a new queue repository (DEPRECATED)
2391 """init a new queue repository (DEPRECATED)
2392
2392
2393 The queue repository is unversioned by default. If
2393 The queue repository is unversioned by default. If
2394 -c/--create-repo is specified, qinit will create a separate nested
2394 -c/--create-repo is specified, qinit will create a separate nested
2395 repository for patches (qinit -c may also be run later to convert
2395 repository for patches (qinit -c may also be run later to convert
2396 an unversioned patch repository into a versioned one). You can use
2396 an unversioned patch repository into a versioned one). You can use
2397 qcommit to commit changes to this queue repository.
2397 qcommit to commit changes to this queue repository.
2398
2398
2399 This command is deprecated. Without -c, it's implied by other relevant
2399 This command is deprecated. Without -c, it's implied by other relevant
2400 commands. With -c, use :hg:`init --mq` instead."""
2400 commands. With -c, use :hg:`init --mq` instead."""
2401 return qinit(ui, repo, create=opts.get('create_repo'))
2401 return qinit(ui, repo, create=opts.get('create_repo'))
2402
2402
2403 @command("qclone",
2403 @command("qclone",
2404 [('', 'pull', None, _('use pull protocol to copy metadata')),
2404 [('', 'pull', None, _('use pull protocol to copy metadata')),
2405 ('U', 'noupdate', None,
2405 ('U', 'noupdate', None,
2406 _('do not update the new working directories')),
2406 _('do not update the new working directories')),
2407 ('', 'uncompressed', None,
2407 ('', 'uncompressed', None,
2408 _('use uncompressed transfer (fast over LAN)')),
2408 _('use uncompressed transfer (fast over LAN)')),
2409 ('p', 'patches', '',
2409 ('p', 'patches', '',
2410 _('location of source patch repository'), _('REPO')),
2410 _('location of source patch repository'), _('REPO')),
2411 ] + commands.remoteopts,
2411 ] + commands.remoteopts,
2412 _('hg qclone [OPTION]... SOURCE [DEST]'),
2412 _('hg qclone [OPTION]... SOURCE [DEST]'),
2413 norepo=True)
2413 norepo=True)
2414 def clone(ui, source, dest=None, **opts):
2414 def clone(ui, source, dest=None, **opts):
2415 '''clone main and patch repository at same time
2415 '''clone main and patch repository at same time
2416
2416
2417 If source is local, destination will have no patches applied. If
2417 If source is local, destination will have no patches applied. If
2418 source is remote, this command can not check if patches are
2418 source is remote, this command can not check if patches are
2419 applied in source, so cannot guarantee that patches are not
2419 applied in source, so cannot guarantee that patches are not
2420 applied in destination. If you clone remote repository, be sure
2420 applied in destination. If you clone remote repository, be sure
2421 before that it has no patches applied.
2421 before that it has no patches applied.
2422
2422
2423 Source patch repository is looked for in <src>/.hg/patches by
2423 Source patch repository is looked for in <src>/.hg/patches by
2424 default. Use -p <url> to change.
2424 default. Use -p <url> to change.
2425
2425
2426 The patch directory must be a nested Mercurial repository, as
2426 The patch directory must be a nested Mercurial repository, as
2427 would be created by :hg:`init --mq`.
2427 would be created by :hg:`init --mq`.
2428
2428
2429 Return 0 on success.
2429 Return 0 on success.
2430 '''
2430 '''
2431 def patchdir(repo):
2431 def patchdir(repo):
2432 """compute a patch repo url from a repo object"""
2432 """compute a patch repo url from a repo object"""
2433 url = repo.url()
2433 url = repo.url()
2434 if url.endswith('/'):
2434 if url.endswith('/'):
2435 url = url[:-1]
2435 url = url[:-1]
2436 return url + '/.hg/patches'
2436 return url + '/.hg/patches'
2437
2437
2438 # main repo (destination and sources)
2438 # main repo (destination and sources)
2439 if dest is None:
2439 if dest is None:
2440 dest = hg.defaultdest(source)
2440 dest = hg.defaultdest(source)
2441 sr = hg.peer(ui, opts, ui.expandpath(source))
2441 sr = hg.peer(ui, opts, ui.expandpath(source))
2442
2442
2443 # patches repo (source only)
2443 # patches repo (source only)
2444 if opts.get('patches'):
2444 if opts.get('patches'):
2445 patchespath = ui.expandpath(opts.get('patches'))
2445 patchespath = ui.expandpath(opts.get('patches'))
2446 else:
2446 else:
2447 patchespath = patchdir(sr)
2447 patchespath = patchdir(sr)
2448 try:
2448 try:
2449 hg.peer(ui, opts, patchespath)
2449 hg.peer(ui, opts, patchespath)
2450 except error.RepoError:
2450 except error.RepoError:
2451 raise error.Abort(_('versioned patch repository not found'
2451 raise error.Abort(_('versioned patch repository not found'
2452 ' (see init --mq)'))
2452 ' (see init --mq)'))
2453 qbase, destrev = None, None
2453 qbase, destrev = None, None
2454 if sr.local():
2454 if sr.local():
2455 repo = sr.local()
2455 repo = sr.local()
2456 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2456 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2457 qbase = repo.mq.applied[0].node
2457 qbase = repo.mq.applied[0].node
2458 if not hg.islocal(dest):
2458 if not hg.islocal(dest):
2459 heads = set(repo.heads())
2459 heads = set(repo.heads())
2460 destrev = list(heads.difference(repo.heads(qbase)))
2460 destrev = list(heads.difference(repo.heads(qbase)))
2461 destrev.append(repo.changelog.parents(qbase)[0])
2461 destrev.append(repo.changelog.parents(qbase)[0])
2462 elif sr.capable('lookup'):
2462 elif sr.capable('lookup'):
2463 try:
2463 try:
2464 qbase = sr.lookup('qbase')
2464 qbase = sr.lookup('qbase')
2465 except error.RepoError:
2465 except error.RepoError:
2466 pass
2466 pass
2467
2467
2468 ui.note(_('cloning main repository\n'))
2468 ui.note(_('cloning main repository\n'))
2469 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2469 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2470 pull=opts.get('pull'),
2470 pull=opts.get('pull'),
2471 rev=destrev,
2471 rev=destrev,
2472 update=False,
2472 update=False,
2473 stream=opts.get('uncompressed'))
2473 stream=opts.get('uncompressed'))
2474
2474
2475 ui.note(_('cloning patch repository\n'))
2475 ui.note(_('cloning patch repository\n'))
2476 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2476 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2477 pull=opts.get('pull'), update=not opts.get('noupdate'),
2477 pull=opts.get('pull'), update=not opts.get('noupdate'),
2478 stream=opts.get('uncompressed'))
2478 stream=opts.get('uncompressed'))
2479
2479
2480 if dr.local():
2480 if dr.local():
2481 repo = dr.local()
2481 repo = dr.local()
2482 if qbase:
2482 if qbase:
2483 ui.note(_('stripping applied patches from destination '
2483 ui.note(_('stripping applied patches from destination '
2484 'repository\n'))
2484 'repository\n'))
2485 strip(ui, repo, [qbase], update=False, backup=None)
2485 strip(ui, repo, [qbase], update=False, backup=None)
2486 if not opts.get('noupdate'):
2486 if not opts.get('noupdate'):
2487 ui.note(_('updating destination repository\n'))
2487 ui.note(_('updating destination repository\n'))
2488 hg.update(repo, repo.changelog.tip())
2488 hg.update(repo, repo.changelog.tip())
2489
2489
2490 @command("qcommit|qci",
2490 @command("qcommit|qci",
2491 commands.table["^commit|ci"][1],
2491 commands.table["^commit|ci"][1],
2492 _('hg qcommit [OPTION]... [FILE]...'),
2492 _('hg qcommit [OPTION]... [FILE]...'),
2493 inferrepo=True)
2493 inferrepo=True)
2494 def commit(ui, repo, *pats, **opts):
2494 def commit(ui, repo, *pats, **opts):
2495 """commit changes in the queue repository (DEPRECATED)
2495 """commit changes in the queue repository (DEPRECATED)
2496
2496
2497 This command is deprecated; use :hg:`commit --mq` instead."""
2497 This command is deprecated; use :hg:`commit --mq` instead."""
2498 q = repo.mq
2498 q = repo.mq
2499 r = q.qrepo()
2499 r = q.qrepo()
2500 if not r:
2500 if not r:
2501 raise error.Abort('no queue repository')
2501 raise error.Abort('no queue repository')
2502 commands.commit(r.ui, r, *pats, **opts)
2502 commands.commit(r.ui, r, *pats, **opts)
2503
2503
2504 @command("qseries",
2504 @command("qseries",
2505 [('m', 'missing', None, _('print patches not in series')),
2505 [('m', 'missing', None, _('print patches not in series')),
2506 ] + seriesopts,
2506 ] + seriesopts,
2507 _('hg qseries [-ms]'))
2507 _('hg qseries [-ms]'))
2508 def series(ui, repo, **opts):
2508 def series(ui, repo, **opts):
2509 """print the entire series file
2509 """print the entire series file
2510
2510
2511 Returns 0 on success."""
2511 Returns 0 on success."""
2512 repo.mq.qseries(repo, missing=opts.get('missing'),
2512 repo.mq.qseries(repo, missing=opts.get('missing'),
2513 summary=opts.get('summary'))
2513 summary=opts.get('summary'))
2514 return 0
2514 return 0
2515
2515
2516 @command("qtop", seriesopts, _('hg qtop [-s]'))
2516 @command("qtop", seriesopts, _('hg qtop [-s]'))
2517 def top(ui, repo, **opts):
2517 def top(ui, repo, **opts):
2518 """print the name of the current patch
2518 """print the name of the current patch
2519
2519
2520 Returns 0 on success."""
2520 Returns 0 on success."""
2521 q = repo.mq
2521 q = repo.mq
2522 if q.applied:
2522 if q.applied:
2523 t = q.seriesend(True)
2523 t = q.seriesend(True)
2524 else:
2524 else:
2525 t = 0
2525 t = 0
2526
2526
2527 if t:
2527 if t:
2528 q.qseries(repo, start=t - 1, length=1, status='A',
2528 q.qseries(repo, start=t - 1, length=1, status='A',
2529 summary=opts.get('summary'))
2529 summary=opts.get('summary'))
2530 else:
2530 else:
2531 ui.write(_("no patches applied\n"))
2531 ui.write(_("no patches applied\n"))
2532 return 1
2532 return 1
2533
2533
2534 @command("qnext", seriesopts, _('hg qnext [-s]'))
2534 @command("qnext", seriesopts, _('hg qnext [-s]'))
2535 def next(ui, repo, **opts):
2535 def next(ui, repo, **opts):
2536 """print the name of the next pushable patch
2536 """print the name of the next pushable patch
2537
2537
2538 Returns 0 on success."""
2538 Returns 0 on success."""
2539 q = repo.mq
2539 q = repo.mq
2540 end = q.seriesend()
2540 end = q.seriesend()
2541 if end == len(q.series):
2541 if end == len(q.series):
2542 ui.write(_("all patches applied\n"))
2542 ui.write(_("all patches applied\n"))
2543 return 1
2543 return 1
2544 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2544 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2545
2545
2546 @command("qprev", seriesopts, _('hg qprev [-s]'))
2546 @command("qprev", seriesopts, _('hg qprev [-s]'))
2547 def prev(ui, repo, **opts):
2547 def prev(ui, repo, **opts):
2548 """print the name of the preceding applied patch
2548 """print the name of the preceding applied patch
2549
2549
2550 Returns 0 on success."""
2550 Returns 0 on success."""
2551 q = repo.mq
2551 q = repo.mq
2552 l = len(q.applied)
2552 l = len(q.applied)
2553 if l == 1:
2553 if l == 1:
2554 ui.write(_("only one patch applied\n"))
2554 ui.write(_("only one patch applied\n"))
2555 return 1
2555 return 1
2556 if not l:
2556 if not l:
2557 ui.write(_("no patches applied\n"))
2557 ui.write(_("no patches applied\n"))
2558 return 1
2558 return 1
2559 idx = q.series.index(q.applied[-2].name)
2559 idx = q.series.index(q.applied[-2].name)
2560 q.qseries(repo, start=idx, length=1, status='A',
2560 q.qseries(repo, start=idx, length=1, status='A',
2561 summary=opts.get('summary'))
2561 summary=opts.get('summary'))
2562
2562
2563 def setupheaderopts(ui, opts):
2563 def setupheaderopts(ui, opts):
2564 if not opts.get('user') and opts.get('currentuser'):
2564 if not opts.get('user') and opts.get('currentuser'):
2565 opts['user'] = ui.username()
2565 opts['user'] = ui.username()
2566 if not opts.get('date') and opts.get('currentdate'):
2566 if not opts.get('date') and opts.get('currentdate'):
2567 opts['date'] = "%d %d" % util.makedate()
2567 opts['date'] = "%d %d" % util.makedate()
2568
2568
2569 @command("^qnew",
2569 @command("^qnew",
2570 [('e', 'edit', None, _('invoke editor on commit messages')),
2570 [('e', 'edit', None, _('invoke editor on commit messages')),
2571 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2571 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2572 ('g', 'git', None, _('use git extended diff format')),
2572 ('g', 'git', None, _('use git extended diff format')),
2573 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2573 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2574 ('u', 'user', '',
2574 ('u', 'user', '',
2575 _('add "From: <USER>" to patch'), _('USER')),
2575 _('add "From: <USER>" to patch'), _('USER')),
2576 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2576 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2577 ('d', 'date', '',
2577 ('d', 'date', '',
2578 _('add "Date: <DATE>" to patch'), _('DATE'))
2578 _('add "Date: <DATE>" to patch'), _('DATE'))
2579 ] + commands.walkopts + commands.commitopts,
2579 ] + commands.walkopts + commands.commitopts,
2580 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
2580 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
2581 inferrepo=True)
2581 inferrepo=True)
2582 def new(ui, repo, patch, *args, **opts):
2582 def new(ui, repo, patch, *args, **opts):
2583 """create a new patch
2583 """create a new patch
2584
2584
2585 qnew creates a new patch on top of the currently-applied patch (if
2585 qnew creates a new patch on top of the currently-applied patch (if
2586 any). The patch will be initialized with any outstanding changes
2586 any). The patch will be initialized with any outstanding changes
2587 in the working directory. You may also use -I/--include,
2587 in the working directory. You may also use -I/--include,
2588 -X/--exclude, and/or a list of files after the patch name to add
2588 -X/--exclude, and/or a list of files after the patch name to add
2589 only changes to matching files to the new patch, leaving the rest
2589 only changes to matching files to the new patch, leaving the rest
2590 as uncommitted modifications.
2590 as uncommitted modifications.
2591
2591
2592 -u/--user and -d/--date can be used to set the (given) user and
2592 -u/--user and -d/--date can be used to set the (given) user and
2593 date, respectively. -U/--currentuser and -D/--currentdate set user
2593 date, respectively. -U/--currentuser and -D/--currentdate set user
2594 to current user and date to current date.
2594 to current user and date to current date.
2595
2595
2596 -e/--edit, -m/--message or -l/--logfile set the patch header as
2596 -e/--edit, -m/--message or -l/--logfile set the patch header as
2597 well as the commit message. If none is specified, the header is
2597 well as the commit message. If none is specified, the header is
2598 empty and the commit message is '[mq]: PATCH'.
2598 empty and the commit message is '[mq]: PATCH'.
2599
2599
2600 Use the -g/--git option to keep the patch in the git extended diff
2600 Use the -g/--git option to keep the patch in the git extended diff
2601 format. Read the diffs help topic for more information on why this
2601 format. Read the diffs help topic for more information on why this
2602 is important for preserving permission changes and copy/rename
2602 is important for preserving permission changes and copy/rename
2603 information.
2603 information.
2604
2604
2605 Returns 0 on successful creation of a new patch.
2605 Returns 0 on successful creation of a new patch.
2606 """
2606 """
2607 msg = cmdutil.logmessage(ui, opts)
2607 msg = cmdutil.logmessage(ui, opts)
2608 q = repo.mq
2608 q = repo.mq
2609 opts['msg'] = msg
2609 opts['msg'] = msg
2610 setupheaderopts(ui, opts)
2610 setupheaderopts(ui, opts)
2611 q.new(repo, patch, *args, **opts)
2611 q.new(repo, patch, *args, **opts)
2612 q.savedirty()
2612 q.savedirty()
2613 return 0
2613 return 0
2614
2614
2615 @command("^qrefresh",
2615 @command("^qrefresh",
2616 [('e', 'edit', None, _('invoke editor on commit messages')),
2616 [('e', 'edit', None, _('invoke editor on commit messages')),
2617 ('g', 'git', None, _('use git extended diff format')),
2617 ('g', 'git', None, _('use git extended diff format')),
2618 ('s', 'short', None,
2618 ('s', 'short', None,
2619 _('refresh only files already in the patch and specified files')),
2619 _('refresh only files already in the patch and specified files')),
2620 ('U', 'currentuser', None,
2620 ('U', 'currentuser', None,
2621 _('add/update author field in patch with current user')),
2621 _('add/update author field in patch with current user')),
2622 ('u', 'user', '',
2622 ('u', 'user', '',
2623 _('add/update author field in patch with given user'), _('USER')),
2623 _('add/update author field in patch with given user'), _('USER')),
2624 ('D', 'currentdate', None,
2624 ('D', 'currentdate', None,
2625 _('add/update date field in patch with current date')),
2625 _('add/update date field in patch with current date')),
2626 ('d', 'date', '',
2626 ('d', 'date', '',
2627 _('add/update date field in patch with given date'), _('DATE'))
2627 _('add/update date field in patch with given date'), _('DATE'))
2628 ] + commands.walkopts + commands.commitopts,
2628 ] + commands.walkopts + commands.commitopts,
2629 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2629 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2630 inferrepo=True)
2630 inferrepo=True)
2631 def refresh(ui, repo, *pats, **opts):
2631 def refresh(ui, repo, *pats, **opts):
2632 """update the current patch
2632 """update the current patch
2633
2633
2634 If any file patterns are provided, the refreshed patch will
2634 If any file patterns are provided, the refreshed patch will
2635 contain only the modifications that match those patterns; the
2635 contain only the modifications that match those patterns; the
2636 remaining modifications will remain in the working directory.
2636 remaining modifications will remain in the working directory.
2637
2637
2638 If -s/--short is specified, files currently included in the patch
2638 If -s/--short is specified, files currently included in the patch
2639 will be refreshed just like matched files and remain in the patch.
2639 will be refreshed just like matched files and remain in the patch.
2640
2640
2641 If -e/--edit is specified, Mercurial will start your configured editor for
2641 If -e/--edit is specified, Mercurial will start your configured editor for
2642 you to enter a message. In case qrefresh fails, you will find a backup of
2642 you to enter a message. In case qrefresh fails, you will find a backup of
2643 your message in ``.hg/last-message.txt``.
2643 your message in ``.hg/last-message.txt``.
2644
2644
2645 hg add/remove/copy/rename work as usual, though you might want to
2645 hg add/remove/copy/rename work as usual, though you might want to
2646 use git-style patches (-g/--git or [diff] git=1) to track copies
2646 use git-style patches (-g/--git or [diff] git=1) to track copies
2647 and renames. See the diffs help topic for more information on the
2647 and renames. See the diffs help topic for more information on the
2648 git diff format.
2648 git diff format.
2649
2649
2650 Returns 0 on success.
2650 Returns 0 on success.
2651 """
2651 """
2652 q = repo.mq
2652 q = repo.mq
2653 message = cmdutil.logmessage(ui, opts)
2653 message = cmdutil.logmessage(ui, opts)
2654 setupheaderopts(ui, opts)
2654 setupheaderopts(ui, opts)
2655 with repo.wlock():
2655 with repo.wlock():
2656 ret = q.refresh(repo, pats, msg=message, **opts)
2656 ret = q.refresh(repo, pats, msg=message, **opts)
2657 q.savedirty()
2657 q.savedirty()
2658 return ret
2658 return ret
2659
2659
2660 @command("^qdiff",
2660 @command("^qdiff",
2661 commands.diffopts + commands.diffopts2 + commands.walkopts,
2661 commands.diffopts + commands.diffopts2 + commands.walkopts,
2662 _('hg qdiff [OPTION]... [FILE]...'),
2662 _('hg qdiff [OPTION]... [FILE]...'),
2663 inferrepo=True)
2663 inferrepo=True)
2664 def diff(ui, repo, *pats, **opts):
2664 def diff(ui, repo, *pats, **opts):
2665 """diff of the current patch and subsequent modifications
2665 """diff of the current patch and subsequent modifications
2666
2666
2667 Shows a diff which includes the current patch as well as any
2667 Shows a diff which includes the current patch as well as any
2668 changes which have been made in the working directory since the
2668 changes which have been made in the working directory since the
2669 last refresh (thus showing what the current patch would become
2669 last refresh (thus showing what the current patch would become
2670 after a qrefresh).
2670 after a qrefresh).
2671
2671
2672 Use :hg:`diff` if you only want to see the changes made since the
2672 Use :hg:`diff` if you only want to see the changes made since the
2673 last qrefresh, or :hg:`export qtip` if you want to see changes
2673 last qrefresh, or :hg:`export qtip` if you want to see changes
2674 made by the current patch without including changes made since the
2674 made by the current patch without including changes made since the
2675 qrefresh.
2675 qrefresh.
2676
2676
2677 Returns 0 on success.
2677 Returns 0 on success.
2678 """
2678 """
2679 repo.mq.diff(repo, pats, opts)
2679 repo.mq.diff(repo, pats, opts)
2680 return 0
2680 return 0
2681
2681
2682 @command('qfold',
2682 @command('qfold',
2683 [('e', 'edit', None, _('invoke editor on commit messages')),
2683 [('e', 'edit', None, _('invoke editor on commit messages')),
2684 ('k', 'keep', None, _('keep folded patch files')),
2684 ('k', 'keep', None, _('keep folded patch files')),
2685 ] + commands.commitopts,
2685 ] + commands.commitopts,
2686 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2686 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2687 def fold(ui, repo, *files, **opts):
2687 def fold(ui, repo, *files, **opts):
2688 """fold the named patches into the current patch
2688 """fold the named patches into the current patch
2689
2689
2690 Patches must not yet be applied. Each patch will be successively
2690 Patches must not yet be applied. Each patch will be successively
2691 applied to the current patch in the order given. If all the
2691 applied to the current patch in the order given. If all the
2692 patches apply successfully, the current patch will be refreshed
2692 patches apply successfully, the current patch will be refreshed
2693 with the new cumulative patch, and the folded patches will be
2693 with the new cumulative patch, and the folded patches will be
2694 deleted. With -k/--keep, the folded patch files will not be
2694 deleted. With -k/--keep, the folded patch files will not be
2695 removed afterwards.
2695 removed afterwards.
2696
2696
2697 The header for each folded patch will be concatenated with the
2697 The header for each folded patch will be concatenated with the
2698 current patch header, separated by a line of ``* * *``.
2698 current patch header, separated by a line of ``* * *``.
2699
2699
2700 Returns 0 on success."""
2700 Returns 0 on success."""
2701 q = repo.mq
2701 q = repo.mq
2702 if not files:
2702 if not files:
2703 raise error.Abort(_('qfold requires at least one patch name'))
2703 raise error.Abort(_('qfold requires at least one patch name'))
2704 if not q.checktoppatch(repo)[0]:
2704 if not q.checktoppatch(repo)[0]:
2705 raise error.Abort(_('no patches applied'))
2705 raise error.Abort(_('no patches applied'))
2706 q.checklocalchanges(repo)
2706 q.checklocalchanges(repo)
2707
2707
2708 message = cmdutil.logmessage(ui, opts)
2708 message = cmdutil.logmessage(ui, opts)
2709
2709
2710 parent = q.lookup('qtip')
2710 parent = q.lookup('qtip')
2711 patches = []
2711 patches = []
2712 messages = []
2712 messages = []
2713 for f in files:
2713 for f in files:
2714 p = q.lookup(f)
2714 p = q.lookup(f)
2715 if p in patches or p == parent:
2715 if p in patches or p == parent:
2716 ui.warn(_('skipping already folded patch %s\n') % p)
2716 ui.warn(_('skipping already folded patch %s\n') % p)
2717 if q.isapplied(p):
2717 if q.isapplied(p):
2718 raise error.Abort(_('qfold cannot fold already applied patch %s')
2718 raise error.Abort(_('qfold cannot fold already applied patch %s')
2719 % p)
2719 % p)
2720 patches.append(p)
2720 patches.append(p)
2721
2721
2722 for p in patches:
2722 for p in patches:
2723 if not message:
2723 if not message:
2724 ph = patchheader(q.join(p), q.plainmode)
2724 ph = patchheader(q.join(p), q.plainmode)
2725 if ph.message:
2725 if ph.message:
2726 messages.append(ph.message)
2726 messages.append(ph.message)
2727 pf = q.join(p)
2727 pf = q.join(p)
2728 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2728 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2729 if not patchsuccess:
2729 if not patchsuccess:
2730 raise error.Abort(_('error folding patch %s') % p)
2730 raise error.Abort(_('error folding patch %s') % p)
2731
2731
2732 if not message:
2732 if not message:
2733 ph = patchheader(q.join(parent), q.plainmode)
2733 ph = patchheader(q.join(parent), q.plainmode)
2734 message = ph.message
2734 message = ph.message
2735 for msg in messages:
2735 for msg in messages:
2736 if msg:
2736 if msg:
2737 if message:
2737 if message:
2738 message.append('* * *')
2738 message.append('* * *')
2739 message.extend(msg)
2739 message.extend(msg)
2740 message = '\n'.join(message)
2740 message = '\n'.join(message)
2741
2741
2742 diffopts = q.patchopts(q.diffopts(), *patches)
2742 diffopts = q.patchopts(q.diffopts(), *patches)
2743 with repo.wlock():
2743 with repo.wlock():
2744 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
2744 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
2745 editform='mq.qfold')
2745 editform='mq.qfold')
2746 q.delete(repo, patches, opts)
2746 q.delete(repo, patches, opts)
2747 q.savedirty()
2747 q.savedirty()
2748
2748
2749 @command("qgoto",
2749 @command("qgoto",
2750 [('', 'keep-changes', None,
2750 [('', 'keep-changes', None,
2751 _('tolerate non-conflicting local changes')),
2751 _('tolerate non-conflicting local changes')),
2752 ('f', 'force', None, _('overwrite any local changes')),
2752 ('f', 'force', None, _('overwrite any local changes')),
2753 ('', 'no-backup', None, _('do not save backup copies of files'))],
2753 ('', 'no-backup', None, _('do not save backup copies of files'))],
2754 _('hg qgoto [OPTION]... PATCH'))
2754 _('hg qgoto [OPTION]... PATCH'))
2755 def goto(ui, repo, patch, **opts):
2755 def goto(ui, repo, patch, **opts):
2756 '''push or pop patches until named patch is at top of stack
2756 '''push or pop patches until named patch is at top of stack
2757
2757
2758 Returns 0 on success.'''
2758 Returns 0 on success.'''
2759 opts = fixkeepchangesopts(ui, opts)
2759 opts = fixkeepchangesopts(ui, opts)
2760 q = repo.mq
2760 q = repo.mq
2761 patch = q.lookup(patch)
2761 patch = q.lookup(patch)
2762 nobackup = opts.get('no_backup')
2762 nobackup = opts.get('no_backup')
2763 keepchanges = opts.get('keep_changes')
2763 keepchanges = opts.get('keep_changes')
2764 if q.isapplied(patch):
2764 if q.isapplied(patch):
2765 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2765 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2766 keepchanges=keepchanges)
2766 keepchanges=keepchanges)
2767 else:
2767 else:
2768 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2768 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2769 keepchanges=keepchanges)
2769 keepchanges=keepchanges)
2770 q.savedirty()
2770 q.savedirty()
2771 return ret
2771 return ret
2772
2772
2773 @command("qguard",
2773 @command("qguard",
2774 [('l', 'list', None, _('list all patches and guards')),
2774 [('l', 'list', None, _('list all patches and guards')),
2775 ('n', 'none', None, _('drop all guards'))],
2775 ('n', 'none', None, _('drop all guards'))],
2776 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2776 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2777 def guard(ui, repo, *args, **opts):
2777 def guard(ui, repo, *args, **opts):
2778 '''set or print guards for a patch
2778 '''set or print guards for a patch
2779
2779
2780 Guards control whether a patch can be pushed. A patch with no
2780 Guards control whether a patch can be pushed. A patch with no
2781 guards is always pushed. A patch with a positive guard ("+foo") is
2781 guards is always pushed. A patch with a positive guard ("+foo") is
2782 pushed only if the :hg:`qselect` command has activated it. A patch with
2782 pushed only if the :hg:`qselect` command has activated it. A patch with
2783 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2783 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2784 has activated it.
2784 has activated it.
2785
2785
2786 With no arguments, print the currently active guards.
2786 With no arguments, print the currently active guards.
2787 With arguments, set guards for the named patch.
2787 With arguments, set guards for the named patch.
2788
2788
2789 .. note::
2789 .. note::
2790
2790
2791 Specifying negative guards now requires '--'.
2791 Specifying negative guards now requires '--'.
2792
2792
2793 To set guards on another patch::
2793 To set guards on another patch::
2794
2794
2795 hg qguard other.patch -- +2.6.17 -stable
2795 hg qguard other.patch -- +2.6.17 -stable
2796
2796
2797 Returns 0 on success.
2797 Returns 0 on success.
2798 '''
2798 '''
2799 def status(idx):
2799 def status(idx):
2800 guards = q.seriesguards[idx] or ['unguarded']
2800 guards = q.seriesguards[idx] or ['unguarded']
2801 if q.series[idx] in applied:
2801 if q.series[idx] in applied:
2802 state = 'applied'
2802 state = 'applied'
2803 elif q.pushable(idx)[0]:
2803 elif q.pushable(idx)[0]:
2804 state = 'unapplied'
2804 state = 'unapplied'
2805 else:
2805 else:
2806 state = 'guarded'
2806 state = 'guarded'
2807 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2807 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2808 ui.write('%s: ' % ui.label(q.series[idx], label))
2808 ui.write('%s: ' % ui.label(q.series[idx], label))
2809
2809
2810 for i, guard in enumerate(guards):
2810 for i, guard in enumerate(guards):
2811 if guard.startswith('+'):
2811 if guard.startswith('+'):
2812 ui.write(guard, label='qguard.positive')
2812 ui.write(guard, label='qguard.positive')
2813 elif guard.startswith('-'):
2813 elif guard.startswith('-'):
2814 ui.write(guard, label='qguard.negative')
2814 ui.write(guard, label='qguard.negative')
2815 else:
2815 else:
2816 ui.write(guard, label='qguard.unguarded')
2816 ui.write(guard, label='qguard.unguarded')
2817 if i != len(guards) - 1:
2817 if i != len(guards) - 1:
2818 ui.write(' ')
2818 ui.write(' ')
2819 ui.write('\n')
2819 ui.write('\n')
2820 q = repo.mq
2820 q = repo.mq
2821 applied = set(p.name for p in q.applied)
2821 applied = set(p.name for p in q.applied)
2822 patch = None
2822 patch = None
2823 args = list(args)
2823 args = list(args)
2824 if opts.get('list'):
2824 if opts.get('list'):
2825 if args or opts.get('none'):
2825 if args or opts.get('none'):
2826 raise error.Abort(_('cannot mix -l/--list with options or '
2826 raise error.Abort(_('cannot mix -l/--list with options or '
2827 'arguments'))
2827 'arguments'))
2828 for i in xrange(len(q.series)):
2828 for i in xrange(len(q.series)):
2829 status(i)
2829 status(i)
2830 return
2830 return
2831 if not args or args[0][0:1] in '-+':
2831 if not args or args[0][0:1] in '-+':
2832 if not q.applied:
2832 if not q.applied:
2833 raise error.Abort(_('no patches applied'))
2833 raise error.Abort(_('no patches applied'))
2834 patch = q.applied[-1].name
2834 patch = q.applied[-1].name
2835 if patch is None and args[0][0:1] not in '-+':
2835 if patch is None and args[0][0:1] not in '-+':
2836 patch = args.pop(0)
2836 patch = args.pop(0)
2837 if patch is None:
2837 if patch is None:
2838 raise error.Abort(_('no patch to work with'))
2838 raise error.Abort(_('no patch to work with'))
2839 if args or opts.get('none'):
2839 if args or opts.get('none'):
2840 idx = q.findseries(patch)
2840 idx = q.findseries(patch)
2841 if idx is None:
2841 if idx is None:
2842 raise error.Abort(_('no patch named %s') % patch)
2842 raise error.Abort(_('no patch named %s') % patch)
2843 q.setguards(idx, args)
2843 q.setguards(idx, args)
2844 q.savedirty()
2844 q.savedirty()
2845 else:
2845 else:
2846 status(q.series.index(q.lookup(patch)))
2846 status(q.series.index(q.lookup(patch)))
2847
2847
2848 @command("qheader", [], _('hg qheader [PATCH]'))
2848 @command("qheader", [], _('hg qheader [PATCH]'))
2849 def header(ui, repo, patch=None):
2849 def header(ui, repo, patch=None):
2850 """print the header of the topmost or specified patch
2850 """print the header of the topmost or specified patch
2851
2851
2852 Returns 0 on success."""
2852 Returns 0 on success."""
2853 q = repo.mq
2853 q = repo.mq
2854
2854
2855 if patch:
2855 if patch:
2856 patch = q.lookup(patch)
2856 patch = q.lookup(patch)
2857 else:
2857 else:
2858 if not q.applied:
2858 if not q.applied:
2859 ui.write(_('no patches applied\n'))
2859 ui.write(_('no patches applied\n'))
2860 return 1
2860 return 1
2861 patch = q.lookup('qtip')
2861 patch = q.lookup('qtip')
2862 ph = patchheader(q.join(patch), q.plainmode)
2862 ph = patchheader(q.join(patch), q.plainmode)
2863
2863
2864 ui.write('\n'.join(ph.message) + '\n')
2864 ui.write('\n'.join(ph.message) + '\n')
2865
2865
2866 def lastsavename(path):
2866 def lastsavename(path):
2867 (directory, base) = os.path.split(path)
2867 (directory, base) = os.path.split(path)
2868 names = os.listdir(directory)
2868 names = os.listdir(directory)
2869 namere = re.compile("%s.([0-9]+)" % base)
2869 namere = re.compile("%s.([0-9]+)" % base)
2870 maxindex = None
2870 maxindex = None
2871 maxname = None
2871 maxname = None
2872 for f in names:
2872 for f in names:
2873 m = namere.match(f)
2873 m = namere.match(f)
2874 if m:
2874 if m:
2875 index = int(m.group(1))
2875 index = int(m.group(1))
2876 if maxindex is None or index > maxindex:
2876 if maxindex is None or index > maxindex:
2877 maxindex = index
2877 maxindex = index
2878 maxname = f
2878 maxname = f
2879 if maxname:
2879 if maxname:
2880 return (os.path.join(directory, maxname), maxindex)
2880 return (os.path.join(directory, maxname), maxindex)
2881 return (None, None)
2881 return (None, None)
2882
2882
2883 def savename(path):
2883 def savename(path):
2884 (last, index) = lastsavename(path)
2884 (last, index) = lastsavename(path)
2885 if last is None:
2885 if last is None:
2886 index = 0
2886 index = 0
2887 newpath = path + ".%d" % (index + 1)
2887 newpath = path + ".%d" % (index + 1)
2888 return newpath
2888 return newpath
2889
2889
2890 @command("^qpush",
2890 @command("^qpush",
2891 [('', 'keep-changes', None,
2891 [('', 'keep-changes', None,
2892 _('tolerate non-conflicting local changes')),
2892 _('tolerate non-conflicting local changes')),
2893 ('f', 'force', None, _('apply on top of local changes')),
2893 ('f', 'force', None, _('apply on top of local changes')),
2894 ('e', 'exact', None,
2894 ('e', 'exact', None,
2895 _('apply the target patch to its recorded parent')),
2895 _('apply the target patch to its recorded parent')),
2896 ('l', 'list', None, _('list patch name in commit text')),
2896 ('l', 'list', None, _('list patch name in commit text')),
2897 ('a', 'all', None, _('apply all patches')),
2897 ('a', 'all', None, _('apply all patches')),
2898 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2898 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2899 ('n', 'name', '',
2899 ('n', 'name', '',
2900 _('merge queue name (DEPRECATED)'), _('NAME')),
2900 _('merge queue name (DEPRECATED)'), _('NAME')),
2901 ('', 'move', None,
2901 ('', 'move', None,
2902 _('reorder patch series and apply only the patch')),
2902 _('reorder patch series and apply only the patch')),
2903 ('', 'no-backup', None, _('do not save backup copies of files'))],
2903 ('', 'no-backup', None, _('do not save backup copies of files'))],
2904 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2904 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2905 def push(ui, repo, patch=None, **opts):
2905 def push(ui, repo, patch=None, **opts):
2906 """push the next patch onto the stack
2906 """push the next patch onto the stack
2907
2907
2908 By default, abort if the working directory contains uncommitted
2908 By default, abort if the working directory contains uncommitted
2909 changes. With --keep-changes, abort only if the uncommitted files
2909 changes. With --keep-changes, abort only if the uncommitted files
2910 overlap with patched files. With -f/--force, backup and patch over
2910 overlap with patched files. With -f/--force, backup and patch over
2911 uncommitted changes.
2911 uncommitted changes.
2912
2912
2913 Return 0 on success.
2913 Return 0 on success.
2914 """
2914 """
2915 q = repo.mq
2915 q = repo.mq
2916 mergeq = None
2916 mergeq = None
2917
2917
2918 opts = fixkeepchangesopts(ui, opts)
2918 opts = fixkeepchangesopts(ui, opts)
2919 if opts.get('merge'):
2919 if opts.get('merge'):
2920 if opts.get('name'):
2920 if opts.get('name'):
2921 newpath = repo.join(opts.get('name'))
2921 newpath = repo.join(opts.get('name'))
2922 else:
2922 else:
2923 newpath, i = lastsavename(q.path)
2923 newpath, i = lastsavename(q.path)
2924 if not newpath:
2924 if not newpath:
2925 ui.warn(_("no saved queues found, please use -n\n"))
2925 ui.warn(_("no saved queues found, please use -n\n"))
2926 return 1
2926 return 1
2927 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2927 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2928 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2928 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2929 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2929 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2930 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2930 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2931 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2931 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2932 keepchanges=opts.get('keep_changes'))
2932 keepchanges=opts.get('keep_changes'))
2933 return ret
2933 return ret
2934
2934
2935 @command("^qpop",
2935 @command("^qpop",
2936 [('a', 'all', None, _('pop all patches')),
2936 [('a', 'all', None, _('pop all patches')),
2937 ('n', 'name', '',
2937 ('n', 'name', '',
2938 _('queue name to pop (DEPRECATED)'), _('NAME')),
2938 _('queue name to pop (DEPRECATED)'), _('NAME')),
2939 ('', 'keep-changes', None,
2939 ('', 'keep-changes', None,
2940 _('tolerate non-conflicting local changes')),
2940 _('tolerate non-conflicting local changes')),
2941 ('f', 'force', None, _('forget any local changes to patched files')),
2941 ('f', 'force', None, _('forget any local changes to patched files')),
2942 ('', 'no-backup', None, _('do not save backup copies of files'))],
2942 ('', 'no-backup', None, _('do not save backup copies of files'))],
2943 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2943 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2944 def pop(ui, repo, patch=None, **opts):
2944 def pop(ui, repo, patch=None, **opts):
2945 """pop the current patch off the stack
2945 """pop the current patch off the stack
2946
2946
2947 Without argument, pops off the top of the patch stack. If given a
2947 Without argument, pops off the top of the patch stack. If given a
2948 patch name, keeps popping off patches until the named patch is at
2948 patch name, keeps popping off patches until the named patch is at
2949 the top of the stack.
2949 the top of the stack.
2950
2950
2951 By default, abort if the working directory contains uncommitted
2951 By default, abort if the working directory contains uncommitted
2952 changes. With --keep-changes, abort only if the uncommitted files
2952 changes. With --keep-changes, abort only if the uncommitted files
2953 overlap with patched files. With -f/--force, backup and discard
2953 overlap with patched files. With -f/--force, backup and discard
2954 changes made to such files.
2954 changes made to such files.
2955
2955
2956 Return 0 on success.
2956 Return 0 on success.
2957 """
2957 """
2958 opts = fixkeepchangesopts(ui, opts)
2958 opts = fixkeepchangesopts(ui, opts)
2959 localupdate = True
2959 localupdate = True
2960 if opts.get('name'):
2960 if opts.get('name'):
2961 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2961 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2962 ui.warn(_('using patch queue: %s\n') % q.path)
2962 ui.warn(_('using patch queue: %s\n') % q.path)
2963 localupdate = False
2963 localupdate = False
2964 else:
2964 else:
2965 q = repo.mq
2965 q = repo.mq
2966 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2966 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2967 all=opts.get('all'), nobackup=opts.get('no_backup'),
2967 all=opts.get('all'), nobackup=opts.get('no_backup'),
2968 keepchanges=opts.get('keep_changes'))
2968 keepchanges=opts.get('keep_changes'))
2969 q.savedirty()
2969 q.savedirty()
2970 return ret
2970 return ret
2971
2971
2972 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2972 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2973 def rename(ui, repo, patch, name=None, **opts):
2973 def rename(ui, repo, patch, name=None, **opts):
2974 """rename a patch
2974 """rename a patch
2975
2975
2976 With one argument, renames the current patch to PATCH1.
2976 With one argument, renames the current patch to PATCH1.
2977 With two arguments, renames PATCH1 to PATCH2.
2977 With two arguments, renames PATCH1 to PATCH2.
2978
2978
2979 Returns 0 on success."""
2979 Returns 0 on success."""
2980 q = repo.mq
2980 q = repo.mq
2981 if not name:
2981 if not name:
2982 name = patch
2982 name = patch
2983 patch = None
2983 patch = None
2984
2984
2985 if patch:
2985 if patch:
2986 patch = q.lookup(patch)
2986 patch = q.lookup(patch)
2987 else:
2987 else:
2988 if not q.applied:
2988 if not q.applied:
2989 ui.write(_('no patches applied\n'))
2989 ui.write(_('no patches applied\n'))
2990 return
2990 return
2991 patch = q.lookup('qtip')
2991 patch = q.lookup('qtip')
2992 absdest = q.join(name)
2992 absdest = q.join(name)
2993 if os.path.isdir(absdest):
2993 if os.path.isdir(absdest):
2994 name = normname(os.path.join(name, os.path.basename(patch)))
2994 name = normname(os.path.join(name, os.path.basename(patch)))
2995 absdest = q.join(name)
2995 absdest = q.join(name)
2996 q.checkpatchname(name)
2996 q.checkpatchname(name)
2997
2997
2998 ui.note(_('renaming %s to %s\n') % (patch, name))
2998 ui.note(_('renaming %s to %s\n') % (patch, name))
2999 i = q.findseries(patch)
2999 i = q.findseries(patch)
3000 guards = q.guard_re.findall(q.fullseries[i])
3000 guards = q.guard_re.findall(q.fullseries[i])
3001 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
3001 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
3002 q.parseseries()
3002 q.parseseries()
3003 q.seriesdirty = True
3003 q.seriesdirty = True
3004
3004
3005 info = q.isapplied(patch)
3005 info = q.isapplied(patch)
3006 if info:
3006 if info:
3007 q.applied[info[0]] = statusentry(info[1], name)
3007 q.applied[info[0]] = statusentry(info[1], name)
3008 q.applieddirty = True
3008 q.applieddirty = True
3009
3009
3010 destdir = os.path.dirname(absdest)
3010 destdir = os.path.dirname(absdest)
3011 if not os.path.isdir(destdir):
3011 if not os.path.isdir(destdir):
3012 os.makedirs(destdir)
3012 os.makedirs(destdir)
3013 util.rename(q.join(patch), absdest)
3013 util.rename(q.join(patch), absdest)
3014 r = q.qrepo()
3014 r = q.qrepo()
3015 if r and patch in r.dirstate:
3015 if r and patch in r.dirstate:
3016 wctx = r[None]
3016 wctx = r[None]
3017 with r.wlock():
3017 with r.wlock():
3018 if r.dirstate[patch] == 'a':
3018 if r.dirstate[patch] == 'a':
3019 r.dirstate.drop(patch)
3019 r.dirstate.drop(patch)
3020 r.dirstate.add(name)
3020 r.dirstate.add(name)
3021 else:
3021 else:
3022 wctx.copy(patch, name)
3022 wctx.copy(patch, name)
3023 wctx.forget([patch])
3023 wctx.forget([patch])
3024
3024
3025 q.savedirty()
3025 q.savedirty()
3026
3026
3027 @command("qrestore",
3027 @command("qrestore",
3028 [('d', 'delete', None, _('delete save entry')),
3028 [('d', 'delete', None, _('delete save entry')),
3029 ('u', 'update', None, _('update queue working directory'))],
3029 ('u', 'update', None, _('update queue working directory'))],
3030 _('hg qrestore [-d] [-u] REV'))
3030 _('hg qrestore [-d] [-u] REV'))
3031 def restore(ui, repo, rev, **opts):
3031 def restore(ui, repo, rev, **opts):
3032 """restore the queue state saved by a revision (DEPRECATED)
3032 """restore the queue state saved by a revision (DEPRECATED)
3033
3033
3034 This command is deprecated, use :hg:`rebase` instead."""
3034 This command is deprecated, use :hg:`rebase` instead."""
3035 rev = repo.lookup(rev)
3035 rev = repo.lookup(rev)
3036 q = repo.mq
3036 q = repo.mq
3037 q.restore(repo, rev, delete=opts.get('delete'),
3037 q.restore(repo, rev, delete=opts.get('delete'),
3038 qupdate=opts.get('update'))
3038 qupdate=opts.get('update'))
3039 q.savedirty()
3039 q.savedirty()
3040 return 0
3040 return 0
3041
3041
3042 @command("qsave",
3042 @command("qsave",
3043 [('c', 'copy', None, _('copy patch directory')),
3043 [('c', 'copy', None, _('copy patch directory')),
3044 ('n', 'name', '',
3044 ('n', 'name', '',
3045 _('copy directory name'), _('NAME')),
3045 _('copy directory name'), _('NAME')),
3046 ('e', 'empty', None, _('clear queue status file')),
3046 ('e', 'empty', None, _('clear queue status file')),
3047 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3047 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3048 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
3048 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
3049 def save(ui, repo, **opts):
3049 def save(ui, repo, **opts):
3050 """save current queue state (DEPRECATED)
3050 """save current queue state (DEPRECATED)
3051
3051
3052 This command is deprecated, use :hg:`rebase` instead."""
3052 This command is deprecated, use :hg:`rebase` instead."""
3053 q = repo.mq
3053 q = repo.mq
3054 message = cmdutil.logmessage(ui, opts)
3054 message = cmdutil.logmessage(ui, opts)
3055 ret = q.save(repo, msg=message)
3055 ret = q.save(repo, msg=message)
3056 if ret:
3056 if ret:
3057 return ret
3057 return ret
3058 q.savedirty() # save to .hg/patches before copying
3058 q.savedirty() # save to .hg/patches before copying
3059 if opts.get('copy'):
3059 if opts.get('copy'):
3060 path = q.path
3060 path = q.path
3061 if opts.get('name'):
3061 if opts.get('name'):
3062 newpath = os.path.join(q.basepath, opts.get('name'))
3062 newpath = os.path.join(q.basepath, opts.get('name'))
3063 if os.path.exists(newpath):
3063 if os.path.exists(newpath):
3064 if not os.path.isdir(newpath):
3064 if not os.path.isdir(newpath):
3065 raise error.Abort(_('destination %s exists and is not '
3065 raise error.Abort(_('destination %s exists and is not '
3066 'a directory') % newpath)
3066 'a directory') % newpath)
3067 if not opts.get('force'):
3067 if not opts.get('force'):
3068 raise error.Abort(_('destination %s exists, '
3068 raise error.Abort(_('destination %s exists, '
3069 'use -f to force') % newpath)
3069 'use -f to force') % newpath)
3070 else:
3070 else:
3071 newpath = savename(path)
3071 newpath = savename(path)
3072 ui.warn(_("copy %s to %s\n") % (path, newpath))
3072 ui.warn(_("copy %s to %s\n") % (path, newpath))
3073 util.copyfiles(path, newpath)
3073 util.copyfiles(path, newpath)
3074 if opts.get('empty'):
3074 if opts.get('empty'):
3075 del q.applied[:]
3075 del q.applied[:]
3076 q.applieddirty = True
3076 q.applieddirty = True
3077 q.savedirty()
3077 q.savedirty()
3078 return 0
3078 return 0
3079
3079
3080
3080
3081 @command("qselect",
3081 @command("qselect",
3082 [('n', 'none', None, _('disable all guards')),
3082 [('n', 'none', None, _('disable all guards')),
3083 ('s', 'series', None, _('list all guards in series file')),
3083 ('s', 'series', None, _('list all guards in series file')),
3084 ('', 'pop', None, _('pop to before first guarded applied patch')),
3084 ('', 'pop', None, _('pop to before first guarded applied patch')),
3085 ('', 'reapply', None, _('pop, then reapply patches'))],
3085 ('', 'reapply', None, _('pop, then reapply patches'))],
3086 _('hg qselect [OPTION]... [GUARD]...'))
3086 _('hg qselect [OPTION]... [GUARD]...'))
3087 def select(ui, repo, *args, **opts):
3087 def select(ui, repo, *args, **opts):
3088 '''set or print guarded patches to push
3088 '''set or print guarded patches to push
3089
3089
3090 Use the :hg:`qguard` command to set or print guards on patch, then use
3090 Use the :hg:`qguard` command to set or print guards on patch, then use
3091 qselect to tell mq which guards to use. A patch will be pushed if
3091 qselect to tell mq which guards to use. A patch will be pushed if
3092 it has no guards or any positive guards match the currently
3092 it has no guards or any positive guards match the currently
3093 selected guard, but will not be pushed if any negative guards
3093 selected guard, but will not be pushed if any negative guards
3094 match the current guard. For example::
3094 match the current guard. For example::
3095
3095
3096 qguard foo.patch -- -stable (negative guard)
3096 qguard foo.patch -- -stable (negative guard)
3097 qguard bar.patch +stable (positive guard)
3097 qguard bar.patch +stable (positive guard)
3098 qselect stable
3098 qselect stable
3099
3099
3100 This activates the "stable" guard. mq will skip foo.patch (because
3100 This activates the "stable" guard. mq will skip foo.patch (because
3101 it has a negative match) but push bar.patch (because it has a
3101 it has a negative match) but push bar.patch (because it has a
3102 positive match).
3102 positive match).
3103
3103
3104 With no arguments, prints the currently active guards.
3104 With no arguments, prints the currently active guards.
3105 With one argument, sets the active guard.
3105 With one argument, sets the active guard.
3106
3106
3107 Use -n/--none to deactivate guards (no other arguments needed).
3107 Use -n/--none to deactivate guards (no other arguments needed).
3108 When no guards are active, patches with positive guards are
3108 When no guards are active, patches with positive guards are
3109 skipped and patches with negative guards are pushed.
3109 skipped and patches with negative guards are pushed.
3110
3110
3111 qselect can change the guards on applied patches. It does not pop
3111 qselect can change the guards on applied patches. It does not pop
3112 guarded patches by default. Use --pop to pop back to the last
3112 guarded patches by default. Use --pop to pop back to the last
3113 applied patch that is not guarded. Use --reapply (which implies
3113 applied patch that is not guarded. Use --reapply (which implies
3114 --pop) to push back to the current patch afterwards, but skip
3114 --pop) to push back to the current patch afterwards, but skip
3115 guarded patches.
3115 guarded patches.
3116
3116
3117 Use -s/--series to print a list of all guards in the series file
3117 Use -s/--series to print a list of all guards in the series file
3118 (no other arguments needed). Use -v for more information.
3118 (no other arguments needed). Use -v for more information.
3119
3119
3120 Returns 0 on success.'''
3120 Returns 0 on success.'''
3121
3121
3122 q = repo.mq
3122 q = repo.mq
3123 guards = q.active()
3123 guards = q.active()
3124 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3124 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3125 if args or opts.get('none'):
3125 if args or opts.get('none'):
3126 old_unapplied = q.unapplied(repo)
3126 old_unapplied = q.unapplied(repo)
3127 old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3127 old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3128 q.setactive(args)
3128 q.setactive(args)
3129 q.savedirty()
3129 q.savedirty()
3130 if not args:
3130 if not args:
3131 ui.status(_('guards deactivated\n'))
3131 ui.status(_('guards deactivated\n'))
3132 if not opts.get('pop') and not opts.get('reapply'):
3132 if not opts.get('pop') and not opts.get('reapply'):
3133 unapplied = q.unapplied(repo)
3133 unapplied = q.unapplied(repo)
3134 guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3134 guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
3135 if len(unapplied) != len(old_unapplied):
3135 if len(unapplied) != len(old_unapplied):
3136 ui.status(_('number of unguarded, unapplied patches has '
3136 ui.status(_('number of unguarded, unapplied patches has '
3137 'changed from %d to %d\n') %
3137 'changed from %d to %d\n') %
3138 (len(old_unapplied), len(unapplied)))
3138 (len(old_unapplied), len(unapplied)))
3139 if len(guarded) != len(old_guarded):
3139 if len(guarded) != len(old_guarded):
3140 ui.status(_('number of guarded, applied patches has changed '
3140 ui.status(_('number of guarded, applied patches has changed '
3141 'from %d to %d\n') %
3141 'from %d to %d\n') %
3142 (len(old_guarded), len(guarded)))
3142 (len(old_guarded), len(guarded)))
3143 elif opts.get('series'):
3143 elif opts.get('series'):
3144 guards = {}
3144 guards = {}
3145 noguards = 0
3145 noguards = 0
3146 for gs in q.seriesguards:
3146 for gs in q.seriesguards:
3147 if not gs:
3147 if not gs:
3148 noguards += 1
3148 noguards += 1
3149 for g in gs:
3149 for g in gs:
3150 guards.setdefault(g, 0)
3150 guards.setdefault(g, 0)
3151 guards[g] += 1
3151 guards[g] += 1
3152 if ui.verbose:
3152 if ui.verbose:
3153 guards['NONE'] = noguards
3153 guards['NONE'] = noguards
3154 guards = guards.items()
3154 guards = guards.items()
3155 guards.sort(key=lambda x: x[0][1:])
3155 guards.sort(key=lambda x: x[0][1:])
3156 if guards:
3156 if guards:
3157 ui.note(_('guards in series file:\n'))
3157 ui.note(_('guards in series file:\n'))
3158 for guard, count in guards:
3158 for guard, count in guards:
3159 ui.note('%2d ' % count)
3159 ui.note('%2d ' % count)
3160 ui.write(guard, '\n')
3160 ui.write(guard, '\n')
3161 else:
3161 else:
3162 ui.note(_('no guards in series file\n'))
3162 ui.note(_('no guards in series file\n'))
3163 else:
3163 else:
3164 if guards:
3164 if guards:
3165 ui.note(_('active guards:\n'))
3165 ui.note(_('active guards:\n'))
3166 for g in guards:
3166 for g in guards:
3167 ui.write(g, '\n')
3167 ui.write(g, '\n')
3168 else:
3168 else:
3169 ui.write(_('no active guards\n'))
3169 ui.write(_('no active guards\n'))
3170 reapply = opts.get('reapply') and q.applied and q.applied[-1].name
3170 reapply = opts.get('reapply') and q.applied and q.applied[-1].name
3171 popped = False
3171 popped = False
3172 if opts.get('pop') or opts.get('reapply'):
3172 if opts.get('pop') or opts.get('reapply'):
3173 for i in xrange(len(q.applied)):
3173 for i in xrange(len(q.applied)):
3174 if not pushable(i):
3174 if not pushable(i):
3175 ui.status(_('popping guarded patches\n'))
3175 ui.status(_('popping guarded patches\n'))
3176 popped = True
3176 popped = True
3177 if i == 0:
3177 if i == 0:
3178 q.pop(repo, all=True)
3178 q.pop(repo, all=True)
3179 else:
3179 else:
3180 q.pop(repo, q.applied[i - 1].name)
3180 q.pop(repo, q.applied[i - 1].name)
3181 break
3181 break
3182 if popped:
3182 if popped:
3183 try:
3183 try:
3184 if reapply:
3184 if reapply:
3185 ui.status(_('reapplying unguarded patches\n'))
3185 ui.status(_('reapplying unguarded patches\n'))
3186 q.push(repo, reapply)
3186 q.push(repo, reapply)
3187 finally:
3187 finally:
3188 q.savedirty()
3188 q.savedirty()
3189
3189
3190 @command("qfinish",
3190 @command("qfinish",
3191 [('a', 'applied', None, _('finish all applied changesets'))],
3191 [('a', 'applied', None, _('finish all applied changesets'))],
3192 _('hg qfinish [-a] [REV]...'))
3192 _('hg qfinish [-a] [REV]...'))
3193 def finish(ui, repo, *revrange, **opts):
3193 def finish(ui, repo, *revrange, **opts):
3194 """move applied patches into repository history
3194 """move applied patches into repository history
3195
3195
3196 Finishes the specified revisions (corresponding to applied
3196 Finishes the specified revisions (corresponding to applied
3197 patches) by moving them out of mq control into regular repository
3197 patches) by moving them out of mq control into regular repository
3198 history.
3198 history.
3199
3199
3200 Accepts a revision range or the -a/--applied option. If --applied
3200 Accepts a revision range or the -a/--applied option. If --applied
3201 is specified, all applied mq revisions are removed from mq
3201 is specified, all applied mq revisions are removed from mq
3202 control. Otherwise, the given revisions must be at the base of the
3202 control. Otherwise, the given revisions must be at the base of the
3203 stack of applied patches.
3203 stack of applied patches.
3204
3204
3205 This can be especially useful if your changes have been applied to
3205 This can be especially useful if your changes have been applied to
3206 an upstream repository, or if you are about to push your changes
3206 an upstream repository, or if you are about to push your changes
3207 to upstream.
3207 to upstream.
3208
3208
3209 Returns 0 on success.
3209 Returns 0 on success.
3210 """
3210 """
3211 if not opts.get('applied') and not revrange:
3211 if not opts.get('applied') and not revrange:
3212 raise error.Abort(_('no revisions specified'))
3212 raise error.Abort(_('no revisions specified'))
3213 elif opts.get('applied'):
3213 elif opts.get('applied'):
3214 revrange = ('qbase::qtip',) + revrange
3214 revrange = ('qbase::qtip',) + revrange
3215
3215
3216 q = repo.mq
3216 q = repo.mq
3217 if not q.applied:
3217 if not q.applied:
3218 ui.status(_('no patches applied\n'))
3218 ui.status(_('no patches applied\n'))
3219 return 0
3219 return 0
3220
3220
3221 revs = scmutil.revrange(repo, revrange)
3221 revs = scmutil.revrange(repo, revrange)
3222 if repo['.'].rev() in revs and repo[None].files():
3222 if repo['.'].rev() in revs and repo[None].files():
3223 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3223 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3224 # queue.finish may changes phases but leave the responsibility to lock the
3224 # queue.finish may changes phases but leave the responsibility to lock the
3225 # repo to the caller to avoid deadlock with wlock. This command code is
3225 # repo to the caller to avoid deadlock with wlock. This command code is
3226 # responsibility for this locking.
3226 # responsibility for this locking.
3227 with repo.lock():
3227 with repo.lock():
3228 q.finish(repo, revs)
3228 q.finish(repo, revs)
3229 q.savedirty()
3229 q.savedirty()
3230 return 0
3230 return 0
3231
3231
3232 @command("qqueue",
3232 @command("qqueue",
3233 [('l', 'list', False, _('list all available queues')),
3233 [('l', 'list', False, _('list all available queues')),
3234 ('', 'active', False, _('print name of active queue')),
3234 ('', 'active', False, _('print name of active queue')),
3235 ('c', 'create', False, _('create new queue')),
3235 ('c', 'create', False, _('create new queue')),
3236 ('', 'rename', False, _('rename active queue')),
3236 ('', 'rename', False, _('rename active queue')),
3237 ('', 'delete', False, _('delete reference to queue')),
3237 ('', 'delete', False, _('delete reference to queue')),
3238 ('', 'purge', False, _('delete queue, and remove patch dir')),
3238 ('', 'purge', False, _('delete queue, and remove patch dir')),
3239 ],
3239 ],
3240 _('[OPTION] [QUEUE]'))
3240 _('[OPTION] [QUEUE]'))
3241 def qqueue(ui, repo, name=None, **opts):
3241 def qqueue(ui, repo, name=None, **opts):
3242 '''manage multiple patch queues
3242 '''manage multiple patch queues
3243
3243
3244 Supports switching between different patch queues, as well as creating
3244 Supports switching between different patch queues, as well as creating
3245 new patch queues and deleting existing ones.
3245 new patch queues and deleting existing ones.
3246
3246
3247 Omitting a queue name or specifying -l/--list will show you the registered
3247 Omitting a queue name or specifying -l/--list will show you the registered
3248 queues - by default the "normal" patches queue is registered. The currently
3248 queues - by default the "normal" patches queue is registered. The currently
3249 active queue will be marked with "(active)". Specifying --active will print
3249 active queue will be marked with "(active)". Specifying --active will print
3250 only the name of the active queue.
3250 only the name of the active queue.
3251
3251
3252 To create a new queue, use -c/--create. The queue is automatically made
3252 To create a new queue, use -c/--create. The queue is automatically made
3253 active, except in the case where there are applied patches from the
3253 active, except in the case where there are applied patches from the
3254 currently active queue in the repository. Then the queue will only be
3254 currently active queue in the repository. Then the queue will only be
3255 created and switching will fail.
3255 created and switching will fail.
3256
3256
3257 To delete an existing queue, use --delete. You cannot delete the currently
3257 To delete an existing queue, use --delete. You cannot delete the currently
3258 active queue.
3258 active queue.
3259
3259
3260 Returns 0 on success.
3260 Returns 0 on success.
3261 '''
3261 '''
3262 q = repo.mq
3262 q = repo.mq
3263 _defaultqueue = 'patches'
3263 _defaultqueue = 'patches'
3264 _allqueues = 'patches.queues'
3264 _allqueues = 'patches.queues'
3265 _activequeue = 'patches.queue'
3265 _activequeue = 'patches.queue'
3266
3266
3267 def _getcurrent():
3267 def _getcurrent():
3268 cur = os.path.basename(q.path)
3268 cur = os.path.basename(q.path)
3269 if cur.startswith('patches-'):
3269 if cur.startswith('patches-'):
3270 cur = cur[8:]
3270 cur = cur[8:]
3271 return cur
3271 return cur
3272
3272
3273 def _noqueues():
3273 def _noqueues():
3274 try:
3274 try:
3275 fh = repo.vfs(_allqueues, 'r')
3275 fh = repo.vfs(_allqueues, 'r')
3276 fh.close()
3276 fh.close()
3277 except IOError:
3277 except IOError:
3278 return True
3278 return True
3279
3279
3280 return False
3280 return False
3281
3281
3282 def _getqueues():
3282 def _getqueues():
3283 current = _getcurrent()
3283 current = _getcurrent()
3284
3284
3285 try:
3285 try:
3286 fh = repo.vfs(_allqueues, 'r')
3286 fh = repo.vfs(_allqueues, 'r')
3287 queues = [queue.strip() for queue in fh if queue.strip()]
3287 queues = [queue.strip() for queue in fh if queue.strip()]
3288 fh.close()
3288 fh.close()
3289 if current not in queues:
3289 if current not in queues:
3290 queues.append(current)
3290 queues.append(current)
3291 except IOError:
3291 except IOError:
3292 queues = [_defaultqueue]
3292 queues = [_defaultqueue]
3293
3293
3294 return sorted(queues)
3294 return sorted(queues)
3295
3295
3296 def _setactive(name):
3296 def _setactive(name):
3297 if q.applied:
3297 if q.applied:
3298 raise error.Abort(_('new queue created, but cannot make active '
3298 raise error.Abort(_('new queue created, but cannot make active '
3299 'as patches are applied'))
3299 'as patches are applied'))
3300 _setactivenocheck(name)
3300 _setactivenocheck(name)
3301
3301
3302 def _setactivenocheck(name):
3302 def _setactivenocheck(name):
3303 fh = repo.vfs(_activequeue, 'w')
3303 fh = repo.vfs(_activequeue, 'w')
3304 if name != 'patches':
3304 if name != 'patches':
3305 fh.write(name)
3305 fh.write(name)
3306 fh.close()
3306 fh.close()
3307
3307
3308 def _addqueue(name):
3308 def _addqueue(name):
3309 fh = repo.vfs(_allqueues, 'a')
3309 fh = repo.vfs(_allqueues, 'a')
3310 fh.write('%s\n' % (name,))
3310 fh.write('%s\n' % (name,))
3311 fh.close()
3311 fh.close()
3312
3312
3313 def _queuedir(name):
3313 def _queuedir(name):
3314 if name == 'patches':
3314 if name == 'patches':
3315 return repo.join('patches')
3315 return repo.join('patches')
3316 else:
3316 else:
3317 return repo.join('patches-' + name)
3317 return repo.join('patches-' + name)
3318
3318
3319 def _validname(name):
3319 def _validname(name):
3320 for n in name:
3320 for n in name:
3321 if n in ':\\/.':
3321 if n in ':\\/.':
3322 return False
3322 return False
3323 return True
3323 return True
3324
3324
3325 def _delete(name):
3325 def _delete(name):
3326 if name not in existing:
3326 if name not in existing:
3327 raise error.Abort(_('cannot delete queue that does not exist'))
3327 raise error.Abort(_('cannot delete queue that does not exist'))
3328
3328
3329 current = _getcurrent()
3329 current = _getcurrent()
3330
3330
3331 if name == current:
3331 if name == current:
3332 raise error.Abort(_('cannot delete currently active queue'))
3332 raise error.Abort(_('cannot delete currently active queue'))
3333
3333
3334 fh = repo.vfs('patches.queues.new', 'w')
3334 fh = repo.vfs('patches.queues.new', 'w')
3335 for queue in existing:
3335 for queue in existing:
3336 if queue == name:
3336 if queue == name:
3337 continue
3337 continue
3338 fh.write('%s\n' % (queue,))
3338 fh.write('%s\n' % (queue,))
3339 fh.close()
3339 fh.close()
3340 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3340 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3341
3341
3342 if not name or opts.get('list') or opts.get('active'):
3342 if not name or opts.get('list') or opts.get('active'):
3343 current = _getcurrent()
3343 current = _getcurrent()
3344 if opts.get('active'):
3344 if opts.get('active'):
3345 ui.write('%s\n' % (current,))
3345 ui.write('%s\n' % (current,))
3346 return
3346 return
3347 for queue in _getqueues():
3347 for queue in _getqueues():
3348 ui.write('%s' % (queue,))
3348 ui.write('%s' % (queue,))
3349 if queue == current and not ui.quiet:
3349 if queue == current and not ui.quiet:
3350 ui.write(_(' (active)\n'))
3350 ui.write(_(' (active)\n'))
3351 else:
3351 else:
3352 ui.write('\n')
3352 ui.write('\n')
3353 return
3353 return
3354
3354
3355 if not _validname(name):
3355 if not _validname(name):
3356 raise error.Abort(
3356 raise error.Abort(
3357 _('invalid queue name, may not contain the characters ":\\/."'))
3357 _('invalid queue name, may not contain the characters ":\\/."'))
3358
3358
3359 with repo.wlock():
3359 with repo.wlock():
3360 existing = _getqueues()
3360 existing = _getqueues()
3361
3361
3362 if opts.get('create'):
3362 if opts.get('create'):
3363 if name in existing:
3363 if name in existing:
3364 raise error.Abort(_('queue "%s" already exists') % name)
3364 raise error.Abort(_('queue "%s" already exists') % name)
3365 if _noqueues():
3365 if _noqueues():
3366 _addqueue(_defaultqueue)
3366 _addqueue(_defaultqueue)
3367 _addqueue(name)
3367 _addqueue(name)
3368 _setactive(name)
3368 _setactive(name)
3369 elif opts.get('rename'):
3369 elif opts.get('rename'):
3370 current = _getcurrent()
3370 current = _getcurrent()
3371 if name == current:
3371 if name == current:
3372 raise error.Abort(_('can\'t rename "%s" to its current name')
3372 raise error.Abort(_('can\'t rename "%s" to its current name')
3373 % name)
3373 % name)
3374 if name in existing:
3374 if name in existing:
3375 raise error.Abort(_('queue "%s" already exists') % name)
3375 raise error.Abort(_('queue "%s" already exists') % name)
3376
3376
3377 olddir = _queuedir(current)
3377 olddir = _queuedir(current)
3378 newdir = _queuedir(name)
3378 newdir = _queuedir(name)
3379
3379
3380 if os.path.exists(newdir):
3380 if os.path.exists(newdir):
3381 raise error.Abort(_('non-queue directory "%s" already exists') %
3381 raise error.Abort(_('non-queue directory "%s" already exists') %
3382 newdir)
3382 newdir)
3383
3383
3384 fh = repo.vfs('patches.queues.new', 'w')
3384 fh = repo.vfs('patches.queues.new', 'w')
3385 for queue in existing:
3385 for queue in existing:
3386 if queue == current:
3386 if queue == current:
3387 fh.write('%s\n' % (name,))
3387 fh.write('%s\n' % (name,))
3388 if os.path.exists(olddir):
3388 if os.path.exists(olddir):
3389 util.rename(olddir, newdir)
3389 util.rename(olddir, newdir)
3390 else:
3390 else:
3391 fh.write('%s\n' % (queue,))
3391 fh.write('%s\n' % (queue,))
3392 fh.close()
3392 fh.close()
3393 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3393 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3394 _setactivenocheck(name)
3394 _setactivenocheck(name)
3395 elif opts.get('delete'):
3395 elif opts.get('delete'):
3396 _delete(name)
3396 _delete(name)
3397 elif opts.get('purge'):
3397 elif opts.get('purge'):
3398 if name in existing:
3398 if name in existing:
3399 _delete(name)
3399 _delete(name)
3400 qdir = _queuedir(name)
3400 qdir = _queuedir(name)
3401 if os.path.exists(qdir):
3401 if os.path.exists(qdir):
3402 shutil.rmtree(qdir)
3402 shutil.rmtree(qdir)
3403 else:
3403 else:
3404 if name not in existing:
3404 if name not in existing:
3405 raise error.Abort(_('use --create to create a new queue'))
3405 raise error.Abort(_('use --create to create a new queue'))
3406 _setactive(name)
3406 _setactive(name)
3407
3407
3408 def mqphasedefaults(repo, roots):
3408 def mqphasedefaults(repo, roots):
3409 """callback used to set mq changeset as secret when no phase data exists"""
3409 """callback used to set mq changeset as secret when no phase data exists"""
3410 if repo.mq.applied:
3410 if repo.mq.applied:
3411 if repo.ui.configbool('mq', 'secret', False):
3411 if repo.ui.configbool('mq', 'secret', False):
3412 mqphase = phases.secret
3412 mqphase = phases.secret
3413 else:
3413 else:
3414 mqphase = phases.draft
3414 mqphase = phases.draft
3415 qbase = repo[repo.mq.applied[0].node]
3415 qbase = repo[repo.mq.applied[0].node]
3416 roots[mqphase].add(qbase.node())
3416 roots[mqphase].add(qbase.node())
3417 return roots
3417 return roots
3418
3418
3419 def reposetup(ui, repo):
3419 def reposetup(ui, repo):
3420 class mqrepo(repo.__class__):
3420 class mqrepo(repo.__class__):
3421 @localrepo.unfilteredpropertycache
3421 @localrepo.unfilteredpropertycache
3422 def mq(self):
3422 def mq(self):
3423 return queue(self.ui, self.baseui, self.path)
3423 return queue(self.ui, self.baseui, self.path)
3424
3424
3425 def invalidateall(self):
3425 def invalidateall(self):
3426 super(mqrepo, self).invalidateall()
3426 super(mqrepo, self).invalidateall()
3427 if localrepo.hasunfilteredcache(self, 'mq'):
3427 if localrepo.hasunfilteredcache(self, 'mq'):
3428 # recreate mq in case queue path was changed
3428 # recreate mq in case queue path was changed
3429 delattr(self.unfiltered(), 'mq')
3429 delattr(self.unfiltered(), 'mq')
3430
3430
3431 def abortifwdirpatched(self, errmsg, force=False):
3431 def abortifwdirpatched(self, errmsg, force=False):
3432 if self.mq.applied and self.mq.checkapplied and not force:
3432 if self.mq.applied and self.mq.checkapplied and not force:
3433 parents = self.dirstate.parents()
3433 parents = self.dirstate.parents()
3434 patches = [s.node for s in self.mq.applied]
3434 patches = [s.node for s in self.mq.applied]
3435 if parents[0] in patches or parents[1] in patches:
3435 if parents[0] in patches or parents[1] in patches:
3436 raise error.Abort(errmsg)
3436 raise error.Abort(errmsg)
3437
3437
3438 def commit(self, text="", user=None, date=None, match=None,
3438 def commit(self, text="", user=None, date=None, match=None,
3439 force=False, editor=False, extra={}):
3439 force=False, editor=False, extra={}):
3440 self.abortifwdirpatched(
3440 self.abortifwdirpatched(
3441 _('cannot commit over an applied mq patch'),
3441 _('cannot commit over an applied mq patch'),
3442 force)
3442 force)
3443
3443
3444 return super(mqrepo, self).commit(text, user, date, match, force,
3444 return super(mqrepo, self).commit(text, user, date, match, force,
3445 editor, extra)
3445 editor, extra)
3446
3446
3447 def checkpush(self, pushop):
3447 def checkpush(self, pushop):
3448 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3448 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3449 outapplied = [e.node for e in self.mq.applied]
3449 outapplied = [e.node for e in self.mq.applied]
3450 if pushop.revs:
3450 if pushop.revs:
3451 # Assume applied patches have no non-patch descendants and
3451 # Assume applied patches have no non-patch descendants and
3452 # are not on remote already. Filtering any changeset not
3452 # are not on remote already. Filtering any changeset not
3453 # pushed.
3453 # pushed.
3454 heads = set(pushop.revs)
3454 heads = set(pushop.revs)
3455 for node in reversed(outapplied):
3455 for node in reversed(outapplied):
3456 if node in heads:
3456 if node in heads:
3457 break
3457 break
3458 else:
3458 else:
3459 outapplied.pop()
3459 outapplied.pop()
3460 # looking for pushed and shared changeset
3460 # looking for pushed and shared changeset
3461 for node in outapplied:
3461 for node in outapplied:
3462 if self[node].phase() < phases.secret:
3462 if self[node].phase() < phases.secret:
3463 raise error.Abort(_('source has mq patches applied'))
3463 raise error.Abort(_('source has mq patches applied'))
3464 # no non-secret patches pushed
3464 # no non-secret patches pushed
3465 super(mqrepo, self).checkpush(pushop)
3465 super(mqrepo, self).checkpush(pushop)
3466
3466
3467 def _findtags(self):
3467 def _findtags(self):
3468 '''augment tags from base class with patch tags'''
3468 '''augment tags from base class with patch tags'''
3469 result = super(mqrepo, self)._findtags()
3469 result = super(mqrepo, self)._findtags()
3470
3470
3471 q = self.mq
3471 q = self.mq
3472 if not q.applied:
3472 if not q.applied:
3473 return result
3473 return result
3474
3474
3475 mqtags = [(patch.node, patch.name) for patch in q.applied]
3475 mqtags = [(patch.node, patch.name) for patch in q.applied]
3476
3476
3477 try:
3477 try:
3478 # for now ignore filtering business
3478 # for now ignore filtering business
3479 self.unfiltered().changelog.rev(mqtags[-1][0])
3479 self.unfiltered().changelog.rev(mqtags[-1][0])
3480 except error.LookupError:
3480 except error.LookupError:
3481 self.ui.warn(_('mq status file refers to unknown node %s\n')
3481 self.ui.warn(_('mq status file refers to unknown node %s\n')
3482 % short(mqtags[-1][0]))
3482 % short(mqtags[-1][0]))
3483 return result
3483 return result
3484
3484
3485 # do not add fake tags for filtered revisions
3485 # do not add fake tags for filtered revisions
3486 included = self.changelog.hasnode
3486 included = self.changelog.hasnode
3487 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3487 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3488 if not mqtags:
3488 if not mqtags:
3489 return result
3489 return result
3490
3490
3491 mqtags.append((mqtags[-1][0], 'qtip'))
3491 mqtags.append((mqtags[-1][0], 'qtip'))
3492 mqtags.append((mqtags[0][0], 'qbase'))
3492 mqtags.append((mqtags[0][0], 'qbase'))
3493 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3493 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3494 tags = result[0]
3494 tags = result[0]
3495 for patch in mqtags:
3495 for patch in mqtags:
3496 if patch[1] in tags:
3496 if patch[1] in tags:
3497 self.ui.warn(_('tag %s overrides mq patch of the same '
3497 self.ui.warn(_('tag %s overrides mq patch of the same '
3498 'name\n') % patch[1])
3498 'name\n') % patch[1])
3499 else:
3499 else:
3500 tags[patch[1]] = patch[0]
3500 tags[patch[1]] = patch[0]
3501
3501
3502 return result
3502 return result
3503
3503
3504 if repo.local():
3504 if repo.local():
3505 repo.__class__ = mqrepo
3505 repo.__class__ = mqrepo
3506
3506
3507 repo._phasedefaults.append(mqphasedefaults)
3507 repo._phasedefaults.append(mqphasedefaults)
3508
3508
3509 def mqimport(orig, ui, repo, *args, **kwargs):
3509 def mqimport(orig, ui, repo, *args, **kwargs):
3510 if (util.safehasattr(repo, 'abortifwdirpatched')
3510 if (util.safehasattr(repo, 'abortifwdirpatched')
3511 and not kwargs.get('no_commit', False)):
3511 and not kwargs.get('no_commit', False)):
3512 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3512 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3513 kwargs.get('force'))
3513 kwargs.get('force'))
3514 return orig(ui, repo, *args, **kwargs)
3514 return orig(ui, repo, *args, **kwargs)
3515
3515
3516 def mqinit(orig, ui, *args, **kwargs):
3516 def mqinit(orig, ui, *args, **kwargs):
3517 mq = kwargs.pop('mq', None)
3517 mq = kwargs.pop('mq', None)
3518
3518
3519 if not mq:
3519 if not mq:
3520 return orig(ui, *args, **kwargs)
3520 return orig(ui, *args, **kwargs)
3521
3521
3522 if args:
3522 if args:
3523 repopath = args[0]
3523 repopath = args[0]
3524 if not hg.islocal(repopath):
3524 if not hg.islocal(repopath):
3525 raise error.Abort(_('only a local queue repository '
3525 raise error.Abort(_('only a local queue repository '
3526 'may be initialized'))
3526 'may be initialized'))
3527 else:
3527 else:
3528 repopath = cmdutil.findrepo(pycompat.getcwd())
3528 repopath = cmdutil.findrepo(pycompat.getcwd())
3529 if not repopath:
3529 if not repopath:
3530 raise error.Abort(_('there is no Mercurial repository here '
3530 raise error.Abort(_('there is no Mercurial repository here '
3531 '(.hg not found)'))
3531 '(.hg not found)'))
3532 repo = hg.repository(ui, repopath)
3532 repo = hg.repository(ui, repopath)
3533 return qinit(ui, repo, True)
3533 return qinit(ui, repo, True)
3534
3534
3535 def mqcommand(orig, ui, repo, *args, **kwargs):
3535 def mqcommand(orig, ui, repo, *args, **kwargs):
3536 """Add --mq option to operate on patch repository instead of main"""
3536 """Add --mq option to operate on patch repository instead of main"""
3537
3537
3538 # some commands do not like getting unknown options
3538 # some commands do not like getting unknown options
3539 mq = kwargs.pop('mq', None)
3539 mq = kwargs.pop('mq', None)
3540
3540
3541 if not mq:
3541 if not mq:
3542 return orig(ui, repo, *args, **kwargs)
3542 return orig(ui, repo, *args, **kwargs)
3543
3543
3544 q = repo.mq
3544 q = repo.mq
3545 r = q.qrepo()
3545 r = q.qrepo()
3546 if not r:
3546 if not r:
3547 raise error.Abort(_('no queue repository'))
3547 raise error.Abort(_('no queue repository'))
3548 return orig(r.ui, r, *args, **kwargs)
3548 return orig(r.ui, r, *args, **kwargs)
3549
3549
3550 def summaryhook(ui, repo):
3550 def summaryhook(ui, repo):
3551 q = repo.mq
3551 q = repo.mq
3552 m = []
3552 m = []
3553 a, u = len(q.applied), len(q.unapplied(repo))
3553 a, u = len(q.applied), len(q.unapplied(repo))
3554 if a:
3554 if a:
3555 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3555 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3556 if u:
3556 if u:
3557 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3557 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3558 if m:
3558 if m:
3559 # i18n: column positioning for "hg summary"
3559 # i18n: column positioning for "hg summary"
3560 ui.write(_("mq: %s\n") % ', '.join(m))
3560 ui.write(_("mq: %s\n") % ', '.join(m))
3561 else:
3561 else:
3562 # i18n: column positioning for "hg summary"
3562 # i18n: column positioning for "hg summary"
3563 ui.note(_("mq: (empty queue)\n"))
3563 ui.note(_("mq: (empty queue)\n"))
3564
3564
3565 revsetpredicate = registrar.revsetpredicate()
3565 revsetpredicate = registrar.revsetpredicate()
3566
3566
3567 @revsetpredicate('mq()')
3567 @revsetpredicate('mq()')
3568 def revsetmq(repo, subset, x):
3568 def revsetmq(repo, subset, x):
3569 """Changesets managed by MQ.
3569 """Changesets managed by MQ.
3570 """
3570 """
3571 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3571 revsetlang.getargs(x, 0, 0, _("mq takes no arguments"))
3572 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3572 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3573 return smartset.baseset([r for r in subset if r in applied])
3573 return smartset.baseset([r for r in subset if r in applied])
3574
3574
3575 # tell hggettext to extract docstrings from these functions:
3575 # tell hggettext to extract docstrings from these functions:
3576 i18nfunctions = [revsetmq]
3576 i18nfunctions = [revsetmq]
3577
3577
3578 def extsetup(ui):
3578 def extsetup(ui):
3579 # Ensure mq wrappers are called first, regardless of extension load order by
3579 # Ensure mq wrappers are called first, regardless of extension load order by
3580 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3580 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3581 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3581 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3582
3582
3583 extensions.wrapcommand(commands.table, 'import', mqimport)
3583 extensions.wrapcommand(commands.table, 'import', mqimport)
3584 cmdutil.summaryhooks.add('mq', summaryhook)
3584 cmdutil.summaryhooks.add('mq', summaryhook)
3585
3585
3586 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3586 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3587 entry[1].extend(mqopt)
3587 entry[1].extend(mqopt)
3588
3588
3589 def dotable(cmdtable):
3589 def dotable(cmdtable):
3590 for cmd, entry in cmdtable.iteritems():
3590 for cmd, entry in cmdtable.iteritems():
3591 cmd = cmdutil.parsealiases(cmd)[0]
3591 cmd = cmdutil.parsealiases(cmd)[0]
3592 func = entry[0]
3592 func = entry[0]
3593 if func.norepo:
3593 if func.norepo:
3594 continue
3594 continue
3595 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3595 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3596 entry[1].extend(mqopt)
3596 entry[1].extend(mqopt)
3597
3597
3598 dotable(commands.table)
3598 dotable(commands.table)
3599
3599
3600 for extname, extmodule in extensions.extensions():
3600 for extname, extmodule in extensions.extensions():
3601 if extmodule.__file__ != __file__:
3601 if extmodule.__file__ != __file__:
3602 dotable(getattr(extmodule, 'cmdtable', {}))
3602 dotable(getattr(extmodule, 'cmdtable', {}))
3603
3603
3604 colortable = {'qguard.negative': 'red',
3604 colortable = {'qguard.negative': 'red',
3605 'qguard.positive': 'yellow',
3605 'qguard.positive': 'yellow',
3606 'qguard.unguarded': 'green',
3606 'qguard.unguarded': 'green',
3607 'qseries.applied': 'blue bold underline',
3607 'qseries.applied': 'blue bold underline',
3608 'qseries.guarded': 'black bold',
3608 'qseries.guarded': 'black bold',
3609 'qseries.missing': 'red bold',
3609 'qseries.missing': 'red bold',
3610 'qseries.unapplied': 'black bold'}
3610 'qseries.unapplied': 'black bold'}
@@ -1,5445 +1,5445
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from . import (
22 from . import (
23 archival,
23 archival,
24 bookmarks,
24 bookmarks,
25 bundle2,
25 bundle2,
26 changegroup,
26 changegroup,
27 cmdutil,
27 cmdutil,
28 copies,
28 copies,
29 destutil,
29 destutil,
30 dirstateguard,
30 dirstateguard,
31 discovery,
31 discovery,
32 encoding,
32 encoding,
33 error,
33 error,
34 exchange,
34 exchange,
35 extensions,
35 extensions,
36 graphmod,
36 graphmod,
37 hbisect,
37 hbisect,
38 help,
38 help,
39 hg,
39 hg,
40 lock as lockmod,
40 lock as lockmod,
41 merge as mergemod,
41 merge as mergemod,
42 minirst,
42 minirst,
43 obsolete,
43 obsolete,
44 patch,
44 patch,
45 phases,
45 phases,
46 pycompat,
46 pycompat,
47 revset,
47 revsetlang,
48 scmutil,
48 scmutil,
49 server,
49 server,
50 sshserver,
50 sshserver,
51 streamclone,
51 streamclone,
52 templatekw,
52 templatekw,
53 ui as uimod,
53 ui as uimod,
54 util,
54 util,
55 )
55 )
56
56
57 release = lockmod.release
57 release = lockmod.release
58
58
59 table = {}
59 table = {}
60
60
61 command = cmdutil.command(table)
61 command = cmdutil.command(table)
62
62
63 # label constants
63 # label constants
64 # until 3.5, bookmarks.current was the advertised name, not
64 # until 3.5, bookmarks.current was the advertised name, not
65 # bookmarks.active, so we must use both to avoid breaking old
65 # bookmarks.active, so we must use both to avoid breaking old
66 # custom styles
66 # custom styles
67 activebookmarklabel = 'bookmarks.active bookmarks.current'
67 activebookmarklabel = 'bookmarks.active bookmarks.current'
68
68
69 # common command options
69 # common command options
70
70
71 globalopts = [
71 globalopts = [
72 ('R', 'repository', '',
72 ('R', 'repository', '',
73 _('repository root directory or name of overlay bundle file'),
73 _('repository root directory or name of overlay bundle file'),
74 _('REPO')),
74 _('REPO')),
75 ('', 'cwd', '',
75 ('', 'cwd', '',
76 _('change working directory'), _('DIR')),
76 _('change working directory'), _('DIR')),
77 ('y', 'noninteractive', None,
77 ('y', 'noninteractive', None,
78 _('do not prompt, automatically pick the first choice for all prompts')),
78 _('do not prompt, automatically pick the first choice for all prompts')),
79 ('q', 'quiet', None, _('suppress output')),
79 ('q', 'quiet', None, _('suppress output')),
80 ('v', 'verbose', None, _('enable additional output')),
80 ('v', 'verbose', None, _('enable additional output')),
81 ('', 'config', [],
81 ('', 'config', [],
82 _('set/override config option (use \'section.name=value\')'),
82 _('set/override config option (use \'section.name=value\')'),
83 _('CONFIG')),
83 _('CONFIG')),
84 ('', 'debug', None, _('enable debugging output')),
84 ('', 'debug', None, _('enable debugging output')),
85 ('', 'debugger', None, _('start debugger')),
85 ('', 'debugger', None, _('start debugger')),
86 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
86 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
87 _('ENCODE')),
87 _('ENCODE')),
88 ('', 'encodingmode', encoding.encodingmode,
88 ('', 'encodingmode', encoding.encodingmode,
89 _('set the charset encoding mode'), _('MODE')),
89 _('set the charset encoding mode'), _('MODE')),
90 ('', 'traceback', None, _('always print a traceback on exception')),
90 ('', 'traceback', None, _('always print a traceback on exception')),
91 ('', 'time', None, _('time how long the command takes')),
91 ('', 'time', None, _('time how long the command takes')),
92 ('', 'profile', None, _('print command execution profile')),
92 ('', 'profile', None, _('print command execution profile')),
93 ('', 'version', None, _('output version information and exit')),
93 ('', 'version', None, _('output version information and exit')),
94 ('h', 'help', None, _('display help and exit')),
94 ('h', 'help', None, _('display help and exit')),
95 ('', 'hidden', False, _('consider hidden changesets')),
95 ('', 'hidden', False, _('consider hidden changesets')),
96 ('', 'pager', 'auto',
96 ('', 'pager', 'auto',
97 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
97 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
98 ]
98 ]
99
99
100 dryrunopts = [('n', 'dry-run', None,
100 dryrunopts = [('n', 'dry-run', None,
101 _('do not perform actions, just print output'))]
101 _('do not perform actions, just print output'))]
102
102
103 remoteopts = [
103 remoteopts = [
104 ('e', 'ssh', '',
104 ('e', 'ssh', '',
105 _('specify ssh command to use'), _('CMD')),
105 _('specify ssh command to use'), _('CMD')),
106 ('', 'remotecmd', '',
106 ('', 'remotecmd', '',
107 _('specify hg command to run on the remote side'), _('CMD')),
107 _('specify hg command to run on the remote side'), _('CMD')),
108 ('', 'insecure', None,
108 ('', 'insecure', None,
109 _('do not verify server certificate (ignoring web.cacerts config)')),
109 _('do not verify server certificate (ignoring web.cacerts config)')),
110 ]
110 ]
111
111
112 walkopts = [
112 walkopts = [
113 ('I', 'include', [],
113 ('I', 'include', [],
114 _('include names matching the given patterns'), _('PATTERN')),
114 _('include names matching the given patterns'), _('PATTERN')),
115 ('X', 'exclude', [],
115 ('X', 'exclude', [],
116 _('exclude names matching the given patterns'), _('PATTERN')),
116 _('exclude names matching the given patterns'), _('PATTERN')),
117 ]
117 ]
118
118
119 commitopts = [
119 commitopts = [
120 ('m', 'message', '',
120 ('m', 'message', '',
121 _('use text as commit message'), _('TEXT')),
121 _('use text as commit message'), _('TEXT')),
122 ('l', 'logfile', '',
122 ('l', 'logfile', '',
123 _('read commit message from file'), _('FILE')),
123 _('read commit message from file'), _('FILE')),
124 ]
124 ]
125
125
126 commitopts2 = [
126 commitopts2 = [
127 ('d', 'date', '',
127 ('d', 'date', '',
128 _('record the specified date as commit date'), _('DATE')),
128 _('record the specified date as commit date'), _('DATE')),
129 ('u', 'user', '',
129 ('u', 'user', '',
130 _('record the specified user as committer'), _('USER')),
130 _('record the specified user as committer'), _('USER')),
131 ]
131 ]
132
132
133 # hidden for now
133 # hidden for now
134 formatteropts = [
134 formatteropts = [
135 ('T', 'template', '',
135 ('T', 'template', '',
136 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
136 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
137 ]
137 ]
138
138
139 templateopts = [
139 templateopts = [
140 ('', 'style', '',
140 ('', 'style', '',
141 _('display using template map file (DEPRECATED)'), _('STYLE')),
141 _('display using template map file (DEPRECATED)'), _('STYLE')),
142 ('T', 'template', '',
142 ('T', 'template', '',
143 _('display with template'), _('TEMPLATE')),
143 _('display with template'), _('TEMPLATE')),
144 ]
144 ]
145
145
146 logopts = [
146 logopts = [
147 ('p', 'patch', None, _('show patch')),
147 ('p', 'patch', None, _('show patch')),
148 ('g', 'git', None, _('use git extended diff format')),
148 ('g', 'git', None, _('use git extended diff format')),
149 ('l', 'limit', '',
149 ('l', 'limit', '',
150 _('limit number of changes displayed'), _('NUM')),
150 _('limit number of changes displayed'), _('NUM')),
151 ('M', 'no-merges', None, _('do not show merges')),
151 ('M', 'no-merges', None, _('do not show merges')),
152 ('', 'stat', None, _('output diffstat-style summary of changes')),
152 ('', 'stat', None, _('output diffstat-style summary of changes')),
153 ('G', 'graph', None, _("show the revision DAG")),
153 ('G', 'graph', None, _("show the revision DAG")),
154 ] + templateopts
154 ] + templateopts
155
155
156 diffopts = [
156 diffopts = [
157 ('a', 'text', None, _('treat all files as text')),
157 ('a', 'text', None, _('treat all files as text')),
158 ('g', 'git', None, _('use git extended diff format')),
158 ('g', 'git', None, _('use git extended diff format')),
159 ('', 'nodates', None, _('omit dates from diff headers'))
159 ('', 'nodates', None, _('omit dates from diff headers'))
160 ]
160 ]
161
161
162 diffwsopts = [
162 diffwsopts = [
163 ('w', 'ignore-all-space', None,
163 ('w', 'ignore-all-space', None,
164 _('ignore white space when comparing lines')),
164 _('ignore white space when comparing lines')),
165 ('b', 'ignore-space-change', None,
165 ('b', 'ignore-space-change', None,
166 _('ignore changes in the amount of white space')),
166 _('ignore changes in the amount of white space')),
167 ('B', 'ignore-blank-lines', None,
167 ('B', 'ignore-blank-lines', None,
168 _('ignore changes whose lines are all blank')),
168 _('ignore changes whose lines are all blank')),
169 ]
169 ]
170
170
171 diffopts2 = [
171 diffopts2 = [
172 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
172 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
173 ('p', 'show-function', None, _('show which function each change is in')),
173 ('p', 'show-function', None, _('show which function each change is in')),
174 ('', 'reverse', None, _('produce a diff that undoes the changes')),
174 ('', 'reverse', None, _('produce a diff that undoes the changes')),
175 ] + diffwsopts + [
175 ] + diffwsopts + [
176 ('U', 'unified', '',
176 ('U', 'unified', '',
177 _('number of lines of context to show'), _('NUM')),
177 _('number of lines of context to show'), _('NUM')),
178 ('', 'stat', None, _('output diffstat-style summary of changes')),
178 ('', 'stat', None, _('output diffstat-style summary of changes')),
179 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
179 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
180 ]
180 ]
181
181
182 mergetoolopts = [
182 mergetoolopts = [
183 ('t', 'tool', '', _('specify merge tool')),
183 ('t', 'tool', '', _('specify merge tool')),
184 ]
184 ]
185
185
186 similarityopts = [
186 similarityopts = [
187 ('s', 'similarity', '',
187 ('s', 'similarity', '',
188 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
188 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
189 ]
189 ]
190
190
191 subrepoopts = [
191 subrepoopts = [
192 ('S', 'subrepos', None,
192 ('S', 'subrepos', None,
193 _('recurse into subrepositories'))
193 _('recurse into subrepositories'))
194 ]
194 ]
195
195
196 debugrevlogopts = [
196 debugrevlogopts = [
197 ('c', 'changelog', False, _('open changelog')),
197 ('c', 'changelog', False, _('open changelog')),
198 ('m', 'manifest', False, _('open manifest')),
198 ('m', 'manifest', False, _('open manifest')),
199 ('', 'dir', '', _('open directory manifest')),
199 ('', 'dir', '', _('open directory manifest')),
200 ]
200 ]
201
201
202 # Commands start here, listed alphabetically
202 # Commands start here, listed alphabetically
203
203
204 @command('^add',
204 @command('^add',
205 walkopts + subrepoopts + dryrunopts,
205 walkopts + subrepoopts + dryrunopts,
206 _('[OPTION]... [FILE]...'),
206 _('[OPTION]... [FILE]...'),
207 inferrepo=True)
207 inferrepo=True)
208 def add(ui, repo, *pats, **opts):
208 def add(ui, repo, *pats, **opts):
209 """add the specified files on the next commit
209 """add the specified files on the next commit
210
210
211 Schedule files to be version controlled and added to the
211 Schedule files to be version controlled and added to the
212 repository.
212 repository.
213
213
214 The files will be added to the repository at the next commit. To
214 The files will be added to the repository at the next commit. To
215 undo an add before that, see :hg:`forget`.
215 undo an add before that, see :hg:`forget`.
216
216
217 If no names are given, add all files to the repository (except
217 If no names are given, add all files to the repository (except
218 files matching ``.hgignore``).
218 files matching ``.hgignore``).
219
219
220 .. container:: verbose
220 .. container:: verbose
221
221
222 Examples:
222 Examples:
223
223
224 - New (unknown) files are added
224 - New (unknown) files are added
225 automatically by :hg:`add`::
225 automatically by :hg:`add`::
226
226
227 $ ls
227 $ ls
228 foo.c
228 foo.c
229 $ hg status
229 $ hg status
230 ? foo.c
230 ? foo.c
231 $ hg add
231 $ hg add
232 adding foo.c
232 adding foo.c
233 $ hg status
233 $ hg status
234 A foo.c
234 A foo.c
235
235
236 - Specific files to be added can be specified::
236 - Specific files to be added can be specified::
237
237
238 $ ls
238 $ ls
239 bar.c foo.c
239 bar.c foo.c
240 $ hg status
240 $ hg status
241 ? bar.c
241 ? bar.c
242 ? foo.c
242 ? foo.c
243 $ hg add bar.c
243 $ hg add bar.c
244 $ hg status
244 $ hg status
245 A bar.c
245 A bar.c
246 ? foo.c
246 ? foo.c
247
247
248 Returns 0 if all files are successfully added.
248 Returns 0 if all files are successfully added.
249 """
249 """
250
250
251 m = scmutil.match(repo[None], pats, opts)
251 m = scmutil.match(repo[None], pats, opts)
252 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
252 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
253 return rejected and 1 or 0
253 return rejected and 1 or 0
254
254
255 @command('addremove',
255 @command('addremove',
256 similarityopts + subrepoopts + walkopts + dryrunopts,
256 similarityopts + subrepoopts + walkopts + dryrunopts,
257 _('[OPTION]... [FILE]...'),
257 _('[OPTION]... [FILE]...'),
258 inferrepo=True)
258 inferrepo=True)
259 def addremove(ui, repo, *pats, **opts):
259 def addremove(ui, repo, *pats, **opts):
260 """add all new files, delete all missing files
260 """add all new files, delete all missing files
261
261
262 Add all new files and remove all missing files from the
262 Add all new files and remove all missing files from the
263 repository.
263 repository.
264
264
265 Unless names are given, new files are ignored if they match any of
265 Unless names are given, new files are ignored if they match any of
266 the patterns in ``.hgignore``. As with add, these changes take
266 the patterns in ``.hgignore``. As with add, these changes take
267 effect at the next commit.
267 effect at the next commit.
268
268
269 Use the -s/--similarity option to detect renamed files. This
269 Use the -s/--similarity option to detect renamed files. This
270 option takes a percentage between 0 (disabled) and 100 (files must
270 option takes a percentage between 0 (disabled) and 100 (files must
271 be identical) as its parameter. With a parameter greater than 0,
271 be identical) as its parameter. With a parameter greater than 0,
272 this compares every removed file with every added file and records
272 this compares every removed file with every added file and records
273 those similar enough as renames. Detecting renamed files this way
273 those similar enough as renames. Detecting renamed files this way
274 can be expensive. After using this option, :hg:`status -C` can be
274 can be expensive. After using this option, :hg:`status -C` can be
275 used to check which files were identified as moved or renamed. If
275 used to check which files were identified as moved or renamed. If
276 not specified, -s/--similarity defaults to 100 and only renames of
276 not specified, -s/--similarity defaults to 100 and only renames of
277 identical files are detected.
277 identical files are detected.
278
278
279 .. container:: verbose
279 .. container:: verbose
280
280
281 Examples:
281 Examples:
282
282
283 - A number of files (bar.c and foo.c) are new,
283 - A number of files (bar.c and foo.c) are new,
284 while foobar.c has been removed (without using :hg:`remove`)
284 while foobar.c has been removed (without using :hg:`remove`)
285 from the repository::
285 from the repository::
286
286
287 $ ls
287 $ ls
288 bar.c foo.c
288 bar.c foo.c
289 $ hg status
289 $ hg status
290 ! foobar.c
290 ! foobar.c
291 ? bar.c
291 ? bar.c
292 ? foo.c
292 ? foo.c
293 $ hg addremove
293 $ hg addremove
294 adding bar.c
294 adding bar.c
295 adding foo.c
295 adding foo.c
296 removing foobar.c
296 removing foobar.c
297 $ hg status
297 $ hg status
298 A bar.c
298 A bar.c
299 A foo.c
299 A foo.c
300 R foobar.c
300 R foobar.c
301
301
302 - A file foobar.c was moved to foo.c without using :hg:`rename`.
302 - A file foobar.c was moved to foo.c without using :hg:`rename`.
303 Afterwards, it was edited slightly::
303 Afterwards, it was edited slightly::
304
304
305 $ ls
305 $ ls
306 foo.c
306 foo.c
307 $ hg status
307 $ hg status
308 ! foobar.c
308 ! foobar.c
309 ? foo.c
309 ? foo.c
310 $ hg addremove --similarity 90
310 $ hg addremove --similarity 90
311 removing foobar.c
311 removing foobar.c
312 adding foo.c
312 adding foo.c
313 recording removal of foobar.c as rename to foo.c (94% similar)
313 recording removal of foobar.c as rename to foo.c (94% similar)
314 $ hg status -C
314 $ hg status -C
315 A foo.c
315 A foo.c
316 foobar.c
316 foobar.c
317 R foobar.c
317 R foobar.c
318
318
319 Returns 0 if all files are successfully added.
319 Returns 0 if all files are successfully added.
320 """
320 """
321 try:
321 try:
322 sim = float(opts.get('similarity') or 100)
322 sim = float(opts.get('similarity') or 100)
323 except ValueError:
323 except ValueError:
324 raise error.Abort(_('similarity must be a number'))
324 raise error.Abort(_('similarity must be a number'))
325 if sim < 0 or sim > 100:
325 if sim < 0 or sim > 100:
326 raise error.Abort(_('similarity must be between 0 and 100'))
326 raise error.Abort(_('similarity must be between 0 and 100'))
327 matcher = scmutil.match(repo[None], pats, opts)
327 matcher = scmutil.match(repo[None], pats, opts)
328 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
328 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
329
329
330 @command('^annotate|blame',
330 @command('^annotate|blame',
331 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
331 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
332 ('', 'follow', None,
332 ('', 'follow', None,
333 _('follow copies/renames and list the filename (DEPRECATED)')),
333 _('follow copies/renames and list the filename (DEPRECATED)')),
334 ('', 'no-follow', None, _("don't follow copies and renames")),
334 ('', 'no-follow', None, _("don't follow copies and renames")),
335 ('a', 'text', None, _('treat all files as text')),
335 ('a', 'text', None, _('treat all files as text')),
336 ('u', 'user', None, _('list the author (long with -v)')),
336 ('u', 'user', None, _('list the author (long with -v)')),
337 ('f', 'file', None, _('list the filename')),
337 ('f', 'file', None, _('list the filename')),
338 ('d', 'date', None, _('list the date (short with -q)')),
338 ('d', 'date', None, _('list the date (short with -q)')),
339 ('n', 'number', None, _('list the revision number (default)')),
339 ('n', 'number', None, _('list the revision number (default)')),
340 ('c', 'changeset', None, _('list the changeset')),
340 ('c', 'changeset', None, _('list the changeset')),
341 ('l', 'line-number', None, _('show line number at the first appearance'))
341 ('l', 'line-number', None, _('show line number at the first appearance'))
342 ] + diffwsopts + walkopts + formatteropts,
342 ] + diffwsopts + walkopts + formatteropts,
343 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
343 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
344 inferrepo=True)
344 inferrepo=True)
345 def annotate(ui, repo, *pats, **opts):
345 def annotate(ui, repo, *pats, **opts):
346 """show changeset information by line for each file
346 """show changeset information by line for each file
347
347
348 List changes in files, showing the revision id responsible for
348 List changes in files, showing the revision id responsible for
349 each line.
349 each line.
350
350
351 This command is useful for discovering when a change was made and
351 This command is useful for discovering when a change was made and
352 by whom.
352 by whom.
353
353
354 If you include --file, --user, or --date, the revision number is
354 If you include --file, --user, or --date, the revision number is
355 suppressed unless you also include --number.
355 suppressed unless you also include --number.
356
356
357 Without the -a/--text option, annotate will avoid processing files
357 Without the -a/--text option, annotate will avoid processing files
358 it detects as binary. With -a, annotate will annotate the file
358 it detects as binary. With -a, annotate will annotate the file
359 anyway, although the results will probably be neither useful
359 anyway, although the results will probably be neither useful
360 nor desirable.
360 nor desirable.
361
361
362 Returns 0 on success.
362 Returns 0 on success.
363 """
363 """
364 ui.pager('annotate')
364 ui.pager('annotate')
365 if not pats:
365 if not pats:
366 raise error.Abort(_('at least one filename or pattern is required'))
366 raise error.Abort(_('at least one filename or pattern is required'))
367
367
368 if opts.get('follow'):
368 if opts.get('follow'):
369 # --follow is deprecated and now just an alias for -f/--file
369 # --follow is deprecated and now just an alias for -f/--file
370 # to mimic the behavior of Mercurial before version 1.5
370 # to mimic the behavior of Mercurial before version 1.5
371 opts['file'] = True
371 opts['file'] = True
372
372
373 ctx = scmutil.revsingle(repo, opts.get('rev'))
373 ctx = scmutil.revsingle(repo, opts.get('rev'))
374
374
375 fm = ui.formatter('annotate', opts)
375 fm = ui.formatter('annotate', opts)
376 if ui.quiet:
376 if ui.quiet:
377 datefunc = util.shortdate
377 datefunc = util.shortdate
378 else:
378 else:
379 datefunc = util.datestr
379 datefunc = util.datestr
380 if ctx.rev() is None:
380 if ctx.rev() is None:
381 def hexfn(node):
381 def hexfn(node):
382 if node is None:
382 if node is None:
383 return None
383 return None
384 else:
384 else:
385 return fm.hexfunc(node)
385 return fm.hexfunc(node)
386 if opts.get('changeset'):
386 if opts.get('changeset'):
387 # omit "+" suffix which is appended to node hex
387 # omit "+" suffix which is appended to node hex
388 def formatrev(rev):
388 def formatrev(rev):
389 if rev is None:
389 if rev is None:
390 return '%d' % ctx.p1().rev()
390 return '%d' % ctx.p1().rev()
391 else:
391 else:
392 return '%d' % rev
392 return '%d' % rev
393 else:
393 else:
394 def formatrev(rev):
394 def formatrev(rev):
395 if rev is None:
395 if rev is None:
396 return '%d+' % ctx.p1().rev()
396 return '%d+' % ctx.p1().rev()
397 else:
397 else:
398 return '%d ' % rev
398 return '%d ' % rev
399 def formathex(hex):
399 def formathex(hex):
400 if hex is None:
400 if hex is None:
401 return '%s+' % fm.hexfunc(ctx.p1().node())
401 return '%s+' % fm.hexfunc(ctx.p1().node())
402 else:
402 else:
403 return '%s ' % hex
403 return '%s ' % hex
404 else:
404 else:
405 hexfn = fm.hexfunc
405 hexfn = fm.hexfunc
406 formatrev = formathex = str
406 formatrev = formathex = str
407
407
408 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
408 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
409 ('number', ' ', lambda x: x[0].rev(), formatrev),
409 ('number', ' ', lambda x: x[0].rev(), formatrev),
410 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
410 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
411 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
411 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
412 ('file', ' ', lambda x: x[0].path(), str),
412 ('file', ' ', lambda x: x[0].path(), str),
413 ('line_number', ':', lambda x: x[1], str),
413 ('line_number', ':', lambda x: x[1], str),
414 ]
414 ]
415 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
415 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
416
416
417 if (not opts.get('user') and not opts.get('changeset')
417 if (not opts.get('user') and not opts.get('changeset')
418 and not opts.get('date') and not opts.get('file')):
418 and not opts.get('date') and not opts.get('file')):
419 opts['number'] = True
419 opts['number'] = True
420
420
421 linenumber = opts.get('line_number') is not None
421 linenumber = opts.get('line_number') is not None
422 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
422 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
423 raise error.Abort(_('at least one of -n/-c is required for -l'))
423 raise error.Abort(_('at least one of -n/-c is required for -l'))
424
424
425 if fm.isplain():
425 if fm.isplain():
426 def makefunc(get, fmt):
426 def makefunc(get, fmt):
427 return lambda x: fmt(get(x))
427 return lambda x: fmt(get(x))
428 else:
428 else:
429 def makefunc(get, fmt):
429 def makefunc(get, fmt):
430 return get
430 return get
431 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
431 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
432 if opts.get(op)]
432 if opts.get(op)]
433 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
433 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
434 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
434 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
435 if opts.get(op))
435 if opts.get(op))
436
436
437 def bad(x, y):
437 def bad(x, y):
438 raise error.Abort("%s: %s" % (x, y))
438 raise error.Abort("%s: %s" % (x, y))
439
439
440 m = scmutil.match(ctx, pats, opts, badfn=bad)
440 m = scmutil.match(ctx, pats, opts, badfn=bad)
441
441
442 follow = not opts.get('no_follow')
442 follow = not opts.get('no_follow')
443 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
443 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
444 whitespace=True)
444 whitespace=True)
445 for abs in ctx.walk(m):
445 for abs in ctx.walk(m):
446 fctx = ctx[abs]
446 fctx = ctx[abs]
447 if not opts.get('text') and util.binary(fctx.data()):
447 if not opts.get('text') and util.binary(fctx.data()):
448 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
448 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
449 continue
449 continue
450
450
451 lines = fctx.annotate(follow=follow, linenumber=linenumber,
451 lines = fctx.annotate(follow=follow, linenumber=linenumber,
452 diffopts=diffopts)
452 diffopts=diffopts)
453 if not lines:
453 if not lines:
454 continue
454 continue
455 formats = []
455 formats = []
456 pieces = []
456 pieces = []
457
457
458 for f, sep in funcmap:
458 for f, sep in funcmap:
459 l = [f(n) for n, dummy in lines]
459 l = [f(n) for n, dummy in lines]
460 if fm.isplain():
460 if fm.isplain():
461 sizes = [encoding.colwidth(x) for x in l]
461 sizes = [encoding.colwidth(x) for x in l]
462 ml = max(sizes)
462 ml = max(sizes)
463 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
463 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
464 else:
464 else:
465 formats.append(['%s' for x in l])
465 formats.append(['%s' for x in l])
466 pieces.append(l)
466 pieces.append(l)
467
467
468 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
468 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
469 fm.startitem()
469 fm.startitem()
470 fm.write(fields, "".join(f), *p)
470 fm.write(fields, "".join(f), *p)
471 fm.write('line', ": %s", l[1])
471 fm.write('line', ": %s", l[1])
472
472
473 if not lines[-1][1].endswith('\n'):
473 if not lines[-1][1].endswith('\n'):
474 fm.plain('\n')
474 fm.plain('\n')
475
475
476 fm.end()
476 fm.end()
477
477
478 @command('archive',
478 @command('archive',
479 [('', 'no-decode', None, _('do not pass files through decoders')),
479 [('', 'no-decode', None, _('do not pass files through decoders')),
480 ('p', 'prefix', '', _('directory prefix for files in archive'),
480 ('p', 'prefix', '', _('directory prefix for files in archive'),
481 _('PREFIX')),
481 _('PREFIX')),
482 ('r', 'rev', '', _('revision to distribute'), _('REV')),
482 ('r', 'rev', '', _('revision to distribute'), _('REV')),
483 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
483 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
484 ] + subrepoopts + walkopts,
484 ] + subrepoopts + walkopts,
485 _('[OPTION]... DEST'))
485 _('[OPTION]... DEST'))
486 def archive(ui, repo, dest, **opts):
486 def archive(ui, repo, dest, **opts):
487 '''create an unversioned archive of a repository revision
487 '''create an unversioned archive of a repository revision
488
488
489 By default, the revision used is the parent of the working
489 By default, the revision used is the parent of the working
490 directory; use -r/--rev to specify a different revision.
490 directory; use -r/--rev to specify a different revision.
491
491
492 The archive type is automatically detected based on file
492 The archive type is automatically detected based on file
493 extension (to override, use -t/--type).
493 extension (to override, use -t/--type).
494
494
495 .. container:: verbose
495 .. container:: verbose
496
496
497 Examples:
497 Examples:
498
498
499 - create a zip file containing the 1.0 release::
499 - create a zip file containing the 1.0 release::
500
500
501 hg archive -r 1.0 project-1.0.zip
501 hg archive -r 1.0 project-1.0.zip
502
502
503 - create a tarball excluding .hg files::
503 - create a tarball excluding .hg files::
504
504
505 hg archive project.tar.gz -X ".hg*"
505 hg archive project.tar.gz -X ".hg*"
506
506
507 Valid types are:
507 Valid types are:
508
508
509 :``files``: a directory full of files (default)
509 :``files``: a directory full of files (default)
510 :``tar``: tar archive, uncompressed
510 :``tar``: tar archive, uncompressed
511 :``tbz2``: tar archive, compressed using bzip2
511 :``tbz2``: tar archive, compressed using bzip2
512 :``tgz``: tar archive, compressed using gzip
512 :``tgz``: tar archive, compressed using gzip
513 :``uzip``: zip archive, uncompressed
513 :``uzip``: zip archive, uncompressed
514 :``zip``: zip archive, compressed using deflate
514 :``zip``: zip archive, compressed using deflate
515
515
516 The exact name of the destination archive or directory is given
516 The exact name of the destination archive or directory is given
517 using a format string; see :hg:`help export` for details.
517 using a format string; see :hg:`help export` for details.
518
518
519 Each member added to an archive file has a directory prefix
519 Each member added to an archive file has a directory prefix
520 prepended. Use -p/--prefix to specify a format string for the
520 prepended. Use -p/--prefix to specify a format string for the
521 prefix. The default is the basename of the archive, with suffixes
521 prefix. The default is the basename of the archive, with suffixes
522 removed.
522 removed.
523
523
524 Returns 0 on success.
524 Returns 0 on success.
525 '''
525 '''
526
526
527 ctx = scmutil.revsingle(repo, opts.get('rev'))
527 ctx = scmutil.revsingle(repo, opts.get('rev'))
528 if not ctx:
528 if not ctx:
529 raise error.Abort(_('no working directory: please specify a revision'))
529 raise error.Abort(_('no working directory: please specify a revision'))
530 node = ctx.node()
530 node = ctx.node()
531 dest = cmdutil.makefilename(repo, dest, node)
531 dest = cmdutil.makefilename(repo, dest, node)
532 if os.path.realpath(dest) == repo.root:
532 if os.path.realpath(dest) == repo.root:
533 raise error.Abort(_('repository root cannot be destination'))
533 raise error.Abort(_('repository root cannot be destination'))
534
534
535 kind = opts.get('type') or archival.guesskind(dest) or 'files'
535 kind = opts.get('type') or archival.guesskind(dest) or 'files'
536 prefix = opts.get('prefix')
536 prefix = opts.get('prefix')
537
537
538 if dest == '-':
538 if dest == '-':
539 if kind == 'files':
539 if kind == 'files':
540 raise error.Abort(_('cannot archive plain files to stdout'))
540 raise error.Abort(_('cannot archive plain files to stdout'))
541 dest = cmdutil.makefileobj(repo, dest)
541 dest = cmdutil.makefileobj(repo, dest)
542 if not prefix:
542 if not prefix:
543 prefix = os.path.basename(repo.root) + '-%h'
543 prefix = os.path.basename(repo.root) + '-%h'
544
544
545 prefix = cmdutil.makefilename(repo, prefix, node)
545 prefix = cmdutil.makefilename(repo, prefix, node)
546 matchfn = scmutil.match(ctx, [], opts)
546 matchfn = scmutil.match(ctx, [], opts)
547 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
547 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
548 matchfn, prefix, subrepos=opts.get('subrepos'))
548 matchfn, prefix, subrepos=opts.get('subrepos'))
549
549
550 @command('backout',
550 @command('backout',
551 [('', 'merge', None, _('merge with old dirstate parent after backout')),
551 [('', 'merge', None, _('merge with old dirstate parent after backout')),
552 ('', 'commit', None,
552 ('', 'commit', None,
553 _('commit if no conflicts were encountered (DEPRECATED)')),
553 _('commit if no conflicts were encountered (DEPRECATED)')),
554 ('', 'no-commit', None, _('do not commit')),
554 ('', 'no-commit', None, _('do not commit')),
555 ('', 'parent', '',
555 ('', 'parent', '',
556 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
556 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
557 ('r', 'rev', '', _('revision to backout'), _('REV')),
557 ('r', 'rev', '', _('revision to backout'), _('REV')),
558 ('e', 'edit', False, _('invoke editor on commit messages')),
558 ('e', 'edit', False, _('invoke editor on commit messages')),
559 ] + mergetoolopts + walkopts + commitopts + commitopts2,
559 ] + mergetoolopts + walkopts + commitopts + commitopts2,
560 _('[OPTION]... [-r] REV'))
560 _('[OPTION]... [-r] REV'))
561 def backout(ui, repo, node=None, rev=None, **opts):
561 def backout(ui, repo, node=None, rev=None, **opts):
562 '''reverse effect of earlier changeset
562 '''reverse effect of earlier changeset
563
563
564 Prepare a new changeset with the effect of REV undone in the
564 Prepare a new changeset with the effect of REV undone in the
565 current working directory. If no conflicts were encountered,
565 current working directory. If no conflicts were encountered,
566 it will be committed immediately.
566 it will be committed immediately.
567
567
568 If REV is the parent of the working directory, then this new changeset
568 If REV is the parent of the working directory, then this new changeset
569 is committed automatically (unless --no-commit is specified).
569 is committed automatically (unless --no-commit is specified).
570
570
571 .. note::
571 .. note::
572
572
573 :hg:`backout` cannot be used to fix either an unwanted or
573 :hg:`backout` cannot be used to fix either an unwanted or
574 incorrect merge.
574 incorrect merge.
575
575
576 .. container:: verbose
576 .. container:: verbose
577
577
578 Examples:
578 Examples:
579
579
580 - Reverse the effect of the parent of the working directory.
580 - Reverse the effect of the parent of the working directory.
581 This backout will be committed immediately::
581 This backout will be committed immediately::
582
582
583 hg backout -r .
583 hg backout -r .
584
584
585 - Reverse the effect of previous bad revision 23::
585 - Reverse the effect of previous bad revision 23::
586
586
587 hg backout -r 23
587 hg backout -r 23
588
588
589 - Reverse the effect of previous bad revision 23 and
589 - Reverse the effect of previous bad revision 23 and
590 leave changes uncommitted::
590 leave changes uncommitted::
591
591
592 hg backout -r 23 --no-commit
592 hg backout -r 23 --no-commit
593 hg commit -m "Backout revision 23"
593 hg commit -m "Backout revision 23"
594
594
595 By default, the pending changeset will have one parent,
595 By default, the pending changeset will have one parent,
596 maintaining a linear history. With --merge, the pending
596 maintaining a linear history. With --merge, the pending
597 changeset will instead have two parents: the old parent of the
597 changeset will instead have two parents: the old parent of the
598 working directory and a new child of REV that simply undoes REV.
598 working directory and a new child of REV that simply undoes REV.
599
599
600 Before version 1.7, the behavior without --merge was equivalent
600 Before version 1.7, the behavior without --merge was equivalent
601 to specifying --merge followed by :hg:`update --clean .` to
601 to specifying --merge followed by :hg:`update --clean .` to
602 cancel the merge and leave the child of REV as a head to be
602 cancel the merge and leave the child of REV as a head to be
603 merged separately.
603 merged separately.
604
604
605 See :hg:`help dates` for a list of formats valid for -d/--date.
605 See :hg:`help dates` for a list of formats valid for -d/--date.
606
606
607 See :hg:`help revert` for a way to restore files to the state
607 See :hg:`help revert` for a way to restore files to the state
608 of another revision.
608 of another revision.
609
609
610 Returns 0 on success, 1 if nothing to backout or there are unresolved
610 Returns 0 on success, 1 if nothing to backout or there are unresolved
611 files.
611 files.
612 '''
612 '''
613 wlock = lock = None
613 wlock = lock = None
614 try:
614 try:
615 wlock = repo.wlock()
615 wlock = repo.wlock()
616 lock = repo.lock()
616 lock = repo.lock()
617 return _dobackout(ui, repo, node, rev, **opts)
617 return _dobackout(ui, repo, node, rev, **opts)
618 finally:
618 finally:
619 release(lock, wlock)
619 release(lock, wlock)
620
620
621 def _dobackout(ui, repo, node=None, rev=None, **opts):
621 def _dobackout(ui, repo, node=None, rev=None, **opts):
622 if opts.get('commit') and opts.get('no_commit'):
622 if opts.get('commit') and opts.get('no_commit'):
623 raise error.Abort(_("cannot use --commit with --no-commit"))
623 raise error.Abort(_("cannot use --commit with --no-commit"))
624 if opts.get('merge') and opts.get('no_commit'):
624 if opts.get('merge') and opts.get('no_commit'):
625 raise error.Abort(_("cannot use --merge with --no-commit"))
625 raise error.Abort(_("cannot use --merge with --no-commit"))
626
626
627 if rev and node:
627 if rev and node:
628 raise error.Abort(_("please specify just one revision"))
628 raise error.Abort(_("please specify just one revision"))
629
629
630 if not rev:
630 if not rev:
631 rev = node
631 rev = node
632
632
633 if not rev:
633 if not rev:
634 raise error.Abort(_("please specify a revision to backout"))
634 raise error.Abort(_("please specify a revision to backout"))
635
635
636 date = opts.get('date')
636 date = opts.get('date')
637 if date:
637 if date:
638 opts['date'] = util.parsedate(date)
638 opts['date'] = util.parsedate(date)
639
639
640 cmdutil.checkunfinished(repo)
640 cmdutil.checkunfinished(repo)
641 cmdutil.bailifchanged(repo)
641 cmdutil.bailifchanged(repo)
642 node = scmutil.revsingle(repo, rev).node()
642 node = scmutil.revsingle(repo, rev).node()
643
643
644 op1, op2 = repo.dirstate.parents()
644 op1, op2 = repo.dirstate.parents()
645 if not repo.changelog.isancestor(node, op1):
645 if not repo.changelog.isancestor(node, op1):
646 raise error.Abort(_('cannot backout change that is not an ancestor'))
646 raise error.Abort(_('cannot backout change that is not an ancestor'))
647
647
648 p1, p2 = repo.changelog.parents(node)
648 p1, p2 = repo.changelog.parents(node)
649 if p1 == nullid:
649 if p1 == nullid:
650 raise error.Abort(_('cannot backout a change with no parents'))
650 raise error.Abort(_('cannot backout a change with no parents'))
651 if p2 != nullid:
651 if p2 != nullid:
652 if not opts.get('parent'):
652 if not opts.get('parent'):
653 raise error.Abort(_('cannot backout a merge changeset'))
653 raise error.Abort(_('cannot backout a merge changeset'))
654 p = repo.lookup(opts['parent'])
654 p = repo.lookup(opts['parent'])
655 if p not in (p1, p2):
655 if p not in (p1, p2):
656 raise error.Abort(_('%s is not a parent of %s') %
656 raise error.Abort(_('%s is not a parent of %s') %
657 (short(p), short(node)))
657 (short(p), short(node)))
658 parent = p
658 parent = p
659 else:
659 else:
660 if opts.get('parent'):
660 if opts.get('parent'):
661 raise error.Abort(_('cannot use --parent on non-merge changeset'))
661 raise error.Abort(_('cannot use --parent on non-merge changeset'))
662 parent = p1
662 parent = p1
663
663
664 # the backout should appear on the same branch
664 # the backout should appear on the same branch
665 branch = repo.dirstate.branch()
665 branch = repo.dirstate.branch()
666 bheads = repo.branchheads(branch)
666 bheads = repo.branchheads(branch)
667 rctx = scmutil.revsingle(repo, hex(parent))
667 rctx = scmutil.revsingle(repo, hex(parent))
668 if not opts.get('merge') and op1 != node:
668 if not opts.get('merge') and op1 != node:
669 dsguard = dirstateguard.dirstateguard(repo, 'backout')
669 dsguard = dirstateguard.dirstateguard(repo, 'backout')
670 try:
670 try:
671 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
671 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
672 'backout')
672 'backout')
673 stats = mergemod.update(repo, parent, True, True, node, False)
673 stats = mergemod.update(repo, parent, True, True, node, False)
674 repo.setparents(op1, op2)
674 repo.setparents(op1, op2)
675 dsguard.close()
675 dsguard.close()
676 hg._showstats(repo, stats)
676 hg._showstats(repo, stats)
677 if stats[3]:
677 if stats[3]:
678 repo.ui.status(_("use 'hg resolve' to retry unresolved "
678 repo.ui.status(_("use 'hg resolve' to retry unresolved "
679 "file merges\n"))
679 "file merges\n"))
680 return 1
680 return 1
681 finally:
681 finally:
682 ui.setconfig('ui', 'forcemerge', '', '')
682 ui.setconfig('ui', 'forcemerge', '', '')
683 lockmod.release(dsguard)
683 lockmod.release(dsguard)
684 else:
684 else:
685 hg.clean(repo, node, show_stats=False)
685 hg.clean(repo, node, show_stats=False)
686 repo.dirstate.setbranch(branch)
686 repo.dirstate.setbranch(branch)
687 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
687 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
688
688
689 if opts.get('no_commit'):
689 if opts.get('no_commit'):
690 msg = _("changeset %s backed out, "
690 msg = _("changeset %s backed out, "
691 "don't forget to commit.\n")
691 "don't forget to commit.\n")
692 ui.status(msg % short(node))
692 ui.status(msg % short(node))
693 return 0
693 return 0
694
694
695 def commitfunc(ui, repo, message, match, opts):
695 def commitfunc(ui, repo, message, match, opts):
696 editform = 'backout'
696 editform = 'backout'
697 e = cmdutil.getcommiteditor(editform=editform, **opts)
697 e = cmdutil.getcommiteditor(editform=editform, **opts)
698 if not message:
698 if not message:
699 # we don't translate commit messages
699 # we don't translate commit messages
700 message = "Backed out changeset %s" % short(node)
700 message = "Backed out changeset %s" % short(node)
701 e = cmdutil.getcommiteditor(edit=True, editform=editform)
701 e = cmdutil.getcommiteditor(edit=True, editform=editform)
702 return repo.commit(message, opts.get('user'), opts.get('date'),
702 return repo.commit(message, opts.get('user'), opts.get('date'),
703 match, editor=e)
703 match, editor=e)
704 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
704 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
705 if not newnode:
705 if not newnode:
706 ui.status(_("nothing changed\n"))
706 ui.status(_("nothing changed\n"))
707 return 1
707 return 1
708 cmdutil.commitstatus(repo, newnode, branch, bheads)
708 cmdutil.commitstatus(repo, newnode, branch, bheads)
709
709
710 def nice(node):
710 def nice(node):
711 return '%d:%s' % (repo.changelog.rev(node), short(node))
711 return '%d:%s' % (repo.changelog.rev(node), short(node))
712 ui.status(_('changeset %s backs out changeset %s\n') %
712 ui.status(_('changeset %s backs out changeset %s\n') %
713 (nice(repo.changelog.tip()), nice(node)))
713 (nice(repo.changelog.tip()), nice(node)))
714 if opts.get('merge') and op1 != node:
714 if opts.get('merge') and op1 != node:
715 hg.clean(repo, op1, show_stats=False)
715 hg.clean(repo, op1, show_stats=False)
716 ui.status(_('merging with changeset %s\n')
716 ui.status(_('merging with changeset %s\n')
717 % nice(repo.changelog.tip()))
717 % nice(repo.changelog.tip()))
718 try:
718 try:
719 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
719 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
720 'backout')
720 'backout')
721 return hg.merge(repo, hex(repo.changelog.tip()))
721 return hg.merge(repo, hex(repo.changelog.tip()))
722 finally:
722 finally:
723 ui.setconfig('ui', 'forcemerge', '', '')
723 ui.setconfig('ui', 'forcemerge', '', '')
724 return 0
724 return 0
725
725
726 @command('bisect',
726 @command('bisect',
727 [('r', 'reset', False, _('reset bisect state')),
727 [('r', 'reset', False, _('reset bisect state')),
728 ('g', 'good', False, _('mark changeset good')),
728 ('g', 'good', False, _('mark changeset good')),
729 ('b', 'bad', False, _('mark changeset bad')),
729 ('b', 'bad', False, _('mark changeset bad')),
730 ('s', 'skip', False, _('skip testing changeset')),
730 ('s', 'skip', False, _('skip testing changeset')),
731 ('e', 'extend', False, _('extend the bisect range')),
731 ('e', 'extend', False, _('extend the bisect range')),
732 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
732 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
733 ('U', 'noupdate', False, _('do not update to target'))],
733 ('U', 'noupdate', False, _('do not update to target'))],
734 _("[-gbsr] [-U] [-c CMD] [REV]"))
734 _("[-gbsr] [-U] [-c CMD] [REV]"))
735 def bisect(ui, repo, rev=None, extra=None, command=None,
735 def bisect(ui, repo, rev=None, extra=None, command=None,
736 reset=None, good=None, bad=None, skip=None, extend=None,
736 reset=None, good=None, bad=None, skip=None, extend=None,
737 noupdate=None):
737 noupdate=None):
738 """subdivision search of changesets
738 """subdivision search of changesets
739
739
740 This command helps to find changesets which introduce problems. To
740 This command helps to find changesets which introduce problems. To
741 use, mark the earliest changeset you know exhibits the problem as
741 use, mark the earliest changeset you know exhibits the problem as
742 bad, then mark the latest changeset which is free from the problem
742 bad, then mark the latest changeset which is free from the problem
743 as good. Bisect will update your working directory to a revision
743 as good. Bisect will update your working directory to a revision
744 for testing (unless the -U/--noupdate option is specified). Once
744 for testing (unless the -U/--noupdate option is specified). Once
745 you have performed tests, mark the working directory as good or
745 you have performed tests, mark the working directory as good or
746 bad, and bisect will either update to another candidate changeset
746 bad, and bisect will either update to another candidate changeset
747 or announce that it has found the bad revision.
747 or announce that it has found the bad revision.
748
748
749 As a shortcut, you can also use the revision argument to mark a
749 As a shortcut, you can also use the revision argument to mark a
750 revision as good or bad without checking it out first.
750 revision as good or bad without checking it out first.
751
751
752 If you supply a command, it will be used for automatic bisection.
752 If you supply a command, it will be used for automatic bisection.
753 The environment variable HG_NODE will contain the ID of the
753 The environment variable HG_NODE will contain the ID of the
754 changeset being tested. The exit status of the command will be
754 changeset being tested. The exit status of the command will be
755 used to mark revisions as good or bad: status 0 means good, 125
755 used to mark revisions as good or bad: status 0 means good, 125
756 means to skip the revision, 127 (command not found) will abort the
756 means to skip the revision, 127 (command not found) will abort the
757 bisection, and any other non-zero exit status means the revision
757 bisection, and any other non-zero exit status means the revision
758 is bad.
758 is bad.
759
759
760 .. container:: verbose
760 .. container:: verbose
761
761
762 Some examples:
762 Some examples:
763
763
764 - start a bisection with known bad revision 34, and good revision 12::
764 - start a bisection with known bad revision 34, and good revision 12::
765
765
766 hg bisect --bad 34
766 hg bisect --bad 34
767 hg bisect --good 12
767 hg bisect --good 12
768
768
769 - advance the current bisection by marking current revision as good or
769 - advance the current bisection by marking current revision as good or
770 bad::
770 bad::
771
771
772 hg bisect --good
772 hg bisect --good
773 hg bisect --bad
773 hg bisect --bad
774
774
775 - mark the current revision, or a known revision, to be skipped (e.g. if
775 - mark the current revision, or a known revision, to be skipped (e.g. if
776 that revision is not usable because of another issue)::
776 that revision is not usable because of another issue)::
777
777
778 hg bisect --skip
778 hg bisect --skip
779 hg bisect --skip 23
779 hg bisect --skip 23
780
780
781 - skip all revisions that do not touch directories ``foo`` or ``bar``::
781 - skip all revisions that do not touch directories ``foo`` or ``bar``::
782
782
783 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
783 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
784
784
785 - forget the current bisection::
785 - forget the current bisection::
786
786
787 hg bisect --reset
787 hg bisect --reset
788
788
789 - use 'make && make tests' to automatically find the first broken
789 - use 'make && make tests' to automatically find the first broken
790 revision::
790 revision::
791
791
792 hg bisect --reset
792 hg bisect --reset
793 hg bisect --bad 34
793 hg bisect --bad 34
794 hg bisect --good 12
794 hg bisect --good 12
795 hg bisect --command "make && make tests"
795 hg bisect --command "make && make tests"
796
796
797 - see all changesets whose states are already known in the current
797 - see all changesets whose states are already known in the current
798 bisection::
798 bisection::
799
799
800 hg log -r "bisect(pruned)"
800 hg log -r "bisect(pruned)"
801
801
802 - see the changeset currently being bisected (especially useful
802 - see the changeset currently being bisected (especially useful
803 if running with -U/--noupdate)::
803 if running with -U/--noupdate)::
804
804
805 hg log -r "bisect(current)"
805 hg log -r "bisect(current)"
806
806
807 - see all changesets that took part in the current bisection::
807 - see all changesets that took part in the current bisection::
808
808
809 hg log -r "bisect(range)"
809 hg log -r "bisect(range)"
810
810
811 - you can even get a nice graph::
811 - you can even get a nice graph::
812
812
813 hg log --graph -r "bisect(range)"
813 hg log --graph -r "bisect(range)"
814
814
815 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
815 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
816
816
817 Returns 0 on success.
817 Returns 0 on success.
818 """
818 """
819 # backward compatibility
819 # backward compatibility
820 if rev in "good bad reset init".split():
820 if rev in "good bad reset init".split():
821 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
821 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
822 cmd, rev, extra = rev, extra, None
822 cmd, rev, extra = rev, extra, None
823 if cmd == "good":
823 if cmd == "good":
824 good = True
824 good = True
825 elif cmd == "bad":
825 elif cmd == "bad":
826 bad = True
826 bad = True
827 else:
827 else:
828 reset = True
828 reset = True
829 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
829 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
830 raise error.Abort(_('incompatible arguments'))
830 raise error.Abort(_('incompatible arguments'))
831
831
832 cmdutil.checkunfinished(repo)
832 cmdutil.checkunfinished(repo)
833
833
834 if reset:
834 if reset:
835 hbisect.resetstate(repo)
835 hbisect.resetstate(repo)
836 return
836 return
837
837
838 state = hbisect.load_state(repo)
838 state = hbisect.load_state(repo)
839
839
840 # update state
840 # update state
841 if good or bad or skip:
841 if good or bad or skip:
842 if rev:
842 if rev:
843 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
843 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
844 else:
844 else:
845 nodes = [repo.lookup('.')]
845 nodes = [repo.lookup('.')]
846 if good:
846 if good:
847 state['good'] += nodes
847 state['good'] += nodes
848 elif bad:
848 elif bad:
849 state['bad'] += nodes
849 state['bad'] += nodes
850 elif skip:
850 elif skip:
851 state['skip'] += nodes
851 state['skip'] += nodes
852 hbisect.save_state(repo, state)
852 hbisect.save_state(repo, state)
853 if not (state['good'] and state['bad']):
853 if not (state['good'] and state['bad']):
854 return
854 return
855
855
856 def mayupdate(repo, node, show_stats=True):
856 def mayupdate(repo, node, show_stats=True):
857 """common used update sequence"""
857 """common used update sequence"""
858 if noupdate:
858 if noupdate:
859 return
859 return
860 cmdutil.bailifchanged(repo)
860 cmdutil.bailifchanged(repo)
861 return hg.clean(repo, node, show_stats=show_stats)
861 return hg.clean(repo, node, show_stats=show_stats)
862
862
863 displayer = cmdutil.show_changeset(ui, repo, {})
863 displayer = cmdutil.show_changeset(ui, repo, {})
864
864
865 if command:
865 if command:
866 changesets = 1
866 changesets = 1
867 if noupdate:
867 if noupdate:
868 try:
868 try:
869 node = state['current'][0]
869 node = state['current'][0]
870 except LookupError:
870 except LookupError:
871 raise error.Abort(_('current bisect revision is unknown - '
871 raise error.Abort(_('current bisect revision is unknown - '
872 'start a new bisect to fix'))
872 'start a new bisect to fix'))
873 else:
873 else:
874 node, p2 = repo.dirstate.parents()
874 node, p2 = repo.dirstate.parents()
875 if p2 != nullid:
875 if p2 != nullid:
876 raise error.Abort(_('current bisect revision is a merge'))
876 raise error.Abort(_('current bisect revision is a merge'))
877 if rev:
877 if rev:
878 node = repo[scmutil.revsingle(repo, rev, node)].node()
878 node = repo[scmutil.revsingle(repo, rev, node)].node()
879 try:
879 try:
880 while changesets:
880 while changesets:
881 # update state
881 # update state
882 state['current'] = [node]
882 state['current'] = [node]
883 hbisect.save_state(repo, state)
883 hbisect.save_state(repo, state)
884 status = ui.system(command, environ={'HG_NODE': hex(node)})
884 status = ui.system(command, environ={'HG_NODE': hex(node)})
885 if status == 125:
885 if status == 125:
886 transition = "skip"
886 transition = "skip"
887 elif status == 0:
887 elif status == 0:
888 transition = "good"
888 transition = "good"
889 # status < 0 means process was killed
889 # status < 0 means process was killed
890 elif status == 127:
890 elif status == 127:
891 raise error.Abort(_("failed to execute %s") % command)
891 raise error.Abort(_("failed to execute %s") % command)
892 elif status < 0:
892 elif status < 0:
893 raise error.Abort(_("%s killed") % command)
893 raise error.Abort(_("%s killed") % command)
894 else:
894 else:
895 transition = "bad"
895 transition = "bad"
896 state[transition].append(node)
896 state[transition].append(node)
897 ctx = repo[node]
897 ctx = repo[node]
898 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
898 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
899 hbisect.checkstate(state)
899 hbisect.checkstate(state)
900 # bisect
900 # bisect
901 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
901 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
902 # update to next check
902 # update to next check
903 node = nodes[0]
903 node = nodes[0]
904 mayupdate(repo, node, show_stats=False)
904 mayupdate(repo, node, show_stats=False)
905 finally:
905 finally:
906 state['current'] = [node]
906 state['current'] = [node]
907 hbisect.save_state(repo, state)
907 hbisect.save_state(repo, state)
908 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
908 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
909 return
909 return
910
910
911 hbisect.checkstate(state)
911 hbisect.checkstate(state)
912
912
913 # actually bisect
913 # actually bisect
914 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
914 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
915 if extend:
915 if extend:
916 if not changesets:
916 if not changesets:
917 extendnode = hbisect.extendrange(repo, state, nodes, good)
917 extendnode = hbisect.extendrange(repo, state, nodes, good)
918 if extendnode is not None:
918 if extendnode is not None:
919 ui.write(_("Extending search to changeset %d:%s\n")
919 ui.write(_("Extending search to changeset %d:%s\n")
920 % (extendnode.rev(), extendnode))
920 % (extendnode.rev(), extendnode))
921 state['current'] = [extendnode.node()]
921 state['current'] = [extendnode.node()]
922 hbisect.save_state(repo, state)
922 hbisect.save_state(repo, state)
923 return mayupdate(repo, extendnode.node())
923 return mayupdate(repo, extendnode.node())
924 raise error.Abort(_("nothing to extend"))
924 raise error.Abort(_("nothing to extend"))
925
925
926 if changesets == 0:
926 if changesets == 0:
927 hbisect.printresult(ui, repo, state, displayer, nodes, good)
927 hbisect.printresult(ui, repo, state, displayer, nodes, good)
928 else:
928 else:
929 assert len(nodes) == 1 # only a single node can be tested next
929 assert len(nodes) == 1 # only a single node can be tested next
930 node = nodes[0]
930 node = nodes[0]
931 # compute the approximate number of remaining tests
931 # compute the approximate number of remaining tests
932 tests, size = 0, 2
932 tests, size = 0, 2
933 while size <= changesets:
933 while size <= changesets:
934 tests, size = tests + 1, size * 2
934 tests, size = tests + 1, size * 2
935 rev = repo.changelog.rev(node)
935 rev = repo.changelog.rev(node)
936 ui.write(_("Testing changeset %d:%s "
936 ui.write(_("Testing changeset %d:%s "
937 "(%d changesets remaining, ~%d tests)\n")
937 "(%d changesets remaining, ~%d tests)\n")
938 % (rev, short(node), changesets, tests))
938 % (rev, short(node), changesets, tests))
939 state['current'] = [node]
939 state['current'] = [node]
940 hbisect.save_state(repo, state)
940 hbisect.save_state(repo, state)
941 return mayupdate(repo, node)
941 return mayupdate(repo, node)
942
942
943 @command('bookmarks|bookmark',
943 @command('bookmarks|bookmark',
944 [('f', 'force', False, _('force')),
944 [('f', 'force', False, _('force')),
945 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
945 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
946 ('d', 'delete', False, _('delete a given bookmark')),
946 ('d', 'delete', False, _('delete a given bookmark')),
947 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
947 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
948 ('i', 'inactive', False, _('mark a bookmark inactive')),
948 ('i', 'inactive', False, _('mark a bookmark inactive')),
949 ] + formatteropts,
949 ] + formatteropts,
950 _('hg bookmarks [OPTIONS]... [NAME]...'))
950 _('hg bookmarks [OPTIONS]... [NAME]...'))
951 def bookmark(ui, repo, *names, **opts):
951 def bookmark(ui, repo, *names, **opts):
952 '''create a new bookmark or list existing bookmarks
952 '''create a new bookmark or list existing bookmarks
953
953
954 Bookmarks are labels on changesets to help track lines of development.
954 Bookmarks are labels on changesets to help track lines of development.
955 Bookmarks are unversioned and can be moved, renamed and deleted.
955 Bookmarks are unversioned and can be moved, renamed and deleted.
956 Deleting or moving a bookmark has no effect on the associated changesets.
956 Deleting or moving a bookmark has no effect on the associated changesets.
957
957
958 Creating or updating to a bookmark causes it to be marked as 'active'.
958 Creating or updating to a bookmark causes it to be marked as 'active'.
959 The active bookmark is indicated with a '*'.
959 The active bookmark is indicated with a '*'.
960 When a commit is made, the active bookmark will advance to the new commit.
960 When a commit is made, the active bookmark will advance to the new commit.
961 A plain :hg:`update` will also advance an active bookmark, if possible.
961 A plain :hg:`update` will also advance an active bookmark, if possible.
962 Updating away from a bookmark will cause it to be deactivated.
962 Updating away from a bookmark will cause it to be deactivated.
963
963
964 Bookmarks can be pushed and pulled between repositories (see
964 Bookmarks can be pushed and pulled between repositories (see
965 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
965 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
966 diverged, a new 'divergent bookmark' of the form 'name@path' will
966 diverged, a new 'divergent bookmark' of the form 'name@path' will
967 be created. Using :hg:`merge` will resolve the divergence.
967 be created. Using :hg:`merge` will resolve the divergence.
968
968
969 A bookmark named '@' has the special property that :hg:`clone` will
969 A bookmark named '@' has the special property that :hg:`clone` will
970 check it out by default if it exists.
970 check it out by default if it exists.
971
971
972 .. container:: verbose
972 .. container:: verbose
973
973
974 Examples:
974 Examples:
975
975
976 - create an active bookmark for a new line of development::
976 - create an active bookmark for a new line of development::
977
977
978 hg book new-feature
978 hg book new-feature
979
979
980 - create an inactive bookmark as a place marker::
980 - create an inactive bookmark as a place marker::
981
981
982 hg book -i reviewed
982 hg book -i reviewed
983
983
984 - create an inactive bookmark on another changeset::
984 - create an inactive bookmark on another changeset::
985
985
986 hg book -r .^ tested
986 hg book -r .^ tested
987
987
988 - rename bookmark turkey to dinner::
988 - rename bookmark turkey to dinner::
989
989
990 hg book -m turkey dinner
990 hg book -m turkey dinner
991
991
992 - move the '@' bookmark from another branch::
992 - move the '@' bookmark from another branch::
993
993
994 hg book -f @
994 hg book -f @
995 '''
995 '''
996 force = opts.get('force')
996 force = opts.get('force')
997 rev = opts.get('rev')
997 rev = opts.get('rev')
998 delete = opts.get('delete')
998 delete = opts.get('delete')
999 rename = opts.get('rename')
999 rename = opts.get('rename')
1000 inactive = opts.get('inactive')
1000 inactive = opts.get('inactive')
1001
1001
1002 def checkformat(mark):
1002 def checkformat(mark):
1003 mark = mark.strip()
1003 mark = mark.strip()
1004 if not mark:
1004 if not mark:
1005 raise error.Abort(_("bookmark names cannot consist entirely of "
1005 raise error.Abort(_("bookmark names cannot consist entirely of "
1006 "whitespace"))
1006 "whitespace"))
1007 scmutil.checknewlabel(repo, mark, 'bookmark')
1007 scmutil.checknewlabel(repo, mark, 'bookmark')
1008 return mark
1008 return mark
1009
1009
1010 def checkconflict(repo, mark, cur, force=False, target=None):
1010 def checkconflict(repo, mark, cur, force=False, target=None):
1011 if mark in marks and not force:
1011 if mark in marks and not force:
1012 if target:
1012 if target:
1013 if marks[mark] == target and target == cur:
1013 if marks[mark] == target and target == cur:
1014 # re-activating a bookmark
1014 # re-activating a bookmark
1015 return
1015 return
1016 anc = repo.changelog.ancestors([repo[target].rev()])
1016 anc = repo.changelog.ancestors([repo[target].rev()])
1017 bmctx = repo[marks[mark]]
1017 bmctx = repo[marks[mark]]
1018 divs = [repo[b].node() for b in marks
1018 divs = [repo[b].node() for b in marks
1019 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1019 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1020
1020
1021 # allow resolving a single divergent bookmark even if moving
1021 # allow resolving a single divergent bookmark even if moving
1022 # the bookmark across branches when a revision is specified
1022 # the bookmark across branches when a revision is specified
1023 # that contains a divergent bookmark
1023 # that contains a divergent bookmark
1024 if bmctx.rev() not in anc and target in divs:
1024 if bmctx.rev() not in anc and target in divs:
1025 bookmarks.deletedivergent(repo, [target], mark)
1025 bookmarks.deletedivergent(repo, [target], mark)
1026 return
1026 return
1027
1027
1028 deletefrom = [b for b in divs
1028 deletefrom = [b for b in divs
1029 if repo[b].rev() in anc or b == target]
1029 if repo[b].rev() in anc or b == target]
1030 bookmarks.deletedivergent(repo, deletefrom, mark)
1030 bookmarks.deletedivergent(repo, deletefrom, mark)
1031 if bookmarks.validdest(repo, bmctx, repo[target]):
1031 if bookmarks.validdest(repo, bmctx, repo[target]):
1032 ui.status(_("moving bookmark '%s' forward from %s\n") %
1032 ui.status(_("moving bookmark '%s' forward from %s\n") %
1033 (mark, short(bmctx.node())))
1033 (mark, short(bmctx.node())))
1034 return
1034 return
1035 raise error.Abort(_("bookmark '%s' already exists "
1035 raise error.Abort(_("bookmark '%s' already exists "
1036 "(use -f to force)") % mark)
1036 "(use -f to force)") % mark)
1037 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1037 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1038 and not force):
1038 and not force):
1039 raise error.Abort(
1039 raise error.Abort(
1040 _("a bookmark cannot have the name of an existing branch"))
1040 _("a bookmark cannot have the name of an existing branch"))
1041
1041
1042 if delete and rename:
1042 if delete and rename:
1043 raise error.Abort(_("--delete and --rename are incompatible"))
1043 raise error.Abort(_("--delete and --rename are incompatible"))
1044 if delete and rev:
1044 if delete and rev:
1045 raise error.Abort(_("--rev is incompatible with --delete"))
1045 raise error.Abort(_("--rev is incompatible with --delete"))
1046 if rename and rev:
1046 if rename and rev:
1047 raise error.Abort(_("--rev is incompatible with --rename"))
1047 raise error.Abort(_("--rev is incompatible with --rename"))
1048 if not names and (delete or rev):
1048 if not names and (delete or rev):
1049 raise error.Abort(_("bookmark name required"))
1049 raise error.Abort(_("bookmark name required"))
1050
1050
1051 if delete or rename or names or inactive:
1051 if delete or rename or names or inactive:
1052 wlock = lock = tr = None
1052 wlock = lock = tr = None
1053 try:
1053 try:
1054 wlock = repo.wlock()
1054 wlock = repo.wlock()
1055 lock = repo.lock()
1055 lock = repo.lock()
1056 cur = repo.changectx('.').node()
1056 cur = repo.changectx('.').node()
1057 marks = repo._bookmarks
1057 marks = repo._bookmarks
1058 if delete:
1058 if delete:
1059 tr = repo.transaction('bookmark')
1059 tr = repo.transaction('bookmark')
1060 for mark in names:
1060 for mark in names:
1061 if mark not in marks:
1061 if mark not in marks:
1062 raise error.Abort(_("bookmark '%s' does not exist") %
1062 raise error.Abort(_("bookmark '%s' does not exist") %
1063 mark)
1063 mark)
1064 if mark == repo._activebookmark:
1064 if mark == repo._activebookmark:
1065 bookmarks.deactivate(repo)
1065 bookmarks.deactivate(repo)
1066 del marks[mark]
1066 del marks[mark]
1067
1067
1068 elif rename:
1068 elif rename:
1069 tr = repo.transaction('bookmark')
1069 tr = repo.transaction('bookmark')
1070 if not names:
1070 if not names:
1071 raise error.Abort(_("new bookmark name required"))
1071 raise error.Abort(_("new bookmark name required"))
1072 elif len(names) > 1:
1072 elif len(names) > 1:
1073 raise error.Abort(_("only one new bookmark name allowed"))
1073 raise error.Abort(_("only one new bookmark name allowed"))
1074 mark = checkformat(names[0])
1074 mark = checkformat(names[0])
1075 if rename not in marks:
1075 if rename not in marks:
1076 raise error.Abort(_("bookmark '%s' does not exist")
1076 raise error.Abort(_("bookmark '%s' does not exist")
1077 % rename)
1077 % rename)
1078 checkconflict(repo, mark, cur, force)
1078 checkconflict(repo, mark, cur, force)
1079 marks[mark] = marks[rename]
1079 marks[mark] = marks[rename]
1080 if repo._activebookmark == rename and not inactive:
1080 if repo._activebookmark == rename and not inactive:
1081 bookmarks.activate(repo, mark)
1081 bookmarks.activate(repo, mark)
1082 del marks[rename]
1082 del marks[rename]
1083 elif names:
1083 elif names:
1084 tr = repo.transaction('bookmark')
1084 tr = repo.transaction('bookmark')
1085 newact = None
1085 newact = None
1086 for mark in names:
1086 for mark in names:
1087 mark = checkformat(mark)
1087 mark = checkformat(mark)
1088 if newact is None:
1088 if newact is None:
1089 newact = mark
1089 newact = mark
1090 if inactive and mark == repo._activebookmark:
1090 if inactive and mark == repo._activebookmark:
1091 bookmarks.deactivate(repo)
1091 bookmarks.deactivate(repo)
1092 return
1092 return
1093 tgt = cur
1093 tgt = cur
1094 if rev:
1094 if rev:
1095 tgt = scmutil.revsingle(repo, rev).node()
1095 tgt = scmutil.revsingle(repo, rev).node()
1096 checkconflict(repo, mark, cur, force, tgt)
1096 checkconflict(repo, mark, cur, force, tgt)
1097 marks[mark] = tgt
1097 marks[mark] = tgt
1098 if not inactive and cur == marks[newact] and not rev:
1098 if not inactive and cur == marks[newact] and not rev:
1099 bookmarks.activate(repo, newact)
1099 bookmarks.activate(repo, newact)
1100 elif cur != tgt and newact == repo._activebookmark:
1100 elif cur != tgt and newact == repo._activebookmark:
1101 bookmarks.deactivate(repo)
1101 bookmarks.deactivate(repo)
1102 elif inactive:
1102 elif inactive:
1103 if len(marks) == 0:
1103 if len(marks) == 0:
1104 ui.status(_("no bookmarks set\n"))
1104 ui.status(_("no bookmarks set\n"))
1105 elif not repo._activebookmark:
1105 elif not repo._activebookmark:
1106 ui.status(_("no active bookmark\n"))
1106 ui.status(_("no active bookmark\n"))
1107 else:
1107 else:
1108 bookmarks.deactivate(repo)
1108 bookmarks.deactivate(repo)
1109 if tr is not None:
1109 if tr is not None:
1110 marks.recordchange(tr)
1110 marks.recordchange(tr)
1111 tr.close()
1111 tr.close()
1112 finally:
1112 finally:
1113 lockmod.release(tr, lock, wlock)
1113 lockmod.release(tr, lock, wlock)
1114 else: # show bookmarks
1114 else: # show bookmarks
1115 fm = ui.formatter('bookmarks', opts)
1115 fm = ui.formatter('bookmarks', opts)
1116 hexfn = fm.hexfunc
1116 hexfn = fm.hexfunc
1117 marks = repo._bookmarks
1117 marks = repo._bookmarks
1118 if len(marks) == 0 and fm.isplain():
1118 if len(marks) == 0 and fm.isplain():
1119 ui.status(_("no bookmarks set\n"))
1119 ui.status(_("no bookmarks set\n"))
1120 for bmark, n in sorted(marks.iteritems()):
1120 for bmark, n in sorted(marks.iteritems()):
1121 active = repo._activebookmark
1121 active = repo._activebookmark
1122 if bmark == active:
1122 if bmark == active:
1123 prefix, label = '*', activebookmarklabel
1123 prefix, label = '*', activebookmarklabel
1124 else:
1124 else:
1125 prefix, label = ' ', ''
1125 prefix, label = ' ', ''
1126
1126
1127 fm.startitem()
1127 fm.startitem()
1128 if not ui.quiet:
1128 if not ui.quiet:
1129 fm.plain(' %s ' % prefix, label=label)
1129 fm.plain(' %s ' % prefix, label=label)
1130 fm.write('bookmark', '%s', bmark, label=label)
1130 fm.write('bookmark', '%s', bmark, label=label)
1131 pad = " " * (25 - encoding.colwidth(bmark))
1131 pad = " " * (25 - encoding.colwidth(bmark))
1132 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1132 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1133 repo.changelog.rev(n), hexfn(n), label=label)
1133 repo.changelog.rev(n), hexfn(n), label=label)
1134 fm.data(active=(bmark == active))
1134 fm.data(active=(bmark == active))
1135 fm.plain('\n')
1135 fm.plain('\n')
1136 fm.end()
1136 fm.end()
1137
1137
1138 @command('branch',
1138 @command('branch',
1139 [('f', 'force', None,
1139 [('f', 'force', None,
1140 _('set branch name even if it shadows an existing branch')),
1140 _('set branch name even if it shadows an existing branch')),
1141 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1141 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1142 _('[-fC] [NAME]'))
1142 _('[-fC] [NAME]'))
1143 def branch(ui, repo, label=None, **opts):
1143 def branch(ui, repo, label=None, **opts):
1144 """set or show the current branch name
1144 """set or show the current branch name
1145
1145
1146 .. note::
1146 .. note::
1147
1147
1148 Branch names are permanent and global. Use :hg:`bookmark` to create a
1148 Branch names are permanent and global. Use :hg:`bookmark` to create a
1149 light-weight bookmark instead. See :hg:`help glossary` for more
1149 light-weight bookmark instead. See :hg:`help glossary` for more
1150 information about named branches and bookmarks.
1150 information about named branches and bookmarks.
1151
1151
1152 With no argument, show the current branch name. With one argument,
1152 With no argument, show the current branch name. With one argument,
1153 set the working directory branch name (the branch will not exist
1153 set the working directory branch name (the branch will not exist
1154 in the repository until the next commit). Standard practice
1154 in the repository until the next commit). Standard practice
1155 recommends that primary development take place on the 'default'
1155 recommends that primary development take place on the 'default'
1156 branch.
1156 branch.
1157
1157
1158 Unless -f/--force is specified, branch will not let you set a
1158 Unless -f/--force is specified, branch will not let you set a
1159 branch name that already exists.
1159 branch name that already exists.
1160
1160
1161 Use -C/--clean to reset the working directory branch to that of
1161 Use -C/--clean to reset the working directory branch to that of
1162 the parent of the working directory, negating a previous branch
1162 the parent of the working directory, negating a previous branch
1163 change.
1163 change.
1164
1164
1165 Use the command :hg:`update` to switch to an existing branch. Use
1165 Use the command :hg:`update` to switch to an existing branch. Use
1166 :hg:`commit --close-branch` to mark this branch head as closed.
1166 :hg:`commit --close-branch` to mark this branch head as closed.
1167 When all heads of a branch are closed, the branch will be
1167 When all heads of a branch are closed, the branch will be
1168 considered closed.
1168 considered closed.
1169
1169
1170 Returns 0 on success.
1170 Returns 0 on success.
1171 """
1171 """
1172 if label:
1172 if label:
1173 label = label.strip()
1173 label = label.strip()
1174
1174
1175 if not opts.get('clean') and not label:
1175 if not opts.get('clean') and not label:
1176 ui.write("%s\n" % repo.dirstate.branch())
1176 ui.write("%s\n" % repo.dirstate.branch())
1177 return
1177 return
1178
1178
1179 with repo.wlock():
1179 with repo.wlock():
1180 if opts.get('clean'):
1180 if opts.get('clean'):
1181 label = repo[None].p1().branch()
1181 label = repo[None].p1().branch()
1182 repo.dirstate.setbranch(label)
1182 repo.dirstate.setbranch(label)
1183 ui.status(_('reset working directory to branch %s\n') % label)
1183 ui.status(_('reset working directory to branch %s\n') % label)
1184 elif label:
1184 elif label:
1185 if not opts.get('force') and label in repo.branchmap():
1185 if not opts.get('force') and label in repo.branchmap():
1186 if label not in [p.branch() for p in repo[None].parents()]:
1186 if label not in [p.branch() for p in repo[None].parents()]:
1187 raise error.Abort(_('a branch of the same name already'
1187 raise error.Abort(_('a branch of the same name already'
1188 ' exists'),
1188 ' exists'),
1189 # i18n: "it" refers to an existing branch
1189 # i18n: "it" refers to an existing branch
1190 hint=_("use 'hg update' to switch to it"))
1190 hint=_("use 'hg update' to switch to it"))
1191 scmutil.checknewlabel(repo, label, 'branch')
1191 scmutil.checknewlabel(repo, label, 'branch')
1192 repo.dirstate.setbranch(label)
1192 repo.dirstate.setbranch(label)
1193 ui.status(_('marked working directory as branch %s\n') % label)
1193 ui.status(_('marked working directory as branch %s\n') % label)
1194
1194
1195 # find any open named branches aside from default
1195 # find any open named branches aside from default
1196 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1196 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1197 if n != "default" and not c]
1197 if n != "default" and not c]
1198 if not others:
1198 if not others:
1199 ui.status(_('(branches are permanent and global, '
1199 ui.status(_('(branches are permanent and global, '
1200 'did you want a bookmark?)\n'))
1200 'did you want a bookmark?)\n'))
1201
1201
1202 @command('branches',
1202 @command('branches',
1203 [('a', 'active', False,
1203 [('a', 'active', False,
1204 _('show only branches that have unmerged heads (DEPRECATED)')),
1204 _('show only branches that have unmerged heads (DEPRECATED)')),
1205 ('c', 'closed', False, _('show normal and closed branches')),
1205 ('c', 'closed', False, _('show normal and closed branches')),
1206 ] + formatteropts,
1206 ] + formatteropts,
1207 _('[-c]'))
1207 _('[-c]'))
1208 def branches(ui, repo, active=False, closed=False, **opts):
1208 def branches(ui, repo, active=False, closed=False, **opts):
1209 """list repository named branches
1209 """list repository named branches
1210
1210
1211 List the repository's named branches, indicating which ones are
1211 List the repository's named branches, indicating which ones are
1212 inactive. If -c/--closed is specified, also list branches which have
1212 inactive. If -c/--closed is specified, also list branches which have
1213 been marked closed (see :hg:`commit --close-branch`).
1213 been marked closed (see :hg:`commit --close-branch`).
1214
1214
1215 Use the command :hg:`update` to switch to an existing branch.
1215 Use the command :hg:`update` to switch to an existing branch.
1216
1216
1217 Returns 0.
1217 Returns 0.
1218 """
1218 """
1219
1219
1220 fm = ui.formatter('branches', opts)
1220 fm = ui.formatter('branches', opts)
1221 hexfunc = fm.hexfunc
1221 hexfunc = fm.hexfunc
1222
1222
1223 allheads = set(repo.heads())
1223 allheads = set(repo.heads())
1224 branches = []
1224 branches = []
1225 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1225 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1226 isactive = not isclosed and bool(set(heads) & allheads)
1226 isactive = not isclosed and bool(set(heads) & allheads)
1227 branches.append((tag, repo[tip], isactive, not isclosed))
1227 branches.append((tag, repo[tip], isactive, not isclosed))
1228 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1228 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1229 reverse=True)
1229 reverse=True)
1230
1230
1231 for tag, ctx, isactive, isopen in branches:
1231 for tag, ctx, isactive, isopen in branches:
1232 if active and not isactive:
1232 if active and not isactive:
1233 continue
1233 continue
1234 if isactive:
1234 if isactive:
1235 label = 'branches.active'
1235 label = 'branches.active'
1236 notice = ''
1236 notice = ''
1237 elif not isopen:
1237 elif not isopen:
1238 if not closed:
1238 if not closed:
1239 continue
1239 continue
1240 label = 'branches.closed'
1240 label = 'branches.closed'
1241 notice = _(' (closed)')
1241 notice = _(' (closed)')
1242 else:
1242 else:
1243 label = 'branches.inactive'
1243 label = 'branches.inactive'
1244 notice = _(' (inactive)')
1244 notice = _(' (inactive)')
1245 current = (tag == repo.dirstate.branch())
1245 current = (tag == repo.dirstate.branch())
1246 if current:
1246 if current:
1247 label = 'branches.current'
1247 label = 'branches.current'
1248
1248
1249 fm.startitem()
1249 fm.startitem()
1250 fm.write('branch', '%s', tag, label=label)
1250 fm.write('branch', '%s', tag, label=label)
1251 rev = ctx.rev()
1251 rev = ctx.rev()
1252 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1252 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1253 fmt = ' ' * padsize + ' %d:%s'
1253 fmt = ' ' * padsize + ' %d:%s'
1254 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1254 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1255 label='log.changeset changeset.%s' % ctx.phasestr())
1255 label='log.changeset changeset.%s' % ctx.phasestr())
1256 fm.data(active=isactive, closed=not isopen, current=current)
1256 fm.data(active=isactive, closed=not isopen, current=current)
1257 if not ui.quiet:
1257 if not ui.quiet:
1258 fm.plain(notice)
1258 fm.plain(notice)
1259 fm.plain('\n')
1259 fm.plain('\n')
1260 fm.end()
1260 fm.end()
1261
1261
1262 @command('bundle',
1262 @command('bundle',
1263 [('f', 'force', None, _('run even when the destination is unrelated')),
1263 [('f', 'force', None, _('run even when the destination is unrelated')),
1264 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1264 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1265 _('REV')),
1265 _('REV')),
1266 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1266 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1267 _('BRANCH')),
1267 _('BRANCH')),
1268 ('', 'base', [],
1268 ('', 'base', [],
1269 _('a base changeset assumed to be available at the destination'),
1269 _('a base changeset assumed to be available at the destination'),
1270 _('REV')),
1270 _('REV')),
1271 ('a', 'all', None, _('bundle all changesets in the repository')),
1271 ('a', 'all', None, _('bundle all changesets in the repository')),
1272 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1272 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1273 ] + remoteopts,
1273 ] + remoteopts,
1274 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1274 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1275 def bundle(ui, repo, fname, dest=None, **opts):
1275 def bundle(ui, repo, fname, dest=None, **opts):
1276 """create a changegroup file
1276 """create a changegroup file
1277
1277
1278 Generate a changegroup file collecting changesets to be added
1278 Generate a changegroup file collecting changesets to be added
1279 to a repository.
1279 to a repository.
1280
1280
1281 To create a bundle containing all changesets, use -a/--all
1281 To create a bundle containing all changesets, use -a/--all
1282 (or --base null). Otherwise, hg assumes the destination will have
1282 (or --base null). Otherwise, hg assumes the destination will have
1283 all the nodes you specify with --base parameters. Otherwise, hg
1283 all the nodes you specify with --base parameters. Otherwise, hg
1284 will assume the repository has all the nodes in destination, or
1284 will assume the repository has all the nodes in destination, or
1285 default-push/default if no destination is specified.
1285 default-push/default if no destination is specified.
1286
1286
1287 You can change bundle format with the -t/--type option. You can
1287 You can change bundle format with the -t/--type option. You can
1288 specify a compression, a bundle version or both using a dash
1288 specify a compression, a bundle version or both using a dash
1289 (comp-version). The available compression methods are: none, bzip2,
1289 (comp-version). The available compression methods are: none, bzip2,
1290 and gzip (by default, bundles are compressed using bzip2). The
1290 and gzip (by default, bundles are compressed using bzip2). The
1291 available formats are: v1, v2 (default to most suitable).
1291 available formats are: v1, v2 (default to most suitable).
1292
1292
1293 The bundle file can then be transferred using conventional means
1293 The bundle file can then be transferred using conventional means
1294 and applied to another repository with the unbundle or pull
1294 and applied to another repository with the unbundle or pull
1295 command. This is useful when direct push and pull are not
1295 command. This is useful when direct push and pull are not
1296 available or when exporting an entire repository is undesirable.
1296 available or when exporting an entire repository is undesirable.
1297
1297
1298 Applying bundles preserves all changeset contents including
1298 Applying bundles preserves all changeset contents including
1299 permissions, copy/rename information, and revision history.
1299 permissions, copy/rename information, and revision history.
1300
1300
1301 Returns 0 on success, 1 if no changes found.
1301 Returns 0 on success, 1 if no changes found.
1302 """
1302 """
1303 revs = None
1303 revs = None
1304 if 'rev' in opts:
1304 if 'rev' in opts:
1305 revstrings = opts['rev']
1305 revstrings = opts['rev']
1306 revs = scmutil.revrange(repo, revstrings)
1306 revs = scmutil.revrange(repo, revstrings)
1307 if revstrings and not revs:
1307 if revstrings and not revs:
1308 raise error.Abort(_('no commits to bundle'))
1308 raise error.Abort(_('no commits to bundle'))
1309
1309
1310 bundletype = opts.get('type', 'bzip2').lower()
1310 bundletype = opts.get('type', 'bzip2').lower()
1311 try:
1311 try:
1312 bcompression, cgversion, params = exchange.parsebundlespec(
1312 bcompression, cgversion, params = exchange.parsebundlespec(
1313 repo, bundletype, strict=False)
1313 repo, bundletype, strict=False)
1314 except error.UnsupportedBundleSpecification as e:
1314 except error.UnsupportedBundleSpecification as e:
1315 raise error.Abort(str(e),
1315 raise error.Abort(str(e),
1316 hint=_("see 'hg help bundle' for supported "
1316 hint=_("see 'hg help bundle' for supported "
1317 "values for --type"))
1317 "values for --type"))
1318
1318
1319 # Packed bundles are a pseudo bundle format for now.
1319 # Packed bundles are a pseudo bundle format for now.
1320 if cgversion == 's1':
1320 if cgversion == 's1':
1321 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1321 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1322 hint=_("use 'hg debugcreatestreamclonebundle'"))
1322 hint=_("use 'hg debugcreatestreamclonebundle'"))
1323
1323
1324 if opts.get('all'):
1324 if opts.get('all'):
1325 if dest:
1325 if dest:
1326 raise error.Abort(_("--all is incompatible with specifying "
1326 raise error.Abort(_("--all is incompatible with specifying "
1327 "a destination"))
1327 "a destination"))
1328 if opts.get('base'):
1328 if opts.get('base'):
1329 ui.warn(_("ignoring --base because --all was specified\n"))
1329 ui.warn(_("ignoring --base because --all was specified\n"))
1330 base = ['null']
1330 base = ['null']
1331 else:
1331 else:
1332 base = scmutil.revrange(repo, opts.get('base'))
1332 base = scmutil.revrange(repo, opts.get('base'))
1333 # TODO: get desired bundlecaps from command line.
1333 # TODO: get desired bundlecaps from command line.
1334 bundlecaps = None
1334 bundlecaps = None
1335 if cgversion not in changegroup.supportedoutgoingversions(repo):
1335 if cgversion not in changegroup.supportedoutgoingversions(repo):
1336 raise error.Abort(_("repository does not support bundle version %s") %
1336 raise error.Abort(_("repository does not support bundle version %s") %
1337 cgversion)
1337 cgversion)
1338
1338
1339 if base:
1339 if base:
1340 if dest:
1340 if dest:
1341 raise error.Abort(_("--base is incompatible with specifying "
1341 raise error.Abort(_("--base is incompatible with specifying "
1342 "a destination"))
1342 "a destination"))
1343 common = [repo.lookup(rev) for rev in base]
1343 common = [repo.lookup(rev) for rev in base]
1344 heads = revs and map(repo.lookup, revs) or None
1344 heads = revs and map(repo.lookup, revs) or None
1345 outgoing = discovery.outgoing(repo, common, heads)
1345 outgoing = discovery.outgoing(repo, common, heads)
1346 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1346 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1347 bundlecaps=bundlecaps,
1347 bundlecaps=bundlecaps,
1348 version=cgversion)
1348 version=cgversion)
1349 outgoing = None
1349 outgoing = None
1350 else:
1350 else:
1351 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1351 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1352 dest, branches = hg.parseurl(dest, opts.get('branch'))
1352 dest, branches = hg.parseurl(dest, opts.get('branch'))
1353 other = hg.peer(repo, opts, dest)
1353 other = hg.peer(repo, opts, dest)
1354 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1354 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1355 heads = revs and map(repo.lookup, revs) or revs
1355 heads = revs and map(repo.lookup, revs) or revs
1356 outgoing = discovery.findcommonoutgoing(repo, other,
1356 outgoing = discovery.findcommonoutgoing(repo, other,
1357 onlyheads=heads,
1357 onlyheads=heads,
1358 force=opts.get('force'),
1358 force=opts.get('force'),
1359 portable=True)
1359 portable=True)
1360 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1360 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1361 bundlecaps, version=cgversion)
1361 bundlecaps, version=cgversion)
1362 if not cg:
1362 if not cg:
1363 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1363 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1364 return 1
1364 return 1
1365
1365
1366 if cgversion == '01': #bundle1
1366 if cgversion == '01': #bundle1
1367 if bcompression is None:
1367 if bcompression is None:
1368 bcompression = 'UN'
1368 bcompression = 'UN'
1369 bversion = 'HG10' + bcompression
1369 bversion = 'HG10' + bcompression
1370 bcompression = None
1370 bcompression = None
1371 else:
1371 else:
1372 assert cgversion == '02'
1372 assert cgversion == '02'
1373 bversion = 'HG20'
1373 bversion = 'HG20'
1374
1374
1375 # TODO compression options should be derived from bundlespec parsing.
1375 # TODO compression options should be derived from bundlespec parsing.
1376 # This is a temporary hack to allow adjusting bundle compression
1376 # This is a temporary hack to allow adjusting bundle compression
1377 # level without a) formalizing the bundlespec changes to declare it
1377 # level without a) formalizing the bundlespec changes to declare it
1378 # b) introducing a command flag.
1378 # b) introducing a command flag.
1379 compopts = {}
1379 compopts = {}
1380 complevel = ui.configint('experimental', 'bundlecomplevel')
1380 complevel = ui.configint('experimental', 'bundlecomplevel')
1381 if complevel is not None:
1381 if complevel is not None:
1382 compopts['level'] = complevel
1382 compopts['level'] = complevel
1383
1383
1384 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression,
1384 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression,
1385 compopts=compopts)
1385 compopts=compopts)
1386
1386
1387 @command('cat',
1387 @command('cat',
1388 [('o', 'output', '',
1388 [('o', 'output', '',
1389 _('print output to file with formatted name'), _('FORMAT')),
1389 _('print output to file with formatted name'), _('FORMAT')),
1390 ('r', 'rev', '', _('print the given revision'), _('REV')),
1390 ('r', 'rev', '', _('print the given revision'), _('REV')),
1391 ('', 'decode', None, _('apply any matching decode filter')),
1391 ('', 'decode', None, _('apply any matching decode filter')),
1392 ] + walkopts,
1392 ] + walkopts,
1393 _('[OPTION]... FILE...'),
1393 _('[OPTION]... FILE...'),
1394 inferrepo=True)
1394 inferrepo=True)
1395 def cat(ui, repo, file1, *pats, **opts):
1395 def cat(ui, repo, file1, *pats, **opts):
1396 """output the current or given revision of files
1396 """output the current or given revision of files
1397
1397
1398 Print the specified files as they were at the given revision. If
1398 Print the specified files as they were at the given revision. If
1399 no revision is given, the parent of the working directory is used.
1399 no revision is given, the parent of the working directory is used.
1400
1400
1401 Output may be to a file, in which case the name of the file is
1401 Output may be to a file, in which case the name of the file is
1402 given using a format string. The formatting rules as follows:
1402 given using a format string. The formatting rules as follows:
1403
1403
1404 :``%%``: literal "%" character
1404 :``%%``: literal "%" character
1405 :``%s``: basename of file being printed
1405 :``%s``: basename of file being printed
1406 :``%d``: dirname of file being printed, or '.' if in repository root
1406 :``%d``: dirname of file being printed, or '.' if in repository root
1407 :``%p``: root-relative path name of file being printed
1407 :``%p``: root-relative path name of file being printed
1408 :``%H``: changeset hash (40 hexadecimal digits)
1408 :``%H``: changeset hash (40 hexadecimal digits)
1409 :``%R``: changeset revision number
1409 :``%R``: changeset revision number
1410 :``%h``: short-form changeset hash (12 hexadecimal digits)
1410 :``%h``: short-form changeset hash (12 hexadecimal digits)
1411 :``%r``: zero-padded changeset revision number
1411 :``%r``: zero-padded changeset revision number
1412 :``%b``: basename of the exporting repository
1412 :``%b``: basename of the exporting repository
1413
1413
1414 Returns 0 on success.
1414 Returns 0 on success.
1415 """
1415 """
1416 ctx = scmutil.revsingle(repo, opts.get('rev'))
1416 ctx = scmutil.revsingle(repo, opts.get('rev'))
1417 m = scmutil.match(ctx, (file1,) + pats, opts)
1417 m = scmutil.match(ctx, (file1,) + pats, opts)
1418
1418
1419 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1419 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1420
1420
1421 @command('^clone',
1421 @command('^clone',
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 'directory (only a repository)')),
1423 'directory (only a repository)')),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 _('REV')),
1425 _('REV')),
1426 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1426 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1427 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1427 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1428 ('', 'pull', None, _('use pull protocol to copy metadata')),
1428 ('', 'pull', None, _('use pull protocol to copy metadata')),
1429 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1429 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1430 ] + remoteopts,
1430 ] + remoteopts,
1431 _('[OPTION]... SOURCE [DEST]'),
1431 _('[OPTION]... SOURCE [DEST]'),
1432 norepo=True)
1432 norepo=True)
1433 def clone(ui, source, dest=None, **opts):
1433 def clone(ui, source, dest=None, **opts):
1434 """make a copy of an existing repository
1434 """make a copy of an existing repository
1435
1435
1436 Create a copy of an existing repository in a new directory.
1436 Create a copy of an existing repository in a new directory.
1437
1437
1438 If no destination directory name is specified, it defaults to the
1438 If no destination directory name is specified, it defaults to the
1439 basename of the source.
1439 basename of the source.
1440
1440
1441 The location of the source is added to the new repository's
1441 The location of the source is added to the new repository's
1442 ``.hg/hgrc`` file, as the default to be used for future pulls.
1442 ``.hg/hgrc`` file, as the default to be used for future pulls.
1443
1443
1444 Only local paths and ``ssh://`` URLs are supported as
1444 Only local paths and ``ssh://`` URLs are supported as
1445 destinations. For ``ssh://`` destinations, no working directory or
1445 destinations. For ``ssh://`` destinations, no working directory or
1446 ``.hg/hgrc`` will be created on the remote side.
1446 ``.hg/hgrc`` will be created on the remote side.
1447
1447
1448 If the source repository has a bookmark called '@' set, that
1448 If the source repository has a bookmark called '@' set, that
1449 revision will be checked out in the new repository by default.
1449 revision will be checked out in the new repository by default.
1450
1450
1451 To check out a particular version, use -u/--update, or
1451 To check out a particular version, use -u/--update, or
1452 -U/--noupdate to create a clone with no working directory.
1452 -U/--noupdate to create a clone with no working directory.
1453
1453
1454 To pull only a subset of changesets, specify one or more revisions
1454 To pull only a subset of changesets, specify one or more revisions
1455 identifiers with -r/--rev or branches with -b/--branch. The
1455 identifiers with -r/--rev or branches with -b/--branch. The
1456 resulting clone will contain only the specified changesets and
1456 resulting clone will contain only the specified changesets and
1457 their ancestors. These options (or 'clone src#rev dest') imply
1457 their ancestors. These options (or 'clone src#rev dest') imply
1458 --pull, even for local source repositories.
1458 --pull, even for local source repositories.
1459
1459
1460 .. note::
1460 .. note::
1461
1461
1462 Specifying a tag will include the tagged changeset but not the
1462 Specifying a tag will include the tagged changeset but not the
1463 changeset containing the tag.
1463 changeset containing the tag.
1464
1464
1465 .. container:: verbose
1465 .. container:: verbose
1466
1466
1467 For efficiency, hardlinks are used for cloning whenever the
1467 For efficiency, hardlinks are used for cloning whenever the
1468 source and destination are on the same filesystem (note this
1468 source and destination are on the same filesystem (note this
1469 applies only to the repository data, not to the working
1469 applies only to the repository data, not to the working
1470 directory). Some filesystems, such as AFS, implement hardlinking
1470 directory). Some filesystems, such as AFS, implement hardlinking
1471 incorrectly, but do not report errors. In these cases, use the
1471 incorrectly, but do not report errors. In these cases, use the
1472 --pull option to avoid hardlinking.
1472 --pull option to avoid hardlinking.
1473
1473
1474 In some cases, you can clone repositories and the working
1474 In some cases, you can clone repositories and the working
1475 directory using full hardlinks with ::
1475 directory using full hardlinks with ::
1476
1476
1477 $ cp -al REPO REPOCLONE
1477 $ cp -al REPO REPOCLONE
1478
1478
1479 This is the fastest way to clone, but it is not always safe. The
1479 This is the fastest way to clone, but it is not always safe. The
1480 operation is not atomic (making sure REPO is not modified during
1480 operation is not atomic (making sure REPO is not modified during
1481 the operation is up to you) and you have to make sure your
1481 the operation is up to you) and you have to make sure your
1482 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1482 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1483 so). Also, this is not compatible with certain extensions that
1483 so). Also, this is not compatible with certain extensions that
1484 place their metadata under the .hg directory, such as mq.
1484 place their metadata under the .hg directory, such as mq.
1485
1485
1486 Mercurial will update the working directory to the first applicable
1486 Mercurial will update the working directory to the first applicable
1487 revision from this list:
1487 revision from this list:
1488
1488
1489 a) null if -U or the source repository has no changesets
1489 a) null if -U or the source repository has no changesets
1490 b) if -u . and the source repository is local, the first parent of
1490 b) if -u . and the source repository is local, the first parent of
1491 the source repository's working directory
1491 the source repository's working directory
1492 c) the changeset specified with -u (if a branch name, this means the
1492 c) the changeset specified with -u (if a branch name, this means the
1493 latest head of that branch)
1493 latest head of that branch)
1494 d) the changeset specified with -r
1494 d) the changeset specified with -r
1495 e) the tipmost head specified with -b
1495 e) the tipmost head specified with -b
1496 f) the tipmost head specified with the url#branch source syntax
1496 f) the tipmost head specified with the url#branch source syntax
1497 g) the revision marked with the '@' bookmark, if present
1497 g) the revision marked with the '@' bookmark, if present
1498 h) the tipmost head of the default branch
1498 h) the tipmost head of the default branch
1499 i) tip
1499 i) tip
1500
1500
1501 When cloning from servers that support it, Mercurial may fetch
1501 When cloning from servers that support it, Mercurial may fetch
1502 pre-generated data from a server-advertised URL. When this is done,
1502 pre-generated data from a server-advertised URL. When this is done,
1503 hooks operating on incoming changesets and changegroups may fire twice,
1503 hooks operating on incoming changesets and changegroups may fire twice,
1504 once for the bundle fetched from the URL and another for any additional
1504 once for the bundle fetched from the URL and another for any additional
1505 data not fetched from this URL. In addition, if an error occurs, the
1505 data not fetched from this URL. In addition, if an error occurs, the
1506 repository may be rolled back to a partial clone. This behavior may
1506 repository may be rolled back to a partial clone. This behavior may
1507 change in future releases. See :hg:`help -e clonebundles` for more.
1507 change in future releases. See :hg:`help -e clonebundles` for more.
1508
1508
1509 Examples:
1509 Examples:
1510
1510
1511 - clone a remote repository to a new directory named hg/::
1511 - clone a remote repository to a new directory named hg/::
1512
1512
1513 hg clone https://www.mercurial-scm.org/repo/hg/
1513 hg clone https://www.mercurial-scm.org/repo/hg/
1514
1514
1515 - create a lightweight local clone::
1515 - create a lightweight local clone::
1516
1516
1517 hg clone project/ project-feature/
1517 hg clone project/ project-feature/
1518
1518
1519 - clone from an absolute path on an ssh server (note double-slash)::
1519 - clone from an absolute path on an ssh server (note double-slash)::
1520
1520
1521 hg clone ssh://user@server//home/projects/alpha/
1521 hg clone ssh://user@server//home/projects/alpha/
1522
1522
1523 - do a high-speed clone over a LAN while checking out a
1523 - do a high-speed clone over a LAN while checking out a
1524 specified version::
1524 specified version::
1525
1525
1526 hg clone --uncompressed http://server/repo -u 1.5
1526 hg clone --uncompressed http://server/repo -u 1.5
1527
1527
1528 - create a repository without changesets after a particular revision::
1528 - create a repository without changesets after a particular revision::
1529
1529
1530 hg clone -r 04e544 experimental/ good/
1530 hg clone -r 04e544 experimental/ good/
1531
1531
1532 - clone (and track) a particular named branch::
1532 - clone (and track) a particular named branch::
1533
1533
1534 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1534 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1535
1535
1536 See :hg:`help urls` for details on specifying URLs.
1536 See :hg:`help urls` for details on specifying URLs.
1537
1537
1538 Returns 0 on success.
1538 Returns 0 on success.
1539 """
1539 """
1540 if opts.get('noupdate') and opts.get('updaterev'):
1540 if opts.get('noupdate') and opts.get('updaterev'):
1541 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1541 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1542
1542
1543 r = hg.clone(ui, opts, source, dest,
1543 r = hg.clone(ui, opts, source, dest,
1544 pull=opts.get('pull'),
1544 pull=opts.get('pull'),
1545 stream=opts.get('uncompressed'),
1545 stream=opts.get('uncompressed'),
1546 rev=opts.get('rev'),
1546 rev=opts.get('rev'),
1547 update=opts.get('updaterev') or not opts.get('noupdate'),
1547 update=opts.get('updaterev') or not opts.get('noupdate'),
1548 branch=opts.get('branch'),
1548 branch=opts.get('branch'),
1549 shareopts=opts.get('shareopts'))
1549 shareopts=opts.get('shareopts'))
1550
1550
1551 return r is None
1551 return r is None
1552
1552
1553 @command('^commit|ci',
1553 @command('^commit|ci',
1554 [('A', 'addremove', None,
1554 [('A', 'addremove', None,
1555 _('mark new/missing files as added/removed before committing')),
1555 _('mark new/missing files as added/removed before committing')),
1556 ('', 'close-branch', None,
1556 ('', 'close-branch', None,
1557 _('mark a branch head as closed')),
1557 _('mark a branch head as closed')),
1558 ('', 'amend', None, _('amend the parent of the working directory')),
1558 ('', 'amend', None, _('amend the parent of the working directory')),
1559 ('s', 'secret', None, _('use the secret phase for committing')),
1559 ('s', 'secret', None, _('use the secret phase for committing')),
1560 ('e', 'edit', None, _('invoke editor on commit messages')),
1560 ('e', 'edit', None, _('invoke editor on commit messages')),
1561 ('i', 'interactive', None, _('use interactive mode')),
1561 ('i', 'interactive', None, _('use interactive mode')),
1562 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1562 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1563 _('[OPTION]... [FILE]...'),
1563 _('[OPTION]... [FILE]...'),
1564 inferrepo=True)
1564 inferrepo=True)
1565 def commit(ui, repo, *pats, **opts):
1565 def commit(ui, repo, *pats, **opts):
1566 """commit the specified files or all outstanding changes
1566 """commit the specified files or all outstanding changes
1567
1567
1568 Commit changes to the given files into the repository. Unlike a
1568 Commit changes to the given files into the repository. Unlike a
1569 centralized SCM, this operation is a local operation. See
1569 centralized SCM, this operation is a local operation. See
1570 :hg:`push` for a way to actively distribute your changes.
1570 :hg:`push` for a way to actively distribute your changes.
1571
1571
1572 If a list of files is omitted, all changes reported by :hg:`status`
1572 If a list of files is omitted, all changes reported by :hg:`status`
1573 will be committed.
1573 will be committed.
1574
1574
1575 If you are committing the result of a merge, do not provide any
1575 If you are committing the result of a merge, do not provide any
1576 filenames or -I/-X filters.
1576 filenames or -I/-X filters.
1577
1577
1578 If no commit message is specified, Mercurial starts your
1578 If no commit message is specified, Mercurial starts your
1579 configured editor where you can enter a message. In case your
1579 configured editor where you can enter a message. In case your
1580 commit fails, you will find a backup of your message in
1580 commit fails, you will find a backup of your message in
1581 ``.hg/last-message.txt``.
1581 ``.hg/last-message.txt``.
1582
1582
1583 The --close-branch flag can be used to mark the current branch
1583 The --close-branch flag can be used to mark the current branch
1584 head closed. When all heads of a branch are closed, the branch
1584 head closed. When all heads of a branch are closed, the branch
1585 will be considered closed and no longer listed.
1585 will be considered closed and no longer listed.
1586
1586
1587 The --amend flag can be used to amend the parent of the
1587 The --amend flag can be used to amend the parent of the
1588 working directory with a new commit that contains the changes
1588 working directory with a new commit that contains the changes
1589 in the parent in addition to those currently reported by :hg:`status`,
1589 in the parent in addition to those currently reported by :hg:`status`,
1590 if there are any. The old commit is stored in a backup bundle in
1590 if there are any. The old commit is stored in a backup bundle in
1591 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1591 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1592 on how to restore it).
1592 on how to restore it).
1593
1593
1594 Message, user and date are taken from the amended commit unless
1594 Message, user and date are taken from the amended commit unless
1595 specified. When a message isn't specified on the command line,
1595 specified. When a message isn't specified on the command line,
1596 the editor will open with the message of the amended commit.
1596 the editor will open with the message of the amended commit.
1597
1597
1598 It is not possible to amend public changesets (see :hg:`help phases`)
1598 It is not possible to amend public changesets (see :hg:`help phases`)
1599 or changesets that have children.
1599 or changesets that have children.
1600
1600
1601 See :hg:`help dates` for a list of formats valid for -d/--date.
1601 See :hg:`help dates` for a list of formats valid for -d/--date.
1602
1602
1603 Returns 0 on success, 1 if nothing changed.
1603 Returns 0 on success, 1 if nothing changed.
1604
1604
1605 .. container:: verbose
1605 .. container:: verbose
1606
1606
1607 Examples:
1607 Examples:
1608
1608
1609 - commit all files ending in .py::
1609 - commit all files ending in .py::
1610
1610
1611 hg commit --include "set:**.py"
1611 hg commit --include "set:**.py"
1612
1612
1613 - commit all non-binary files::
1613 - commit all non-binary files::
1614
1614
1615 hg commit --exclude "set:binary()"
1615 hg commit --exclude "set:binary()"
1616
1616
1617 - amend the current commit and set the date to now::
1617 - amend the current commit and set the date to now::
1618
1618
1619 hg commit --amend --date now
1619 hg commit --amend --date now
1620 """
1620 """
1621 wlock = lock = None
1621 wlock = lock = None
1622 try:
1622 try:
1623 wlock = repo.wlock()
1623 wlock = repo.wlock()
1624 lock = repo.lock()
1624 lock = repo.lock()
1625 return _docommit(ui, repo, *pats, **opts)
1625 return _docommit(ui, repo, *pats, **opts)
1626 finally:
1626 finally:
1627 release(lock, wlock)
1627 release(lock, wlock)
1628
1628
1629 def _docommit(ui, repo, *pats, **opts):
1629 def _docommit(ui, repo, *pats, **opts):
1630 if opts.get('interactive'):
1630 if opts.get('interactive'):
1631 opts.pop('interactive')
1631 opts.pop('interactive')
1632 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1632 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1633 cmdutil.recordfilter, *pats, **opts)
1633 cmdutil.recordfilter, *pats, **opts)
1634 # ret can be 0 (no changes to record) or the value returned by
1634 # ret can be 0 (no changes to record) or the value returned by
1635 # commit(), 1 if nothing changed or None on success.
1635 # commit(), 1 if nothing changed or None on success.
1636 return 1 if ret == 0 else ret
1636 return 1 if ret == 0 else ret
1637
1637
1638 if opts.get('subrepos'):
1638 if opts.get('subrepos'):
1639 if opts.get('amend'):
1639 if opts.get('amend'):
1640 raise error.Abort(_('cannot amend with --subrepos'))
1640 raise error.Abort(_('cannot amend with --subrepos'))
1641 # Let --subrepos on the command line override config setting.
1641 # Let --subrepos on the command line override config setting.
1642 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1642 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1643
1643
1644 cmdutil.checkunfinished(repo, commit=True)
1644 cmdutil.checkunfinished(repo, commit=True)
1645
1645
1646 branch = repo[None].branch()
1646 branch = repo[None].branch()
1647 bheads = repo.branchheads(branch)
1647 bheads = repo.branchheads(branch)
1648
1648
1649 extra = {}
1649 extra = {}
1650 if opts.get('close_branch'):
1650 if opts.get('close_branch'):
1651 extra['close'] = 1
1651 extra['close'] = 1
1652
1652
1653 if not bheads:
1653 if not bheads:
1654 raise error.Abort(_('can only close branch heads'))
1654 raise error.Abort(_('can only close branch heads'))
1655 elif opts.get('amend'):
1655 elif opts.get('amend'):
1656 if repo[None].parents()[0].p1().branch() != branch and \
1656 if repo[None].parents()[0].p1().branch() != branch and \
1657 repo[None].parents()[0].p2().branch() != branch:
1657 repo[None].parents()[0].p2().branch() != branch:
1658 raise error.Abort(_('can only close branch heads'))
1658 raise error.Abort(_('can only close branch heads'))
1659
1659
1660 if opts.get('amend'):
1660 if opts.get('amend'):
1661 if ui.configbool('ui', 'commitsubrepos'):
1661 if ui.configbool('ui', 'commitsubrepos'):
1662 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1662 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1663
1663
1664 old = repo['.']
1664 old = repo['.']
1665 if not old.mutable():
1665 if not old.mutable():
1666 raise error.Abort(_('cannot amend public changesets'))
1666 raise error.Abort(_('cannot amend public changesets'))
1667 if len(repo[None].parents()) > 1:
1667 if len(repo[None].parents()) > 1:
1668 raise error.Abort(_('cannot amend while merging'))
1668 raise error.Abort(_('cannot amend while merging'))
1669 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1669 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1670 if not allowunstable and old.children():
1670 if not allowunstable and old.children():
1671 raise error.Abort(_('cannot amend changeset with children'))
1671 raise error.Abort(_('cannot amend changeset with children'))
1672
1672
1673 # Currently histedit gets confused if an amend happens while histedit
1673 # Currently histedit gets confused if an amend happens while histedit
1674 # is in progress. Since we have a checkunfinished command, we are
1674 # is in progress. Since we have a checkunfinished command, we are
1675 # temporarily honoring it.
1675 # temporarily honoring it.
1676 #
1676 #
1677 # Note: eventually this guard will be removed. Please do not expect
1677 # Note: eventually this guard will be removed. Please do not expect
1678 # this behavior to remain.
1678 # this behavior to remain.
1679 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1679 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1680 cmdutil.checkunfinished(repo)
1680 cmdutil.checkunfinished(repo)
1681
1681
1682 # commitfunc is used only for temporary amend commit by cmdutil.amend
1682 # commitfunc is used only for temporary amend commit by cmdutil.amend
1683 def commitfunc(ui, repo, message, match, opts):
1683 def commitfunc(ui, repo, message, match, opts):
1684 return repo.commit(message,
1684 return repo.commit(message,
1685 opts.get('user') or old.user(),
1685 opts.get('user') or old.user(),
1686 opts.get('date') or old.date(),
1686 opts.get('date') or old.date(),
1687 match,
1687 match,
1688 extra=extra)
1688 extra=extra)
1689
1689
1690 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1690 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1691 if node == old.node():
1691 if node == old.node():
1692 ui.status(_("nothing changed\n"))
1692 ui.status(_("nothing changed\n"))
1693 return 1
1693 return 1
1694 else:
1694 else:
1695 def commitfunc(ui, repo, message, match, opts):
1695 def commitfunc(ui, repo, message, match, opts):
1696 backup = ui.backupconfig('phases', 'new-commit')
1696 backup = ui.backupconfig('phases', 'new-commit')
1697 baseui = repo.baseui
1697 baseui = repo.baseui
1698 basebackup = baseui.backupconfig('phases', 'new-commit')
1698 basebackup = baseui.backupconfig('phases', 'new-commit')
1699 try:
1699 try:
1700 if opts.get('secret'):
1700 if opts.get('secret'):
1701 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1701 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1702 # Propagate to subrepos
1702 # Propagate to subrepos
1703 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1703 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1704
1704
1705 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1705 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1706 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1706 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1707 return repo.commit(message, opts.get('user'), opts.get('date'),
1707 return repo.commit(message, opts.get('user'), opts.get('date'),
1708 match,
1708 match,
1709 editor=editor,
1709 editor=editor,
1710 extra=extra)
1710 extra=extra)
1711 finally:
1711 finally:
1712 ui.restoreconfig(backup)
1712 ui.restoreconfig(backup)
1713 repo.baseui.restoreconfig(basebackup)
1713 repo.baseui.restoreconfig(basebackup)
1714
1714
1715
1715
1716 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1716 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1717
1717
1718 if not node:
1718 if not node:
1719 stat = cmdutil.postcommitstatus(repo, pats, opts)
1719 stat = cmdutil.postcommitstatus(repo, pats, opts)
1720 if stat[3]:
1720 if stat[3]:
1721 ui.status(_("nothing changed (%d missing files, see "
1721 ui.status(_("nothing changed (%d missing files, see "
1722 "'hg status')\n") % len(stat[3]))
1722 "'hg status')\n") % len(stat[3]))
1723 else:
1723 else:
1724 ui.status(_("nothing changed\n"))
1724 ui.status(_("nothing changed\n"))
1725 return 1
1725 return 1
1726
1726
1727 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1727 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1728
1728
1729 @command('config|showconfig|debugconfig',
1729 @command('config|showconfig|debugconfig',
1730 [('u', 'untrusted', None, _('show untrusted configuration options')),
1730 [('u', 'untrusted', None, _('show untrusted configuration options')),
1731 ('e', 'edit', None, _('edit user config')),
1731 ('e', 'edit', None, _('edit user config')),
1732 ('l', 'local', None, _('edit repository config')),
1732 ('l', 'local', None, _('edit repository config')),
1733 ('g', 'global', None, _('edit global config'))] + formatteropts,
1733 ('g', 'global', None, _('edit global config'))] + formatteropts,
1734 _('[-u] [NAME]...'),
1734 _('[-u] [NAME]...'),
1735 optionalrepo=True)
1735 optionalrepo=True)
1736 def config(ui, repo, *values, **opts):
1736 def config(ui, repo, *values, **opts):
1737 """show combined config settings from all hgrc files
1737 """show combined config settings from all hgrc files
1738
1738
1739 With no arguments, print names and values of all config items.
1739 With no arguments, print names and values of all config items.
1740
1740
1741 With one argument of the form section.name, print just the value
1741 With one argument of the form section.name, print just the value
1742 of that config item.
1742 of that config item.
1743
1743
1744 With multiple arguments, print names and values of all config
1744 With multiple arguments, print names and values of all config
1745 items with matching section names.
1745 items with matching section names.
1746
1746
1747 With --edit, start an editor on the user-level config file. With
1747 With --edit, start an editor on the user-level config file. With
1748 --global, edit the system-wide config file. With --local, edit the
1748 --global, edit the system-wide config file. With --local, edit the
1749 repository-level config file.
1749 repository-level config file.
1750
1750
1751 With --debug, the source (filename and line number) is printed
1751 With --debug, the source (filename and line number) is printed
1752 for each config item.
1752 for each config item.
1753
1753
1754 See :hg:`help config` for more information about config files.
1754 See :hg:`help config` for more information about config files.
1755
1755
1756 Returns 0 on success, 1 if NAME does not exist.
1756 Returns 0 on success, 1 if NAME does not exist.
1757
1757
1758 """
1758 """
1759
1759
1760 if opts.get('edit') or opts.get('local') or opts.get('global'):
1760 if opts.get('edit') or opts.get('local') or opts.get('global'):
1761 if opts.get('local') and opts.get('global'):
1761 if opts.get('local') and opts.get('global'):
1762 raise error.Abort(_("can't use --local and --global together"))
1762 raise error.Abort(_("can't use --local and --global together"))
1763
1763
1764 if opts.get('local'):
1764 if opts.get('local'):
1765 if not repo:
1765 if not repo:
1766 raise error.Abort(_("can't use --local outside a repository"))
1766 raise error.Abort(_("can't use --local outside a repository"))
1767 paths = [repo.join('hgrc')]
1767 paths = [repo.join('hgrc')]
1768 elif opts.get('global'):
1768 elif opts.get('global'):
1769 paths = scmutil.systemrcpath()
1769 paths = scmutil.systemrcpath()
1770 else:
1770 else:
1771 paths = scmutil.userrcpath()
1771 paths = scmutil.userrcpath()
1772
1772
1773 for f in paths:
1773 for f in paths:
1774 if os.path.exists(f):
1774 if os.path.exists(f):
1775 break
1775 break
1776 else:
1776 else:
1777 if opts.get('global'):
1777 if opts.get('global'):
1778 samplehgrc = uimod.samplehgrcs['global']
1778 samplehgrc = uimod.samplehgrcs['global']
1779 elif opts.get('local'):
1779 elif opts.get('local'):
1780 samplehgrc = uimod.samplehgrcs['local']
1780 samplehgrc = uimod.samplehgrcs['local']
1781 else:
1781 else:
1782 samplehgrc = uimod.samplehgrcs['user']
1782 samplehgrc = uimod.samplehgrcs['user']
1783
1783
1784 f = paths[0]
1784 f = paths[0]
1785 fp = open(f, "w")
1785 fp = open(f, "w")
1786 fp.write(samplehgrc)
1786 fp.write(samplehgrc)
1787 fp.close()
1787 fp.close()
1788
1788
1789 editor = ui.geteditor()
1789 editor = ui.geteditor()
1790 ui.system("%s \"%s\"" % (editor, f),
1790 ui.system("%s \"%s\"" % (editor, f),
1791 onerr=error.Abort, errprefix=_("edit failed"))
1791 onerr=error.Abort, errprefix=_("edit failed"))
1792 return
1792 return
1793
1793
1794 fm = ui.formatter('config', opts)
1794 fm = ui.formatter('config', opts)
1795 for f in scmutil.rcpath():
1795 for f in scmutil.rcpath():
1796 ui.debug('read config from: %s\n' % f)
1796 ui.debug('read config from: %s\n' % f)
1797 untrusted = bool(opts.get('untrusted'))
1797 untrusted = bool(opts.get('untrusted'))
1798 if values:
1798 if values:
1799 sections = [v for v in values if '.' not in v]
1799 sections = [v for v in values if '.' not in v]
1800 items = [v for v in values if '.' in v]
1800 items = [v for v in values if '.' in v]
1801 if len(items) > 1 or items and sections:
1801 if len(items) > 1 or items and sections:
1802 raise error.Abort(_('only one config item permitted'))
1802 raise error.Abort(_('only one config item permitted'))
1803 matched = False
1803 matched = False
1804 for section, name, value in ui.walkconfig(untrusted=untrusted):
1804 for section, name, value in ui.walkconfig(untrusted=untrusted):
1805 source = ui.configsource(section, name, untrusted)
1805 source = ui.configsource(section, name, untrusted)
1806 value = str(value)
1806 value = str(value)
1807 if fm.isplain():
1807 if fm.isplain():
1808 source = source or 'none'
1808 source = source or 'none'
1809 value = value.replace('\n', '\\n')
1809 value = value.replace('\n', '\\n')
1810 entryname = section + '.' + name
1810 entryname = section + '.' + name
1811 if values:
1811 if values:
1812 for v in values:
1812 for v in values:
1813 if v == section:
1813 if v == section:
1814 fm.startitem()
1814 fm.startitem()
1815 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1815 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1816 fm.write('name value', '%s=%s\n', entryname, value)
1816 fm.write('name value', '%s=%s\n', entryname, value)
1817 matched = True
1817 matched = True
1818 elif v == entryname:
1818 elif v == entryname:
1819 fm.startitem()
1819 fm.startitem()
1820 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1820 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1821 fm.write('value', '%s\n', value)
1821 fm.write('value', '%s\n', value)
1822 fm.data(name=entryname)
1822 fm.data(name=entryname)
1823 matched = True
1823 matched = True
1824 else:
1824 else:
1825 fm.startitem()
1825 fm.startitem()
1826 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1826 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1827 fm.write('name value', '%s=%s\n', entryname, value)
1827 fm.write('name value', '%s=%s\n', entryname, value)
1828 matched = True
1828 matched = True
1829 fm.end()
1829 fm.end()
1830 if matched:
1830 if matched:
1831 return 0
1831 return 0
1832 return 1
1832 return 1
1833
1833
1834 @command('copy|cp',
1834 @command('copy|cp',
1835 [('A', 'after', None, _('record a copy that has already occurred')),
1835 [('A', 'after', None, _('record a copy that has already occurred')),
1836 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1836 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1837 ] + walkopts + dryrunopts,
1837 ] + walkopts + dryrunopts,
1838 _('[OPTION]... [SOURCE]... DEST'))
1838 _('[OPTION]... [SOURCE]... DEST'))
1839 def copy(ui, repo, *pats, **opts):
1839 def copy(ui, repo, *pats, **opts):
1840 """mark files as copied for the next commit
1840 """mark files as copied for the next commit
1841
1841
1842 Mark dest as having copies of source files. If dest is a
1842 Mark dest as having copies of source files. If dest is a
1843 directory, copies are put in that directory. If dest is a file,
1843 directory, copies are put in that directory. If dest is a file,
1844 the source must be a single file.
1844 the source must be a single file.
1845
1845
1846 By default, this command copies the contents of files as they
1846 By default, this command copies the contents of files as they
1847 exist in the working directory. If invoked with -A/--after, the
1847 exist in the working directory. If invoked with -A/--after, the
1848 operation is recorded, but no copying is performed.
1848 operation is recorded, but no copying is performed.
1849
1849
1850 This command takes effect with the next commit. To undo a copy
1850 This command takes effect with the next commit. To undo a copy
1851 before that, see :hg:`revert`.
1851 before that, see :hg:`revert`.
1852
1852
1853 Returns 0 on success, 1 if errors are encountered.
1853 Returns 0 on success, 1 if errors are encountered.
1854 """
1854 """
1855 with repo.wlock(False):
1855 with repo.wlock(False):
1856 return cmdutil.copy(ui, repo, pats, opts)
1856 return cmdutil.copy(ui, repo, pats, opts)
1857
1857
1858 @command('^diff',
1858 @command('^diff',
1859 [('r', 'rev', [], _('revision'), _('REV')),
1859 [('r', 'rev', [], _('revision'), _('REV')),
1860 ('c', 'change', '', _('change made by revision'), _('REV'))
1860 ('c', 'change', '', _('change made by revision'), _('REV'))
1861 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1861 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1862 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1862 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1863 inferrepo=True)
1863 inferrepo=True)
1864 def diff(ui, repo, *pats, **opts):
1864 def diff(ui, repo, *pats, **opts):
1865 """diff repository (or selected files)
1865 """diff repository (or selected files)
1866
1866
1867 Show differences between revisions for the specified files.
1867 Show differences between revisions for the specified files.
1868
1868
1869 Differences between files are shown using the unified diff format.
1869 Differences between files are shown using the unified diff format.
1870
1870
1871 .. note::
1871 .. note::
1872
1872
1873 :hg:`diff` may generate unexpected results for merges, as it will
1873 :hg:`diff` may generate unexpected results for merges, as it will
1874 default to comparing against the working directory's first
1874 default to comparing against the working directory's first
1875 parent changeset if no revisions are specified.
1875 parent changeset if no revisions are specified.
1876
1876
1877 When two revision arguments are given, then changes are shown
1877 When two revision arguments are given, then changes are shown
1878 between those revisions. If only one revision is specified then
1878 between those revisions. If only one revision is specified then
1879 that revision is compared to the working directory, and, when no
1879 that revision is compared to the working directory, and, when no
1880 revisions are specified, the working directory files are compared
1880 revisions are specified, the working directory files are compared
1881 to its first parent.
1881 to its first parent.
1882
1882
1883 Alternatively you can specify -c/--change with a revision to see
1883 Alternatively you can specify -c/--change with a revision to see
1884 the changes in that changeset relative to its first parent.
1884 the changes in that changeset relative to its first parent.
1885
1885
1886 Without the -a/--text option, diff will avoid generating diffs of
1886 Without the -a/--text option, diff will avoid generating diffs of
1887 files it detects as binary. With -a, diff will generate a diff
1887 files it detects as binary. With -a, diff will generate a diff
1888 anyway, probably with undesirable results.
1888 anyway, probably with undesirable results.
1889
1889
1890 Use the -g/--git option to generate diffs in the git extended diff
1890 Use the -g/--git option to generate diffs in the git extended diff
1891 format. For more information, read :hg:`help diffs`.
1891 format. For more information, read :hg:`help diffs`.
1892
1892
1893 .. container:: verbose
1893 .. container:: verbose
1894
1894
1895 Examples:
1895 Examples:
1896
1896
1897 - compare a file in the current working directory to its parent::
1897 - compare a file in the current working directory to its parent::
1898
1898
1899 hg diff foo.c
1899 hg diff foo.c
1900
1900
1901 - compare two historical versions of a directory, with rename info::
1901 - compare two historical versions of a directory, with rename info::
1902
1902
1903 hg diff --git -r 1.0:1.2 lib/
1903 hg diff --git -r 1.0:1.2 lib/
1904
1904
1905 - get change stats relative to the last change on some date::
1905 - get change stats relative to the last change on some date::
1906
1906
1907 hg diff --stat -r "date('may 2')"
1907 hg diff --stat -r "date('may 2')"
1908
1908
1909 - diff all newly-added files that contain a keyword::
1909 - diff all newly-added files that contain a keyword::
1910
1910
1911 hg diff "set:added() and grep(GNU)"
1911 hg diff "set:added() and grep(GNU)"
1912
1912
1913 - compare a revision and its parents::
1913 - compare a revision and its parents::
1914
1914
1915 hg diff -c 9353 # compare against first parent
1915 hg diff -c 9353 # compare against first parent
1916 hg diff -r 9353^:9353 # same using revset syntax
1916 hg diff -r 9353^:9353 # same using revset syntax
1917 hg diff -r 9353^2:9353 # compare against the second parent
1917 hg diff -r 9353^2:9353 # compare against the second parent
1918
1918
1919 Returns 0 on success.
1919 Returns 0 on success.
1920 """
1920 """
1921
1921
1922 revs = opts.get('rev')
1922 revs = opts.get('rev')
1923 change = opts.get('change')
1923 change = opts.get('change')
1924 stat = opts.get('stat')
1924 stat = opts.get('stat')
1925 reverse = opts.get('reverse')
1925 reverse = opts.get('reverse')
1926
1926
1927 if revs and change:
1927 if revs and change:
1928 msg = _('cannot specify --rev and --change at the same time')
1928 msg = _('cannot specify --rev and --change at the same time')
1929 raise error.Abort(msg)
1929 raise error.Abort(msg)
1930 elif change:
1930 elif change:
1931 node2 = scmutil.revsingle(repo, change, None).node()
1931 node2 = scmutil.revsingle(repo, change, None).node()
1932 node1 = repo[node2].p1().node()
1932 node1 = repo[node2].p1().node()
1933 else:
1933 else:
1934 node1, node2 = scmutil.revpair(repo, revs)
1934 node1, node2 = scmutil.revpair(repo, revs)
1935
1935
1936 if reverse:
1936 if reverse:
1937 node1, node2 = node2, node1
1937 node1, node2 = node2, node1
1938
1938
1939 diffopts = patch.diffallopts(ui, opts)
1939 diffopts = patch.diffallopts(ui, opts)
1940 m = scmutil.match(repo[node2], pats, opts)
1940 m = scmutil.match(repo[node2], pats, opts)
1941 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1941 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1942 listsubrepos=opts.get('subrepos'),
1942 listsubrepos=opts.get('subrepos'),
1943 root=opts.get('root'))
1943 root=opts.get('root'))
1944
1944
1945 @command('^export',
1945 @command('^export',
1946 [('o', 'output', '',
1946 [('o', 'output', '',
1947 _('print output to file with formatted name'), _('FORMAT')),
1947 _('print output to file with formatted name'), _('FORMAT')),
1948 ('', 'switch-parent', None, _('diff against the second parent')),
1948 ('', 'switch-parent', None, _('diff against the second parent')),
1949 ('r', 'rev', [], _('revisions to export'), _('REV')),
1949 ('r', 'rev', [], _('revisions to export'), _('REV')),
1950 ] + diffopts,
1950 ] + diffopts,
1951 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1951 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1952 def export(ui, repo, *changesets, **opts):
1952 def export(ui, repo, *changesets, **opts):
1953 """dump the header and diffs for one or more changesets
1953 """dump the header and diffs for one or more changesets
1954
1954
1955 Print the changeset header and diffs for one or more revisions.
1955 Print the changeset header and diffs for one or more revisions.
1956 If no revision is given, the parent of the working directory is used.
1956 If no revision is given, the parent of the working directory is used.
1957
1957
1958 The information shown in the changeset header is: author, date,
1958 The information shown in the changeset header is: author, date,
1959 branch name (if non-default), changeset hash, parent(s) and commit
1959 branch name (if non-default), changeset hash, parent(s) and commit
1960 comment.
1960 comment.
1961
1961
1962 .. note::
1962 .. note::
1963
1963
1964 :hg:`export` may generate unexpected diff output for merge
1964 :hg:`export` may generate unexpected diff output for merge
1965 changesets, as it will compare the merge changeset against its
1965 changesets, as it will compare the merge changeset against its
1966 first parent only.
1966 first parent only.
1967
1967
1968 Output may be to a file, in which case the name of the file is
1968 Output may be to a file, in which case the name of the file is
1969 given using a format string. The formatting rules are as follows:
1969 given using a format string. The formatting rules are as follows:
1970
1970
1971 :``%%``: literal "%" character
1971 :``%%``: literal "%" character
1972 :``%H``: changeset hash (40 hexadecimal digits)
1972 :``%H``: changeset hash (40 hexadecimal digits)
1973 :``%N``: number of patches being generated
1973 :``%N``: number of patches being generated
1974 :``%R``: changeset revision number
1974 :``%R``: changeset revision number
1975 :``%b``: basename of the exporting repository
1975 :``%b``: basename of the exporting repository
1976 :``%h``: short-form changeset hash (12 hexadecimal digits)
1976 :``%h``: short-form changeset hash (12 hexadecimal digits)
1977 :``%m``: first line of the commit message (only alphanumeric characters)
1977 :``%m``: first line of the commit message (only alphanumeric characters)
1978 :``%n``: zero-padded sequence number, starting at 1
1978 :``%n``: zero-padded sequence number, starting at 1
1979 :``%r``: zero-padded changeset revision number
1979 :``%r``: zero-padded changeset revision number
1980
1980
1981 Without the -a/--text option, export will avoid generating diffs
1981 Without the -a/--text option, export will avoid generating diffs
1982 of files it detects as binary. With -a, export will generate a
1982 of files it detects as binary. With -a, export will generate a
1983 diff anyway, probably with undesirable results.
1983 diff anyway, probably with undesirable results.
1984
1984
1985 Use the -g/--git option to generate diffs in the git extended diff
1985 Use the -g/--git option to generate diffs in the git extended diff
1986 format. See :hg:`help diffs` for more information.
1986 format. See :hg:`help diffs` for more information.
1987
1987
1988 With the --switch-parent option, the diff will be against the
1988 With the --switch-parent option, the diff will be against the
1989 second parent. It can be useful to review a merge.
1989 second parent. It can be useful to review a merge.
1990
1990
1991 .. container:: verbose
1991 .. container:: verbose
1992
1992
1993 Examples:
1993 Examples:
1994
1994
1995 - use export and import to transplant a bugfix to the current
1995 - use export and import to transplant a bugfix to the current
1996 branch::
1996 branch::
1997
1997
1998 hg export -r 9353 | hg import -
1998 hg export -r 9353 | hg import -
1999
1999
2000 - export all the changesets between two revisions to a file with
2000 - export all the changesets between two revisions to a file with
2001 rename information::
2001 rename information::
2002
2002
2003 hg export --git -r 123:150 > changes.txt
2003 hg export --git -r 123:150 > changes.txt
2004
2004
2005 - split outgoing changes into a series of patches with
2005 - split outgoing changes into a series of patches with
2006 descriptive names::
2006 descriptive names::
2007
2007
2008 hg export -r "outgoing()" -o "%n-%m.patch"
2008 hg export -r "outgoing()" -o "%n-%m.patch"
2009
2009
2010 Returns 0 on success.
2010 Returns 0 on success.
2011 """
2011 """
2012 changesets += tuple(opts.get('rev', []))
2012 changesets += tuple(opts.get('rev', []))
2013 if not changesets:
2013 if not changesets:
2014 changesets = ['.']
2014 changesets = ['.']
2015 revs = scmutil.revrange(repo, changesets)
2015 revs = scmutil.revrange(repo, changesets)
2016 if not revs:
2016 if not revs:
2017 raise error.Abort(_("export requires at least one changeset"))
2017 raise error.Abort(_("export requires at least one changeset"))
2018 if len(revs) > 1:
2018 if len(revs) > 1:
2019 ui.note(_('exporting patches:\n'))
2019 ui.note(_('exporting patches:\n'))
2020 else:
2020 else:
2021 ui.note(_('exporting patch:\n'))
2021 ui.note(_('exporting patch:\n'))
2022 cmdutil.export(repo, revs, template=opts.get('output'),
2022 cmdutil.export(repo, revs, template=opts.get('output'),
2023 switch_parent=opts.get('switch_parent'),
2023 switch_parent=opts.get('switch_parent'),
2024 opts=patch.diffallopts(ui, opts))
2024 opts=patch.diffallopts(ui, opts))
2025
2025
2026 @command('files',
2026 @command('files',
2027 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2027 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2028 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2028 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2029 ] + walkopts + formatteropts + subrepoopts,
2029 ] + walkopts + formatteropts + subrepoopts,
2030 _('[OPTION]... [FILE]...'))
2030 _('[OPTION]... [FILE]...'))
2031 def files(ui, repo, *pats, **opts):
2031 def files(ui, repo, *pats, **opts):
2032 """list tracked files
2032 """list tracked files
2033
2033
2034 Print files under Mercurial control in the working directory or
2034 Print files under Mercurial control in the working directory or
2035 specified revision for given files (excluding removed files).
2035 specified revision for given files (excluding removed files).
2036 Files can be specified as filenames or filesets.
2036 Files can be specified as filenames or filesets.
2037
2037
2038 If no files are given to match, this command prints the names
2038 If no files are given to match, this command prints the names
2039 of all files under Mercurial control.
2039 of all files under Mercurial control.
2040
2040
2041 .. container:: verbose
2041 .. container:: verbose
2042
2042
2043 Examples:
2043 Examples:
2044
2044
2045 - list all files under the current directory::
2045 - list all files under the current directory::
2046
2046
2047 hg files .
2047 hg files .
2048
2048
2049 - shows sizes and flags for current revision::
2049 - shows sizes and flags for current revision::
2050
2050
2051 hg files -vr .
2051 hg files -vr .
2052
2052
2053 - list all files named README::
2053 - list all files named README::
2054
2054
2055 hg files -I "**/README"
2055 hg files -I "**/README"
2056
2056
2057 - list all binary files::
2057 - list all binary files::
2058
2058
2059 hg files "set:binary()"
2059 hg files "set:binary()"
2060
2060
2061 - find files containing a regular expression::
2061 - find files containing a regular expression::
2062
2062
2063 hg files "set:grep('bob')"
2063 hg files "set:grep('bob')"
2064
2064
2065 - search tracked file contents with xargs and grep::
2065 - search tracked file contents with xargs and grep::
2066
2066
2067 hg files -0 | xargs -0 grep foo
2067 hg files -0 | xargs -0 grep foo
2068
2068
2069 See :hg:`help patterns` and :hg:`help filesets` for more information
2069 See :hg:`help patterns` and :hg:`help filesets` for more information
2070 on specifying file patterns.
2070 on specifying file patterns.
2071
2071
2072 Returns 0 if a match is found, 1 otherwise.
2072 Returns 0 if a match is found, 1 otherwise.
2073
2073
2074 """
2074 """
2075 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2075 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2076
2076
2077 end = '\n'
2077 end = '\n'
2078 if opts.get('print0'):
2078 if opts.get('print0'):
2079 end = '\0'
2079 end = '\0'
2080 fmt = '%s' + end
2080 fmt = '%s' + end
2081
2081
2082 m = scmutil.match(ctx, pats, opts)
2082 m = scmutil.match(ctx, pats, opts)
2083 with ui.formatter('files', opts) as fm:
2083 with ui.formatter('files', opts) as fm:
2084 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2084 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2085
2085
2086 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2086 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2087 def forget(ui, repo, *pats, **opts):
2087 def forget(ui, repo, *pats, **opts):
2088 """forget the specified files on the next commit
2088 """forget the specified files on the next commit
2089
2089
2090 Mark the specified files so they will no longer be tracked
2090 Mark the specified files so they will no longer be tracked
2091 after the next commit.
2091 after the next commit.
2092
2092
2093 This only removes files from the current branch, not from the
2093 This only removes files from the current branch, not from the
2094 entire project history, and it does not delete them from the
2094 entire project history, and it does not delete them from the
2095 working directory.
2095 working directory.
2096
2096
2097 To delete the file from the working directory, see :hg:`remove`.
2097 To delete the file from the working directory, see :hg:`remove`.
2098
2098
2099 To undo a forget before the next commit, see :hg:`add`.
2099 To undo a forget before the next commit, see :hg:`add`.
2100
2100
2101 .. container:: verbose
2101 .. container:: verbose
2102
2102
2103 Examples:
2103 Examples:
2104
2104
2105 - forget newly-added binary files::
2105 - forget newly-added binary files::
2106
2106
2107 hg forget "set:added() and binary()"
2107 hg forget "set:added() and binary()"
2108
2108
2109 - forget files that would be excluded by .hgignore::
2109 - forget files that would be excluded by .hgignore::
2110
2110
2111 hg forget "set:hgignore()"
2111 hg forget "set:hgignore()"
2112
2112
2113 Returns 0 on success.
2113 Returns 0 on success.
2114 """
2114 """
2115
2115
2116 if not pats:
2116 if not pats:
2117 raise error.Abort(_('no files specified'))
2117 raise error.Abort(_('no files specified'))
2118
2118
2119 m = scmutil.match(repo[None], pats, opts)
2119 m = scmutil.match(repo[None], pats, opts)
2120 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2120 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2121 return rejected and 1 or 0
2121 return rejected and 1 or 0
2122
2122
2123 @command(
2123 @command(
2124 'graft',
2124 'graft',
2125 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2125 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2126 ('c', 'continue', False, _('resume interrupted graft')),
2126 ('c', 'continue', False, _('resume interrupted graft')),
2127 ('e', 'edit', False, _('invoke editor on commit messages')),
2127 ('e', 'edit', False, _('invoke editor on commit messages')),
2128 ('', 'log', None, _('append graft info to log message')),
2128 ('', 'log', None, _('append graft info to log message')),
2129 ('f', 'force', False, _('force graft')),
2129 ('f', 'force', False, _('force graft')),
2130 ('D', 'currentdate', False,
2130 ('D', 'currentdate', False,
2131 _('record the current date as commit date')),
2131 _('record the current date as commit date')),
2132 ('U', 'currentuser', False,
2132 ('U', 'currentuser', False,
2133 _('record the current user as committer'), _('DATE'))]
2133 _('record the current user as committer'), _('DATE'))]
2134 + commitopts2 + mergetoolopts + dryrunopts,
2134 + commitopts2 + mergetoolopts + dryrunopts,
2135 _('[OPTION]... [-r REV]... REV...'))
2135 _('[OPTION]... [-r REV]... REV...'))
2136 def graft(ui, repo, *revs, **opts):
2136 def graft(ui, repo, *revs, **opts):
2137 '''copy changes from other branches onto the current branch
2137 '''copy changes from other branches onto the current branch
2138
2138
2139 This command uses Mercurial's merge logic to copy individual
2139 This command uses Mercurial's merge logic to copy individual
2140 changes from other branches without merging branches in the
2140 changes from other branches without merging branches in the
2141 history graph. This is sometimes known as 'backporting' or
2141 history graph. This is sometimes known as 'backporting' or
2142 'cherry-picking'. By default, graft will copy user, date, and
2142 'cherry-picking'. By default, graft will copy user, date, and
2143 description from the source changesets.
2143 description from the source changesets.
2144
2144
2145 Changesets that are ancestors of the current revision, that have
2145 Changesets that are ancestors of the current revision, that have
2146 already been grafted, or that are merges will be skipped.
2146 already been grafted, or that are merges will be skipped.
2147
2147
2148 If --log is specified, log messages will have a comment appended
2148 If --log is specified, log messages will have a comment appended
2149 of the form::
2149 of the form::
2150
2150
2151 (grafted from CHANGESETHASH)
2151 (grafted from CHANGESETHASH)
2152
2152
2153 If --force is specified, revisions will be grafted even if they
2153 If --force is specified, revisions will be grafted even if they
2154 are already ancestors of or have been grafted to the destination.
2154 are already ancestors of or have been grafted to the destination.
2155 This is useful when the revisions have since been backed out.
2155 This is useful when the revisions have since been backed out.
2156
2156
2157 If a graft merge results in conflicts, the graft process is
2157 If a graft merge results in conflicts, the graft process is
2158 interrupted so that the current merge can be manually resolved.
2158 interrupted so that the current merge can be manually resolved.
2159 Once all conflicts are addressed, the graft process can be
2159 Once all conflicts are addressed, the graft process can be
2160 continued with the -c/--continue option.
2160 continued with the -c/--continue option.
2161
2161
2162 .. note::
2162 .. note::
2163
2163
2164 The -c/--continue option does not reapply earlier options, except
2164 The -c/--continue option does not reapply earlier options, except
2165 for --force.
2165 for --force.
2166
2166
2167 .. container:: verbose
2167 .. container:: verbose
2168
2168
2169 Examples:
2169 Examples:
2170
2170
2171 - copy a single change to the stable branch and edit its description::
2171 - copy a single change to the stable branch and edit its description::
2172
2172
2173 hg update stable
2173 hg update stable
2174 hg graft --edit 9393
2174 hg graft --edit 9393
2175
2175
2176 - graft a range of changesets with one exception, updating dates::
2176 - graft a range of changesets with one exception, updating dates::
2177
2177
2178 hg graft -D "2085::2093 and not 2091"
2178 hg graft -D "2085::2093 and not 2091"
2179
2179
2180 - continue a graft after resolving conflicts::
2180 - continue a graft after resolving conflicts::
2181
2181
2182 hg graft -c
2182 hg graft -c
2183
2183
2184 - show the source of a grafted changeset::
2184 - show the source of a grafted changeset::
2185
2185
2186 hg log --debug -r .
2186 hg log --debug -r .
2187
2187
2188 - show revisions sorted by date::
2188 - show revisions sorted by date::
2189
2189
2190 hg log -r "sort(all(), date)"
2190 hg log -r "sort(all(), date)"
2191
2191
2192 See :hg:`help revisions` for more about specifying revisions.
2192 See :hg:`help revisions` for more about specifying revisions.
2193
2193
2194 Returns 0 on successful completion.
2194 Returns 0 on successful completion.
2195 '''
2195 '''
2196 with repo.wlock():
2196 with repo.wlock():
2197 return _dograft(ui, repo, *revs, **opts)
2197 return _dograft(ui, repo, *revs, **opts)
2198
2198
2199 def _dograft(ui, repo, *revs, **opts):
2199 def _dograft(ui, repo, *revs, **opts):
2200 if revs and opts.get('rev'):
2200 if revs and opts.get('rev'):
2201 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2201 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2202 'revision ordering!\n'))
2202 'revision ordering!\n'))
2203
2203
2204 revs = list(revs)
2204 revs = list(revs)
2205 revs.extend(opts.get('rev'))
2205 revs.extend(opts.get('rev'))
2206
2206
2207 if not opts.get('user') and opts.get('currentuser'):
2207 if not opts.get('user') and opts.get('currentuser'):
2208 opts['user'] = ui.username()
2208 opts['user'] = ui.username()
2209 if not opts.get('date') and opts.get('currentdate'):
2209 if not opts.get('date') and opts.get('currentdate'):
2210 opts['date'] = "%d %d" % util.makedate()
2210 opts['date'] = "%d %d" % util.makedate()
2211
2211
2212 editor = cmdutil.getcommiteditor(editform='graft', **opts)
2212 editor = cmdutil.getcommiteditor(editform='graft', **opts)
2213
2213
2214 cont = False
2214 cont = False
2215 if opts.get('continue'):
2215 if opts.get('continue'):
2216 cont = True
2216 cont = True
2217 if revs:
2217 if revs:
2218 raise error.Abort(_("can't specify --continue and revisions"))
2218 raise error.Abort(_("can't specify --continue and revisions"))
2219 # read in unfinished revisions
2219 # read in unfinished revisions
2220 try:
2220 try:
2221 nodes = repo.vfs.read('graftstate').splitlines()
2221 nodes = repo.vfs.read('graftstate').splitlines()
2222 revs = [repo[node].rev() for node in nodes]
2222 revs = [repo[node].rev() for node in nodes]
2223 except IOError as inst:
2223 except IOError as inst:
2224 if inst.errno != errno.ENOENT:
2224 if inst.errno != errno.ENOENT:
2225 raise
2225 raise
2226 cmdutil.wrongtooltocontinue(repo, _('graft'))
2226 cmdutil.wrongtooltocontinue(repo, _('graft'))
2227 else:
2227 else:
2228 cmdutil.checkunfinished(repo)
2228 cmdutil.checkunfinished(repo)
2229 cmdutil.bailifchanged(repo)
2229 cmdutil.bailifchanged(repo)
2230 if not revs:
2230 if not revs:
2231 raise error.Abort(_('no revisions specified'))
2231 raise error.Abort(_('no revisions specified'))
2232 revs = scmutil.revrange(repo, revs)
2232 revs = scmutil.revrange(repo, revs)
2233
2233
2234 skipped = set()
2234 skipped = set()
2235 # check for merges
2235 # check for merges
2236 for rev in repo.revs('%ld and merge()', revs):
2236 for rev in repo.revs('%ld and merge()', revs):
2237 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2237 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2238 skipped.add(rev)
2238 skipped.add(rev)
2239 revs = [r for r in revs if r not in skipped]
2239 revs = [r for r in revs if r not in skipped]
2240 if not revs:
2240 if not revs:
2241 return -1
2241 return -1
2242
2242
2243 # Don't check in the --continue case, in effect retaining --force across
2243 # Don't check in the --continue case, in effect retaining --force across
2244 # --continues. That's because without --force, any revisions we decided to
2244 # --continues. That's because without --force, any revisions we decided to
2245 # skip would have been filtered out here, so they wouldn't have made their
2245 # skip would have been filtered out here, so they wouldn't have made their
2246 # way to the graftstate. With --force, any revisions we would have otherwise
2246 # way to the graftstate. With --force, any revisions we would have otherwise
2247 # skipped would not have been filtered out, and if they hadn't been applied
2247 # skipped would not have been filtered out, and if they hadn't been applied
2248 # already, they'd have been in the graftstate.
2248 # already, they'd have been in the graftstate.
2249 if not (cont or opts.get('force')):
2249 if not (cont or opts.get('force')):
2250 # check for ancestors of dest branch
2250 # check for ancestors of dest branch
2251 crev = repo['.'].rev()
2251 crev = repo['.'].rev()
2252 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2252 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2253 # XXX make this lazy in the future
2253 # XXX make this lazy in the future
2254 # don't mutate while iterating, create a copy
2254 # don't mutate while iterating, create a copy
2255 for rev in list(revs):
2255 for rev in list(revs):
2256 if rev in ancestors:
2256 if rev in ancestors:
2257 ui.warn(_('skipping ancestor revision %d:%s\n') %
2257 ui.warn(_('skipping ancestor revision %d:%s\n') %
2258 (rev, repo[rev]))
2258 (rev, repo[rev]))
2259 # XXX remove on list is slow
2259 # XXX remove on list is slow
2260 revs.remove(rev)
2260 revs.remove(rev)
2261 if not revs:
2261 if not revs:
2262 return -1
2262 return -1
2263
2263
2264 # analyze revs for earlier grafts
2264 # analyze revs for earlier grafts
2265 ids = {}
2265 ids = {}
2266 for ctx in repo.set("%ld", revs):
2266 for ctx in repo.set("%ld", revs):
2267 ids[ctx.hex()] = ctx.rev()
2267 ids[ctx.hex()] = ctx.rev()
2268 n = ctx.extra().get('source')
2268 n = ctx.extra().get('source')
2269 if n:
2269 if n:
2270 ids[n] = ctx.rev()
2270 ids[n] = ctx.rev()
2271
2271
2272 # check ancestors for earlier grafts
2272 # check ancestors for earlier grafts
2273 ui.debug('scanning for duplicate grafts\n')
2273 ui.debug('scanning for duplicate grafts\n')
2274
2274
2275 for rev in repo.changelog.findmissingrevs(revs, [crev]):
2275 for rev in repo.changelog.findmissingrevs(revs, [crev]):
2276 ctx = repo[rev]
2276 ctx = repo[rev]
2277 n = ctx.extra().get('source')
2277 n = ctx.extra().get('source')
2278 if n in ids:
2278 if n in ids:
2279 try:
2279 try:
2280 r = repo[n].rev()
2280 r = repo[n].rev()
2281 except error.RepoLookupError:
2281 except error.RepoLookupError:
2282 r = None
2282 r = None
2283 if r in revs:
2283 if r in revs:
2284 ui.warn(_('skipping revision %d:%s '
2284 ui.warn(_('skipping revision %d:%s '
2285 '(already grafted to %d:%s)\n')
2285 '(already grafted to %d:%s)\n')
2286 % (r, repo[r], rev, ctx))
2286 % (r, repo[r], rev, ctx))
2287 revs.remove(r)
2287 revs.remove(r)
2288 elif ids[n] in revs:
2288 elif ids[n] in revs:
2289 if r is None:
2289 if r is None:
2290 ui.warn(_('skipping already grafted revision %d:%s '
2290 ui.warn(_('skipping already grafted revision %d:%s '
2291 '(%d:%s also has unknown origin %s)\n')
2291 '(%d:%s also has unknown origin %s)\n')
2292 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2292 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2293 else:
2293 else:
2294 ui.warn(_('skipping already grafted revision %d:%s '
2294 ui.warn(_('skipping already grafted revision %d:%s '
2295 '(%d:%s also has origin %d:%s)\n')
2295 '(%d:%s also has origin %d:%s)\n')
2296 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2296 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2297 revs.remove(ids[n])
2297 revs.remove(ids[n])
2298 elif ctx.hex() in ids:
2298 elif ctx.hex() in ids:
2299 r = ids[ctx.hex()]
2299 r = ids[ctx.hex()]
2300 ui.warn(_('skipping already grafted revision %d:%s '
2300 ui.warn(_('skipping already grafted revision %d:%s '
2301 '(was grafted from %d:%s)\n') %
2301 '(was grafted from %d:%s)\n') %
2302 (r, repo[r], rev, ctx))
2302 (r, repo[r], rev, ctx))
2303 revs.remove(r)
2303 revs.remove(r)
2304 if not revs:
2304 if not revs:
2305 return -1
2305 return -1
2306
2306
2307 for pos, ctx in enumerate(repo.set("%ld", revs)):
2307 for pos, ctx in enumerate(repo.set("%ld", revs)):
2308 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2308 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2309 ctx.description().split('\n', 1)[0])
2309 ctx.description().split('\n', 1)[0])
2310 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2310 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2311 if names:
2311 if names:
2312 desc += ' (%s)' % ' '.join(names)
2312 desc += ' (%s)' % ' '.join(names)
2313 ui.status(_('grafting %s\n') % desc)
2313 ui.status(_('grafting %s\n') % desc)
2314 if opts.get('dry_run'):
2314 if opts.get('dry_run'):
2315 continue
2315 continue
2316
2316
2317 source = ctx.extra().get('source')
2317 source = ctx.extra().get('source')
2318 extra = {}
2318 extra = {}
2319 if source:
2319 if source:
2320 extra['source'] = source
2320 extra['source'] = source
2321 extra['intermediate-source'] = ctx.hex()
2321 extra['intermediate-source'] = ctx.hex()
2322 else:
2322 else:
2323 extra['source'] = ctx.hex()
2323 extra['source'] = ctx.hex()
2324 user = ctx.user()
2324 user = ctx.user()
2325 if opts.get('user'):
2325 if opts.get('user'):
2326 user = opts['user']
2326 user = opts['user']
2327 date = ctx.date()
2327 date = ctx.date()
2328 if opts.get('date'):
2328 if opts.get('date'):
2329 date = opts['date']
2329 date = opts['date']
2330 message = ctx.description()
2330 message = ctx.description()
2331 if opts.get('log'):
2331 if opts.get('log'):
2332 message += '\n(grafted from %s)' % ctx.hex()
2332 message += '\n(grafted from %s)' % ctx.hex()
2333
2333
2334 # we don't merge the first commit when continuing
2334 # we don't merge the first commit when continuing
2335 if not cont:
2335 if not cont:
2336 # perform the graft merge with p1(rev) as 'ancestor'
2336 # perform the graft merge with p1(rev) as 'ancestor'
2337 try:
2337 try:
2338 # ui.forcemerge is an internal variable, do not document
2338 # ui.forcemerge is an internal variable, do not document
2339 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2339 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2340 'graft')
2340 'graft')
2341 stats = mergemod.graft(repo, ctx, ctx.p1(),
2341 stats = mergemod.graft(repo, ctx, ctx.p1(),
2342 ['local', 'graft'])
2342 ['local', 'graft'])
2343 finally:
2343 finally:
2344 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2344 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2345 # report any conflicts
2345 # report any conflicts
2346 if stats and stats[3] > 0:
2346 if stats and stats[3] > 0:
2347 # write out state for --continue
2347 # write out state for --continue
2348 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2348 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2349 repo.vfs.write('graftstate', ''.join(nodelines))
2349 repo.vfs.write('graftstate', ''.join(nodelines))
2350 extra = ''
2350 extra = ''
2351 if opts.get('user'):
2351 if opts.get('user'):
2352 extra += ' --user %s' % util.shellquote(opts['user'])
2352 extra += ' --user %s' % util.shellquote(opts['user'])
2353 if opts.get('date'):
2353 if opts.get('date'):
2354 extra += ' --date %s' % util.shellquote(opts['date'])
2354 extra += ' --date %s' % util.shellquote(opts['date'])
2355 if opts.get('log'):
2355 if opts.get('log'):
2356 extra += ' --log'
2356 extra += ' --log'
2357 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2357 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2358 raise error.Abort(
2358 raise error.Abort(
2359 _("unresolved conflicts, can't continue"),
2359 _("unresolved conflicts, can't continue"),
2360 hint=hint)
2360 hint=hint)
2361 else:
2361 else:
2362 cont = False
2362 cont = False
2363
2363
2364 # commit
2364 # commit
2365 node = repo.commit(text=message, user=user,
2365 node = repo.commit(text=message, user=user,
2366 date=date, extra=extra, editor=editor)
2366 date=date, extra=extra, editor=editor)
2367 if node is None:
2367 if node is None:
2368 ui.warn(
2368 ui.warn(
2369 _('note: graft of %d:%s created no changes to commit\n') %
2369 _('note: graft of %d:%s created no changes to commit\n') %
2370 (ctx.rev(), ctx))
2370 (ctx.rev(), ctx))
2371
2371
2372 # remove state when we complete successfully
2372 # remove state when we complete successfully
2373 if not opts.get('dry_run'):
2373 if not opts.get('dry_run'):
2374 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
2374 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
2375
2375
2376 return 0
2376 return 0
2377
2377
2378 @command('grep',
2378 @command('grep',
2379 [('0', 'print0', None, _('end fields with NUL')),
2379 [('0', 'print0', None, _('end fields with NUL')),
2380 ('', 'all', None, _('print all revisions that match')),
2380 ('', 'all', None, _('print all revisions that match')),
2381 ('a', 'text', None, _('treat all files as text')),
2381 ('a', 'text', None, _('treat all files as text')),
2382 ('f', 'follow', None,
2382 ('f', 'follow', None,
2383 _('follow changeset history,'
2383 _('follow changeset history,'
2384 ' or file history across copies and renames')),
2384 ' or file history across copies and renames')),
2385 ('i', 'ignore-case', None, _('ignore case when matching')),
2385 ('i', 'ignore-case', None, _('ignore case when matching')),
2386 ('l', 'files-with-matches', None,
2386 ('l', 'files-with-matches', None,
2387 _('print only filenames and revisions that match')),
2387 _('print only filenames and revisions that match')),
2388 ('n', 'line-number', None, _('print matching line numbers')),
2388 ('n', 'line-number', None, _('print matching line numbers')),
2389 ('r', 'rev', [],
2389 ('r', 'rev', [],
2390 _('only search files changed within revision range'), _('REV')),
2390 _('only search files changed within revision range'), _('REV')),
2391 ('u', 'user', None, _('list the author (long with -v)')),
2391 ('u', 'user', None, _('list the author (long with -v)')),
2392 ('d', 'date', None, _('list the date (short with -q)')),
2392 ('d', 'date', None, _('list the date (short with -q)')),
2393 ] + formatteropts + walkopts,
2393 ] + formatteropts + walkopts,
2394 _('[OPTION]... PATTERN [FILE]...'),
2394 _('[OPTION]... PATTERN [FILE]...'),
2395 inferrepo=True)
2395 inferrepo=True)
2396 def grep(ui, repo, pattern, *pats, **opts):
2396 def grep(ui, repo, pattern, *pats, **opts):
2397 """search revision history for a pattern in specified files
2397 """search revision history for a pattern in specified files
2398
2398
2399 Search revision history for a regular expression in the specified
2399 Search revision history for a regular expression in the specified
2400 files or the entire project.
2400 files or the entire project.
2401
2401
2402 By default, grep prints the most recent revision number for each
2402 By default, grep prints the most recent revision number for each
2403 file in which it finds a match. To get it to print every revision
2403 file in which it finds a match. To get it to print every revision
2404 that contains a change in match status ("-" for a match that becomes
2404 that contains a change in match status ("-" for a match that becomes
2405 a non-match, or "+" for a non-match that becomes a match), use the
2405 a non-match, or "+" for a non-match that becomes a match), use the
2406 --all flag.
2406 --all flag.
2407
2407
2408 PATTERN can be any Python (roughly Perl-compatible) regular
2408 PATTERN can be any Python (roughly Perl-compatible) regular
2409 expression.
2409 expression.
2410
2410
2411 If no FILEs are specified (and -f/--follow isn't set), all files in
2411 If no FILEs are specified (and -f/--follow isn't set), all files in
2412 the repository are searched, including those that don't exist in the
2412 the repository are searched, including those that don't exist in the
2413 current branch or have been deleted in a prior changeset.
2413 current branch or have been deleted in a prior changeset.
2414
2414
2415 Returns 0 if a match is found, 1 otherwise.
2415 Returns 0 if a match is found, 1 otherwise.
2416 """
2416 """
2417 reflags = re.M
2417 reflags = re.M
2418 if opts.get('ignore_case'):
2418 if opts.get('ignore_case'):
2419 reflags |= re.I
2419 reflags |= re.I
2420 try:
2420 try:
2421 regexp = util.re.compile(pattern, reflags)
2421 regexp = util.re.compile(pattern, reflags)
2422 except re.error as inst:
2422 except re.error as inst:
2423 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2423 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2424 return 1
2424 return 1
2425 sep, eol = ':', '\n'
2425 sep, eol = ':', '\n'
2426 if opts.get('print0'):
2426 if opts.get('print0'):
2427 sep = eol = '\0'
2427 sep = eol = '\0'
2428
2428
2429 getfile = util.lrucachefunc(repo.file)
2429 getfile = util.lrucachefunc(repo.file)
2430
2430
2431 def matchlines(body):
2431 def matchlines(body):
2432 begin = 0
2432 begin = 0
2433 linenum = 0
2433 linenum = 0
2434 while begin < len(body):
2434 while begin < len(body):
2435 match = regexp.search(body, begin)
2435 match = regexp.search(body, begin)
2436 if not match:
2436 if not match:
2437 break
2437 break
2438 mstart, mend = match.span()
2438 mstart, mend = match.span()
2439 linenum += body.count('\n', begin, mstart) + 1
2439 linenum += body.count('\n', begin, mstart) + 1
2440 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2440 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2441 begin = body.find('\n', mend) + 1 or len(body) + 1
2441 begin = body.find('\n', mend) + 1 or len(body) + 1
2442 lend = begin - 1
2442 lend = begin - 1
2443 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2443 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2444
2444
2445 class linestate(object):
2445 class linestate(object):
2446 def __init__(self, line, linenum, colstart, colend):
2446 def __init__(self, line, linenum, colstart, colend):
2447 self.line = line
2447 self.line = line
2448 self.linenum = linenum
2448 self.linenum = linenum
2449 self.colstart = colstart
2449 self.colstart = colstart
2450 self.colend = colend
2450 self.colend = colend
2451
2451
2452 def __hash__(self):
2452 def __hash__(self):
2453 return hash((self.linenum, self.line))
2453 return hash((self.linenum, self.line))
2454
2454
2455 def __eq__(self, other):
2455 def __eq__(self, other):
2456 return self.line == other.line
2456 return self.line == other.line
2457
2457
2458 def findpos(self):
2458 def findpos(self):
2459 """Iterate all (start, end) indices of matches"""
2459 """Iterate all (start, end) indices of matches"""
2460 yield self.colstart, self.colend
2460 yield self.colstart, self.colend
2461 p = self.colend
2461 p = self.colend
2462 while p < len(self.line):
2462 while p < len(self.line):
2463 m = regexp.search(self.line, p)
2463 m = regexp.search(self.line, p)
2464 if not m:
2464 if not m:
2465 break
2465 break
2466 yield m.span()
2466 yield m.span()
2467 p = m.end()
2467 p = m.end()
2468
2468
2469 matches = {}
2469 matches = {}
2470 copies = {}
2470 copies = {}
2471 def grepbody(fn, rev, body):
2471 def grepbody(fn, rev, body):
2472 matches[rev].setdefault(fn, [])
2472 matches[rev].setdefault(fn, [])
2473 m = matches[rev][fn]
2473 m = matches[rev][fn]
2474 for lnum, cstart, cend, line in matchlines(body):
2474 for lnum, cstart, cend, line in matchlines(body):
2475 s = linestate(line, lnum, cstart, cend)
2475 s = linestate(line, lnum, cstart, cend)
2476 m.append(s)
2476 m.append(s)
2477
2477
2478 def difflinestates(a, b):
2478 def difflinestates(a, b):
2479 sm = difflib.SequenceMatcher(None, a, b)
2479 sm = difflib.SequenceMatcher(None, a, b)
2480 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2480 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2481 if tag == 'insert':
2481 if tag == 'insert':
2482 for i in xrange(blo, bhi):
2482 for i in xrange(blo, bhi):
2483 yield ('+', b[i])
2483 yield ('+', b[i])
2484 elif tag == 'delete':
2484 elif tag == 'delete':
2485 for i in xrange(alo, ahi):
2485 for i in xrange(alo, ahi):
2486 yield ('-', a[i])
2486 yield ('-', a[i])
2487 elif tag == 'replace':
2487 elif tag == 'replace':
2488 for i in xrange(alo, ahi):
2488 for i in xrange(alo, ahi):
2489 yield ('-', a[i])
2489 yield ('-', a[i])
2490 for i in xrange(blo, bhi):
2490 for i in xrange(blo, bhi):
2491 yield ('+', b[i])
2491 yield ('+', b[i])
2492
2492
2493 def display(fm, fn, ctx, pstates, states):
2493 def display(fm, fn, ctx, pstates, states):
2494 rev = ctx.rev()
2494 rev = ctx.rev()
2495 if fm.isplain():
2495 if fm.isplain():
2496 formatuser = ui.shortuser
2496 formatuser = ui.shortuser
2497 else:
2497 else:
2498 formatuser = str
2498 formatuser = str
2499 if ui.quiet:
2499 if ui.quiet:
2500 datefmt = '%Y-%m-%d'
2500 datefmt = '%Y-%m-%d'
2501 else:
2501 else:
2502 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2502 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2503 found = False
2503 found = False
2504 @util.cachefunc
2504 @util.cachefunc
2505 def binary():
2505 def binary():
2506 flog = getfile(fn)
2506 flog = getfile(fn)
2507 return util.binary(flog.read(ctx.filenode(fn)))
2507 return util.binary(flog.read(ctx.filenode(fn)))
2508
2508
2509 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2509 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2510 if opts.get('all'):
2510 if opts.get('all'):
2511 iter = difflinestates(pstates, states)
2511 iter = difflinestates(pstates, states)
2512 else:
2512 else:
2513 iter = [('', l) for l in states]
2513 iter = [('', l) for l in states]
2514 for change, l in iter:
2514 for change, l in iter:
2515 fm.startitem()
2515 fm.startitem()
2516 fm.data(node=fm.hexfunc(ctx.node()))
2516 fm.data(node=fm.hexfunc(ctx.node()))
2517 cols = [
2517 cols = [
2518 ('filename', fn, True),
2518 ('filename', fn, True),
2519 ('rev', rev, True),
2519 ('rev', rev, True),
2520 ('linenumber', l.linenum, opts.get('line_number')),
2520 ('linenumber', l.linenum, opts.get('line_number')),
2521 ]
2521 ]
2522 if opts.get('all'):
2522 if opts.get('all'):
2523 cols.append(('change', change, True))
2523 cols.append(('change', change, True))
2524 cols.extend([
2524 cols.extend([
2525 ('user', formatuser(ctx.user()), opts.get('user')),
2525 ('user', formatuser(ctx.user()), opts.get('user')),
2526 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2526 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2527 ])
2527 ])
2528 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2528 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2529 for name, data, cond in cols:
2529 for name, data, cond in cols:
2530 field = fieldnamemap.get(name, name)
2530 field = fieldnamemap.get(name, name)
2531 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2531 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2532 if cond and name != lastcol:
2532 if cond and name != lastcol:
2533 fm.plain(sep, label='grep.sep')
2533 fm.plain(sep, label='grep.sep')
2534 if not opts.get('files_with_matches'):
2534 if not opts.get('files_with_matches'):
2535 fm.plain(sep, label='grep.sep')
2535 fm.plain(sep, label='grep.sep')
2536 if not opts.get('text') and binary():
2536 if not opts.get('text') and binary():
2537 fm.plain(_(" Binary file matches"))
2537 fm.plain(_(" Binary file matches"))
2538 else:
2538 else:
2539 displaymatches(fm.nested('texts'), l)
2539 displaymatches(fm.nested('texts'), l)
2540 fm.plain(eol)
2540 fm.plain(eol)
2541 found = True
2541 found = True
2542 if opts.get('files_with_matches'):
2542 if opts.get('files_with_matches'):
2543 break
2543 break
2544 return found
2544 return found
2545
2545
2546 def displaymatches(fm, l):
2546 def displaymatches(fm, l):
2547 p = 0
2547 p = 0
2548 for s, e in l.findpos():
2548 for s, e in l.findpos():
2549 if p < s:
2549 if p < s:
2550 fm.startitem()
2550 fm.startitem()
2551 fm.write('text', '%s', l.line[p:s])
2551 fm.write('text', '%s', l.line[p:s])
2552 fm.data(matched=False)
2552 fm.data(matched=False)
2553 fm.startitem()
2553 fm.startitem()
2554 fm.write('text', '%s', l.line[s:e], label='grep.match')
2554 fm.write('text', '%s', l.line[s:e], label='grep.match')
2555 fm.data(matched=True)
2555 fm.data(matched=True)
2556 p = e
2556 p = e
2557 if p < len(l.line):
2557 if p < len(l.line):
2558 fm.startitem()
2558 fm.startitem()
2559 fm.write('text', '%s', l.line[p:])
2559 fm.write('text', '%s', l.line[p:])
2560 fm.data(matched=False)
2560 fm.data(matched=False)
2561 fm.end()
2561 fm.end()
2562
2562
2563 skip = {}
2563 skip = {}
2564 revfiles = {}
2564 revfiles = {}
2565 matchfn = scmutil.match(repo[None], pats, opts)
2565 matchfn = scmutil.match(repo[None], pats, opts)
2566 found = False
2566 found = False
2567 follow = opts.get('follow')
2567 follow = opts.get('follow')
2568
2568
2569 def prep(ctx, fns):
2569 def prep(ctx, fns):
2570 rev = ctx.rev()
2570 rev = ctx.rev()
2571 pctx = ctx.p1()
2571 pctx = ctx.p1()
2572 parent = pctx.rev()
2572 parent = pctx.rev()
2573 matches.setdefault(rev, {})
2573 matches.setdefault(rev, {})
2574 matches.setdefault(parent, {})
2574 matches.setdefault(parent, {})
2575 files = revfiles.setdefault(rev, [])
2575 files = revfiles.setdefault(rev, [])
2576 for fn in fns:
2576 for fn in fns:
2577 flog = getfile(fn)
2577 flog = getfile(fn)
2578 try:
2578 try:
2579 fnode = ctx.filenode(fn)
2579 fnode = ctx.filenode(fn)
2580 except error.LookupError:
2580 except error.LookupError:
2581 continue
2581 continue
2582
2582
2583 copied = flog.renamed(fnode)
2583 copied = flog.renamed(fnode)
2584 copy = follow and copied and copied[0]
2584 copy = follow and copied and copied[0]
2585 if copy:
2585 if copy:
2586 copies.setdefault(rev, {})[fn] = copy
2586 copies.setdefault(rev, {})[fn] = copy
2587 if fn in skip:
2587 if fn in skip:
2588 if copy:
2588 if copy:
2589 skip[copy] = True
2589 skip[copy] = True
2590 continue
2590 continue
2591 files.append(fn)
2591 files.append(fn)
2592
2592
2593 if fn not in matches[rev]:
2593 if fn not in matches[rev]:
2594 grepbody(fn, rev, flog.read(fnode))
2594 grepbody(fn, rev, flog.read(fnode))
2595
2595
2596 pfn = copy or fn
2596 pfn = copy or fn
2597 if pfn not in matches[parent]:
2597 if pfn not in matches[parent]:
2598 try:
2598 try:
2599 fnode = pctx.filenode(pfn)
2599 fnode = pctx.filenode(pfn)
2600 grepbody(pfn, parent, flog.read(fnode))
2600 grepbody(pfn, parent, flog.read(fnode))
2601 except error.LookupError:
2601 except error.LookupError:
2602 pass
2602 pass
2603
2603
2604 fm = ui.formatter('grep', opts)
2604 fm = ui.formatter('grep', opts)
2605 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2605 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2606 rev = ctx.rev()
2606 rev = ctx.rev()
2607 parent = ctx.p1().rev()
2607 parent = ctx.p1().rev()
2608 for fn in sorted(revfiles.get(rev, [])):
2608 for fn in sorted(revfiles.get(rev, [])):
2609 states = matches[rev][fn]
2609 states = matches[rev][fn]
2610 copy = copies.get(rev, {}).get(fn)
2610 copy = copies.get(rev, {}).get(fn)
2611 if fn in skip:
2611 if fn in skip:
2612 if copy:
2612 if copy:
2613 skip[copy] = True
2613 skip[copy] = True
2614 continue
2614 continue
2615 pstates = matches.get(parent, {}).get(copy or fn, [])
2615 pstates = matches.get(parent, {}).get(copy or fn, [])
2616 if pstates or states:
2616 if pstates or states:
2617 r = display(fm, fn, ctx, pstates, states)
2617 r = display(fm, fn, ctx, pstates, states)
2618 found = found or r
2618 found = found or r
2619 if r and not opts.get('all'):
2619 if r and not opts.get('all'):
2620 skip[fn] = True
2620 skip[fn] = True
2621 if copy:
2621 if copy:
2622 skip[copy] = True
2622 skip[copy] = True
2623 del matches[rev]
2623 del matches[rev]
2624 del revfiles[rev]
2624 del revfiles[rev]
2625 fm.end()
2625 fm.end()
2626
2626
2627 return not found
2627 return not found
2628
2628
2629 @command('heads',
2629 @command('heads',
2630 [('r', 'rev', '',
2630 [('r', 'rev', '',
2631 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2631 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2632 ('t', 'topo', False, _('show topological heads only')),
2632 ('t', 'topo', False, _('show topological heads only')),
2633 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2633 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2634 ('c', 'closed', False, _('show normal and closed branch heads')),
2634 ('c', 'closed', False, _('show normal and closed branch heads')),
2635 ] + templateopts,
2635 ] + templateopts,
2636 _('[-ct] [-r STARTREV] [REV]...'))
2636 _('[-ct] [-r STARTREV] [REV]...'))
2637 def heads(ui, repo, *branchrevs, **opts):
2637 def heads(ui, repo, *branchrevs, **opts):
2638 """show branch heads
2638 """show branch heads
2639
2639
2640 With no arguments, show all open branch heads in the repository.
2640 With no arguments, show all open branch heads in the repository.
2641 Branch heads are changesets that have no descendants on the
2641 Branch heads are changesets that have no descendants on the
2642 same branch. They are where development generally takes place and
2642 same branch. They are where development generally takes place and
2643 are the usual targets for update and merge operations.
2643 are the usual targets for update and merge operations.
2644
2644
2645 If one or more REVs are given, only open branch heads on the
2645 If one or more REVs are given, only open branch heads on the
2646 branches associated with the specified changesets are shown. This
2646 branches associated with the specified changesets are shown. This
2647 means that you can use :hg:`heads .` to see the heads on the
2647 means that you can use :hg:`heads .` to see the heads on the
2648 currently checked-out branch.
2648 currently checked-out branch.
2649
2649
2650 If -c/--closed is specified, also show branch heads marked closed
2650 If -c/--closed is specified, also show branch heads marked closed
2651 (see :hg:`commit --close-branch`).
2651 (see :hg:`commit --close-branch`).
2652
2652
2653 If STARTREV is specified, only those heads that are descendants of
2653 If STARTREV is specified, only those heads that are descendants of
2654 STARTREV will be displayed.
2654 STARTREV will be displayed.
2655
2655
2656 If -t/--topo is specified, named branch mechanics will be ignored and only
2656 If -t/--topo is specified, named branch mechanics will be ignored and only
2657 topological heads (changesets with no children) will be shown.
2657 topological heads (changesets with no children) will be shown.
2658
2658
2659 Returns 0 if matching heads are found, 1 if not.
2659 Returns 0 if matching heads are found, 1 if not.
2660 """
2660 """
2661
2661
2662 start = None
2662 start = None
2663 if 'rev' in opts:
2663 if 'rev' in opts:
2664 start = scmutil.revsingle(repo, opts['rev'], None).node()
2664 start = scmutil.revsingle(repo, opts['rev'], None).node()
2665
2665
2666 if opts.get('topo'):
2666 if opts.get('topo'):
2667 heads = [repo[h] for h in repo.heads(start)]
2667 heads = [repo[h] for h in repo.heads(start)]
2668 else:
2668 else:
2669 heads = []
2669 heads = []
2670 for branch in repo.branchmap():
2670 for branch in repo.branchmap():
2671 heads += repo.branchheads(branch, start, opts.get('closed'))
2671 heads += repo.branchheads(branch, start, opts.get('closed'))
2672 heads = [repo[h] for h in heads]
2672 heads = [repo[h] for h in heads]
2673
2673
2674 if branchrevs:
2674 if branchrevs:
2675 branches = set(repo[br].branch() for br in branchrevs)
2675 branches = set(repo[br].branch() for br in branchrevs)
2676 heads = [h for h in heads if h.branch() in branches]
2676 heads = [h for h in heads if h.branch() in branches]
2677
2677
2678 if opts.get('active') and branchrevs:
2678 if opts.get('active') and branchrevs:
2679 dagheads = repo.heads(start)
2679 dagheads = repo.heads(start)
2680 heads = [h for h in heads if h.node() in dagheads]
2680 heads = [h for h in heads if h.node() in dagheads]
2681
2681
2682 if branchrevs:
2682 if branchrevs:
2683 haveheads = set(h.branch() for h in heads)
2683 haveheads = set(h.branch() for h in heads)
2684 if branches - haveheads:
2684 if branches - haveheads:
2685 headless = ', '.join(b for b in branches - haveheads)
2685 headless = ', '.join(b for b in branches - haveheads)
2686 msg = _('no open branch heads found on branches %s')
2686 msg = _('no open branch heads found on branches %s')
2687 if opts.get('rev'):
2687 if opts.get('rev'):
2688 msg += _(' (started at %s)') % opts['rev']
2688 msg += _(' (started at %s)') % opts['rev']
2689 ui.warn((msg + '\n') % headless)
2689 ui.warn((msg + '\n') % headless)
2690
2690
2691 if not heads:
2691 if not heads:
2692 return 1
2692 return 1
2693
2693
2694 heads = sorted(heads, key=lambda x: -x.rev())
2694 heads = sorted(heads, key=lambda x: -x.rev())
2695 displayer = cmdutil.show_changeset(ui, repo, opts)
2695 displayer = cmdutil.show_changeset(ui, repo, opts)
2696 for ctx in heads:
2696 for ctx in heads:
2697 displayer.show(ctx)
2697 displayer.show(ctx)
2698 displayer.close()
2698 displayer.close()
2699
2699
2700 @command('help',
2700 @command('help',
2701 [('e', 'extension', None, _('show only help for extensions')),
2701 [('e', 'extension', None, _('show only help for extensions')),
2702 ('c', 'command', None, _('show only help for commands')),
2702 ('c', 'command', None, _('show only help for commands')),
2703 ('k', 'keyword', None, _('show topics matching keyword')),
2703 ('k', 'keyword', None, _('show topics matching keyword')),
2704 ('s', 'system', [], _('show help for specific platform(s)')),
2704 ('s', 'system', [], _('show help for specific platform(s)')),
2705 ],
2705 ],
2706 _('[-ecks] [TOPIC]'),
2706 _('[-ecks] [TOPIC]'),
2707 norepo=True)
2707 norepo=True)
2708 def help_(ui, name=None, **opts):
2708 def help_(ui, name=None, **opts):
2709 """show help for a given topic or a help overview
2709 """show help for a given topic or a help overview
2710
2710
2711 With no arguments, print a list of commands with short help messages.
2711 With no arguments, print a list of commands with short help messages.
2712
2712
2713 Given a topic, extension, or command name, print help for that
2713 Given a topic, extension, or command name, print help for that
2714 topic.
2714 topic.
2715
2715
2716 Returns 0 if successful.
2716 Returns 0 if successful.
2717 """
2717 """
2718
2718
2719 textwidth = ui.configint('ui', 'textwidth', 78)
2719 textwidth = ui.configint('ui', 'textwidth', 78)
2720 termwidth = ui.termwidth() - 2
2720 termwidth = ui.termwidth() - 2
2721 if textwidth <= 0 or termwidth < textwidth:
2721 if textwidth <= 0 or termwidth < textwidth:
2722 textwidth = termwidth
2722 textwidth = termwidth
2723
2723
2724 keep = opts.get('system') or []
2724 keep = opts.get('system') or []
2725 if len(keep) == 0:
2725 if len(keep) == 0:
2726 if pycompat.sysplatform.startswith('win'):
2726 if pycompat.sysplatform.startswith('win'):
2727 keep.append('windows')
2727 keep.append('windows')
2728 elif pycompat.sysplatform == 'OpenVMS':
2728 elif pycompat.sysplatform == 'OpenVMS':
2729 keep.append('vms')
2729 keep.append('vms')
2730 elif pycompat.sysplatform == 'plan9':
2730 elif pycompat.sysplatform == 'plan9':
2731 keep.append('plan9')
2731 keep.append('plan9')
2732 else:
2732 else:
2733 keep.append('unix')
2733 keep.append('unix')
2734 keep.append(pycompat.sysplatform.lower())
2734 keep.append(pycompat.sysplatform.lower())
2735 if ui.verbose:
2735 if ui.verbose:
2736 keep.append('verbose')
2736 keep.append('verbose')
2737
2737
2738 fullname = name
2738 fullname = name
2739 section = None
2739 section = None
2740 subtopic = None
2740 subtopic = None
2741 if name and '.' in name:
2741 if name and '.' in name:
2742 name, remaining = name.split('.', 1)
2742 name, remaining = name.split('.', 1)
2743 remaining = encoding.lower(remaining)
2743 remaining = encoding.lower(remaining)
2744 if '.' in remaining:
2744 if '.' in remaining:
2745 subtopic, section = remaining.split('.', 1)
2745 subtopic, section = remaining.split('.', 1)
2746 else:
2746 else:
2747 if name in help.subtopics:
2747 if name in help.subtopics:
2748 subtopic = remaining
2748 subtopic = remaining
2749 else:
2749 else:
2750 section = remaining
2750 section = remaining
2751
2751
2752 text = help.help_(ui, name, subtopic=subtopic, **opts)
2752 text = help.help_(ui, name, subtopic=subtopic, **opts)
2753
2753
2754 formatted, pruned = minirst.format(text, textwidth, keep=keep,
2754 formatted, pruned = minirst.format(text, textwidth, keep=keep,
2755 section=section)
2755 section=section)
2756
2756
2757 # We could have been given a weird ".foo" section without a name
2757 # We could have been given a weird ".foo" section without a name
2758 # to look for, or we could have simply failed to found "foo.bar"
2758 # to look for, or we could have simply failed to found "foo.bar"
2759 # because bar isn't a section of foo
2759 # because bar isn't a section of foo
2760 if section and not (formatted and name):
2760 if section and not (formatted and name):
2761 raise error.Abort(_("help section not found: %s") % fullname)
2761 raise error.Abort(_("help section not found: %s") % fullname)
2762
2762
2763 if 'verbose' in pruned:
2763 if 'verbose' in pruned:
2764 keep.append('omitted')
2764 keep.append('omitted')
2765 else:
2765 else:
2766 keep.append('notomitted')
2766 keep.append('notomitted')
2767 formatted, pruned = minirst.format(text, textwidth, keep=keep,
2767 formatted, pruned = minirst.format(text, textwidth, keep=keep,
2768 section=section)
2768 section=section)
2769 ui.write(formatted)
2769 ui.write(formatted)
2770
2770
2771
2771
2772 @command('identify|id',
2772 @command('identify|id',
2773 [('r', 'rev', '',
2773 [('r', 'rev', '',
2774 _('identify the specified revision'), _('REV')),
2774 _('identify the specified revision'), _('REV')),
2775 ('n', 'num', None, _('show local revision number')),
2775 ('n', 'num', None, _('show local revision number')),
2776 ('i', 'id', None, _('show global revision id')),
2776 ('i', 'id', None, _('show global revision id')),
2777 ('b', 'branch', None, _('show branch')),
2777 ('b', 'branch', None, _('show branch')),
2778 ('t', 'tags', None, _('show tags')),
2778 ('t', 'tags', None, _('show tags')),
2779 ('B', 'bookmarks', None, _('show bookmarks')),
2779 ('B', 'bookmarks', None, _('show bookmarks')),
2780 ] + remoteopts,
2780 ] + remoteopts,
2781 _('[-nibtB] [-r REV] [SOURCE]'),
2781 _('[-nibtB] [-r REV] [SOURCE]'),
2782 optionalrepo=True)
2782 optionalrepo=True)
2783 def identify(ui, repo, source=None, rev=None,
2783 def identify(ui, repo, source=None, rev=None,
2784 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2784 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2785 """identify the working directory or specified revision
2785 """identify the working directory or specified revision
2786
2786
2787 Print a summary identifying the repository state at REV using one or
2787 Print a summary identifying the repository state at REV using one or
2788 two parent hash identifiers, followed by a "+" if the working
2788 two parent hash identifiers, followed by a "+" if the working
2789 directory has uncommitted changes, the branch name (if not default),
2789 directory has uncommitted changes, the branch name (if not default),
2790 a list of tags, and a list of bookmarks.
2790 a list of tags, and a list of bookmarks.
2791
2791
2792 When REV is not given, print a summary of the current state of the
2792 When REV is not given, print a summary of the current state of the
2793 repository.
2793 repository.
2794
2794
2795 Specifying a path to a repository root or Mercurial bundle will
2795 Specifying a path to a repository root or Mercurial bundle will
2796 cause lookup to operate on that repository/bundle.
2796 cause lookup to operate on that repository/bundle.
2797
2797
2798 .. container:: verbose
2798 .. container:: verbose
2799
2799
2800 Examples:
2800 Examples:
2801
2801
2802 - generate a build identifier for the working directory::
2802 - generate a build identifier for the working directory::
2803
2803
2804 hg id --id > build-id.dat
2804 hg id --id > build-id.dat
2805
2805
2806 - find the revision corresponding to a tag::
2806 - find the revision corresponding to a tag::
2807
2807
2808 hg id -n -r 1.3
2808 hg id -n -r 1.3
2809
2809
2810 - check the most recent revision of a remote repository::
2810 - check the most recent revision of a remote repository::
2811
2811
2812 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2812 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2813
2813
2814 See :hg:`log` for generating more information about specific revisions,
2814 See :hg:`log` for generating more information about specific revisions,
2815 including full hash identifiers.
2815 including full hash identifiers.
2816
2816
2817 Returns 0 if successful.
2817 Returns 0 if successful.
2818 """
2818 """
2819
2819
2820 if not repo and not source:
2820 if not repo and not source:
2821 raise error.Abort(_("there is no Mercurial repository here "
2821 raise error.Abort(_("there is no Mercurial repository here "
2822 "(.hg not found)"))
2822 "(.hg not found)"))
2823
2823
2824 if ui.debugflag:
2824 if ui.debugflag:
2825 hexfunc = hex
2825 hexfunc = hex
2826 else:
2826 else:
2827 hexfunc = short
2827 hexfunc = short
2828 default = not (num or id or branch or tags or bookmarks)
2828 default = not (num or id or branch or tags or bookmarks)
2829 output = []
2829 output = []
2830 revs = []
2830 revs = []
2831
2831
2832 if source:
2832 if source:
2833 source, branches = hg.parseurl(ui.expandpath(source))
2833 source, branches = hg.parseurl(ui.expandpath(source))
2834 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2834 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2835 repo = peer.local()
2835 repo = peer.local()
2836 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2836 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2837
2837
2838 if not repo:
2838 if not repo:
2839 if num or branch or tags:
2839 if num or branch or tags:
2840 raise error.Abort(
2840 raise error.Abort(
2841 _("can't query remote revision number, branch, or tags"))
2841 _("can't query remote revision number, branch, or tags"))
2842 if not rev and revs:
2842 if not rev and revs:
2843 rev = revs[0]
2843 rev = revs[0]
2844 if not rev:
2844 if not rev:
2845 rev = "tip"
2845 rev = "tip"
2846
2846
2847 remoterev = peer.lookup(rev)
2847 remoterev = peer.lookup(rev)
2848 if default or id:
2848 if default or id:
2849 output = [hexfunc(remoterev)]
2849 output = [hexfunc(remoterev)]
2850
2850
2851 def getbms():
2851 def getbms():
2852 bms = []
2852 bms = []
2853
2853
2854 if 'bookmarks' in peer.listkeys('namespaces'):
2854 if 'bookmarks' in peer.listkeys('namespaces'):
2855 hexremoterev = hex(remoterev)
2855 hexremoterev = hex(remoterev)
2856 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2856 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2857 if bmr == hexremoterev]
2857 if bmr == hexremoterev]
2858
2858
2859 return sorted(bms)
2859 return sorted(bms)
2860
2860
2861 if bookmarks:
2861 if bookmarks:
2862 output.extend(getbms())
2862 output.extend(getbms())
2863 elif default and not ui.quiet:
2863 elif default and not ui.quiet:
2864 # multiple bookmarks for a single parent separated by '/'
2864 # multiple bookmarks for a single parent separated by '/'
2865 bm = '/'.join(getbms())
2865 bm = '/'.join(getbms())
2866 if bm:
2866 if bm:
2867 output.append(bm)
2867 output.append(bm)
2868 else:
2868 else:
2869 ctx = scmutil.revsingle(repo, rev, None)
2869 ctx = scmutil.revsingle(repo, rev, None)
2870
2870
2871 if ctx.rev() is None:
2871 if ctx.rev() is None:
2872 ctx = repo[None]
2872 ctx = repo[None]
2873 parents = ctx.parents()
2873 parents = ctx.parents()
2874 taglist = []
2874 taglist = []
2875 for p in parents:
2875 for p in parents:
2876 taglist.extend(p.tags())
2876 taglist.extend(p.tags())
2877
2877
2878 changed = ""
2878 changed = ""
2879 if default or id or num:
2879 if default or id or num:
2880 if (any(repo.status())
2880 if (any(repo.status())
2881 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2881 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2882 changed = '+'
2882 changed = '+'
2883 if default or id:
2883 if default or id:
2884 output = ["%s%s" %
2884 output = ["%s%s" %
2885 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2885 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2886 if num:
2886 if num:
2887 output.append("%s%s" %
2887 output.append("%s%s" %
2888 ('+'.join([str(p.rev()) for p in parents]), changed))
2888 ('+'.join([str(p.rev()) for p in parents]), changed))
2889 else:
2889 else:
2890 if default or id:
2890 if default or id:
2891 output = [hexfunc(ctx.node())]
2891 output = [hexfunc(ctx.node())]
2892 if num:
2892 if num:
2893 output.append(str(ctx.rev()))
2893 output.append(str(ctx.rev()))
2894 taglist = ctx.tags()
2894 taglist = ctx.tags()
2895
2895
2896 if default and not ui.quiet:
2896 if default and not ui.quiet:
2897 b = ctx.branch()
2897 b = ctx.branch()
2898 if b != 'default':
2898 if b != 'default':
2899 output.append("(%s)" % b)
2899 output.append("(%s)" % b)
2900
2900
2901 # multiple tags for a single parent separated by '/'
2901 # multiple tags for a single parent separated by '/'
2902 t = '/'.join(taglist)
2902 t = '/'.join(taglist)
2903 if t:
2903 if t:
2904 output.append(t)
2904 output.append(t)
2905
2905
2906 # multiple bookmarks for a single parent separated by '/'
2906 # multiple bookmarks for a single parent separated by '/'
2907 bm = '/'.join(ctx.bookmarks())
2907 bm = '/'.join(ctx.bookmarks())
2908 if bm:
2908 if bm:
2909 output.append(bm)
2909 output.append(bm)
2910 else:
2910 else:
2911 if branch:
2911 if branch:
2912 output.append(ctx.branch())
2912 output.append(ctx.branch())
2913
2913
2914 if tags:
2914 if tags:
2915 output.extend(taglist)
2915 output.extend(taglist)
2916
2916
2917 if bookmarks:
2917 if bookmarks:
2918 output.extend(ctx.bookmarks())
2918 output.extend(ctx.bookmarks())
2919
2919
2920 ui.write("%s\n" % ' '.join(output))
2920 ui.write("%s\n" % ' '.join(output))
2921
2921
2922 @command('import|patch',
2922 @command('import|patch',
2923 [('p', 'strip', 1,
2923 [('p', 'strip', 1,
2924 _('directory strip option for patch. This has the same '
2924 _('directory strip option for patch. This has the same '
2925 'meaning as the corresponding patch option'), _('NUM')),
2925 'meaning as the corresponding patch option'), _('NUM')),
2926 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2926 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2927 ('e', 'edit', False, _('invoke editor on commit messages')),
2927 ('e', 'edit', False, _('invoke editor on commit messages')),
2928 ('f', 'force', None,
2928 ('f', 'force', None,
2929 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2929 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2930 ('', 'no-commit', None,
2930 ('', 'no-commit', None,
2931 _("don't commit, just update the working directory")),
2931 _("don't commit, just update the working directory")),
2932 ('', 'bypass', None,
2932 ('', 'bypass', None,
2933 _("apply patch without touching the working directory")),
2933 _("apply patch without touching the working directory")),
2934 ('', 'partial', None,
2934 ('', 'partial', None,
2935 _('commit even if some hunks fail')),
2935 _('commit even if some hunks fail')),
2936 ('', 'exact', None,
2936 ('', 'exact', None,
2937 _('abort if patch would apply lossily')),
2937 _('abort if patch would apply lossily')),
2938 ('', 'prefix', '',
2938 ('', 'prefix', '',
2939 _('apply patch to subdirectory'), _('DIR')),
2939 _('apply patch to subdirectory'), _('DIR')),
2940 ('', 'import-branch', None,
2940 ('', 'import-branch', None,
2941 _('use any branch information in patch (implied by --exact)'))] +
2941 _('use any branch information in patch (implied by --exact)'))] +
2942 commitopts + commitopts2 + similarityopts,
2942 commitopts + commitopts2 + similarityopts,
2943 _('[OPTION]... PATCH...'))
2943 _('[OPTION]... PATCH...'))
2944 def import_(ui, repo, patch1=None, *patches, **opts):
2944 def import_(ui, repo, patch1=None, *patches, **opts):
2945 """import an ordered set of patches
2945 """import an ordered set of patches
2946
2946
2947 Import a list of patches and commit them individually (unless
2947 Import a list of patches and commit them individually (unless
2948 --no-commit is specified).
2948 --no-commit is specified).
2949
2949
2950 To read a patch from standard input (stdin), use "-" as the patch
2950 To read a patch from standard input (stdin), use "-" as the patch
2951 name. If a URL is specified, the patch will be downloaded from
2951 name. If a URL is specified, the patch will be downloaded from
2952 there.
2952 there.
2953
2953
2954 Import first applies changes to the working directory (unless
2954 Import first applies changes to the working directory (unless
2955 --bypass is specified), import will abort if there are outstanding
2955 --bypass is specified), import will abort if there are outstanding
2956 changes.
2956 changes.
2957
2957
2958 Use --bypass to apply and commit patches directly to the
2958 Use --bypass to apply and commit patches directly to the
2959 repository, without affecting the working directory. Without
2959 repository, without affecting the working directory. Without
2960 --exact, patches will be applied on top of the working directory
2960 --exact, patches will be applied on top of the working directory
2961 parent revision.
2961 parent revision.
2962
2962
2963 You can import a patch straight from a mail message. Even patches
2963 You can import a patch straight from a mail message. Even patches
2964 as attachments work (to use the body part, it must have type
2964 as attachments work (to use the body part, it must have type
2965 text/plain or text/x-patch). From and Subject headers of email
2965 text/plain or text/x-patch). From and Subject headers of email
2966 message are used as default committer and commit message. All
2966 message are used as default committer and commit message. All
2967 text/plain body parts before first diff are added to the commit
2967 text/plain body parts before first diff are added to the commit
2968 message.
2968 message.
2969
2969
2970 If the imported patch was generated by :hg:`export`, user and
2970 If the imported patch was generated by :hg:`export`, user and
2971 description from patch override values from message headers and
2971 description from patch override values from message headers and
2972 body. Values given on command line with -m/--message and -u/--user
2972 body. Values given on command line with -m/--message and -u/--user
2973 override these.
2973 override these.
2974
2974
2975 If --exact is specified, import will set the working directory to
2975 If --exact is specified, import will set the working directory to
2976 the parent of each patch before applying it, and will abort if the
2976 the parent of each patch before applying it, and will abort if the
2977 resulting changeset has a different ID than the one recorded in
2977 resulting changeset has a different ID than the one recorded in
2978 the patch. This will guard against various ways that portable
2978 the patch. This will guard against various ways that portable
2979 patch formats and mail systems might fail to transfer Mercurial
2979 patch formats and mail systems might fail to transfer Mercurial
2980 data or metadata. See :hg:`bundle` for lossless transmission.
2980 data or metadata. See :hg:`bundle` for lossless transmission.
2981
2981
2982 Use --partial to ensure a changeset will be created from the patch
2982 Use --partial to ensure a changeset will be created from the patch
2983 even if some hunks fail to apply. Hunks that fail to apply will be
2983 even if some hunks fail to apply. Hunks that fail to apply will be
2984 written to a <target-file>.rej file. Conflicts can then be resolved
2984 written to a <target-file>.rej file. Conflicts can then be resolved
2985 by hand before :hg:`commit --amend` is run to update the created
2985 by hand before :hg:`commit --amend` is run to update the created
2986 changeset. This flag exists to let people import patches that
2986 changeset. This flag exists to let people import patches that
2987 partially apply without losing the associated metadata (author,
2987 partially apply without losing the associated metadata (author,
2988 date, description, ...).
2988 date, description, ...).
2989
2989
2990 .. note::
2990 .. note::
2991
2991
2992 When no hunks apply cleanly, :hg:`import --partial` will create
2992 When no hunks apply cleanly, :hg:`import --partial` will create
2993 an empty changeset, importing only the patch metadata.
2993 an empty changeset, importing only the patch metadata.
2994
2994
2995 With -s/--similarity, hg will attempt to discover renames and
2995 With -s/--similarity, hg will attempt to discover renames and
2996 copies in the patch in the same way as :hg:`addremove`.
2996 copies in the patch in the same way as :hg:`addremove`.
2997
2997
2998 It is possible to use external patch programs to perform the patch
2998 It is possible to use external patch programs to perform the patch
2999 by setting the ``ui.patch`` configuration option. For the default
2999 by setting the ``ui.patch`` configuration option. For the default
3000 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3000 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3001 See :hg:`help config` for more information about configuration
3001 See :hg:`help config` for more information about configuration
3002 files and how to use these options.
3002 files and how to use these options.
3003
3003
3004 See :hg:`help dates` for a list of formats valid for -d/--date.
3004 See :hg:`help dates` for a list of formats valid for -d/--date.
3005
3005
3006 .. container:: verbose
3006 .. container:: verbose
3007
3007
3008 Examples:
3008 Examples:
3009
3009
3010 - import a traditional patch from a website and detect renames::
3010 - import a traditional patch from a website and detect renames::
3011
3011
3012 hg import -s 80 http://example.com/bugfix.patch
3012 hg import -s 80 http://example.com/bugfix.patch
3013
3013
3014 - import a changeset from an hgweb server::
3014 - import a changeset from an hgweb server::
3015
3015
3016 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3016 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3017
3017
3018 - import all the patches in an Unix-style mbox::
3018 - import all the patches in an Unix-style mbox::
3019
3019
3020 hg import incoming-patches.mbox
3020 hg import incoming-patches.mbox
3021
3021
3022 - import patches from stdin::
3022 - import patches from stdin::
3023
3023
3024 hg import -
3024 hg import -
3025
3025
3026 - attempt to exactly restore an exported changeset (not always
3026 - attempt to exactly restore an exported changeset (not always
3027 possible)::
3027 possible)::
3028
3028
3029 hg import --exact proposed-fix.patch
3029 hg import --exact proposed-fix.patch
3030
3030
3031 - use an external tool to apply a patch which is too fuzzy for
3031 - use an external tool to apply a patch which is too fuzzy for
3032 the default internal tool.
3032 the default internal tool.
3033
3033
3034 hg import --config ui.patch="patch --merge" fuzzy.patch
3034 hg import --config ui.patch="patch --merge" fuzzy.patch
3035
3035
3036 - change the default fuzzing from 2 to a less strict 7
3036 - change the default fuzzing from 2 to a less strict 7
3037
3037
3038 hg import --config ui.fuzz=7 fuzz.patch
3038 hg import --config ui.fuzz=7 fuzz.patch
3039
3039
3040 Returns 0 on success, 1 on partial success (see --partial).
3040 Returns 0 on success, 1 on partial success (see --partial).
3041 """
3041 """
3042
3042
3043 if not patch1:
3043 if not patch1:
3044 raise error.Abort(_('need at least one patch to import'))
3044 raise error.Abort(_('need at least one patch to import'))
3045
3045
3046 patches = (patch1,) + patches
3046 patches = (patch1,) + patches
3047
3047
3048 date = opts.get('date')
3048 date = opts.get('date')
3049 if date:
3049 if date:
3050 opts['date'] = util.parsedate(date)
3050 opts['date'] = util.parsedate(date)
3051
3051
3052 exact = opts.get('exact')
3052 exact = opts.get('exact')
3053 update = not opts.get('bypass')
3053 update = not opts.get('bypass')
3054 if not update and opts.get('no_commit'):
3054 if not update and opts.get('no_commit'):
3055 raise error.Abort(_('cannot use --no-commit with --bypass'))
3055 raise error.Abort(_('cannot use --no-commit with --bypass'))
3056 try:
3056 try:
3057 sim = float(opts.get('similarity') or 0)
3057 sim = float(opts.get('similarity') or 0)
3058 except ValueError:
3058 except ValueError:
3059 raise error.Abort(_('similarity must be a number'))
3059 raise error.Abort(_('similarity must be a number'))
3060 if sim < 0 or sim > 100:
3060 if sim < 0 or sim > 100:
3061 raise error.Abort(_('similarity must be between 0 and 100'))
3061 raise error.Abort(_('similarity must be between 0 and 100'))
3062 if sim and not update:
3062 if sim and not update:
3063 raise error.Abort(_('cannot use --similarity with --bypass'))
3063 raise error.Abort(_('cannot use --similarity with --bypass'))
3064 if exact:
3064 if exact:
3065 if opts.get('edit'):
3065 if opts.get('edit'):
3066 raise error.Abort(_('cannot use --exact with --edit'))
3066 raise error.Abort(_('cannot use --exact with --edit'))
3067 if opts.get('prefix'):
3067 if opts.get('prefix'):
3068 raise error.Abort(_('cannot use --exact with --prefix'))
3068 raise error.Abort(_('cannot use --exact with --prefix'))
3069
3069
3070 base = opts["base"]
3070 base = opts["base"]
3071 wlock = dsguard = lock = tr = None
3071 wlock = dsguard = lock = tr = None
3072 msgs = []
3072 msgs = []
3073 ret = 0
3073 ret = 0
3074
3074
3075
3075
3076 try:
3076 try:
3077 wlock = repo.wlock()
3077 wlock = repo.wlock()
3078
3078
3079 if update:
3079 if update:
3080 cmdutil.checkunfinished(repo)
3080 cmdutil.checkunfinished(repo)
3081 if (exact or not opts.get('force')):
3081 if (exact or not opts.get('force')):
3082 cmdutil.bailifchanged(repo)
3082 cmdutil.bailifchanged(repo)
3083
3083
3084 if not opts.get('no_commit'):
3084 if not opts.get('no_commit'):
3085 lock = repo.lock()
3085 lock = repo.lock()
3086 tr = repo.transaction('import')
3086 tr = repo.transaction('import')
3087 else:
3087 else:
3088 dsguard = dirstateguard.dirstateguard(repo, 'import')
3088 dsguard = dirstateguard.dirstateguard(repo, 'import')
3089 parents = repo[None].parents()
3089 parents = repo[None].parents()
3090 for patchurl in patches:
3090 for patchurl in patches:
3091 if patchurl == '-':
3091 if patchurl == '-':
3092 ui.status(_('applying patch from stdin\n'))
3092 ui.status(_('applying patch from stdin\n'))
3093 patchfile = ui.fin
3093 patchfile = ui.fin
3094 patchurl = 'stdin' # for error message
3094 patchurl = 'stdin' # for error message
3095 else:
3095 else:
3096 patchurl = os.path.join(base, patchurl)
3096 patchurl = os.path.join(base, patchurl)
3097 ui.status(_('applying %s\n') % patchurl)
3097 ui.status(_('applying %s\n') % patchurl)
3098 patchfile = hg.openpath(ui, patchurl)
3098 patchfile = hg.openpath(ui, patchurl)
3099
3099
3100 haspatch = False
3100 haspatch = False
3101 for hunk in patch.split(patchfile):
3101 for hunk in patch.split(patchfile):
3102 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3102 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3103 parents, opts,
3103 parents, opts,
3104 msgs, hg.clean)
3104 msgs, hg.clean)
3105 if msg:
3105 if msg:
3106 haspatch = True
3106 haspatch = True
3107 ui.note(msg + '\n')
3107 ui.note(msg + '\n')
3108 if update or exact:
3108 if update or exact:
3109 parents = repo[None].parents()
3109 parents = repo[None].parents()
3110 else:
3110 else:
3111 parents = [repo[node]]
3111 parents = [repo[node]]
3112 if rej:
3112 if rej:
3113 ui.write_err(_("patch applied partially\n"))
3113 ui.write_err(_("patch applied partially\n"))
3114 ui.write_err(_("(fix the .rej files and run "
3114 ui.write_err(_("(fix the .rej files and run "
3115 "`hg commit --amend`)\n"))
3115 "`hg commit --amend`)\n"))
3116 ret = 1
3116 ret = 1
3117 break
3117 break
3118
3118
3119 if not haspatch:
3119 if not haspatch:
3120 raise error.Abort(_('%s: no diffs found') % patchurl)
3120 raise error.Abort(_('%s: no diffs found') % patchurl)
3121
3121
3122 if tr:
3122 if tr:
3123 tr.close()
3123 tr.close()
3124 if msgs:
3124 if msgs:
3125 repo.savecommitmessage('\n* * *\n'.join(msgs))
3125 repo.savecommitmessage('\n* * *\n'.join(msgs))
3126 if dsguard:
3126 if dsguard:
3127 dsguard.close()
3127 dsguard.close()
3128 return ret
3128 return ret
3129 finally:
3129 finally:
3130 if tr:
3130 if tr:
3131 tr.release()
3131 tr.release()
3132 release(lock, dsguard, wlock)
3132 release(lock, dsguard, wlock)
3133
3133
3134 @command('incoming|in',
3134 @command('incoming|in',
3135 [('f', 'force', None,
3135 [('f', 'force', None,
3136 _('run even if remote repository is unrelated')),
3136 _('run even if remote repository is unrelated')),
3137 ('n', 'newest-first', None, _('show newest record first')),
3137 ('n', 'newest-first', None, _('show newest record first')),
3138 ('', 'bundle', '',
3138 ('', 'bundle', '',
3139 _('file to store the bundles into'), _('FILE')),
3139 _('file to store the bundles into'), _('FILE')),
3140 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3140 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3141 ('B', 'bookmarks', False, _("compare bookmarks")),
3141 ('B', 'bookmarks', False, _("compare bookmarks")),
3142 ('b', 'branch', [],
3142 ('b', 'branch', [],
3143 _('a specific branch you would like to pull'), _('BRANCH')),
3143 _('a specific branch you would like to pull'), _('BRANCH')),
3144 ] + logopts + remoteopts + subrepoopts,
3144 ] + logopts + remoteopts + subrepoopts,
3145 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3145 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3146 def incoming(ui, repo, source="default", **opts):
3146 def incoming(ui, repo, source="default", **opts):
3147 """show new changesets found in source
3147 """show new changesets found in source
3148
3148
3149 Show new changesets found in the specified path/URL or the default
3149 Show new changesets found in the specified path/URL or the default
3150 pull location. These are the changesets that would have been pulled
3150 pull location. These are the changesets that would have been pulled
3151 if a pull at the time you issued this command.
3151 if a pull at the time you issued this command.
3152
3152
3153 See pull for valid source format details.
3153 See pull for valid source format details.
3154
3154
3155 .. container:: verbose
3155 .. container:: verbose
3156
3156
3157 With -B/--bookmarks, the result of bookmark comparison between
3157 With -B/--bookmarks, the result of bookmark comparison between
3158 local and remote repositories is displayed. With -v/--verbose,
3158 local and remote repositories is displayed. With -v/--verbose,
3159 status is also displayed for each bookmark like below::
3159 status is also displayed for each bookmark like below::
3160
3160
3161 BM1 01234567890a added
3161 BM1 01234567890a added
3162 BM2 1234567890ab advanced
3162 BM2 1234567890ab advanced
3163 BM3 234567890abc diverged
3163 BM3 234567890abc diverged
3164 BM4 34567890abcd changed
3164 BM4 34567890abcd changed
3165
3165
3166 The action taken locally when pulling depends on the
3166 The action taken locally when pulling depends on the
3167 status of each bookmark:
3167 status of each bookmark:
3168
3168
3169 :``added``: pull will create it
3169 :``added``: pull will create it
3170 :``advanced``: pull will update it
3170 :``advanced``: pull will update it
3171 :``diverged``: pull will create a divergent bookmark
3171 :``diverged``: pull will create a divergent bookmark
3172 :``changed``: result depends on remote changesets
3172 :``changed``: result depends on remote changesets
3173
3173
3174 From the point of view of pulling behavior, bookmark
3174 From the point of view of pulling behavior, bookmark
3175 existing only in the remote repository are treated as ``added``,
3175 existing only in the remote repository are treated as ``added``,
3176 even if it is in fact locally deleted.
3176 even if it is in fact locally deleted.
3177
3177
3178 .. container:: verbose
3178 .. container:: verbose
3179
3179
3180 For remote repository, using --bundle avoids downloading the
3180 For remote repository, using --bundle avoids downloading the
3181 changesets twice if the incoming is followed by a pull.
3181 changesets twice if the incoming is followed by a pull.
3182
3182
3183 Examples:
3183 Examples:
3184
3184
3185 - show incoming changes with patches and full description::
3185 - show incoming changes with patches and full description::
3186
3186
3187 hg incoming -vp
3187 hg incoming -vp
3188
3188
3189 - show incoming changes excluding merges, store a bundle::
3189 - show incoming changes excluding merges, store a bundle::
3190
3190
3191 hg in -vpM --bundle incoming.hg
3191 hg in -vpM --bundle incoming.hg
3192 hg pull incoming.hg
3192 hg pull incoming.hg
3193
3193
3194 - briefly list changes inside a bundle::
3194 - briefly list changes inside a bundle::
3195
3195
3196 hg in changes.hg -T "{desc|firstline}\\n"
3196 hg in changes.hg -T "{desc|firstline}\\n"
3197
3197
3198 Returns 0 if there are incoming changes, 1 otherwise.
3198 Returns 0 if there are incoming changes, 1 otherwise.
3199 """
3199 """
3200 if opts.get('graph'):
3200 if opts.get('graph'):
3201 cmdutil.checkunsupportedgraphflags([], opts)
3201 cmdutil.checkunsupportedgraphflags([], opts)
3202 def display(other, chlist, displayer):
3202 def display(other, chlist, displayer):
3203 revdag = cmdutil.graphrevs(other, chlist, opts)
3203 revdag = cmdutil.graphrevs(other, chlist, opts)
3204 cmdutil.displaygraph(ui, repo, revdag, displayer,
3204 cmdutil.displaygraph(ui, repo, revdag, displayer,
3205 graphmod.asciiedges)
3205 graphmod.asciiedges)
3206
3206
3207 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3207 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3208 return 0
3208 return 0
3209
3209
3210 if opts.get('bundle') and opts.get('subrepos'):
3210 if opts.get('bundle') and opts.get('subrepos'):
3211 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3211 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3212
3212
3213 if opts.get('bookmarks'):
3213 if opts.get('bookmarks'):
3214 source, branches = hg.parseurl(ui.expandpath(source),
3214 source, branches = hg.parseurl(ui.expandpath(source),
3215 opts.get('branch'))
3215 opts.get('branch'))
3216 other = hg.peer(repo, opts, source)
3216 other = hg.peer(repo, opts, source)
3217 if 'bookmarks' not in other.listkeys('namespaces'):
3217 if 'bookmarks' not in other.listkeys('namespaces'):
3218 ui.warn(_("remote doesn't support bookmarks\n"))
3218 ui.warn(_("remote doesn't support bookmarks\n"))
3219 return 0
3219 return 0
3220 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3220 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3221 return bookmarks.incoming(ui, repo, other)
3221 return bookmarks.incoming(ui, repo, other)
3222
3222
3223 repo._subtoppath = ui.expandpath(source)
3223 repo._subtoppath = ui.expandpath(source)
3224 try:
3224 try:
3225 return hg.incoming(ui, repo, source, opts)
3225 return hg.incoming(ui, repo, source, opts)
3226 finally:
3226 finally:
3227 del repo._subtoppath
3227 del repo._subtoppath
3228
3228
3229
3229
3230 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3230 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3231 norepo=True)
3231 norepo=True)
3232 def init(ui, dest=".", **opts):
3232 def init(ui, dest=".", **opts):
3233 """create a new repository in the given directory
3233 """create a new repository in the given directory
3234
3234
3235 Initialize a new repository in the given directory. If the given
3235 Initialize a new repository in the given directory. If the given
3236 directory does not exist, it will be created.
3236 directory does not exist, it will be created.
3237
3237
3238 If no directory is given, the current directory is used.
3238 If no directory is given, the current directory is used.
3239
3239
3240 It is possible to specify an ``ssh://`` URL as the destination.
3240 It is possible to specify an ``ssh://`` URL as the destination.
3241 See :hg:`help urls` for more information.
3241 See :hg:`help urls` for more information.
3242
3242
3243 Returns 0 on success.
3243 Returns 0 on success.
3244 """
3244 """
3245 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3245 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3246
3246
3247 @command('locate',
3247 @command('locate',
3248 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3248 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3249 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3249 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3250 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3250 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3251 ] + walkopts,
3251 ] + walkopts,
3252 _('[OPTION]... [PATTERN]...'))
3252 _('[OPTION]... [PATTERN]...'))
3253 def locate(ui, repo, *pats, **opts):
3253 def locate(ui, repo, *pats, **opts):
3254 """locate files matching specific patterns (DEPRECATED)
3254 """locate files matching specific patterns (DEPRECATED)
3255
3255
3256 Print files under Mercurial control in the working directory whose
3256 Print files under Mercurial control in the working directory whose
3257 names match the given patterns.
3257 names match the given patterns.
3258
3258
3259 By default, this command searches all directories in the working
3259 By default, this command searches all directories in the working
3260 directory. To search just the current directory and its
3260 directory. To search just the current directory and its
3261 subdirectories, use "--include .".
3261 subdirectories, use "--include .".
3262
3262
3263 If no patterns are given to match, this command prints the names
3263 If no patterns are given to match, this command prints the names
3264 of all files under Mercurial control in the working directory.
3264 of all files under Mercurial control in the working directory.
3265
3265
3266 If you want to feed the output of this command into the "xargs"
3266 If you want to feed the output of this command into the "xargs"
3267 command, use the -0 option to both this command and "xargs". This
3267 command, use the -0 option to both this command and "xargs". This
3268 will avoid the problem of "xargs" treating single filenames that
3268 will avoid the problem of "xargs" treating single filenames that
3269 contain whitespace as multiple filenames.
3269 contain whitespace as multiple filenames.
3270
3270
3271 See :hg:`help files` for a more versatile command.
3271 See :hg:`help files` for a more versatile command.
3272
3272
3273 Returns 0 if a match is found, 1 otherwise.
3273 Returns 0 if a match is found, 1 otherwise.
3274 """
3274 """
3275 if opts.get('print0'):
3275 if opts.get('print0'):
3276 end = '\0'
3276 end = '\0'
3277 else:
3277 else:
3278 end = '\n'
3278 end = '\n'
3279 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3279 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3280
3280
3281 ret = 1
3281 ret = 1
3282 ctx = repo[rev]
3282 ctx = repo[rev]
3283 m = scmutil.match(ctx, pats, opts, default='relglob',
3283 m = scmutil.match(ctx, pats, opts, default='relglob',
3284 badfn=lambda x, y: False)
3284 badfn=lambda x, y: False)
3285
3285
3286 for abs in ctx.matches(m):
3286 for abs in ctx.matches(m):
3287 if opts.get('fullpath'):
3287 if opts.get('fullpath'):
3288 ui.write(repo.wjoin(abs), end)
3288 ui.write(repo.wjoin(abs), end)
3289 else:
3289 else:
3290 ui.write(((pats and m.rel(abs)) or abs), end)
3290 ui.write(((pats and m.rel(abs)) or abs), end)
3291 ret = 0
3291 ret = 0
3292
3292
3293 return ret
3293 return ret
3294
3294
3295 @command('^log|history',
3295 @command('^log|history',
3296 [('f', 'follow', None,
3296 [('f', 'follow', None,
3297 _('follow changeset history, or file history across copies and renames')),
3297 _('follow changeset history, or file history across copies and renames')),
3298 ('', 'follow-first', None,
3298 ('', 'follow-first', None,
3299 _('only follow the first parent of merge changesets (DEPRECATED)')),
3299 _('only follow the first parent of merge changesets (DEPRECATED)')),
3300 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3300 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3301 ('C', 'copies', None, _('show copied files')),
3301 ('C', 'copies', None, _('show copied files')),
3302 ('k', 'keyword', [],
3302 ('k', 'keyword', [],
3303 _('do case-insensitive search for a given text'), _('TEXT')),
3303 _('do case-insensitive search for a given text'), _('TEXT')),
3304 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3304 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3305 ('', 'removed', None, _('include revisions where files were removed')),
3305 ('', 'removed', None, _('include revisions where files were removed')),
3306 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3306 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3307 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3307 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3308 ('', 'only-branch', [],
3308 ('', 'only-branch', [],
3309 _('show only changesets within the given named branch (DEPRECATED)'),
3309 _('show only changesets within the given named branch (DEPRECATED)'),
3310 _('BRANCH')),
3310 _('BRANCH')),
3311 ('b', 'branch', [],
3311 ('b', 'branch', [],
3312 _('show changesets within the given named branch'), _('BRANCH')),
3312 _('show changesets within the given named branch'), _('BRANCH')),
3313 ('P', 'prune', [],
3313 ('P', 'prune', [],
3314 _('do not display revision or any of its ancestors'), _('REV')),
3314 _('do not display revision or any of its ancestors'), _('REV')),
3315 ] + logopts + walkopts,
3315 ] + logopts + walkopts,
3316 _('[OPTION]... [FILE]'),
3316 _('[OPTION]... [FILE]'),
3317 inferrepo=True)
3317 inferrepo=True)
3318 def log(ui, repo, *pats, **opts):
3318 def log(ui, repo, *pats, **opts):
3319 """show revision history of entire repository or files
3319 """show revision history of entire repository or files
3320
3320
3321 Print the revision history of the specified files or the entire
3321 Print the revision history of the specified files or the entire
3322 project.
3322 project.
3323
3323
3324 If no revision range is specified, the default is ``tip:0`` unless
3324 If no revision range is specified, the default is ``tip:0`` unless
3325 --follow is set, in which case the working directory parent is
3325 --follow is set, in which case the working directory parent is
3326 used as the starting revision.
3326 used as the starting revision.
3327
3327
3328 File history is shown without following rename or copy history of
3328 File history is shown without following rename or copy history of
3329 files. Use -f/--follow with a filename to follow history across
3329 files. Use -f/--follow with a filename to follow history across
3330 renames and copies. --follow without a filename will only show
3330 renames and copies. --follow without a filename will only show
3331 ancestors or descendants of the starting revision.
3331 ancestors or descendants of the starting revision.
3332
3332
3333 By default this command prints revision number and changeset id,
3333 By default this command prints revision number and changeset id,
3334 tags, non-trivial parents, user, date and time, and a summary for
3334 tags, non-trivial parents, user, date and time, and a summary for
3335 each commit. When the -v/--verbose switch is used, the list of
3335 each commit. When the -v/--verbose switch is used, the list of
3336 changed files and full commit message are shown.
3336 changed files and full commit message are shown.
3337
3337
3338 With --graph the revisions are shown as an ASCII art DAG with the most
3338 With --graph the revisions are shown as an ASCII art DAG with the most
3339 recent changeset at the top.
3339 recent changeset at the top.
3340 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3340 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3341 and '+' represents a fork where the changeset from the lines below is a
3341 and '+' represents a fork where the changeset from the lines below is a
3342 parent of the 'o' merge on the same line.
3342 parent of the 'o' merge on the same line.
3343
3343
3344 .. note::
3344 .. note::
3345
3345
3346 :hg:`log --patch` may generate unexpected diff output for merge
3346 :hg:`log --patch` may generate unexpected diff output for merge
3347 changesets, as it will only compare the merge changeset against
3347 changesets, as it will only compare the merge changeset against
3348 its first parent. Also, only files different from BOTH parents
3348 its first parent. Also, only files different from BOTH parents
3349 will appear in files:.
3349 will appear in files:.
3350
3350
3351 .. note::
3351 .. note::
3352
3352
3353 For performance reasons, :hg:`log FILE` may omit duplicate changes
3353 For performance reasons, :hg:`log FILE` may omit duplicate changes
3354 made on branches and will not show removals or mode changes. To
3354 made on branches and will not show removals or mode changes. To
3355 see all such changes, use the --removed switch.
3355 see all such changes, use the --removed switch.
3356
3356
3357 .. container:: verbose
3357 .. container:: verbose
3358
3358
3359 Some examples:
3359 Some examples:
3360
3360
3361 - changesets with full descriptions and file lists::
3361 - changesets with full descriptions and file lists::
3362
3362
3363 hg log -v
3363 hg log -v
3364
3364
3365 - changesets ancestral to the working directory::
3365 - changesets ancestral to the working directory::
3366
3366
3367 hg log -f
3367 hg log -f
3368
3368
3369 - last 10 commits on the current branch::
3369 - last 10 commits on the current branch::
3370
3370
3371 hg log -l 10 -b .
3371 hg log -l 10 -b .
3372
3372
3373 - changesets showing all modifications of a file, including removals::
3373 - changesets showing all modifications of a file, including removals::
3374
3374
3375 hg log --removed file.c
3375 hg log --removed file.c
3376
3376
3377 - all changesets that touch a directory, with diffs, excluding merges::
3377 - all changesets that touch a directory, with diffs, excluding merges::
3378
3378
3379 hg log -Mp lib/
3379 hg log -Mp lib/
3380
3380
3381 - all revision numbers that match a keyword::
3381 - all revision numbers that match a keyword::
3382
3382
3383 hg log -k bug --template "{rev}\\n"
3383 hg log -k bug --template "{rev}\\n"
3384
3384
3385 - the full hash identifier of the working directory parent::
3385 - the full hash identifier of the working directory parent::
3386
3386
3387 hg log -r . --template "{node}\\n"
3387 hg log -r . --template "{node}\\n"
3388
3388
3389 - list available log templates::
3389 - list available log templates::
3390
3390
3391 hg log -T list
3391 hg log -T list
3392
3392
3393 - check if a given changeset is included in a tagged release::
3393 - check if a given changeset is included in a tagged release::
3394
3394
3395 hg log -r "a21ccf and ancestor(1.9)"
3395 hg log -r "a21ccf and ancestor(1.9)"
3396
3396
3397 - find all changesets by some user in a date range::
3397 - find all changesets by some user in a date range::
3398
3398
3399 hg log -k alice -d "may 2008 to jul 2008"
3399 hg log -k alice -d "may 2008 to jul 2008"
3400
3400
3401 - summary of all changesets after the last tag::
3401 - summary of all changesets after the last tag::
3402
3402
3403 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3403 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3404
3404
3405 See :hg:`help dates` for a list of formats valid for -d/--date.
3405 See :hg:`help dates` for a list of formats valid for -d/--date.
3406
3406
3407 See :hg:`help revisions` for more about specifying and ordering
3407 See :hg:`help revisions` for more about specifying and ordering
3408 revisions.
3408 revisions.
3409
3409
3410 See :hg:`help templates` for more about pre-packaged styles and
3410 See :hg:`help templates` for more about pre-packaged styles and
3411 specifying custom templates.
3411 specifying custom templates.
3412
3412
3413 Returns 0 on success.
3413 Returns 0 on success.
3414
3414
3415 """
3415 """
3416 if opts.get('follow') and opts.get('rev'):
3416 if opts.get('follow') and opts.get('rev'):
3417 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
3417 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3418 del opts['follow']
3418 del opts['follow']
3419
3419
3420 if opts.get('graph'):
3420 if opts.get('graph'):
3421 return cmdutil.graphlog(ui, repo, *pats, **opts)
3421 return cmdutil.graphlog(ui, repo, *pats, **opts)
3422
3422
3423 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3423 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3424 limit = cmdutil.loglimit(opts)
3424 limit = cmdutil.loglimit(opts)
3425 count = 0
3425 count = 0
3426
3426
3427 getrenamed = None
3427 getrenamed = None
3428 if opts.get('copies'):
3428 if opts.get('copies'):
3429 endrev = None
3429 endrev = None
3430 if opts.get('rev'):
3430 if opts.get('rev'):
3431 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3431 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3432 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3432 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3433
3433
3434 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3434 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3435 for rev in revs:
3435 for rev in revs:
3436 if count == limit:
3436 if count == limit:
3437 break
3437 break
3438 ctx = repo[rev]
3438 ctx = repo[rev]
3439 copies = None
3439 copies = None
3440 if getrenamed is not None and rev:
3440 if getrenamed is not None and rev:
3441 copies = []
3441 copies = []
3442 for fn in ctx.files():
3442 for fn in ctx.files():
3443 rename = getrenamed(fn, rev)
3443 rename = getrenamed(fn, rev)
3444 if rename:
3444 if rename:
3445 copies.append((fn, rename[0]))
3445 copies.append((fn, rename[0]))
3446 if filematcher:
3446 if filematcher:
3447 revmatchfn = filematcher(ctx.rev())
3447 revmatchfn = filematcher(ctx.rev())
3448 else:
3448 else:
3449 revmatchfn = None
3449 revmatchfn = None
3450 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3450 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3451 if displayer.flush(ctx):
3451 if displayer.flush(ctx):
3452 count += 1
3452 count += 1
3453
3453
3454 displayer.close()
3454 displayer.close()
3455
3455
3456 @command('manifest',
3456 @command('manifest',
3457 [('r', 'rev', '', _('revision to display'), _('REV')),
3457 [('r', 'rev', '', _('revision to display'), _('REV')),
3458 ('', 'all', False, _("list files from all revisions"))]
3458 ('', 'all', False, _("list files from all revisions"))]
3459 + formatteropts,
3459 + formatteropts,
3460 _('[-r REV]'))
3460 _('[-r REV]'))
3461 def manifest(ui, repo, node=None, rev=None, **opts):
3461 def manifest(ui, repo, node=None, rev=None, **opts):
3462 """output the current or given revision of the project manifest
3462 """output the current or given revision of the project manifest
3463
3463
3464 Print a list of version controlled files for the given revision.
3464 Print a list of version controlled files for the given revision.
3465 If no revision is given, the first parent of the working directory
3465 If no revision is given, the first parent of the working directory
3466 is used, or the null revision if no revision is checked out.
3466 is used, or the null revision if no revision is checked out.
3467
3467
3468 With -v, print file permissions, symlink and executable bits.
3468 With -v, print file permissions, symlink and executable bits.
3469 With --debug, print file revision hashes.
3469 With --debug, print file revision hashes.
3470
3470
3471 If option --all is specified, the list of all files from all revisions
3471 If option --all is specified, the list of all files from all revisions
3472 is printed. This includes deleted and renamed files.
3472 is printed. This includes deleted and renamed files.
3473
3473
3474 Returns 0 on success.
3474 Returns 0 on success.
3475 """
3475 """
3476
3476
3477 fm = ui.formatter('manifest', opts)
3477 fm = ui.formatter('manifest', opts)
3478
3478
3479 if opts.get('all'):
3479 if opts.get('all'):
3480 if rev or node:
3480 if rev or node:
3481 raise error.Abort(_("can't specify a revision with --all"))
3481 raise error.Abort(_("can't specify a revision with --all"))
3482
3482
3483 res = []
3483 res = []
3484 prefix = "data/"
3484 prefix = "data/"
3485 suffix = ".i"
3485 suffix = ".i"
3486 plen = len(prefix)
3486 plen = len(prefix)
3487 slen = len(suffix)
3487 slen = len(suffix)
3488 with repo.lock():
3488 with repo.lock():
3489 for fn, b, size in repo.store.datafiles():
3489 for fn, b, size in repo.store.datafiles():
3490 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3490 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3491 res.append(fn[plen:-slen])
3491 res.append(fn[plen:-slen])
3492 for f in res:
3492 for f in res:
3493 fm.startitem()
3493 fm.startitem()
3494 fm.write("path", '%s\n', f)
3494 fm.write("path", '%s\n', f)
3495 fm.end()
3495 fm.end()
3496 return
3496 return
3497
3497
3498 if rev and node:
3498 if rev and node:
3499 raise error.Abort(_("please specify just one revision"))
3499 raise error.Abort(_("please specify just one revision"))
3500
3500
3501 if not node:
3501 if not node:
3502 node = rev
3502 node = rev
3503
3503
3504 char = {'l': '@', 'x': '*', '': ''}
3504 char = {'l': '@', 'x': '*', '': ''}
3505 mode = {'l': '644', 'x': '755', '': '644'}
3505 mode = {'l': '644', 'x': '755', '': '644'}
3506 ctx = scmutil.revsingle(repo, node)
3506 ctx = scmutil.revsingle(repo, node)
3507 mf = ctx.manifest()
3507 mf = ctx.manifest()
3508 for f in ctx:
3508 for f in ctx:
3509 fm.startitem()
3509 fm.startitem()
3510 fl = ctx[f].flags()
3510 fl = ctx[f].flags()
3511 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3511 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3512 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3512 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3513 fm.write('path', '%s\n', f)
3513 fm.write('path', '%s\n', f)
3514 fm.end()
3514 fm.end()
3515
3515
3516 @command('^merge',
3516 @command('^merge',
3517 [('f', 'force', None,
3517 [('f', 'force', None,
3518 _('force a merge including outstanding changes (DEPRECATED)')),
3518 _('force a merge including outstanding changes (DEPRECATED)')),
3519 ('r', 'rev', '', _('revision to merge'), _('REV')),
3519 ('r', 'rev', '', _('revision to merge'), _('REV')),
3520 ('P', 'preview', None,
3520 ('P', 'preview', None,
3521 _('review revisions to merge (no merge is performed)'))
3521 _('review revisions to merge (no merge is performed)'))
3522 ] + mergetoolopts,
3522 ] + mergetoolopts,
3523 _('[-P] [[-r] REV]'))
3523 _('[-P] [[-r] REV]'))
3524 def merge(ui, repo, node=None, **opts):
3524 def merge(ui, repo, node=None, **opts):
3525 """merge another revision into working directory
3525 """merge another revision into working directory
3526
3526
3527 The current working directory is updated with all changes made in
3527 The current working directory is updated with all changes made in
3528 the requested revision since the last common predecessor revision.
3528 the requested revision since the last common predecessor revision.
3529
3529
3530 Files that changed between either parent are marked as changed for
3530 Files that changed between either parent are marked as changed for
3531 the next commit and a commit must be performed before any further
3531 the next commit and a commit must be performed before any further
3532 updates to the repository are allowed. The next commit will have
3532 updates to the repository are allowed. The next commit will have
3533 two parents.
3533 two parents.
3534
3534
3535 ``--tool`` can be used to specify the merge tool used for file
3535 ``--tool`` can be used to specify the merge tool used for file
3536 merges. It overrides the HGMERGE environment variable and your
3536 merges. It overrides the HGMERGE environment variable and your
3537 configuration files. See :hg:`help merge-tools` for options.
3537 configuration files. See :hg:`help merge-tools` for options.
3538
3538
3539 If no revision is specified, the working directory's parent is a
3539 If no revision is specified, the working directory's parent is a
3540 head revision, and the current branch contains exactly one other
3540 head revision, and the current branch contains exactly one other
3541 head, the other head is merged with by default. Otherwise, an
3541 head, the other head is merged with by default. Otherwise, an
3542 explicit revision with which to merge with must be provided.
3542 explicit revision with which to merge with must be provided.
3543
3543
3544 See :hg:`help resolve` for information on handling file conflicts.
3544 See :hg:`help resolve` for information on handling file conflicts.
3545
3545
3546 To undo an uncommitted merge, use :hg:`update --clean .` which
3546 To undo an uncommitted merge, use :hg:`update --clean .` which
3547 will check out a clean copy of the original merge parent, losing
3547 will check out a clean copy of the original merge parent, losing
3548 all changes.
3548 all changes.
3549
3549
3550 Returns 0 on success, 1 if there are unresolved files.
3550 Returns 0 on success, 1 if there are unresolved files.
3551 """
3551 """
3552
3552
3553 if opts.get('rev') and node:
3553 if opts.get('rev') and node:
3554 raise error.Abort(_("please specify just one revision"))
3554 raise error.Abort(_("please specify just one revision"))
3555 if not node:
3555 if not node:
3556 node = opts.get('rev')
3556 node = opts.get('rev')
3557
3557
3558 if node:
3558 if node:
3559 node = scmutil.revsingle(repo, node).node()
3559 node = scmutil.revsingle(repo, node).node()
3560
3560
3561 if not node:
3561 if not node:
3562 node = repo[destutil.destmerge(repo)].node()
3562 node = repo[destutil.destmerge(repo)].node()
3563
3563
3564 if opts.get('preview'):
3564 if opts.get('preview'):
3565 # find nodes that are ancestors of p2 but not of p1
3565 # find nodes that are ancestors of p2 but not of p1
3566 p1 = repo.lookup('.')
3566 p1 = repo.lookup('.')
3567 p2 = repo.lookup(node)
3567 p2 = repo.lookup(node)
3568 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3568 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3569
3569
3570 displayer = cmdutil.show_changeset(ui, repo, opts)
3570 displayer = cmdutil.show_changeset(ui, repo, opts)
3571 for node in nodes:
3571 for node in nodes:
3572 displayer.show(repo[node])
3572 displayer.show(repo[node])
3573 displayer.close()
3573 displayer.close()
3574 return 0
3574 return 0
3575
3575
3576 try:
3576 try:
3577 # ui.forcemerge is an internal variable, do not document
3577 # ui.forcemerge is an internal variable, do not document
3578 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3578 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3579 force = opts.get('force')
3579 force = opts.get('force')
3580 labels = ['working copy', 'merge rev']
3580 labels = ['working copy', 'merge rev']
3581 return hg.merge(repo, node, force=force, mergeforce=force,
3581 return hg.merge(repo, node, force=force, mergeforce=force,
3582 labels=labels)
3582 labels=labels)
3583 finally:
3583 finally:
3584 ui.setconfig('ui', 'forcemerge', '', 'merge')
3584 ui.setconfig('ui', 'forcemerge', '', 'merge')
3585
3585
3586 @command('outgoing|out',
3586 @command('outgoing|out',
3587 [('f', 'force', None, _('run even when the destination is unrelated')),
3587 [('f', 'force', None, _('run even when the destination is unrelated')),
3588 ('r', 'rev', [],
3588 ('r', 'rev', [],
3589 _('a changeset intended to be included in the destination'), _('REV')),
3589 _('a changeset intended to be included in the destination'), _('REV')),
3590 ('n', 'newest-first', None, _('show newest record first')),
3590 ('n', 'newest-first', None, _('show newest record first')),
3591 ('B', 'bookmarks', False, _('compare bookmarks')),
3591 ('B', 'bookmarks', False, _('compare bookmarks')),
3592 ('b', 'branch', [], _('a specific branch you would like to push'),
3592 ('b', 'branch', [], _('a specific branch you would like to push'),
3593 _('BRANCH')),
3593 _('BRANCH')),
3594 ] + logopts + remoteopts + subrepoopts,
3594 ] + logopts + remoteopts + subrepoopts,
3595 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3595 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3596 def outgoing(ui, repo, dest=None, **opts):
3596 def outgoing(ui, repo, dest=None, **opts):
3597 """show changesets not found in the destination
3597 """show changesets not found in the destination
3598
3598
3599 Show changesets not found in the specified destination repository
3599 Show changesets not found in the specified destination repository
3600 or the default push location. These are the changesets that would
3600 or the default push location. These are the changesets that would
3601 be pushed if a push was requested.
3601 be pushed if a push was requested.
3602
3602
3603 See pull for details of valid destination formats.
3603 See pull for details of valid destination formats.
3604
3604
3605 .. container:: verbose
3605 .. container:: verbose
3606
3606
3607 With -B/--bookmarks, the result of bookmark comparison between
3607 With -B/--bookmarks, the result of bookmark comparison between
3608 local and remote repositories is displayed. With -v/--verbose,
3608 local and remote repositories is displayed. With -v/--verbose,
3609 status is also displayed for each bookmark like below::
3609 status is also displayed for each bookmark like below::
3610
3610
3611 BM1 01234567890a added
3611 BM1 01234567890a added
3612 BM2 deleted
3612 BM2 deleted
3613 BM3 234567890abc advanced
3613 BM3 234567890abc advanced
3614 BM4 34567890abcd diverged
3614 BM4 34567890abcd diverged
3615 BM5 4567890abcde changed
3615 BM5 4567890abcde changed
3616
3616
3617 The action taken when pushing depends on the
3617 The action taken when pushing depends on the
3618 status of each bookmark:
3618 status of each bookmark:
3619
3619
3620 :``added``: push with ``-B`` will create it
3620 :``added``: push with ``-B`` will create it
3621 :``deleted``: push with ``-B`` will delete it
3621 :``deleted``: push with ``-B`` will delete it
3622 :``advanced``: push will update it
3622 :``advanced``: push will update it
3623 :``diverged``: push with ``-B`` will update it
3623 :``diverged``: push with ``-B`` will update it
3624 :``changed``: push with ``-B`` will update it
3624 :``changed``: push with ``-B`` will update it
3625
3625
3626 From the point of view of pushing behavior, bookmarks
3626 From the point of view of pushing behavior, bookmarks
3627 existing only in the remote repository are treated as
3627 existing only in the remote repository are treated as
3628 ``deleted``, even if it is in fact added remotely.
3628 ``deleted``, even if it is in fact added remotely.
3629
3629
3630 Returns 0 if there are outgoing changes, 1 otherwise.
3630 Returns 0 if there are outgoing changes, 1 otherwise.
3631 """
3631 """
3632 if opts.get('graph'):
3632 if opts.get('graph'):
3633 cmdutil.checkunsupportedgraphflags([], opts)
3633 cmdutil.checkunsupportedgraphflags([], opts)
3634 o, other = hg._outgoing(ui, repo, dest, opts)
3634 o, other = hg._outgoing(ui, repo, dest, opts)
3635 if not o:
3635 if not o:
3636 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3636 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3637 return
3637 return
3638
3638
3639 revdag = cmdutil.graphrevs(repo, o, opts)
3639 revdag = cmdutil.graphrevs(repo, o, opts)
3640 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3640 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3641 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3641 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3642 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3642 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3643 return 0
3643 return 0
3644
3644
3645 if opts.get('bookmarks'):
3645 if opts.get('bookmarks'):
3646 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3646 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3647 dest, branches = hg.parseurl(dest, opts.get('branch'))
3647 dest, branches = hg.parseurl(dest, opts.get('branch'))
3648 other = hg.peer(repo, opts, dest)
3648 other = hg.peer(repo, opts, dest)
3649 if 'bookmarks' not in other.listkeys('namespaces'):
3649 if 'bookmarks' not in other.listkeys('namespaces'):
3650 ui.warn(_("remote doesn't support bookmarks\n"))
3650 ui.warn(_("remote doesn't support bookmarks\n"))
3651 return 0
3651 return 0
3652 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3652 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3653 return bookmarks.outgoing(ui, repo, other)
3653 return bookmarks.outgoing(ui, repo, other)
3654
3654
3655 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3655 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3656 try:
3656 try:
3657 return hg.outgoing(ui, repo, dest, opts)
3657 return hg.outgoing(ui, repo, dest, opts)
3658 finally:
3658 finally:
3659 del repo._subtoppath
3659 del repo._subtoppath
3660
3660
3661 @command('parents',
3661 @command('parents',
3662 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3662 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3663 ] + templateopts,
3663 ] + templateopts,
3664 _('[-r REV] [FILE]'),
3664 _('[-r REV] [FILE]'),
3665 inferrepo=True)
3665 inferrepo=True)
3666 def parents(ui, repo, file_=None, **opts):
3666 def parents(ui, repo, file_=None, **opts):
3667 """show the parents of the working directory or revision (DEPRECATED)
3667 """show the parents of the working directory or revision (DEPRECATED)
3668
3668
3669 Print the working directory's parent revisions. If a revision is
3669 Print the working directory's parent revisions. If a revision is
3670 given via -r/--rev, the parent of that revision will be printed.
3670 given via -r/--rev, the parent of that revision will be printed.
3671 If a file argument is given, the revision in which the file was
3671 If a file argument is given, the revision in which the file was
3672 last changed (before the working directory revision or the
3672 last changed (before the working directory revision or the
3673 argument to --rev if given) is printed.
3673 argument to --rev if given) is printed.
3674
3674
3675 This command is equivalent to::
3675 This command is equivalent to::
3676
3676
3677 hg log -r "p1()+p2()" or
3677 hg log -r "p1()+p2()" or
3678 hg log -r "p1(REV)+p2(REV)" or
3678 hg log -r "p1(REV)+p2(REV)" or
3679 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3679 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3680 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3680 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3681
3681
3682 See :hg:`summary` and :hg:`help revsets` for related information.
3682 See :hg:`summary` and :hg:`help revsets` for related information.
3683
3683
3684 Returns 0 on success.
3684 Returns 0 on success.
3685 """
3685 """
3686
3686
3687 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3687 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3688
3688
3689 if file_:
3689 if file_:
3690 m = scmutil.match(ctx, (file_,), opts)
3690 m = scmutil.match(ctx, (file_,), opts)
3691 if m.anypats() or len(m.files()) != 1:
3691 if m.anypats() or len(m.files()) != 1:
3692 raise error.Abort(_('can only specify an explicit filename'))
3692 raise error.Abort(_('can only specify an explicit filename'))
3693 file_ = m.files()[0]
3693 file_ = m.files()[0]
3694 filenodes = []
3694 filenodes = []
3695 for cp in ctx.parents():
3695 for cp in ctx.parents():
3696 if not cp:
3696 if not cp:
3697 continue
3697 continue
3698 try:
3698 try:
3699 filenodes.append(cp.filenode(file_))
3699 filenodes.append(cp.filenode(file_))
3700 except error.LookupError:
3700 except error.LookupError:
3701 pass
3701 pass
3702 if not filenodes:
3702 if not filenodes:
3703 raise error.Abort(_("'%s' not found in manifest!") % file_)
3703 raise error.Abort(_("'%s' not found in manifest!") % file_)
3704 p = []
3704 p = []
3705 for fn in filenodes:
3705 for fn in filenodes:
3706 fctx = repo.filectx(file_, fileid=fn)
3706 fctx = repo.filectx(file_, fileid=fn)
3707 p.append(fctx.node())
3707 p.append(fctx.node())
3708 else:
3708 else:
3709 p = [cp.node() for cp in ctx.parents()]
3709 p = [cp.node() for cp in ctx.parents()]
3710
3710
3711 displayer = cmdutil.show_changeset(ui, repo, opts)
3711 displayer = cmdutil.show_changeset(ui, repo, opts)
3712 for n in p:
3712 for n in p:
3713 if n != nullid:
3713 if n != nullid:
3714 displayer.show(repo[n])
3714 displayer.show(repo[n])
3715 displayer.close()
3715 displayer.close()
3716
3716
3717 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3717 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3718 def paths(ui, repo, search=None, **opts):
3718 def paths(ui, repo, search=None, **opts):
3719 """show aliases for remote repositories
3719 """show aliases for remote repositories
3720
3720
3721 Show definition of symbolic path name NAME. If no name is given,
3721 Show definition of symbolic path name NAME. If no name is given,
3722 show definition of all available names.
3722 show definition of all available names.
3723
3723
3724 Option -q/--quiet suppresses all output when searching for NAME
3724 Option -q/--quiet suppresses all output when searching for NAME
3725 and shows only the path names when listing all definitions.
3725 and shows only the path names when listing all definitions.
3726
3726
3727 Path names are defined in the [paths] section of your
3727 Path names are defined in the [paths] section of your
3728 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3728 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3729 repository, ``.hg/hgrc`` is used, too.
3729 repository, ``.hg/hgrc`` is used, too.
3730
3730
3731 The path names ``default`` and ``default-push`` have a special
3731 The path names ``default`` and ``default-push`` have a special
3732 meaning. When performing a push or pull operation, they are used
3732 meaning. When performing a push or pull operation, they are used
3733 as fallbacks if no location is specified on the command-line.
3733 as fallbacks if no location is specified on the command-line.
3734 When ``default-push`` is set, it will be used for push and
3734 When ``default-push`` is set, it will be used for push and
3735 ``default`` will be used for pull; otherwise ``default`` is used
3735 ``default`` will be used for pull; otherwise ``default`` is used
3736 as the fallback for both. When cloning a repository, the clone
3736 as the fallback for both. When cloning a repository, the clone
3737 source is written as ``default`` in ``.hg/hgrc``.
3737 source is written as ``default`` in ``.hg/hgrc``.
3738
3738
3739 .. note::
3739 .. note::
3740
3740
3741 ``default`` and ``default-push`` apply to all inbound (e.g.
3741 ``default`` and ``default-push`` apply to all inbound (e.g.
3742 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3742 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3743 and :hg:`bundle`) operations.
3743 and :hg:`bundle`) operations.
3744
3744
3745 See :hg:`help urls` for more information.
3745 See :hg:`help urls` for more information.
3746
3746
3747 Returns 0 on success.
3747 Returns 0 on success.
3748 """
3748 """
3749 if search:
3749 if search:
3750 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3750 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3751 if name == search]
3751 if name == search]
3752 else:
3752 else:
3753 pathitems = sorted(ui.paths.iteritems())
3753 pathitems = sorted(ui.paths.iteritems())
3754
3754
3755 fm = ui.formatter('paths', opts)
3755 fm = ui.formatter('paths', opts)
3756 if fm.isplain():
3756 if fm.isplain():
3757 hidepassword = util.hidepassword
3757 hidepassword = util.hidepassword
3758 else:
3758 else:
3759 hidepassword = str
3759 hidepassword = str
3760 if ui.quiet:
3760 if ui.quiet:
3761 namefmt = '%s\n'
3761 namefmt = '%s\n'
3762 else:
3762 else:
3763 namefmt = '%s = '
3763 namefmt = '%s = '
3764 showsubopts = not search and not ui.quiet
3764 showsubopts = not search and not ui.quiet
3765
3765
3766 for name, path in pathitems:
3766 for name, path in pathitems:
3767 fm.startitem()
3767 fm.startitem()
3768 fm.condwrite(not search, 'name', namefmt, name)
3768 fm.condwrite(not search, 'name', namefmt, name)
3769 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3769 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3770 for subopt, value in sorted(path.suboptions.items()):
3770 for subopt, value in sorted(path.suboptions.items()):
3771 assert subopt not in ('name', 'url')
3771 assert subopt not in ('name', 'url')
3772 if showsubopts:
3772 if showsubopts:
3773 fm.plain('%s:%s = ' % (name, subopt))
3773 fm.plain('%s:%s = ' % (name, subopt))
3774 fm.condwrite(showsubopts, subopt, '%s\n', value)
3774 fm.condwrite(showsubopts, subopt, '%s\n', value)
3775
3775
3776 fm.end()
3776 fm.end()
3777
3777
3778 if search and not pathitems:
3778 if search and not pathitems:
3779 if not ui.quiet:
3779 if not ui.quiet:
3780 ui.warn(_("not found!\n"))
3780 ui.warn(_("not found!\n"))
3781 return 1
3781 return 1
3782 else:
3782 else:
3783 return 0
3783 return 0
3784
3784
3785 @command('phase',
3785 @command('phase',
3786 [('p', 'public', False, _('set changeset phase to public')),
3786 [('p', 'public', False, _('set changeset phase to public')),
3787 ('d', 'draft', False, _('set changeset phase to draft')),
3787 ('d', 'draft', False, _('set changeset phase to draft')),
3788 ('s', 'secret', False, _('set changeset phase to secret')),
3788 ('s', 'secret', False, _('set changeset phase to secret')),
3789 ('f', 'force', False, _('allow to move boundary backward')),
3789 ('f', 'force', False, _('allow to move boundary backward')),
3790 ('r', 'rev', [], _('target revision'), _('REV')),
3790 ('r', 'rev', [], _('target revision'), _('REV')),
3791 ],
3791 ],
3792 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3792 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3793 def phase(ui, repo, *revs, **opts):
3793 def phase(ui, repo, *revs, **opts):
3794 """set or show the current phase name
3794 """set or show the current phase name
3795
3795
3796 With no argument, show the phase name of the current revision(s).
3796 With no argument, show the phase name of the current revision(s).
3797
3797
3798 With one of -p/--public, -d/--draft or -s/--secret, change the
3798 With one of -p/--public, -d/--draft or -s/--secret, change the
3799 phase value of the specified revisions.
3799 phase value of the specified revisions.
3800
3800
3801 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3801 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3802 lower phase to an higher phase. Phases are ordered as follows::
3802 lower phase to an higher phase. Phases are ordered as follows::
3803
3803
3804 public < draft < secret
3804 public < draft < secret
3805
3805
3806 Returns 0 on success, 1 if some phases could not be changed.
3806 Returns 0 on success, 1 if some phases could not be changed.
3807
3807
3808 (For more information about the phases concept, see :hg:`help phases`.)
3808 (For more information about the phases concept, see :hg:`help phases`.)
3809 """
3809 """
3810 # search for a unique phase argument
3810 # search for a unique phase argument
3811 targetphase = None
3811 targetphase = None
3812 for idx, name in enumerate(phases.phasenames):
3812 for idx, name in enumerate(phases.phasenames):
3813 if opts[name]:
3813 if opts[name]:
3814 if targetphase is not None:
3814 if targetphase is not None:
3815 raise error.Abort(_('only one phase can be specified'))
3815 raise error.Abort(_('only one phase can be specified'))
3816 targetphase = idx
3816 targetphase = idx
3817
3817
3818 # look for specified revision
3818 # look for specified revision
3819 revs = list(revs)
3819 revs = list(revs)
3820 revs.extend(opts['rev'])
3820 revs.extend(opts['rev'])
3821 if not revs:
3821 if not revs:
3822 # display both parents as the second parent phase can influence
3822 # display both parents as the second parent phase can influence
3823 # the phase of a merge commit
3823 # the phase of a merge commit
3824 revs = [c.rev() for c in repo[None].parents()]
3824 revs = [c.rev() for c in repo[None].parents()]
3825
3825
3826 revs = scmutil.revrange(repo, revs)
3826 revs = scmutil.revrange(repo, revs)
3827
3827
3828 lock = None
3828 lock = None
3829 ret = 0
3829 ret = 0
3830 if targetphase is None:
3830 if targetphase is None:
3831 # display
3831 # display
3832 for r in revs:
3832 for r in revs:
3833 ctx = repo[r]
3833 ctx = repo[r]
3834 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3834 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3835 else:
3835 else:
3836 tr = None
3836 tr = None
3837 lock = repo.lock()
3837 lock = repo.lock()
3838 try:
3838 try:
3839 tr = repo.transaction("phase")
3839 tr = repo.transaction("phase")
3840 # set phase
3840 # set phase
3841 if not revs:
3841 if not revs:
3842 raise error.Abort(_('empty revision set'))
3842 raise error.Abort(_('empty revision set'))
3843 nodes = [repo[r].node() for r in revs]
3843 nodes = [repo[r].node() for r in revs]
3844 # moving revision from public to draft may hide them
3844 # moving revision from public to draft may hide them
3845 # We have to check result on an unfiltered repository
3845 # We have to check result on an unfiltered repository
3846 unfi = repo.unfiltered()
3846 unfi = repo.unfiltered()
3847 getphase = unfi._phasecache.phase
3847 getphase = unfi._phasecache.phase
3848 olddata = [getphase(unfi, r) for r in unfi]
3848 olddata = [getphase(unfi, r) for r in unfi]
3849 phases.advanceboundary(repo, tr, targetphase, nodes)
3849 phases.advanceboundary(repo, tr, targetphase, nodes)
3850 if opts['force']:
3850 if opts['force']:
3851 phases.retractboundary(repo, tr, targetphase, nodes)
3851 phases.retractboundary(repo, tr, targetphase, nodes)
3852 tr.close()
3852 tr.close()
3853 finally:
3853 finally:
3854 if tr is not None:
3854 if tr is not None:
3855 tr.release()
3855 tr.release()
3856 lock.release()
3856 lock.release()
3857 getphase = unfi._phasecache.phase
3857 getphase = unfi._phasecache.phase
3858 newdata = [getphase(unfi, r) for r in unfi]
3858 newdata = [getphase(unfi, r) for r in unfi]
3859 changes = sum(newdata[r] != olddata[r] for r in unfi)
3859 changes = sum(newdata[r] != olddata[r] for r in unfi)
3860 cl = unfi.changelog
3860 cl = unfi.changelog
3861 rejected = [n for n in nodes
3861 rejected = [n for n in nodes
3862 if newdata[cl.rev(n)] < targetphase]
3862 if newdata[cl.rev(n)] < targetphase]
3863 if rejected:
3863 if rejected:
3864 ui.warn(_('cannot move %i changesets to a higher '
3864 ui.warn(_('cannot move %i changesets to a higher '
3865 'phase, use --force\n') % len(rejected))
3865 'phase, use --force\n') % len(rejected))
3866 ret = 1
3866 ret = 1
3867 if changes:
3867 if changes:
3868 msg = _('phase changed for %i changesets\n') % changes
3868 msg = _('phase changed for %i changesets\n') % changes
3869 if ret:
3869 if ret:
3870 ui.status(msg)
3870 ui.status(msg)
3871 else:
3871 else:
3872 ui.note(msg)
3872 ui.note(msg)
3873 else:
3873 else:
3874 ui.warn(_('no phases changed\n'))
3874 ui.warn(_('no phases changed\n'))
3875 return ret
3875 return ret
3876
3876
3877 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3877 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3878 """Run after a changegroup has been added via pull/unbundle
3878 """Run after a changegroup has been added via pull/unbundle
3879
3879
3880 This takes arguments below:
3880 This takes arguments below:
3881
3881
3882 :modheads: change of heads by pull/unbundle
3882 :modheads: change of heads by pull/unbundle
3883 :optupdate: updating working directory is needed or not
3883 :optupdate: updating working directory is needed or not
3884 :checkout: update destination revision (or None to default destination)
3884 :checkout: update destination revision (or None to default destination)
3885 :brev: a name, which might be a bookmark to be activated after updating
3885 :brev: a name, which might be a bookmark to be activated after updating
3886 """
3886 """
3887 if modheads == 0:
3887 if modheads == 0:
3888 return
3888 return
3889 if optupdate:
3889 if optupdate:
3890 try:
3890 try:
3891 return hg.updatetotally(ui, repo, checkout, brev)
3891 return hg.updatetotally(ui, repo, checkout, brev)
3892 except error.UpdateAbort as inst:
3892 except error.UpdateAbort as inst:
3893 msg = _("not updating: %s") % str(inst)
3893 msg = _("not updating: %s") % str(inst)
3894 hint = inst.hint
3894 hint = inst.hint
3895 raise error.UpdateAbort(msg, hint=hint)
3895 raise error.UpdateAbort(msg, hint=hint)
3896 if modheads > 1:
3896 if modheads > 1:
3897 currentbranchheads = len(repo.branchheads())
3897 currentbranchheads = len(repo.branchheads())
3898 if currentbranchheads == modheads:
3898 if currentbranchheads == modheads:
3899 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3899 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3900 elif currentbranchheads > 1:
3900 elif currentbranchheads > 1:
3901 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3901 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3902 "merge)\n"))
3902 "merge)\n"))
3903 else:
3903 else:
3904 ui.status(_("(run 'hg heads' to see heads)\n"))
3904 ui.status(_("(run 'hg heads' to see heads)\n"))
3905 else:
3905 else:
3906 ui.status(_("(run 'hg update' to get a working copy)\n"))
3906 ui.status(_("(run 'hg update' to get a working copy)\n"))
3907
3907
3908 @command('^pull',
3908 @command('^pull',
3909 [('u', 'update', None,
3909 [('u', 'update', None,
3910 _('update to new branch head if changesets were pulled')),
3910 _('update to new branch head if changesets were pulled')),
3911 ('f', 'force', None, _('run even when remote repository is unrelated')),
3911 ('f', 'force', None, _('run even when remote repository is unrelated')),
3912 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3912 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3913 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3913 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3914 ('b', 'branch', [], _('a specific branch you would like to pull'),
3914 ('b', 'branch', [], _('a specific branch you would like to pull'),
3915 _('BRANCH')),
3915 _('BRANCH')),
3916 ] + remoteopts,
3916 ] + remoteopts,
3917 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3917 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3918 def pull(ui, repo, source="default", **opts):
3918 def pull(ui, repo, source="default", **opts):
3919 """pull changes from the specified source
3919 """pull changes from the specified source
3920
3920
3921 Pull changes from a remote repository to a local one.
3921 Pull changes from a remote repository to a local one.
3922
3922
3923 This finds all changes from the repository at the specified path
3923 This finds all changes from the repository at the specified path
3924 or URL and adds them to a local repository (the current one unless
3924 or URL and adds them to a local repository (the current one unless
3925 -R is specified). By default, this does not update the copy of the
3925 -R is specified). By default, this does not update the copy of the
3926 project in the working directory.
3926 project in the working directory.
3927
3927
3928 Use :hg:`incoming` if you want to see what would have been added
3928 Use :hg:`incoming` if you want to see what would have been added
3929 by a pull at the time you issued this command. If you then decide
3929 by a pull at the time you issued this command. If you then decide
3930 to add those changes to the repository, you should use :hg:`pull
3930 to add those changes to the repository, you should use :hg:`pull
3931 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3931 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3932
3932
3933 If SOURCE is omitted, the 'default' path will be used.
3933 If SOURCE is omitted, the 'default' path will be used.
3934 See :hg:`help urls` for more information.
3934 See :hg:`help urls` for more information.
3935
3935
3936 Specifying bookmark as ``.`` is equivalent to specifying the active
3936 Specifying bookmark as ``.`` is equivalent to specifying the active
3937 bookmark's name.
3937 bookmark's name.
3938
3938
3939 Returns 0 on success, 1 if an update had unresolved files.
3939 Returns 0 on success, 1 if an update had unresolved files.
3940 """
3940 """
3941 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3941 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3942 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3942 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3943 other = hg.peer(repo, opts, source)
3943 other = hg.peer(repo, opts, source)
3944 try:
3944 try:
3945 revs, checkout = hg.addbranchrevs(repo, other, branches,
3945 revs, checkout = hg.addbranchrevs(repo, other, branches,
3946 opts.get('rev'))
3946 opts.get('rev'))
3947
3947
3948
3948
3949 pullopargs = {}
3949 pullopargs = {}
3950 if opts.get('bookmark'):
3950 if opts.get('bookmark'):
3951 if not revs:
3951 if not revs:
3952 revs = []
3952 revs = []
3953 # The list of bookmark used here is not the one used to actually
3953 # The list of bookmark used here is not the one used to actually
3954 # update the bookmark name. This can result in the revision pulled
3954 # update the bookmark name. This can result in the revision pulled
3955 # not ending up with the name of the bookmark because of a race
3955 # not ending up with the name of the bookmark because of a race
3956 # condition on the server. (See issue 4689 for details)
3956 # condition on the server. (See issue 4689 for details)
3957 remotebookmarks = other.listkeys('bookmarks')
3957 remotebookmarks = other.listkeys('bookmarks')
3958 pullopargs['remotebookmarks'] = remotebookmarks
3958 pullopargs['remotebookmarks'] = remotebookmarks
3959 for b in opts['bookmark']:
3959 for b in opts['bookmark']:
3960 b = repo._bookmarks.expandname(b)
3960 b = repo._bookmarks.expandname(b)
3961 if b not in remotebookmarks:
3961 if b not in remotebookmarks:
3962 raise error.Abort(_('remote bookmark %s not found!') % b)
3962 raise error.Abort(_('remote bookmark %s not found!') % b)
3963 revs.append(remotebookmarks[b])
3963 revs.append(remotebookmarks[b])
3964
3964
3965 if revs:
3965 if revs:
3966 try:
3966 try:
3967 # When 'rev' is a bookmark name, we cannot guarantee that it
3967 # When 'rev' is a bookmark name, we cannot guarantee that it
3968 # will be updated with that name because of a race condition
3968 # will be updated with that name because of a race condition
3969 # server side. (See issue 4689 for details)
3969 # server side. (See issue 4689 for details)
3970 oldrevs = revs
3970 oldrevs = revs
3971 revs = [] # actually, nodes
3971 revs = [] # actually, nodes
3972 for r in oldrevs:
3972 for r in oldrevs:
3973 node = other.lookup(r)
3973 node = other.lookup(r)
3974 revs.append(node)
3974 revs.append(node)
3975 if r == checkout:
3975 if r == checkout:
3976 checkout = node
3976 checkout = node
3977 except error.CapabilityError:
3977 except error.CapabilityError:
3978 err = _("other repository doesn't support revision lookup, "
3978 err = _("other repository doesn't support revision lookup, "
3979 "so a rev cannot be specified.")
3979 "so a rev cannot be specified.")
3980 raise error.Abort(err)
3980 raise error.Abort(err)
3981
3981
3982 pullopargs.update(opts.get('opargs', {}))
3982 pullopargs.update(opts.get('opargs', {}))
3983 modheads = exchange.pull(repo, other, heads=revs,
3983 modheads = exchange.pull(repo, other, heads=revs,
3984 force=opts.get('force'),
3984 force=opts.get('force'),
3985 bookmarks=opts.get('bookmark', ()),
3985 bookmarks=opts.get('bookmark', ()),
3986 opargs=pullopargs).cgresult
3986 opargs=pullopargs).cgresult
3987
3987
3988 # brev is a name, which might be a bookmark to be activated at
3988 # brev is a name, which might be a bookmark to be activated at
3989 # the end of the update. In other words, it is an explicit
3989 # the end of the update. In other words, it is an explicit
3990 # destination of the update
3990 # destination of the update
3991 brev = None
3991 brev = None
3992
3992
3993 if checkout:
3993 if checkout:
3994 checkout = str(repo.changelog.rev(checkout))
3994 checkout = str(repo.changelog.rev(checkout))
3995
3995
3996 # order below depends on implementation of
3996 # order below depends on implementation of
3997 # hg.addbranchrevs(). opts['bookmark'] is ignored,
3997 # hg.addbranchrevs(). opts['bookmark'] is ignored,
3998 # because 'checkout' is determined without it.
3998 # because 'checkout' is determined without it.
3999 if opts.get('rev'):
3999 if opts.get('rev'):
4000 brev = opts['rev'][0]
4000 brev = opts['rev'][0]
4001 elif opts.get('branch'):
4001 elif opts.get('branch'):
4002 brev = opts['branch'][0]
4002 brev = opts['branch'][0]
4003 else:
4003 else:
4004 brev = branches[0]
4004 brev = branches[0]
4005 repo._subtoppath = source
4005 repo._subtoppath = source
4006 try:
4006 try:
4007 ret = postincoming(ui, repo, modheads, opts.get('update'),
4007 ret = postincoming(ui, repo, modheads, opts.get('update'),
4008 checkout, brev)
4008 checkout, brev)
4009
4009
4010 finally:
4010 finally:
4011 del repo._subtoppath
4011 del repo._subtoppath
4012
4012
4013 finally:
4013 finally:
4014 other.close()
4014 other.close()
4015 return ret
4015 return ret
4016
4016
4017 @command('^push',
4017 @command('^push',
4018 [('f', 'force', None, _('force push')),
4018 [('f', 'force', None, _('force push')),
4019 ('r', 'rev', [],
4019 ('r', 'rev', [],
4020 _('a changeset intended to be included in the destination'),
4020 _('a changeset intended to be included in the destination'),
4021 _('REV')),
4021 _('REV')),
4022 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4022 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4023 ('b', 'branch', [],
4023 ('b', 'branch', [],
4024 _('a specific branch you would like to push'), _('BRANCH')),
4024 _('a specific branch you would like to push'), _('BRANCH')),
4025 ('', 'new-branch', False, _('allow pushing a new branch')),
4025 ('', 'new-branch', False, _('allow pushing a new branch')),
4026 ] + remoteopts,
4026 ] + remoteopts,
4027 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4027 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4028 def push(ui, repo, dest=None, **opts):
4028 def push(ui, repo, dest=None, **opts):
4029 """push changes to the specified destination
4029 """push changes to the specified destination
4030
4030
4031 Push changesets from the local repository to the specified
4031 Push changesets from the local repository to the specified
4032 destination.
4032 destination.
4033
4033
4034 This operation is symmetrical to pull: it is identical to a pull
4034 This operation is symmetrical to pull: it is identical to a pull
4035 in the destination repository from the current one.
4035 in the destination repository from the current one.
4036
4036
4037 By default, push will not allow creation of new heads at the
4037 By default, push will not allow creation of new heads at the
4038 destination, since multiple heads would make it unclear which head
4038 destination, since multiple heads would make it unclear which head
4039 to use. In this situation, it is recommended to pull and merge
4039 to use. In this situation, it is recommended to pull and merge
4040 before pushing.
4040 before pushing.
4041
4041
4042 Use --new-branch if you want to allow push to create a new named
4042 Use --new-branch if you want to allow push to create a new named
4043 branch that is not present at the destination. This allows you to
4043 branch that is not present at the destination. This allows you to
4044 only create a new branch without forcing other changes.
4044 only create a new branch without forcing other changes.
4045
4045
4046 .. note::
4046 .. note::
4047
4047
4048 Extra care should be taken with the -f/--force option,
4048 Extra care should be taken with the -f/--force option,
4049 which will push all new heads on all branches, an action which will
4049 which will push all new heads on all branches, an action which will
4050 almost always cause confusion for collaborators.
4050 almost always cause confusion for collaborators.
4051
4051
4052 If -r/--rev is used, the specified revision and all its ancestors
4052 If -r/--rev is used, the specified revision and all its ancestors
4053 will be pushed to the remote repository.
4053 will be pushed to the remote repository.
4054
4054
4055 If -B/--bookmark is used, the specified bookmarked revision, its
4055 If -B/--bookmark is used, the specified bookmarked revision, its
4056 ancestors, and the bookmark will be pushed to the remote
4056 ancestors, and the bookmark will be pushed to the remote
4057 repository. Specifying ``.`` is equivalent to specifying the active
4057 repository. Specifying ``.`` is equivalent to specifying the active
4058 bookmark's name.
4058 bookmark's name.
4059
4059
4060 Please see :hg:`help urls` for important details about ``ssh://``
4060 Please see :hg:`help urls` for important details about ``ssh://``
4061 URLs. If DESTINATION is omitted, a default path will be used.
4061 URLs. If DESTINATION is omitted, a default path will be used.
4062
4062
4063 Returns 0 if push was successful, 1 if nothing to push.
4063 Returns 0 if push was successful, 1 if nothing to push.
4064 """
4064 """
4065
4065
4066 if opts.get('bookmark'):
4066 if opts.get('bookmark'):
4067 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4067 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4068 for b in opts['bookmark']:
4068 for b in opts['bookmark']:
4069 # translate -B options to -r so changesets get pushed
4069 # translate -B options to -r so changesets get pushed
4070 b = repo._bookmarks.expandname(b)
4070 b = repo._bookmarks.expandname(b)
4071 if b in repo._bookmarks:
4071 if b in repo._bookmarks:
4072 opts.setdefault('rev', []).append(b)
4072 opts.setdefault('rev', []).append(b)
4073 else:
4073 else:
4074 # if we try to push a deleted bookmark, translate it to null
4074 # if we try to push a deleted bookmark, translate it to null
4075 # this lets simultaneous -r, -b options continue working
4075 # this lets simultaneous -r, -b options continue working
4076 opts.setdefault('rev', []).append("null")
4076 opts.setdefault('rev', []).append("null")
4077
4077
4078 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4078 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4079 if not path:
4079 if not path:
4080 raise error.Abort(_('default repository not configured!'),
4080 raise error.Abort(_('default repository not configured!'),
4081 hint=_("see 'hg help config.paths'"))
4081 hint=_("see 'hg help config.paths'"))
4082 dest = path.pushloc or path.loc
4082 dest = path.pushloc or path.loc
4083 branches = (path.branch, opts.get('branch') or [])
4083 branches = (path.branch, opts.get('branch') or [])
4084 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4084 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4085 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4085 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4086 other = hg.peer(repo, opts, dest)
4086 other = hg.peer(repo, opts, dest)
4087
4087
4088 if revs:
4088 if revs:
4089 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4089 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4090 if not revs:
4090 if not revs:
4091 raise error.Abort(_("specified revisions evaluate to an empty set"),
4091 raise error.Abort(_("specified revisions evaluate to an empty set"),
4092 hint=_("use different revision arguments"))
4092 hint=_("use different revision arguments"))
4093 elif path.pushrev:
4093 elif path.pushrev:
4094 # It doesn't make any sense to specify ancestor revisions. So limit
4094 # It doesn't make any sense to specify ancestor revisions. So limit
4095 # to DAG heads to make discovery simpler.
4095 # to DAG heads to make discovery simpler.
4096 expr = revset.formatspec('heads(%r)', path.pushrev)
4096 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4097 revs = scmutil.revrange(repo, [expr])
4097 revs = scmutil.revrange(repo, [expr])
4098 revs = [repo[rev].node() for rev in revs]
4098 revs = [repo[rev].node() for rev in revs]
4099 if not revs:
4099 if not revs:
4100 raise error.Abort(_('default push revset for path evaluates to an '
4100 raise error.Abort(_('default push revset for path evaluates to an '
4101 'empty set'))
4101 'empty set'))
4102
4102
4103 repo._subtoppath = dest
4103 repo._subtoppath = dest
4104 try:
4104 try:
4105 # push subrepos depth-first for coherent ordering
4105 # push subrepos depth-first for coherent ordering
4106 c = repo['']
4106 c = repo['']
4107 subs = c.substate # only repos that are committed
4107 subs = c.substate # only repos that are committed
4108 for s in sorted(subs):
4108 for s in sorted(subs):
4109 result = c.sub(s).push(opts)
4109 result = c.sub(s).push(opts)
4110 if result == 0:
4110 if result == 0:
4111 return not result
4111 return not result
4112 finally:
4112 finally:
4113 del repo._subtoppath
4113 del repo._subtoppath
4114 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4114 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4115 newbranch=opts.get('new_branch'),
4115 newbranch=opts.get('new_branch'),
4116 bookmarks=opts.get('bookmark', ()),
4116 bookmarks=opts.get('bookmark', ()),
4117 opargs=opts.get('opargs'))
4117 opargs=opts.get('opargs'))
4118
4118
4119 result = not pushop.cgresult
4119 result = not pushop.cgresult
4120
4120
4121 if pushop.bkresult is not None:
4121 if pushop.bkresult is not None:
4122 if pushop.bkresult == 2:
4122 if pushop.bkresult == 2:
4123 result = 2
4123 result = 2
4124 elif not result and pushop.bkresult:
4124 elif not result and pushop.bkresult:
4125 result = 2
4125 result = 2
4126
4126
4127 return result
4127 return result
4128
4128
4129 @command('recover', [])
4129 @command('recover', [])
4130 def recover(ui, repo):
4130 def recover(ui, repo):
4131 """roll back an interrupted transaction
4131 """roll back an interrupted transaction
4132
4132
4133 Recover from an interrupted commit or pull.
4133 Recover from an interrupted commit or pull.
4134
4134
4135 This command tries to fix the repository status after an
4135 This command tries to fix the repository status after an
4136 interrupted operation. It should only be necessary when Mercurial
4136 interrupted operation. It should only be necessary when Mercurial
4137 suggests it.
4137 suggests it.
4138
4138
4139 Returns 0 if successful, 1 if nothing to recover or verify fails.
4139 Returns 0 if successful, 1 if nothing to recover or verify fails.
4140 """
4140 """
4141 if repo.recover():
4141 if repo.recover():
4142 return hg.verify(repo)
4142 return hg.verify(repo)
4143 return 1
4143 return 1
4144
4144
4145 @command('^remove|rm',
4145 @command('^remove|rm',
4146 [('A', 'after', None, _('record delete for missing files')),
4146 [('A', 'after', None, _('record delete for missing files')),
4147 ('f', 'force', None,
4147 ('f', 'force', None,
4148 _('forget added files, delete modified files')),
4148 _('forget added files, delete modified files')),
4149 ] + subrepoopts + walkopts,
4149 ] + subrepoopts + walkopts,
4150 _('[OPTION]... FILE...'),
4150 _('[OPTION]... FILE...'),
4151 inferrepo=True)
4151 inferrepo=True)
4152 def remove(ui, repo, *pats, **opts):
4152 def remove(ui, repo, *pats, **opts):
4153 """remove the specified files on the next commit
4153 """remove the specified files on the next commit
4154
4154
4155 Schedule the indicated files for removal from the current branch.
4155 Schedule the indicated files for removal from the current branch.
4156
4156
4157 This command schedules the files to be removed at the next commit.
4157 This command schedules the files to be removed at the next commit.
4158 To undo a remove before that, see :hg:`revert`. To undo added
4158 To undo a remove before that, see :hg:`revert`. To undo added
4159 files, see :hg:`forget`.
4159 files, see :hg:`forget`.
4160
4160
4161 .. container:: verbose
4161 .. container:: verbose
4162
4162
4163 -A/--after can be used to remove only files that have already
4163 -A/--after can be used to remove only files that have already
4164 been deleted, -f/--force can be used to force deletion, and -Af
4164 been deleted, -f/--force can be used to force deletion, and -Af
4165 can be used to remove files from the next revision without
4165 can be used to remove files from the next revision without
4166 deleting them from the working directory.
4166 deleting them from the working directory.
4167
4167
4168 The following table details the behavior of remove for different
4168 The following table details the behavior of remove for different
4169 file states (columns) and option combinations (rows). The file
4169 file states (columns) and option combinations (rows). The file
4170 states are Added [A], Clean [C], Modified [M] and Missing [!]
4170 states are Added [A], Clean [C], Modified [M] and Missing [!]
4171 (as reported by :hg:`status`). The actions are Warn, Remove
4171 (as reported by :hg:`status`). The actions are Warn, Remove
4172 (from branch) and Delete (from disk):
4172 (from branch) and Delete (from disk):
4173
4173
4174 ========= == == == ==
4174 ========= == == == ==
4175 opt/state A C M !
4175 opt/state A C M !
4176 ========= == == == ==
4176 ========= == == == ==
4177 none W RD W R
4177 none W RD W R
4178 -f R RD RD R
4178 -f R RD RD R
4179 -A W W W R
4179 -A W W W R
4180 -Af R R R R
4180 -Af R R R R
4181 ========= == == == ==
4181 ========= == == == ==
4182
4182
4183 .. note::
4183 .. note::
4184
4184
4185 :hg:`remove` never deletes files in Added [A] state from the
4185 :hg:`remove` never deletes files in Added [A] state from the
4186 working directory, not even if ``--force`` is specified.
4186 working directory, not even if ``--force`` is specified.
4187
4187
4188 Returns 0 on success, 1 if any warnings encountered.
4188 Returns 0 on success, 1 if any warnings encountered.
4189 """
4189 """
4190
4190
4191 after, force = opts.get('after'), opts.get('force')
4191 after, force = opts.get('after'), opts.get('force')
4192 if not pats and not after:
4192 if not pats and not after:
4193 raise error.Abort(_('no files specified'))
4193 raise error.Abort(_('no files specified'))
4194
4194
4195 m = scmutil.match(repo[None], pats, opts)
4195 m = scmutil.match(repo[None], pats, opts)
4196 subrepos = opts.get('subrepos')
4196 subrepos = opts.get('subrepos')
4197 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4197 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4198
4198
4199 @command('rename|move|mv',
4199 @command('rename|move|mv',
4200 [('A', 'after', None, _('record a rename that has already occurred')),
4200 [('A', 'after', None, _('record a rename that has already occurred')),
4201 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4201 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4202 ] + walkopts + dryrunopts,
4202 ] + walkopts + dryrunopts,
4203 _('[OPTION]... SOURCE... DEST'))
4203 _('[OPTION]... SOURCE... DEST'))
4204 def rename(ui, repo, *pats, **opts):
4204 def rename(ui, repo, *pats, **opts):
4205 """rename files; equivalent of copy + remove
4205 """rename files; equivalent of copy + remove
4206
4206
4207 Mark dest as copies of sources; mark sources for deletion. If dest
4207 Mark dest as copies of sources; mark sources for deletion. If dest
4208 is a directory, copies are put in that directory. If dest is a
4208 is a directory, copies are put in that directory. If dest is a
4209 file, there can only be one source.
4209 file, there can only be one source.
4210
4210
4211 By default, this command copies the contents of files as they
4211 By default, this command copies the contents of files as they
4212 exist in the working directory. If invoked with -A/--after, the
4212 exist in the working directory. If invoked with -A/--after, the
4213 operation is recorded, but no copying is performed.
4213 operation is recorded, but no copying is performed.
4214
4214
4215 This command takes effect at the next commit. To undo a rename
4215 This command takes effect at the next commit. To undo a rename
4216 before that, see :hg:`revert`.
4216 before that, see :hg:`revert`.
4217
4217
4218 Returns 0 on success, 1 if errors are encountered.
4218 Returns 0 on success, 1 if errors are encountered.
4219 """
4219 """
4220 with repo.wlock(False):
4220 with repo.wlock(False):
4221 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4221 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4222
4222
4223 @command('resolve',
4223 @command('resolve',
4224 [('a', 'all', None, _('select all unresolved files')),
4224 [('a', 'all', None, _('select all unresolved files')),
4225 ('l', 'list', None, _('list state of files needing merge')),
4225 ('l', 'list', None, _('list state of files needing merge')),
4226 ('m', 'mark', None, _('mark files as resolved')),
4226 ('m', 'mark', None, _('mark files as resolved')),
4227 ('u', 'unmark', None, _('mark files as unresolved')),
4227 ('u', 'unmark', None, _('mark files as unresolved')),
4228 ('n', 'no-status', None, _('hide status prefix'))]
4228 ('n', 'no-status', None, _('hide status prefix'))]
4229 + mergetoolopts + walkopts + formatteropts,
4229 + mergetoolopts + walkopts + formatteropts,
4230 _('[OPTION]... [FILE]...'),
4230 _('[OPTION]... [FILE]...'),
4231 inferrepo=True)
4231 inferrepo=True)
4232 def resolve(ui, repo, *pats, **opts):
4232 def resolve(ui, repo, *pats, **opts):
4233 """redo merges or set/view the merge status of files
4233 """redo merges or set/view the merge status of files
4234
4234
4235 Merges with unresolved conflicts are often the result of
4235 Merges with unresolved conflicts are often the result of
4236 non-interactive merging using the ``internal:merge`` configuration
4236 non-interactive merging using the ``internal:merge`` configuration
4237 setting, or a command-line merge tool like ``diff3``. The resolve
4237 setting, or a command-line merge tool like ``diff3``. The resolve
4238 command is used to manage the files involved in a merge, after
4238 command is used to manage the files involved in a merge, after
4239 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4239 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4240 working directory must have two parents). See :hg:`help
4240 working directory must have two parents). See :hg:`help
4241 merge-tools` for information on configuring merge tools.
4241 merge-tools` for information on configuring merge tools.
4242
4242
4243 The resolve command can be used in the following ways:
4243 The resolve command can be used in the following ways:
4244
4244
4245 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4245 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4246 files, discarding any previous merge attempts. Re-merging is not
4246 files, discarding any previous merge attempts. Re-merging is not
4247 performed for files already marked as resolved. Use ``--all/-a``
4247 performed for files already marked as resolved. Use ``--all/-a``
4248 to select all unresolved files. ``--tool`` can be used to specify
4248 to select all unresolved files. ``--tool`` can be used to specify
4249 the merge tool used for the given files. It overrides the HGMERGE
4249 the merge tool used for the given files. It overrides the HGMERGE
4250 environment variable and your configuration files. Previous file
4250 environment variable and your configuration files. Previous file
4251 contents are saved with a ``.orig`` suffix.
4251 contents are saved with a ``.orig`` suffix.
4252
4252
4253 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4253 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4254 (e.g. after having manually fixed-up the files). The default is
4254 (e.g. after having manually fixed-up the files). The default is
4255 to mark all unresolved files.
4255 to mark all unresolved files.
4256
4256
4257 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4257 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4258 default is to mark all resolved files.
4258 default is to mark all resolved files.
4259
4259
4260 - :hg:`resolve -l`: list files which had or still have conflicts.
4260 - :hg:`resolve -l`: list files which had or still have conflicts.
4261 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4261 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4262 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4262 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4263 the list. See :hg:`help filesets` for details.
4263 the list. See :hg:`help filesets` for details.
4264
4264
4265 .. note::
4265 .. note::
4266
4266
4267 Mercurial will not let you commit files with unresolved merge
4267 Mercurial will not let you commit files with unresolved merge
4268 conflicts. You must use :hg:`resolve -m ...` before you can
4268 conflicts. You must use :hg:`resolve -m ...` before you can
4269 commit after a conflicting merge.
4269 commit after a conflicting merge.
4270
4270
4271 Returns 0 on success, 1 if any files fail a resolve attempt.
4271 Returns 0 on success, 1 if any files fail a resolve attempt.
4272 """
4272 """
4273
4273
4274 flaglist = 'all mark unmark list no_status'.split()
4274 flaglist = 'all mark unmark list no_status'.split()
4275 all, mark, unmark, show, nostatus = \
4275 all, mark, unmark, show, nostatus = \
4276 [opts.get(o) for o in flaglist]
4276 [opts.get(o) for o in flaglist]
4277
4277
4278 if (show and (mark or unmark)) or (mark and unmark):
4278 if (show and (mark or unmark)) or (mark and unmark):
4279 raise error.Abort(_("too many options specified"))
4279 raise error.Abort(_("too many options specified"))
4280 if pats and all:
4280 if pats and all:
4281 raise error.Abort(_("can't specify --all and patterns"))
4281 raise error.Abort(_("can't specify --all and patterns"))
4282 if not (all or pats or show or mark or unmark):
4282 if not (all or pats or show or mark or unmark):
4283 raise error.Abort(_('no files or directories specified'),
4283 raise error.Abort(_('no files or directories specified'),
4284 hint=('use --all to re-merge all unresolved files'))
4284 hint=('use --all to re-merge all unresolved files'))
4285
4285
4286 if show:
4286 if show:
4287 fm = ui.formatter('resolve', opts)
4287 fm = ui.formatter('resolve', opts)
4288 ms = mergemod.mergestate.read(repo)
4288 ms = mergemod.mergestate.read(repo)
4289 m = scmutil.match(repo[None], pats, opts)
4289 m = scmutil.match(repo[None], pats, opts)
4290 for f in ms:
4290 for f in ms:
4291 if not m(f):
4291 if not m(f):
4292 continue
4292 continue
4293 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4293 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4294 'd': 'driverresolved'}[ms[f]]
4294 'd': 'driverresolved'}[ms[f]]
4295 fm.startitem()
4295 fm.startitem()
4296 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4296 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4297 fm.write('path', '%s\n', f, label=l)
4297 fm.write('path', '%s\n', f, label=l)
4298 fm.end()
4298 fm.end()
4299 return 0
4299 return 0
4300
4300
4301 with repo.wlock():
4301 with repo.wlock():
4302 ms = mergemod.mergestate.read(repo)
4302 ms = mergemod.mergestate.read(repo)
4303
4303
4304 if not (ms.active() or repo.dirstate.p2() != nullid):
4304 if not (ms.active() or repo.dirstate.p2() != nullid):
4305 raise error.Abort(
4305 raise error.Abort(
4306 _('resolve command not applicable when not merging'))
4306 _('resolve command not applicable when not merging'))
4307
4307
4308 wctx = repo[None]
4308 wctx = repo[None]
4309
4309
4310 if ms.mergedriver and ms.mdstate() == 'u':
4310 if ms.mergedriver and ms.mdstate() == 'u':
4311 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4311 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4312 ms.commit()
4312 ms.commit()
4313 # allow mark and unmark to go through
4313 # allow mark and unmark to go through
4314 if not mark and not unmark and not proceed:
4314 if not mark and not unmark and not proceed:
4315 return 1
4315 return 1
4316
4316
4317 m = scmutil.match(wctx, pats, opts)
4317 m = scmutil.match(wctx, pats, opts)
4318 ret = 0
4318 ret = 0
4319 didwork = False
4319 didwork = False
4320 runconclude = False
4320 runconclude = False
4321
4321
4322 tocomplete = []
4322 tocomplete = []
4323 for f in ms:
4323 for f in ms:
4324 if not m(f):
4324 if not m(f):
4325 continue
4325 continue
4326
4326
4327 didwork = True
4327 didwork = True
4328
4328
4329 # don't let driver-resolved files be marked, and run the conclude
4329 # don't let driver-resolved files be marked, and run the conclude
4330 # step if asked to resolve
4330 # step if asked to resolve
4331 if ms[f] == "d":
4331 if ms[f] == "d":
4332 exact = m.exact(f)
4332 exact = m.exact(f)
4333 if mark:
4333 if mark:
4334 if exact:
4334 if exact:
4335 ui.warn(_('not marking %s as it is driver-resolved\n')
4335 ui.warn(_('not marking %s as it is driver-resolved\n')
4336 % f)
4336 % f)
4337 elif unmark:
4337 elif unmark:
4338 if exact:
4338 if exact:
4339 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4339 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4340 % f)
4340 % f)
4341 else:
4341 else:
4342 runconclude = True
4342 runconclude = True
4343 continue
4343 continue
4344
4344
4345 if mark:
4345 if mark:
4346 ms.mark(f, "r")
4346 ms.mark(f, "r")
4347 elif unmark:
4347 elif unmark:
4348 ms.mark(f, "u")
4348 ms.mark(f, "u")
4349 else:
4349 else:
4350 # backup pre-resolve (merge uses .orig for its own purposes)
4350 # backup pre-resolve (merge uses .orig for its own purposes)
4351 a = repo.wjoin(f)
4351 a = repo.wjoin(f)
4352 try:
4352 try:
4353 util.copyfile(a, a + ".resolve")
4353 util.copyfile(a, a + ".resolve")
4354 except (IOError, OSError) as inst:
4354 except (IOError, OSError) as inst:
4355 if inst.errno != errno.ENOENT:
4355 if inst.errno != errno.ENOENT:
4356 raise
4356 raise
4357
4357
4358 try:
4358 try:
4359 # preresolve file
4359 # preresolve file
4360 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4360 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4361 'resolve')
4361 'resolve')
4362 complete, r = ms.preresolve(f, wctx)
4362 complete, r = ms.preresolve(f, wctx)
4363 if not complete:
4363 if not complete:
4364 tocomplete.append(f)
4364 tocomplete.append(f)
4365 elif r:
4365 elif r:
4366 ret = 1
4366 ret = 1
4367 finally:
4367 finally:
4368 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4368 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4369 ms.commit()
4369 ms.commit()
4370
4370
4371 # replace filemerge's .orig file with our resolve file, but only
4371 # replace filemerge's .orig file with our resolve file, but only
4372 # for merges that are complete
4372 # for merges that are complete
4373 if complete:
4373 if complete:
4374 try:
4374 try:
4375 util.rename(a + ".resolve",
4375 util.rename(a + ".resolve",
4376 scmutil.origpath(ui, repo, a))
4376 scmutil.origpath(ui, repo, a))
4377 except OSError as inst:
4377 except OSError as inst:
4378 if inst.errno != errno.ENOENT:
4378 if inst.errno != errno.ENOENT:
4379 raise
4379 raise
4380
4380
4381 for f in tocomplete:
4381 for f in tocomplete:
4382 try:
4382 try:
4383 # resolve file
4383 # resolve file
4384 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4384 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4385 'resolve')
4385 'resolve')
4386 r = ms.resolve(f, wctx)
4386 r = ms.resolve(f, wctx)
4387 if r:
4387 if r:
4388 ret = 1
4388 ret = 1
4389 finally:
4389 finally:
4390 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4390 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4391 ms.commit()
4391 ms.commit()
4392
4392
4393 # replace filemerge's .orig file with our resolve file
4393 # replace filemerge's .orig file with our resolve file
4394 a = repo.wjoin(f)
4394 a = repo.wjoin(f)
4395 try:
4395 try:
4396 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4396 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4397 except OSError as inst:
4397 except OSError as inst:
4398 if inst.errno != errno.ENOENT:
4398 if inst.errno != errno.ENOENT:
4399 raise
4399 raise
4400
4400
4401 ms.commit()
4401 ms.commit()
4402 ms.recordactions()
4402 ms.recordactions()
4403
4403
4404 if not didwork and pats:
4404 if not didwork and pats:
4405 hint = None
4405 hint = None
4406 if not any([p for p in pats if p.find(':') >= 0]):
4406 if not any([p for p in pats if p.find(':') >= 0]):
4407 pats = ['path:%s' % p for p in pats]
4407 pats = ['path:%s' % p for p in pats]
4408 m = scmutil.match(wctx, pats, opts)
4408 m = scmutil.match(wctx, pats, opts)
4409 for f in ms:
4409 for f in ms:
4410 if not m(f):
4410 if not m(f):
4411 continue
4411 continue
4412 flags = ''.join(['-%s ' % o[0] for o in flaglist
4412 flags = ''.join(['-%s ' % o[0] for o in flaglist
4413 if opts.get(o)])
4413 if opts.get(o)])
4414 hint = _("(try: hg resolve %s%s)\n") % (
4414 hint = _("(try: hg resolve %s%s)\n") % (
4415 flags,
4415 flags,
4416 ' '.join(pats))
4416 ' '.join(pats))
4417 break
4417 break
4418 ui.warn(_("arguments do not match paths that need resolving\n"))
4418 ui.warn(_("arguments do not match paths that need resolving\n"))
4419 if hint:
4419 if hint:
4420 ui.warn(hint)
4420 ui.warn(hint)
4421 elif ms.mergedriver and ms.mdstate() != 's':
4421 elif ms.mergedriver and ms.mdstate() != 's':
4422 # run conclude step when either a driver-resolved file is requested
4422 # run conclude step when either a driver-resolved file is requested
4423 # or there are no driver-resolved files
4423 # or there are no driver-resolved files
4424 # we can't use 'ret' to determine whether any files are unresolved
4424 # we can't use 'ret' to determine whether any files are unresolved
4425 # because we might not have tried to resolve some
4425 # because we might not have tried to resolve some
4426 if ((runconclude or not list(ms.driverresolved()))
4426 if ((runconclude or not list(ms.driverresolved()))
4427 and not list(ms.unresolved())):
4427 and not list(ms.unresolved())):
4428 proceed = mergemod.driverconclude(repo, ms, wctx)
4428 proceed = mergemod.driverconclude(repo, ms, wctx)
4429 ms.commit()
4429 ms.commit()
4430 if not proceed:
4430 if not proceed:
4431 return 1
4431 return 1
4432
4432
4433 # Nudge users into finishing an unfinished operation
4433 # Nudge users into finishing an unfinished operation
4434 unresolvedf = list(ms.unresolved())
4434 unresolvedf = list(ms.unresolved())
4435 driverresolvedf = list(ms.driverresolved())
4435 driverresolvedf = list(ms.driverresolved())
4436 if not unresolvedf and not driverresolvedf:
4436 if not unresolvedf and not driverresolvedf:
4437 ui.status(_('(no more unresolved files)\n'))
4437 ui.status(_('(no more unresolved files)\n'))
4438 cmdutil.checkafterresolved(repo)
4438 cmdutil.checkafterresolved(repo)
4439 elif not unresolvedf:
4439 elif not unresolvedf:
4440 ui.status(_('(no more unresolved files -- '
4440 ui.status(_('(no more unresolved files -- '
4441 'run "hg resolve --all" to conclude)\n'))
4441 'run "hg resolve --all" to conclude)\n'))
4442
4442
4443 return ret
4443 return ret
4444
4444
4445 @command('revert',
4445 @command('revert',
4446 [('a', 'all', None, _('revert all changes when no arguments given')),
4446 [('a', 'all', None, _('revert all changes when no arguments given')),
4447 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4447 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4448 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4448 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4449 ('C', 'no-backup', None, _('do not save backup copies of files')),
4449 ('C', 'no-backup', None, _('do not save backup copies of files')),
4450 ('i', 'interactive', None,
4450 ('i', 'interactive', None,
4451 _('interactively select the changes (EXPERIMENTAL)')),
4451 _('interactively select the changes (EXPERIMENTAL)')),
4452 ] + walkopts + dryrunopts,
4452 ] + walkopts + dryrunopts,
4453 _('[OPTION]... [-r REV] [NAME]...'))
4453 _('[OPTION]... [-r REV] [NAME]...'))
4454 def revert(ui, repo, *pats, **opts):
4454 def revert(ui, repo, *pats, **opts):
4455 """restore files to their checkout state
4455 """restore files to their checkout state
4456
4456
4457 .. note::
4457 .. note::
4458
4458
4459 To check out earlier revisions, you should use :hg:`update REV`.
4459 To check out earlier revisions, you should use :hg:`update REV`.
4460 To cancel an uncommitted merge (and lose your changes),
4460 To cancel an uncommitted merge (and lose your changes),
4461 use :hg:`update --clean .`.
4461 use :hg:`update --clean .`.
4462
4462
4463 With no revision specified, revert the specified files or directories
4463 With no revision specified, revert the specified files or directories
4464 to the contents they had in the parent of the working directory.
4464 to the contents they had in the parent of the working directory.
4465 This restores the contents of files to an unmodified
4465 This restores the contents of files to an unmodified
4466 state and unschedules adds, removes, copies, and renames. If the
4466 state and unschedules adds, removes, copies, and renames. If the
4467 working directory has two parents, you must explicitly specify a
4467 working directory has two parents, you must explicitly specify a
4468 revision.
4468 revision.
4469
4469
4470 Using the -r/--rev or -d/--date options, revert the given files or
4470 Using the -r/--rev or -d/--date options, revert the given files or
4471 directories to their states as of a specific revision. Because
4471 directories to their states as of a specific revision. Because
4472 revert does not change the working directory parents, this will
4472 revert does not change the working directory parents, this will
4473 cause these files to appear modified. This can be helpful to "back
4473 cause these files to appear modified. This can be helpful to "back
4474 out" some or all of an earlier change. See :hg:`backout` for a
4474 out" some or all of an earlier change. See :hg:`backout` for a
4475 related method.
4475 related method.
4476
4476
4477 Modified files are saved with a .orig suffix before reverting.
4477 Modified files are saved with a .orig suffix before reverting.
4478 To disable these backups, use --no-backup. It is possible to store
4478 To disable these backups, use --no-backup. It is possible to store
4479 the backup files in a custom directory relative to the root of the
4479 the backup files in a custom directory relative to the root of the
4480 repository by setting the ``ui.origbackuppath`` configuration
4480 repository by setting the ``ui.origbackuppath`` configuration
4481 option.
4481 option.
4482
4482
4483 See :hg:`help dates` for a list of formats valid for -d/--date.
4483 See :hg:`help dates` for a list of formats valid for -d/--date.
4484
4484
4485 See :hg:`help backout` for a way to reverse the effect of an
4485 See :hg:`help backout` for a way to reverse the effect of an
4486 earlier changeset.
4486 earlier changeset.
4487
4487
4488 Returns 0 on success.
4488 Returns 0 on success.
4489 """
4489 """
4490
4490
4491 if opts.get("date"):
4491 if opts.get("date"):
4492 if opts.get("rev"):
4492 if opts.get("rev"):
4493 raise error.Abort(_("you can't specify a revision and a date"))
4493 raise error.Abort(_("you can't specify a revision and a date"))
4494 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4494 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4495
4495
4496 parent, p2 = repo.dirstate.parents()
4496 parent, p2 = repo.dirstate.parents()
4497 if not opts.get('rev') and p2 != nullid:
4497 if not opts.get('rev') and p2 != nullid:
4498 # revert after merge is a trap for new users (issue2915)
4498 # revert after merge is a trap for new users (issue2915)
4499 raise error.Abort(_('uncommitted merge with no revision specified'),
4499 raise error.Abort(_('uncommitted merge with no revision specified'),
4500 hint=_("use 'hg update' or see 'hg help revert'"))
4500 hint=_("use 'hg update' or see 'hg help revert'"))
4501
4501
4502 ctx = scmutil.revsingle(repo, opts.get('rev'))
4502 ctx = scmutil.revsingle(repo, opts.get('rev'))
4503
4503
4504 if (not (pats or opts.get('include') or opts.get('exclude') or
4504 if (not (pats or opts.get('include') or opts.get('exclude') or
4505 opts.get('all') or opts.get('interactive'))):
4505 opts.get('all') or opts.get('interactive'))):
4506 msg = _("no files or directories specified")
4506 msg = _("no files or directories specified")
4507 if p2 != nullid:
4507 if p2 != nullid:
4508 hint = _("uncommitted merge, use --all to discard all changes,"
4508 hint = _("uncommitted merge, use --all to discard all changes,"
4509 " or 'hg update -C .' to abort the merge")
4509 " or 'hg update -C .' to abort the merge")
4510 raise error.Abort(msg, hint=hint)
4510 raise error.Abort(msg, hint=hint)
4511 dirty = any(repo.status())
4511 dirty = any(repo.status())
4512 node = ctx.node()
4512 node = ctx.node()
4513 if node != parent:
4513 if node != parent:
4514 if dirty:
4514 if dirty:
4515 hint = _("uncommitted changes, use --all to discard all"
4515 hint = _("uncommitted changes, use --all to discard all"
4516 " changes, or 'hg update %s' to update") % ctx.rev()
4516 " changes, or 'hg update %s' to update") % ctx.rev()
4517 else:
4517 else:
4518 hint = _("use --all to revert all files,"
4518 hint = _("use --all to revert all files,"
4519 " or 'hg update %s' to update") % ctx.rev()
4519 " or 'hg update %s' to update") % ctx.rev()
4520 elif dirty:
4520 elif dirty:
4521 hint = _("uncommitted changes, use --all to discard all changes")
4521 hint = _("uncommitted changes, use --all to discard all changes")
4522 else:
4522 else:
4523 hint = _("use --all to revert all files")
4523 hint = _("use --all to revert all files")
4524 raise error.Abort(msg, hint=hint)
4524 raise error.Abort(msg, hint=hint)
4525
4525
4526 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4526 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4527
4527
4528 @command('rollback', dryrunopts +
4528 @command('rollback', dryrunopts +
4529 [('f', 'force', False, _('ignore safety measures'))])
4529 [('f', 'force', False, _('ignore safety measures'))])
4530 def rollback(ui, repo, **opts):
4530 def rollback(ui, repo, **opts):
4531 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4531 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4532
4532
4533 Please use :hg:`commit --amend` instead of rollback to correct
4533 Please use :hg:`commit --amend` instead of rollback to correct
4534 mistakes in the last commit.
4534 mistakes in the last commit.
4535
4535
4536 This command should be used with care. There is only one level of
4536 This command should be used with care. There is only one level of
4537 rollback, and there is no way to undo a rollback. It will also
4537 rollback, and there is no way to undo a rollback. It will also
4538 restore the dirstate at the time of the last transaction, losing
4538 restore the dirstate at the time of the last transaction, losing
4539 any dirstate changes since that time. This command does not alter
4539 any dirstate changes since that time. This command does not alter
4540 the working directory.
4540 the working directory.
4541
4541
4542 Transactions are used to encapsulate the effects of all commands
4542 Transactions are used to encapsulate the effects of all commands
4543 that create new changesets or propagate existing changesets into a
4543 that create new changesets or propagate existing changesets into a
4544 repository.
4544 repository.
4545
4545
4546 .. container:: verbose
4546 .. container:: verbose
4547
4547
4548 For example, the following commands are transactional, and their
4548 For example, the following commands are transactional, and their
4549 effects can be rolled back:
4549 effects can be rolled back:
4550
4550
4551 - commit
4551 - commit
4552 - import
4552 - import
4553 - pull
4553 - pull
4554 - push (with this repository as the destination)
4554 - push (with this repository as the destination)
4555 - unbundle
4555 - unbundle
4556
4556
4557 To avoid permanent data loss, rollback will refuse to rollback a
4557 To avoid permanent data loss, rollback will refuse to rollback a
4558 commit transaction if it isn't checked out. Use --force to
4558 commit transaction if it isn't checked out. Use --force to
4559 override this protection.
4559 override this protection.
4560
4560
4561 The rollback command can be entirely disabled by setting the
4561 The rollback command can be entirely disabled by setting the
4562 ``ui.rollback`` configuration setting to false. If you're here
4562 ``ui.rollback`` configuration setting to false. If you're here
4563 because you want to use rollback and it's disabled, you can
4563 because you want to use rollback and it's disabled, you can
4564 re-enable the command by setting ``ui.rollback`` to true.
4564 re-enable the command by setting ``ui.rollback`` to true.
4565
4565
4566 This command is not intended for use on public repositories. Once
4566 This command is not intended for use on public repositories. Once
4567 changes are visible for pull by other users, rolling a transaction
4567 changes are visible for pull by other users, rolling a transaction
4568 back locally is ineffective (someone else may already have pulled
4568 back locally is ineffective (someone else may already have pulled
4569 the changes). Furthermore, a race is possible with readers of the
4569 the changes). Furthermore, a race is possible with readers of the
4570 repository; for example an in-progress pull from the repository
4570 repository; for example an in-progress pull from the repository
4571 may fail if a rollback is performed.
4571 may fail if a rollback is performed.
4572
4572
4573 Returns 0 on success, 1 if no rollback data is available.
4573 Returns 0 on success, 1 if no rollback data is available.
4574 """
4574 """
4575 if not ui.configbool('ui', 'rollback', True):
4575 if not ui.configbool('ui', 'rollback', True):
4576 raise error.Abort(_('rollback is disabled because it is unsafe'),
4576 raise error.Abort(_('rollback is disabled because it is unsafe'),
4577 hint=('see `hg help -v rollback` for information'))
4577 hint=('see `hg help -v rollback` for information'))
4578 return repo.rollback(dryrun=opts.get('dry_run'),
4578 return repo.rollback(dryrun=opts.get('dry_run'),
4579 force=opts.get('force'))
4579 force=opts.get('force'))
4580
4580
4581 @command('root', [])
4581 @command('root', [])
4582 def root(ui, repo):
4582 def root(ui, repo):
4583 """print the root (top) of the current working directory
4583 """print the root (top) of the current working directory
4584
4584
4585 Print the root directory of the current repository.
4585 Print the root directory of the current repository.
4586
4586
4587 Returns 0 on success.
4587 Returns 0 on success.
4588 """
4588 """
4589 ui.write(repo.root + "\n")
4589 ui.write(repo.root + "\n")
4590
4590
4591 @command('^serve',
4591 @command('^serve',
4592 [('A', 'accesslog', '', _('name of access log file to write to'),
4592 [('A', 'accesslog', '', _('name of access log file to write to'),
4593 _('FILE')),
4593 _('FILE')),
4594 ('d', 'daemon', None, _('run server in background')),
4594 ('d', 'daemon', None, _('run server in background')),
4595 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4595 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4596 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4596 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4597 # use string type, then we can check if something was passed
4597 # use string type, then we can check if something was passed
4598 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4598 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4599 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4599 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4600 _('ADDR')),
4600 _('ADDR')),
4601 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4601 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4602 _('PREFIX')),
4602 _('PREFIX')),
4603 ('n', 'name', '',
4603 ('n', 'name', '',
4604 _('name to show in web pages (default: working directory)'), _('NAME')),
4604 _('name to show in web pages (default: working directory)'), _('NAME')),
4605 ('', 'web-conf', '',
4605 ('', 'web-conf', '',
4606 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4606 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4607 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4607 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4608 _('FILE')),
4608 _('FILE')),
4609 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4609 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4610 ('', 'stdio', None, _('for remote clients')),
4610 ('', 'stdio', None, _('for remote clients')),
4611 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4611 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4612 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4612 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4613 ('', 'style', '', _('template style to use'), _('STYLE')),
4613 ('', 'style', '', _('template style to use'), _('STYLE')),
4614 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4614 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4615 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4615 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4616 _('[OPTION]...'),
4616 _('[OPTION]...'),
4617 optionalrepo=True)
4617 optionalrepo=True)
4618 def serve(ui, repo, **opts):
4618 def serve(ui, repo, **opts):
4619 """start stand-alone webserver
4619 """start stand-alone webserver
4620
4620
4621 Start a local HTTP repository browser and pull server. You can use
4621 Start a local HTTP repository browser and pull server. You can use
4622 this for ad-hoc sharing and browsing of repositories. It is
4622 this for ad-hoc sharing and browsing of repositories. It is
4623 recommended to use a real web server to serve a repository for
4623 recommended to use a real web server to serve a repository for
4624 longer periods of time.
4624 longer periods of time.
4625
4625
4626 Please note that the server does not implement access control.
4626 Please note that the server does not implement access control.
4627 This means that, by default, anybody can read from the server and
4627 This means that, by default, anybody can read from the server and
4628 nobody can write to it by default. Set the ``web.allow_push``
4628 nobody can write to it by default. Set the ``web.allow_push``
4629 option to ``*`` to allow everybody to push to the server. You
4629 option to ``*`` to allow everybody to push to the server. You
4630 should use a real web server if you need to authenticate users.
4630 should use a real web server if you need to authenticate users.
4631
4631
4632 By default, the server logs accesses to stdout and errors to
4632 By default, the server logs accesses to stdout and errors to
4633 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4633 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4634 files.
4634 files.
4635
4635
4636 To have the server choose a free port number to listen on, specify
4636 To have the server choose a free port number to listen on, specify
4637 a port number of 0; in this case, the server will print the port
4637 a port number of 0; in this case, the server will print the port
4638 number it uses.
4638 number it uses.
4639
4639
4640 Returns 0 on success.
4640 Returns 0 on success.
4641 """
4641 """
4642
4642
4643 if opts["stdio"] and opts["cmdserver"]:
4643 if opts["stdio"] and opts["cmdserver"]:
4644 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4644 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4645
4645
4646 if opts["stdio"]:
4646 if opts["stdio"]:
4647 if repo is None:
4647 if repo is None:
4648 raise error.RepoError(_("there is no Mercurial repository here"
4648 raise error.RepoError(_("there is no Mercurial repository here"
4649 " (.hg not found)"))
4649 " (.hg not found)"))
4650 s = sshserver.sshserver(ui, repo)
4650 s = sshserver.sshserver(ui, repo)
4651 s.serve_forever()
4651 s.serve_forever()
4652
4652
4653 service = server.createservice(ui, repo, opts)
4653 service = server.createservice(ui, repo, opts)
4654 return server.runservice(opts, initfn=service.init, runfn=service.run)
4654 return server.runservice(opts, initfn=service.init, runfn=service.run)
4655
4655
4656 @command('^status|st',
4656 @command('^status|st',
4657 [('A', 'all', None, _('show status of all files')),
4657 [('A', 'all', None, _('show status of all files')),
4658 ('m', 'modified', None, _('show only modified files')),
4658 ('m', 'modified', None, _('show only modified files')),
4659 ('a', 'added', None, _('show only added files')),
4659 ('a', 'added', None, _('show only added files')),
4660 ('r', 'removed', None, _('show only removed files')),
4660 ('r', 'removed', None, _('show only removed files')),
4661 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4661 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4662 ('c', 'clean', None, _('show only files without changes')),
4662 ('c', 'clean', None, _('show only files without changes')),
4663 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4663 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4664 ('i', 'ignored', None, _('show only ignored files')),
4664 ('i', 'ignored', None, _('show only ignored files')),
4665 ('n', 'no-status', None, _('hide status prefix')),
4665 ('n', 'no-status', None, _('hide status prefix')),
4666 ('C', 'copies', None, _('show source of copied files')),
4666 ('C', 'copies', None, _('show source of copied files')),
4667 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4667 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4668 ('', 'rev', [], _('show difference from revision'), _('REV')),
4668 ('', 'rev', [], _('show difference from revision'), _('REV')),
4669 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4669 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4670 ] + walkopts + subrepoopts + formatteropts,
4670 ] + walkopts + subrepoopts + formatteropts,
4671 _('[OPTION]... [FILE]...'),
4671 _('[OPTION]... [FILE]...'),
4672 inferrepo=True)
4672 inferrepo=True)
4673 def status(ui, repo, *pats, **opts):
4673 def status(ui, repo, *pats, **opts):
4674 """show changed files in the working directory
4674 """show changed files in the working directory
4675
4675
4676 Show status of files in the repository. If names are given, only
4676 Show status of files in the repository. If names are given, only
4677 files that match are shown. Files that are clean or ignored or
4677 files that match are shown. Files that are clean or ignored or
4678 the source of a copy/move operation, are not listed unless
4678 the source of a copy/move operation, are not listed unless
4679 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4679 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4680 Unless options described with "show only ..." are given, the
4680 Unless options described with "show only ..." are given, the
4681 options -mardu are used.
4681 options -mardu are used.
4682
4682
4683 Option -q/--quiet hides untracked (unknown and ignored) files
4683 Option -q/--quiet hides untracked (unknown and ignored) files
4684 unless explicitly requested with -u/--unknown or -i/--ignored.
4684 unless explicitly requested with -u/--unknown or -i/--ignored.
4685
4685
4686 .. note::
4686 .. note::
4687
4687
4688 :hg:`status` may appear to disagree with diff if permissions have
4688 :hg:`status` may appear to disagree with diff if permissions have
4689 changed or a merge has occurred. The standard diff format does
4689 changed or a merge has occurred. The standard diff format does
4690 not report permission changes and diff only reports changes
4690 not report permission changes and diff only reports changes
4691 relative to one merge parent.
4691 relative to one merge parent.
4692
4692
4693 If one revision is given, it is used as the base revision.
4693 If one revision is given, it is used as the base revision.
4694 If two revisions are given, the differences between them are
4694 If two revisions are given, the differences between them are
4695 shown. The --change option can also be used as a shortcut to list
4695 shown. The --change option can also be used as a shortcut to list
4696 the changed files of a revision from its first parent.
4696 the changed files of a revision from its first parent.
4697
4697
4698 The codes used to show the status of files are::
4698 The codes used to show the status of files are::
4699
4699
4700 M = modified
4700 M = modified
4701 A = added
4701 A = added
4702 R = removed
4702 R = removed
4703 C = clean
4703 C = clean
4704 ! = missing (deleted by non-hg command, but still tracked)
4704 ! = missing (deleted by non-hg command, but still tracked)
4705 ? = not tracked
4705 ? = not tracked
4706 I = ignored
4706 I = ignored
4707 = origin of the previous file (with --copies)
4707 = origin of the previous file (with --copies)
4708
4708
4709 .. container:: verbose
4709 .. container:: verbose
4710
4710
4711 Examples:
4711 Examples:
4712
4712
4713 - show changes in the working directory relative to a
4713 - show changes in the working directory relative to a
4714 changeset::
4714 changeset::
4715
4715
4716 hg status --rev 9353
4716 hg status --rev 9353
4717
4717
4718 - show changes in the working directory relative to the
4718 - show changes in the working directory relative to the
4719 current directory (see :hg:`help patterns` for more information)::
4719 current directory (see :hg:`help patterns` for more information)::
4720
4720
4721 hg status re:
4721 hg status re:
4722
4722
4723 - show all changes including copies in an existing changeset::
4723 - show all changes including copies in an existing changeset::
4724
4724
4725 hg status --copies --change 9353
4725 hg status --copies --change 9353
4726
4726
4727 - get a NUL separated list of added files, suitable for xargs::
4727 - get a NUL separated list of added files, suitable for xargs::
4728
4728
4729 hg status -an0
4729 hg status -an0
4730
4730
4731 Returns 0 on success.
4731 Returns 0 on success.
4732 """
4732 """
4733
4733
4734 revs = opts.get('rev')
4734 revs = opts.get('rev')
4735 change = opts.get('change')
4735 change = opts.get('change')
4736
4736
4737 if revs and change:
4737 if revs and change:
4738 msg = _('cannot specify --rev and --change at the same time')
4738 msg = _('cannot specify --rev and --change at the same time')
4739 raise error.Abort(msg)
4739 raise error.Abort(msg)
4740 elif change:
4740 elif change:
4741 node2 = scmutil.revsingle(repo, change, None).node()
4741 node2 = scmutil.revsingle(repo, change, None).node()
4742 node1 = repo[node2].p1().node()
4742 node1 = repo[node2].p1().node()
4743 else:
4743 else:
4744 node1, node2 = scmutil.revpair(repo, revs)
4744 node1, node2 = scmutil.revpair(repo, revs)
4745
4745
4746 if pats:
4746 if pats:
4747 cwd = repo.getcwd()
4747 cwd = repo.getcwd()
4748 else:
4748 else:
4749 cwd = ''
4749 cwd = ''
4750
4750
4751 if opts.get('print0'):
4751 if opts.get('print0'):
4752 end = '\0'
4752 end = '\0'
4753 else:
4753 else:
4754 end = '\n'
4754 end = '\n'
4755 copy = {}
4755 copy = {}
4756 states = 'modified added removed deleted unknown ignored clean'.split()
4756 states = 'modified added removed deleted unknown ignored clean'.split()
4757 show = [k for k in states if opts.get(k)]
4757 show = [k for k in states if opts.get(k)]
4758 if opts.get('all'):
4758 if opts.get('all'):
4759 show += ui.quiet and (states[:4] + ['clean']) or states
4759 show += ui.quiet and (states[:4] + ['clean']) or states
4760 if not show:
4760 if not show:
4761 if ui.quiet:
4761 if ui.quiet:
4762 show = states[:4]
4762 show = states[:4]
4763 else:
4763 else:
4764 show = states[:5]
4764 show = states[:5]
4765
4765
4766 m = scmutil.match(repo[node2], pats, opts)
4766 m = scmutil.match(repo[node2], pats, opts)
4767 stat = repo.status(node1, node2, m,
4767 stat = repo.status(node1, node2, m,
4768 'ignored' in show, 'clean' in show, 'unknown' in show,
4768 'ignored' in show, 'clean' in show, 'unknown' in show,
4769 opts.get('subrepos'))
4769 opts.get('subrepos'))
4770 changestates = zip(states, 'MAR!?IC', stat)
4770 changestates = zip(states, 'MAR!?IC', stat)
4771
4771
4772 if (opts.get('all') or opts.get('copies')
4772 if (opts.get('all') or opts.get('copies')
4773 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4773 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4774 copy = copies.pathcopies(repo[node1], repo[node2], m)
4774 copy = copies.pathcopies(repo[node1], repo[node2], m)
4775
4775
4776 fm = ui.formatter('status', opts)
4776 fm = ui.formatter('status', opts)
4777 fmt = '%s' + end
4777 fmt = '%s' + end
4778 showchar = not opts.get('no_status')
4778 showchar = not opts.get('no_status')
4779
4779
4780 for state, char, files in changestates:
4780 for state, char, files in changestates:
4781 if state in show:
4781 if state in show:
4782 label = 'status.' + state
4782 label = 'status.' + state
4783 for f in files:
4783 for f in files:
4784 fm.startitem()
4784 fm.startitem()
4785 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4785 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4786 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4786 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4787 if f in copy:
4787 if f in copy:
4788 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4788 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4789 label='status.copied')
4789 label='status.copied')
4790 fm.end()
4790 fm.end()
4791
4791
4792 @command('^summary|sum',
4792 @command('^summary|sum',
4793 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4793 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4794 def summary(ui, repo, **opts):
4794 def summary(ui, repo, **opts):
4795 """summarize working directory state
4795 """summarize working directory state
4796
4796
4797 This generates a brief summary of the working directory state,
4797 This generates a brief summary of the working directory state,
4798 including parents, branch, commit status, phase and available updates.
4798 including parents, branch, commit status, phase and available updates.
4799
4799
4800 With the --remote option, this will check the default paths for
4800 With the --remote option, this will check the default paths for
4801 incoming and outgoing changes. This can be time-consuming.
4801 incoming and outgoing changes. This can be time-consuming.
4802
4802
4803 Returns 0 on success.
4803 Returns 0 on success.
4804 """
4804 """
4805
4805
4806 ctx = repo[None]
4806 ctx = repo[None]
4807 parents = ctx.parents()
4807 parents = ctx.parents()
4808 pnode = parents[0].node()
4808 pnode = parents[0].node()
4809 marks = []
4809 marks = []
4810
4810
4811 ms = None
4811 ms = None
4812 try:
4812 try:
4813 ms = mergemod.mergestate.read(repo)
4813 ms = mergemod.mergestate.read(repo)
4814 except error.UnsupportedMergeRecords as e:
4814 except error.UnsupportedMergeRecords as e:
4815 s = ' '.join(e.recordtypes)
4815 s = ' '.join(e.recordtypes)
4816 ui.warn(
4816 ui.warn(
4817 _('warning: merge state has unsupported record types: %s\n') % s)
4817 _('warning: merge state has unsupported record types: %s\n') % s)
4818 unresolved = 0
4818 unresolved = 0
4819 else:
4819 else:
4820 unresolved = [f for f in ms if ms[f] == 'u']
4820 unresolved = [f for f in ms if ms[f] == 'u']
4821
4821
4822 for p in parents:
4822 for p in parents:
4823 # label with log.changeset (instead of log.parent) since this
4823 # label with log.changeset (instead of log.parent) since this
4824 # shows a working directory parent *changeset*:
4824 # shows a working directory parent *changeset*:
4825 # i18n: column positioning for "hg summary"
4825 # i18n: column positioning for "hg summary"
4826 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4826 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4827 label=cmdutil._changesetlabels(p))
4827 label=cmdutil._changesetlabels(p))
4828 ui.write(' '.join(p.tags()), label='log.tag')
4828 ui.write(' '.join(p.tags()), label='log.tag')
4829 if p.bookmarks():
4829 if p.bookmarks():
4830 marks.extend(p.bookmarks())
4830 marks.extend(p.bookmarks())
4831 if p.rev() == -1:
4831 if p.rev() == -1:
4832 if not len(repo):
4832 if not len(repo):
4833 ui.write(_(' (empty repository)'))
4833 ui.write(_(' (empty repository)'))
4834 else:
4834 else:
4835 ui.write(_(' (no revision checked out)'))
4835 ui.write(_(' (no revision checked out)'))
4836 if p.troubled():
4836 if p.troubled():
4837 ui.write(' ('
4837 ui.write(' ('
4838 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4838 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4839 for trouble in p.troubles())
4839 for trouble in p.troubles())
4840 + ')')
4840 + ')')
4841 ui.write('\n')
4841 ui.write('\n')
4842 if p.description():
4842 if p.description():
4843 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4843 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4844 label='log.summary')
4844 label='log.summary')
4845
4845
4846 branch = ctx.branch()
4846 branch = ctx.branch()
4847 bheads = repo.branchheads(branch)
4847 bheads = repo.branchheads(branch)
4848 # i18n: column positioning for "hg summary"
4848 # i18n: column positioning for "hg summary"
4849 m = _('branch: %s\n') % branch
4849 m = _('branch: %s\n') % branch
4850 if branch != 'default':
4850 if branch != 'default':
4851 ui.write(m, label='log.branch')
4851 ui.write(m, label='log.branch')
4852 else:
4852 else:
4853 ui.status(m, label='log.branch')
4853 ui.status(m, label='log.branch')
4854
4854
4855 if marks:
4855 if marks:
4856 active = repo._activebookmark
4856 active = repo._activebookmark
4857 # i18n: column positioning for "hg summary"
4857 # i18n: column positioning for "hg summary"
4858 ui.write(_('bookmarks:'), label='log.bookmark')
4858 ui.write(_('bookmarks:'), label='log.bookmark')
4859 if active is not None:
4859 if active is not None:
4860 if active in marks:
4860 if active in marks:
4861 ui.write(' *' + active, label=activebookmarklabel)
4861 ui.write(' *' + active, label=activebookmarklabel)
4862 marks.remove(active)
4862 marks.remove(active)
4863 else:
4863 else:
4864 ui.write(' [%s]' % active, label=activebookmarklabel)
4864 ui.write(' [%s]' % active, label=activebookmarklabel)
4865 for m in marks:
4865 for m in marks:
4866 ui.write(' ' + m, label='log.bookmark')
4866 ui.write(' ' + m, label='log.bookmark')
4867 ui.write('\n', label='log.bookmark')
4867 ui.write('\n', label='log.bookmark')
4868
4868
4869 status = repo.status(unknown=True)
4869 status = repo.status(unknown=True)
4870
4870
4871 c = repo.dirstate.copies()
4871 c = repo.dirstate.copies()
4872 copied, renamed = [], []
4872 copied, renamed = [], []
4873 for d, s in c.iteritems():
4873 for d, s in c.iteritems():
4874 if s in status.removed:
4874 if s in status.removed:
4875 status.removed.remove(s)
4875 status.removed.remove(s)
4876 renamed.append(d)
4876 renamed.append(d)
4877 else:
4877 else:
4878 copied.append(d)
4878 copied.append(d)
4879 if d in status.added:
4879 if d in status.added:
4880 status.added.remove(d)
4880 status.added.remove(d)
4881
4881
4882 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4882 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4883
4883
4884 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4884 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4885 (ui.label(_('%d added'), 'status.added'), status.added),
4885 (ui.label(_('%d added'), 'status.added'), status.added),
4886 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4886 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4887 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4887 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4888 (ui.label(_('%d copied'), 'status.copied'), copied),
4888 (ui.label(_('%d copied'), 'status.copied'), copied),
4889 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4889 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4890 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4890 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4891 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4891 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4892 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4892 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4893 t = []
4893 t = []
4894 for l, s in labels:
4894 for l, s in labels:
4895 if s:
4895 if s:
4896 t.append(l % len(s))
4896 t.append(l % len(s))
4897
4897
4898 t = ', '.join(t)
4898 t = ', '.join(t)
4899 cleanworkdir = False
4899 cleanworkdir = False
4900
4900
4901 if repo.vfs.exists('graftstate'):
4901 if repo.vfs.exists('graftstate'):
4902 t += _(' (graft in progress)')
4902 t += _(' (graft in progress)')
4903 if repo.vfs.exists('updatestate'):
4903 if repo.vfs.exists('updatestate'):
4904 t += _(' (interrupted update)')
4904 t += _(' (interrupted update)')
4905 elif len(parents) > 1:
4905 elif len(parents) > 1:
4906 t += _(' (merge)')
4906 t += _(' (merge)')
4907 elif branch != parents[0].branch():
4907 elif branch != parents[0].branch():
4908 t += _(' (new branch)')
4908 t += _(' (new branch)')
4909 elif (parents[0].closesbranch() and
4909 elif (parents[0].closesbranch() and
4910 pnode in repo.branchheads(branch, closed=True)):
4910 pnode in repo.branchheads(branch, closed=True)):
4911 t += _(' (head closed)')
4911 t += _(' (head closed)')
4912 elif not (status.modified or status.added or status.removed or renamed or
4912 elif not (status.modified or status.added or status.removed or renamed or
4913 copied or subs):
4913 copied or subs):
4914 t += _(' (clean)')
4914 t += _(' (clean)')
4915 cleanworkdir = True
4915 cleanworkdir = True
4916 elif pnode not in bheads:
4916 elif pnode not in bheads:
4917 t += _(' (new branch head)')
4917 t += _(' (new branch head)')
4918
4918
4919 if parents:
4919 if parents:
4920 pendingphase = max(p.phase() for p in parents)
4920 pendingphase = max(p.phase() for p in parents)
4921 else:
4921 else:
4922 pendingphase = phases.public
4922 pendingphase = phases.public
4923
4923
4924 if pendingphase > phases.newcommitphase(ui):
4924 if pendingphase > phases.newcommitphase(ui):
4925 t += ' (%s)' % phases.phasenames[pendingphase]
4925 t += ' (%s)' % phases.phasenames[pendingphase]
4926
4926
4927 if cleanworkdir:
4927 if cleanworkdir:
4928 # i18n: column positioning for "hg summary"
4928 # i18n: column positioning for "hg summary"
4929 ui.status(_('commit: %s\n') % t.strip())
4929 ui.status(_('commit: %s\n') % t.strip())
4930 else:
4930 else:
4931 # i18n: column positioning for "hg summary"
4931 # i18n: column positioning for "hg summary"
4932 ui.write(_('commit: %s\n') % t.strip())
4932 ui.write(_('commit: %s\n') % t.strip())
4933
4933
4934 # all ancestors of branch heads - all ancestors of parent = new csets
4934 # all ancestors of branch heads - all ancestors of parent = new csets
4935 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4935 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4936 bheads))
4936 bheads))
4937
4937
4938 if new == 0:
4938 if new == 0:
4939 # i18n: column positioning for "hg summary"
4939 # i18n: column positioning for "hg summary"
4940 ui.status(_('update: (current)\n'))
4940 ui.status(_('update: (current)\n'))
4941 elif pnode not in bheads:
4941 elif pnode not in bheads:
4942 # i18n: column positioning for "hg summary"
4942 # i18n: column positioning for "hg summary"
4943 ui.write(_('update: %d new changesets (update)\n') % new)
4943 ui.write(_('update: %d new changesets (update)\n') % new)
4944 else:
4944 else:
4945 # i18n: column positioning for "hg summary"
4945 # i18n: column positioning for "hg summary"
4946 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4946 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4947 (new, len(bheads)))
4947 (new, len(bheads)))
4948
4948
4949 t = []
4949 t = []
4950 draft = len(repo.revs('draft()'))
4950 draft = len(repo.revs('draft()'))
4951 if draft:
4951 if draft:
4952 t.append(_('%d draft') % draft)
4952 t.append(_('%d draft') % draft)
4953 secret = len(repo.revs('secret()'))
4953 secret = len(repo.revs('secret()'))
4954 if secret:
4954 if secret:
4955 t.append(_('%d secret') % secret)
4955 t.append(_('%d secret') % secret)
4956
4956
4957 if draft or secret:
4957 if draft or secret:
4958 ui.status(_('phases: %s\n') % ', '.join(t))
4958 ui.status(_('phases: %s\n') % ', '.join(t))
4959
4959
4960 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4960 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4961 for trouble in ("unstable", "divergent", "bumped"):
4961 for trouble in ("unstable", "divergent", "bumped"):
4962 numtrouble = len(repo.revs(trouble + "()"))
4962 numtrouble = len(repo.revs(trouble + "()"))
4963 # We write all the possibilities to ease translation
4963 # We write all the possibilities to ease translation
4964 troublemsg = {
4964 troublemsg = {
4965 "unstable": _("unstable: %d changesets"),
4965 "unstable": _("unstable: %d changesets"),
4966 "divergent": _("divergent: %d changesets"),
4966 "divergent": _("divergent: %d changesets"),
4967 "bumped": _("bumped: %d changesets"),
4967 "bumped": _("bumped: %d changesets"),
4968 }
4968 }
4969 if numtrouble > 0:
4969 if numtrouble > 0:
4970 ui.status(troublemsg[trouble] % numtrouble + "\n")
4970 ui.status(troublemsg[trouble] % numtrouble + "\n")
4971
4971
4972 cmdutil.summaryhooks(ui, repo)
4972 cmdutil.summaryhooks(ui, repo)
4973
4973
4974 if opts.get('remote'):
4974 if opts.get('remote'):
4975 needsincoming, needsoutgoing = True, True
4975 needsincoming, needsoutgoing = True, True
4976 else:
4976 else:
4977 needsincoming, needsoutgoing = False, False
4977 needsincoming, needsoutgoing = False, False
4978 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
4978 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
4979 if i:
4979 if i:
4980 needsincoming = True
4980 needsincoming = True
4981 if o:
4981 if o:
4982 needsoutgoing = True
4982 needsoutgoing = True
4983 if not needsincoming and not needsoutgoing:
4983 if not needsincoming and not needsoutgoing:
4984 return
4984 return
4985
4985
4986 def getincoming():
4986 def getincoming():
4987 source, branches = hg.parseurl(ui.expandpath('default'))
4987 source, branches = hg.parseurl(ui.expandpath('default'))
4988 sbranch = branches[0]
4988 sbranch = branches[0]
4989 try:
4989 try:
4990 other = hg.peer(repo, {}, source)
4990 other = hg.peer(repo, {}, source)
4991 except error.RepoError:
4991 except error.RepoError:
4992 if opts.get('remote'):
4992 if opts.get('remote'):
4993 raise
4993 raise
4994 return source, sbranch, None, None, None
4994 return source, sbranch, None, None, None
4995 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
4995 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
4996 if revs:
4996 if revs:
4997 revs = [other.lookup(rev) for rev in revs]
4997 revs = [other.lookup(rev) for rev in revs]
4998 ui.debug('comparing with %s\n' % util.hidepassword(source))
4998 ui.debug('comparing with %s\n' % util.hidepassword(source))
4999 repo.ui.pushbuffer()
4999 repo.ui.pushbuffer()
5000 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5000 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5001 repo.ui.popbuffer()
5001 repo.ui.popbuffer()
5002 return source, sbranch, other, commoninc, commoninc[1]
5002 return source, sbranch, other, commoninc, commoninc[1]
5003
5003
5004 if needsincoming:
5004 if needsincoming:
5005 source, sbranch, sother, commoninc, incoming = getincoming()
5005 source, sbranch, sother, commoninc, incoming = getincoming()
5006 else:
5006 else:
5007 source = sbranch = sother = commoninc = incoming = None
5007 source = sbranch = sother = commoninc = incoming = None
5008
5008
5009 def getoutgoing():
5009 def getoutgoing():
5010 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5010 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5011 dbranch = branches[0]
5011 dbranch = branches[0]
5012 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5012 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5013 if source != dest:
5013 if source != dest:
5014 try:
5014 try:
5015 dother = hg.peer(repo, {}, dest)
5015 dother = hg.peer(repo, {}, dest)
5016 except error.RepoError:
5016 except error.RepoError:
5017 if opts.get('remote'):
5017 if opts.get('remote'):
5018 raise
5018 raise
5019 return dest, dbranch, None, None
5019 return dest, dbranch, None, None
5020 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5020 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5021 elif sother is None:
5021 elif sother is None:
5022 # there is no explicit destination peer, but source one is invalid
5022 # there is no explicit destination peer, but source one is invalid
5023 return dest, dbranch, None, None
5023 return dest, dbranch, None, None
5024 else:
5024 else:
5025 dother = sother
5025 dother = sother
5026 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5026 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5027 common = None
5027 common = None
5028 else:
5028 else:
5029 common = commoninc
5029 common = commoninc
5030 if revs:
5030 if revs:
5031 revs = [repo.lookup(rev) for rev in revs]
5031 revs = [repo.lookup(rev) for rev in revs]
5032 repo.ui.pushbuffer()
5032 repo.ui.pushbuffer()
5033 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5033 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5034 commoninc=common)
5034 commoninc=common)
5035 repo.ui.popbuffer()
5035 repo.ui.popbuffer()
5036 return dest, dbranch, dother, outgoing
5036 return dest, dbranch, dother, outgoing
5037
5037
5038 if needsoutgoing:
5038 if needsoutgoing:
5039 dest, dbranch, dother, outgoing = getoutgoing()
5039 dest, dbranch, dother, outgoing = getoutgoing()
5040 else:
5040 else:
5041 dest = dbranch = dother = outgoing = None
5041 dest = dbranch = dother = outgoing = None
5042
5042
5043 if opts.get('remote'):
5043 if opts.get('remote'):
5044 t = []
5044 t = []
5045 if incoming:
5045 if incoming:
5046 t.append(_('1 or more incoming'))
5046 t.append(_('1 or more incoming'))
5047 o = outgoing.missing
5047 o = outgoing.missing
5048 if o:
5048 if o:
5049 t.append(_('%d outgoing') % len(o))
5049 t.append(_('%d outgoing') % len(o))
5050 other = dother or sother
5050 other = dother or sother
5051 if 'bookmarks' in other.listkeys('namespaces'):
5051 if 'bookmarks' in other.listkeys('namespaces'):
5052 counts = bookmarks.summary(repo, other)
5052 counts = bookmarks.summary(repo, other)
5053 if counts[0] > 0:
5053 if counts[0] > 0:
5054 t.append(_('%d incoming bookmarks') % counts[0])
5054 t.append(_('%d incoming bookmarks') % counts[0])
5055 if counts[1] > 0:
5055 if counts[1] > 0:
5056 t.append(_('%d outgoing bookmarks') % counts[1])
5056 t.append(_('%d outgoing bookmarks') % counts[1])
5057
5057
5058 if t:
5058 if t:
5059 # i18n: column positioning for "hg summary"
5059 # i18n: column positioning for "hg summary"
5060 ui.write(_('remote: %s\n') % (', '.join(t)))
5060 ui.write(_('remote: %s\n') % (', '.join(t)))
5061 else:
5061 else:
5062 # i18n: column positioning for "hg summary"
5062 # i18n: column positioning for "hg summary"
5063 ui.status(_('remote: (synced)\n'))
5063 ui.status(_('remote: (synced)\n'))
5064
5064
5065 cmdutil.summaryremotehooks(ui, repo, opts,
5065 cmdutil.summaryremotehooks(ui, repo, opts,
5066 ((source, sbranch, sother, commoninc),
5066 ((source, sbranch, sother, commoninc),
5067 (dest, dbranch, dother, outgoing)))
5067 (dest, dbranch, dother, outgoing)))
5068
5068
5069 @command('tag',
5069 @command('tag',
5070 [('f', 'force', None, _('force tag')),
5070 [('f', 'force', None, _('force tag')),
5071 ('l', 'local', None, _('make the tag local')),
5071 ('l', 'local', None, _('make the tag local')),
5072 ('r', 'rev', '', _('revision to tag'), _('REV')),
5072 ('r', 'rev', '', _('revision to tag'), _('REV')),
5073 ('', 'remove', None, _('remove a tag')),
5073 ('', 'remove', None, _('remove a tag')),
5074 # -l/--local is already there, commitopts cannot be used
5074 # -l/--local is already there, commitopts cannot be used
5075 ('e', 'edit', None, _('invoke editor on commit messages')),
5075 ('e', 'edit', None, _('invoke editor on commit messages')),
5076 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5076 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5077 ] + commitopts2,
5077 ] + commitopts2,
5078 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5078 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5079 def tag(ui, repo, name1, *names, **opts):
5079 def tag(ui, repo, name1, *names, **opts):
5080 """add one or more tags for the current or given revision
5080 """add one or more tags for the current or given revision
5081
5081
5082 Name a particular revision using <name>.
5082 Name a particular revision using <name>.
5083
5083
5084 Tags are used to name particular revisions of the repository and are
5084 Tags are used to name particular revisions of the repository and are
5085 very useful to compare different revisions, to go back to significant
5085 very useful to compare different revisions, to go back to significant
5086 earlier versions or to mark branch points as releases, etc. Changing
5086 earlier versions or to mark branch points as releases, etc. Changing
5087 an existing tag is normally disallowed; use -f/--force to override.
5087 an existing tag is normally disallowed; use -f/--force to override.
5088
5088
5089 If no revision is given, the parent of the working directory is
5089 If no revision is given, the parent of the working directory is
5090 used.
5090 used.
5091
5091
5092 To facilitate version control, distribution, and merging of tags,
5092 To facilitate version control, distribution, and merging of tags,
5093 they are stored as a file named ".hgtags" which is managed similarly
5093 they are stored as a file named ".hgtags" which is managed similarly
5094 to other project files and can be hand-edited if necessary. This
5094 to other project files and can be hand-edited if necessary. This
5095 also means that tagging creates a new commit. The file
5095 also means that tagging creates a new commit. The file
5096 ".hg/localtags" is used for local tags (not shared among
5096 ".hg/localtags" is used for local tags (not shared among
5097 repositories).
5097 repositories).
5098
5098
5099 Tag commits are usually made at the head of a branch. If the parent
5099 Tag commits are usually made at the head of a branch. If the parent
5100 of the working directory is not a branch head, :hg:`tag` aborts; use
5100 of the working directory is not a branch head, :hg:`tag` aborts; use
5101 -f/--force to force the tag commit to be based on a non-head
5101 -f/--force to force the tag commit to be based on a non-head
5102 changeset.
5102 changeset.
5103
5103
5104 See :hg:`help dates` for a list of formats valid for -d/--date.
5104 See :hg:`help dates` for a list of formats valid for -d/--date.
5105
5105
5106 Since tag names have priority over branch names during revision
5106 Since tag names have priority over branch names during revision
5107 lookup, using an existing branch name as a tag name is discouraged.
5107 lookup, using an existing branch name as a tag name is discouraged.
5108
5108
5109 Returns 0 on success.
5109 Returns 0 on success.
5110 """
5110 """
5111 wlock = lock = None
5111 wlock = lock = None
5112 try:
5112 try:
5113 wlock = repo.wlock()
5113 wlock = repo.wlock()
5114 lock = repo.lock()
5114 lock = repo.lock()
5115 rev_ = "."
5115 rev_ = "."
5116 names = [t.strip() for t in (name1,) + names]
5116 names = [t.strip() for t in (name1,) + names]
5117 if len(names) != len(set(names)):
5117 if len(names) != len(set(names)):
5118 raise error.Abort(_('tag names must be unique'))
5118 raise error.Abort(_('tag names must be unique'))
5119 for n in names:
5119 for n in names:
5120 scmutil.checknewlabel(repo, n, 'tag')
5120 scmutil.checknewlabel(repo, n, 'tag')
5121 if not n:
5121 if not n:
5122 raise error.Abort(_('tag names cannot consist entirely of '
5122 raise error.Abort(_('tag names cannot consist entirely of '
5123 'whitespace'))
5123 'whitespace'))
5124 if opts.get('rev') and opts.get('remove'):
5124 if opts.get('rev') and opts.get('remove'):
5125 raise error.Abort(_("--rev and --remove are incompatible"))
5125 raise error.Abort(_("--rev and --remove are incompatible"))
5126 if opts.get('rev'):
5126 if opts.get('rev'):
5127 rev_ = opts['rev']
5127 rev_ = opts['rev']
5128 message = opts.get('message')
5128 message = opts.get('message')
5129 if opts.get('remove'):
5129 if opts.get('remove'):
5130 if opts.get('local'):
5130 if opts.get('local'):
5131 expectedtype = 'local'
5131 expectedtype = 'local'
5132 else:
5132 else:
5133 expectedtype = 'global'
5133 expectedtype = 'global'
5134
5134
5135 for n in names:
5135 for n in names:
5136 if not repo.tagtype(n):
5136 if not repo.tagtype(n):
5137 raise error.Abort(_("tag '%s' does not exist") % n)
5137 raise error.Abort(_("tag '%s' does not exist") % n)
5138 if repo.tagtype(n) != expectedtype:
5138 if repo.tagtype(n) != expectedtype:
5139 if expectedtype == 'global':
5139 if expectedtype == 'global':
5140 raise error.Abort(_("tag '%s' is not a global tag") % n)
5140 raise error.Abort(_("tag '%s' is not a global tag") % n)
5141 else:
5141 else:
5142 raise error.Abort(_("tag '%s' is not a local tag") % n)
5142 raise error.Abort(_("tag '%s' is not a local tag") % n)
5143 rev_ = 'null'
5143 rev_ = 'null'
5144 if not message:
5144 if not message:
5145 # we don't translate commit messages
5145 # we don't translate commit messages
5146 message = 'Removed tag %s' % ', '.join(names)
5146 message = 'Removed tag %s' % ', '.join(names)
5147 elif not opts.get('force'):
5147 elif not opts.get('force'):
5148 for n in names:
5148 for n in names:
5149 if n in repo.tags():
5149 if n in repo.tags():
5150 raise error.Abort(_("tag '%s' already exists "
5150 raise error.Abort(_("tag '%s' already exists "
5151 "(use -f to force)") % n)
5151 "(use -f to force)") % n)
5152 if not opts.get('local'):
5152 if not opts.get('local'):
5153 p1, p2 = repo.dirstate.parents()
5153 p1, p2 = repo.dirstate.parents()
5154 if p2 != nullid:
5154 if p2 != nullid:
5155 raise error.Abort(_('uncommitted merge'))
5155 raise error.Abort(_('uncommitted merge'))
5156 bheads = repo.branchheads()
5156 bheads = repo.branchheads()
5157 if not opts.get('force') and bheads and p1 not in bheads:
5157 if not opts.get('force') and bheads and p1 not in bheads:
5158 raise error.Abort(_('working directory is not at a branch head '
5158 raise error.Abort(_('working directory is not at a branch head '
5159 '(use -f to force)'))
5159 '(use -f to force)'))
5160 r = scmutil.revsingle(repo, rev_).node()
5160 r = scmutil.revsingle(repo, rev_).node()
5161
5161
5162 if not message:
5162 if not message:
5163 # we don't translate commit messages
5163 # we don't translate commit messages
5164 message = ('Added tag %s for changeset %s' %
5164 message = ('Added tag %s for changeset %s' %
5165 (', '.join(names), short(r)))
5165 (', '.join(names), short(r)))
5166
5166
5167 date = opts.get('date')
5167 date = opts.get('date')
5168 if date:
5168 if date:
5169 date = util.parsedate(date)
5169 date = util.parsedate(date)
5170
5170
5171 if opts.get('remove'):
5171 if opts.get('remove'):
5172 editform = 'tag.remove'
5172 editform = 'tag.remove'
5173 else:
5173 else:
5174 editform = 'tag.add'
5174 editform = 'tag.add'
5175 editor = cmdutil.getcommiteditor(editform=editform, **opts)
5175 editor = cmdutil.getcommiteditor(editform=editform, **opts)
5176
5176
5177 # don't allow tagging the null rev
5177 # don't allow tagging the null rev
5178 if (not opts.get('remove') and
5178 if (not opts.get('remove') and
5179 scmutil.revsingle(repo, rev_).rev() == nullrev):
5179 scmutil.revsingle(repo, rev_).rev() == nullrev):
5180 raise error.Abort(_("cannot tag null revision"))
5180 raise error.Abort(_("cannot tag null revision"))
5181
5181
5182 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
5182 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
5183 editor=editor)
5183 editor=editor)
5184 finally:
5184 finally:
5185 release(lock, wlock)
5185 release(lock, wlock)
5186
5186
5187 @command('tags', formatteropts, '')
5187 @command('tags', formatteropts, '')
5188 def tags(ui, repo, **opts):
5188 def tags(ui, repo, **opts):
5189 """list repository tags
5189 """list repository tags
5190
5190
5191 This lists both regular and local tags. When the -v/--verbose
5191 This lists both regular and local tags. When the -v/--verbose
5192 switch is used, a third column "local" is printed for local tags.
5192 switch is used, a third column "local" is printed for local tags.
5193 When the -q/--quiet switch is used, only the tag name is printed.
5193 When the -q/--quiet switch is used, only the tag name is printed.
5194
5194
5195 Returns 0 on success.
5195 Returns 0 on success.
5196 """
5196 """
5197
5197
5198 fm = ui.formatter('tags', opts)
5198 fm = ui.formatter('tags', opts)
5199 hexfunc = fm.hexfunc
5199 hexfunc = fm.hexfunc
5200 tagtype = ""
5200 tagtype = ""
5201
5201
5202 for t, n in reversed(repo.tagslist()):
5202 for t, n in reversed(repo.tagslist()):
5203 hn = hexfunc(n)
5203 hn = hexfunc(n)
5204 label = 'tags.normal'
5204 label = 'tags.normal'
5205 tagtype = ''
5205 tagtype = ''
5206 if repo.tagtype(t) == 'local':
5206 if repo.tagtype(t) == 'local':
5207 label = 'tags.local'
5207 label = 'tags.local'
5208 tagtype = 'local'
5208 tagtype = 'local'
5209
5209
5210 fm.startitem()
5210 fm.startitem()
5211 fm.write('tag', '%s', t, label=label)
5211 fm.write('tag', '%s', t, label=label)
5212 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5212 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5213 fm.condwrite(not ui.quiet, 'rev node', fmt,
5213 fm.condwrite(not ui.quiet, 'rev node', fmt,
5214 repo.changelog.rev(n), hn, label=label)
5214 repo.changelog.rev(n), hn, label=label)
5215 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5215 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5216 tagtype, label=label)
5216 tagtype, label=label)
5217 fm.plain('\n')
5217 fm.plain('\n')
5218 fm.end()
5218 fm.end()
5219
5219
5220 @command('tip',
5220 @command('tip',
5221 [('p', 'patch', None, _('show patch')),
5221 [('p', 'patch', None, _('show patch')),
5222 ('g', 'git', None, _('use git extended diff format')),
5222 ('g', 'git', None, _('use git extended diff format')),
5223 ] + templateopts,
5223 ] + templateopts,
5224 _('[-p] [-g]'))
5224 _('[-p] [-g]'))
5225 def tip(ui, repo, **opts):
5225 def tip(ui, repo, **opts):
5226 """show the tip revision (DEPRECATED)
5226 """show the tip revision (DEPRECATED)
5227
5227
5228 The tip revision (usually just called the tip) is the changeset
5228 The tip revision (usually just called the tip) is the changeset
5229 most recently added to the repository (and therefore the most
5229 most recently added to the repository (and therefore the most
5230 recently changed head).
5230 recently changed head).
5231
5231
5232 If you have just made a commit, that commit will be the tip. If
5232 If you have just made a commit, that commit will be the tip. If
5233 you have just pulled changes from another repository, the tip of
5233 you have just pulled changes from another repository, the tip of
5234 that repository becomes the current tip. The "tip" tag is special
5234 that repository becomes the current tip. The "tip" tag is special
5235 and cannot be renamed or assigned to a different changeset.
5235 and cannot be renamed or assigned to a different changeset.
5236
5236
5237 This command is deprecated, please use :hg:`heads` instead.
5237 This command is deprecated, please use :hg:`heads` instead.
5238
5238
5239 Returns 0 on success.
5239 Returns 0 on success.
5240 """
5240 """
5241 displayer = cmdutil.show_changeset(ui, repo, opts)
5241 displayer = cmdutil.show_changeset(ui, repo, opts)
5242 displayer.show(repo['tip'])
5242 displayer.show(repo['tip'])
5243 displayer.close()
5243 displayer.close()
5244
5244
5245 @command('unbundle',
5245 @command('unbundle',
5246 [('u', 'update', None,
5246 [('u', 'update', None,
5247 _('update to new branch head if changesets were unbundled'))],
5247 _('update to new branch head if changesets were unbundled'))],
5248 _('[-u] FILE...'))
5248 _('[-u] FILE...'))
5249 def unbundle(ui, repo, fname1, *fnames, **opts):
5249 def unbundle(ui, repo, fname1, *fnames, **opts):
5250 """apply one or more changegroup files
5250 """apply one or more changegroup files
5251
5251
5252 Apply one or more compressed changegroup files generated by the
5252 Apply one or more compressed changegroup files generated by the
5253 bundle command.
5253 bundle command.
5254
5254
5255 Returns 0 on success, 1 if an update has unresolved files.
5255 Returns 0 on success, 1 if an update has unresolved files.
5256 """
5256 """
5257 fnames = (fname1,) + fnames
5257 fnames = (fname1,) + fnames
5258
5258
5259 with repo.lock():
5259 with repo.lock():
5260 for fname in fnames:
5260 for fname in fnames:
5261 f = hg.openpath(ui, fname)
5261 f = hg.openpath(ui, fname)
5262 gen = exchange.readbundle(ui, f, fname)
5262 gen = exchange.readbundle(ui, f, fname)
5263 if isinstance(gen, bundle2.unbundle20):
5263 if isinstance(gen, bundle2.unbundle20):
5264 tr = repo.transaction('unbundle')
5264 tr = repo.transaction('unbundle')
5265 try:
5265 try:
5266 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5266 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5267 url='bundle:' + fname)
5267 url='bundle:' + fname)
5268 tr.close()
5268 tr.close()
5269 except error.BundleUnknownFeatureError as exc:
5269 except error.BundleUnknownFeatureError as exc:
5270 raise error.Abort(_('%s: unknown bundle feature, %s')
5270 raise error.Abort(_('%s: unknown bundle feature, %s')
5271 % (fname, exc),
5271 % (fname, exc),
5272 hint=_("see https://mercurial-scm.org/"
5272 hint=_("see https://mercurial-scm.org/"
5273 "wiki/BundleFeature for more "
5273 "wiki/BundleFeature for more "
5274 "information"))
5274 "information"))
5275 finally:
5275 finally:
5276 if tr:
5276 if tr:
5277 tr.release()
5277 tr.release()
5278 changes = [r.get('return', 0)
5278 changes = [r.get('return', 0)
5279 for r in op.records['changegroup']]
5279 for r in op.records['changegroup']]
5280 modheads = changegroup.combineresults(changes)
5280 modheads = changegroup.combineresults(changes)
5281 elif isinstance(gen, streamclone.streamcloneapplier):
5281 elif isinstance(gen, streamclone.streamcloneapplier):
5282 raise error.Abort(
5282 raise error.Abort(
5283 _('packed bundles cannot be applied with '
5283 _('packed bundles cannot be applied with '
5284 '"hg unbundle"'),
5284 '"hg unbundle"'),
5285 hint=_('use "hg debugapplystreamclonebundle"'))
5285 hint=_('use "hg debugapplystreamclonebundle"'))
5286 else:
5286 else:
5287 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5287 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5288
5288
5289 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
5289 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
5290
5290
5291 @command('^update|up|checkout|co',
5291 @command('^update|up|checkout|co',
5292 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5292 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5293 ('c', 'check', None, _('require clean working directory')),
5293 ('c', 'check', None, _('require clean working directory')),
5294 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5294 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5295 ('r', 'rev', '', _('revision'), _('REV'))
5295 ('r', 'rev', '', _('revision'), _('REV'))
5296 ] + mergetoolopts,
5296 ] + mergetoolopts,
5297 _('[-C|-c] [-d DATE] [[-r] REV]'))
5297 _('[-C|-c] [-d DATE] [[-r] REV]'))
5298 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5298 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5299 tool=None):
5299 tool=None):
5300 """update working directory (or switch revisions)
5300 """update working directory (or switch revisions)
5301
5301
5302 Update the repository's working directory to the specified
5302 Update the repository's working directory to the specified
5303 changeset. If no changeset is specified, update to the tip of the
5303 changeset. If no changeset is specified, update to the tip of the
5304 current named branch and move the active bookmark (see :hg:`help
5304 current named branch and move the active bookmark (see :hg:`help
5305 bookmarks`).
5305 bookmarks`).
5306
5306
5307 Update sets the working directory's parent revision to the specified
5307 Update sets the working directory's parent revision to the specified
5308 changeset (see :hg:`help parents`).
5308 changeset (see :hg:`help parents`).
5309
5309
5310 If the changeset is not a descendant or ancestor of the working
5310 If the changeset is not a descendant or ancestor of the working
5311 directory's parent and there are uncommitted changes, the update is
5311 directory's parent and there are uncommitted changes, the update is
5312 aborted. With the -c/--check option, the working directory is checked
5312 aborted. With the -c/--check option, the working directory is checked
5313 for uncommitted changes; if none are found, the working directory is
5313 for uncommitted changes; if none are found, the working directory is
5314 updated to the specified changeset.
5314 updated to the specified changeset.
5315
5315
5316 .. container:: verbose
5316 .. container:: verbose
5317
5317
5318 The -C/--clean and -c/--check options control what happens if the
5318 The -C/--clean and -c/--check options control what happens if the
5319 working directory contains uncommitted changes.
5319 working directory contains uncommitted changes.
5320 At most of one of them can be specified.
5320 At most of one of them can be specified.
5321
5321
5322 1. If no option is specified, and if
5322 1. If no option is specified, and if
5323 the requested changeset is an ancestor or descendant of
5323 the requested changeset is an ancestor or descendant of
5324 the working directory's parent, the uncommitted changes
5324 the working directory's parent, the uncommitted changes
5325 are merged into the requested changeset and the merged
5325 are merged into the requested changeset and the merged
5326 result is left uncommitted. If the requested changeset is
5326 result is left uncommitted. If the requested changeset is
5327 not an ancestor or descendant (that is, it is on another
5327 not an ancestor or descendant (that is, it is on another
5328 branch), the update is aborted and the uncommitted changes
5328 branch), the update is aborted and the uncommitted changes
5329 are preserved.
5329 are preserved.
5330
5330
5331 2. With the -c/--check option, the update is aborted and the
5331 2. With the -c/--check option, the update is aborted and the
5332 uncommitted changes are preserved.
5332 uncommitted changes are preserved.
5333
5333
5334 3. With the -C/--clean option, uncommitted changes are discarded and
5334 3. With the -C/--clean option, uncommitted changes are discarded and
5335 the working directory is updated to the requested changeset.
5335 the working directory is updated to the requested changeset.
5336
5336
5337 To cancel an uncommitted merge (and lose your changes), use
5337 To cancel an uncommitted merge (and lose your changes), use
5338 :hg:`update --clean .`.
5338 :hg:`update --clean .`.
5339
5339
5340 Use null as the changeset to remove the working directory (like
5340 Use null as the changeset to remove the working directory (like
5341 :hg:`clone -U`).
5341 :hg:`clone -U`).
5342
5342
5343 If you want to revert just one file to an older revision, use
5343 If you want to revert just one file to an older revision, use
5344 :hg:`revert [-r REV] NAME`.
5344 :hg:`revert [-r REV] NAME`.
5345
5345
5346 See :hg:`help dates` for a list of formats valid for -d/--date.
5346 See :hg:`help dates` for a list of formats valid for -d/--date.
5347
5347
5348 Returns 0 on success, 1 if there are unresolved files.
5348 Returns 0 on success, 1 if there are unresolved files.
5349 """
5349 """
5350 if rev and node:
5350 if rev and node:
5351 raise error.Abort(_("please specify just one revision"))
5351 raise error.Abort(_("please specify just one revision"))
5352
5352
5353 if rev is None or rev == '':
5353 if rev is None or rev == '':
5354 rev = node
5354 rev = node
5355
5355
5356 if date and rev is not None:
5356 if date and rev is not None:
5357 raise error.Abort(_("you can't specify a revision and a date"))
5357 raise error.Abort(_("you can't specify a revision and a date"))
5358
5358
5359 if check and clean:
5359 if check and clean:
5360 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
5360 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
5361
5361
5362 with repo.wlock():
5362 with repo.wlock():
5363 cmdutil.clearunfinished(repo)
5363 cmdutil.clearunfinished(repo)
5364
5364
5365 if date:
5365 if date:
5366 rev = cmdutil.finddate(ui, repo, date)
5366 rev = cmdutil.finddate(ui, repo, date)
5367
5367
5368 # if we defined a bookmark, we have to remember the original name
5368 # if we defined a bookmark, we have to remember the original name
5369 brev = rev
5369 brev = rev
5370 rev = scmutil.revsingle(repo, rev, rev).rev()
5370 rev = scmutil.revsingle(repo, rev, rev).rev()
5371
5371
5372 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5372 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5373
5373
5374 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
5374 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
5375
5375
5376 @command('verify', [])
5376 @command('verify', [])
5377 def verify(ui, repo):
5377 def verify(ui, repo):
5378 """verify the integrity of the repository
5378 """verify the integrity of the repository
5379
5379
5380 Verify the integrity of the current repository.
5380 Verify the integrity of the current repository.
5381
5381
5382 This will perform an extensive check of the repository's
5382 This will perform an extensive check of the repository's
5383 integrity, validating the hashes and checksums of each entry in
5383 integrity, validating the hashes and checksums of each entry in
5384 the changelog, manifest, and tracked files, as well as the
5384 the changelog, manifest, and tracked files, as well as the
5385 integrity of their crosslinks and indices.
5385 integrity of their crosslinks and indices.
5386
5386
5387 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5387 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5388 for more information about recovery from corruption of the
5388 for more information about recovery from corruption of the
5389 repository.
5389 repository.
5390
5390
5391 Returns 0 on success, 1 if errors are encountered.
5391 Returns 0 on success, 1 if errors are encountered.
5392 """
5392 """
5393 return hg.verify(repo)
5393 return hg.verify(repo)
5394
5394
5395 @command('version', [] + formatteropts, norepo=True)
5395 @command('version', [] + formatteropts, norepo=True)
5396 def version_(ui, **opts):
5396 def version_(ui, **opts):
5397 """output version and copyright information"""
5397 """output version and copyright information"""
5398 fm = ui.formatter("version", opts)
5398 fm = ui.formatter("version", opts)
5399 fm.startitem()
5399 fm.startitem()
5400 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5400 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5401 util.version())
5401 util.version())
5402 license = _(
5402 license = _(
5403 "(see https://mercurial-scm.org for more information)\n"
5403 "(see https://mercurial-scm.org for more information)\n"
5404 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5404 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5405 "This is free software; see the source for copying conditions. "
5405 "This is free software; see the source for copying conditions. "
5406 "There is NO\nwarranty; "
5406 "There is NO\nwarranty; "
5407 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5407 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5408 )
5408 )
5409 if not ui.quiet:
5409 if not ui.quiet:
5410 fm.plain(license)
5410 fm.plain(license)
5411
5411
5412 if ui.verbose:
5412 if ui.verbose:
5413 fm.plain(_("\nEnabled extensions:\n\n"))
5413 fm.plain(_("\nEnabled extensions:\n\n"))
5414 # format names and versions into columns
5414 # format names and versions into columns
5415 names = []
5415 names = []
5416 vers = []
5416 vers = []
5417 isinternals = []
5417 isinternals = []
5418 for name, module in extensions.extensions():
5418 for name, module in extensions.extensions():
5419 names.append(name)
5419 names.append(name)
5420 vers.append(extensions.moduleversion(module) or None)
5420 vers.append(extensions.moduleversion(module) or None)
5421 isinternals.append(extensions.ismoduleinternal(module))
5421 isinternals.append(extensions.ismoduleinternal(module))
5422 fn = fm.nested("extensions")
5422 fn = fm.nested("extensions")
5423 if names:
5423 if names:
5424 namefmt = " %%-%ds " % max(len(n) for n in names)
5424 namefmt = " %%-%ds " % max(len(n) for n in names)
5425 places = [_("external"), _("internal")]
5425 places = [_("external"), _("internal")]
5426 for n, v, p in zip(names, vers, isinternals):
5426 for n, v, p in zip(names, vers, isinternals):
5427 fn.startitem()
5427 fn.startitem()
5428 fn.condwrite(ui.verbose, "name", namefmt, n)
5428 fn.condwrite(ui.verbose, "name", namefmt, n)
5429 if ui.verbose:
5429 if ui.verbose:
5430 fn.plain("%s " % places[p])
5430 fn.plain("%s " % places[p])
5431 fn.data(bundled=p)
5431 fn.data(bundled=p)
5432 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5432 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5433 if ui.verbose:
5433 if ui.verbose:
5434 fn.plain("\n")
5434 fn.plain("\n")
5435 fn.end()
5435 fn.end()
5436 fm.end()
5436 fm.end()
5437
5437
5438 def loadcmdtable(ui, name, cmdtable):
5438 def loadcmdtable(ui, name, cmdtable):
5439 """Load command functions from specified cmdtable
5439 """Load command functions from specified cmdtable
5440 """
5440 """
5441 overrides = [cmd for cmd in cmdtable if cmd in table]
5441 overrides = [cmd for cmd in cmdtable if cmd in table]
5442 if overrides:
5442 if overrides:
5443 ui.warn(_("extension '%s' overrides commands: %s\n")
5443 ui.warn(_("extension '%s' overrides commands: %s\n")
5444 % (name, " ".join(overrides)))
5444 % (name, " ".join(overrides)))
5445 table.update(cmdtable)
5445 table.update(cmdtable)
@@ -1,2067 +1,2068
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import operator
12 import operator
13 import os
13 import os
14 import random
14 import random
15 import socket
15 import socket
16 import string
16 import string
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import time
19 import time
20
20
21 from .i18n import _
21 from .i18n import _
22 from .node import (
22 from .node import (
23 bin,
23 bin,
24 hex,
24 hex,
25 nullhex,
25 nullhex,
26 nullid,
26 nullid,
27 nullrev,
27 nullrev,
28 short,
28 short,
29 )
29 )
30 from . import (
30 from . import (
31 bundle2,
31 bundle2,
32 changegroup,
32 changegroup,
33 cmdutil,
33 cmdutil,
34 commands,
34 commands,
35 context,
35 context,
36 dagparser,
36 dagparser,
37 dagutil,
37 dagutil,
38 encoding,
38 encoding,
39 error,
39 error,
40 exchange,
40 exchange,
41 extensions,
41 extensions,
42 fileset,
42 fileset,
43 formatter,
43 formatter,
44 hg,
44 hg,
45 localrepo,
45 localrepo,
46 lock as lockmod,
46 lock as lockmod,
47 merge as mergemod,
47 merge as mergemod,
48 obsolete,
48 obsolete,
49 policy,
49 policy,
50 pvec,
50 pvec,
51 pycompat,
51 pycompat,
52 repair,
52 repair,
53 revlog,
53 revlog,
54 revset,
54 revset,
55 revsetlang,
55 scmutil,
56 scmutil,
56 setdiscovery,
57 setdiscovery,
57 simplemerge,
58 simplemerge,
58 smartset,
59 smartset,
59 sslutil,
60 sslutil,
60 streamclone,
61 streamclone,
61 templater,
62 templater,
62 treediscovery,
63 treediscovery,
63 util,
64 util,
64 )
65 )
65
66
66 release = lockmod.release
67 release = lockmod.release
67
68
68 # We reuse the command table from commands because it is easier than
69 # We reuse the command table from commands because it is easier than
69 # teaching dispatch about multiple tables.
70 # teaching dispatch about multiple tables.
70 command = cmdutil.command(commands.table)
71 command = cmdutil.command(commands.table)
71
72
72 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
73 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
73 def debugancestor(ui, repo, *args):
74 def debugancestor(ui, repo, *args):
74 """find the ancestor revision of two revisions in a given index"""
75 """find the ancestor revision of two revisions in a given index"""
75 if len(args) == 3:
76 if len(args) == 3:
76 index, rev1, rev2 = args
77 index, rev1, rev2 = args
77 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False), index)
78 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False), index)
78 lookup = r.lookup
79 lookup = r.lookup
79 elif len(args) == 2:
80 elif len(args) == 2:
80 if not repo:
81 if not repo:
81 raise error.Abort(_('there is no Mercurial repository here '
82 raise error.Abort(_('there is no Mercurial repository here '
82 '(.hg not found)'))
83 '(.hg not found)'))
83 rev1, rev2 = args
84 rev1, rev2 = args
84 r = repo.changelog
85 r = repo.changelog
85 lookup = repo.lookup
86 lookup = repo.lookup
86 else:
87 else:
87 raise error.Abort(_('either two or three arguments required'))
88 raise error.Abort(_('either two or three arguments required'))
88 a = r.ancestor(lookup(rev1), lookup(rev2))
89 a = r.ancestor(lookup(rev1), lookup(rev2))
89 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
90 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
90
91
91 @command('debugapplystreamclonebundle', [], 'FILE')
92 @command('debugapplystreamclonebundle', [], 'FILE')
92 def debugapplystreamclonebundle(ui, repo, fname):
93 def debugapplystreamclonebundle(ui, repo, fname):
93 """apply a stream clone bundle file"""
94 """apply a stream clone bundle file"""
94 f = hg.openpath(ui, fname)
95 f = hg.openpath(ui, fname)
95 gen = exchange.readbundle(ui, f, fname)
96 gen = exchange.readbundle(ui, f, fname)
96 gen.apply(repo)
97 gen.apply(repo)
97
98
98 @command('debugbuilddag',
99 @command('debugbuilddag',
99 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
100 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
100 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
101 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
101 ('n', 'new-file', None, _('add new file at each rev'))],
102 ('n', 'new-file', None, _('add new file at each rev'))],
102 _('[OPTION]... [TEXT]'))
103 _('[OPTION]... [TEXT]'))
103 def debugbuilddag(ui, repo, text=None,
104 def debugbuilddag(ui, repo, text=None,
104 mergeable_file=False,
105 mergeable_file=False,
105 overwritten_file=False,
106 overwritten_file=False,
106 new_file=False):
107 new_file=False):
107 """builds a repo with a given DAG from scratch in the current empty repo
108 """builds a repo with a given DAG from scratch in the current empty repo
108
109
109 The description of the DAG is read from stdin if not given on the
110 The description of the DAG is read from stdin if not given on the
110 command line.
111 command line.
111
112
112 Elements:
113 Elements:
113
114
114 - "+n" is a linear run of n nodes based on the current default parent
115 - "+n" is a linear run of n nodes based on the current default parent
115 - "." is a single node based on the current default parent
116 - "." is a single node based on the current default parent
116 - "$" resets the default parent to null (implied at the start);
117 - "$" resets the default parent to null (implied at the start);
117 otherwise the default parent is always the last node created
118 otherwise the default parent is always the last node created
118 - "<p" sets the default parent to the backref p
119 - "<p" sets the default parent to the backref p
119 - "*p" is a fork at parent p, which is a backref
120 - "*p" is a fork at parent p, which is a backref
120 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
121 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
121 - "/p2" is a merge of the preceding node and p2
122 - "/p2" is a merge of the preceding node and p2
122 - ":tag" defines a local tag for the preceding node
123 - ":tag" defines a local tag for the preceding node
123 - "@branch" sets the named branch for subsequent nodes
124 - "@branch" sets the named branch for subsequent nodes
124 - "#...\\n" is a comment up to the end of the line
125 - "#...\\n" is a comment up to the end of the line
125
126
126 Whitespace between the above elements is ignored.
127 Whitespace between the above elements is ignored.
127
128
128 A backref is either
129 A backref is either
129
130
130 - a number n, which references the node curr-n, where curr is the current
131 - a number n, which references the node curr-n, where curr is the current
131 node, or
132 node, or
132 - the name of a local tag you placed earlier using ":tag", or
133 - the name of a local tag you placed earlier using ":tag", or
133 - empty to denote the default parent.
134 - empty to denote the default parent.
134
135
135 All string valued-elements are either strictly alphanumeric, or must
136 All string valued-elements are either strictly alphanumeric, or must
136 be enclosed in double quotes ("..."), with "\\" as escape character.
137 be enclosed in double quotes ("..."), with "\\" as escape character.
137 """
138 """
138
139
139 if text is None:
140 if text is None:
140 ui.status(_("reading DAG from stdin\n"))
141 ui.status(_("reading DAG from stdin\n"))
141 text = ui.fin.read()
142 text = ui.fin.read()
142
143
143 cl = repo.changelog
144 cl = repo.changelog
144 if len(cl) > 0:
145 if len(cl) > 0:
145 raise error.Abort(_('repository is not empty'))
146 raise error.Abort(_('repository is not empty'))
146
147
147 # determine number of revs in DAG
148 # determine number of revs in DAG
148 total = 0
149 total = 0
149 for type, data in dagparser.parsedag(text):
150 for type, data in dagparser.parsedag(text):
150 if type == 'n':
151 if type == 'n':
151 total += 1
152 total += 1
152
153
153 if mergeable_file:
154 if mergeable_file:
154 linesperrev = 2
155 linesperrev = 2
155 # make a file with k lines per rev
156 # make a file with k lines per rev
156 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
157 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
157 initialmergedlines.append("")
158 initialmergedlines.append("")
158
159
159 tags = []
160 tags = []
160
161
161 wlock = lock = tr = None
162 wlock = lock = tr = None
162 try:
163 try:
163 wlock = repo.wlock()
164 wlock = repo.wlock()
164 lock = repo.lock()
165 lock = repo.lock()
165 tr = repo.transaction("builddag")
166 tr = repo.transaction("builddag")
166
167
167 at = -1
168 at = -1
168 atbranch = 'default'
169 atbranch = 'default'
169 nodeids = []
170 nodeids = []
170 id = 0
171 id = 0
171 ui.progress(_('building'), id, unit=_('revisions'), total=total)
172 ui.progress(_('building'), id, unit=_('revisions'), total=total)
172 for type, data in dagparser.parsedag(text):
173 for type, data in dagparser.parsedag(text):
173 if type == 'n':
174 if type == 'n':
174 ui.note(('node %s\n' % str(data)))
175 ui.note(('node %s\n' % str(data)))
175 id, ps = data
176 id, ps = data
176
177
177 files = []
178 files = []
178 fctxs = {}
179 fctxs = {}
179
180
180 p2 = None
181 p2 = None
181 if mergeable_file:
182 if mergeable_file:
182 fn = "mf"
183 fn = "mf"
183 p1 = repo[ps[0]]
184 p1 = repo[ps[0]]
184 if len(ps) > 1:
185 if len(ps) > 1:
185 p2 = repo[ps[1]]
186 p2 = repo[ps[1]]
186 pa = p1.ancestor(p2)
187 pa = p1.ancestor(p2)
187 base, local, other = [x[fn].data() for x in (pa, p1,
188 base, local, other = [x[fn].data() for x in (pa, p1,
188 p2)]
189 p2)]
189 m3 = simplemerge.Merge3Text(base, local, other)
190 m3 = simplemerge.Merge3Text(base, local, other)
190 ml = [l.strip() for l in m3.merge_lines()]
191 ml = [l.strip() for l in m3.merge_lines()]
191 ml.append("")
192 ml.append("")
192 elif at > 0:
193 elif at > 0:
193 ml = p1[fn].data().split("\n")
194 ml = p1[fn].data().split("\n")
194 else:
195 else:
195 ml = initialmergedlines
196 ml = initialmergedlines
196 ml[id * linesperrev] += " r%i" % id
197 ml[id * linesperrev] += " r%i" % id
197 mergedtext = "\n".join(ml)
198 mergedtext = "\n".join(ml)
198 files.append(fn)
199 files.append(fn)
199 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
200 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
200
201
201 if overwritten_file:
202 if overwritten_file:
202 fn = "of"
203 fn = "of"
203 files.append(fn)
204 files.append(fn)
204 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
205 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
205
206
206 if new_file:
207 if new_file:
207 fn = "nf%i" % id
208 fn = "nf%i" % id
208 files.append(fn)
209 files.append(fn)
209 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
210 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
210 if len(ps) > 1:
211 if len(ps) > 1:
211 if not p2:
212 if not p2:
212 p2 = repo[ps[1]]
213 p2 = repo[ps[1]]
213 for fn in p2:
214 for fn in p2:
214 if fn.startswith("nf"):
215 if fn.startswith("nf"):
215 files.append(fn)
216 files.append(fn)
216 fctxs[fn] = p2[fn]
217 fctxs[fn] = p2[fn]
217
218
218 def fctxfn(repo, cx, path):
219 def fctxfn(repo, cx, path):
219 return fctxs.get(path)
220 return fctxs.get(path)
220
221
221 if len(ps) == 0 or ps[0] < 0:
222 if len(ps) == 0 or ps[0] < 0:
222 pars = [None, None]
223 pars = [None, None]
223 elif len(ps) == 1:
224 elif len(ps) == 1:
224 pars = [nodeids[ps[0]], None]
225 pars = [nodeids[ps[0]], None]
225 else:
226 else:
226 pars = [nodeids[p] for p in ps]
227 pars = [nodeids[p] for p in ps]
227 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
228 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
228 date=(id, 0),
229 date=(id, 0),
229 user="debugbuilddag",
230 user="debugbuilddag",
230 extra={'branch': atbranch})
231 extra={'branch': atbranch})
231 nodeid = repo.commitctx(cx)
232 nodeid = repo.commitctx(cx)
232 nodeids.append(nodeid)
233 nodeids.append(nodeid)
233 at = id
234 at = id
234 elif type == 'l':
235 elif type == 'l':
235 id, name = data
236 id, name = data
236 ui.note(('tag %s\n' % name))
237 ui.note(('tag %s\n' % name))
237 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
238 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
238 elif type == 'a':
239 elif type == 'a':
239 ui.note(('branch %s\n' % data))
240 ui.note(('branch %s\n' % data))
240 atbranch = data
241 atbranch = data
241 ui.progress(_('building'), id, unit=_('revisions'), total=total)
242 ui.progress(_('building'), id, unit=_('revisions'), total=total)
242 tr.close()
243 tr.close()
243
244
244 if tags:
245 if tags:
245 repo.vfs.write("localtags", "".join(tags))
246 repo.vfs.write("localtags", "".join(tags))
246 finally:
247 finally:
247 ui.progress(_('building'), None)
248 ui.progress(_('building'), None)
248 release(tr, lock, wlock)
249 release(tr, lock, wlock)
249
250
250 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
251 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
251 indent_string = ' ' * indent
252 indent_string = ' ' * indent
252 if all:
253 if all:
253 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
254 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
254 % indent_string)
255 % indent_string)
255
256
256 def showchunks(named):
257 def showchunks(named):
257 ui.write("\n%s%s\n" % (indent_string, named))
258 ui.write("\n%s%s\n" % (indent_string, named))
258 chain = None
259 chain = None
259 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
260 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
260 node = chunkdata['node']
261 node = chunkdata['node']
261 p1 = chunkdata['p1']
262 p1 = chunkdata['p1']
262 p2 = chunkdata['p2']
263 p2 = chunkdata['p2']
263 cs = chunkdata['cs']
264 cs = chunkdata['cs']
264 deltabase = chunkdata['deltabase']
265 deltabase = chunkdata['deltabase']
265 delta = chunkdata['delta']
266 delta = chunkdata['delta']
266 ui.write("%s%s %s %s %s %s %s\n" %
267 ui.write("%s%s %s %s %s %s %s\n" %
267 (indent_string, hex(node), hex(p1), hex(p2),
268 (indent_string, hex(node), hex(p1), hex(p2),
268 hex(cs), hex(deltabase), len(delta)))
269 hex(cs), hex(deltabase), len(delta)))
269 chain = node
270 chain = node
270
271
271 chunkdata = gen.changelogheader()
272 chunkdata = gen.changelogheader()
272 showchunks("changelog")
273 showchunks("changelog")
273 chunkdata = gen.manifestheader()
274 chunkdata = gen.manifestheader()
274 showchunks("manifest")
275 showchunks("manifest")
275 for chunkdata in iter(gen.filelogheader, {}):
276 for chunkdata in iter(gen.filelogheader, {}):
276 fname = chunkdata['filename']
277 fname = chunkdata['filename']
277 showchunks(fname)
278 showchunks(fname)
278 else:
279 else:
279 if isinstance(gen, bundle2.unbundle20):
280 if isinstance(gen, bundle2.unbundle20):
280 raise error.Abort(_('use debugbundle2 for this file'))
281 raise error.Abort(_('use debugbundle2 for this file'))
281 chunkdata = gen.changelogheader()
282 chunkdata = gen.changelogheader()
282 chain = None
283 chain = None
283 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
284 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
284 node = chunkdata['node']
285 node = chunkdata['node']
285 ui.write("%s%s\n" % (indent_string, hex(node)))
286 ui.write("%s%s\n" % (indent_string, hex(node)))
286 chain = node
287 chain = node
287
288
288 def _debugbundle2(ui, gen, all=None, **opts):
289 def _debugbundle2(ui, gen, all=None, **opts):
289 """lists the contents of a bundle2"""
290 """lists the contents of a bundle2"""
290 if not isinstance(gen, bundle2.unbundle20):
291 if not isinstance(gen, bundle2.unbundle20):
291 raise error.Abort(_('not a bundle2 file'))
292 raise error.Abort(_('not a bundle2 file'))
292 ui.write(('Stream params: %s\n' % repr(gen.params)))
293 ui.write(('Stream params: %s\n' % repr(gen.params)))
293 for part in gen.iterparts():
294 for part in gen.iterparts():
294 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
295 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
295 if part.type == 'changegroup':
296 if part.type == 'changegroup':
296 version = part.params.get('version', '01')
297 version = part.params.get('version', '01')
297 cg = changegroup.getunbundler(version, part, 'UN')
298 cg = changegroup.getunbundler(version, part, 'UN')
298 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
299 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
299
300
300 @command('debugbundle',
301 @command('debugbundle',
301 [('a', 'all', None, _('show all details')),
302 [('a', 'all', None, _('show all details')),
302 ('', 'spec', None, _('print the bundlespec of the bundle'))],
303 ('', 'spec', None, _('print the bundlespec of the bundle'))],
303 _('FILE'),
304 _('FILE'),
304 norepo=True)
305 norepo=True)
305 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
306 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
306 """lists the contents of a bundle"""
307 """lists the contents of a bundle"""
307 with hg.openpath(ui, bundlepath) as f:
308 with hg.openpath(ui, bundlepath) as f:
308 if spec:
309 if spec:
309 spec = exchange.getbundlespec(ui, f)
310 spec = exchange.getbundlespec(ui, f)
310 ui.write('%s\n' % spec)
311 ui.write('%s\n' % spec)
311 return
312 return
312
313
313 gen = exchange.readbundle(ui, f, bundlepath)
314 gen = exchange.readbundle(ui, f, bundlepath)
314 if isinstance(gen, bundle2.unbundle20):
315 if isinstance(gen, bundle2.unbundle20):
315 return _debugbundle2(ui, gen, all=all, **opts)
316 return _debugbundle2(ui, gen, all=all, **opts)
316 _debugchangegroup(ui, gen, all=all, **opts)
317 _debugchangegroup(ui, gen, all=all, **opts)
317
318
318 @command('debugcheckstate', [], '')
319 @command('debugcheckstate', [], '')
319 def debugcheckstate(ui, repo):
320 def debugcheckstate(ui, repo):
320 """validate the correctness of the current dirstate"""
321 """validate the correctness of the current dirstate"""
321 parent1, parent2 = repo.dirstate.parents()
322 parent1, parent2 = repo.dirstate.parents()
322 m1 = repo[parent1].manifest()
323 m1 = repo[parent1].manifest()
323 m2 = repo[parent2].manifest()
324 m2 = repo[parent2].manifest()
324 errors = 0
325 errors = 0
325 for f in repo.dirstate:
326 for f in repo.dirstate:
326 state = repo.dirstate[f]
327 state = repo.dirstate[f]
327 if state in "nr" and f not in m1:
328 if state in "nr" and f not in m1:
328 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
329 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
329 errors += 1
330 errors += 1
330 if state in "a" and f in m1:
331 if state in "a" and f in m1:
331 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
332 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
332 errors += 1
333 errors += 1
333 if state in "m" and f not in m1 and f not in m2:
334 if state in "m" and f not in m1 and f not in m2:
334 ui.warn(_("%s in state %s, but not in either manifest\n") %
335 ui.warn(_("%s in state %s, but not in either manifest\n") %
335 (f, state))
336 (f, state))
336 errors += 1
337 errors += 1
337 for f in m1:
338 for f in m1:
338 state = repo.dirstate[f]
339 state = repo.dirstate[f]
339 if state not in "nrm":
340 if state not in "nrm":
340 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
341 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
341 errors += 1
342 errors += 1
342 if errors:
343 if errors:
343 error = _(".hg/dirstate inconsistent with current parent's manifest")
344 error = _(".hg/dirstate inconsistent with current parent's manifest")
344 raise error.Abort(error)
345 raise error.Abort(error)
345
346
346 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
347 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
347 def debugcommands(ui, cmd='', *args):
348 def debugcommands(ui, cmd='', *args):
348 """list all available commands and options"""
349 """list all available commands and options"""
349 for cmd, vals in sorted(commands.table.iteritems()):
350 for cmd, vals in sorted(commands.table.iteritems()):
350 cmd = cmd.split('|')[0].strip('^')
351 cmd = cmd.split('|')[0].strip('^')
351 opts = ', '.join([i[1] for i in vals[1]])
352 opts = ', '.join([i[1] for i in vals[1]])
352 ui.write('%s: %s\n' % (cmd, opts))
353 ui.write('%s: %s\n' % (cmd, opts))
353
354
354 @command('debugcomplete',
355 @command('debugcomplete',
355 [('o', 'options', None, _('show the command options'))],
356 [('o', 'options', None, _('show the command options'))],
356 _('[-o] CMD'),
357 _('[-o] CMD'),
357 norepo=True)
358 norepo=True)
358 def debugcomplete(ui, cmd='', **opts):
359 def debugcomplete(ui, cmd='', **opts):
359 """returns the completion list associated with the given command"""
360 """returns the completion list associated with the given command"""
360
361
361 if opts.get('options'):
362 if opts.get('options'):
362 options = []
363 options = []
363 otables = [commands.globalopts]
364 otables = [commands.globalopts]
364 if cmd:
365 if cmd:
365 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
366 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
366 otables.append(entry[1])
367 otables.append(entry[1])
367 for t in otables:
368 for t in otables:
368 for o in t:
369 for o in t:
369 if "(DEPRECATED)" in o[3]:
370 if "(DEPRECATED)" in o[3]:
370 continue
371 continue
371 if o[0]:
372 if o[0]:
372 options.append('-%s' % o[0])
373 options.append('-%s' % o[0])
373 options.append('--%s' % o[1])
374 options.append('--%s' % o[1])
374 ui.write("%s\n" % "\n".join(options))
375 ui.write("%s\n" % "\n".join(options))
375 return
376 return
376
377
377 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, commands.table)
378 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, commands.table)
378 if ui.verbose:
379 if ui.verbose:
379 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
380 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
380 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
381 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
381
382
382 @command('debugcreatestreamclonebundle', [], 'FILE')
383 @command('debugcreatestreamclonebundle', [], 'FILE')
383 def debugcreatestreamclonebundle(ui, repo, fname):
384 def debugcreatestreamclonebundle(ui, repo, fname):
384 """create a stream clone bundle file
385 """create a stream clone bundle file
385
386
386 Stream bundles are special bundles that are essentially archives of
387 Stream bundles are special bundles that are essentially archives of
387 revlog files. They are commonly used for cloning very quickly.
388 revlog files. They are commonly used for cloning very quickly.
388 """
389 """
389 requirements, gen = streamclone.generatebundlev1(repo)
390 requirements, gen = streamclone.generatebundlev1(repo)
390 changegroup.writechunks(ui, gen, fname)
391 changegroup.writechunks(ui, gen, fname)
391
392
392 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
393 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
393
394
394 @command('debugdag',
395 @command('debugdag',
395 [('t', 'tags', None, _('use tags as labels')),
396 [('t', 'tags', None, _('use tags as labels')),
396 ('b', 'branches', None, _('annotate with branch names')),
397 ('b', 'branches', None, _('annotate with branch names')),
397 ('', 'dots', None, _('use dots for runs')),
398 ('', 'dots', None, _('use dots for runs')),
398 ('s', 'spaces', None, _('separate elements by spaces'))],
399 ('s', 'spaces', None, _('separate elements by spaces'))],
399 _('[OPTION]... [FILE [REV]...]'),
400 _('[OPTION]... [FILE [REV]...]'),
400 optionalrepo=True)
401 optionalrepo=True)
401 def debugdag(ui, repo, file_=None, *revs, **opts):
402 def debugdag(ui, repo, file_=None, *revs, **opts):
402 """format the changelog or an index DAG as a concise textual description
403 """format the changelog or an index DAG as a concise textual description
403
404
404 If you pass a revlog index, the revlog's DAG is emitted. If you list
405 If you pass a revlog index, the revlog's DAG is emitted. If you list
405 revision numbers, they get labeled in the output as rN.
406 revision numbers, they get labeled in the output as rN.
406
407
407 Otherwise, the changelog DAG of the current repo is emitted.
408 Otherwise, the changelog DAG of the current repo is emitted.
408 """
409 """
409 spaces = opts.get('spaces')
410 spaces = opts.get('spaces')
410 dots = opts.get('dots')
411 dots = opts.get('dots')
411 if file_:
412 if file_:
412 rlog = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
413 rlog = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
413 file_)
414 file_)
414 revs = set((int(r) for r in revs))
415 revs = set((int(r) for r in revs))
415 def events():
416 def events():
416 for r in rlog:
417 for r in rlog:
417 yield 'n', (r, list(p for p in rlog.parentrevs(r)
418 yield 'n', (r, list(p for p in rlog.parentrevs(r)
418 if p != -1))
419 if p != -1))
419 if r in revs:
420 if r in revs:
420 yield 'l', (r, "r%i" % r)
421 yield 'l', (r, "r%i" % r)
421 elif repo:
422 elif repo:
422 cl = repo.changelog
423 cl = repo.changelog
423 tags = opts.get('tags')
424 tags = opts.get('tags')
424 branches = opts.get('branches')
425 branches = opts.get('branches')
425 if tags:
426 if tags:
426 labels = {}
427 labels = {}
427 for l, n in repo.tags().items():
428 for l, n in repo.tags().items():
428 labels.setdefault(cl.rev(n), []).append(l)
429 labels.setdefault(cl.rev(n), []).append(l)
429 def events():
430 def events():
430 b = "default"
431 b = "default"
431 for r in cl:
432 for r in cl:
432 if branches:
433 if branches:
433 newb = cl.read(cl.node(r))[5]['branch']
434 newb = cl.read(cl.node(r))[5]['branch']
434 if newb != b:
435 if newb != b:
435 yield 'a', newb
436 yield 'a', newb
436 b = newb
437 b = newb
437 yield 'n', (r, list(p for p in cl.parentrevs(r)
438 yield 'n', (r, list(p for p in cl.parentrevs(r)
438 if p != -1))
439 if p != -1))
439 if tags:
440 if tags:
440 ls = labels.get(r)
441 ls = labels.get(r)
441 if ls:
442 if ls:
442 for l in ls:
443 for l in ls:
443 yield 'l', (r, l)
444 yield 'l', (r, l)
444 else:
445 else:
445 raise error.Abort(_('need repo for changelog dag'))
446 raise error.Abort(_('need repo for changelog dag'))
446
447
447 for line in dagparser.dagtextlines(events(),
448 for line in dagparser.dagtextlines(events(),
448 addspaces=spaces,
449 addspaces=spaces,
449 wraplabels=True,
450 wraplabels=True,
450 wrapannotations=True,
451 wrapannotations=True,
451 wrapnonlinear=dots,
452 wrapnonlinear=dots,
452 usedots=dots,
453 usedots=dots,
453 maxlinewidth=70):
454 maxlinewidth=70):
454 ui.write(line)
455 ui.write(line)
455 ui.write("\n")
456 ui.write("\n")
456
457
457 @command('debugdata', commands.debugrevlogopts, _('-c|-m|FILE REV'))
458 @command('debugdata', commands.debugrevlogopts, _('-c|-m|FILE REV'))
458 def debugdata(ui, repo, file_, rev=None, **opts):
459 def debugdata(ui, repo, file_, rev=None, **opts):
459 """dump the contents of a data file revision"""
460 """dump the contents of a data file revision"""
460 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
461 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
461 if rev is not None:
462 if rev is not None:
462 raise error.CommandError('debugdata', _('invalid arguments'))
463 raise error.CommandError('debugdata', _('invalid arguments'))
463 file_, rev = None, file_
464 file_, rev = None, file_
464 elif rev is None:
465 elif rev is None:
465 raise error.CommandError('debugdata', _('invalid arguments'))
466 raise error.CommandError('debugdata', _('invalid arguments'))
466 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
467 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
467 try:
468 try:
468 ui.write(r.revision(r.lookup(rev), raw=True))
469 ui.write(r.revision(r.lookup(rev), raw=True))
469 except KeyError:
470 except KeyError:
470 raise error.Abort(_('invalid revision identifier %s') % rev)
471 raise error.Abort(_('invalid revision identifier %s') % rev)
471
472
472 @command('debugdate',
473 @command('debugdate',
473 [('e', 'extended', None, _('try extended date formats'))],
474 [('e', 'extended', None, _('try extended date formats'))],
474 _('[-e] DATE [RANGE]'),
475 _('[-e] DATE [RANGE]'),
475 norepo=True, optionalrepo=True)
476 norepo=True, optionalrepo=True)
476 def debugdate(ui, date, range=None, **opts):
477 def debugdate(ui, date, range=None, **opts):
477 """parse and display a date"""
478 """parse and display a date"""
478 if opts["extended"]:
479 if opts["extended"]:
479 d = util.parsedate(date, util.extendeddateformats)
480 d = util.parsedate(date, util.extendeddateformats)
480 else:
481 else:
481 d = util.parsedate(date)
482 d = util.parsedate(date)
482 ui.write(("internal: %s %s\n") % d)
483 ui.write(("internal: %s %s\n") % d)
483 ui.write(("standard: %s\n") % util.datestr(d))
484 ui.write(("standard: %s\n") % util.datestr(d))
484 if range:
485 if range:
485 m = util.matchdate(range)
486 m = util.matchdate(range)
486 ui.write(("match: %s\n") % m(d[0]))
487 ui.write(("match: %s\n") % m(d[0]))
487
488
488 @command('debugdeltachain',
489 @command('debugdeltachain',
489 commands.debugrevlogopts + commands.formatteropts,
490 commands.debugrevlogopts + commands.formatteropts,
490 _('-c|-m|FILE'),
491 _('-c|-m|FILE'),
491 optionalrepo=True)
492 optionalrepo=True)
492 def debugdeltachain(ui, repo, file_=None, **opts):
493 def debugdeltachain(ui, repo, file_=None, **opts):
493 """dump information about delta chains in a revlog
494 """dump information about delta chains in a revlog
494
495
495 Output can be templatized. Available template keywords are:
496 Output can be templatized. Available template keywords are:
496
497
497 :``rev``: revision number
498 :``rev``: revision number
498 :``chainid``: delta chain identifier (numbered by unique base)
499 :``chainid``: delta chain identifier (numbered by unique base)
499 :``chainlen``: delta chain length to this revision
500 :``chainlen``: delta chain length to this revision
500 :``prevrev``: previous revision in delta chain
501 :``prevrev``: previous revision in delta chain
501 :``deltatype``: role of delta / how it was computed
502 :``deltatype``: role of delta / how it was computed
502 :``compsize``: compressed size of revision
503 :``compsize``: compressed size of revision
503 :``uncompsize``: uncompressed size of revision
504 :``uncompsize``: uncompressed size of revision
504 :``chainsize``: total size of compressed revisions in chain
505 :``chainsize``: total size of compressed revisions in chain
505 :``chainratio``: total chain size divided by uncompressed revision size
506 :``chainratio``: total chain size divided by uncompressed revision size
506 (new delta chains typically start at ratio 2.00)
507 (new delta chains typically start at ratio 2.00)
507 :``lindist``: linear distance from base revision in delta chain to end
508 :``lindist``: linear distance from base revision in delta chain to end
508 of this revision
509 of this revision
509 :``extradist``: total size of revisions not part of this delta chain from
510 :``extradist``: total size of revisions not part of this delta chain from
510 base of delta chain to end of this revision; a measurement
511 base of delta chain to end of this revision; a measurement
511 of how much extra data we need to read/seek across to read
512 of how much extra data we need to read/seek across to read
512 the delta chain for this revision
513 the delta chain for this revision
513 :``extraratio``: extradist divided by chainsize; another representation of
514 :``extraratio``: extradist divided by chainsize; another representation of
514 how much unrelated data is needed to load this delta chain
515 how much unrelated data is needed to load this delta chain
515 """
516 """
516 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
517 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
517 index = r.index
518 index = r.index
518 generaldelta = r.version & revlog.REVLOGGENERALDELTA
519 generaldelta = r.version & revlog.REVLOGGENERALDELTA
519
520
520 def revinfo(rev):
521 def revinfo(rev):
521 e = index[rev]
522 e = index[rev]
522 compsize = e[1]
523 compsize = e[1]
523 uncompsize = e[2]
524 uncompsize = e[2]
524 chainsize = 0
525 chainsize = 0
525
526
526 if generaldelta:
527 if generaldelta:
527 if e[3] == e[5]:
528 if e[3] == e[5]:
528 deltatype = 'p1'
529 deltatype = 'p1'
529 elif e[3] == e[6]:
530 elif e[3] == e[6]:
530 deltatype = 'p2'
531 deltatype = 'p2'
531 elif e[3] == rev - 1:
532 elif e[3] == rev - 1:
532 deltatype = 'prev'
533 deltatype = 'prev'
533 elif e[3] == rev:
534 elif e[3] == rev:
534 deltatype = 'base'
535 deltatype = 'base'
535 else:
536 else:
536 deltatype = 'other'
537 deltatype = 'other'
537 else:
538 else:
538 if e[3] == rev:
539 if e[3] == rev:
539 deltatype = 'base'
540 deltatype = 'base'
540 else:
541 else:
541 deltatype = 'prev'
542 deltatype = 'prev'
542
543
543 chain = r._deltachain(rev)[0]
544 chain = r._deltachain(rev)[0]
544 for iterrev in chain:
545 for iterrev in chain:
545 e = index[iterrev]
546 e = index[iterrev]
546 chainsize += e[1]
547 chainsize += e[1]
547
548
548 return compsize, uncompsize, deltatype, chain, chainsize
549 return compsize, uncompsize, deltatype, chain, chainsize
549
550
550 fm = ui.formatter('debugdeltachain', opts)
551 fm = ui.formatter('debugdeltachain', opts)
551
552
552 fm.plain(' rev chain# chainlen prev delta '
553 fm.plain(' rev chain# chainlen prev delta '
553 'size rawsize chainsize ratio lindist extradist '
554 'size rawsize chainsize ratio lindist extradist '
554 'extraratio\n')
555 'extraratio\n')
555
556
556 chainbases = {}
557 chainbases = {}
557 for rev in r:
558 for rev in r:
558 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
559 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
559 chainbase = chain[0]
560 chainbase = chain[0]
560 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
561 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
561 basestart = r.start(chainbase)
562 basestart = r.start(chainbase)
562 revstart = r.start(rev)
563 revstart = r.start(rev)
563 lineardist = revstart + comp - basestart
564 lineardist = revstart + comp - basestart
564 extradist = lineardist - chainsize
565 extradist = lineardist - chainsize
565 try:
566 try:
566 prevrev = chain[-2]
567 prevrev = chain[-2]
567 except IndexError:
568 except IndexError:
568 prevrev = -1
569 prevrev = -1
569
570
570 chainratio = float(chainsize) / float(uncomp)
571 chainratio = float(chainsize) / float(uncomp)
571 extraratio = float(extradist) / float(chainsize)
572 extraratio = float(extradist) / float(chainsize)
572
573
573 fm.startitem()
574 fm.startitem()
574 fm.write('rev chainid chainlen prevrev deltatype compsize '
575 fm.write('rev chainid chainlen prevrev deltatype compsize '
575 'uncompsize chainsize chainratio lindist extradist '
576 'uncompsize chainsize chainratio lindist extradist '
576 'extraratio',
577 'extraratio',
577 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
578 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
578 rev, chainid, len(chain), prevrev, deltatype, comp,
579 rev, chainid, len(chain), prevrev, deltatype, comp,
579 uncomp, chainsize, chainratio, lineardist, extradist,
580 uncomp, chainsize, chainratio, lineardist, extradist,
580 extraratio,
581 extraratio,
581 rev=rev, chainid=chainid, chainlen=len(chain),
582 rev=rev, chainid=chainid, chainlen=len(chain),
582 prevrev=prevrev, deltatype=deltatype, compsize=comp,
583 prevrev=prevrev, deltatype=deltatype, compsize=comp,
583 uncompsize=uncomp, chainsize=chainsize,
584 uncompsize=uncomp, chainsize=chainsize,
584 chainratio=chainratio, lindist=lineardist,
585 chainratio=chainratio, lindist=lineardist,
585 extradist=extradist, extraratio=extraratio)
586 extradist=extradist, extraratio=extraratio)
586
587
587 fm.end()
588 fm.end()
588
589
589 @command('debugdirstate|debugstate',
590 @command('debugdirstate|debugstate',
590 [('', 'nodates', None, _('do not display the saved mtime')),
591 [('', 'nodates', None, _('do not display the saved mtime')),
591 ('', 'datesort', None, _('sort by saved mtime'))],
592 ('', 'datesort', None, _('sort by saved mtime'))],
592 _('[OPTION]...'))
593 _('[OPTION]...'))
593 def debugstate(ui, repo, **opts):
594 def debugstate(ui, repo, **opts):
594 """show the contents of the current dirstate"""
595 """show the contents of the current dirstate"""
595
596
596 nodates = opts.get('nodates')
597 nodates = opts.get('nodates')
597 datesort = opts.get('datesort')
598 datesort = opts.get('datesort')
598
599
599 timestr = ""
600 timestr = ""
600 if datesort:
601 if datesort:
601 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
602 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
602 else:
603 else:
603 keyfunc = None # sort by filename
604 keyfunc = None # sort by filename
604 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
605 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
605 if ent[3] == -1:
606 if ent[3] == -1:
606 timestr = 'unset '
607 timestr = 'unset '
607 elif nodates:
608 elif nodates:
608 timestr = 'set '
609 timestr = 'set '
609 else:
610 else:
610 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
611 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
611 time.localtime(ent[3]))
612 time.localtime(ent[3]))
612 if ent[1] & 0o20000:
613 if ent[1] & 0o20000:
613 mode = 'lnk'
614 mode = 'lnk'
614 else:
615 else:
615 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
616 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
616 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
617 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
617 for f in repo.dirstate.copies():
618 for f in repo.dirstate.copies():
618 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
619 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
619
620
620 @command('debugdiscovery',
621 @command('debugdiscovery',
621 [('', 'old', None, _('use old-style discovery')),
622 [('', 'old', None, _('use old-style discovery')),
622 ('', 'nonheads', None,
623 ('', 'nonheads', None,
623 _('use old-style discovery with non-heads included')),
624 _('use old-style discovery with non-heads included')),
624 ] + commands.remoteopts,
625 ] + commands.remoteopts,
625 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
626 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
626 def debugdiscovery(ui, repo, remoteurl="default", **opts):
627 def debugdiscovery(ui, repo, remoteurl="default", **opts):
627 """runs the changeset discovery protocol in isolation"""
628 """runs the changeset discovery protocol in isolation"""
628 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
629 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
629 opts.get('branch'))
630 opts.get('branch'))
630 remote = hg.peer(repo, opts, remoteurl)
631 remote = hg.peer(repo, opts, remoteurl)
631 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
632 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
632
633
633 # make sure tests are repeatable
634 # make sure tests are repeatable
634 random.seed(12323)
635 random.seed(12323)
635
636
636 def doit(localheads, remoteheads, remote=remote):
637 def doit(localheads, remoteheads, remote=remote):
637 if opts.get('old'):
638 if opts.get('old'):
638 if localheads:
639 if localheads:
639 raise error.Abort('cannot use localheads with old style '
640 raise error.Abort('cannot use localheads with old style '
640 'discovery')
641 'discovery')
641 if not util.safehasattr(remote, 'branches'):
642 if not util.safehasattr(remote, 'branches'):
642 # enable in-client legacy support
643 # enable in-client legacy support
643 remote = localrepo.locallegacypeer(remote.local())
644 remote = localrepo.locallegacypeer(remote.local())
644 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
645 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
645 force=True)
646 force=True)
646 common = set(common)
647 common = set(common)
647 if not opts.get('nonheads'):
648 if not opts.get('nonheads'):
648 ui.write(("unpruned common: %s\n") %
649 ui.write(("unpruned common: %s\n") %
649 " ".join(sorted(short(n) for n in common)))
650 " ".join(sorted(short(n) for n in common)))
650 dag = dagutil.revlogdag(repo.changelog)
651 dag = dagutil.revlogdag(repo.changelog)
651 all = dag.ancestorset(dag.internalizeall(common))
652 all = dag.ancestorset(dag.internalizeall(common))
652 common = dag.externalizeall(dag.headsetofconnecteds(all))
653 common = dag.externalizeall(dag.headsetofconnecteds(all))
653 else:
654 else:
654 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
655 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
655 common = set(common)
656 common = set(common)
656 rheads = set(hds)
657 rheads = set(hds)
657 lheads = set(repo.heads())
658 lheads = set(repo.heads())
658 ui.write(("common heads: %s\n") %
659 ui.write(("common heads: %s\n") %
659 " ".join(sorted(short(n) for n in common)))
660 " ".join(sorted(short(n) for n in common)))
660 if lheads <= common:
661 if lheads <= common:
661 ui.write(("local is subset\n"))
662 ui.write(("local is subset\n"))
662 elif rheads <= common:
663 elif rheads <= common:
663 ui.write(("remote is subset\n"))
664 ui.write(("remote is subset\n"))
664
665
665 serverlogs = opts.get('serverlog')
666 serverlogs = opts.get('serverlog')
666 if serverlogs:
667 if serverlogs:
667 for filename in serverlogs:
668 for filename in serverlogs:
668 with open(filename, 'r') as logfile:
669 with open(filename, 'r') as logfile:
669 line = logfile.readline()
670 line = logfile.readline()
670 while line:
671 while line:
671 parts = line.strip().split(';')
672 parts = line.strip().split(';')
672 op = parts[1]
673 op = parts[1]
673 if op == 'cg':
674 if op == 'cg':
674 pass
675 pass
675 elif op == 'cgss':
676 elif op == 'cgss':
676 doit(parts[2].split(' '), parts[3].split(' '))
677 doit(parts[2].split(' '), parts[3].split(' '))
677 elif op == 'unb':
678 elif op == 'unb':
678 doit(parts[3].split(' '), parts[2].split(' '))
679 doit(parts[3].split(' '), parts[2].split(' '))
679 line = logfile.readline()
680 line = logfile.readline()
680 else:
681 else:
681 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
682 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
682 opts.get('remote_head'))
683 opts.get('remote_head'))
683 localrevs = opts.get('local_head')
684 localrevs = opts.get('local_head')
684 doit(localrevs, remoterevs)
685 doit(localrevs, remoterevs)
685
686
686 @command('debugextensions', commands.formatteropts, [], norepo=True)
687 @command('debugextensions', commands.formatteropts, [], norepo=True)
687 def debugextensions(ui, **opts):
688 def debugextensions(ui, **opts):
688 '''show information about active extensions'''
689 '''show information about active extensions'''
689 exts = extensions.extensions(ui)
690 exts = extensions.extensions(ui)
690 hgver = util.version()
691 hgver = util.version()
691 fm = ui.formatter('debugextensions', opts)
692 fm = ui.formatter('debugextensions', opts)
692 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
693 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
693 isinternal = extensions.ismoduleinternal(extmod)
694 isinternal = extensions.ismoduleinternal(extmod)
694 extsource = extmod.__file__
695 extsource = extmod.__file__
695 if isinternal:
696 if isinternal:
696 exttestedwith = [] # never expose magic string to users
697 exttestedwith = [] # never expose magic string to users
697 else:
698 else:
698 exttestedwith = getattr(extmod, 'testedwith', '').split()
699 exttestedwith = getattr(extmod, 'testedwith', '').split()
699 extbuglink = getattr(extmod, 'buglink', None)
700 extbuglink = getattr(extmod, 'buglink', None)
700
701
701 fm.startitem()
702 fm.startitem()
702
703
703 if ui.quiet or ui.verbose:
704 if ui.quiet or ui.verbose:
704 fm.write('name', '%s\n', extname)
705 fm.write('name', '%s\n', extname)
705 else:
706 else:
706 fm.write('name', '%s', extname)
707 fm.write('name', '%s', extname)
707 if isinternal or hgver in exttestedwith:
708 if isinternal or hgver in exttestedwith:
708 fm.plain('\n')
709 fm.plain('\n')
709 elif not exttestedwith:
710 elif not exttestedwith:
710 fm.plain(_(' (untested!)\n'))
711 fm.plain(_(' (untested!)\n'))
711 else:
712 else:
712 lasttestedversion = exttestedwith[-1]
713 lasttestedversion = exttestedwith[-1]
713 fm.plain(' (%s!)\n' % lasttestedversion)
714 fm.plain(' (%s!)\n' % lasttestedversion)
714
715
715 fm.condwrite(ui.verbose and extsource, 'source',
716 fm.condwrite(ui.verbose and extsource, 'source',
716 _(' location: %s\n'), extsource or "")
717 _(' location: %s\n'), extsource or "")
717
718
718 if ui.verbose:
719 if ui.verbose:
719 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
720 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
720 fm.data(bundled=isinternal)
721 fm.data(bundled=isinternal)
721
722
722 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
723 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
723 _(' tested with: %s\n'),
724 _(' tested with: %s\n'),
724 fm.formatlist(exttestedwith, name='ver'))
725 fm.formatlist(exttestedwith, name='ver'))
725
726
726 fm.condwrite(ui.verbose and extbuglink, 'buglink',
727 fm.condwrite(ui.verbose and extbuglink, 'buglink',
727 _(' bug reporting: %s\n'), extbuglink or "")
728 _(' bug reporting: %s\n'), extbuglink or "")
728
729
729 fm.end()
730 fm.end()
730
731
731 @command('debugfileset',
732 @command('debugfileset',
732 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
733 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
733 _('[-r REV] FILESPEC'))
734 _('[-r REV] FILESPEC'))
734 def debugfileset(ui, repo, expr, **opts):
735 def debugfileset(ui, repo, expr, **opts):
735 '''parse and apply a fileset specification'''
736 '''parse and apply a fileset specification'''
736 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
737 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
737 if ui.verbose:
738 if ui.verbose:
738 tree = fileset.parse(expr)
739 tree = fileset.parse(expr)
739 ui.note(fileset.prettyformat(tree), "\n")
740 ui.note(fileset.prettyformat(tree), "\n")
740
741
741 for f in ctx.getfileset(expr):
742 for f in ctx.getfileset(expr):
742 ui.write("%s\n" % f)
743 ui.write("%s\n" % f)
743
744
744 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
745 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
745 def debugfsinfo(ui, path="."):
746 def debugfsinfo(ui, path="."):
746 """show information detected about current filesystem"""
747 """show information detected about current filesystem"""
747 util.writefile('.debugfsinfo', '')
748 util.writefile('.debugfsinfo', '')
748 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
749 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
749 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
750 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
750 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
751 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
751 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
752 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
752 and 'yes' or 'no'))
753 and 'yes' or 'no'))
753 os.unlink('.debugfsinfo')
754 os.unlink('.debugfsinfo')
754
755
755 @command('debuggetbundle',
756 @command('debuggetbundle',
756 [('H', 'head', [], _('id of head node'), _('ID')),
757 [('H', 'head', [], _('id of head node'), _('ID')),
757 ('C', 'common', [], _('id of common node'), _('ID')),
758 ('C', 'common', [], _('id of common node'), _('ID')),
758 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
759 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
759 _('REPO FILE [-H|-C ID]...'),
760 _('REPO FILE [-H|-C ID]...'),
760 norepo=True)
761 norepo=True)
761 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
762 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
762 """retrieves a bundle from a repo
763 """retrieves a bundle from a repo
763
764
764 Every ID must be a full-length hex node id string. Saves the bundle to the
765 Every ID must be a full-length hex node id string. Saves the bundle to the
765 given file.
766 given file.
766 """
767 """
767 repo = hg.peer(ui, opts, repopath)
768 repo = hg.peer(ui, opts, repopath)
768 if not repo.capable('getbundle'):
769 if not repo.capable('getbundle'):
769 raise error.Abort("getbundle() not supported by target repository")
770 raise error.Abort("getbundle() not supported by target repository")
770 args = {}
771 args = {}
771 if common:
772 if common:
772 args['common'] = [bin(s) for s in common]
773 args['common'] = [bin(s) for s in common]
773 if head:
774 if head:
774 args['heads'] = [bin(s) for s in head]
775 args['heads'] = [bin(s) for s in head]
775 # TODO: get desired bundlecaps from command line.
776 # TODO: get desired bundlecaps from command line.
776 args['bundlecaps'] = None
777 args['bundlecaps'] = None
777 bundle = repo.getbundle('debug', **args)
778 bundle = repo.getbundle('debug', **args)
778
779
779 bundletype = opts.get('type', 'bzip2').lower()
780 bundletype = opts.get('type', 'bzip2').lower()
780 btypes = {'none': 'HG10UN',
781 btypes = {'none': 'HG10UN',
781 'bzip2': 'HG10BZ',
782 'bzip2': 'HG10BZ',
782 'gzip': 'HG10GZ',
783 'gzip': 'HG10GZ',
783 'bundle2': 'HG20'}
784 'bundle2': 'HG20'}
784 bundletype = btypes.get(bundletype)
785 bundletype = btypes.get(bundletype)
785 if bundletype not in bundle2.bundletypes:
786 if bundletype not in bundle2.bundletypes:
786 raise error.Abort(_('unknown bundle type specified with --type'))
787 raise error.Abort(_('unknown bundle type specified with --type'))
787 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
788 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
788
789
789 @command('debugignore', [], '[FILE]')
790 @command('debugignore', [], '[FILE]')
790 def debugignore(ui, repo, *files, **opts):
791 def debugignore(ui, repo, *files, **opts):
791 """display the combined ignore pattern and information about ignored files
792 """display the combined ignore pattern and information about ignored files
792
793
793 With no argument display the combined ignore pattern.
794 With no argument display the combined ignore pattern.
794
795
795 Given space separated file names, shows if the given file is ignored and
796 Given space separated file names, shows if the given file is ignored and
796 if so, show the ignore rule (file and line number) that matched it.
797 if so, show the ignore rule (file and line number) that matched it.
797 """
798 """
798 ignore = repo.dirstate._ignore
799 ignore = repo.dirstate._ignore
799 if not files:
800 if not files:
800 # Show all the patterns
801 # Show all the patterns
801 includepat = getattr(ignore, 'includepat', None)
802 includepat = getattr(ignore, 'includepat', None)
802 if includepat is not None:
803 if includepat is not None:
803 ui.write("%s\n" % includepat)
804 ui.write("%s\n" % includepat)
804 else:
805 else:
805 raise error.Abort(_("no ignore patterns found"))
806 raise error.Abort(_("no ignore patterns found"))
806 else:
807 else:
807 for f in files:
808 for f in files:
808 nf = util.normpath(f)
809 nf = util.normpath(f)
809 ignored = None
810 ignored = None
810 ignoredata = None
811 ignoredata = None
811 if nf != '.':
812 if nf != '.':
812 if ignore(nf):
813 if ignore(nf):
813 ignored = nf
814 ignored = nf
814 ignoredata = repo.dirstate._ignorefileandline(nf)
815 ignoredata = repo.dirstate._ignorefileandline(nf)
815 else:
816 else:
816 for p in util.finddirs(nf):
817 for p in util.finddirs(nf):
817 if ignore(p):
818 if ignore(p):
818 ignored = p
819 ignored = p
819 ignoredata = repo.dirstate._ignorefileandline(p)
820 ignoredata = repo.dirstate._ignorefileandline(p)
820 break
821 break
821 if ignored:
822 if ignored:
822 if ignored == nf:
823 if ignored == nf:
823 ui.write(_("%s is ignored\n") % f)
824 ui.write(_("%s is ignored\n") % f)
824 else:
825 else:
825 ui.write(_("%s is ignored because of "
826 ui.write(_("%s is ignored because of "
826 "containing folder %s\n")
827 "containing folder %s\n")
827 % (f, ignored))
828 % (f, ignored))
828 ignorefile, lineno, line = ignoredata
829 ignorefile, lineno, line = ignoredata
829 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
830 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
830 % (ignorefile, lineno, line))
831 % (ignorefile, lineno, line))
831 else:
832 else:
832 ui.write(_("%s is not ignored\n") % f)
833 ui.write(_("%s is not ignored\n") % f)
833
834
834 @command('debugindex', commands.debugrevlogopts +
835 @command('debugindex', commands.debugrevlogopts +
835 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
836 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
836 _('[-f FORMAT] -c|-m|FILE'),
837 _('[-f FORMAT] -c|-m|FILE'),
837 optionalrepo=True)
838 optionalrepo=True)
838 def debugindex(ui, repo, file_=None, **opts):
839 def debugindex(ui, repo, file_=None, **opts):
839 """dump the contents of an index file"""
840 """dump the contents of an index file"""
840 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
841 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
841 format = opts.get('format', 0)
842 format = opts.get('format', 0)
842 if format not in (0, 1):
843 if format not in (0, 1):
843 raise error.Abort(_("unknown format %d") % format)
844 raise error.Abort(_("unknown format %d") % format)
844
845
845 generaldelta = r.version & revlog.REVLOGGENERALDELTA
846 generaldelta = r.version & revlog.REVLOGGENERALDELTA
846 if generaldelta:
847 if generaldelta:
847 basehdr = ' delta'
848 basehdr = ' delta'
848 else:
849 else:
849 basehdr = ' base'
850 basehdr = ' base'
850
851
851 if ui.debugflag:
852 if ui.debugflag:
852 shortfn = hex
853 shortfn = hex
853 else:
854 else:
854 shortfn = short
855 shortfn = short
855
856
856 # There might not be anything in r, so have a sane default
857 # There might not be anything in r, so have a sane default
857 idlen = 12
858 idlen = 12
858 for i in r:
859 for i in r:
859 idlen = len(shortfn(r.node(i)))
860 idlen = len(shortfn(r.node(i)))
860 break
861 break
861
862
862 if format == 0:
863 if format == 0:
863 ui.write((" rev offset length " + basehdr + " linkrev"
864 ui.write((" rev offset length " + basehdr + " linkrev"
864 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
865 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
865 elif format == 1:
866 elif format == 1:
866 ui.write((" rev flag offset length"
867 ui.write((" rev flag offset length"
867 " size " + basehdr + " link p1 p2"
868 " size " + basehdr + " link p1 p2"
868 " %s\n") % "nodeid".rjust(idlen))
869 " %s\n") % "nodeid".rjust(idlen))
869
870
870 for i in r:
871 for i in r:
871 node = r.node(i)
872 node = r.node(i)
872 if generaldelta:
873 if generaldelta:
873 base = r.deltaparent(i)
874 base = r.deltaparent(i)
874 else:
875 else:
875 base = r.chainbase(i)
876 base = r.chainbase(i)
876 if format == 0:
877 if format == 0:
877 try:
878 try:
878 pp = r.parents(node)
879 pp = r.parents(node)
879 except Exception:
880 except Exception:
880 pp = [nullid, nullid]
881 pp = [nullid, nullid]
881 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
882 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
882 i, r.start(i), r.length(i), base, r.linkrev(i),
883 i, r.start(i), r.length(i), base, r.linkrev(i),
883 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
884 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
884 elif format == 1:
885 elif format == 1:
885 pr = r.parentrevs(i)
886 pr = r.parentrevs(i)
886 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
887 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
887 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
888 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
888 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
889 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
889
890
890 @command('debugindexdot', commands.debugrevlogopts,
891 @command('debugindexdot', commands.debugrevlogopts,
891 _('-c|-m|FILE'), optionalrepo=True)
892 _('-c|-m|FILE'), optionalrepo=True)
892 def debugindexdot(ui, repo, file_=None, **opts):
893 def debugindexdot(ui, repo, file_=None, **opts):
893 """dump an index DAG as a graphviz dot file"""
894 """dump an index DAG as a graphviz dot file"""
894 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
895 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
895 ui.write(("digraph G {\n"))
896 ui.write(("digraph G {\n"))
896 for i in r:
897 for i in r:
897 node = r.node(i)
898 node = r.node(i)
898 pp = r.parents(node)
899 pp = r.parents(node)
899 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
900 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
900 if pp[1] != nullid:
901 if pp[1] != nullid:
901 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
902 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
902 ui.write("}\n")
903 ui.write("}\n")
903
904
904 @command('debuginstall', [] + commands.formatteropts, '', norepo=True)
905 @command('debuginstall', [] + commands.formatteropts, '', norepo=True)
905 def debuginstall(ui, **opts):
906 def debuginstall(ui, **opts):
906 '''test Mercurial installation
907 '''test Mercurial installation
907
908
908 Returns 0 on success.
909 Returns 0 on success.
909 '''
910 '''
910
911
911 def writetemp(contents):
912 def writetemp(contents):
912 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
913 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
913 f = os.fdopen(fd, pycompat.sysstr("wb"))
914 f = os.fdopen(fd, pycompat.sysstr("wb"))
914 f.write(contents)
915 f.write(contents)
915 f.close()
916 f.close()
916 return name
917 return name
917
918
918 problems = 0
919 problems = 0
919
920
920 fm = ui.formatter('debuginstall', opts)
921 fm = ui.formatter('debuginstall', opts)
921 fm.startitem()
922 fm.startitem()
922
923
923 # encoding
924 # encoding
924 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
925 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
925 err = None
926 err = None
926 try:
927 try:
927 encoding.fromlocal("test")
928 encoding.fromlocal("test")
928 except error.Abort as inst:
929 except error.Abort as inst:
929 err = inst
930 err = inst
930 problems += 1
931 problems += 1
931 fm.condwrite(err, 'encodingerror', _(" %s\n"
932 fm.condwrite(err, 'encodingerror', _(" %s\n"
932 " (check that your locale is properly set)\n"), err)
933 " (check that your locale is properly set)\n"), err)
933
934
934 # Python
935 # Python
935 fm.write('pythonexe', _("checking Python executable (%s)\n"),
936 fm.write('pythonexe', _("checking Python executable (%s)\n"),
936 pycompat.sysexecutable)
937 pycompat.sysexecutable)
937 fm.write('pythonver', _("checking Python version (%s)\n"),
938 fm.write('pythonver', _("checking Python version (%s)\n"),
938 ("%d.%d.%d" % sys.version_info[:3]))
939 ("%d.%d.%d" % sys.version_info[:3]))
939 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
940 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
940 os.path.dirname(pycompat.fsencode(os.__file__)))
941 os.path.dirname(pycompat.fsencode(os.__file__)))
941
942
942 security = set(sslutil.supportedprotocols)
943 security = set(sslutil.supportedprotocols)
943 if sslutil.hassni:
944 if sslutil.hassni:
944 security.add('sni')
945 security.add('sni')
945
946
946 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
947 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
947 fm.formatlist(sorted(security), name='protocol',
948 fm.formatlist(sorted(security), name='protocol',
948 fmt='%s', sep=','))
949 fmt='%s', sep=','))
949
950
950 # These are warnings, not errors. So don't increment problem count. This
951 # These are warnings, not errors. So don't increment problem count. This
951 # may change in the future.
952 # may change in the future.
952 if 'tls1.2' not in security:
953 if 'tls1.2' not in security:
953 fm.plain(_(' TLS 1.2 not supported by Python install; '
954 fm.plain(_(' TLS 1.2 not supported by Python install; '
954 'network connections lack modern security\n'))
955 'network connections lack modern security\n'))
955 if 'sni' not in security:
956 if 'sni' not in security:
956 fm.plain(_(' SNI not supported by Python install; may have '
957 fm.plain(_(' SNI not supported by Python install; may have '
957 'connectivity issues with some servers\n'))
958 'connectivity issues with some servers\n'))
958
959
959 # TODO print CA cert info
960 # TODO print CA cert info
960
961
961 # hg version
962 # hg version
962 hgver = util.version()
963 hgver = util.version()
963 fm.write('hgver', _("checking Mercurial version (%s)\n"),
964 fm.write('hgver', _("checking Mercurial version (%s)\n"),
964 hgver.split('+')[0])
965 hgver.split('+')[0])
965 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
966 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
966 '+'.join(hgver.split('+')[1:]))
967 '+'.join(hgver.split('+')[1:]))
967
968
968 # compiled modules
969 # compiled modules
969 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
970 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
970 policy.policy)
971 policy.policy)
971 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
972 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
972 os.path.dirname(__file__))
973 os.path.dirname(__file__))
973
974
974 err = None
975 err = None
975 try:
976 try:
976 from . import (
977 from . import (
977 base85,
978 base85,
978 bdiff,
979 bdiff,
979 mpatch,
980 mpatch,
980 osutil,
981 osutil,
981 )
982 )
982 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
983 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
983 except Exception as inst:
984 except Exception as inst:
984 err = inst
985 err = inst
985 problems += 1
986 problems += 1
986 fm.condwrite(err, 'extensionserror', " %s\n", err)
987 fm.condwrite(err, 'extensionserror', " %s\n", err)
987
988
988 compengines = util.compengines._engines.values()
989 compengines = util.compengines._engines.values()
989 fm.write('compengines', _('checking registered compression engines (%s)\n'),
990 fm.write('compengines', _('checking registered compression engines (%s)\n'),
990 fm.formatlist(sorted(e.name() for e in compengines),
991 fm.formatlist(sorted(e.name() for e in compengines),
991 name='compengine', fmt='%s', sep=', '))
992 name='compengine', fmt='%s', sep=', '))
992 fm.write('compenginesavail', _('checking available compression engines '
993 fm.write('compenginesavail', _('checking available compression engines '
993 '(%s)\n'),
994 '(%s)\n'),
994 fm.formatlist(sorted(e.name() for e in compengines
995 fm.formatlist(sorted(e.name() for e in compengines
995 if e.available()),
996 if e.available()),
996 name='compengine', fmt='%s', sep=', '))
997 name='compengine', fmt='%s', sep=', '))
997 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
998 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
998 fm.write('compenginesserver', _('checking available compression engines '
999 fm.write('compenginesserver', _('checking available compression engines '
999 'for wire protocol (%s)\n'),
1000 'for wire protocol (%s)\n'),
1000 fm.formatlist([e.name() for e in wirecompengines
1001 fm.formatlist([e.name() for e in wirecompengines
1001 if e.wireprotosupport()],
1002 if e.wireprotosupport()],
1002 name='compengine', fmt='%s', sep=', '))
1003 name='compengine', fmt='%s', sep=', '))
1003
1004
1004 # templates
1005 # templates
1005 p = templater.templatepaths()
1006 p = templater.templatepaths()
1006 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1007 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1007 fm.condwrite(not p, '', _(" no template directories found\n"))
1008 fm.condwrite(not p, '', _(" no template directories found\n"))
1008 if p:
1009 if p:
1009 m = templater.templatepath("map-cmdline.default")
1010 m = templater.templatepath("map-cmdline.default")
1010 if m:
1011 if m:
1011 # template found, check if it is working
1012 # template found, check if it is working
1012 err = None
1013 err = None
1013 try:
1014 try:
1014 templater.templater.frommapfile(m)
1015 templater.templater.frommapfile(m)
1015 except Exception as inst:
1016 except Exception as inst:
1016 err = inst
1017 err = inst
1017 p = None
1018 p = None
1018 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1019 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1019 else:
1020 else:
1020 p = None
1021 p = None
1021 fm.condwrite(p, 'defaulttemplate',
1022 fm.condwrite(p, 'defaulttemplate',
1022 _("checking default template (%s)\n"), m)
1023 _("checking default template (%s)\n"), m)
1023 fm.condwrite(not m, 'defaulttemplatenotfound',
1024 fm.condwrite(not m, 'defaulttemplatenotfound',
1024 _(" template '%s' not found\n"), "default")
1025 _(" template '%s' not found\n"), "default")
1025 if not p:
1026 if not p:
1026 problems += 1
1027 problems += 1
1027 fm.condwrite(not p, '',
1028 fm.condwrite(not p, '',
1028 _(" (templates seem to have been installed incorrectly)\n"))
1029 _(" (templates seem to have been installed incorrectly)\n"))
1029
1030
1030 # editor
1031 # editor
1031 editor = ui.geteditor()
1032 editor = ui.geteditor()
1032 editor = util.expandpath(editor)
1033 editor = util.expandpath(editor)
1033 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
1034 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
1034 cmdpath = util.findexe(pycompat.shlexsplit(editor)[0])
1035 cmdpath = util.findexe(pycompat.shlexsplit(editor)[0])
1035 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1036 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1036 _(" No commit editor set and can't find %s in PATH\n"
1037 _(" No commit editor set and can't find %s in PATH\n"
1037 " (specify a commit editor in your configuration"
1038 " (specify a commit editor in your configuration"
1038 " file)\n"), not cmdpath and editor == 'vi' and editor)
1039 " file)\n"), not cmdpath and editor == 'vi' and editor)
1039 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1040 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1040 _(" Can't find editor '%s' in PATH\n"
1041 _(" Can't find editor '%s' in PATH\n"
1041 " (specify a commit editor in your configuration"
1042 " (specify a commit editor in your configuration"
1042 " file)\n"), not cmdpath and editor)
1043 " file)\n"), not cmdpath and editor)
1043 if not cmdpath and editor != 'vi':
1044 if not cmdpath and editor != 'vi':
1044 problems += 1
1045 problems += 1
1045
1046
1046 # check username
1047 # check username
1047 username = None
1048 username = None
1048 err = None
1049 err = None
1049 try:
1050 try:
1050 username = ui.username()
1051 username = ui.username()
1051 except error.Abort as e:
1052 except error.Abort as e:
1052 err = e
1053 err = e
1053 problems += 1
1054 problems += 1
1054
1055
1055 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1056 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1056 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1057 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1057 " (specify a username in your configuration file)\n"), err)
1058 " (specify a username in your configuration file)\n"), err)
1058
1059
1059 fm.condwrite(not problems, '',
1060 fm.condwrite(not problems, '',
1060 _("no problems detected\n"))
1061 _("no problems detected\n"))
1061 if not problems:
1062 if not problems:
1062 fm.data(problems=problems)
1063 fm.data(problems=problems)
1063 fm.condwrite(problems, 'problems',
1064 fm.condwrite(problems, 'problems',
1064 _("%d problems detected,"
1065 _("%d problems detected,"
1065 " please check your install!\n"), problems)
1066 " please check your install!\n"), problems)
1066 fm.end()
1067 fm.end()
1067
1068
1068 return problems
1069 return problems
1069
1070
1070 @command('debugknown', [], _('REPO ID...'), norepo=True)
1071 @command('debugknown', [], _('REPO ID...'), norepo=True)
1071 def debugknown(ui, repopath, *ids, **opts):
1072 def debugknown(ui, repopath, *ids, **opts):
1072 """test whether node ids are known to a repo
1073 """test whether node ids are known to a repo
1073
1074
1074 Every ID must be a full-length hex node id string. Returns a list of 0s
1075 Every ID must be a full-length hex node id string. Returns a list of 0s
1075 and 1s indicating unknown/known.
1076 and 1s indicating unknown/known.
1076 """
1077 """
1077 repo = hg.peer(ui, opts, repopath)
1078 repo = hg.peer(ui, opts, repopath)
1078 if not repo.capable('known'):
1079 if not repo.capable('known'):
1079 raise error.Abort("known() not supported by target repository")
1080 raise error.Abort("known() not supported by target repository")
1080 flags = repo.known([bin(s) for s in ids])
1081 flags = repo.known([bin(s) for s in ids])
1081 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1082 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1082
1083
1083 @command('debuglabelcomplete', [], _('LABEL...'))
1084 @command('debuglabelcomplete', [], _('LABEL...'))
1084 def debuglabelcomplete(ui, repo, *args):
1085 def debuglabelcomplete(ui, repo, *args):
1085 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1086 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1086 commands.debugnamecomplete(ui, repo, *args)
1087 commands.debugnamecomplete(ui, repo, *args)
1087
1088
1088 @command('debuglocks',
1089 @command('debuglocks',
1089 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1090 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1090 ('W', 'force-wlock', None,
1091 ('W', 'force-wlock', None,
1091 _('free the working state lock (DANGEROUS)'))],
1092 _('free the working state lock (DANGEROUS)'))],
1092 _('[OPTION]...'))
1093 _('[OPTION]...'))
1093 def debuglocks(ui, repo, **opts):
1094 def debuglocks(ui, repo, **opts):
1094 """show or modify state of locks
1095 """show or modify state of locks
1095
1096
1096 By default, this command will show which locks are held. This
1097 By default, this command will show which locks are held. This
1097 includes the user and process holding the lock, the amount of time
1098 includes the user and process holding the lock, the amount of time
1098 the lock has been held, and the machine name where the process is
1099 the lock has been held, and the machine name where the process is
1099 running if it's not local.
1100 running if it's not local.
1100
1101
1101 Locks protect the integrity of Mercurial's data, so should be
1102 Locks protect the integrity of Mercurial's data, so should be
1102 treated with care. System crashes or other interruptions may cause
1103 treated with care. System crashes or other interruptions may cause
1103 locks to not be properly released, though Mercurial will usually
1104 locks to not be properly released, though Mercurial will usually
1104 detect and remove such stale locks automatically.
1105 detect and remove such stale locks automatically.
1105
1106
1106 However, detecting stale locks may not always be possible (for
1107 However, detecting stale locks may not always be possible (for
1107 instance, on a shared filesystem). Removing locks may also be
1108 instance, on a shared filesystem). Removing locks may also be
1108 blocked by filesystem permissions.
1109 blocked by filesystem permissions.
1109
1110
1110 Returns 0 if no locks are held.
1111 Returns 0 if no locks are held.
1111
1112
1112 """
1113 """
1113
1114
1114 if opts.get('force_lock'):
1115 if opts.get('force_lock'):
1115 repo.svfs.unlink('lock')
1116 repo.svfs.unlink('lock')
1116 if opts.get('force_wlock'):
1117 if opts.get('force_wlock'):
1117 repo.vfs.unlink('wlock')
1118 repo.vfs.unlink('wlock')
1118 if opts.get('force_lock') or opts.get('force_lock'):
1119 if opts.get('force_lock') or opts.get('force_lock'):
1119 return 0
1120 return 0
1120
1121
1121 now = time.time()
1122 now = time.time()
1122 held = 0
1123 held = 0
1123
1124
1124 def report(vfs, name, method):
1125 def report(vfs, name, method):
1125 # this causes stale locks to get reaped for more accurate reporting
1126 # this causes stale locks to get reaped for more accurate reporting
1126 try:
1127 try:
1127 l = method(False)
1128 l = method(False)
1128 except error.LockHeld:
1129 except error.LockHeld:
1129 l = None
1130 l = None
1130
1131
1131 if l:
1132 if l:
1132 l.release()
1133 l.release()
1133 else:
1134 else:
1134 try:
1135 try:
1135 stat = vfs.lstat(name)
1136 stat = vfs.lstat(name)
1136 age = now - stat.st_mtime
1137 age = now - stat.st_mtime
1137 user = util.username(stat.st_uid)
1138 user = util.username(stat.st_uid)
1138 locker = vfs.readlock(name)
1139 locker = vfs.readlock(name)
1139 if ":" in locker:
1140 if ":" in locker:
1140 host, pid = locker.split(':')
1141 host, pid = locker.split(':')
1141 if host == socket.gethostname():
1142 if host == socket.gethostname():
1142 locker = 'user %s, process %s' % (user, pid)
1143 locker = 'user %s, process %s' % (user, pid)
1143 else:
1144 else:
1144 locker = 'user %s, process %s, host %s' \
1145 locker = 'user %s, process %s, host %s' \
1145 % (user, pid, host)
1146 % (user, pid, host)
1146 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1147 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1147 return 1
1148 return 1
1148 except OSError as e:
1149 except OSError as e:
1149 if e.errno != errno.ENOENT:
1150 if e.errno != errno.ENOENT:
1150 raise
1151 raise
1151
1152
1152 ui.write(("%-6s free\n") % (name + ":"))
1153 ui.write(("%-6s free\n") % (name + ":"))
1153 return 0
1154 return 0
1154
1155
1155 held += report(repo.svfs, "lock", repo.lock)
1156 held += report(repo.svfs, "lock", repo.lock)
1156 held += report(repo.vfs, "wlock", repo.wlock)
1157 held += report(repo.vfs, "wlock", repo.wlock)
1157
1158
1158 return held
1159 return held
1159
1160
1160 @command('debugmergestate', [], '')
1161 @command('debugmergestate', [], '')
1161 def debugmergestate(ui, repo, *args):
1162 def debugmergestate(ui, repo, *args):
1162 """print merge state
1163 """print merge state
1163
1164
1164 Use --verbose to print out information about whether v1 or v2 merge state
1165 Use --verbose to print out information about whether v1 or v2 merge state
1165 was chosen."""
1166 was chosen."""
1166 def _hashornull(h):
1167 def _hashornull(h):
1167 if h == nullhex:
1168 if h == nullhex:
1168 return 'null'
1169 return 'null'
1169 else:
1170 else:
1170 return h
1171 return h
1171
1172
1172 def printrecords(version):
1173 def printrecords(version):
1173 ui.write(('* version %s records\n') % version)
1174 ui.write(('* version %s records\n') % version)
1174 if version == 1:
1175 if version == 1:
1175 records = v1records
1176 records = v1records
1176 else:
1177 else:
1177 records = v2records
1178 records = v2records
1178
1179
1179 for rtype, record in records:
1180 for rtype, record in records:
1180 # pretty print some record types
1181 # pretty print some record types
1181 if rtype == 'L':
1182 if rtype == 'L':
1182 ui.write(('local: %s\n') % record)
1183 ui.write(('local: %s\n') % record)
1183 elif rtype == 'O':
1184 elif rtype == 'O':
1184 ui.write(('other: %s\n') % record)
1185 ui.write(('other: %s\n') % record)
1185 elif rtype == 'm':
1186 elif rtype == 'm':
1186 driver, mdstate = record.split('\0', 1)
1187 driver, mdstate = record.split('\0', 1)
1187 ui.write(('merge driver: %s (state "%s")\n')
1188 ui.write(('merge driver: %s (state "%s")\n')
1188 % (driver, mdstate))
1189 % (driver, mdstate))
1189 elif rtype in 'FDC':
1190 elif rtype in 'FDC':
1190 r = record.split('\0')
1191 r = record.split('\0')
1191 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1192 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1192 if version == 1:
1193 if version == 1:
1193 onode = 'not stored in v1 format'
1194 onode = 'not stored in v1 format'
1194 flags = r[7]
1195 flags = r[7]
1195 else:
1196 else:
1196 onode, flags = r[7:9]
1197 onode, flags = r[7:9]
1197 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1198 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1198 % (f, rtype, state, _hashornull(hash)))
1199 % (f, rtype, state, _hashornull(hash)))
1199 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1200 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1200 ui.write((' ancestor path: %s (node %s)\n')
1201 ui.write((' ancestor path: %s (node %s)\n')
1201 % (afile, _hashornull(anode)))
1202 % (afile, _hashornull(anode)))
1202 ui.write((' other path: %s (node %s)\n')
1203 ui.write((' other path: %s (node %s)\n')
1203 % (ofile, _hashornull(onode)))
1204 % (ofile, _hashornull(onode)))
1204 elif rtype == 'f':
1205 elif rtype == 'f':
1205 filename, rawextras = record.split('\0', 1)
1206 filename, rawextras = record.split('\0', 1)
1206 extras = rawextras.split('\0')
1207 extras = rawextras.split('\0')
1207 i = 0
1208 i = 0
1208 extrastrings = []
1209 extrastrings = []
1209 while i < len(extras):
1210 while i < len(extras):
1210 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1211 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1211 i += 2
1212 i += 2
1212
1213
1213 ui.write(('file extras: %s (%s)\n')
1214 ui.write(('file extras: %s (%s)\n')
1214 % (filename, ', '.join(extrastrings)))
1215 % (filename, ', '.join(extrastrings)))
1215 elif rtype == 'l':
1216 elif rtype == 'l':
1216 labels = record.split('\0', 2)
1217 labels = record.split('\0', 2)
1217 labels = [l for l in labels if len(l) > 0]
1218 labels = [l for l in labels if len(l) > 0]
1218 ui.write(('labels:\n'))
1219 ui.write(('labels:\n'))
1219 ui.write((' local: %s\n' % labels[0]))
1220 ui.write((' local: %s\n' % labels[0]))
1220 ui.write((' other: %s\n' % labels[1]))
1221 ui.write((' other: %s\n' % labels[1]))
1221 if len(labels) > 2:
1222 if len(labels) > 2:
1222 ui.write((' base: %s\n' % labels[2]))
1223 ui.write((' base: %s\n' % labels[2]))
1223 else:
1224 else:
1224 ui.write(('unrecognized entry: %s\t%s\n')
1225 ui.write(('unrecognized entry: %s\t%s\n')
1225 % (rtype, record.replace('\0', '\t')))
1226 % (rtype, record.replace('\0', '\t')))
1226
1227
1227 # Avoid mergestate.read() since it may raise an exception for unsupported
1228 # Avoid mergestate.read() since it may raise an exception for unsupported
1228 # merge state records. We shouldn't be doing this, but this is OK since this
1229 # merge state records. We shouldn't be doing this, but this is OK since this
1229 # command is pretty low-level.
1230 # command is pretty low-level.
1230 ms = mergemod.mergestate(repo)
1231 ms = mergemod.mergestate(repo)
1231
1232
1232 # sort so that reasonable information is on top
1233 # sort so that reasonable information is on top
1233 v1records = ms._readrecordsv1()
1234 v1records = ms._readrecordsv1()
1234 v2records = ms._readrecordsv2()
1235 v2records = ms._readrecordsv2()
1235 order = 'LOml'
1236 order = 'LOml'
1236 def key(r):
1237 def key(r):
1237 idx = order.find(r[0])
1238 idx = order.find(r[0])
1238 if idx == -1:
1239 if idx == -1:
1239 return (1, r[1])
1240 return (1, r[1])
1240 else:
1241 else:
1241 return (0, idx)
1242 return (0, idx)
1242 v1records.sort(key=key)
1243 v1records.sort(key=key)
1243 v2records.sort(key=key)
1244 v2records.sort(key=key)
1244
1245
1245 if not v1records and not v2records:
1246 if not v1records and not v2records:
1246 ui.write(('no merge state found\n'))
1247 ui.write(('no merge state found\n'))
1247 elif not v2records:
1248 elif not v2records:
1248 ui.note(('no version 2 merge state\n'))
1249 ui.note(('no version 2 merge state\n'))
1249 printrecords(1)
1250 printrecords(1)
1250 elif ms._v1v2match(v1records, v2records):
1251 elif ms._v1v2match(v1records, v2records):
1251 ui.note(('v1 and v2 states match: using v2\n'))
1252 ui.note(('v1 and v2 states match: using v2\n'))
1252 printrecords(2)
1253 printrecords(2)
1253 else:
1254 else:
1254 ui.note(('v1 and v2 states mismatch: using v1\n'))
1255 ui.note(('v1 and v2 states mismatch: using v1\n'))
1255 printrecords(1)
1256 printrecords(1)
1256 if ui.verbose:
1257 if ui.verbose:
1257 printrecords(2)
1258 printrecords(2)
1258
1259
1259 @command('debugnamecomplete', [], _('NAME...'))
1260 @command('debugnamecomplete', [], _('NAME...'))
1260 def debugnamecomplete(ui, repo, *args):
1261 def debugnamecomplete(ui, repo, *args):
1261 '''complete "names" - tags, open branch names, bookmark names'''
1262 '''complete "names" - tags, open branch names, bookmark names'''
1262
1263
1263 names = set()
1264 names = set()
1264 # since we previously only listed open branches, we will handle that
1265 # since we previously only listed open branches, we will handle that
1265 # specially (after this for loop)
1266 # specially (after this for loop)
1266 for name, ns in repo.names.iteritems():
1267 for name, ns in repo.names.iteritems():
1267 if name != 'branches':
1268 if name != 'branches':
1268 names.update(ns.listnames(repo))
1269 names.update(ns.listnames(repo))
1269 names.update(tag for (tag, heads, tip, closed)
1270 names.update(tag for (tag, heads, tip, closed)
1270 in repo.branchmap().iterbranches() if not closed)
1271 in repo.branchmap().iterbranches() if not closed)
1271 completions = set()
1272 completions = set()
1272 if not args:
1273 if not args:
1273 args = ['']
1274 args = ['']
1274 for a in args:
1275 for a in args:
1275 completions.update(n for n in names if n.startswith(a))
1276 completions.update(n for n in names if n.startswith(a))
1276 ui.write('\n'.join(sorted(completions)))
1277 ui.write('\n'.join(sorted(completions)))
1277 ui.write('\n')
1278 ui.write('\n')
1278
1279
1279 @command('debugobsolete',
1280 @command('debugobsolete',
1280 [('', 'flags', 0, _('markers flag')),
1281 [('', 'flags', 0, _('markers flag')),
1281 ('', 'record-parents', False,
1282 ('', 'record-parents', False,
1282 _('record parent information for the precursor')),
1283 _('record parent information for the precursor')),
1283 ('r', 'rev', [], _('display markers relevant to REV')),
1284 ('r', 'rev', [], _('display markers relevant to REV')),
1284 ('', 'index', False, _('display index of the marker')),
1285 ('', 'index', False, _('display index of the marker')),
1285 ('', 'delete', [], _('delete markers specified by indices')),
1286 ('', 'delete', [], _('delete markers specified by indices')),
1286 ] + commands.commitopts2 + commands.formatteropts,
1287 ] + commands.commitopts2 + commands.formatteropts,
1287 _('[OBSOLETED [REPLACEMENT ...]]'))
1288 _('[OBSOLETED [REPLACEMENT ...]]'))
1288 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1289 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1289 """create arbitrary obsolete marker
1290 """create arbitrary obsolete marker
1290
1291
1291 With no arguments, displays the list of obsolescence markers."""
1292 With no arguments, displays the list of obsolescence markers."""
1292
1293
1293 def parsenodeid(s):
1294 def parsenodeid(s):
1294 try:
1295 try:
1295 # We do not use revsingle/revrange functions here to accept
1296 # We do not use revsingle/revrange functions here to accept
1296 # arbitrary node identifiers, possibly not present in the
1297 # arbitrary node identifiers, possibly not present in the
1297 # local repository.
1298 # local repository.
1298 n = bin(s)
1299 n = bin(s)
1299 if len(n) != len(nullid):
1300 if len(n) != len(nullid):
1300 raise TypeError()
1301 raise TypeError()
1301 return n
1302 return n
1302 except TypeError:
1303 except TypeError:
1303 raise error.Abort('changeset references must be full hexadecimal '
1304 raise error.Abort('changeset references must be full hexadecimal '
1304 'node identifiers')
1305 'node identifiers')
1305
1306
1306 if opts.get('delete'):
1307 if opts.get('delete'):
1307 indices = []
1308 indices = []
1308 for v in opts.get('delete'):
1309 for v in opts.get('delete'):
1309 try:
1310 try:
1310 indices.append(int(v))
1311 indices.append(int(v))
1311 except ValueError:
1312 except ValueError:
1312 raise error.Abort(_('invalid index value: %r') % v,
1313 raise error.Abort(_('invalid index value: %r') % v,
1313 hint=_('use integers for indices'))
1314 hint=_('use integers for indices'))
1314
1315
1315 if repo.currenttransaction():
1316 if repo.currenttransaction():
1316 raise error.Abort(_('cannot delete obsmarkers in the middle '
1317 raise error.Abort(_('cannot delete obsmarkers in the middle '
1317 'of transaction.'))
1318 'of transaction.'))
1318
1319
1319 with repo.lock():
1320 with repo.lock():
1320 n = repair.deleteobsmarkers(repo.obsstore, indices)
1321 n = repair.deleteobsmarkers(repo.obsstore, indices)
1321 ui.write(_('deleted %i obsolescence markers\n') % n)
1322 ui.write(_('deleted %i obsolescence markers\n') % n)
1322
1323
1323 return
1324 return
1324
1325
1325 if precursor is not None:
1326 if precursor is not None:
1326 if opts['rev']:
1327 if opts['rev']:
1327 raise error.Abort('cannot select revision when creating marker')
1328 raise error.Abort('cannot select revision when creating marker')
1328 metadata = {}
1329 metadata = {}
1329 metadata['user'] = opts['user'] or ui.username()
1330 metadata['user'] = opts['user'] or ui.username()
1330 succs = tuple(parsenodeid(succ) for succ in successors)
1331 succs = tuple(parsenodeid(succ) for succ in successors)
1331 l = repo.lock()
1332 l = repo.lock()
1332 try:
1333 try:
1333 tr = repo.transaction('debugobsolete')
1334 tr = repo.transaction('debugobsolete')
1334 try:
1335 try:
1335 date = opts.get('date')
1336 date = opts.get('date')
1336 if date:
1337 if date:
1337 date = util.parsedate(date)
1338 date = util.parsedate(date)
1338 else:
1339 else:
1339 date = None
1340 date = None
1340 prec = parsenodeid(precursor)
1341 prec = parsenodeid(precursor)
1341 parents = None
1342 parents = None
1342 if opts['record_parents']:
1343 if opts['record_parents']:
1343 if prec not in repo.unfiltered():
1344 if prec not in repo.unfiltered():
1344 raise error.Abort('cannot used --record-parents on '
1345 raise error.Abort('cannot used --record-parents on '
1345 'unknown changesets')
1346 'unknown changesets')
1346 parents = repo.unfiltered()[prec].parents()
1347 parents = repo.unfiltered()[prec].parents()
1347 parents = tuple(p.node() for p in parents)
1348 parents = tuple(p.node() for p in parents)
1348 repo.obsstore.create(tr, prec, succs, opts['flags'],
1349 repo.obsstore.create(tr, prec, succs, opts['flags'],
1349 parents=parents, date=date,
1350 parents=parents, date=date,
1350 metadata=metadata)
1351 metadata=metadata)
1351 tr.close()
1352 tr.close()
1352 except ValueError as exc:
1353 except ValueError as exc:
1353 raise error.Abort(_('bad obsmarker input: %s') % exc)
1354 raise error.Abort(_('bad obsmarker input: %s') % exc)
1354 finally:
1355 finally:
1355 tr.release()
1356 tr.release()
1356 finally:
1357 finally:
1357 l.release()
1358 l.release()
1358 else:
1359 else:
1359 if opts['rev']:
1360 if opts['rev']:
1360 revs = scmutil.revrange(repo, opts['rev'])
1361 revs = scmutil.revrange(repo, opts['rev'])
1361 nodes = [repo[r].node() for r in revs]
1362 nodes = [repo[r].node() for r in revs]
1362 markers = list(obsolete.getmarkers(repo, nodes=nodes))
1363 markers = list(obsolete.getmarkers(repo, nodes=nodes))
1363 markers.sort(key=lambda x: x._data)
1364 markers.sort(key=lambda x: x._data)
1364 else:
1365 else:
1365 markers = obsolete.getmarkers(repo)
1366 markers = obsolete.getmarkers(repo)
1366
1367
1367 markerstoiter = markers
1368 markerstoiter = markers
1368 isrelevant = lambda m: True
1369 isrelevant = lambda m: True
1369 if opts.get('rev') and opts.get('index'):
1370 if opts.get('rev') and opts.get('index'):
1370 markerstoiter = obsolete.getmarkers(repo)
1371 markerstoiter = obsolete.getmarkers(repo)
1371 markerset = set(markers)
1372 markerset = set(markers)
1372 isrelevant = lambda m: m in markerset
1373 isrelevant = lambda m: m in markerset
1373
1374
1374 fm = ui.formatter('debugobsolete', opts)
1375 fm = ui.formatter('debugobsolete', opts)
1375 for i, m in enumerate(markerstoiter):
1376 for i, m in enumerate(markerstoiter):
1376 if not isrelevant(m):
1377 if not isrelevant(m):
1377 # marker can be irrelevant when we're iterating over a set
1378 # marker can be irrelevant when we're iterating over a set
1378 # of markers (markerstoiter) which is bigger than the set
1379 # of markers (markerstoiter) which is bigger than the set
1379 # of markers we want to display (markers)
1380 # of markers we want to display (markers)
1380 # this can happen if both --index and --rev options are
1381 # this can happen if both --index and --rev options are
1381 # provided and thus we need to iterate over all of the markers
1382 # provided and thus we need to iterate over all of the markers
1382 # to get the correct indices, but only display the ones that
1383 # to get the correct indices, but only display the ones that
1383 # are relevant to --rev value
1384 # are relevant to --rev value
1384 continue
1385 continue
1385 fm.startitem()
1386 fm.startitem()
1386 ind = i if opts.get('index') else None
1387 ind = i if opts.get('index') else None
1387 cmdutil.showmarker(fm, m, index=ind)
1388 cmdutil.showmarker(fm, m, index=ind)
1388 fm.end()
1389 fm.end()
1389
1390
1390 @command('debugpathcomplete',
1391 @command('debugpathcomplete',
1391 [('f', 'full', None, _('complete an entire path')),
1392 [('f', 'full', None, _('complete an entire path')),
1392 ('n', 'normal', None, _('show only normal files')),
1393 ('n', 'normal', None, _('show only normal files')),
1393 ('a', 'added', None, _('show only added files')),
1394 ('a', 'added', None, _('show only added files')),
1394 ('r', 'removed', None, _('show only removed files'))],
1395 ('r', 'removed', None, _('show only removed files'))],
1395 _('FILESPEC...'))
1396 _('FILESPEC...'))
1396 def debugpathcomplete(ui, repo, *specs, **opts):
1397 def debugpathcomplete(ui, repo, *specs, **opts):
1397 '''complete part or all of a tracked path
1398 '''complete part or all of a tracked path
1398
1399
1399 This command supports shells that offer path name completion. It
1400 This command supports shells that offer path name completion. It
1400 currently completes only files already known to the dirstate.
1401 currently completes only files already known to the dirstate.
1401
1402
1402 Completion extends only to the next path segment unless
1403 Completion extends only to the next path segment unless
1403 --full is specified, in which case entire paths are used.'''
1404 --full is specified, in which case entire paths are used.'''
1404
1405
1405 def complete(path, acceptable):
1406 def complete(path, acceptable):
1406 dirstate = repo.dirstate
1407 dirstate = repo.dirstate
1407 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1408 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1408 rootdir = repo.root + pycompat.ossep
1409 rootdir = repo.root + pycompat.ossep
1409 if spec != repo.root and not spec.startswith(rootdir):
1410 if spec != repo.root and not spec.startswith(rootdir):
1410 return [], []
1411 return [], []
1411 if os.path.isdir(spec):
1412 if os.path.isdir(spec):
1412 spec += '/'
1413 spec += '/'
1413 spec = spec[len(rootdir):]
1414 spec = spec[len(rootdir):]
1414 fixpaths = pycompat.ossep != '/'
1415 fixpaths = pycompat.ossep != '/'
1415 if fixpaths:
1416 if fixpaths:
1416 spec = spec.replace(pycompat.ossep, '/')
1417 spec = spec.replace(pycompat.ossep, '/')
1417 speclen = len(spec)
1418 speclen = len(spec)
1418 fullpaths = opts['full']
1419 fullpaths = opts['full']
1419 files, dirs = set(), set()
1420 files, dirs = set(), set()
1420 adddir, addfile = dirs.add, files.add
1421 adddir, addfile = dirs.add, files.add
1421 for f, st in dirstate.iteritems():
1422 for f, st in dirstate.iteritems():
1422 if f.startswith(spec) and st[0] in acceptable:
1423 if f.startswith(spec) and st[0] in acceptable:
1423 if fixpaths:
1424 if fixpaths:
1424 f = f.replace('/', pycompat.ossep)
1425 f = f.replace('/', pycompat.ossep)
1425 if fullpaths:
1426 if fullpaths:
1426 addfile(f)
1427 addfile(f)
1427 continue
1428 continue
1428 s = f.find(pycompat.ossep, speclen)
1429 s = f.find(pycompat.ossep, speclen)
1429 if s >= 0:
1430 if s >= 0:
1430 adddir(f[:s])
1431 adddir(f[:s])
1431 else:
1432 else:
1432 addfile(f)
1433 addfile(f)
1433 return files, dirs
1434 return files, dirs
1434
1435
1435 acceptable = ''
1436 acceptable = ''
1436 if opts['normal']:
1437 if opts['normal']:
1437 acceptable += 'nm'
1438 acceptable += 'nm'
1438 if opts['added']:
1439 if opts['added']:
1439 acceptable += 'a'
1440 acceptable += 'a'
1440 if opts['removed']:
1441 if opts['removed']:
1441 acceptable += 'r'
1442 acceptable += 'r'
1442 cwd = repo.getcwd()
1443 cwd = repo.getcwd()
1443 if not specs:
1444 if not specs:
1444 specs = ['.']
1445 specs = ['.']
1445
1446
1446 files, dirs = set(), set()
1447 files, dirs = set(), set()
1447 for spec in specs:
1448 for spec in specs:
1448 f, d = complete(spec, acceptable or 'nmar')
1449 f, d = complete(spec, acceptable or 'nmar')
1449 files.update(f)
1450 files.update(f)
1450 dirs.update(d)
1451 dirs.update(d)
1451 files.update(dirs)
1452 files.update(dirs)
1452 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1453 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1453 ui.write('\n')
1454 ui.write('\n')
1454
1455
1455 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1456 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1456 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1457 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1457 '''access the pushkey key/value protocol
1458 '''access the pushkey key/value protocol
1458
1459
1459 With two args, list the keys in the given namespace.
1460 With two args, list the keys in the given namespace.
1460
1461
1461 With five args, set a key to new if it currently is set to old.
1462 With five args, set a key to new if it currently is set to old.
1462 Reports success or failure.
1463 Reports success or failure.
1463 '''
1464 '''
1464
1465
1465 target = hg.peer(ui, {}, repopath)
1466 target = hg.peer(ui, {}, repopath)
1466 if keyinfo:
1467 if keyinfo:
1467 key, old, new = keyinfo
1468 key, old, new = keyinfo
1468 r = target.pushkey(namespace, key, old, new)
1469 r = target.pushkey(namespace, key, old, new)
1469 ui.status(str(r) + '\n')
1470 ui.status(str(r) + '\n')
1470 return not r
1471 return not r
1471 else:
1472 else:
1472 for k, v in sorted(target.listkeys(namespace).iteritems()):
1473 for k, v in sorted(target.listkeys(namespace).iteritems()):
1473 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1474 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1474 v.encode('string-escape')))
1475 v.encode('string-escape')))
1475
1476
1476 @command('debugpvec', [], _('A B'))
1477 @command('debugpvec', [], _('A B'))
1477 def debugpvec(ui, repo, a, b=None):
1478 def debugpvec(ui, repo, a, b=None):
1478 ca = scmutil.revsingle(repo, a)
1479 ca = scmutil.revsingle(repo, a)
1479 cb = scmutil.revsingle(repo, b)
1480 cb = scmutil.revsingle(repo, b)
1480 pa = pvec.ctxpvec(ca)
1481 pa = pvec.ctxpvec(ca)
1481 pb = pvec.ctxpvec(cb)
1482 pb = pvec.ctxpvec(cb)
1482 if pa == pb:
1483 if pa == pb:
1483 rel = "="
1484 rel = "="
1484 elif pa > pb:
1485 elif pa > pb:
1485 rel = ">"
1486 rel = ">"
1486 elif pa < pb:
1487 elif pa < pb:
1487 rel = "<"
1488 rel = "<"
1488 elif pa | pb:
1489 elif pa | pb:
1489 rel = "|"
1490 rel = "|"
1490 ui.write(_("a: %s\n") % pa)
1491 ui.write(_("a: %s\n") % pa)
1491 ui.write(_("b: %s\n") % pb)
1492 ui.write(_("b: %s\n") % pb)
1492 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1493 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1493 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1494 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1494 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1495 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1495 pa.distance(pb), rel))
1496 pa.distance(pb), rel))
1496
1497
1497 @command('debugrebuilddirstate|debugrebuildstate',
1498 @command('debugrebuilddirstate|debugrebuildstate',
1498 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1499 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1499 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1500 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1500 'the working copy parent')),
1501 'the working copy parent')),
1501 ],
1502 ],
1502 _('[-r REV]'))
1503 _('[-r REV]'))
1503 def debugrebuilddirstate(ui, repo, rev, **opts):
1504 def debugrebuilddirstate(ui, repo, rev, **opts):
1504 """rebuild the dirstate as it would look like for the given revision
1505 """rebuild the dirstate as it would look like for the given revision
1505
1506
1506 If no revision is specified the first current parent will be used.
1507 If no revision is specified the first current parent will be used.
1507
1508
1508 The dirstate will be set to the files of the given revision.
1509 The dirstate will be set to the files of the given revision.
1509 The actual working directory content or existing dirstate
1510 The actual working directory content or existing dirstate
1510 information such as adds or removes is not considered.
1511 information such as adds or removes is not considered.
1511
1512
1512 ``minimal`` will only rebuild the dirstate status for files that claim to be
1513 ``minimal`` will only rebuild the dirstate status for files that claim to be
1513 tracked but are not in the parent manifest, or that exist in the parent
1514 tracked but are not in the parent manifest, or that exist in the parent
1514 manifest but are not in the dirstate. It will not change adds, removes, or
1515 manifest but are not in the dirstate. It will not change adds, removes, or
1515 modified files that are in the working copy parent.
1516 modified files that are in the working copy parent.
1516
1517
1517 One use of this command is to make the next :hg:`status` invocation
1518 One use of this command is to make the next :hg:`status` invocation
1518 check the actual file content.
1519 check the actual file content.
1519 """
1520 """
1520 ctx = scmutil.revsingle(repo, rev)
1521 ctx = scmutil.revsingle(repo, rev)
1521 with repo.wlock():
1522 with repo.wlock():
1522 dirstate = repo.dirstate
1523 dirstate = repo.dirstate
1523 changedfiles = None
1524 changedfiles = None
1524 # See command doc for what minimal does.
1525 # See command doc for what minimal does.
1525 if opts.get('minimal'):
1526 if opts.get('minimal'):
1526 manifestfiles = set(ctx.manifest().keys())
1527 manifestfiles = set(ctx.manifest().keys())
1527 dirstatefiles = set(dirstate)
1528 dirstatefiles = set(dirstate)
1528 manifestonly = manifestfiles - dirstatefiles
1529 manifestonly = manifestfiles - dirstatefiles
1529 dsonly = dirstatefiles - manifestfiles
1530 dsonly = dirstatefiles - manifestfiles
1530 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1531 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1531 changedfiles = manifestonly | dsnotadded
1532 changedfiles = manifestonly | dsnotadded
1532
1533
1533 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1534 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1534
1535
1535 @command('debugrebuildfncache', [], '')
1536 @command('debugrebuildfncache', [], '')
1536 def debugrebuildfncache(ui, repo):
1537 def debugrebuildfncache(ui, repo):
1537 """rebuild the fncache file"""
1538 """rebuild the fncache file"""
1538 repair.rebuildfncache(ui, repo)
1539 repair.rebuildfncache(ui, repo)
1539
1540
1540 @command('debugrename',
1541 @command('debugrename',
1541 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1542 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1542 _('[-r REV] FILE'))
1543 _('[-r REV] FILE'))
1543 def debugrename(ui, repo, file1, *pats, **opts):
1544 def debugrename(ui, repo, file1, *pats, **opts):
1544 """dump rename information"""
1545 """dump rename information"""
1545
1546
1546 ctx = scmutil.revsingle(repo, opts.get('rev'))
1547 ctx = scmutil.revsingle(repo, opts.get('rev'))
1547 m = scmutil.match(ctx, (file1,) + pats, opts)
1548 m = scmutil.match(ctx, (file1,) + pats, opts)
1548 for abs in ctx.walk(m):
1549 for abs in ctx.walk(m):
1549 fctx = ctx[abs]
1550 fctx = ctx[abs]
1550 o = fctx.filelog().renamed(fctx.filenode())
1551 o = fctx.filelog().renamed(fctx.filenode())
1551 rel = m.rel(abs)
1552 rel = m.rel(abs)
1552 if o:
1553 if o:
1553 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1554 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1554 else:
1555 else:
1555 ui.write(_("%s not renamed\n") % rel)
1556 ui.write(_("%s not renamed\n") % rel)
1556
1557
1557 @command('debugrevlog', commands.debugrevlogopts +
1558 @command('debugrevlog', commands.debugrevlogopts +
1558 [('d', 'dump', False, _('dump index data'))],
1559 [('d', 'dump', False, _('dump index data'))],
1559 _('-c|-m|FILE'),
1560 _('-c|-m|FILE'),
1560 optionalrepo=True)
1561 optionalrepo=True)
1561 def debugrevlog(ui, repo, file_=None, **opts):
1562 def debugrevlog(ui, repo, file_=None, **opts):
1562 """show data and statistics about a revlog"""
1563 """show data and statistics about a revlog"""
1563 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1564 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1564
1565
1565 if opts.get("dump"):
1566 if opts.get("dump"):
1566 numrevs = len(r)
1567 numrevs = len(r)
1567 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1568 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1568 " rawsize totalsize compression heads chainlen\n"))
1569 " rawsize totalsize compression heads chainlen\n"))
1569 ts = 0
1570 ts = 0
1570 heads = set()
1571 heads = set()
1571
1572
1572 for rev in xrange(numrevs):
1573 for rev in xrange(numrevs):
1573 dbase = r.deltaparent(rev)
1574 dbase = r.deltaparent(rev)
1574 if dbase == -1:
1575 if dbase == -1:
1575 dbase = rev
1576 dbase = rev
1576 cbase = r.chainbase(rev)
1577 cbase = r.chainbase(rev)
1577 clen = r.chainlen(rev)
1578 clen = r.chainlen(rev)
1578 p1, p2 = r.parentrevs(rev)
1579 p1, p2 = r.parentrevs(rev)
1579 rs = r.rawsize(rev)
1580 rs = r.rawsize(rev)
1580 ts = ts + rs
1581 ts = ts + rs
1581 heads -= set(r.parentrevs(rev))
1582 heads -= set(r.parentrevs(rev))
1582 heads.add(rev)
1583 heads.add(rev)
1583 try:
1584 try:
1584 compression = ts / r.end(rev)
1585 compression = ts / r.end(rev)
1585 except ZeroDivisionError:
1586 except ZeroDivisionError:
1586 compression = 0
1587 compression = 0
1587 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1588 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1588 "%11d %5d %8d\n" %
1589 "%11d %5d %8d\n" %
1589 (rev, p1, p2, r.start(rev), r.end(rev),
1590 (rev, p1, p2, r.start(rev), r.end(rev),
1590 r.start(dbase), r.start(cbase),
1591 r.start(dbase), r.start(cbase),
1591 r.start(p1), r.start(p2),
1592 r.start(p1), r.start(p2),
1592 rs, ts, compression, len(heads), clen))
1593 rs, ts, compression, len(heads), clen))
1593 return 0
1594 return 0
1594
1595
1595 v = r.version
1596 v = r.version
1596 format = v & 0xFFFF
1597 format = v & 0xFFFF
1597 flags = []
1598 flags = []
1598 gdelta = False
1599 gdelta = False
1599 if v & revlog.REVLOGNGINLINEDATA:
1600 if v & revlog.REVLOGNGINLINEDATA:
1600 flags.append('inline')
1601 flags.append('inline')
1601 if v & revlog.REVLOGGENERALDELTA:
1602 if v & revlog.REVLOGGENERALDELTA:
1602 gdelta = True
1603 gdelta = True
1603 flags.append('generaldelta')
1604 flags.append('generaldelta')
1604 if not flags:
1605 if not flags:
1605 flags = ['(none)']
1606 flags = ['(none)']
1606
1607
1607 nummerges = 0
1608 nummerges = 0
1608 numfull = 0
1609 numfull = 0
1609 numprev = 0
1610 numprev = 0
1610 nump1 = 0
1611 nump1 = 0
1611 nump2 = 0
1612 nump2 = 0
1612 numother = 0
1613 numother = 0
1613 nump1prev = 0
1614 nump1prev = 0
1614 nump2prev = 0
1615 nump2prev = 0
1615 chainlengths = []
1616 chainlengths = []
1616
1617
1617 datasize = [None, 0, 0]
1618 datasize = [None, 0, 0]
1618 fullsize = [None, 0, 0]
1619 fullsize = [None, 0, 0]
1619 deltasize = [None, 0, 0]
1620 deltasize = [None, 0, 0]
1620 chunktypecounts = {}
1621 chunktypecounts = {}
1621 chunktypesizes = {}
1622 chunktypesizes = {}
1622
1623
1623 def addsize(size, l):
1624 def addsize(size, l):
1624 if l[0] is None or size < l[0]:
1625 if l[0] is None or size < l[0]:
1625 l[0] = size
1626 l[0] = size
1626 if size > l[1]:
1627 if size > l[1]:
1627 l[1] = size
1628 l[1] = size
1628 l[2] += size
1629 l[2] += size
1629
1630
1630 numrevs = len(r)
1631 numrevs = len(r)
1631 for rev in xrange(numrevs):
1632 for rev in xrange(numrevs):
1632 p1, p2 = r.parentrevs(rev)
1633 p1, p2 = r.parentrevs(rev)
1633 delta = r.deltaparent(rev)
1634 delta = r.deltaparent(rev)
1634 if format > 0:
1635 if format > 0:
1635 addsize(r.rawsize(rev), datasize)
1636 addsize(r.rawsize(rev), datasize)
1636 if p2 != nullrev:
1637 if p2 != nullrev:
1637 nummerges += 1
1638 nummerges += 1
1638 size = r.length(rev)
1639 size = r.length(rev)
1639 if delta == nullrev:
1640 if delta == nullrev:
1640 chainlengths.append(0)
1641 chainlengths.append(0)
1641 numfull += 1
1642 numfull += 1
1642 addsize(size, fullsize)
1643 addsize(size, fullsize)
1643 else:
1644 else:
1644 chainlengths.append(chainlengths[delta] + 1)
1645 chainlengths.append(chainlengths[delta] + 1)
1645 addsize(size, deltasize)
1646 addsize(size, deltasize)
1646 if delta == rev - 1:
1647 if delta == rev - 1:
1647 numprev += 1
1648 numprev += 1
1648 if delta == p1:
1649 if delta == p1:
1649 nump1prev += 1
1650 nump1prev += 1
1650 elif delta == p2:
1651 elif delta == p2:
1651 nump2prev += 1
1652 nump2prev += 1
1652 elif delta == p1:
1653 elif delta == p1:
1653 nump1 += 1
1654 nump1 += 1
1654 elif delta == p2:
1655 elif delta == p2:
1655 nump2 += 1
1656 nump2 += 1
1656 elif delta != nullrev:
1657 elif delta != nullrev:
1657 numother += 1
1658 numother += 1
1658
1659
1659 # Obtain data on the raw chunks in the revlog.
1660 # Obtain data on the raw chunks in the revlog.
1660 chunk = r._chunkraw(rev, rev)[1]
1661 chunk = r._chunkraw(rev, rev)[1]
1661 if chunk:
1662 if chunk:
1662 chunktype = chunk[0]
1663 chunktype = chunk[0]
1663 else:
1664 else:
1664 chunktype = 'empty'
1665 chunktype = 'empty'
1665
1666
1666 if chunktype not in chunktypecounts:
1667 if chunktype not in chunktypecounts:
1667 chunktypecounts[chunktype] = 0
1668 chunktypecounts[chunktype] = 0
1668 chunktypesizes[chunktype] = 0
1669 chunktypesizes[chunktype] = 0
1669
1670
1670 chunktypecounts[chunktype] += 1
1671 chunktypecounts[chunktype] += 1
1671 chunktypesizes[chunktype] += size
1672 chunktypesizes[chunktype] += size
1672
1673
1673 # Adjust size min value for empty cases
1674 # Adjust size min value for empty cases
1674 for size in (datasize, fullsize, deltasize):
1675 for size in (datasize, fullsize, deltasize):
1675 if size[0] is None:
1676 if size[0] is None:
1676 size[0] = 0
1677 size[0] = 0
1677
1678
1678 numdeltas = numrevs - numfull
1679 numdeltas = numrevs - numfull
1679 numoprev = numprev - nump1prev - nump2prev
1680 numoprev = numprev - nump1prev - nump2prev
1680 totalrawsize = datasize[2]
1681 totalrawsize = datasize[2]
1681 datasize[2] /= numrevs
1682 datasize[2] /= numrevs
1682 fulltotal = fullsize[2]
1683 fulltotal = fullsize[2]
1683 fullsize[2] /= numfull
1684 fullsize[2] /= numfull
1684 deltatotal = deltasize[2]
1685 deltatotal = deltasize[2]
1685 if numrevs - numfull > 0:
1686 if numrevs - numfull > 0:
1686 deltasize[2] /= numrevs - numfull
1687 deltasize[2] /= numrevs - numfull
1687 totalsize = fulltotal + deltatotal
1688 totalsize = fulltotal + deltatotal
1688 avgchainlen = sum(chainlengths) / numrevs
1689 avgchainlen = sum(chainlengths) / numrevs
1689 maxchainlen = max(chainlengths)
1690 maxchainlen = max(chainlengths)
1690 compratio = 1
1691 compratio = 1
1691 if totalsize:
1692 if totalsize:
1692 compratio = totalrawsize / totalsize
1693 compratio = totalrawsize / totalsize
1693
1694
1694 basedfmtstr = '%%%dd\n'
1695 basedfmtstr = '%%%dd\n'
1695 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1696 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1696
1697
1697 def dfmtstr(max):
1698 def dfmtstr(max):
1698 return basedfmtstr % len(str(max))
1699 return basedfmtstr % len(str(max))
1699 def pcfmtstr(max, padding=0):
1700 def pcfmtstr(max, padding=0):
1700 return basepcfmtstr % (len(str(max)), ' ' * padding)
1701 return basepcfmtstr % (len(str(max)), ' ' * padding)
1701
1702
1702 def pcfmt(value, total):
1703 def pcfmt(value, total):
1703 if total:
1704 if total:
1704 return (value, 100 * float(value) / total)
1705 return (value, 100 * float(value) / total)
1705 else:
1706 else:
1706 return value, 100.0
1707 return value, 100.0
1707
1708
1708 ui.write(('format : %d\n') % format)
1709 ui.write(('format : %d\n') % format)
1709 ui.write(('flags : %s\n') % ', '.join(flags))
1710 ui.write(('flags : %s\n') % ', '.join(flags))
1710
1711
1711 ui.write('\n')
1712 ui.write('\n')
1712 fmt = pcfmtstr(totalsize)
1713 fmt = pcfmtstr(totalsize)
1713 fmt2 = dfmtstr(totalsize)
1714 fmt2 = dfmtstr(totalsize)
1714 ui.write(('revisions : ') + fmt2 % numrevs)
1715 ui.write(('revisions : ') + fmt2 % numrevs)
1715 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
1716 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
1716 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
1717 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
1717 ui.write(('revisions : ') + fmt2 % numrevs)
1718 ui.write(('revisions : ') + fmt2 % numrevs)
1718 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
1719 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
1719 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
1720 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
1720 ui.write(('revision size : ') + fmt2 % totalsize)
1721 ui.write(('revision size : ') + fmt2 % totalsize)
1721 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
1722 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
1722 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
1723 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
1723
1724
1724 def fmtchunktype(chunktype):
1725 def fmtchunktype(chunktype):
1725 if chunktype == 'empty':
1726 if chunktype == 'empty':
1726 return ' %s : ' % chunktype
1727 return ' %s : ' % chunktype
1727 elif chunktype in string.ascii_letters:
1728 elif chunktype in string.ascii_letters:
1728 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
1729 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
1729 else:
1730 else:
1730 return ' 0x%s : ' % hex(chunktype)
1731 return ' 0x%s : ' % hex(chunktype)
1731
1732
1732 ui.write('\n')
1733 ui.write('\n')
1733 ui.write(('chunks : ') + fmt2 % numrevs)
1734 ui.write(('chunks : ') + fmt2 % numrevs)
1734 for chunktype in sorted(chunktypecounts):
1735 for chunktype in sorted(chunktypecounts):
1735 ui.write(fmtchunktype(chunktype))
1736 ui.write(fmtchunktype(chunktype))
1736 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
1737 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
1737 ui.write(('chunks size : ') + fmt2 % totalsize)
1738 ui.write(('chunks size : ') + fmt2 % totalsize)
1738 for chunktype in sorted(chunktypecounts):
1739 for chunktype in sorted(chunktypecounts):
1739 ui.write(fmtchunktype(chunktype))
1740 ui.write(fmtchunktype(chunktype))
1740 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
1741 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
1741
1742
1742 ui.write('\n')
1743 ui.write('\n')
1743 fmt = dfmtstr(max(avgchainlen, compratio))
1744 fmt = dfmtstr(max(avgchainlen, compratio))
1744 ui.write(('avg chain length : ') + fmt % avgchainlen)
1745 ui.write(('avg chain length : ') + fmt % avgchainlen)
1745 ui.write(('max chain length : ') + fmt % maxchainlen)
1746 ui.write(('max chain length : ') + fmt % maxchainlen)
1746 ui.write(('compression ratio : ') + fmt % compratio)
1747 ui.write(('compression ratio : ') + fmt % compratio)
1747
1748
1748 if format > 0:
1749 if format > 0:
1749 ui.write('\n')
1750 ui.write('\n')
1750 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
1751 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
1751 % tuple(datasize))
1752 % tuple(datasize))
1752 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
1753 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
1753 % tuple(fullsize))
1754 % tuple(fullsize))
1754 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
1755 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
1755 % tuple(deltasize))
1756 % tuple(deltasize))
1756
1757
1757 if numdeltas > 0:
1758 if numdeltas > 0:
1758 ui.write('\n')
1759 ui.write('\n')
1759 fmt = pcfmtstr(numdeltas)
1760 fmt = pcfmtstr(numdeltas)
1760 fmt2 = pcfmtstr(numdeltas, 4)
1761 fmt2 = pcfmtstr(numdeltas, 4)
1761 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
1762 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
1762 if numprev > 0:
1763 if numprev > 0:
1763 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
1764 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
1764 numprev))
1765 numprev))
1765 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
1766 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
1766 numprev))
1767 numprev))
1767 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
1768 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
1768 numprev))
1769 numprev))
1769 if gdelta:
1770 if gdelta:
1770 ui.write(('deltas against p1 : ')
1771 ui.write(('deltas against p1 : ')
1771 + fmt % pcfmt(nump1, numdeltas))
1772 + fmt % pcfmt(nump1, numdeltas))
1772 ui.write(('deltas against p2 : ')
1773 ui.write(('deltas against p2 : ')
1773 + fmt % pcfmt(nump2, numdeltas))
1774 + fmt % pcfmt(nump2, numdeltas))
1774 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
1775 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
1775 numdeltas))
1776 numdeltas))
1776
1777
1777 @command('debugrevspec',
1778 @command('debugrevspec',
1778 [('', 'optimize', None,
1779 [('', 'optimize', None,
1779 _('print parsed tree after optimizing (DEPRECATED)')),
1780 _('print parsed tree after optimizing (DEPRECATED)')),
1780 ('p', 'show-stage', [],
1781 ('p', 'show-stage', [],
1781 _('print parsed tree at the given stage'), _('NAME')),
1782 _('print parsed tree at the given stage'), _('NAME')),
1782 ('', 'no-optimized', False, _('evaluate tree without optimization')),
1783 ('', 'no-optimized', False, _('evaluate tree without optimization')),
1783 ('', 'verify-optimized', False, _('verify optimized result')),
1784 ('', 'verify-optimized', False, _('verify optimized result')),
1784 ],
1785 ],
1785 ('REVSPEC'))
1786 ('REVSPEC'))
1786 def debugrevspec(ui, repo, expr, **opts):
1787 def debugrevspec(ui, repo, expr, **opts):
1787 """parse and apply a revision specification
1788 """parse and apply a revision specification
1788
1789
1789 Use -p/--show-stage option to print the parsed tree at the given stages.
1790 Use -p/--show-stage option to print the parsed tree at the given stages.
1790 Use -p all to print tree at every stage.
1791 Use -p all to print tree at every stage.
1791
1792
1792 Use --verify-optimized to compare the optimized result with the unoptimized
1793 Use --verify-optimized to compare the optimized result with the unoptimized
1793 one. Returns 1 if the optimized result differs.
1794 one. Returns 1 if the optimized result differs.
1794 """
1795 """
1795 stages = [
1796 stages = [
1796 ('parsed', lambda tree: tree),
1797 ('parsed', lambda tree: tree),
1797 ('expanded', lambda tree: revset.expandaliases(ui, tree)),
1798 ('expanded', lambda tree: revsetlang.expandaliases(ui, tree)),
1798 ('concatenated', revset.foldconcat),
1799 ('concatenated', revsetlang.foldconcat),
1799 ('analyzed', revset.analyze),
1800 ('analyzed', revsetlang.analyze),
1800 ('optimized', revset.optimize),
1801 ('optimized', revsetlang.optimize),
1801 ]
1802 ]
1802 if opts['no_optimized']:
1803 if opts['no_optimized']:
1803 stages = stages[:-1]
1804 stages = stages[:-1]
1804 if opts['verify_optimized'] and opts['no_optimized']:
1805 if opts['verify_optimized'] and opts['no_optimized']:
1805 raise error.Abort(_('cannot use --verify-optimized with '
1806 raise error.Abort(_('cannot use --verify-optimized with '
1806 '--no-optimized'))
1807 '--no-optimized'))
1807 stagenames = set(n for n, f in stages)
1808 stagenames = set(n for n, f in stages)
1808
1809
1809 showalways = set()
1810 showalways = set()
1810 showchanged = set()
1811 showchanged = set()
1811 if ui.verbose and not opts['show_stage']:
1812 if ui.verbose and not opts['show_stage']:
1812 # show parsed tree by --verbose (deprecated)
1813 # show parsed tree by --verbose (deprecated)
1813 showalways.add('parsed')
1814 showalways.add('parsed')
1814 showchanged.update(['expanded', 'concatenated'])
1815 showchanged.update(['expanded', 'concatenated'])
1815 if opts['optimize']:
1816 if opts['optimize']:
1816 showalways.add('optimized')
1817 showalways.add('optimized')
1817 if opts['show_stage'] and opts['optimize']:
1818 if opts['show_stage'] and opts['optimize']:
1818 raise error.Abort(_('cannot use --optimize with --show-stage'))
1819 raise error.Abort(_('cannot use --optimize with --show-stage'))
1819 if opts['show_stage'] == ['all']:
1820 if opts['show_stage'] == ['all']:
1820 showalways.update(stagenames)
1821 showalways.update(stagenames)
1821 else:
1822 else:
1822 for n in opts['show_stage']:
1823 for n in opts['show_stage']:
1823 if n not in stagenames:
1824 if n not in stagenames:
1824 raise error.Abort(_('invalid stage name: %s') % n)
1825 raise error.Abort(_('invalid stage name: %s') % n)
1825 showalways.update(opts['show_stage'])
1826 showalways.update(opts['show_stage'])
1826
1827
1827 treebystage = {}
1828 treebystage = {}
1828 printedtree = None
1829 printedtree = None
1829 tree = revset.parse(expr, lookup=repo.__contains__)
1830 tree = revsetlang.parse(expr, lookup=repo.__contains__)
1830 for n, f in stages:
1831 for n, f in stages:
1831 treebystage[n] = tree = f(tree)
1832 treebystage[n] = tree = f(tree)
1832 if n in showalways or (n in showchanged and tree != printedtree):
1833 if n in showalways or (n in showchanged and tree != printedtree):
1833 if opts['show_stage'] or n != 'parsed':
1834 if opts['show_stage'] or n != 'parsed':
1834 ui.write(("* %s:\n") % n)
1835 ui.write(("* %s:\n") % n)
1835 ui.write(revset.prettyformat(tree), "\n")
1836 ui.write(revsetlang.prettyformat(tree), "\n")
1836 printedtree = tree
1837 printedtree = tree
1837
1838
1838 if opts['verify_optimized']:
1839 if opts['verify_optimized']:
1839 arevs = revset.makematcher(treebystage['analyzed'])(repo)
1840 arevs = revset.makematcher(treebystage['analyzed'])(repo)
1840 brevs = revset.makematcher(treebystage['optimized'])(repo)
1841 brevs = revset.makematcher(treebystage['optimized'])(repo)
1841 if ui.verbose:
1842 if ui.verbose:
1842 ui.note(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
1843 ui.note(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
1843 ui.note(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
1844 ui.note(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
1844 arevs = list(arevs)
1845 arevs = list(arevs)
1845 brevs = list(brevs)
1846 brevs = list(brevs)
1846 if arevs == brevs:
1847 if arevs == brevs:
1847 return 0
1848 return 0
1848 ui.write(('--- analyzed\n'), label='diff.file_a')
1849 ui.write(('--- analyzed\n'), label='diff.file_a')
1849 ui.write(('+++ optimized\n'), label='diff.file_b')
1850 ui.write(('+++ optimized\n'), label='diff.file_b')
1850 sm = difflib.SequenceMatcher(None, arevs, brevs)
1851 sm = difflib.SequenceMatcher(None, arevs, brevs)
1851 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1852 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1852 if tag in ('delete', 'replace'):
1853 if tag in ('delete', 'replace'):
1853 for c in arevs[alo:ahi]:
1854 for c in arevs[alo:ahi]:
1854 ui.write('-%s\n' % c, label='diff.deleted')
1855 ui.write('-%s\n' % c, label='diff.deleted')
1855 if tag in ('insert', 'replace'):
1856 if tag in ('insert', 'replace'):
1856 for c in brevs[blo:bhi]:
1857 for c in brevs[blo:bhi]:
1857 ui.write('+%s\n' % c, label='diff.inserted')
1858 ui.write('+%s\n' % c, label='diff.inserted')
1858 if tag == 'equal':
1859 if tag == 'equal':
1859 for c in arevs[alo:ahi]:
1860 for c in arevs[alo:ahi]:
1860 ui.write(' %s\n' % c)
1861 ui.write(' %s\n' % c)
1861 return 1
1862 return 1
1862
1863
1863 func = revset.makematcher(tree)
1864 func = revset.makematcher(tree)
1864 revs = func(repo)
1865 revs = func(repo)
1865 if ui.verbose:
1866 if ui.verbose:
1866 ui.note(("* set:\n"), smartset.prettyformat(revs), "\n")
1867 ui.note(("* set:\n"), smartset.prettyformat(revs), "\n")
1867 for c in revs:
1868 for c in revs:
1868 ui.write("%s\n" % c)
1869 ui.write("%s\n" % c)
1869
1870
1870 @command('debugsetparents', [], _('REV1 [REV2]'))
1871 @command('debugsetparents', [], _('REV1 [REV2]'))
1871 def debugsetparents(ui, repo, rev1, rev2=None):
1872 def debugsetparents(ui, repo, rev1, rev2=None):
1872 """manually set the parents of the current working directory
1873 """manually set the parents of the current working directory
1873
1874
1874 This is useful for writing repository conversion tools, but should
1875 This is useful for writing repository conversion tools, but should
1875 be used with care. For example, neither the working directory nor the
1876 be used with care. For example, neither the working directory nor the
1876 dirstate is updated, so file status may be incorrect after running this
1877 dirstate is updated, so file status may be incorrect after running this
1877 command.
1878 command.
1878
1879
1879 Returns 0 on success.
1880 Returns 0 on success.
1880 """
1881 """
1881
1882
1882 r1 = scmutil.revsingle(repo, rev1).node()
1883 r1 = scmutil.revsingle(repo, rev1).node()
1883 r2 = scmutil.revsingle(repo, rev2, 'null').node()
1884 r2 = scmutil.revsingle(repo, rev2, 'null').node()
1884
1885
1885 with repo.wlock():
1886 with repo.wlock():
1886 repo.setparents(r1, r2)
1887 repo.setparents(r1, r2)
1887
1888
1888 @command('debugsub',
1889 @command('debugsub',
1889 [('r', 'rev', '',
1890 [('r', 'rev', '',
1890 _('revision to check'), _('REV'))],
1891 _('revision to check'), _('REV'))],
1891 _('[-r REV] [REV]'))
1892 _('[-r REV] [REV]'))
1892 def debugsub(ui, repo, rev=None):
1893 def debugsub(ui, repo, rev=None):
1893 ctx = scmutil.revsingle(repo, rev, None)
1894 ctx = scmutil.revsingle(repo, rev, None)
1894 for k, v in sorted(ctx.substate.items()):
1895 for k, v in sorted(ctx.substate.items()):
1895 ui.write(('path %s\n') % k)
1896 ui.write(('path %s\n') % k)
1896 ui.write((' source %s\n') % v[0])
1897 ui.write((' source %s\n') % v[0])
1897 ui.write((' revision %s\n') % v[1])
1898 ui.write((' revision %s\n') % v[1])
1898
1899
1899 @command('debugsuccessorssets',
1900 @command('debugsuccessorssets',
1900 [],
1901 [],
1901 _('[REV]'))
1902 _('[REV]'))
1902 def debugsuccessorssets(ui, repo, *revs):
1903 def debugsuccessorssets(ui, repo, *revs):
1903 """show set of successors for revision
1904 """show set of successors for revision
1904
1905
1905 A successors set of changeset A is a consistent group of revisions that
1906 A successors set of changeset A is a consistent group of revisions that
1906 succeed A. It contains non-obsolete changesets only.
1907 succeed A. It contains non-obsolete changesets only.
1907
1908
1908 In most cases a changeset A has a single successors set containing a single
1909 In most cases a changeset A has a single successors set containing a single
1909 successor (changeset A replaced by A').
1910 successor (changeset A replaced by A').
1910
1911
1911 A changeset that is made obsolete with no successors are called "pruned".
1912 A changeset that is made obsolete with no successors are called "pruned".
1912 Such changesets have no successors sets at all.
1913 Such changesets have no successors sets at all.
1913
1914
1914 A changeset that has been "split" will have a successors set containing
1915 A changeset that has been "split" will have a successors set containing
1915 more than one successor.
1916 more than one successor.
1916
1917
1917 A changeset that has been rewritten in multiple different ways is called
1918 A changeset that has been rewritten in multiple different ways is called
1918 "divergent". Such changesets have multiple successor sets (each of which
1919 "divergent". Such changesets have multiple successor sets (each of which
1919 may also be split, i.e. have multiple successors).
1920 may also be split, i.e. have multiple successors).
1920
1921
1921 Results are displayed as follows::
1922 Results are displayed as follows::
1922
1923
1923 <rev1>
1924 <rev1>
1924 <successors-1A>
1925 <successors-1A>
1925 <rev2>
1926 <rev2>
1926 <successors-2A>
1927 <successors-2A>
1927 <successors-2B1> <successors-2B2> <successors-2B3>
1928 <successors-2B1> <successors-2B2> <successors-2B3>
1928
1929
1929 Here rev2 has two possible (i.e. divergent) successors sets. The first
1930 Here rev2 has two possible (i.e. divergent) successors sets. The first
1930 holds one element, whereas the second holds three (i.e. the changeset has
1931 holds one element, whereas the second holds three (i.e. the changeset has
1931 been split).
1932 been split).
1932 """
1933 """
1933 # passed to successorssets caching computation from one call to another
1934 # passed to successorssets caching computation from one call to another
1934 cache = {}
1935 cache = {}
1935 ctx2str = str
1936 ctx2str = str
1936 node2str = short
1937 node2str = short
1937 if ui.debug():
1938 if ui.debug():
1938 def ctx2str(ctx):
1939 def ctx2str(ctx):
1939 return ctx.hex()
1940 return ctx.hex()
1940 node2str = hex
1941 node2str = hex
1941 for rev in scmutil.revrange(repo, revs):
1942 for rev in scmutil.revrange(repo, revs):
1942 ctx = repo[rev]
1943 ctx = repo[rev]
1943 ui.write('%s\n'% ctx2str(ctx))
1944 ui.write('%s\n'% ctx2str(ctx))
1944 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
1945 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
1945 if succsset:
1946 if succsset:
1946 ui.write(' ')
1947 ui.write(' ')
1947 ui.write(node2str(succsset[0]))
1948 ui.write(node2str(succsset[0]))
1948 for node in succsset[1:]:
1949 for node in succsset[1:]:
1949 ui.write(' ')
1950 ui.write(' ')
1950 ui.write(node2str(node))
1951 ui.write(node2str(node))
1951 ui.write('\n')
1952 ui.write('\n')
1952
1953
1953 @command('debugtemplate',
1954 @command('debugtemplate',
1954 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
1955 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
1955 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
1956 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
1956 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
1957 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
1957 optionalrepo=True)
1958 optionalrepo=True)
1958 def debugtemplate(ui, repo, tmpl, **opts):
1959 def debugtemplate(ui, repo, tmpl, **opts):
1959 """parse and apply a template
1960 """parse and apply a template
1960
1961
1961 If -r/--rev is given, the template is processed as a log template and
1962 If -r/--rev is given, the template is processed as a log template and
1962 applied to the given changesets. Otherwise, it is processed as a generic
1963 applied to the given changesets. Otherwise, it is processed as a generic
1963 template.
1964 template.
1964
1965
1965 Use --verbose to print the parsed tree.
1966 Use --verbose to print the parsed tree.
1966 """
1967 """
1967 revs = None
1968 revs = None
1968 if opts['rev']:
1969 if opts['rev']:
1969 if repo is None:
1970 if repo is None:
1970 raise error.RepoError(_('there is no Mercurial repository here '
1971 raise error.RepoError(_('there is no Mercurial repository here '
1971 '(.hg not found)'))
1972 '(.hg not found)'))
1972 revs = scmutil.revrange(repo, opts['rev'])
1973 revs = scmutil.revrange(repo, opts['rev'])
1973
1974
1974 props = {}
1975 props = {}
1975 for d in opts['define']:
1976 for d in opts['define']:
1976 try:
1977 try:
1977 k, v = (e.strip() for e in d.split('=', 1))
1978 k, v = (e.strip() for e in d.split('=', 1))
1978 if not k:
1979 if not k:
1979 raise ValueError
1980 raise ValueError
1980 props[k] = v
1981 props[k] = v
1981 except ValueError:
1982 except ValueError:
1982 raise error.Abort(_('malformed keyword definition: %s') % d)
1983 raise error.Abort(_('malformed keyword definition: %s') % d)
1983
1984
1984 if ui.verbose:
1985 if ui.verbose:
1985 aliases = ui.configitems('templatealias')
1986 aliases = ui.configitems('templatealias')
1986 tree = templater.parse(tmpl)
1987 tree = templater.parse(tmpl)
1987 ui.note(templater.prettyformat(tree), '\n')
1988 ui.note(templater.prettyformat(tree), '\n')
1988 newtree = templater.expandaliases(tree, aliases)
1989 newtree = templater.expandaliases(tree, aliases)
1989 if newtree != tree:
1990 if newtree != tree:
1990 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
1991 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
1991
1992
1992 mapfile = None
1993 mapfile = None
1993 if revs is None:
1994 if revs is None:
1994 k = 'debugtemplate'
1995 k = 'debugtemplate'
1995 t = formatter.maketemplater(ui, k, tmpl)
1996 t = formatter.maketemplater(ui, k, tmpl)
1996 ui.write(templater.stringify(t(k, **props)))
1997 ui.write(templater.stringify(t(k, **props)))
1997 else:
1998 else:
1998 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
1999 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
1999 mapfile, buffered=False)
2000 mapfile, buffered=False)
2000 for r in revs:
2001 for r in revs:
2001 displayer.show(repo[r], **props)
2002 displayer.show(repo[r], **props)
2002 displayer.close()
2003 displayer.close()
2003
2004
2004 @command('debugupgraderepo', [
2005 @command('debugupgraderepo', [
2005 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2006 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2006 ('', 'run', False, _('performs an upgrade')),
2007 ('', 'run', False, _('performs an upgrade')),
2007 ])
2008 ])
2008 def debugupgraderepo(ui, repo, run=False, optimize=None):
2009 def debugupgraderepo(ui, repo, run=False, optimize=None):
2009 """upgrade a repository to use different features
2010 """upgrade a repository to use different features
2010
2011
2011 If no arguments are specified, the repository is evaluated for upgrade
2012 If no arguments are specified, the repository is evaluated for upgrade
2012 and a list of problems and potential optimizations is printed.
2013 and a list of problems and potential optimizations is printed.
2013
2014
2014 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2015 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2015 can be influenced via additional arguments. More details will be provided
2016 can be influenced via additional arguments. More details will be provided
2016 by the command output when run without ``--run``.
2017 by the command output when run without ``--run``.
2017
2018
2018 During the upgrade, the repository will be locked and no writes will be
2019 During the upgrade, the repository will be locked and no writes will be
2019 allowed.
2020 allowed.
2020
2021
2021 At the end of the upgrade, the repository may not be readable while new
2022 At the end of the upgrade, the repository may not be readable while new
2022 repository data is swapped in. This window will be as long as it takes to
2023 repository data is swapped in. This window will be as long as it takes to
2023 rename some directories inside the ``.hg`` directory. On most machines, this
2024 rename some directories inside the ``.hg`` directory. On most machines, this
2024 should complete almost instantaneously and the chances of a consumer being
2025 should complete almost instantaneously and the chances of a consumer being
2025 unable to access the repository should be low.
2026 unable to access the repository should be low.
2026 """
2027 """
2027 return repair.upgraderepo(ui, repo, run=run, optimize=optimize)
2028 return repair.upgraderepo(ui, repo, run=run, optimize=optimize)
2028
2029
2029 @command('debugwalk', commands.walkopts, _('[OPTION]... [FILE]...'),
2030 @command('debugwalk', commands.walkopts, _('[OPTION]... [FILE]...'),
2030 inferrepo=True)
2031 inferrepo=True)
2031 def debugwalk(ui, repo, *pats, **opts):
2032 def debugwalk(ui, repo, *pats, **opts):
2032 """show how files match on given patterns"""
2033 """show how files match on given patterns"""
2033 m = scmutil.match(repo[None], pats, opts)
2034 m = scmutil.match(repo[None], pats, opts)
2034 items = list(repo.walk(m))
2035 items = list(repo.walk(m))
2035 if not items:
2036 if not items:
2036 return
2037 return
2037 f = lambda fn: fn
2038 f = lambda fn: fn
2038 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2039 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2039 f = lambda fn: util.normpath(fn)
2040 f = lambda fn: util.normpath(fn)
2040 fmt = 'f %%-%ds %%-%ds %%s' % (
2041 fmt = 'f %%-%ds %%-%ds %%s' % (
2041 max([len(abs) for abs in items]),
2042 max([len(abs) for abs in items]),
2042 max([len(m.rel(abs)) for abs in items]))
2043 max([len(m.rel(abs)) for abs in items]))
2043 for abs in items:
2044 for abs in items:
2044 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2045 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2045 ui.write("%s\n" % line.rstrip())
2046 ui.write("%s\n" % line.rstrip())
2046
2047
2047 @command('debugwireargs',
2048 @command('debugwireargs',
2048 [('', 'three', '', 'three'),
2049 [('', 'three', '', 'three'),
2049 ('', 'four', '', 'four'),
2050 ('', 'four', '', 'four'),
2050 ('', 'five', '', 'five'),
2051 ('', 'five', '', 'five'),
2051 ] + commands.remoteopts,
2052 ] + commands.remoteopts,
2052 _('REPO [OPTIONS]... [ONE [TWO]]'),
2053 _('REPO [OPTIONS]... [ONE [TWO]]'),
2053 norepo=True)
2054 norepo=True)
2054 def debugwireargs(ui, repopath, *vals, **opts):
2055 def debugwireargs(ui, repopath, *vals, **opts):
2055 repo = hg.peer(ui, opts, repopath)
2056 repo = hg.peer(ui, opts, repopath)
2056 for opt in commands.remoteopts:
2057 for opt in commands.remoteopts:
2057 del opts[opt[1]]
2058 del opts[opt[1]]
2058 args = {}
2059 args = {}
2059 for k, v in opts.iteritems():
2060 for k, v in opts.iteritems():
2060 if v:
2061 if v:
2061 args[k] = v
2062 args[k] = v
2062 # run twice to check that we don't mess up the stream for the next command
2063 # run twice to check that we don't mess up the stream for the next command
2063 res1 = repo.debugwireargs(*vals, **args)
2064 res1 = repo.debugwireargs(*vals, **args)
2064 res2 = repo.debugwireargs(*vals, **args)
2065 res2 = repo.debugwireargs(*vals, **args)
2065 ui.write("%s\n" % res1)
2066 ui.write("%s\n" % res1)
2066 if res1 != res2:
2067 if res1 != res2:
2067 ui.warn("%s\n" % res2)
2068 ui.warn("%s\n" % res2)
@@ -1,1322 +1,1323
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cgi
10 import cgi
11 import copy
11 import copy
12 import mimetypes
12 import mimetypes
13 import os
13 import os
14 import re
14 import re
15
15
16 from ..i18n import _
16 from ..i18n import _
17 from ..node import hex, short
17 from ..node import hex, short
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_FORBIDDEN,
21 HTTP_FORBIDDEN,
22 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
23 HTTP_OK,
23 HTTP_OK,
24 get_contact,
24 get_contact,
25 paritygen,
25 paritygen,
26 staticfile,
26 staticfile,
27 )
27 )
28
28
29 from .. import (
29 from .. import (
30 archival,
30 archival,
31 encoding,
31 encoding,
32 error,
32 error,
33 graphmod,
33 graphmod,
34 revset,
34 revset,
35 revsetlang,
35 scmutil,
36 scmutil,
36 smartset,
37 smartset,
37 templatefilters,
38 templatefilters,
38 templater,
39 templater,
39 util,
40 util,
40 )
41 )
41
42
42 from . import (
43 from . import (
43 webutil,
44 webutil,
44 )
45 )
45
46
46 __all__ = []
47 __all__ = []
47 commands = {}
48 commands = {}
48
49
49 class webcommand(object):
50 class webcommand(object):
50 """Decorator used to register a web command handler.
51 """Decorator used to register a web command handler.
51
52
52 The decorator takes as its positional arguments the name/path the
53 The decorator takes as its positional arguments the name/path the
53 command should be accessible under.
54 command should be accessible under.
54
55
55 Usage:
56 Usage:
56
57
57 @webcommand('mycommand')
58 @webcommand('mycommand')
58 def mycommand(web, req, tmpl):
59 def mycommand(web, req, tmpl):
59 pass
60 pass
60 """
61 """
61
62
62 def __init__(self, name):
63 def __init__(self, name):
63 self.name = name
64 self.name = name
64
65
65 def __call__(self, func):
66 def __call__(self, func):
66 __all__.append(self.name)
67 __all__.append(self.name)
67 commands[self.name] = func
68 commands[self.name] = func
68 return func
69 return func
69
70
70 @webcommand('log')
71 @webcommand('log')
71 def log(web, req, tmpl):
72 def log(web, req, tmpl):
72 """
73 """
73 /log[/{revision}[/{path}]]
74 /log[/{revision}[/{path}]]
74 --------------------------
75 --------------------------
75
76
76 Show repository or file history.
77 Show repository or file history.
77
78
78 For URLs of the form ``/log/{revision}``, a list of changesets starting at
79 For URLs of the form ``/log/{revision}``, a list of changesets starting at
79 the specified changeset identifier is shown. If ``{revision}`` is not
80 the specified changeset identifier is shown. If ``{revision}`` is not
80 defined, the default is ``tip``. This form is equivalent to the
81 defined, the default is ``tip``. This form is equivalent to the
81 ``changelog`` handler.
82 ``changelog`` handler.
82
83
83 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
84 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
84 file will be shown. This form is equivalent to the ``filelog`` handler.
85 file will be shown. This form is equivalent to the ``filelog`` handler.
85 """
86 """
86
87
87 if 'file' in req.form and req.form['file'][0]:
88 if 'file' in req.form and req.form['file'][0]:
88 return filelog(web, req, tmpl)
89 return filelog(web, req, tmpl)
89 else:
90 else:
90 return changelog(web, req, tmpl)
91 return changelog(web, req, tmpl)
91
92
92 @webcommand('rawfile')
93 @webcommand('rawfile')
93 def rawfile(web, req, tmpl):
94 def rawfile(web, req, tmpl):
94 guessmime = web.configbool('web', 'guessmime', False)
95 guessmime = web.configbool('web', 'guessmime', False)
95
96
96 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
97 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
97 if not path:
98 if not path:
98 content = manifest(web, req, tmpl)
99 content = manifest(web, req, tmpl)
99 req.respond(HTTP_OK, web.ctype)
100 req.respond(HTTP_OK, web.ctype)
100 return content
101 return content
101
102
102 try:
103 try:
103 fctx = webutil.filectx(web.repo, req)
104 fctx = webutil.filectx(web.repo, req)
104 except error.LookupError as inst:
105 except error.LookupError as inst:
105 try:
106 try:
106 content = manifest(web, req, tmpl)
107 content = manifest(web, req, tmpl)
107 req.respond(HTTP_OK, web.ctype)
108 req.respond(HTTP_OK, web.ctype)
108 return content
109 return content
109 except ErrorResponse:
110 except ErrorResponse:
110 raise inst
111 raise inst
111
112
112 path = fctx.path()
113 path = fctx.path()
113 text = fctx.data()
114 text = fctx.data()
114 mt = 'application/binary'
115 mt = 'application/binary'
115 if guessmime:
116 if guessmime:
116 mt = mimetypes.guess_type(path)[0]
117 mt = mimetypes.guess_type(path)[0]
117 if mt is None:
118 if mt is None:
118 if util.binary(text):
119 if util.binary(text):
119 mt = 'application/binary'
120 mt = 'application/binary'
120 else:
121 else:
121 mt = 'text/plain'
122 mt = 'text/plain'
122 if mt.startswith('text/'):
123 if mt.startswith('text/'):
123 mt += '; charset="%s"' % encoding.encoding
124 mt += '; charset="%s"' % encoding.encoding
124
125
125 req.respond(HTTP_OK, mt, path, body=text)
126 req.respond(HTTP_OK, mt, path, body=text)
126 return []
127 return []
127
128
128 def _filerevision(web, req, tmpl, fctx):
129 def _filerevision(web, req, tmpl, fctx):
129 f = fctx.path()
130 f = fctx.path()
130 text = fctx.data()
131 text = fctx.data()
131 parity = paritygen(web.stripecount)
132 parity = paritygen(web.stripecount)
132
133
133 if util.binary(text):
134 if util.binary(text):
134 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
135 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
135 text = '(binary:%s)' % mt
136 text = '(binary:%s)' % mt
136
137
137 def lines():
138 def lines():
138 for lineno, t in enumerate(text.splitlines(True)):
139 for lineno, t in enumerate(text.splitlines(True)):
139 yield {"line": t,
140 yield {"line": t,
140 "lineid": "l%d" % (lineno + 1),
141 "lineid": "l%d" % (lineno + 1),
141 "linenumber": "% 6d" % (lineno + 1),
142 "linenumber": "% 6d" % (lineno + 1),
142 "parity": next(parity)}
143 "parity": next(parity)}
143
144
144 return tmpl("filerevision",
145 return tmpl("filerevision",
145 file=f,
146 file=f,
146 path=webutil.up(f),
147 path=webutil.up(f),
147 text=lines(),
148 text=lines(),
148 symrev=webutil.symrevorshortnode(req, fctx),
149 symrev=webutil.symrevorshortnode(req, fctx),
149 rename=webutil.renamelink(fctx),
150 rename=webutil.renamelink(fctx),
150 permissions=fctx.manifest().flags(f),
151 permissions=fctx.manifest().flags(f),
151 **webutil.commonentry(web.repo, fctx))
152 **webutil.commonentry(web.repo, fctx))
152
153
153 @webcommand('file')
154 @webcommand('file')
154 def file(web, req, tmpl):
155 def file(web, req, tmpl):
155 """
156 """
156 /file/{revision}[/{path}]
157 /file/{revision}[/{path}]
157 -------------------------
158 -------------------------
158
159
159 Show information about a directory or file in the repository.
160 Show information about a directory or file in the repository.
160
161
161 Info about the ``path`` given as a URL parameter will be rendered.
162 Info about the ``path`` given as a URL parameter will be rendered.
162
163
163 If ``path`` is a directory, information about the entries in that
164 If ``path`` is a directory, information about the entries in that
164 directory will be rendered. This form is equivalent to the ``manifest``
165 directory will be rendered. This form is equivalent to the ``manifest``
165 handler.
166 handler.
166
167
167 If ``path`` is a file, information about that file will be shown via
168 If ``path`` is a file, information about that file will be shown via
168 the ``filerevision`` template.
169 the ``filerevision`` template.
169
170
170 If ``path`` is not defined, information about the root directory will
171 If ``path`` is not defined, information about the root directory will
171 be rendered.
172 be rendered.
172 """
173 """
173 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
174 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
174 if not path:
175 if not path:
175 return manifest(web, req, tmpl)
176 return manifest(web, req, tmpl)
176 try:
177 try:
177 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
178 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
178 except error.LookupError as inst:
179 except error.LookupError as inst:
179 try:
180 try:
180 return manifest(web, req, tmpl)
181 return manifest(web, req, tmpl)
181 except ErrorResponse:
182 except ErrorResponse:
182 raise inst
183 raise inst
183
184
184 def _search(web, req, tmpl):
185 def _search(web, req, tmpl):
185 MODE_REVISION = 'rev'
186 MODE_REVISION = 'rev'
186 MODE_KEYWORD = 'keyword'
187 MODE_KEYWORD = 'keyword'
187 MODE_REVSET = 'revset'
188 MODE_REVSET = 'revset'
188
189
189 def revsearch(ctx):
190 def revsearch(ctx):
190 yield ctx
191 yield ctx
191
192
192 def keywordsearch(query):
193 def keywordsearch(query):
193 lower = encoding.lower
194 lower = encoding.lower
194 qw = lower(query).split()
195 qw = lower(query).split()
195
196
196 def revgen():
197 def revgen():
197 cl = web.repo.changelog
198 cl = web.repo.changelog
198 for i in xrange(len(web.repo) - 1, 0, -100):
199 for i in xrange(len(web.repo) - 1, 0, -100):
199 l = []
200 l = []
200 for j in cl.revs(max(0, i - 99), i):
201 for j in cl.revs(max(0, i - 99), i):
201 ctx = web.repo[j]
202 ctx = web.repo[j]
202 l.append(ctx)
203 l.append(ctx)
203 l.reverse()
204 l.reverse()
204 for e in l:
205 for e in l:
205 yield e
206 yield e
206
207
207 for ctx in revgen():
208 for ctx in revgen():
208 miss = 0
209 miss = 0
209 for q in qw:
210 for q in qw:
210 if not (q in lower(ctx.user()) or
211 if not (q in lower(ctx.user()) or
211 q in lower(ctx.description()) or
212 q in lower(ctx.description()) or
212 q in lower(" ".join(ctx.files()))):
213 q in lower(" ".join(ctx.files()))):
213 miss = 1
214 miss = 1
214 break
215 break
215 if miss:
216 if miss:
216 continue
217 continue
217
218
218 yield ctx
219 yield ctx
219
220
220 def revsetsearch(revs):
221 def revsetsearch(revs):
221 for r in revs:
222 for r in revs:
222 yield web.repo[r]
223 yield web.repo[r]
223
224
224 searchfuncs = {
225 searchfuncs = {
225 MODE_REVISION: (revsearch, 'exact revision search'),
226 MODE_REVISION: (revsearch, 'exact revision search'),
226 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
227 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
227 MODE_REVSET: (revsetsearch, 'revset expression search'),
228 MODE_REVSET: (revsetsearch, 'revset expression search'),
228 }
229 }
229
230
230 def getsearchmode(query):
231 def getsearchmode(query):
231 try:
232 try:
232 ctx = web.repo[query]
233 ctx = web.repo[query]
233 except (error.RepoError, error.LookupError):
234 except (error.RepoError, error.LookupError):
234 # query is not an exact revision pointer, need to
235 # query is not an exact revision pointer, need to
235 # decide if it's a revset expression or keywords
236 # decide if it's a revset expression or keywords
236 pass
237 pass
237 else:
238 else:
238 return MODE_REVISION, ctx
239 return MODE_REVISION, ctx
239
240
240 revdef = 'reverse(%s)' % query
241 revdef = 'reverse(%s)' % query
241 try:
242 try:
242 tree = revset.parse(revdef)
243 tree = revsetlang.parse(revdef)
243 except error.ParseError:
244 except error.ParseError:
244 # can't parse to a revset tree
245 # can't parse to a revset tree
245 return MODE_KEYWORD, query
246 return MODE_KEYWORD, query
246
247
247 if revset.depth(tree) <= 2:
248 if revsetlang.depth(tree) <= 2:
248 # no revset syntax used
249 # no revset syntax used
249 return MODE_KEYWORD, query
250 return MODE_KEYWORD, query
250
251
251 if any((token, (value or '')[:3]) == ('string', 're:')
252 if any((token, (value or '')[:3]) == ('string', 're:')
252 for token, value, pos in revset.tokenize(revdef)):
253 for token, value, pos in revsetlang.tokenize(revdef)):
253 return MODE_KEYWORD, query
254 return MODE_KEYWORD, query
254
255
255 funcsused = revset.funcsused(tree)
256 funcsused = revsetlang.funcsused(tree)
256 if not funcsused.issubset(revset.safesymbols):
257 if not funcsused.issubset(revset.safesymbols):
257 return MODE_KEYWORD, query
258 return MODE_KEYWORD, query
258
259
259 mfunc = revset.match(web.repo.ui, revdef)
260 mfunc = revset.match(web.repo.ui, revdef)
260 try:
261 try:
261 revs = mfunc(web.repo)
262 revs = mfunc(web.repo)
262 return MODE_REVSET, revs
263 return MODE_REVSET, revs
263 # ParseError: wrongly placed tokens, wrongs arguments, etc
264 # ParseError: wrongly placed tokens, wrongs arguments, etc
264 # RepoLookupError: no such revision, e.g. in 'revision:'
265 # RepoLookupError: no such revision, e.g. in 'revision:'
265 # Abort: bookmark/tag not exists
266 # Abort: bookmark/tag not exists
266 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
267 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
267 except (error.ParseError, error.RepoLookupError, error.Abort,
268 except (error.ParseError, error.RepoLookupError, error.Abort,
268 LookupError):
269 LookupError):
269 return MODE_KEYWORD, query
270 return MODE_KEYWORD, query
270
271
271 def changelist(**map):
272 def changelist(**map):
272 count = 0
273 count = 0
273
274
274 for ctx in searchfunc[0](funcarg):
275 for ctx in searchfunc[0](funcarg):
275 count += 1
276 count += 1
276 n = ctx.node()
277 n = ctx.node()
277 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
278 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
278 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
279 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
279
280
280 yield tmpl('searchentry',
281 yield tmpl('searchentry',
281 parity=next(parity),
282 parity=next(parity),
282 changelogtag=showtags,
283 changelogtag=showtags,
283 files=files,
284 files=files,
284 **webutil.commonentry(web.repo, ctx))
285 **webutil.commonentry(web.repo, ctx))
285
286
286 if count >= revcount:
287 if count >= revcount:
287 break
288 break
288
289
289 query = req.form['rev'][0]
290 query = req.form['rev'][0]
290 revcount = web.maxchanges
291 revcount = web.maxchanges
291 if 'revcount' in req.form:
292 if 'revcount' in req.form:
292 try:
293 try:
293 revcount = int(req.form.get('revcount', [revcount])[0])
294 revcount = int(req.form.get('revcount', [revcount])[0])
294 revcount = max(revcount, 1)
295 revcount = max(revcount, 1)
295 tmpl.defaults['sessionvars']['revcount'] = revcount
296 tmpl.defaults['sessionvars']['revcount'] = revcount
296 except ValueError:
297 except ValueError:
297 pass
298 pass
298
299
299 lessvars = copy.copy(tmpl.defaults['sessionvars'])
300 lessvars = copy.copy(tmpl.defaults['sessionvars'])
300 lessvars['revcount'] = max(revcount / 2, 1)
301 lessvars['revcount'] = max(revcount / 2, 1)
301 lessvars['rev'] = query
302 lessvars['rev'] = query
302 morevars = copy.copy(tmpl.defaults['sessionvars'])
303 morevars = copy.copy(tmpl.defaults['sessionvars'])
303 morevars['revcount'] = revcount * 2
304 morevars['revcount'] = revcount * 2
304 morevars['rev'] = query
305 morevars['rev'] = query
305
306
306 mode, funcarg = getsearchmode(query)
307 mode, funcarg = getsearchmode(query)
307
308
308 if 'forcekw' in req.form:
309 if 'forcekw' in req.form:
309 showforcekw = ''
310 showforcekw = ''
310 showunforcekw = searchfuncs[mode][1]
311 showunforcekw = searchfuncs[mode][1]
311 mode = MODE_KEYWORD
312 mode = MODE_KEYWORD
312 funcarg = query
313 funcarg = query
313 else:
314 else:
314 if mode != MODE_KEYWORD:
315 if mode != MODE_KEYWORD:
315 showforcekw = searchfuncs[MODE_KEYWORD][1]
316 showforcekw = searchfuncs[MODE_KEYWORD][1]
316 else:
317 else:
317 showforcekw = ''
318 showforcekw = ''
318 showunforcekw = ''
319 showunforcekw = ''
319
320
320 searchfunc = searchfuncs[mode]
321 searchfunc = searchfuncs[mode]
321
322
322 tip = web.repo['tip']
323 tip = web.repo['tip']
323 parity = paritygen(web.stripecount)
324 parity = paritygen(web.stripecount)
324
325
325 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
326 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
326 entries=changelist, archives=web.archivelist("tip"),
327 entries=changelist, archives=web.archivelist("tip"),
327 morevars=morevars, lessvars=lessvars,
328 morevars=morevars, lessvars=lessvars,
328 modedesc=searchfunc[1],
329 modedesc=searchfunc[1],
329 showforcekw=showforcekw, showunforcekw=showunforcekw)
330 showforcekw=showforcekw, showunforcekw=showunforcekw)
330
331
331 @webcommand('changelog')
332 @webcommand('changelog')
332 def changelog(web, req, tmpl, shortlog=False):
333 def changelog(web, req, tmpl, shortlog=False):
333 """
334 """
334 /changelog[/{revision}]
335 /changelog[/{revision}]
335 -----------------------
336 -----------------------
336
337
337 Show information about multiple changesets.
338 Show information about multiple changesets.
338
339
339 If the optional ``revision`` URL argument is absent, information about
340 If the optional ``revision`` URL argument is absent, information about
340 all changesets starting at ``tip`` will be rendered. If the ``revision``
341 all changesets starting at ``tip`` will be rendered. If the ``revision``
341 argument is present, changesets will be shown starting from the specified
342 argument is present, changesets will be shown starting from the specified
342 revision.
343 revision.
343
344
344 If ``revision`` is absent, the ``rev`` query string argument may be
345 If ``revision`` is absent, the ``rev`` query string argument may be
345 defined. This will perform a search for changesets.
346 defined. This will perform a search for changesets.
346
347
347 The argument for ``rev`` can be a single revision, a revision set,
348 The argument for ``rev`` can be a single revision, a revision set,
348 or a literal keyword to search for in changeset data (equivalent to
349 or a literal keyword to search for in changeset data (equivalent to
349 :hg:`log -k`).
350 :hg:`log -k`).
350
351
351 The ``revcount`` query string argument defines the maximum numbers of
352 The ``revcount`` query string argument defines the maximum numbers of
352 changesets to render.
353 changesets to render.
353
354
354 For non-searches, the ``changelog`` template will be rendered.
355 For non-searches, the ``changelog`` template will be rendered.
355 """
356 """
356
357
357 query = ''
358 query = ''
358 if 'node' in req.form:
359 if 'node' in req.form:
359 ctx = webutil.changectx(web.repo, req)
360 ctx = webutil.changectx(web.repo, req)
360 symrev = webutil.symrevorshortnode(req, ctx)
361 symrev = webutil.symrevorshortnode(req, ctx)
361 elif 'rev' in req.form:
362 elif 'rev' in req.form:
362 return _search(web, req, tmpl)
363 return _search(web, req, tmpl)
363 else:
364 else:
364 ctx = web.repo['tip']
365 ctx = web.repo['tip']
365 symrev = 'tip'
366 symrev = 'tip'
366
367
367 def changelist():
368 def changelist():
368 revs = []
369 revs = []
369 if pos != -1:
370 if pos != -1:
370 revs = web.repo.changelog.revs(pos, 0)
371 revs = web.repo.changelog.revs(pos, 0)
371 curcount = 0
372 curcount = 0
372 for rev in revs:
373 for rev in revs:
373 curcount += 1
374 curcount += 1
374 if curcount > revcount + 1:
375 if curcount > revcount + 1:
375 break
376 break
376
377
377 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
378 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
378 entry['parity'] = next(parity)
379 entry['parity'] = next(parity)
379 yield entry
380 yield entry
380
381
381 if shortlog:
382 if shortlog:
382 revcount = web.maxshortchanges
383 revcount = web.maxshortchanges
383 else:
384 else:
384 revcount = web.maxchanges
385 revcount = web.maxchanges
385
386
386 if 'revcount' in req.form:
387 if 'revcount' in req.form:
387 try:
388 try:
388 revcount = int(req.form.get('revcount', [revcount])[0])
389 revcount = int(req.form.get('revcount', [revcount])[0])
389 revcount = max(revcount, 1)
390 revcount = max(revcount, 1)
390 tmpl.defaults['sessionvars']['revcount'] = revcount
391 tmpl.defaults['sessionvars']['revcount'] = revcount
391 except ValueError:
392 except ValueError:
392 pass
393 pass
393
394
394 lessvars = copy.copy(tmpl.defaults['sessionvars'])
395 lessvars = copy.copy(tmpl.defaults['sessionvars'])
395 lessvars['revcount'] = max(revcount / 2, 1)
396 lessvars['revcount'] = max(revcount / 2, 1)
396 morevars = copy.copy(tmpl.defaults['sessionvars'])
397 morevars = copy.copy(tmpl.defaults['sessionvars'])
397 morevars['revcount'] = revcount * 2
398 morevars['revcount'] = revcount * 2
398
399
399 count = len(web.repo)
400 count = len(web.repo)
400 pos = ctx.rev()
401 pos = ctx.rev()
401 parity = paritygen(web.stripecount)
402 parity = paritygen(web.stripecount)
402
403
403 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
404 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
404
405
405 entries = list(changelist())
406 entries = list(changelist())
406 latestentry = entries[:1]
407 latestentry = entries[:1]
407 if len(entries) > revcount:
408 if len(entries) > revcount:
408 nextentry = entries[-1:]
409 nextentry = entries[-1:]
409 entries = entries[:-1]
410 entries = entries[:-1]
410 else:
411 else:
411 nextentry = []
412 nextentry = []
412
413
413 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
414 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
414 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
415 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
415 entries=entries,
416 entries=entries,
416 latestentry=latestentry, nextentry=nextentry,
417 latestentry=latestentry, nextentry=nextentry,
417 archives=web.archivelist("tip"), revcount=revcount,
418 archives=web.archivelist("tip"), revcount=revcount,
418 morevars=morevars, lessvars=lessvars, query=query)
419 morevars=morevars, lessvars=lessvars, query=query)
419
420
420 @webcommand('shortlog')
421 @webcommand('shortlog')
421 def shortlog(web, req, tmpl):
422 def shortlog(web, req, tmpl):
422 """
423 """
423 /shortlog
424 /shortlog
424 ---------
425 ---------
425
426
426 Show basic information about a set of changesets.
427 Show basic information about a set of changesets.
427
428
428 This accepts the same parameters as the ``changelog`` handler. The only
429 This accepts the same parameters as the ``changelog`` handler. The only
429 difference is the ``shortlog`` template will be rendered instead of the
430 difference is the ``shortlog`` template will be rendered instead of the
430 ``changelog`` template.
431 ``changelog`` template.
431 """
432 """
432 return changelog(web, req, tmpl, shortlog=True)
433 return changelog(web, req, tmpl, shortlog=True)
433
434
434 @webcommand('changeset')
435 @webcommand('changeset')
435 def changeset(web, req, tmpl):
436 def changeset(web, req, tmpl):
436 """
437 """
437 /changeset[/{revision}]
438 /changeset[/{revision}]
438 -----------------------
439 -----------------------
439
440
440 Show information about a single changeset.
441 Show information about a single changeset.
441
442
442 A URL path argument is the changeset identifier to show. See ``hg help
443 A URL path argument is the changeset identifier to show. See ``hg help
443 revisions`` for possible values. If not defined, the ``tip`` changeset
444 revisions`` for possible values. If not defined, the ``tip`` changeset
444 will be shown.
445 will be shown.
445
446
446 The ``changeset`` template is rendered. Contents of the ``changesettag``,
447 The ``changeset`` template is rendered. Contents of the ``changesettag``,
447 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
448 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
448 templates related to diffs may all be used to produce the output.
449 templates related to diffs may all be used to produce the output.
449 """
450 """
450 ctx = webutil.changectx(web.repo, req)
451 ctx = webutil.changectx(web.repo, req)
451
452
452 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
453 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
453
454
454 rev = webcommand('rev')(changeset)
455 rev = webcommand('rev')(changeset)
455
456
456 def decodepath(path):
457 def decodepath(path):
457 """Hook for mapping a path in the repository to a path in the
458 """Hook for mapping a path in the repository to a path in the
458 working copy.
459 working copy.
459
460
460 Extensions (e.g., largefiles) can override this to remap files in
461 Extensions (e.g., largefiles) can override this to remap files in
461 the virtual file system presented by the manifest command below."""
462 the virtual file system presented by the manifest command below."""
462 return path
463 return path
463
464
464 @webcommand('manifest')
465 @webcommand('manifest')
465 def manifest(web, req, tmpl):
466 def manifest(web, req, tmpl):
466 """
467 """
467 /manifest[/{revision}[/{path}]]
468 /manifest[/{revision}[/{path}]]
468 -------------------------------
469 -------------------------------
469
470
470 Show information about a directory.
471 Show information about a directory.
471
472
472 If the URL path arguments are omitted, information about the root
473 If the URL path arguments are omitted, information about the root
473 directory for the ``tip`` changeset will be shown.
474 directory for the ``tip`` changeset will be shown.
474
475
475 Because this handler can only show information for directories, it
476 Because this handler can only show information for directories, it
476 is recommended to use the ``file`` handler instead, as it can handle both
477 is recommended to use the ``file`` handler instead, as it can handle both
477 directories and files.
478 directories and files.
478
479
479 The ``manifest`` template will be rendered for this handler.
480 The ``manifest`` template will be rendered for this handler.
480 """
481 """
481 if 'node' in req.form:
482 if 'node' in req.form:
482 ctx = webutil.changectx(web.repo, req)
483 ctx = webutil.changectx(web.repo, req)
483 symrev = webutil.symrevorshortnode(req, ctx)
484 symrev = webutil.symrevorshortnode(req, ctx)
484 else:
485 else:
485 ctx = web.repo['tip']
486 ctx = web.repo['tip']
486 symrev = 'tip'
487 symrev = 'tip'
487 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
488 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
488 mf = ctx.manifest()
489 mf = ctx.manifest()
489 node = ctx.node()
490 node = ctx.node()
490
491
491 files = {}
492 files = {}
492 dirs = {}
493 dirs = {}
493 parity = paritygen(web.stripecount)
494 parity = paritygen(web.stripecount)
494
495
495 if path and path[-1] != "/":
496 if path and path[-1] != "/":
496 path += "/"
497 path += "/"
497 l = len(path)
498 l = len(path)
498 abspath = "/" + path
499 abspath = "/" + path
499
500
500 for full, n in mf.iteritems():
501 for full, n in mf.iteritems():
501 # the virtual path (working copy path) used for the full
502 # the virtual path (working copy path) used for the full
502 # (repository) path
503 # (repository) path
503 f = decodepath(full)
504 f = decodepath(full)
504
505
505 if f[:l] != path:
506 if f[:l] != path:
506 continue
507 continue
507 remain = f[l:]
508 remain = f[l:]
508 elements = remain.split('/')
509 elements = remain.split('/')
509 if len(elements) == 1:
510 if len(elements) == 1:
510 files[remain] = full
511 files[remain] = full
511 else:
512 else:
512 h = dirs # need to retain ref to dirs (root)
513 h = dirs # need to retain ref to dirs (root)
513 for elem in elements[0:-1]:
514 for elem in elements[0:-1]:
514 if elem not in h:
515 if elem not in h:
515 h[elem] = {}
516 h[elem] = {}
516 h = h[elem]
517 h = h[elem]
517 if len(h) > 1:
518 if len(h) > 1:
518 break
519 break
519 h[None] = None # denotes files present
520 h[None] = None # denotes files present
520
521
521 if mf and not files and not dirs:
522 if mf and not files and not dirs:
522 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
523 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
523
524
524 def filelist(**map):
525 def filelist(**map):
525 for f in sorted(files):
526 for f in sorted(files):
526 full = files[f]
527 full = files[f]
527
528
528 fctx = ctx.filectx(full)
529 fctx = ctx.filectx(full)
529 yield {"file": full,
530 yield {"file": full,
530 "parity": next(parity),
531 "parity": next(parity),
531 "basename": f,
532 "basename": f,
532 "date": fctx.date(),
533 "date": fctx.date(),
533 "size": fctx.size(),
534 "size": fctx.size(),
534 "permissions": mf.flags(full)}
535 "permissions": mf.flags(full)}
535
536
536 def dirlist(**map):
537 def dirlist(**map):
537 for d in sorted(dirs):
538 for d in sorted(dirs):
538
539
539 emptydirs = []
540 emptydirs = []
540 h = dirs[d]
541 h = dirs[d]
541 while isinstance(h, dict) and len(h) == 1:
542 while isinstance(h, dict) and len(h) == 1:
542 k, v = h.items()[0]
543 k, v = h.items()[0]
543 if v:
544 if v:
544 emptydirs.append(k)
545 emptydirs.append(k)
545 h = v
546 h = v
546
547
547 path = "%s%s" % (abspath, d)
548 path = "%s%s" % (abspath, d)
548 yield {"parity": next(parity),
549 yield {"parity": next(parity),
549 "path": path,
550 "path": path,
550 "emptydirs": "/".join(emptydirs),
551 "emptydirs": "/".join(emptydirs),
551 "basename": d}
552 "basename": d}
552
553
553 return tmpl("manifest",
554 return tmpl("manifest",
554 symrev=symrev,
555 symrev=symrev,
555 path=abspath,
556 path=abspath,
556 up=webutil.up(abspath),
557 up=webutil.up(abspath),
557 upparity=next(parity),
558 upparity=next(parity),
558 fentries=filelist,
559 fentries=filelist,
559 dentries=dirlist,
560 dentries=dirlist,
560 archives=web.archivelist(hex(node)),
561 archives=web.archivelist(hex(node)),
561 **webutil.commonentry(web.repo, ctx))
562 **webutil.commonentry(web.repo, ctx))
562
563
563 @webcommand('tags')
564 @webcommand('tags')
564 def tags(web, req, tmpl):
565 def tags(web, req, tmpl):
565 """
566 """
566 /tags
567 /tags
567 -----
568 -----
568
569
569 Show information about tags.
570 Show information about tags.
570
571
571 No arguments are accepted.
572 No arguments are accepted.
572
573
573 The ``tags`` template is rendered.
574 The ``tags`` template is rendered.
574 """
575 """
575 i = list(reversed(web.repo.tagslist()))
576 i = list(reversed(web.repo.tagslist()))
576 parity = paritygen(web.stripecount)
577 parity = paritygen(web.stripecount)
577
578
578 def entries(notip, latestonly, **map):
579 def entries(notip, latestonly, **map):
579 t = i
580 t = i
580 if notip:
581 if notip:
581 t = [(k, n) for k, n in i if k != "tip"]
582 t = [(k, n) for k, n in i if k != "tip"]
582 if latestonly:
583 if latestonly:
583 t = t[:1]
584 t = t[:1]
584 for k, n in t:
585 for k, n in t:
585 yield {"parity": next(parity),
586 yield {"parity": next(parity),
586 "tag": k,
587 "tag": k,
587 "date": web.repo[n].date(),
588 "date": web.repo[n].date(),
588 "node": hex(n)}
589 "node": hex(n)}
589
590
590 return tmpl("tags",
591 return tmpl("tags",
591 node=hex(web.repo.changelog.tip()),
592 node=hex(web.repo.changelog.tip()),
592 entries=lambda **x: entries(False, False, **x),
593 entries=lambda **x: entries(False, False, **x),
593 entriesnotip=lambda **x: entries(True, False, **x),
594 entriesnotip=lambda **x: entries(True, False, **x),
594 latestentry=lambda **x: entries(True, True, **x))
595 latestentry=lambda **x: entries(True, True, **x))
595
596
596 @webcommand('bookmarks')
597 @webcommand('bookmarks')
597 def bookmarks(web, req, tmpl):
598 def bookmarks(web, req, tmpl):
598 """
599 """
599 /bookmarks
600 /bookmarks
600 ----------
601 ----------
601
602
602 Show information about bookmarks.
603 Show information about bookmarks.
603
604
604 No arguments are accepted.
605 No arguments are accepted.
605
606
606 The ``bookmarks`` template is rendered.
607 The ``bookmarks`` template is rendered.
607 """
608 """
608 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
609 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
609 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
610 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
610 i = sorted(i, key=sortkey, reverse=True)
611 i = sorted(i, key=sortkey, reverse=True)
611 parity = paritygen(web.stripecount)
612 parity = paritygen(web.stripecount)
612
613
613 def entries(latestonly, **map):
614 def entries(latestonly, **map):
614 t = i
615 t = i
615 if latestonly:
616 if latestonly:
616 t = i[:1]
617 t = i[:1]
617 for k, n in t:
618 for k, n in t:
618 yield {"parity": next(parity),
619 yield {"parity": next(parity),
619 "bookmark": k,
620 "bookmark": k,
620 "date": web.repo[n].date(),
621 "date": web.repo[n].date(),
621 "node": hex(n)}
622 "node": hex(n)}
622
623
623 if i:
624 if i:
624 latestrev = i[0][1]
625 latestrev = i[0][1]
625 else:
626 else:
626 latestrev = -1
627 latestrev = -1
627
628
628 return tmpl("bookmarks",
629 return tmpl("bookmarks",
629 node=hex(web.repo.changelog.tip()),
630 node=hex(web.repo.changelog.tip()),
630 lastchange=[{"date": web.repo[latestrev].date()}],
631 lastchange=[{"date": web.repo[latestrev].date()}],
631 entries=lambda **x: entries(latestonly=False, **x),
632 entries=lambda **x: entries(latestonly=False, **x),
632 latestentry=lambda **x: entries(latestonly=True, **x))
633 latestentry=lambda **x: entries(latestonly=True, **x))
633
634
634 @webcommand('branches')
635 @webcommand('branches')
635 def branches(web, req, tmpl):
636 def branches(web, req, tmpl):
636 """
637 """
637 /branches
638 /branches
638 ---------
639 ---------
639
640
640 Show information about branches.
641 Show information about branches.
641
642
642 All known branches are contained in the output, even closed branches.
643 All known branches are contained in the output, even closed branches.
643
644
644 No arguments are accepted.
645 No arguments are accepted.
645
646
646 The ``branches`` template is rendered.
647 The ``branches`` template is rendered.
647 """
648 """
648 entries = webutil.branchentries(web.repo, web.stripecount)
649 entries = webutil.branchentries(web.repo, web.stripecount)
649 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
650 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
650 return tmpl('branches', node=hex(web.repo.changelog.tip()),
651 return tmpl('branches', node=hex(web.repo.changelog.tip()),
651 entries=entries, latestentry=latestentry)
652 entries=entries, latestentry=latestentry)
652
653
653 @webcommand('summary')
654 @webcommand('summary')
654 def summary(web, req, tmpl):
655 def summary(web, req, tmpl):
655 """
656 """
656 /summary
657 /summary
657 --------
658 --------
658
659
659 Show a summary of repository state.
660 Show a summary of repository state.
660
661
661 Information about the latest changesets, bookmarks, tags, and branches
662 Information about the latest changesets, bookmarks, tags, and branches
662 is captured by this handler.
663 is captured by this handler.
663
664
664 The ``summary`` template is rendered.
665 The ``summary`` template is rendered.
665 """
666 """
666 i = reversed(web.repo.tagslist())
667 i = reversed(web.repo.tagslist())
667
668
668 def tagentries(**map):
669 def tagentries(**map):
669 parity = paritygen(web.stripecount)
670 parity = paritygen(web.stripecount)
670 count = 0
671 count = 0
671 for k, n in i:
672 for k, n in i:
672 if k == "tip": # skip tip
673 if k == "tip": # skip tip
673 continue
674 continue
674
675
675 count += 1
676 count += 1
676 if count > 10: # limit to 10 tags
677 if count > 10: # limit to 10 tags
677 break
678 break
678
679
679 yield tmpl("tagentry",
680 yield tmpl("tagentry",
680 parity=next(parity),
681 parity=next(parity),
681 tag=k,
682 tag=k,
682 node=hex(n),
683 node=hex(n),
683 date=web.repo[n].date())
684 date=web.repo[n].date())
684
685
685 def bookmarks(**map):
686 def bookmarks(**map):
686 parity = paritygen(web.stripecount)
687 parity = paritygen(web.stripecount)
687 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
688 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
689 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
689 marks = sorted(marks, key=sortkey, reverse=True)
690 marks = sorted(marks, key=sortkey, reverse=True)
690 for k, n in marks[:10]: # limit to 10 bookmarks
691 for k, n in marks[:10]: # limit to 10 bookmarks
691 yield {'parity': next(parity),
692 yield {'parity': next(parity),
692 'bookmark': k,
693 'bookmark': k,
693 'date': web.repo[n].date(),
694 'date': web.repo[n].date(),
694 'node': hex(n)}
695 'node': hex(n)}
695
696
696 def changelist(**map):
697 def changelist(**map):
697 parity = paritygen(web.stripecount, offset=start - end)
698 parity = paritygen(web.stripecount, offset=start - end)
698 l = [] # build a list in forward order for efficiency
699 l = [] # build a list in forward order for efficiency
699 revs = []
700 revs = []
700 if start < end:
701 if start < end:
701 revs = web.repo.changelog.revs(start, end - 1)
702 revs = web.repo.changelog.revs(start, end - 1)
702 for i in revs:
703 for i in revs:
703 ctx = web.repo[i]
704 ctx = web.repo[i]
704
705
705 l.append(tmpl(
706 l.append(tmpl(
706 'shortlogentry',
707 'shortlogentry',
707 parity=next(parity),
708 parity=next(parity),
708 **webutil.commonentry(web.repo, ctx)))
709 **webutil.commonentry(web.repo, ctx)))
709
710
710 for entry in reversed(l):
711 for entry in reversed(l):
711 yield entry
712 yield entry
712
713
713 tip = web.repo['tip']
714 tip = web.repo['tip']
714 count = len(web.repo)
715 count = len(web.repo)
715 start = max(0, count - web.maxchanges)
716 start = max(0, count - web.maxchanges)
716 end = min(count, start + web.maxchanges)
717 end = min(count, start + web.maxchanges)
717
718
718 return tmpl("summary",
719 return tmpl("summary",
719 desc=web.config("web", "description", "unknown"),
720 desc=web.config("web", "description", "unknown"),
720 owner=get_contact(web.config) or "unknown",
721 owner=get_contact(web.config) or "unknown",
721 lastchange=tip.date(),
722 lastchange=tip.date(),
722 tags=tagentries,
723 tags=tagentries,
723 bookmarks=bookmarks,
724 bookmarks=bookmarks,
724 branches=webutil.branchentries(web.repo, web.stripecount, 10),
725 branches=webutil.branchentries(web.repo, web.stripecount, 10),
725 shortlog=changelist,
726 shortlog=changelist,
726 node=tip.hex(),
727 node=tip.hex(),
727 symrev='tip',
728 symrev='tip',
728 archives=web.archivelist("tip"),
729 archives=web.archivelist("tip"),
729 labels=web.configlist('web', 'labels'))
730 labels=web.configlist('web', 'labels'))
730
731
731 @webcommand('filediff')
732 @webcommand('filediff')
732 def filediff(web, req, tmpl):
733 def filediff(web, req, tmpl):
733 """
734 """
734 /diff/{revision}/{path}
735 /diff/{revision}/{path}
735 -----------------------
736 -----------------------
736
737
737 Show how a file changed in a particular commit.
738 Show how a file changed in a particular commit.
738
739
739 The ``filediff`` template is rendered.
740 The ``filediff`` template is rendered.
740
741
741 This handler is registered under both the ``/diff`` and ``/filediff``
742 This handler is registered under both the ``/diff`` and ``/filediff``
742 paths. ``/diff`` is used in modern code.
743 paths. ``/diff`` is used in modern code.
743 """
744 """
744 fctx, ctx = None, None
745 fctx, ctx = None, None
745 try:
746 try:
746 fctx = webutil.filectx(web.repo, req)
747 fctx = webutil.filectx(web.repo, req)
747 except LookupError:
748 except LookupError:
748 ctx = webutil.changectx(web.repo, req)
749 ctx = webutil.changectx(web.repo, req)
749 path = webutil.cleanpath(web.repo, req.form['file'][0])
750 path = webutil.cleanpath(web.repo, req.form['file'][0])
750 if path not in ctx.files():
751 if path not in ctx.files():
751 raise
752 raise
752
753
753 if fctx is not None:
754 if fctx is not None:
754 path = fctx.path()
755 path = fctx.path()
755 ctx = fctx.changectx()
756 ctx = fctx.changectx()
756
757
757 parity = paritygen(web.stripecount)
758 parity = paritygen(web.stripecount)
758 style = web.config('web', 'style', 'paper')
759 style = web.config('web', 'style', 'paper')
759 if 'style' in req.form:
760 if 'style' in req.form:
760 style = req.form['style'][0]
761 style = req.form['style'][0]
761
762
762 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
763 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
763 if fctx is not None:
764 if fctx is not None:
764 rename = webutil.renamelink(fctx)
765 rename = webutil.renamelink(fctx)
765 ctx = fctx
766 ctx = fctx
766 else:
767 else:
767 rename = []
768 rename = []
768 ctx = ctx
769 ctx = ctx
769 return tmpl("filediff",
770 return tmpl("filediff",
770 file=path,
771 file=path,
771 symrev=webutil.symrevorshortnode(req, ctx),
772 symrev=webutil.symrevorshortnode(req, ctx),
772 rename=rename,
773 rename=rename,
773 diff=diffs,
774 diff=diffs,
774 **webutil.commonentry(web.repo, ctx))
775 **webutil.commonentry(web.repo, ctx))
775
776
776 diff = webcommand('diff')(filediff)
777 diff = webcommand('diff')(filediff)
777
778
778 @webcommand('comparison')
779 @webcommand('comparison')
779 def comparison(web, req, tmpl):
780 def comparison(web, req, tmpl):
780 """
781 """
781 /comparison/{revision}/{path}
782 /comparison/{revision}/{path}
782 -----------------------------
783 -----------------------------
783
784
784 Show a comparison between the old and new versions of a file from changes
785 Show a comparison between the old and new versions of a file from changes
785 made on a particular revision.
786 made on a particular revision.
786
787
787 This is similar to the ``diff`` handler. However, this form features
788 This is similar to the ``diff`` handler. However, this form features
788 a split or side-by-side diff rather than a unified diff.
789 a split or side-by-side diff rather than a unified diff.
789
790
790 The ``context`` query string argument can be used to control the lines of
791 The ``context`` query string argument can be used to control the lines of
791 context in the diff.
792 context in the diff.
792
793
793 The ``filecomparison`` template is rendered.
794 The ``filecomparison`` template is rendered.
794 """
795 """
795 ctx = webutil.changectx(web.repo, req)
796 ctx = webutil.changectx(web.repo, req)
796 if 'file' not in req.form:
797 if 'file' not in req.form:
797 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
798 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
798 path = webutil.cleanpath(web.repo, req.form['file'][0])
799 path = webutil.cleanpath(web.repo, req.form['file'][0])
799
800
800 parsecontext = lambda v: v == 'full' and -1 or int(v)
801 parsecontext = lambda v: v == 'full' and -1 or int(v)
801 if 'context' in req.form:
802 if 'context' in req.form:
802 context = parsecontext(req.form['context'][0])
803 context = parsecontext(req.form['context'][0])
803 else:
804 else:
804 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
805 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
805
806
806 def filelines(f):
807 def filelines(f):
807 if util.binary(f.data()):
808 if util.binary(f.data()):
808 mt = mimetypes.guess_type(f.path())[0]
809 mt = mimetypes.guess_type(f.path())[0]
809 if not mt:
810 if not mt:
810 mt = 'application/octet-stream'
811 mt = 'application/octet-stream'
811 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
812 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
812 return f.data().splitlines()
813 return f.data().splitlines()
813
814
814 fctx = None
815 fctx = None
815 parent = ctx.p1()
816 parent = ctx.p1()
816 leftrev = parent.rev()
817 leftrev = parent.rev()
817 leftnode = parent.node()
818 leftnode = parent.node()
818 rightrev = ctx.rev()
819 rightrev = ctx.rev()
819 rightnode = ctx.node()
820 rightnode = ctx.node()
820 if path in ctx:
821 if path in ctx:
821 fctx = ctx[path]
822 fctx = ctx[path]
822 rightlines = filelines(fctx)
823 rightlines = filelines(fctx)
823 if path not in parent:
824 if path not in parent:
824 leftlines = ()
825 leftlines = ()
825 else:
826 else:
826 pfctx = parent[path]
827 pfctx = parent[path]
827 leftlines = filelines(pfctx)
828 leftlines = filelines(pfctx)
828 else:
829 else:
829 rightlines = ()
830 rightlines = ()
830 pfctx = ctx.parents()[0][path]
831 pfctx = ctx.parents()[0][path]
831 leftlines = filelines(pfctx)
832 leftlines = filelines(pfctx)
832
833
833 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
834 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
834 if fctx is not None:
835 if fctx is not None:
835 rename = webutil.renamelink(fctx)
836 rename = webutil.renamelink(fctx)
836 ctx = fctx
837 ctx = fctx
837 else:
838 else:
838 rename = []
839 rename = []
839 ctx = ctx
840 ctx = ctx
840 return tmpl('filecomparison',
841 return tmpl('filecomparison',
841 file=path,
842 file=path,
842 symrev=webutil.symrevorshortnode(req, ctx),
843 symrev=webutil.symrevorshortnode(req, ctx),
843 rename=rename,
844 rename=rename,
844 leftrev=leftrev,
845 leftrev=leftrev,
845 leftnode=hex(leftnode),
846 leftnode=hex(leftnode),
846 rightrev=rightrev,
847 rightrev=rightrev,
847 rightnode=hex(rightnode),
848 rightnode=hex(rightnode),
848 comparison=comparison,
849 comparison=comparison,
849 **webutil.commonentry(web.repo, ctx))
850 **webutil.commonentry(web.repo, ctx))
850
851
851 @webcommand('annotate')
852 @webcommand('annotate')
852 def annotate(web, req, tmpl):
853 def annotate(web, req, tmpl):
853 """
854 """
854 /annotate/{revision}/{path}
855 /annotate/{revision}/{path}
855 ---------------------------
856 ---------------------------
856
857
857 Show changeset information for each line in a file.
858 Show changeset information for each line in a file.
858
859
859 The ``fileannotate`` template is rendered.
860 The ``fileannotate`` template is rendered.
860 """
861 """
861 fctx = webutil.filectx(web.repo, req)
862 fctx = webutil.filectx(web.repo, req)
862 f = fctx.path()
863 f = fctx.path()
863 parity = paritygen(web.stripecount)
864 parity = paritygen(web.stripecount)
864
865
865 # parents() is called once per line and several lines likely belong to
866 # parents() is called once per line and several lines likely belong to
866 # same revision. So it is worth caching.
867 # same revision. So it is worth caching.
867 # TODO there are still redundant operations within basefilectx.parents()
868 # TODO there are still redundant operations within basefilectx.parents()
868 # and from the fctx.annotate() call itself that could be cached.
869 # and from the fctx.annotate() call itself that could be cached.
869 parentscache = {}
870 parentscache = {}
870 def parents(f):
871 def parents(f):
871 rev = f.rev()
872 rev = f.rev()
872 if rev not in parentscache:
873 if rev not in parentscache:
873 parentscache[rev] = []
874 parentscache[rev] = []
874 for p in f.parents():
875 for p in f.parents():
875 entry = {
876 entry = {
876 'node': p.hex(),
877 'node': p.hex(),
877 'rev': p.rev(),
878 'rev': p.rev(),
878 }
879 }
879 parentscache[rev].append(entry)
880 parentscache[rev].append(entry)
880
881
881 for p in parentscache[rev]:
882 for p in parentscache[rev]:
882 yield p
883 yield p
883
884
884 def annotate(**map):
885 def annotate(**map):
885 if util.binary(fctx.data()):
886 if util.binary(fctx.data()):
886 mt = (mimetypes.guess_type(fctx.path())[0]
887 mt = (mimetypes.guess_type(fctx.path())[0]
887 or 'application/octet-stream')
888 or 'application/octet-stream')
888 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
889 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
889 else:
890 else:
890 lines = webutil.annotate(fctx, web.repo.ui)
891 lines = webutil.annotate(fctx, web.repo.ui)
891
892
892 previousrev = None
893 previousrev = None
893 blockparitygen = paritygen(1)
894 blockparitygen = paritygen(1)
894 for lineno, ((f, targetline), l) in enumerate(lines):
895 for lineno, ((f, targetline), l) in enumerate(lines):
895 rev = f.rev()
896 rev = f.rev()
896 if rev != previousrev:
897 if rev != previousrev:
897 blockhead = True
898 blockhead = True
898 blockparity = next(blockparitygen)
899 blockparity = next(blockparitygen)
899 else:
900 else:
900 blockhead = None
901 blockhead = None
901 previousrev = rev
902 previousrev = rev
902 yield {"parity": next(parity),
903 yield {"parity": next(parity),
903 "node": f.hex(),
904 "node": f.hex(),
904 "rev": rev,
905 "rev": rev,
905 "author": f.user(),
906 "author": f.user(),
906 "parents": parents(f),
907 "parents": parents(f),
907 "desc": f.description(),
908 "desc": f.description(),
908 "extra": f.extra(),
909 "extra": f.extra(),
909 "file": f.path(),
910 "file": f.path(),
910 "blockhead": blockhead,
911 "blockhead": blockhead,
911 "blockparity": blockparity,
912 "blockparity": blockparity,
912 "targetline": targetline,
913 "targetline": targetline,
913 "line": l,
914 "line": l,
914 "lineno": lineno + 1,
915 "lineno": lineno + 1,
915 "lineid": "l%d" % (lineno + 1),
916 "lineid": "l%d" % (lineno + 1),
916 "linenumber": "% 6d" % (lineno + 1),
917 "linenumber": "% 6d" % (lineno + 1),
917 "revdate": f.date()}
918 "revdate": f.date()}
918
919
919 return tmpl("fileannotate",
920 return tmpl("fileannotate",
920 file=f,
921 file=f,
921 annotate=annotate,
922 annotate=annotate,
922 path=webutil.up(f),
923 path=webutil.up(f),
923 symrev=webutil.symrevorshortnode(req, fctx),
924 symrev=webutil.symrevorshortnode(req, fctx),
924 rename=webutil.renamelink(fctx),
925 rename=webutil.renamelink(fctx),
925 permissions=fctx.manifest().flags(f),
926 permissions=fctx.manifest().flags(f),
926 **webutil.commonentry(web.repo, fctx))
927 **webutil.commonentry(web.repo, fctx))
927
928
928 @webcommand('filelog')
929 @webcommand('filelog')
929 def filelog(web, req, tmpl):
930 def filelog(web, req, tmpl):
930 """
931 """
931 /filelog/{revision}/{path}
932 /filelog/{revision}/{path}
932 --------------------------
933 --------------------------
933
934
934 Show information about the history of a file in the repository.
935 Show information about the history of a file in the repository.
935
936
936 The ``revcount`` query string argument can be defined to control the
937 The ``revcount`` query string argument can be defined to control the
937 maximum number of entries to show.
938 maximum number of entries to show.
938
939
939 The ``filelog`` template will be rendered.
940 The ``filelog`` template will be rendered.
940 """
941 """
941
942
942 try:
943 try:
943 fctx = webutil.filectx(web.repo, req)
944 fctx = webutil.filectx(web.repo, req)
944 f = fctx.path()
945 f = fctx.path()
945 fl = fctx.filelog()
946 fl = fctx.filelog()
946 except error.LookupError:
947 except error.LookupError:
947 f = webutil.cleanpath(web.repo, req.form['file'][0])
948 f = webutil.cleanpath(web.repo, req.form['file'][0])
948 fl = web.repo.file(f)
949 fl = web.repo.file(f)
949 numrevs = len(fl)
950 numrevs = len(fl)
950 if not numrevs: # file doesn't exist at all
951 if not numrevs: # file doesn't exist at all
951 raise
952 raise
952 rev = webutil.changectx(web.repo, req).rev()
953 rev = webutil.changectx(web.repo, req).rev()
953 first = fl.linkrev(0)
954 first = fl.linkrev(0)
954 if rev < first: # current rev is from before file existed
955 if rev < first: # current rev is from before file existed
955 raise
956 raise
956 frev = numrevs - 1
957 frev = numrevs - 1
957 while fl.linkrev(frev) > rev:
958 while fl.linkrev(frev) > rev:
958 frev -= 1
959 frev -= 1
959 fctx = web.repo.filectx(f, fl.linkrev(frev))
960 fctx = web.repo.filectx(f, fl.linkrev(frev))
960
961
961 revcount = web.maxshortchanges
962 revcount = web.maxshortchanges
962 if 'revcount' in req.form:
963 if 'revcount' in req.form:
963 try:
964 try:
964 revcount = int(req.form.get('revcount', [revcount])[0])
965 revcount = int(req.form.get('revcount', [revcount])[0])
965 revcount = max(revcount, 1)
966 revcount = max(revcount, 1)
966 tmpl.defaults['sessionvars']['revcount'] = revcount
967 tmpl.defaults['sessionvars']['revcount'] = revcount
967 except ValueError:
968 except ValueError:
968 pass
969 pass
969
970
970 lessvars = copy.copy(tmpl.defaults['sessionvars'])
971 lessvars = copy.copy(tmpl.defaults['sessionvars'])
971 lessvars['revcount'] = max(revcount / 2, 1)
972 lessvars['revcount'] = max(revcount / 2, 1)
972 morevars = copy.copy(tmpl.defaults['sessionvars'])
973 morevars = copy.copy(tmpl.defaults['sessionvars'])
973 morevars['revcount'] = revcount * 2
974 morevars['revcount'] = revcount * 2
974
975
975 count = fctx.filerev() + 1
976 count = fctx.filerev() + 1
976 start = max(0, count - revcount) # first rev on this page
977 start = max(0, count - revcount) # first rev on this page
977 end = min(count, start + revcount) # last rev on this page
978 end = min(count, start + revcount) # last rev on this page
978 parity = paritygen(web.stripecount, offset=start - end)
979 parity = paritygen(web.stripecount, offset=start - end)
979
980
980 repo = web.repo
981 repo = web.repo
981 revs = fctx.filelog().revs(start, end - 1)
982 revs = fctx.filelog().revs(start, end - 1)
982 entries = []
983 entries = []
983 for i in revs:
984 for i in revs:
984 iterfctx = fctx.filectx(i)
985 iterfctx = fctx.filectx(i)
985 entries.append(dict(
986 entries.append(dict(
986 parity=next(parity),
987 parity=next(parity),
987 filerev=i,
988 filerev=i,
988 file=f,
989 file=f,
989 rename=webutil.renamelink(iterfctx),
990 rename=webutil.renamelink(iterfctx),
990 **webutil.commonentry(repo, iterfctx)))
991 **webutil.commonentry(repo, iterfctx)))
991 entries.reverse()
992 entries.reverse()
992
993
993 latestentry = entries[:1]
994 latestentry = entries[:1]
994
995
995 revnav = webutil.filerevnav(web.repo, fctx.path())
996 revnav = webutil.filerevnav(web.repo, fctx.path())
996 nav = revnav.gen(end - 1, revcount, count)
997 nav = revnav.gen(end - 1, revcount, count)
997 return tmpl("filelog",
998 return tmpl("filelog",
998 file=f,
999 file=f,
999 nav=nav,
1000 nav=nav,
1000 symrev=webutil.symrevorshortnode(req, fctx),
1001 symrev=webutil.symrevorshortnode(req, fctx),
1001 entries=entries,
1002 entries=entries,
1002 latestentry=latestentry,
1003 latestentry=latestentry,
1003 revcount=revcount,
1004 revcount=revcount,
1004 morevars=morevars,
1005 morevars=morevars,
1005 lessvars=lessvars,
1006 lessvars=lessvars,
1006 **webutil.commonentry(web.repo, fctx))
1007 **webutil.commonentry(web.repo, fctx))
1007
1008
1008 @webcommand('archive')
1009 @webcommand('archive')
1009 def archive(web, req, tmpl):
1010 def archive(web, req, tmpl):
1010 """
1011 """
1011 /archive/{revision}.{format}[/{path}]
1012 /archive/{revision}.{format}[/{path}]
1012 -------------------------------------
1013 -------------------------------------
1013
1014
1014 Obtain an archive of repository content.
1015 Obtain an archive of repository content.
1015
1016
1016 The content and type of the archive is defined by a URL path parameter.
1017 The content and type of the archive is defined by a URL path parameter.
1017 ``format`` is the file extension of the archive type to be generated. e.g.
1018 ``format`` is the file extension of the archive type to be generated. e.g.
1018 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1019 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1019 server configuration.
1020 server configuration.
1020
1021
1021 The optional ``path`` URL parameter controls content to include in the
1022 The optional ``path`` URL parameter controls content to include in the
1022 archive. If omitted, every file in the specified revision is present in the
1023 archive. If omitted, every file in the specified revision is present in the
1023 archive. If included, only the specified file or contents of the specified
1024 archive. If included, only the specified file or contents of the specified
1024 directory will be included in the archive.
1025 directory will be included in the archive.
1025
1026
1026 No template is used for this handler. Raw, binary content is generated.
1027 No template is used for this handler. Raw, binary content is generated.
1027 """
1028 """
1028
1029
1029 type_ = req.form.get('type', [None])[0]
1030 type_ = req.form.get('type', [None])[0]
1030 allowed = web.configlist("web", "allow_archive")
1031 allowed = web.configlist("web", "allow_archive")
1031 key = req.form['node'][0]
1032 key = req.form['node'][0]
1032
1033
1033 if type_ not in web.archivespecs:
1034 if type_ not in web.archivespecs:
1034 msg = 'Unsupported archive type: %s' % type_
1035 msg = 'Unsupported archive type: %s' % type_
1035 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1036 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1036
1037
1037 if not ((type_ in allowed or
1038 if not ((type_ in allowed or
1038 web.configbool("web", "allow" + type_, False))):
1039 web.configbool("web", "allow" + type_, False))):
1039 msg = 'Archive type not allowed: %s' % type_
1040 msg = 'Archive type not allowed: %s' % type_
1040 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1041 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1041
1042
1042 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1043 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1043 cnode = web.repo.lookup(key)
1044 cnode = web.repo.lookup(key)
1044 arch_version = key
1045 arch_version = key
1045 if cnode == key or key == 'tip':
1046 if cnode == key or key == 'tip':
1046 arch_version = short(cnode)
1047 arch_version = short(cnode)
1047 name = "%s-%s" % (reponame, arch_version)
1048 name = "%s-%s" % (reponame, arch_version)
1048
1049
1049 ctx = webutil.changectx(web.repo, req)
1050 ctx = webutil.changectx(web.repo, req)
1050 pats = []
1051 pats = []
1051 matchfn = scmutil.match(ctx, [])
1052 matchfn = scmutil.match(ctx, [])
1052 file = req.form.get('file', None)
1053 file = req.form.get('file', None)
1053 if file:
1054 if file:
1054 pats = ['path:' + file[0]]
1055 pats = ['path:' + file[0]]
1055 matchfn = scmutil.match(ctx, pats, default='path')
1056 matchfn = scmutil.match(ctx, pats, default='path')
1056 if pats:
1057 if pats:
1057 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1058 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1058 if not files:
1059 if not files:
1059 raise ErrorResponse(HTTP_NOT_FOUND,
1060 raise ErrorResponse(HTTP_NOT_FOUND,
1060 'file(s) not found: %s' % file[0])
1061 'file(s) not found: %s' % file[0])
1061
1062
1062 mimetype, artype, extension, encoding = web.archivespecs[type_]
1063 mimetype, artype, extension, encoding = web.archivespecs[type_]
1063 headers = [
1064 headers = [
1064 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1065 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1065 ]
1066 ]
1066 if encoding:
1067 if encoding:
1067 headers.append(('Content-Encoding', encoding))
1068 headers.append(('Content-Encoding', encoding))
1068 req.headers.extend(headers)
1069 req.headers.extend(headers)
1069 req.respond(HTTP_OK, mimetype)
1070 req.respond(HTTP_OK, mimetype)
1070
1071
1071 archival.archive(web.repo, req, cnode, artype, prefix=name,
1072 archival.archive(web.repo, req, cnode, artype, prefix=name,
1072 matchfn=matchfn,
1073 matchfn=matchfn,
1073 subrepos=web.configbool("web", "archivesubrepos"))
1074 subrepos=web.configbool("web", "archivesubrepos"))
1074 return []
1075 return []
1075
1076
1076
1077
1077 @webcommand('static')
1078 @webcommand('static')
1078 def static(web, req, tmpl):
1079 def static(web, req, tmpl):
1079 fname = req.form['file'][0]
1080 fname = req.form['file'][0]
1080 # a repo owner may set web.static in .hg/hgrc to get any file
1081 # a repo owner may set web.static in .hg/hgrc to get any file
1081 # readable by the user running the CGI script
1082 # readable by the user running the CGI script
1082 static = web.config("web", "static", None, untrusted=False)
1083 static = web.config("web", "static", None, untrusted=False)
1083 if not static:
1084 if not static:
1084 tp = web.templatepath or templater.templatepaths()
1085 tp = web.templatepath or templater.templatepaths()
1085 if isinstance(tp, str):
1086 if isinstance(tp, str):
1086 tp = [tp]
1087 tp = [tp]
1087 static = [os.path.join(p, 'static') for p in tp]
1088 static = [os.path.join(p, 'static') for p in tp]
1088 staticfile(static, fname, req)
1089 staticfile(static, fname, req)
1089 return []
1090 return []
1090
1091
1091 @webcommand('graph')
1092 @webcommand('graph')
1092 def graph(web, req, tmpl):
1093 def graph(web, req, tmpl):
1093 """
1094 """
1094 /graph[/{revision}]
1095 /graph[/{revision}]
1095 -------------------
1096 -------------------
1096
1097
1097 Show information about the graphical topology of the repository.
1098 Show information about the graphical topology of the repository.
1098
1099
1099 Information rendered by this handler can be used to create visual
1100 Information rendered by this handler can be used to create visual
1100 representations of repository topology.
1101 representations of repository topology.
1101
1102
1102 The ``revision`` URL parameter controls the starting changeset.
1103 The ``revision`` URL parameter controls the starting changeset.
1103
1104
1104 The ``revcount`` query string argument can define the number of changesets
1105 The ``revcount`` query string argument can define the number of changesets
1105 to show information for.
1106 to show information for.
1106
1107
1107 This handler will render the ``graph`` template.
1108 This handler will render the ``graph`` template.
1108 """
1109 """
1109
1110
1110 if 'node' in req.form:
1111 if 'node' in req.form:
1111 ctx = webutil.changectx(web.repo, req)
1112 ctx = webutil.changectx(web.repo, req)
1112 symrev = webutil.symrevorshortnode(req, ctx)
1113 symrev = webutil.symrevorshortnode(req, ctx)
1113 else:
1114 else:
1114 ctx = web.repo['tip']
1115 ctx = web.repo['tip']
1115 symrev = 'tip'
1116 symrev = 'tip'
1116 rev = ctx.rev()
1117 rev = ctx.rev()
1117
1118
1118 bg_height = 39
1119 bg_height = 39
1119 revcount = web.maxshortchanges
1120 revcount = web.maxshortchanges
1120 if 'revcount' in req.form:
1121 if 'revcount' in req.form:
1121 try:
1122 try:
1122 revcount = int(req.form.get('revcount', [revcount])[0])
1123 revcount = int(req.form.get('revcount', [revcount])[0])
1123 revcount = max(revcount, 1)
1124 revcount = max(revcount, 1)
1124 tmpl.defaults['sessionvars']['revcount'] = revcount
1125 tmpl.defaults['sessionvars']['revcount'] = revcount
1125 except ValueError:
1126 except ValueError:
1126 pass
1127 pass
1127
1128
1128 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1129 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1129 lessvars['revcount'] = max(revcount / 2, 1)
1130 lessvars['revcount'] = max(revcount / 2, 1)
1130 morevars = copy.copy(tmpl.defaults['sessionvars'])
1131 morevars = copy.copy(tmpl.defaults['sessionvars'])
1131 morevars['revcount'] = revcount * 2
1132 morevars['revcount'] = revcount * 2
1132
1133
1133 count = len(web.repo)
1134 count = len(web.repo)
1134 pos = rev
1135 pos = rev
1135
1136
1136 uprev = min(max(0, count - 1), rev + revcount)
1137 uprev = min(max(0, count - 1), rev + revcount)
1137 downrev = max(0, rev - revcount)
1138 downrev = max(0, rev - revcount)
1138 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1139 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1139
1140
1140 tree = []
1141 tree = []
1141 if pos != -1:
1142 if pos != -1:
1142 allrevs = web.repo.changelog.revs(pos, 0)
1143 allrevs = web.repo.changelog.revs(pos, 0)
1143 revs = []
1144 revs = []
1144 for i in allrevs:
1145 for i in allrevs:
1145 revs.append(i)
1146 revs.append(i)
1146 if len(revs) >= revcount:
1147 if len(revs) >= revcount:
1147 break
1148 break
1148
1149
1149 # We have to feed a baseset to dagwalker as it is expecting smartset
1150 # We have to feed a baseset to dagwalker as it is expecting smartset
1150 # object. This does not have a big impact on hgweb performance itself
1151 # object. This does not have a big impact on hgweb performance itself
1151 # since hgweb graphing code is not itself lazy yet.
1152 # since hgweb graphing code is not itself lazy yet.
1152 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1153 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1153 # As we said one line above... not lazy.
1154 # As we said one line above... not lazy.
1154 tree = list(graphmod.colored(dag, web.repo))
1155 tree = list(graphmod.colored(dag, web.repo))
1155
1156
1156 def getcolumns(tree):
1157 def getcolumns(tree):
1157 cols = 0
1158 cols = 0
1158 for (id, type, ctx, vtx, edges) in tree:
1159 for (id, type, ctx, vtx, edges) in tree:
1159 if type != graphmod.CHANGESET:
1160 if type != graphmod.CHANGESET:
1160 continue
1161 continue
1161 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1162 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1162 max([edge[1] for edge in edges] or [0]))
1163 max([edge[1] for edge in edges] or [0]))
1163 return cols
1164 return cols
1164
1165
1165 def graphdata(usetuples, encodestr):
1166 def graphdata(usetuples, encodestr):
1166 data = []
1167 data = []
1167
1168
1168 row = 0
1169 row = 0
1169 for (id, type, ctx, vtx, edges) in tree:
1170 for (id, type, ctx, vtx, edges) in tree:
1170 if type != graphmod.CHANGESET:
1171 if type != graphmod.CHANGESET:
1171 continue
1172 continue
1172 node = str(ctx)
1173 node = str(ctx)
1173 age = encodestr(templatefilters.age(ctx.date()))
1174 age = encodestr(templatefilters.age(ctx.date()))
1174 desc = templatefilters.firstline(encodestr(ctx.description()))
1175 desc = templatefilters.firstline(encodestr(ctx.description()))
1175 desc = cgi.escape(templatefilters.nonempty(desc))
1176 desc = cgi.escape(templatefilters.nonempty(desc))
1176 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1177 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1177 branch = cgi.escape(encodestr(ctx.branch()))
1178 branch = cgi.escape(encodestr(ctx.branch()))
1178 try:
1179 try:
1179 branchnode = web.repo.branchtip(branch)
1180 branchnode = web.repo.branchtip(branch)
1180 except error.RepoLookupError:
1181 except error.RepoLookupError:
1181 branchnode = None
1182 branchnode = None
1182 branch = branch, branchnode == ctx.node()
1183 branch = branch, branchnode == ctx.node()
1183
1184
1184 if usetuples:
1185 if usetuples:
1185 data.append((node, vtx, edges, desc, user, age, branch,
1186 data.append((node, vtx, edges, desc, user, age, branch,
1186 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1187 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1187 [cgi.escape(encodestr(x))
1188 [cgi.escape(encodestr(x))
1188 for x in ctx.bookmarks()]))
1189 for x in ctx.bookmarks()]))
1189 else:
1190 else:
1190 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1191 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1191 'color': (edge[2] - 1) % 6 + 1,
1192 'color': (edge[2] - 1) % 6 + 1,
1192 'width': edge[3], 'bcolor': edge[4]}
1193 'width': edge[3], 'bcolor': edge[4]}
1193 for edge in edges]
1194 for edge in edges]
1194
1195
1195 data.append(
1196 data.append(
1196 {'node': node,
1197 {'node': node,
1197 'col': vtx[0],
1198 'col': vtx[0],
1198 'color': (vtx[1] - 1) % 6 + 1,
1199 'color': (vtx[1] - 1) % 6 + 1,
1199 'edges': edgedata,
1200 'edges': edgedata,
1200 'row': row,
1201 'row': row,
1201 'nextrow': row + 1,
1202 'nextrow': row + 1,
1202 'desc': desc,
1203 'desc': desc,
1203 'user': user,
1204 'user': user,
1204 'age': age,
1205 'age': age,
1205 'bookmarks': webutil.nodebookmarksdict(
1206 'bookmarks': webutil.nodebookmarksdict(
1206 web.repo, ctx.node()),
1207 web.repo, ctx.node()),
1207 'branches': webutil.nodebranchdict(web.repo, ctx),
1208 'branches': webutil.nodebranchdict(web.repo, ctx),
1208 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1209 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1209 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1210 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1210
1211
1211 row += 1
1212 row += 1
1212
1213
1213 return data
1214 return data
1214
1215
1215 cols = getcolumns(tree)
1216 cols = getcolumns(tree)
1216 rows = len(tree)
1217 rows = len(tree)
1217 canvasheight = (rows + 1) * bg_height - 27
1218 canvasheight = (rows + 1) * bg_height - 27
1218
1219
1219 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1220 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1220 uprev=uprev,
1221 uprev=uprev,
1221 lessvars=lessvars, morevars=morevars, downrev=downrev,
1222 lessvars=lessvars, morevars=morevars, downrev=downrev,
1222 cols=cols, rows=rows,
1223 cols=cols, rows=rows,
1223 canvaswidth=(cols + 1) * bg_height,
1224 canvaswidth=(cols + 1) * bg_height,
1224 truecanvasheight=rows * bg_height,
1225 truecanvasheight=rows * bg_height,
1225 canvasheight=canvasheight, bg_height=bg_height,
1226 canvasheight=canvasheight, bg_height=bg_height,
1226 # {jsdata} will be passed to |json, so it must be in utf-8
1227 # {jsdata} will be passed to |json, so it must be in utf-8
1227 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1228 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1228 nodes=lambda **x: graphdata(False, str),
1229 nodes=lambda **x: graphdata(False, str),
1229 node=ctx.hex(), changenav=changenav)
1230 node=ctx.hex(), changenav=changenav)
1230
1231
1231 def _getdoc(e):
1232 def _getdoc(e):
1232 doc = e[0].__doc__
1233 doc = e[0].__doc__
1233 if doc:
1234 if doc:
1234 doc = _(doc).partition('\n')[0]
1235 doc = _(doc).partition('\n')[0]
1235 else:
1236 else:
1236 doc = _('(no help text available)')
1237 doc = _('(no help text available)')
1237 return doc
1238 return doc
1238
1239
1239 @webcommand('help')
1240 @webcommand('help')
1240 def help(web, req, tmpl):
1241 def help(web, req, tmpl):
1241 """
1242 """
1242 /help[/{topic}]
1243 /help[/{topic}]
1243 ---------------
1244 ---------------
1244
1245
1245 Render help documentation.
1246 Render help documentation.
1246
1247
1247 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1248 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1248 is defined, that help topic will be rendered. If not, an index of
1249 is defined, that help topic will be rendered. If not, an index of
1249 available help topics will be rendered.
1250 available help topics will be rendered.
1250
1251
1251 The ``help`` template will be rendered when requesting help for a topic.
1252 The ``help`` template will be rendered when requesting help for a topic.
1252 ``helptopics`` will be rendered for the index of help topics.
1253 ``helptopics`` will be rendered for the index of help topics.
1253 """
1254 """
1254 from .. import commands, help as helpmod # avoid cycle
1255 from .. import commands, help as helpmod # avoid cycle
1255
1256
1256 topicname = req.form.get('node', [None])[0]
1257 topicname = req.form.get('node', [None])[0]
1257 if not topicname:
1258 if not topicname:
1258 def topics(**map):
1259 def topics(**map):
1259 for entries, summary, _doc in helpmod.helptable:
1260 for entries, summary, _doc in helpmod.helptable:
1260 yield {'topic': entries[0], 'summary': summary}
1261 yield {'topic': entries[0], 'summary': summary}
1261
1262
1262 early, other = [], []
1263 early, other = [], []
1263 primary = lambda s: s.partition('|')[0]
1264 primary = lambda s: s.partition('|')[0]
1264 for c, e in commands.table.iteritems():
1265 for c, e in commands.table.iteritems():
1265 doc = _getdoc(e)
1266 doc = _getdoc(e)
1266 if 'DEPRECATED' in doc or c.startswith('debug'):
1267 if 'DEPRECATED' in doc or c.startswith('debug'):
1267 continue
1268 continue
1268 cmd = primary(c)
1269 cmd = primary(c)
1269 if cmd.startswith('^'):
1270 if cmd.startswith('^'):
1270 early.append((cmd[1:], doc))
1271 early.append((cmd[1:], doc))
1271 else:
1272 else:
1272 other.append((cmd, doc))
1273 other.append((cmd, doc))
1273
1274
1274 early.sort()
1275 early.sort()
1275 other.sort()
1276 other.sort()
1276
1277
1277 def earlycommands(**map):
1278 def earlycommands(**map):
1278 for c, doc in early:
1279 for c, doc in early:
1279 yield {'topic': c, 'summary': doc}
1280 yield {'topic': c, 'summary': doc}
1280
1281
1281 def othercommands(**map):
1282 def othercommands(**map):
1282 for c, doc in other:
1283 for c, doc in other:
1283 yield {'topic': c, 'summary': doc}
1284 yield {'topic': c, 'summary': doc}
1284
1285
1285 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1286 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1286 othercommands=othercommands, title='Index')
1287 othercommands=othercommands, title='Index')
1287
1288
1288 # Render an index of sub-topics.
1289 # Render an index of sub-topics.
1289 if topicname in helpmod.subtopics:
1290 if topicname in helpmod.subtopics:
1290 topics = []
1291 topics = []
1291 for entries, summary, _doc in helpmod.subtopics[topicname]:
1292 for entries, summary, _doc in helpmod.subtopics[topicname]:
1292 topics.append({
1293 topics.append({
1293 'topic': '%s.%s' % (topicname, entries[0]),
1294 'topic': '%s.%s' % (topicname, entries[0]),
1294 'basename': entries[0],
1295 'basename': entries[0],
1295 'summary': summary,
1296 'summary': summary,
1296 })
1297 })
1297
1298
1298 return tmpl('helptopics', topics=topics, title=topicname,
1299 return tmpl('helptopics', topics=topics, title=topicname,
1299 subindex=True)
1300 subindex=True)
1300
1301
1301 u = webutil.wsgiui.load()
1302 u = webutil.wsgiui.load()
1302 u.verbose = True
1303 u.verbose = True
1303
1304
1304 # Render a page from a sub-topic.
1305 # Render a page from a sub-topic.
1305 if '.' in topicname:
1306 if '.' in topicname:
1306 # TODO implement support for rendering sections, like
1307 # TODO implement support for rendering sections, like
1307 # `hg help` works.
1308 # `hg help` works.
1308 topic, subtopic = topicname.split('.', 1)
1309 topic, subtopic = topicname.split('.', 1)
1309 if topic not in helpmod.subtopics:
1310 if topic not in helpmod.subtopics:
1310 raise ErrorResponse(HTTP_NOT_FOUND)
1311 raise ErrorResponse(HTTP_NOT_FOUND)
1311 else:
1312 else:
1312 topic = topicname
1313 topic = topicname
1313 subtopic = None
1314 subtopic = None
1314
1315
1315 try:
1316 try:
1316 doc = helpmod.help_(u, topic, subtopic=subtopic)
1317 doc = helpmod.help_(u, topic, subtopic=subtopic)
1317 except error.UnknownCommand:
1318 except error.UnknownCommand:
1318 raise ErrorResponse(HTTP_NOT_FOUND)
1319 raise ErrorResponse(HTTP_NOT_FOUND)
1319 return tmpl('help', topic=topicname, doc=doc)
1320 return tmpl('help', topic=topicname, doc=doc)
1320
1321
1321 # tell hggettext to extract docstrings from these functions:
1322 # tell hggettext to extract docstrings from these functions:
1322 i18nfunctions = commands.values()
1323 i18nfunctions = commands.values()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file copied from mercurial/revset.py to mercurial/revsetlang.py
NO CONTENT: file copied from mercurial/revset.py to mercurial/revsetlang.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now