##// END OF EJS Templates
patch: break import cycle with cmdutil...
Martin Geisler -
r12266:00658492 default
parent child Browse files
Show More
@@ -1,3119 +1,3119 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, patch, util
48 from mercurial import commands, cmdutil, hg, patch, util
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 import os, sys, re, errno, shutil
50 import os, sys, re, errno, shutil
51
51
52 commands.norepo += " qclone"
52 commands.norepo += " qclone"
53
53
54 # Patch names looks like unix-file names.
54 # Patch names looks like unix-file names.
55 # They must be joinable with queue directory and result in the patch path.
55 # They must be joinable with queue directory and result in the patch path.
56 normname = util.normpath
56 normname = util.normpath
57
57
58 class statusentry(object):
58 class statusentry(object):
59 def __init__(self, node, name):
59 def __init__(self, node, name):
60 self.node, self.name = node, name
60 self.node, self.name = node, name
61 def __repr__(self):
61 def __repr__(self):
62 return hex(self.node) + ':' + self.name
62 return hex(self.node) + ':' + self.name
63
63
64 class patchheader(object):
64 class patchheader(object):
65 def __init__(self, pf, plainmode=False):
65 def __init__(self, pf, plainmode=False):
66 def eatdiff(lines):
66 def eatdiff(lines):
67 while lines:
67 while lines:
68 l = lines[-1]
68 l = lines[-1]
69 if (l.startswith("diff -") or
69 if (l.startswith("diff -") or
70 l.startswith("Index:") or
70 l.startswith("Index:") or
71 l.startswith("===========")):
71 l.startswith("===========")):
72 del lines[-1]
72 del lines[-1]
73 else:
73 else:
74 break
74 break
75 def eatempty(lines):
75 def eatempty(lines):
76 while lines:
76 while lines:
77 if not lines[-1].strip():
77 if not lines[-1].strip():
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81
81
82 message = []
82 message = []
83 comments = []
83 comments = []
84 user = None
84 user = None
85 date = None
85 date = None
86 parent = None
86 parent = None
87 format = None
87 format = None
88 subject = None
88 subject = None
89 diffstart = 0
89 diffstart = 0
90
90
91 for line in file(pf):
91 for line in file(pf):
92 line = line.rstrip()
92 line = line.rstrip()
93 if (line.startswith('diff --git')
93 if (line.startswith('diff --git')
94 or (diffstart and line.startswith('+++ '))):
94 or (diffstart and line.startswith('+++ '))):
95 diffstart = 2
95 diffstart = 2
96 break
96 break
97 diffstart = 0 # reset
97 diffstart = 0 # reset
98 if line.startswith("--- "):
98 if line.startswith("--- "):
99 diffstart = 1
99 diffstart = 1
100 continue
100 continue
101 elif format == "hgpatch":
101 elif format == "hgpatch":
102 # parse values when importing the result of an hg export
102 # parse values when importing the result of an hg export
103 if line.startswith("# User "):
103 if line.startswith("# User "):
104 user = line[7:]
104 user = line[7:]
105 elif line.startswith("# Date "):
105 elif line.startswith("# Date "):
106 date = line[7:]
106 date = line[7:]
107 elif line.startswith("# Parent "):
107 elif line.startswith("# Parent "):
108 parent = line[9:]
108 parent = line[9:]
109 elif not line.startswith("# ") and line:
109 elif not line.startswith("# ") and line:
110 message.append(line)
110 message.append(line)
111 format = None
111 format = None
112 elif line == '# HG changeset patch':
112 elif line == '# HG changeset patch':
113 message = []
113 message = []
114 format = "hgpatch"
114 format = "hgpatch"
115 elif (format != "tagdone" and (line.startswith("Subject: ") or
115 elif (format != "tagdone" and (line.startswith("Subject: ") or
116 line.startswith("subject: "))):
116 line.startswith("subject: "))):
117 subject = line[9:]
117 subject = line[9:]
118 format = "tag"
118 format = "tag"
119 elif (format != "tagdone" and (line.startswith("From: ") or
119 elif (format != "tagdone" and (line.startswith("From: ") or
120 line.startswith("from: "))):
120 line.startswith("from: "))):
121 user = line[6:]
121 user = line[6:]
122 format = "tag"
122 format = "tag"
123 elif (format != "tagdone" and (line.startswith("Date: ") or
123 elif (format != "tagdone" and (line.startswith("Date: ") or
124 line.startswith("date: "))):
124 line.startswith("date: "))):
125 date = line[6:]
125 date = line[6:]
126 format = "tag"
126 format = "tag"
127 elif format == "tag" and line == "":
127 elif format == "tag" and line == "":
128 # when looking for tags (subject: from: etc) they
128 # when looking for tags (subject: from: etc) they
129 # end once you find a blank line in the source
129 # end once you find a blank line in the source
130 format = "tagdone"
130 format = "tagdone"
131 elif message or line:
131 elif message or line:
132 message.append(line)
132 message.append(line)
133 comments.append(line)
133 comments.append(line)
134
134
135 eatdiff(message)
135 eatdiff(message)
136 eatdiff(comments)
136 eatdiff(comments)
137 eatempty(message)
137 eatempty(message)
138 eatempty(comments)
138 eatempty(comments)
139
139
140 # make sure message isn't empty
140 # make sure message isn't empty
141 if format and format.startswith("tag") and subject:
141 if format and format.startswith("tag") and subject:
142 message.insert(0, "")
142 message.insert(0, "")
143 message.insert(0, subject)
143 message.insert(0, subject)
144
144
145 self.message = message
145 self.message = message
146 self.comments = comments
146 self.comments = comments
147 self.user = user
147 self.user = user
148 self.date = date
148 self.date = date
149 self.parent = parent
149 self.parent = parent
150 self.haspatch = diffstart > 1
150 self.haspatch = diffstart > 1
151 self.plainmode = plainmode
151 self.plainmode = plainmode
152
152
153 def setuser(self, user):
153 def setuser(self, user):
154 if not self.updateheader(['From: ', '# User '], user):
154 if not self.updateheader(['From: ', '# User '], user):
155 try:
155 try:
156 patchheaderat = self.comments.index('# HG changeset patch')
156 patchheaderat = self.comments.index('# HG changeset patch')
157 self.comments.insert(patchheaderat + 1, '# User ' + user)
157 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 except ValueError:
158 except ValueError:
159 if self.plainmode or self._hasheader(['Date: ']):
159 if self.plainmode or self._hasheader(['Date: ']):
160 self.comments = ['From: ' + user] + self.comments
160 self.comments = ['From: ' + user] + self.comments
161 else:
161 else:
162 tmp = ['# HG changeset patch', '# User ' + user, '']
162 tmp = ['# HG changeset patch', '# User ' + user, '']
163 self.comments = tmp + self.comments
163 self.comments = tmp + self.comments
164 self.user = user
164 self.user = user
165
165
166 def setdate(self, date):
166 def setdate(self, date):
167 if not self.updateheader(['Date: ', '# Date '], date):
167 if not self.updateheader(['Date: ', '# Date '], date):
168 try:
168 try:
169 patchheaderat = self.comments.index('# HG changeset patch')
169 patchheaderat = self.comments.index('# HG changeset patch')
170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 except ValueError:
171 except ValueError:
172 if self.plainmode or self._hasheader(['From: ']):
172 if self.plainmode or self._hasheader(['From: ']):
173 self.comments = ['Date: ' + date] + self.comments
173 self.comments = ['Date: ' + date] + self.comments
174 else:
174 else:
175 tmp = ['# HG changeset patch', '# Date ' + date, '']
175 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 self.comments = tmp + self.comments
176 self.comments = tmp + self.comments
177 self.date = date
177 self.date = date
178
178
179 def setparent(self, parent):
179 def setparent(self, parent):
180 if not self.updateheader(['# Parent '], parent):
180 if not self.updateheader(['# Parent '], parent):
181 try:
181 try:
182 patchheaderat = self.comments.index('# HG changeset patch')
182 patchheaderat = self.comments.index('# HG changeset patch')
183 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
183 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
184 except ValueError:
184 except ValueError:
185 pass
185 pass
186 self.parent = parent
186 self.parent = parent
187
187
188 def setmessage(self, message):
188 def setmessage(self, message):
189 if self.comments:
189 if self.comments:
190 self._delmsg()
190 self._delmsg()
191 self.message = [message]
191 self.message = [message]
192 self.comments += self.message
192 self.comments += self.message
193
193
194 def updateheader(self, prefixes, new):
194 def updateheader(self, prefixes, new):
195 '''Update all references to a field in the patch header.
195 '''Update all references to a field in the patch header.
196 Return whether the field is present.'''
196 Return whether the field is present.'''
197 res = False
197 res = False
198 for prefix in prefixes:
198 for prefix in prefixes:
199 for i in xrange(len(self.comments)):
199 for i in xrange(len(self.comments)):
200 if self.comments[i].startswith(prefix):
200 if self.comments[i].startswith(prefix):
201 self.comments[i] = prefix + new
201 self.comments[i] = prefix + new
202 res = True
202 res = True
203 break
203 break
204 return res
204 return res
205
205
206 def _hasheader(self, prefixes):
206 def _hasheader(self, prefixes):
207 '''Check if a header starts with any of the given prefixes.'''
207 '''Check if a header starts with any of the given prefixes.'''
208 for prefix in prefixes:
208 for prefix in prefixes:
209 for comment in self.comments:
209 for comment in self.comments:
210 if comment.startswith(prefix):
210 if comment.startswith(prefix):
211 return True
211 return True
212 return False
212 return False
213
213
214 def __str__(self):
214 def __str__(self):
215 if not self.comments:
215 if not self.comments:
216 return ''
216 return ''
217 return '\n'.join(self.comments) + '\n\n'
217 return '\n'.join(self.comments) + '\n\n'
218
218
219 def _delmsg(self):
219 def _delmsg(self):
220 '''Remove existing message, keeping the rest of the comments fields.
220 '''Remove existing message, keeping the rest of the comments fields.
221 If comments contains 'subject: ', message will prepend
221 If comments contains 'subject: ', message will prepend
222 the field and a blank line.'''
222 the field and a blank line.'''
223 if self.message:
223 if self.message:
224 subj = 'subject: ' + self.message[0].lower()
224 subj = 'subject: ' + self.message[0].lower()
225 for i in xrange(len(self.comments)):
225 for i in xrange(len(self.comments)):
226 if subj == self.comments[i].lower():
226 if subj == self.comments[i].lower():
227 del self.comments[i]
227 del self.comments[i]
228 self.message = self.message[2:]
228 self.message = self.message[2:]
229 break
229 break
230 ci = 0
230 ci = 0
231 for mi in self.message:
231 for mi in self.message:
232 while mi != self.comments[ci]:
232 while mi != self.comments[ci]:
233 ci += 1
233 ci += 1
234 del self.comments[ci]
234 del self.comments[ci]
235
235
236 class queue(object):
236 class queue(object):
237 def __init__(self, ui, path, patchdir=None):
237 def __init__(self, ui, path, patchdir=None):
238 self.basepath = path
238 self.basepath = path
239 try:
239 try:
240 fh = open(os.path.join(path, 'patches.queue'))
240 fh = open(os.path.join(path, 'patches.queue'))
241 cur = fh.read().rstrip()
241 cur = fh.read().rstrip()
242 if not cur:
242 if not cur:
243 curpath = os.path.join(path, 'patches')
243 curpath = os.path.join(path, 'patches')
244 else:
244 else:
245 curpath = os.path.join(path, 'patches-' + cur)
245 curpath = os.path.join(path, 'patches-' + cur)
246 except IOError:
246 except IOError:
247 curpath = os.path.join(path, 'patches')
247 curpath = os.path.join(path, 'patches')
248 self.path = patchdir or curpath
248 self.path = patchdir or curpath
249 self.opener = util.opener(self.path)
249 self.opener = util.opener(self.path)
250 self.ui = ui
250 self.ui = ui
251 self.applied_dirty = 0
251 self.applied_dirty = 0
252 self.series_dirty = 0
252 self.series_dirty = 0
253 self.added = []
253 self.added = []
254 self.series_path = "series"
254 self.series_path = "series"
255 self.status_path = "status"
255 self.status_path = "status"
256 self.guards_path = "guards"
256 self.guards_path = "guards"
257 self.active_guards = None
257 self.active_guards = None
258 self.guards_dirty = False
258 self.guards_dirty = False
259 # Handle mq.git as a bool with extended values
259 # Handle mq.git as a bool with extended values
260 try:
260 try:
261 gitmode = ui.configbool('mq', 'git', None)
261 gitmode = ui.configbool('mq', 'git', None)
262 if gitmode is None:
262 if gitmode is None:
263 raise error.ConfigError()
263 raise error.ConfigError()
264 self.gitmode = gitmode and 'yes' or 'no'
264 self.gitmode = gitmode and 'yes' or 'no'
265 except error.ConfigError:
265 except error.ConfigError:
266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
267 self.plainmode = ui.configbool('mq', 'plain', False)
267 self.plainmode = ui.configbool('mq', 'plain', False)
268
268
269 @util.propertycache
269 @util.propertycache
270 def applied(self):
270 def applied(self):
271 if os.path.exists(self.join(self.status_path)):
271 if os.path.exists(self.join(self.status_path)):
272 def parse(l):
272 def parse(l):
273 n, name = l.split(':', 1)
273 n, name = l.split(':', 1)
274 return statusentry(bin(n), name)
274 return statusentry(bin(n), name)
275 lines = self.opener(self.status_path).read().splitlines()
275 lines = self.opener(self.status_path).read().splitlines()
276 return [parse(l) for l in lines]
276 return [parse(l) for l in lines]
277 return []
277 return []
278
278
279 @util.propertycache
279 @util.propertycache
280 def full_series(self):
280 def full_series(self):
281 if os.path.exists(self.join(self.series_path)):
281 if os.path.exists(self.join(self.series_path)):
282 return self.opener(self.series_path).read().splitlines()
282 return self.opener(self.series_path).read().splitlines()
283 return []
283 return []
284
284
285 @util.propertycache
285 @util.propertycache
286 def series(self):
286 def series(self):
287 self.parse_series()
287 self.parse_series()
288 return self.series
288 return self.series
289
289
290 @util.propertycache
290 @util.propertycache
291 def series_guards(self):
291 def series_guards(self):
292 self.parse_series()
292 self.parse_series()
293 return self.series_guards
293 return self.series_guards
294
294
295 def invalidate(self):
295 def invalidate(self):
296 for a in 'applied full_series series series_guards'.split():
296 for a in 'applied full_series series series_guards'.split():
297 if a in self.__dict__:
297 if a in self.__dict__:
298 delattr(self, a)
298 delattr(self, a)
299 self.applied_dirty = 0
299 self.applied_dirty = 0
300 self.series_dirty = 0
300 self.series_dirty = 0
301 self.guards_dirty = False
301 self.guards_dirty = False
302 self.active_guards = None
302 self.active_guards = None
303
303
304 def diffopts(self, opts={}, patchfn=None):
304 def diffopts(self, opts={}, patchfn=None):
305 diffopts = patch.diffopts(self.ui, opts)
305 diffopts = patch.diffopts(self.ui, opts)
306 if self.gitmode == 'auto':
306 if self.gitmode == 'auto':
307 diffopts.upgrade = True
307 diffopts.upgrade = True
308 elif self.gitmode == 'keep':
308 elif self.gitmode == 'keep':
309 pass
309 pass
310 elif self.gitmode in ('yes', 'no'):
310 elif self.gitmode in ('yes', 'no'):
311 diffopts.git = self.gitmode == 'yes'
311 diffopts.git = self.gitmode == 'yes'
312 else:
312 else:
313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
314 ' got %s') % self.gitmode)
314 ' got %s') % self.gitmode)
315 if patchfn:
315 if patchfn:
316 diffopts = self.patchopts(diffopts, patchfn)
316 diffopts = self.patchopts(diffopts, patchfn)
317 return diffopts
317 return diffopts
318
318
319 def patchopts(self, diffopts, *patches):
319 def patchopts(self, diffopts, *patches):
320 """Return a copy of input diff options with git set to true if
320 """Return a copy of input diff options with git set to true if
321 referenced patch is a git patch and should be preserved as such.
321 referenced patch is a git patch and should be preserved as such.
322 """
322 """
323 diffopts = diffopts.copy()
323 diffopts = diffopts.copy()
324 if not diffopts.git and self.gitmode == 'keep':
324 if not diffopts.git and self.gitmode == 'keep':
325 for patchfn in patches:
325 for patchfn in patches:
326 patchf = self.opener(patchfn, 'r')
326 patchf = self.opener(patchfn, 'r')
327 # if the patch was a git patch, refresh it as a git patch
327 # if the patch was a git patch, refresh it as a git patch
328 for line in patchf:
328 for line in patchf:
329 if line.startswith('diff --git'):
329 if line.startswith('diff --git'):
330 diffopts.git = True
330 diffopts.git = True
331 break
331 break
332 patchf.close()
332 patchf.close()
333 return diffopts
333 return diffopts
334
334
335 def join(self, *p):
335 def join(self, *p):
336 return os.path.join(self.path, *p)
336 return os.path.join(self.path, *p)
337
337
338 def find_series(self, patch):
338 def find_series(self, patch):
339 def matchpatch(l):
339 def matchpatch(l):
340 l = l.split('#', 1)[0]
340 l = l.split('#', 1)[0]
341 return l.strip() == patch
341 return l.strip() == patch
342 for index, l in enumerate(self.full_series):
342 for index, l in enumerate(self.full_series):
343 if matchpatch(l):
343 if matchpatch(l):
344 return index
344 return index
345 return None
345 return None
346
346
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348
348
349 def parse_series(self):
349 def parse_series(self):
350 self.series = []
350 self.series = []
351 self.series_guards = []
351 self.series_guards = []
352 for l in self.full_series:
352 for l in self.full_series:
353 h = l.find('#')
353 h = l.find('#')
354 if h == -1:
354 if h == -1:
355 patch = l
355 patch = l
356 comment = ''
356 comment = ''
357 elif h == 0:
357 elif h == 0:
358 continue
358 continue
359 else:
359 else:
360 patch = l[:h]
360 patch = l[:h]
361 comment = l[h:]
361 comment = l[h:]
362 patch = patch.strip()
362 patch = patch.strip()
363 if patch:
363 if patch:
364 if patch in self.series:
364 if patch in self.series:
365 raise util.Abort(_('%s appears more than once in %s') %
365 raise util.Abort(_('%s appears more than once in %s') %
366 (patch, self.join(self.series_path)))
366 (patch, self.join(self.series_path)))
367 self.series.append(patch)
367 self.series.append(patch)
368 self.series_guards.append(self.guard_re.findall(comment))
368 self.series_guards.append(self.guard_re.findall(comment))
369
369
370 def check_guard(self, guard):
370 def check_guard(self, guard):
371 if not guard:
371 if not guard:
372 return _('guard cannot be an empty string')
372 return _('guard cannot be an empty string')
373 bad_chars = '# \t\r\n\f'
373 bad_chars = '# \t\r\n\f'
374 first = guard[0]
374 first = guard[0]
375 if first in '-+':
375 if first in '-+':
376 return (_('guard %r starts with invalid character: %r') %
376 return (_('guard %r starts with invalid character: %r') %
377 (guard, first))
377 (guard, first))
378 for c in bad_chars:
378 for c in bad_chars:
379 if c in guard:
379 if c in guard:
380 return _('invalid character in guard %r: %r') % (guard, c)
380 return _('invalid character in guard %r: %r') % (guard, c)
381
381
382 def set_active(self, guards):
382 def set_active(self, guards):
383 for guard in guards:
383 for guard in guards:
384 bad = self.check_guard(guard)
384 bad = self.check_guard(guard)
385 if bad:
385 if bad:
386 raise util.Abort(bad)
386 raise util.Abort(bad)
387 guards = sorted(set(guards))
387 guards = sorted(set(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 self.active_guards = guards
389 self.active_guards = guards
390 self.guards_dirty = True
390 self.guards_dirty = True
391
391
392 def active(self):
392 def active(self):
393 if self.active_guards is None:
393 if self.active_guards is None:
394 self.active_guards = []
394 self.active_guards = []
395 try:
395 try:
396 guards = self.opener(self.guards_path).read().split()
396 guards = self.opener(self.guards_path).read().split()
397 except IOError, err:
397 except IOError, err:
398 if err.errno != errno.ENOENT:
398 if err.errno != errno.ENOENT:
399 raise
399 raise
400 guards = []
400 guards = []
401 for i, guard in enumerate(guards):
401 for i, guard in enumerate(guards):
402 bad = self.check_guard(guard)
402 bad = self.check_guard(guard)
403 if bad:
403 if bad:
404 self.ui.warn('%s:%d: %s\n' %
404 self.ui.warn('%s:%d: %s\n' %
405 (self.join(self.guards_path), i + 1, bad))
405 (self.join(self.guards_path), i + 1, bad))
406 else:
406 else:
407 self.active_guards.append(guard)
407 self.active_guards.append(guard)
408 return self.active_guards
408 return self.active_guards
409
409
410 def set_guards(self, idx, guards):
410 def set_guards(self, idx, guards):
411 for g in guards:
411 for g in guards:
412 if len(g) < 2:
412 if len(g) < 2:
413 raise util.Abort(_('guard %r too short') % g)
413 raise util.Abort(_('guard %r too short') % g)
414 if g[0] not in '-+':
414 if g[0] not in '-+':
415 raise util.Abort(_('guard %r starts with invalid char') % g)
415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 bad = self.check_guard(g[1:])
416 bad = self.check_guard(g[1:])
417 if bad:
417 if bad:
418 raise util.Abort(bad)
418 raise util.Abort(bad)
419 drop = self.guard_re.sub('', self.full_series[idx])
419 drop = self.guard_re.sub('', self.full_series[idx])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 self.parse_series()
421 self.parse_series()
422 self.series_dirty = True
422 self.series_dirty = True
423
423
424 def pushable(self, idx):
424 def pushable(self, idx):
425 if isinstance(idx, str):
425 if isinstance(idx, str):
426 idx = self.series.index(idx)
426 idx = self.series.index(idx)
427 patchguards = self.series_guards[idx]
427 patchguards = self.series_guards[idx]
428 if not patchguards:
428 if not patchguards:
429 return True, None
429 return True, None
430 guards = self.active()
430 guards = self.active()
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 if exactneg:
432 if exactneg:
433 return False, exactneg[0]
433 return False, exactneg[0]
434 pos = [g for g in patchguards if g[0] == '+']
434 pos = [g for g in patchguards if g[0] == '+']
435 exactpos = [g for g in pos if g[1:] in guards]
435 exactpos = [g for g in pos if g[1:] in guards]
436 if pos:
436 if pos:
437 if exactpos:
437 if exactpos:
438 return True, exactpos[0]
438 return True, exactpos[0]
439 return False, pos
439 return False, pos
440 return True, ''
440 return True, ''
441
441
442 def explain_pushable(self, idx, all_patches=False):
442 def explain_pushable(self, idx, all_patches=False):
443 write = all_patches and self.ui.write or self.ui.warn
443 write = all_patches and self.ui.write or self.ui.warn
444 if all_patches or self.ui.verbose:
444 if all_patches or self.ui.verbose:
445 if isinstance(idx, str):
445 if isinstance(idx, str):
446 idx = self.series.index(idx)
446 idx = self.series.index(idx)
447 pushable, why = self.pushable(idx)
447 pushable, why = self.pushable(idx)
448 if all_patches and pushable:
448 if all_patches and pushable:
449 if why is None:
449 if why is None:
450 write(_('allowing %s - no guards in effect\n') %
450 write(_('allowing %s - no guards in effect\n') %
451 self.series[idx])
451 self.series[idx])
452 else:
452 else:
453 if not why:
453 if not why:
454 write(_('allowing %s - no matching negative guards\n') %
454 write(_('allowing %s - no matching negative guards\n') %
455 self.series[idx])
455 self.series[idx])
456 else:
456 else:
457 write(_('allowing %s - guarded by %r\n') %
457 write(_('allowing %s - guarded by %r\n') %
458 (self.series[idx], why))
458 (self.series[idx], why))
459 if not pushable:
459 if not pushable:
460 if why:
460 if why:
461 write(_('skipping %s - guarded by %r\n') %
461 write(_('skipping %s - guarded by %r\n') %
462 (self.series[idx], why))
462 (self.series[idx], why))
463 else:
463 else:
464 write(_('skipping %s - no matching guards\n') %
464 write(_('skipping %s - no matching guards\n') %
465 self.series[idx])
465 self.series[idx])
466
466
467 def save_dirty(self):
467 def save_dirty(self):
468 def write_list(items, path):
468 def write_list(items, path):
469 fp = self.opener(path, 'w')
469 fp = self.opener(path, 'w')
470 for i in items:
470 for i in items:
471 fp.write("%s\n" % i)
471 fp.write("%s\n" % i)
472 fp.close()
472 fp.close()
473 if self.applied_dirty:
473 if self.applied_dirty:
474 write_list(map(str, self.applied), self.status_path)
474 write_list(map(str, self.applied), self.status_path)
475 if self.series_dirty:
475 if self.series_dirty:
476 write_list(self.full_series, self.series_path)
476 write_list(self.full_series, self.series_path)
477 if self.guards_dirty:
477 if self.guards_dirty:
478 write_list(self.active_guards, self.guards_path)
478 write_list(self.active_guards, self.guards_path)
479 if self.added:
479 if self.added:
480 qrepo = self.qrepo()
480 qrepo = self.qrepo()
481 if qrepo:
481 if qrepo:
482 qrepo[None].add(self.added)
482 qrepo[None].add(self.added)
483 self.added = []
483 self.added = []
484
484
485 def removeundo(self, repo):
485 def removeundo(self, repo):
486 undo = repo.sjoin('undo')
486 undo = repo.sjoin('undo')
487 if not os.path.exists(undo):
487 if not os.path.exists(undo):
488 return
488 return
489 try:
489 try:
490 os.unlink(undo)
490 os.unlink(undo)
491 except OSError, inst:
491 except OSError, inst:
492 self.ui.warn(_('error removing undo: %s\n') % str(inst))
492 self.ui.warn(_('error removing undo: %s\n') % str(inst))
493
493
494 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
494 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
495 fp=None, changes=None, opts={}):
495 fp=None, changes=None, opts={}):
496 stat = opts.get('stat')
496 stat = opts.get('stat')
497 m = cmdutil.match(repo, files, opts)
497 m = cmdutil.match(repo, files, opts)
498 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
498 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
499 changes, stat, fp)
499 changes, stat, fp)
500
500
501 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
501 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
502 # first try just applying the patch
502 # first try just applying the patch
503 (err, n) = self.apply(repo, [patch], update_status=False,
503 (err, n) = self.apply(repo, [patch], update_status=False,
504 strict=True, merge=rev)
504 strict=True, merge=rev)
505
505
506 if err == 0:
506 if err == 0:
507 return (err, n)
507 return (err, n)
508
508
509 if n is None:
509 if n is None:
510 raise util.Abort(_("apply failed for patch %s") % patch)
510 raise util.Abort(_("apply failed for patch %s") % patch)
511
511
512 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
512 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
513
513
514 # apply failed, strip away that rev and merge.
514 # apply failed, strip away that rev and merge.
515 hg.clean(repo, head)
515 hg.clean(repo, head)
516 self.strip(repo, [n], update=False, backup='strip')
516 self.strip(repo, [n], update=False, backup='strip')
517
517
518 ctx = repo[rev]
518 ctx = repo[rev]
519 ret = hg.merge(repo, rev)
519 ret = hg.merge(repo, rev)
520 if ret:
520 if ret:
521 raise util.Abort(_("update returned %d") % ret)
521 raise util.Abort(_("update returned %d") % ret)
522 n = repo.commit(ctx.description(), ctx.user(), force=True)
522 n = repo.commit(ctx.description(), ctx.user(), force=True)
523 if n is None:
523 if n is None:
524 raise util.Abort(_("repo commit failed"))
524 raise util.Abort(_("repo commit failed"))
525 try:
525 try:
526 ph = patchheader(mergeq.join(patch), self.plainmode)
526 ph = patchheader(mergeq.join(patch), self.plainmode)
527 except:
527 except:
528 raise util.Abort(_("unable to read %s") % patch)
528 raise util.Abort(_("unable to read %s") % patch)
529
529
530 diffopts = self.patchopts(diffopts, patch)
530 diffopts = self.patchopts(diffopts, patch)
531 patchf = self.opener(patch, "w")
531 patchf = self.opener(patch, "w")
532 comments = str(ph)
532 comments = str(ph)
533 if comments:
533 if comments:
534 patchf.write(comments)
534 patchf.write(comments)
535 self.printdiff(repo, diffopts, head, n, fp=patchf)
535 self.printdiff(repo, diffopts, head, n, fp=patchf)
536 patchf.close()
536 patchf.close()
537 self.removeundo(repo)
537 self.removeundo(repo)
538 return (0, n)
538 return (0, n)
539
539
540 def qparents(self, repo, rev=None):
540 def qparents(self, repo, rev=None):
541 if rev is None:
541 if rev is None:
542 (p1, p2) = repo.dirstate.parents()
542 (p1, p2) = repo.dirstate.parents()
543 if p2 == nullid:
543 if p2 == nullid:
544 return p1
544 return p1
545 if not self.applied:
545 if not self.applied:
546 return None
546 return None
547 return self.applied[-1].node
547 return self.applied[-1].node
548 p1, p2 = repo.changelog.parents(rev)
548 p1, p2 = repo.changelog.parents(rev)
549 if p2 != nullid and p2 in [x.node for x in self.applied]:
549 if p2 != nullid and p2 in [x.node for x in self.applied]:
550 return p2
550 return p2
551 return p1
551 return p1
552
552
553 def mergepatch(self, repo, mergeq, series, diffopts):
553 def mergepatch(self, repo, mergeq, series, diffopts):
554 if not self.applied:
554 if not self.applied:
555 # each of the patches merged in will have two parents. This
555 # each of the patches merged in will have two parents. This
556 # can confuse the qrefresh, qdiff, and strip code because it
556 # can confuse the qrefresh, qdiff, and strip code because it
557 # needs to know which parent is actually in the patch queue.
557 # needs to know which parent is actually in the patch queue.
558 # so, we insert a merge marker with only one parent. This way
558 # so, we insert a merge marker with only one parent. This way
559 # the first patch in the queue is never a merge patch
559 # the first patch in the queue is never a merge patch
560 #
560 #
561 pname = ".hg.patches.merge.marker"
561 pname = ".hg.patches.merge.marker"
562 n = repo.commit('[mq]: merge marker', force=True)
562 n = repo.commit('[mq]: merge marker', force=True)
563 self.removeundo(repo)
563 self.removeundo(repo)
564 self.applied.append(statusentry(n, pname))
564 self.applied.append(statusentry(n, pname))
565 self.applied_dirty = 1
565 self.applied_dirty = 1
566
566
567 head = self.qparents(repo)
567 head = self.qparents(repo)
568
568
569 for patch in series:
569 for patch in series:
570 patch = mergeq.lookup(patch, strict=True)
570 patch = mergeq.lookup(patch, strict=True)
571 if not patch:
571 if not patch:
572 self.ui.warn(_("patch %s does not exist\n") % patch)
572 self.ui.warn(_("patch %s does not exist\n") % patch)
573 return (1, None)
573 return (1, None)
574 pushable, reason = self.pushable(patch)
574 pushable, reason = self.pushable(patch)
575 if not pushable:
575 if not pushable:
576 self.explain_pushable(patch, all_patches=True)
576 self.explain_pushable(patch, all_patches=True)
577 continue
577 continue
578 info = mergeq.isapplied(patch)
578 info = mergeq.isapplied(patch)
579 if not info:
579 if not info:
580 self.ui.warn(_("patch %s is not applied\n") % patch)
580 self.ui.warn(_("patch %s is not applied\n") % patch)
581 return (1, None)
581 return (1, None)
582 rev = info[1]
582 rev = info[1]
583 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
583 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
584 if head:
584 if head:
585 self.applied.append(statusentry(head, patch))
585 self.applied.append(statusentry(head, patch))
586 self.applied_dirty = 1
586 self.applied_dirty = 1
587 if err:
587 if err:
588 return (err, head)
588 return (err, head)
589 self.save_dirty()
589 self.save_dirty()
590 return (0, head)
590 return (0, head)
591
591
592 def patch(self, repo, patchfile):
592 def patch(self, repo, patchfile):
593 '''Apply patchfile to the working directory.
593 '''Apply patchfile to the working directory.
594 patchfile: name of patch file'''
594 patchfile: name of patch file'''
595 files = {}
595 files = {}
596 try:
596 try:
597 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
597 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
598 files=files, eolmode=None)
598 files=files, eolmode=None)
599 except Exception, inst:
599 except Exception, inst:
600 self.ui.note(str(inst) + '\n')
600 self.ui.note(str(inst) + '\n')
601 if not self.ui.verbose:
601 if not self.ui.verbose:
602 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
602 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
603 return (False, files, False)
603 return (False, files, False)
604
604
605 return (True, files, fuzz)
605 return (True, files, fuzz)
606
606
607 def apply(self, repo, series, list=False, update_status=True,
607 def apply(self, repo, series, list=False, update_status=True,
608 strict=False, patchdir=None, merge=None, all_files=None):
608 strict=False, patchdir=None, merge=None, all_files=None):
609 wlock = lock = tr = None
609 wlock = lock = tr = None
610 try:
610 try:
611 wlock = repo.wlock()
611 wlock = repo.wlock()
612 lock = repo.lock()
612 lock = repo.lock()
613 tr = repo.transaction("qpush")
613 tr = repo.transaction("qpush")
614 try:
614 try:
615 ret = self._apply(repo, series, list, update_status,
615 ret = self._apply(repo, series, list, update_status,
616 strict, patchdir, merge, all_files=all_files)
616 strict, patchdir, merge, all_files=all_files)
617 tr.close()
617 tr.close()
618 self.save_dirty()
618 self.save_dirty()
619 return ret
619 return ret
620 except:
620 except:
621 try:
621 try:
622 tr.abort()
622 tr.abort()
623 finally:
623 finally:
624 repo.invalidate()
624 repo.invalidate()
625 repo.dirstate.invalidate()
625 repo.dirstate.invalidate()
626 raise
626 raise
627 finally:
627 finally:
628 release(tr, lock, wlock)
628 release(tr, lock, wlock)
629 self.removeundo(repo)
629 self.removeundo(repo)
630
630
631 def _apply(self, repo, series, list=False, update_status=True,
631 def _apply(self, repo, series, list=False, update_status=True,
632 strict=False, patchdir=None, merge=None, all_files=None):
632 strict=False, patchdir=None, merge=None, all_files=None):
633 '''returns (error, hash)
633 '''returns (error, hash)
634 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
634 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
635 # TODO unify with commands.py
635 # TODO unify with commands.py
636 if not patchdir:
636 if not patchdir:
637 patchdir = self.path
637 patchdir = self.path
638 err = 0
638 err = 0
639 n = None
639 n = None
640 for patchname in series:
640 for patchname in series:
641 pushable, reason = self.pushable(patchname)
641 pushable, reason = self.pushable(patchname)
642 if not pushable:
642 if not pushable:
643 self.explain_pushable(patchname, all_patches=True)
643 self.explain_pushable(patchname, all_patches=True)
644 continue
644 continue
645 self.ui.status(_("applying %s\n") % patchname)
645 self.ui.status(_("applying %s\n") % patchname)
646 pf = os.path.join(patchdir, patchname)
646 pf = os.path.join(patchdir, patchname)
647
647
648 try:
648 try:
649 ph = patchheader(self.join(patchname), self.plainmode)
649 ph = patchheader(self.join(patchname), self.plainmode)
650 except:
650 except:
651 self.ui.warn(_("unable to read %s\n") % patchname)
651 self.ui.warn(_("unable to read %s\n") % patchname)
652 err = 1
652 err = 1
653 break
653 break
654
654
655 message = ph.message
655 message = ph.message
656 if not message:
656 if not message:
657 message = "imported patch %s\n" % patchname
657 message = "imported patch %s\n" % patchname
658 else:
658 else:
659 if list:
659 if list:
660 message.append("\nimported patch %s" % patchname)
660 message.append("\nimported patch %s" % patchname)
661 message = '\n'.join(message)
661 message = '\n'.join(message)
662
662
663 if ph.haspatch:
663 if ph.haspatch:
664 (patcherr, files, fuzz) = self.patch(repo, pf)
664 (patcherr, files, fuzz) = self.patch(repo, pf)
665 if all_files is not None:
665 if all_files is not None:
666 all_files.update(files)
666 all_files.update(files)
667 patcherr = not patcherr
667 patcherr = not patcherr
668 else:
668 else:
669 self.ui.warn(_("patch %s is empty\n") % patchname)
669 self.ui.warn(_("patch %s is empty\n") % patchname)
670 patcherr, files, fuzz = 0, [], 0
670 patcherr, files, fuzz = 0, [], 0
671
671
672 if merge and files:
672 if merge and files:
673 # Mark as removed/merged and update dirstate parent info
673 # Mark as removed/merged and update dirstate parent info
674 removed = []
674 removed = []
675 merged = []
675 merged = []
676 for f in files:
676 for f in files:
677 if os.path.exists(repo.wjoin(f)):
677 if os.path.exists(repo.wjoin(f)):
678 merged.append(f)
678 merged.append(f)
679 else:
679 else:
680 removed.append(f)
680 removed.append(f)
681 for f in removed:
681 for f in removed:
682 repo.dirstate.remove(f)
682 repo.dirstate.remove(f)
683 for f in merged:
683 for f in merged:
684 repo.dirstate.merge(f)
684 repo.dirstate.merge(f)
685 p1, p2 = repo.dirstate.parents()
685 p1, p2 = repo.dirstate.parents()
686 repo.dirstate.setparents(p1, merge)
686 repo.dirstate.setparents(p1, merge)
687
687
688 files = patch.updatedir(self.ui, repo, files)
688 files = cmdutil.updatedir(self.ui, repo, files)
689 match = cmdutil.matchfiles(repo, files or [])
689 match = cmdutil.matchfiles(repo, files or [])
690 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
690 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
691
691
692 if n is None:
692 if n is None:
693 raise util.Abort(_("repo commit failed"))
693 raise util.Abort(_("repo commit failed"))
694
694
695 if update_status:
695 if update_status:
696 self.applied.append(statusentry(n, patchname))
696 self.applied.append(statusentry(n, patchname))
697
697
698 if patcherr:
698 if patcherr:
699 self.ui.warn(_("patch failed, rejects left in working dir\n"))
699 self.ui.warn(_("patch failed, rejects left in working dir\n"))
700 err = 2
700 err = 2
701 break
701 break
702
702
703 if fuzz and strict:
703 if fuzz and strict:
704 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
704 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
705 err = 3
705 err = 3
706 break
706 break
707 return (err, n)
707 return (err, n)
708
708
709 def _cleanup(self, patches, numrevs, keep=False):
709 def _cleanup(self, patches, numrevs, keep=False):
710 if not keep:
710 if not keep:
711 r = self.qrepo()
711 r = self.qrepo()
712 if r:
712 if r:
713 r[None].remove(patches, True)
713 r[None].remove(patches, True)
714 else:
714 else:
715 for p in patches:
715 for p in patches:
716 os.unlink(self.join(p))
716 os.unlink(self.join(p))
717
717
718 if numrevs:
718 if numrevs:
719 del self.applied[:numrevs]
719 del self.applied[:numrevs]
720 self.applied_dirty = 1
720 self.applied_dirty = 1
721
721
722 for i in sorted([self.find_series(p) for p in patches], reverse=True):
722 for i in sorted([self.find_series(p) for p in patches], reverse=True):
723 del self.full_series[i]
723 del self.full_series[i]
724 self.parse_series()
724 self.parse_series()
725 self.series_dirty = 1
725 self.series_dirty = 1
726
726
727 def _revpatches(self, repo, revs):
727 def _revpatches(self, repo, revs):
728 firstrev = repo[self.applied[0].node].rev()
728 firstrev = repo[self.applied[0].node].rev()
729 patches = []
729 patches = []
730 for i, rev in enumerate(revs):
730 for i, rev in enumerate(revs):
731
731
732 if rev < firstrev:
732 if rev < firstrev:
733 raise util.Abort(_('revision %d is not managed') % rev)
733 raise util.Abort(_('revision %d is not managed') % rev)
734
734
735 ctx = repo[rev]
735 ctx = repo[rev]
736 base = self.applied[i].node
736 base = self.applied[i].node
737 if ctx.node() != base:
737 if ctx.node() != base:
738 msg = _('cannot delete revision %d above applied patches')
738 msg = _('cannot delete revision %d above applied patches')
739 raise util.Abort(msg % rev)
739 raise util.Abort(msg % rev)
740
740
741 patch = self.applied[i].name
741 patch = self.applied[i].name
742 for fmt in ('[mq]: %s', 'imported patch %s'):
742 for fmt in ('[mq]: %s', 'imported patch %s'):
743 if ctx.description() == fmt % patch:
743 if ctx.description() == fmt % patch:
744 msg = _('patch %s finalized without changeset message\n')
744 msg = _('patch %s finalized without changeset message\n')
745 repo.ui.status(msg % patch)
745 repo.ui.status(msg % patch)
746 break
746 break
747
747
748 patches.append(patch)
748 patches.append(patch)
749 return patches
749 return patches
750
750
751 def finish(self, repo, revs):
751 def finish(self, repo, revs):
752 patches = self._revpatches(repo, sorted(revs))
752 patches = self._revpatches(repo, sorted(revs))
753 self._cleanup(patches, len(patches))
753 self._cleanup(patches, len(patches))
754
754
755 def delete(self, repo, patches, opts):
755 def delete(self, repo, patches, opts):
756 if not patches and not opts.get('rev'):
756 if not patches and not opts.get('rev'):
757 raise util.Abort(_('qdelete requires at least one revision or '
757 raise util.Abort(_('qdelete requires at least one revision or '
758 'patch name'))
758 'patch name'))
759
759
760 realpatches = []
760 realpatches = []
761 for patch in patches:
761 for patch in patches:
762 patch = self.lookup(patch, strict=True)
762 patch = self.lookup(patch, strict=True)
763 info = self.isapplied(patch)
763 info = self.isapplied(patch)
764 if info:
764 if info:
765 raise util.Abort(_("cannot delete applied patch %s") % patch)
765 raise util.Abort(_("cannot delete applied patch %s") % patch)
766 if patch not in self.series:
766 if patch not in self.series:
767 raise util.Abort(_("patch %s not in series file") % patch)
767 raise util.Abort(_("patch %s not in series file") % patch)
768 realpatches.append(patch)
768 realpatches.append(patch)
769
769
770 numrevs = 0
770 numrevs = 0
771 if opts.get('rev'):
771 if opts.get('rev'):
772 if not self.applied:
772 if not self.applied:
773 raise util.Abort(_('no patches applied'))
773 raise util.Abort(_('no patches applied'))
774 revs = cmdutil.revrange(repo, opts['rev'])
774 revs = cmdutil.revrange(repo, opts['rev'])
775 if len(revs) > 1 and revs[0] > revs[1]:
775 if len(revs) > 1 and revs[0] > revs[1]:
776 revs.reverse()
776 revs.reverse()
777 revpatches = self._revpatches(repo, revs)
777 revpatches = self._revpatches(repo, revs)
778 realpatches += revpatches
778 realpatches += revpatches
779 numrevs = len(revpatches)
779 numrevs = len(revpatches)
780
780
781 self._cleanup(realpatches, numrevs, opts.get('keep'))
781 self._cleanup(realpatches, numrevs, opts.get('keep'))
782
782
783 def check_toppatch(self, repo):
783 def check_toppatch(self, repo):
784 if self.applied:
784 if self.applied:
785 top = self.applied[-1].node
785 top = self.applied[-1].node
786 patch = self.applied[-1].name
786 patch = self.applied[-1].name
787 pp = repo.dirstate.parents()
787 pp = repo.dirstate.parents()
788 if top not in pp:
788 if top not in pp:
789 raise util.Abort(_("working directory revision is not qtip"))
789 raise util.Abort(_("working directory revision is not qtip"))
790 return top, patch
790 return top, patch
791 return None, None
791 return None, None
792
792
793 def check_localchanges(self, repo, force=False, refresh=True):
793 def check_localchanges(self, repo, force=False, refresh=True):
794 m, a, r, d = repo.status()[:4]
794 m, a, r, d = repo.status()[:4]
795 if (m or a or r or d) and not force:
795 if (m or a or r or d) and not force:
796 if refresh:
796 if refresh:
797 raise util.Abort(_("local changes found, refresh first"))
797 raise util.Abort(_("local changes found, refresh first"))
798 else:
798 else:
799 raise util.Abort(_("local changes found"))
799 raise util.Abort(_("local changes found"))
800 return m, a, r, d
800 return m, a, r, d
801
801
802 _reserved = ('series', 'status', 'guards')
802 _reserved = ('series', 'status', 'guards')
803 def check_reserved_name(self, name):
803 def check_reserved_name(self, name):
804 if (name in self._reserved or name.startswith('.hg')
804 if (name in self._reserved or name.startswith('.hg')
805 or name.startswith('.mq') or '#' in name or ':' in name):
805 or name.startswith('.mq') or '#' in name or ':' in name):
806 raise util.Abort(_('"%s" cannot be used as the name of a patch')
806 raise util.Abort(_('"%s" cannot be used as the name of a patch')
807 % name)
807 % name)
808
808
809 def new(self, repo, patchfn, *pats, **opts):
809 def new(self, repo, patchfn, *pats, **opts):
810 """options:
810 """options:
811 msg: a string or a no-argument function returning a string
811 msg: a string or a no-argument function returning a string
812 """
812 """
813 msg = opts.get('msg')
813 msg = opts.get('msg')
814 user = opts.get('user')
814 user = opts.get('user')
815 date = opts.get('date')
815 date = opts.get('date')
816 if date:
816 if date:
817 date = util.parsedate(date)
817 date = util.parsedate(date)
818 diffopts = self.diffopts({'git': opts.get('git')})
818 diffopts = self.diffopts({'git': opts.get('git')})
819 self.check_reserved_name(patchfn)
819 self.check_reserved_name(patchfn)
820 if os.path.exists(self.join(patchfn)):
820 if os.path.exists(self.join(patchfn)):
821 raise util.Abort(_('patch "%s" already exists') % patchfn)
821 raise util.Abort(_('patch "%s" already exists') % patchfn)
822 if opts.get('include') or opts.get('exclude') or pats:
822 if opts.get('include') or opts.get('exclude') or pats:
823 match = cmdutil.match(repo, pats, opts)
823 match = cmdutil.match(repo, pats, opts)
824 # detect missing files in pats
824 # detect missing files in pats
825 def badfn(f, msg):
825 def badfn(f, msg):
826 raise util.Abort('%s: %s' % (f, msg))
826 raise util.Abort('%s: %s' % (f, msg))
827 match.bad = badfn
827 match.bad = badfn
828 m, a, r, d = repo.status(match=match)[:4]
828 m, a, r, d = repo.status(match=match)[:4]
829 else:
829 else:
830 m, a, r, d = self.check_localchanges(repo, force=True)
830 m, a, r, d = self.check_localchanges(repo, force=True)
831 match = cmdutil.matchfiles(repo, m + a + r)
831 match = cmdutil.matchfiles(repo, m + a + r)
832 if len(repo[None].parents()) > 1:
832 if len(repo[None].parents()) > 1:
833 raise util.Abort(_('cannot manage merge changesets'))
833 raise util.Abort(_('cannot manage merge changesets'))
834 commitfiles = m + a + r
834 commitfiles = m + a + r
835 self.check_toppatch(repo)
835 self.check_toppatch(repo)
836 insert = self.full_series_end()
836 insert = self.full_series_end()
837 wlock = repo.wlock()
837 wlock = repo.wlock()
838 try:
838 try:
839 # if patch file write fails, abort early
839 # if patch file write fails, abort early
840 p = self.opener(patchfn, "w")
840 p = self.opener(patchfn, "w")
841 try:
841 try:
842 if self.plainmode:
842 if self.plainmode:
843 if user:
843 if user:
844 p.write("From: " + user + "\n")
844 p.write("From: " + user + "\n")
845 if not date:
845 if not date:
846 p.write("\n")
846 p.write("\n")
847 if date:
847 if date:
848 p.write("Date: %d %d\n\n" % date)
848 p.write("Date: %d %d\n\n" % date)
849 else:
849 else:
850 p.write("# HG changeset patch\n")
850 p.write("# HG changeset patch\n")
851 p.write("# Parent "
851 p.write("# Parent "
852 + hex(repo[None].parents()[0].node()) + "\n")
852 + hex(repo[None].parents()[0].node()) + "\n")
853 if user:
853 if user:
854 p.write("# User " + user + "\n")
854 p.write("# User " + user + "\n")
855 if date:
855 if date:
856 p.write("# Date %s %s\n\n" % date)
856 p.write("# Date %s %s\n\n" % date)
857 if hasattr(msg, '__call__'):
857 if hasattr(msg, '__call__'):
858 msg = msg()
858 msg = msg()
859 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
859 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
860 n = repo.commit(commitmsg, user, date, match=match, force=True)
860 n = repo.commit(commitmsg, user, date, match=match, force=True)
861 if n is None:
861 if n is None:
862 raise util.Abort(_("repo commit failed"))
862 raise util.Abort(_("repo commit failed"))
863 try:
863 try:
864 self.full_series[insert:insert] = [patchfn]
864 self.full_series[insert:insert] = [patchfn]
865 self.applied.append(statusentry(n, patchfn))
865 self.applied.append(statusentry(n, patchfn))
866 self.parse_series()
866 self.parse_series()
867 self.series_dirty = 1
867 self.series_dirty = 1
868 self.applied_dirty = 1
868 self.applied_dirty = 1
869 if msg:
869 if msg:
870 msg = msg + "\n\n"
870 msg = msg + "\n\n"
871 p.write(msg)
871 p.write(msg)
872 if commitfiles:
872 if commitfiles:
873 parent = self.qparents(repo, n)
873 parent = self.qparents(repo, n)
874 chunks = patch.diff(repo, node1=parent, node2=n,
874 chunks = patch.diff(repo, node1=parent, node2=n,
875 match=match, opts=diffopts)
875 match=match, opts=diffopts)
876 for chunk in chunks:
876 for chunk in chunks:
877 p.write(chunk)
877 p.write(chunk)
878 p.close()
878 p.close()
879 wlock.release()
879 wlock.release()
880 wlock = None
880 wlock = None
881 r = self.qrepo()
881 r = self.qrepo()
882 if r:
882 if r:
883 r[None].add([patchfn])
883 r[None].add([patchfn])
884 except:
884 except:
885 repo.rollback()
885 repo.rollback()
886 raise
886 raise
887 except Exception:
887 except Exception:
888 patchpath = self.join(patchfn)
888 patchpath = self.join(patchfn)
889 try:
889 try:
890 os.unlink(patchpath)
890 os.unlink(patchpath)
891 except:
891 except:
892 self.ui.warn(_('error unlinking %s\n') % patchpath)
892 self.ui.warn(_('error unlinking %s\n') % patchpath)
893 raise
893 raise
894 self.removeundo(repo)
894 self.removeundo(repo)
895 finally:
895 finally:
896 release(wlock)
896 release(wlock)
897
897
898 def strip(self, repo, revs, update=True, backup="all", force=None):
898 def strip(self, repo, revs, update=True, backup="all", force=None):
899 wlock = lock = None
899 wlock = lock = None
900 try:
900 try:
901 wlock = repo.wlock()
901 wlock = repo.wlock()
902 lock = repo.lock()
902 lock = repo.lock()
903
903
904 if update:
904 if update:
905 self.check_localchanges(repo, force=force, refresh=False)
905 self.check_localchanges(repo, force=force, refresh=False)
906 urev = self.qparents(repo, revs[0])
906 urev = self.qparents(repo, revs[0])
907 hg.clean(repo, urev)
907 hg.clean(repo, urev)
908 repo.dirstate.write()
908 repo.dirstate.write()
909
909
910 self.removeundo(repo)
910 self.removeundo(repo)
911 for rev in revs:
911 for rev in revs:
912 repair.strip(self.ui, repo, rev, backup)
912 repair.strip(self.ui, repo, rev, backup)
913 # strip may have unbundled a set of backed up revisions after
913 # strip may have unbundled a set of backed up revisions after
914 # the actual strip
914 # the actual strip
915 self.removeundo(repo)
915 self.removeundo(repo)
916 finally:
916 finally:
917 release(lock, wlock)
917 release(lock, wlock)
918
918
919 def isapplied(self, patch):
919 def isapplied(self, patch):
920 """returns (index, rev, patch)"""
920 """returns (index, rev, patch)"""
921 for i, a in enumerate(self.applied):
921 for i, a in enumerate(self.applied):
922 if a.name == patch:
922 if a.name == patch:
923 return (i, a.node, a.name)
923 return (i, a.node, a.name)
924 return None
924 return None
925
925
926 # if the exact patch name does not exist, we try a few
926 # if the exact patch name does not exist, we try a few
927 # variations. If strict is passed, we try only #1
927 # variations. If strict is passed, we try only #1
928 #
928 #
929 # 1) a number to indicate an offset in the series file
929 # 1) a number to indicate an offset in the series file
930 # 2) a unique substring of the patch name was given
930 # 2) a unique substring of the patch name was given
931 # 3) patchname[-+]num to indicate an offset in the series file
931 # 3) patchname[-+]num to indicate an offset in the series file
932 def lookup(self, patch, strict=False):
932 def lookup(self, patch, strict=False):
933 patch = patch and str(patch)
933 patch = patch and str(patch)
934
934
935 def partial_name(s):
935 def partial_name(s):
936 if s in self.series:
936 if s in self.series:
937 return s
937 return s
938 matches = [x for x in self.series if s in x]
938 matches = [x for x in self.series if s in x]
939 if len(matches) > 1:
939 if len(matches) > 1:
940 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
940 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
941 for m in matches:
941 for m in matches:
942 self.ui.warn(' %s\n' % m)
942 self.ui.warn(' %s\n' % m)
943 return None
943 return None
944 if matches:
944 if matches:
945 return matches[0]
945 return matches[0]
946 if self.series and self.applied:
946 if self.series and self.applied:
947 if s == 'qtip':
947 if s == 'qtip':
948 return self.series[self.series_end(True)-1]
948 return self.series[self.series_end(True)-1]
949 if s == 'qbase':
949 if s == 'qbase':
950 return self.series[0]
950 return self.series[0]
951 return None
951 return None
952
952
953 if patch is None:
953 if patch is None:
954 return None
954 return None
955 if patch in self.series:
955 if patch in self.series:
956 return patch
956 return patch
957
957
958 if not os.path.isfile(self.join(patch)):
958 if not os.path.isfile(self.join(patch)):
959 try:
959 try:
960 sno = int(patch)
960 sno = int(patch)
961 except (ValueError, OverflowError):
961 except (ValueError, OverflowError):
962 pass
962 pass
963 else:
963 else:
964 if -len(self.series) <= sno < len(self.series):
964 if -len(self.series) <= sno < len(self.series):
965 return self.series[sno]
965 return self.series[sno]
966
966
967 if not strict:
967 if not strict:
968 res = partial_name(patch)
968 res = partial_name(patch)
969 if res:
969 if res:
970 return res
970 return res
971 minus = patch.rfind('-')
971 minus = patch.rfind('-')
972 if minus >= 0:
972 if minus >= 0:
973 res = partial_name(patch[:minus])
973 res = partial_name(patch[:minus])
974 if res:
974 if res:
975 i = self.series.index(res)
975 i = self.series.index(res)
976 try:
976 try:
977 off = int(patch[minus + 1:] or 1)
977 off = int(patch[minus + 1:] or 1)
978 except (ValueError, OverflowError):
978 except (ValueError, OverflowError):
979 pass
979 pass
980 else:
980 else:
981 if i - off >= 0:
981 if i - off >= 0:
982 return self.series[i - off]
982 return self.series[i - off]
983 plus = patch.rfind('+')
983 plus = patch.rfind('+')
984 if plus >= 0:
984 if plus >= 0:
985 res = partial_name(patch[:plus])
985 res = partial_name(patch[:plus])
986 if res:
986 if res:
987 i = self.series.index(res)
987 i = self.series.index(res)
988 try:
988 try:
989 off = int(patch[plus + 1:] or 1)
989 off = int(patch[plus + 1:] or 1)
990 except (ValueError, OverflowError):
990 except (ValueError, OverflowError):
991 pass
991 pass
992 else:
992 else:
993 if i + off < len(self.series):
993 if i + off < len(self.series):
994 return self.series[i + off]
994 return self.series[i + off]
995 raise util.Abort(_("patch %s not in series") % patch)
995 raise util.Abort(_("patch %s not in series") % patch)
996
996
997 def push(self, repo, patch=None, force=False, list=False,
997 def push(self, repo, patch=None, force=False, list=False,
998 mergeq=None, all=False, move=False):
998 mergeq=None, all=False, move=False):
999 diffopts = self.diffopts()
999 diffopts = self.diffopts()
1000 wlock = repo.wlock()
1000 wlock = repo.wlock()
1001 try:
1001 try:
1002 heads = []
1002 heads = []
1003 for b, ls in repo.branchmap().iteritems():
1003 for b, ls in repo.branchmap().iteritems():
1004 heads += ls
1004 heads += ls
1005 if not heads:
1005 if not heads:
1006 heads = [nullid]
1006 heads = [nullid]
1007 if repo.dirstate.parents()[0] not in heads:
1007 if repo.dirstate.parents()[0] not in heads:
1008 self.ui.status(_("(working directory not at a head)\n"))
1008 self.ui.status(_("(working directory not at a head)\n"))
1009
1009
1010 if not self.series:
1010 if not self.series:
1011 self.ui.warn(_('no patches in series\n'))
1011 self.ui.warn(_('no patches in series\n'))
1012 return 0
1012 return 0
1013
1013
1014 patch = self.lookup(patch)
1014 patch = self.lookup(patch)
1015 # Suppose our series file is: A B C and the current 'top'
1015 # Suppose our series file is: A B C and the current 'top'
1016 # patch is B. qpush C should be performed (moving forward)
1016 # patch is B. qpush C should be performed (moving forward)
1017 # qpush B is a NOP (no change) qpush A is an error (can't
1017 # qpush B is a NOP (no change) qpush A is an error (can't
1018 # go backwards with qpush)
1018 # go backwards with qpush)
1019 if patch:
1019 if patch:
1020 info = self.isapplied(patch)
1020 info = self.isapplied(patch)
1021 if info:
1021 if info:
1022 if info[0] < len(self.applied) - 1:
1022 if info[0] < len(self.applied) - 1:
1023 raise util.Abort(
1023 raise util.Abort(
1024 _("cannot push to a previous patch: %s") % patch)
1024 _("cannot push to a previous patch: %s") % patch)
1025 self.ui.warn(
1025 self.ui.warn(
1026 _('qpush: %s is already at the top\n') % patch)
1026 _('qpush: %s is already at the top\n') % patch)
1027 return 0
1027 return 0
1028 pushable, reason = self.pushable(patch)
1028 pushable, reason = self.pushable(patch)
1029 if not pushable:
1029 if not pushable:
1030 if reason:
1030 if reason:
1031 reason = _('guarded by %r') % reason
1031 reason = _('guarded by %r') % reason
1032 else:
1032 else:
1033 reason = _('no matching guards')
1033 reason = _('no matching guards')
1034 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1034 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1035 return 1
1035 return 1
1036 elif all:
1036 elif all:
1037 patch = self.series[-1]
1037 patch = self.series[-1]
1038 if self.isapplied(patch):
1038 if self.isapplied(patch):
1039 self.ui.warn(_('all patches are currently applied\n'))
1039 self.ui.warn(_('all patches are currently applied\n'))
1040 return 0
1040 return 0
1041
1041
1042 # Following the above example, starting at 'top' of B:
1042 # Following the above example, starting at 'top' of B:
1043 # qpush should be performed (pushes C), but a subsequent
1043 # qpush should be performed (pushes C), but a subsequent
1044 # qpush without an argument is an error (nothing to
1044 # qpush without an argument is an error (nothing to
1045 # apply). This allows a loop of "...while hg qpush..." to
1045 # apply). This allows a loop of "...while hg qpush..." to
1046 # work as it detects an error when done
1046 # work as it detects an error when done
1047 start = self.series_end()
1047 start = self.series_end()
1048 if start == len(self.series):
1048 if start == len(self.series):
1049 self.ui.warn(_('patch series already fully applied\n'))
1049 self.ui.warn(_('patch series already fully applied\n'))
1050 return 1
1050 return 1
1051 if not force:
1051 if not force:
1052 self.check_localchanges(repo)
1052 self.check_localchanges(repo)
1053
1053
1054 if move:
1054 if move:
1055 if not patch:
1055 if not patch:
1056 raise util.Abort(_("please specify the patch to move"))
1056 raise util.Abort(_("please specify the patch to move"))
1057 for i, rpn in enumerate(self.full_series[start:]):
1057 for i, rpn in enumerate(self.full_series[start:]):
1058 # strip markers for patch guards
1058 # strip markers for patch guards
1059 if self.guard_re.split(rpn, 1)[0] == patch:
1059 if self.guard_re.split(rpn, 1)[0] == patch:
1060 break
1060 break
1061 index = start + i
1061 index = start + i
1062 assert index < len(self.full_series)
1062 assert index < len(self.full_series)
1063 fullpatch = self.full_series[index]
1063 fullpatch = self.full_series[index]
1064 del self.full_series[index]
1064 del self.full_series[index]
1065 self.full_series.insert(start, fullpatch)
1065 self.full_series.insert(start, fullpatch)
1066 self.parse_series()
1066 self.parse_series()
1067 self.series_dirty = 1
1067 self.series_dirty = 1
1068
1068
1069 self.applied_dirty = 1
1069 self.applied_dirty = 1
1070 if start > 0:
1070 if start > 0:
1071 self.check_toppatch(repo)
1071 self.check_toppatch(repo)
1072 if not patch:
1072 if not patch:
1073 patch = self.series[start]
1073 patch = self.series[start]
1074 end = start + 1
1074 end = start + 1
1075 else:
1075 else:
1076 end = self.series.index(patch, start) + 1
1076 end = self.series.index(patch, start) + 1
1077
1077
1078 s = self.series[start:end]
1078 s = self.series[start:end]
1079 all_files = set()
1079 all_files = set()
1080 try:
1080 try:
1081 if mergeq:
1081 if mergeq:
1082 ret = self.mergepatch(repo, mergeq, s, diffopts)
1082 ret = self.mergepatch(repo, mergeq, s, diffopts)
1083 else:
1083 else:
1084 ret = self.apply(repo, s, list, all_files=all_files)
1084 ret = self.apply(repo, s, list, all_files=all_files)
1085 except:
1085 except:
1086 self.ui.warn(_('cleaning up working directory...'))
1086 self.ui.warn(_('cleaning up working directory...'))
1087 node = repo.dirstate.parents()[0]
1087 node = repo.dirstate.parents()[0]
1088 hg.revert(repo, node, None)
1088 hg.revert(repo, node, None)
1089 # only remove unknown files that we know we touched or
1089 # only remove unknown files that we know we touched or
1090 # created while patching
1090 # created while patching
1091 for f in all_files:
1091 for f in all_files:
1092 if f not in repo.dirstate:
1092 if f not in repo.dirstate:
1093 try:
1093 try:
1094 util.unlink(repo.wjoin(f))
1094 util.unlink(repo.wjoin(f))
1095 except OSError, inst:
1095 except OSError, inst:
1096 if inst.errno != errno.ENOENT:
1096 if inst.errno != errno.ENOENT:
1097 raise
1097 raise
1098 self.ui.warn(_('done\n'))
1098 self.ui.warn(_('done\n'))
1099 raise
1099 raise
1100
1100
1101 if not self.applied:
1101 if not self.applied:
1102 return ret[0]
1102 return ret[0]
1103 top = self.applied[-1].name
1103 top = self.applied[-1].name
1104 if ret[0] and ret[0] > 1:
1104 if ret[0] and ret[0] > 1:
1105 msg = _("errors during apply, please fix and refresh %s\n")
1105 msg = _("errors during apply, please fix and refresh %s\n")
1106 self.ui.write(msg % top)
1106 self.ui.write(msg % top)
1107 else:
1107 else:
1108 self.ui.write(_("now at: %s\n") % top)
1108 self.ui.write(_("now at: %s\n") % top)
1109 return ret[0]
1109 return ret[0]
1110
1110
1111 finally:
1111 finally:
1112 wlock.release()
1112 wlock.release()
1113
1113
1114 def pop(self, repo, patch=None, force=False, update=True, all=False):
1114 def pop(self, repo, patch=None, force=False, update=True, all=False):
1115 wlock = repo.wlock()
1115 wlock = repo.wlock()
1116 try:
1116 try:
1117 if patch:
1117 if patch:
1118 # index, rev, patch
1118 # index, rev, patch
1119 info = self.isapplied(patch)
1119 info = self.isapplied(patch)
1120 if not info:
1120 if not info:
1121 patch = self.lookup(patch)
1121 patch = self.lookup(patch)
1122 info = self.isapplied(patch)
1122 info = self.isapplied(patch)
1123 if not info:
1123 if not info:
1124 raise util.Abort(_("patch %s is not applied") % patch)
1124 raise util.Abort(_("patch %s is not applied") % patch)
1125
1125
1126 if not self.applied:
1126 if not self.applied:
1127 # Allow qpop -a to work repeatedly,
1127 # Allow qpop -a to work repeatedly,
1128 # but not qpop without an argument
1128 # but not qpop without an argument
1129 self.ui.warn(_("no patches applied\n"))
1129 self.ui.warn(_("no patches applied\n"))
1130 return not all
1130 return not all
1131
1131
1132 if all:
1132 if all:
1133 start = 0
1133 start = 0
1134 elif patch:
1134 elif patch:
1135 start = info[0] + 1
1135 start = info[0] + 1
1136 else:
1136 else:
1137 start = len(self.applied) - 1
1137 start = len(self.applied) - 1
1138
1138
1139 if start >= len(self.applied):
1139 if start >= len(self.applied):
1140 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1140 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1141 return
1141 return
1142
1142
1143 if not update:
1143 if not update:
1144 parents = repo.dirstate.parents()
1144 parents = repo.dirstate.parents()
1145 rr = [x.node for x in self.applied]
1145 rr = [x.node for x in self.applied]
1146 for p in parents:
1146 for p in parents:
1147 if p in rr:
1147 if p in rr:
1148 self.ui.warn(_("qpop: forcing dirstate update\n"))
1148 self.ui.warn(_("qpop: forcing dirstate update\n"))
1149 update = True
1149 update = True
1150 else:
1150 else:
1151 parents = [p.node() for p in repo[None].parents()]
1151 parents = [p.node() for p in repo[None].parents()]
1152 needupdate = False
1152 needupdate = False
1153 for entry in self.applied[start:]:
1153 for entry in self.applied[start:]:
1154 if entry.node in parents:
1154 if entry.node in parents:
1155 needupdate = True
1155 needupdate = True
1156 break
1156 break
1157 update = needupdate
1157 update = needupdate
1158
1158
1159 if not force and update:
1159 if not force and update:
1160 self.check_localchanges(repo)
1160 self.check_localchanges(repo)
1161
1161
1162 self.applied_dirty = 1
1162 self.applied_dirty = 1
1163 end = len(self.applied)
1163 end = len(self.applied)
1164 rev = self.applied[start].node
1164 rev = self.applied[start].node
1165 if update:
1165 if update:
1166 top = self.check_toppatch(repo)[0]
1166 top = self.check_toppatch(repo)[0]
1167
1167
1168 try:
1168 try:
1169 heads = repo.changelog.heads(rev)
1169 heads = repo.changelog.heads(rev)
1170 except error.LookupError:
1170 except error.LookupError:
1171 node = short(rev)
1171 node = short(rev)
1172 raise util.Abort(_('trying to pop unknown node %s') % node)
1172 raise util.Abort(_('trying to pop unknown node %s') % node)
1173
1173
1174 if heads != [self.applied[-1].node]:
1174 if heads != [self.applied[-1].node]:
1175 raise util.Abort(_("popping would remove a revision not "
1175 raise util.Abort(_("popping would remove a revision not "
1176 "managed by this patch queue"))
1176 "managed by this patch queue"))
1177
1177
1178 # we know there are no local changes, so we can make a simplified
1178 # we know there are no local changes, so we can make a simplified
1179 # form of hg.update.
1179 # form of hg.update.
1180 if update:
1180 if update:
1181 qp = self.qparents(repo, rev)
1181 qp = self.qparents(repo, rev)
1182 ctx = repo[qp]
1182 ctx = repo[qp]
1183 m, a, r, d = repo.status(qp, top)[:4]
1183 m, a, r, d = repo.status(qp, top)[:4]
1184 if d:
1184 if d:
1185 raise util.Abort(_("deletions found between repo revs"))
1185 raise util.Abort(_("deletions found between repo revs"))
1186 for f in a:
1186 for f in a:
1187 try:
1187 try:
1188 util.unlink(repo.wjoin(f))
1188 util.unlink(repo.wjoin(f))
1189 except OSError, e:
1189 except OSError, e:
1190 if e.errno != errno.ENOENT:
1190 if e.errno != errno.ENOENT:
1191 raise
1191 raise
1192 repo.dirstate.forget(f)
1192 repo.dirstate.forget(f)
1193 for f in m + r:
1193 for f in m + r:
1194 fctx = ctx[f]
1194 fctx = ctx[f]
1195 repo.wwrite(f, fctx.data(), fctx.flags())
1195 repo.wwrite(f, fctx.data(), fctx.flags())
1196 repo.dirstate.normal(f)
1196 repo.dirstate.normal(f)
1197 repo.dirstate.setparents(qp, nullid)
1197 repo.dirstate.setparents(qp, nullid)
1198 for patch in reversed(self.applied[start:end]):
1198 for patch in reversed(self.applied[start:end]):
1199 self.ui.status(_("popping %s\n") % patch.name)
1199 self.ui.status(_("popping %s\n") % patch.name)
1200 del self.applied[start:end]
1200 del self.applied[start:end]
1201 self.strip(repo, [rev], update=False, backup='strip')
1201 self.strip(repo, [rev], update=False, backup='strip')
1202 if self.applied:
1202 if self.applied:
1203 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1203 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1204 else:
1204 else:
1205 self.ui.write(_("patch queue now empty\n"))
1205 self.ui.write(_("patch queue now empty\n"))
1206 finally:
1206 finally:
1207 wlock.release()
1207 wlock.release()
1208
1208
1209 def diff(self, repo, pats, opts):
1209 def diff(self, repo, pats, opts):
1210 top, patch = self.check_toppatch(repo)
1210 top, patch = self.check_toppatch(repo)
1211 if not top:
1211 if not top:
1212 self.ui.write(_("no patches applied\n"))
1212 self.ui.write(_("no patches applied\n"))
1213 return
1213 return
1214 qp = self.qparents(repo, top)
1214 qp = self.qparents(repo, top)
1215 if opts.get('reverse'):
1215 if opts.get('reverse'):
1216 node1, node2 = None, qp
1216 node1, node2 = None, qp
1217 else:
1217 else:
1218 node1, node2 = qp, None
1218 node1, node2 = qp, None
1219 diffopts = self.diffopts(opts, patch)
1219 diffopts = self.diffopts(opts, patch)
1220 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1220 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1221
1221
1222 def refresh(self, repo, pats=None, **opts):
1222 def refresh(self, repo, pats=None, **opts):
1223 if not self.applied:
1223 if not self.applied:
1224 self.ui.write(_("no patches applied\n"))
1224 self.ui.write(_("no patches applied\n"))
1225 return 1
1225 return 1
1226 msg = opts.get('msg', '').rstrip()
1226 msg = opts.get('msg', '').rstrip()
1227 newuser = opts.get('user')
1227 newuser = opts.get('user')
1228 newdate = opts.get('date')
1228 newdate = opts.get('date')
1229 if newdate:
1229 if newdate:
1230 newdate = '%d %d' % util.parsedate(newdate)
1230 newdate = '%d %d' % util.parsedate(newdate)
1231 wlock = repo.wlock()
1231 wlock = repo.wlock()
1232
1232
1233 try:
1233 try:
1234 self.check_toppatch(repo)
1234 self.check_toppatch(repo)
1235 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1235 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1236 if repo.changelog.heads(top) != [top]:
1236 if repo.changelog.heads(top) != [top]:
1237 raise util.Abort(_("cannot refresh a revision with children"))
1237 raise util.Abort(_("cannot refresh a revision with children"))
1238
1238
1239 cparents = repo.changelog.parents(top)
1239 cparents = repo.changelog.parents(top)
1240 patchparent = self.qparents(repo, top)
1240 patchparent = self.qparents(repo, top)
1241 ph = patchheader(self.join(patchfn), self.plainmode)
1241 ph = patchheader(self.join(patchfn), self.plainmode)
1242 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1242 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1243 if msg:
1243 if msg:
1244 ph.setmessage(msg)
1244 ph.setmessage(msg)
1245 if newuser:
1245 if newuser:
1246 ph.setuser(newuser)
1246 ph.setuser(newuser)
1247 if newdate:
1247 if newdate:
1248 ph.setdate(newdate)
1248 ph.setdate(newdate)
1249 ph.setparent(hex(patchparent))
1249 ph.setparent(hex(patchparent))
1250
1250
1251 # only commit new patch when write is complete
1251 # only commit new patch when write is complete
1252 patchf = self.opener(patchfn, 'w', atomictemp=True)
1252 patchf = self.opener(patchfn, 'w', atomictemp=True)
1253
1253
1254 comments = str(ph)
1254 comments = str(ph)
1255 if comments:
1255 if comments:
1256 patchf.write(comments)
1256 patchf.write(comments)
1257
1257
1258 # update the dirstate in place, strip off the qtip commit
1258 # update the dirstate in place, strip off the qtip commit
1259 # and then commit.
1259 # and then commit.
1260 #
1260 #
1261 # this should really read:
1261 # this should really read:
1262 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1262 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1263 # but we do it backwards to take advantage of manifest/chlog
1263 # but we do it backwards to take advantage of manifest/chlog
1264 # caching against the next repo.status call
1264 # caching against the next repo.status call
1265 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1265 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1266 changes = repo.changelog.read(top)
1266 changes = repo.changelog.read(top)
1267 man = repo.manifest.read(changes[0])
1267 man = repo.manifest.read(changes[0])
1268 aaa = aa[:]
1268 aaa = aa[:]
1269 matchfn = cmdutil.match(repo, pats, opts)
1269 matchfn = cmdutil.match(repo, pats, opts)
1270 # in short mode, we only diff the files included in the
1270 # in short mode, we only diff the files included in the
1271 # patch already plus specified files
1271 # patch already plus specified files
1272 if opts.get('short'):
1272 if opts.get('short'):
1273 # if amending a patch, we start with existing
1273 # if amending a patch, we start with existing
1274 # files plus specified files - unfiltered
1274 # files plus specified files - unfiltered
1275 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1275 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1276 # filter with inc/exl options
1276 # filter with inc/exl options
1277 matchfn = cmdutil.match(repo, opts=opts)
1277 matchfn = cmdutil.match(repo, opts=opts)
1278 else:
1278 else:
1279 match = cmdutil.matchall(repo)
1279 match = cmdutil.matchall(repo)
1280 m, a, r, d = repo.status(match=match)[:4]
1280 m, a, r, d = repo.status(match=match)[:4]
1281
1281
1282 # we might end up with files that were added between
1282 # we might end up with files that were added between
1283 # qtip and the dirstate parent, but then changed in the
1283 # qtip and the dirstate parent, but then changed in the
1284 # local dirstate. in this case, we want them to only
1284 # local dirstate. in this case, we want them to only
1285 # show up in the added section
1285 # show up in the added section
1286 for x in m:
1286 for x in m:
1287 if x not in aa:
1287 if x not in aa:
1288 mm.append(x)
1288 mm.append(x)
1289 # we might end up with files added by the local dirstate that
1289 # we might end up with files added by the local dirstate that
1290 # were deleted by the patch. In this case, they should only
1290 # were deleted by the patch. In this case, they should only
1291 # show up in the changed section.
1291 # show up in the changed section.
1292 for x in a:
1292 for x in a:
1293 if x in dd:
1293 if x in dd:
1294 del dd[dd.index(x)]
1294 del dd[dd.index(x)]
1295 mm.append(x)
1295 mm.append(x)
1296 else:
1296 else:
1297 aa.append(x)
1297 aa.append(x)
1298 # make sure any files deleted in the local dirstate
1298 # make sure any files deleted in the local dirstate
1299 # are not in the add or change column of the patch
1299 # are not in the add or change column of the patch
1300 forget = []
1300 forget = []
1301 for x in d + r:
1301 for x in d + r:
1302 if x in aa:
1302 if x in aa:
1303 del aa[aa.index(x)]
1303 del aa[aa.index(x)]
1304 forget.append(x)
1304 forget.append(x)
1305 continue
1305 continue
1306 elif x in mm:
1306 elif x in mm:
1307 del mm[mm.index(x)]
1307 del mm[mm.index(x)]
1308 dd.append(x)
1308 dd.append(x)
1309
1309
1310 m = list(set(mm))
1310 m = list(set(mm))
1311 r = list(set(dd))
1311 r = list(set(dd))
1312 a = list(set(aa))
1312 a = list(set(aa))
1313 c = [filter(matchfn, l) for l in (m, a, r)]
1313 c = [filter(matchfn, l) for l in (m, a, r)]
1314 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1314 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1315 chunks = patch.diff(repo, patchparent, match=match,
1315 chunks = patch.diff(repo, patchparent, match=match,
1316 changes=c, opts=diffopts)
1316 changes=c, opts=diffopts)
1317 for chunk in chunks:
1317 for chunk in chunks:
1318 patchf.write(chunk)
1318 patchf.write(chunk)
1319
1319
1320 try:
1320 try:
1321 if diffopts.git or diffopts.upgrade:
1321 if diffopts.git or diffopts.upgrade:
1322 copies = {}
1322 copies = {}
1323 for dst in a:
1323 for dst in a:
1324 src = repo.dirstate.copied(dst)
1324 src = repo.dirstate.copied(dst)
1325 # during qfold, the source file for copies may
1325 # during qfold, the source file for copies may
1326 # be removed. Treat this as a simple add.
1326 # be removed. Treat this as a simple add.
1327 if src is not None and src in repo.dirstate:
1327 if src is not None and src in repo.dirstate:
1328 copies.setdefault(src, []).append(dst)
1328 copies.setdefault(src, []).append(dst)
1329 repo.dirstate.add(dst)
1329 repo.dirstate.add(dst)
1330 # remember the copies between patchparent and qtip
1330 # remember the copies between patchparent and qtip
1331 for dst in aaa:
1331 for dst in aaa:
1332 f = repo.file(dst)
1332 f = repo.file(dst)
1333 src = f.renamed(man[dst])
1333 src = f.renamed(man[dst])
1334 if src:
1334 if src:
1335 copies.setdefault(src[0], []).extend(
1335 copies.setdefault(src[0], []).extend(
1336 copies.get(dst, []))
1336 copies.get(dst, []))
1337 if dst in a:
1337 if dst in a:
1338 copies[src[0]].append(dst)
1338 copies[src[0]].append(dst)
1339 # we can't copy a file created by the patch itself
1339 # we can't copy a file created by the patch itself
1340 if dst in copies:
1340 if dst in copies:
1341 del copies[dst]
1341 del copies[dst]
1342 for src, dsts in copies.iteritems():
1342 for src, dsts in copies.iteritems():
1343 for dst in dsts:
1343 for dst in dsts:
1344 repo.dirstate.copy(src, dst)
1344 repo.dirstate.copy(src, dst)
1345 else:
1345 else:
1346 for dst in a:
1346 for dst in a:
1347 repo.dirstate.add(dst)
1347 repo.dirstate.add(dst)
1348 # Drop useless copy information
1348 # Drop useless copy information
1349 for f in list(repo.dirstate.copies()):
1349 for f in list(repo.dirstate.copies()):
1350 repo.dirstate.copy(None, f)
1350 repo.dirstate.copy(None, f)
1351 for f in r:
1351 for f in r:
1352 repo.dirstate.remove(f)
1352 repo.dirstate.remove(f)
1353 # if the patch excludes a modified file, mark that
1353 # if the patch excludes a modified file, mark that
1354 # file with mtime=0 so status can see it.
1354 # file with mtime=0 so status can see it.
1355 mm = []
1355 mm = []
1356 for i in xrange(len(m)-1, -1, -1):
1356 for i in xrange(len(m)-1, -1, -1):
1357 if not matchfn(m[i]):
1357 if not matchfn(m[i]):
1358 mm.append(m[i])
1358 mm.append(m[i])
1359 del m[i]
1359 del m[i]
1360 for f in m:
1360 for f in m:
1361 repo.dirstate.normal(f)
1361 repo.dirstate.normal(f)
1362 for f in mm:
1362 for f in mm:
1363 repo.dirstate.normallookup(f)
1363 repo.dirstate.normallookup(f)
1364 for f in forget:
1364 for f in forget:
1365 repo.dirstate.forget(f)
1365 repo.dirstate.forget(f)
1366
1366
1367 if not msg:
1367 if not msg:
1368 if not ph.message:
1368 if not ph.message:
1369 message = "[mq]: %s\n" % patchfn
1369 message = "[mq]: %s\n" % patchfn
1370 else:
1370 else:
1371 message = "\n".join(ph.message)
1371 message = "\n".join(ph.message)
1372 else:
1372 else:
1373 message = msg
1373 message = msg
1374
1374
1375 user = ph.user or changes[1]
1375 user = ph.user or changes[1]
1376
1376
1377 # assumes strip can roll itself back if interrupted
1377 # assumes strip can roll itself back if interrupted
1378 repo.dirstate.setparents(*cparents)
1378 repo.dirstate.setparents(*cparents)
1379 self.applied.pop()
1379 self.applied.pop()
1380 self.applied_dirty = 1
1380 self.applied_dirty = 1
1381 self.strip(repo, [top], update=False,
1381 self.strip(repo, [top], update=False,
1382 backup='strip')
1382 backup='strip')
1383 except:
1383 except:
1384 repo.dirstate.invalidate()
1384 repo.dirstate.invalidate()
1385 raise
1385 raise
1386
1386
1387 try:
1387 try:
1388 # might be nice to attempt to roll back strip after this
1388 # might be nice to attempt to roll back strip after this
1389 patchf.rename()
1389 patchf.rename()
1390 n = repo.commit(message, user, ph.date, match=match,
1390 n = repo.commit(message, user, ph.date, match=match,
1391 force=True)
1391 force=True)
1392 self.applied.append(statusentry(n, patchfn))
1392 self.applied.append(statusentry(n, patchfn))
1393 except:
1393 except:
1394 ctx = repo[cparents[0]]
1394 ctx = repo[cparents[0]]
1395 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1395 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1396 self.save_dirty()
1396 self.save_dirty()
1397 self.ui.warn(_('refresh interrupted while patch was popped! '
1397 self.ui.warn(_('refresh interrupted while patch was popped! '
1398 '(revert --all, qpush to recover)\n'))
1398 '(revert --all, qpush to recover)\n'))
1399 raise
1399 raise
1400 finally:
1400 finally:
1401 wlock.release()
1401 wlock.release()
1402 self.removeundo(repo)
1402 self.removeundo(repo)
1403
1403
1404 def init(self, repo, create=False):
1404 def init(self, repo, create=False):
1405 if not create and os.path.isdir(self.path):
1405 if not create and os.path.isdir(self.path):
1406 raise util.Abort(_("patch queue directory already exists"))
1406 raise util.Abort(_("patch queue directory already exists"))
1407 try:
1407 try:
1408 os.mkdir(self.path)
1408 os.mkdir(self.path)
1409 except OSError, inst:
1409 except OSError, inst:
1410 if inst.errno != errno.EEXIST or not create:
1410 if inst.errno != errno.EEXIST or not create:
1411 raise
1411 raise
1412 if create:
1412 if create:
1413 return self.qrepo(create=True)
1413 return self.qrepo(create=True)
1414
1414
1415 def unapplied(self, repo, patch=None):
1415 def unapplied(self, repo, patch=None):
1416 if patch and patch not in self.series:
1416 if patch and patch not in self.series:
1417 raise util.Abort(_("patch %s is not in series file") % patch)
1417 raise util.Abort(_("patch %s is not in series file") % patch)
1418 if not patch:
1418 if not patch:
1419 start = self.series_end()
1419 start = self.series_end()
1420 else:
1420 else:
1421 start = self.series.index(patch) + 1
1421 start = self.series.index(patch) + 1
1422 unapplied = []
1422 unapplied = []
1423 for i in xrange(start, len(self.series)):
1423 for i in xrange(start, len(self.series)):
1424 pushable, reason = self.pushable(i)
1424 pushable, reason = self.pushable(i)
1425 if pushable:
1425 if pushable:
1426 unapplied.append((i, self.series[i]))
1426 unapplied.append((i, self.series[i]))
1427 self.explain_pushable(i)
1427 self.explain_pushable(i)
1428 return unapplied
1428 return unapplied
1429
1429
1430 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1430 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1431 summary=False):
1431 summary=False):
1432 def displayname(pfx, patchname, state):
1432 def displayname(pfx, patchname, state):
1433 if pfx:
1433 if pfx:
1434 self.ui.write(pfx)
1434 self.ui.write(pfx)
1435 if summary:
1435 if summary:
1436 ph = patchheader(self.join(patchname), self.plainmode)
1436 ph = patchheader(self.join(patchname), self.plainmode)
1437 msg = ph.message and ph.message[0] or ''
1437 msg = ph.message and ph.message[0] or ''
1438 if self.ui.formatted():
1438 if self.ui.formatted():
1439 width = util.termwidth() - len(pfx) - len(patchname) - 2
1439 width = util.termwidth() - len(pfx) - len(patchname) - 2
1440 if width > 0:
1440 if width > 0:
1441 msg = util.ellipsis(msg, width)
1441 msg = util.ellipsis(msg, width)
1442 else:
1442 else:
1443 msg = ''
1443 msg = ''
1444 self.ui.write(patchname, label='qseries.' + state)
1444 self.ui.write(patchname, label='qseries.' + state)
1445 self.ui.write(': ')
1445 self.ui.write(': ')
1446 self.ui.write(msg, label='qseries.message.' + state)
1446 self.ui.write(msg, label='qseries.message.' + state)
1447 else:
1447 else:
1448 self.ui.write(patchname, label='qseries.' + state)
1448 self.ui.write(patchname, label='qseries.' + state)
1449 self.ui.write('\n')
1449 self.ui.write('\n')
1450
1450
1451 applied = set([p.name for p in self.applied])
1451 applied = set([p.name for p in self.applied])
1452 if length is None:
1452 if length is None:
1453 length = len(self.series) - start
1453 length = len(self.series) - start
1454 if not missing:
1454 if not missing:
1455 if self.ui.verbose:
1455 if self.ui.verbose:
1456 idxwidth = len(str(start + length - 1))
1456 idxwidth = len(str(start + length - 1))
1457 for i in xrange(start, start + length):
1457 for i in xrange(start, start + length):
1458 patch = self.series[i]
1458 patch = self.series[i]
1459 if patch in applied:
1459 if patch in applied:
1460 char, state = 'A', 'applied'
1460 char, state = 'A', 'applied'
1461 elif self.pushable(i)[0]:
1461 elif self.pushable(i)[0]:
1462 char, state = 'U', 'unapplied'
1462 char, state = 'U', 'unapplied'
1463 else:
1463 else:
1464 char, state = 'G', 'guarded'
1464 char, state = 'G', 'guarded'
1465 pfx = ''
1465 pfx = ''
1466 if self.ui.verbose:
1466 if self.ui.verbose:
1467 pfx = '%*d %s ' % (idxwidth, i, char)
1467 pfx = '%*d %s ' % (idxwidth, i, char)
1468 elif status and status != char:
1468 elif status and status != char:
1469 continue
1469 continue
1470 displayname(pfx, patch, state)
1470 displayname(pfx, patch, state)
1471 else:
1471 else:
1472 msng_list = []
1472 msng_list = []
1473 for root, dirs, files in os.walk(self.path):
1473 for root, dirs, files in os.walk(self.path):
1474 d = root[len(self.path) + 1:]
1474 d = root[len(self.path) + 1:]
1475 for f in files:
1475 for f in files:
1476 fl = os.path.join(d, f)
1476 fl = os.path.join(d, f)
1477 if (fl not in self.series and
1477 if (fl not in self.series and
1478 fl not in (self.status_path, self.series_path,
1478 fl not in (self.status_path, self.series_path,
1479 self.guards_path)
1479 self.guards_path)
1480 and not fl.startswith('.')):
1480 and not fl.startswith('.')):
1481 msng_list.append(fl)
1481 msng_list.append(fl)
1482 for x in sorted(msng_list):
1482 for x in sorted(msng_list):
1483 pfx = self.ui.verbose and ('D ') or ''
1483 pfx = self.ui.verbose and ('D ') or ''
1484 displayname(pfx, x, 'missing')
1484 displayname(pfx, x, 'missing')
1485
1485
1486 def issaveline(self, l):
1486 def issaveline(self, l):
1487 if l.name == '.hg.patches.save.line':
1487 if l.name == '.hg.patches.save.line':
1488 return True
1488 return True
1489
1489
1490 def qrepo(self, create=False):
1490 def qrepo(self, create=False):
1491 ui = self.ui.copy()
1491 ui = self.ui.copy()
1492 ui.setconfig('paths', 'default', '', overlay=False)
1492 ui.setconfig('paths', 'default', '', overlay=False)
1493 ui.setconfig('paths', 'default-push', '', overlay=False)
1493 ui.setconfig('paths', 'default-push', '', overlay=False)
1494 if create or os.path.isdir(self.join(".hg")):
1494 if create or os.path.isdir(self.join(".hg")):
1495 return hg.repository(ui, path=self.path, create=create)
1495 return hg.repository(ui, path=self.path, create=create)
1496
1496
1497 def restore(self, repo, rev, delete=None, qupdate=None):
1497 def restore(self, repo, rev, delete=None, qupdate=None):
1498 desc = repo[rev].description().strip()
1498 desc = repo[rev].description().strip()
1499 lines = desc.splitlines()
1499 lines = desc.splitlines()
1500 i = 0
1500 i = 0
1501 datastart = None
1501 datastart = None
1502 series = []
1502 series = []
1503 applied = []
1503 applied = []
1504 qpp = None
1504 qpp = None
1505 for i, line in enumerate(lines):
1505 for i, line in enumerate(lines):
1506 if line == 'Patch Data:':
1506 if line == 'Patch Data:':
1507 datastart = i + 1
1507 datastart = i + 1
1508 elif line.startswith('Dirstate:'):
1508 elif line.startswith('Dirstate:'):
1509 l = line.rstrip()
1509 l = line.rstrip()
1510 l = l[10:].split(' ')
1510 l = l[10:].split(' ')
1511 qpp = [bin(x) for x in l]
1511 qpp = [bin(x) for x in l]
1512 elif datastart != None:
1512 elif datastart != None:
1513 l = line.rstrip()
1513 l = line.rstrip()
1514 n, name = l.split(':', 1)
1514 n, name = l.split(':', 1)
1515 if n:
1515 if n:
1516 applied.append(statusentry(bin(n), name))
1516 applied.append(statusentry(bin(n), name))
1517 else:
1517 else:
1518 series.append(l)
1518 series.append(l)
1519 if datastart is None:
1519 if datastart is None:
1520 self.ui.warn(_("No saved patch data found\n"))
1520 self.ui.warn(_("No saved patch data found\n"))
1521 return 1
1521 return 1
1522 self.ui.warn(_("restoring status: %s\n") % lines[0])
1522 self.ui.warn(_("restoring status: %s\n") % lines[0])
1523 self.full_series = series
1523 self.full_series = series
1524 self.applied = applied
1524 self.applied = applied
1525 self.parse_series()
1525 self.parse_series()
1526 self.series_dirty = 1
1526 self.series_dirty = 1
1527 self.applied_dirty = 1
1527 self.applied_dirty = 1
1528 heads = repo.changelog.heads()
1528 heads = repo.changelog.heads()
1529 if delete:
1529 if delete:
1530 if rev not in heads:
1530 if rev not in heads:
1531 self.ui.warn(_("save entry has children, leaving it alone\n"))
1531 self.ui.warn(_("save entry has children, leaving it alone\n"))
1532 else:
1532 else:
1533 self.ui.warn(_("removing save entry %s\n") % short(rev))
1533 self.ui.warn(_("removing save entry %s\n") % short(rev))
1534 pp = repo.dirstate.parents()
1534 pp = repo.dirstate.parents()
1535 if rev in pp:
1535 if rev in pp:
1536 update = True
1536 update = True
1537 else:
1537 else:
1538 update = False
1538 update = False
1539 self.strip(repo, [rev], update=update, backup='strip')
1539 self.strip(repo, [rev], update=update, backup='strip')
1540 if qpp:
1540 if qpp:
1541 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1541 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1542 (short(qpp[0]), short(qpp[1])))
1542 (short(qpp[0]), short(qpp[1])))
1543 if qupdate:
1543 if qupdate:
1544 self.ui.status(_("queue directory updating\n"))
1544 self.ui.status(_("queue directory updating\n"))
1545 r = self.qrepo()
1545 r = self.qrepo()
1546 if not r:
1546 if not r:
1547 self.ui.warn(_("Unable to load queue repository\n"))
1547 self.ui.warn(_("Unable to load queue repository\n"))
1548 return 1
1548 return 1
1549 hg.clean(r, qpp[0])
1549 hg.clean(r, qpp[0])
1550
1550
1551 def save(self, repo, msg=None):
1551 def save(self, repo, msg=None):
1552 if not self.applied:
1552 if not self.applied:
1553 self.ui.warn(_("save: no patches applied, exiting\n"))
1553 self.ui.warn(_("save: no patches applied, exiting\n"))
1554 return 1
1554 return 1
1555 if self.issaveline(self.applied[-1]):
1555 if self.issaveline(self.applied[-1]):
1556 self.ui.warn(_("status is already saved\n"))
1556 self.ui.warn(_("status is already saved\n"))
1557 return 1
1557 return 1
1558
1558
1559 if not msg:
1559 if not msg:
1560 msg = _("hg patches saved state")
1560 msg = _("hg patches saved state")
1561 else:
1561 else:
1562 msg = "hg patches: " + msg.rstrip('\r\n')
1562 msg = "hg patches: " + msg.rstrip('\r\n')
1563 r = self.qrepo()
1563 r = self.qrepo()
1564 if r:
1564 if r:
1565 pp = r.dirstate.parents()
1565 pp = r.dirstate.parents()
1566 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1566 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1567 msg += "\n\nPatch Data:\n"
1567 msg += "\n\nPatch Data:\n"
1568 msg += ''.join('%s\n' % x for x in self.applied)
1568 msg += ''.join('%s\n' % x for x in self.applied)
1569 msg += ''.join(':%s\n' % x for x in self.full_series)
1569 msg += ''.join(':%s\n' % x for x in self.full_series)
1570 n = repo.commit(msg, force=True)
1570 n = repo.commit(msg, force=True)
1571 if not n:
1571 if not n:
1572 self.ui.warn(_("repo commit failed\n"))
1572 self.ui.warn(_("repo commit failed\n"))
1573 return 1
1573 return 1
1574 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1574 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1575 self.applied_dirty = 1
1575 self.applied_dirty = 1
1576 self.removeundo(repo)
1576 self.removeundo(repo)
1577
1577
1578 def full_series_end(self):
1578 def full_series_end(self):
1579 if self.applied:
1579 if self.applied:
1580 p = self.applied[-1].name
1580 p = self.applied[-1].name
1581 end = self.find_series(p)
1581 end = self.find_series(p)
1582 if end is None:
1582 if end is None:
1583 return len(self.full_series)
1583 return len(self.full_series)
1584 return end + 1
1584 return end + 1
1585 return 0
1585 return 0
1586
1586
1587 def series_end(self, all_patches=False):
1587 def series_end(self, all_patches=False):
1588 """If all_patches is False, return the index of the next pushable patch
1588 """If all_patches is False, return the index of the next pushable patch
1589 in the series, or the series length. If all_patches is True, return the
1589 in the series, or the series length. If all_patches is True, return the
1590 index of the first patch past the last applied one.
1590 index of the first patch past the last applied one.
1591 """
1591 """
1592 end = 0
1592 end = 0
1593 def next(start):
1593 def next(start):
1594 if all_patches or start >= len(self.series):
1594 if all_patches or start >= len(self.series):
1595 return start
1595 return start
1596 for i in xrange(start, len(self.series)):
1596 for i in xrange(start, len(self.series)):
1597 p, reason = self.pushable(i)
1597 p, reason = self.pushable(i)
1598 if p:
1598 if p:
1599 break
1599 break
1600 self.explain_pushable(i)
1600 self.explain_pushable(i)
1601 return i
1601 return i
1602 if self.applied:
1602 if self.applied:
1603 p = self.applied[-1].name
1603 p = self.applied[-1].name
1604 try:
1604 try:
1605 end = self.series.index(p)
1605 end = self.series.index(p)
1606 except ValueError:
1606 except ValueError:
1607 return 0
1607 return 0
1608 return next(end + 1)
1608 return next(end + 1)
1609 return next(end)
1609 return next(end)
1610
1610
1611 def appliedname(self, index):
1611 def appliedname(self, index):
1612 pname = self.applied[index].name
1612 pname = self.applied[index].name
1613 if not self.ui.verbose:
1613 if not self.ui.verbose:
1614 p = pname
1614 p = pname
1615 else:
1615 else:
1616 p = str(self.series.index(pname)) + " " + pname
1616 p = str(self.series.index(pname)) + " " + pname
1617 return p
1617 return p
1618
1618
1619 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1619 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1620 force=None, git=False):
1620 force=None, git=False):
1621 def checkseries(patchname):
1621 def checkseries(patchname):
1622 if patchname in self.series:
1622 if patchname in self.series:
1623 raise util.Abort(_('patch %s is already in the series file')
1623 raise util.Abort(_('patch %s is already in the series file')
1624 % patchname)
1624 % patchname)
1625 def checkfile(patchname):
1625 def checkfile(patchname):
1626 if not force and os.path.exists(self.join(patchname)):
1626 if not force and os.path.exists(self.join(patchname)):
1627 raise util.Abort(_('patch "%s" already exists')
1627 raise util.Abort(_('patch "%s" already exists')
1628 % patchname)
1628 % patchname)
1629
1629
1630 if rev:
1630 if rev:
1631 if files:
1631 if files:
1632 raise util.Abort(_('option "-r" not valid when importing '
1632 raise util.Abort(_('option "-r" not valid when importing '
1633 'files'))
1633 'files'))
1634 rev = cmdutil.revrange(repo, rev)
1634 rev = cmdutil.revrange(repo, rev)
1635 rev.sort(reverse=True)
1635 rev.sort(reverse=True)
1636 if (len(files) > 1 or len(rev) > 1) and patchname:
1636 if (len(files) > 1 or len(rev) > 1) and patchname:
1637 raise util.Abort(_('option "-n" not valid when importing multiple '
1637 raise util.Abort(_('option "-n" not valid when importing multiple '
1638 'patches'))
1638 'patches'))
1639 if rev:
1639 if rev:
1640 # If mq patches are applied, we can only import revisions
1640 # If mq patches are applied, we can only import revisions
1641 # that form a linear path to qbase.
1641 # that form a linear path to qbase.
1642 # Otherwise, they should form a linear path to a head.
1642 # Otherwise, they should form a linear path to a head.
1643 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1643 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1644 if len(heads) > 1:
1644 if len(heads) > 1:
1645 raise util.Abort(_('revision %d is the root of more than one '
1645 raise util.Abort(_('revision %d is the root of more than one '
1646 'branch') % rev[-1])
1646 'branch') % rev[-1])
1647 if self.applied:
1647 if self.applied:
1648 base = repo.changelog.node(rev[0])
1648 base = repo.changelog.node(rev[0])
1649 if base in [n.node for n in self.applied]:
1649 if base in [n.node for n in self.applied]:
1650 raise util.Abort(_('revision %d is already managed')
1650 raise util.Abort(_('revision %d is already managed')
1651 % rev[0])
1651 % rev[0])
1652 if heads != [self.applied[-1].node]:
1652 if heads != [self.applied[-1].node]:
1653 raise util.Abort(_('revision %d is not the parent of '
1653 raise util.Abort(_('revision %d is not the parent of '
1654 'the queue') % rev[0])
1654 'the queue') % rev[0])
1655 base = repo.changelog.rev(self.applied[0].node)
1655 base = repo.changelog.rev(self.applied[0].node)
1656 lastparent = repo.changelog.parentrevs(base)[0]
1656 lastparent = repo.changelog.parentrevs(base)[0]
1657 else:
1657 else:
1658 if heads != [repo.changelog.node(rev[0])]:
1658 if heads != [repo.changelog.node(rev[0])]:
1659 raise util.Abort(_('revision %d has unmanaged children')
1659 raise util.Abort(_('revision %d has unmanaged children')
1660 % rev[0])
1660 % rev[0])
1661 lastparent = None
1661 lastparent = None
1662
1662
1663 diffopts = self.diffopts({'git': git})
1663 diffopts = self.diffopts({'git': git})
1664 for r in rev:
1664 for r in rev:
1665 p1, p2 = repo.changelog.parentrevs(r)
1665 p1, p2 = repo.changelog.parentrevs(r)
1666 n = repo.changelog.node(r)
1666 n = repo.changelog.node(r)
1667 if p2 != nullrev:
1667 if p2 != nullrev:
1668 raise util.Abort(_('cannot import merge revision %d') % r)
1668 raise util.Abort(_('cannot import merge revision %d') % r)
1669 if lastparent and lastparent != r:
1669 if lastparent and lastparent != r:
1670 raise util.Abort(_('revision %d is not the parent of %d')
1670 raise util.Abort(_('revision %d is not the parent of %d')
1671 % (r, lastparent))
1671 % (r, lastparent))
1672 lastparent = p1
1672 lastparent = p1
1673
1673
1674 if not patchname:
1674 if not patchname:
1675 patchname = normname('%d.diff' % r)
1675 patchname = normname('%d.diff' % r)
1676 self.check_reserved_name(patchname)
1676 self.check_reserved_name(patchname)
1677 checkseries(patchname)
1677 checkseries(patchname)
1678 checkfile(patchname)
1678 checkfile(patchname)
1679 self.full_series.insert(0, patchname)
1679 self.full_series.insert(0, patchname)
1680
1680
1681 patchf = self.opener(patchname, "w")
1681 patchf = self.opener(patchname, "w")
1682 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1682 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1683 patchf.close()
1683 patchf.close()
1684
1684
1685 se = statusentry(n, patchname)
1685 se = statusentry(n, patchname)
1686 self.applied.insert(0, se)
1686 self.applied.insert(0, se)
1687
1687
1688 self.added.append(patchname)
1688 self.added.append(patchname)
1689 patchname = None
1689 patchname = None
1690 self.parse_series()
1690 self.parse_series()
1691 self.applied_dirty = 1
1691 self.applied_dirty = 1
1692 self.series_dirty = True
1692 self.series_dirty = True
1693
1693
1694 for i, filename in enumerate(files):
1694 for i, filename in enumerate(files):
1695 if existing:
1695 if existing:
1696 if filename == '-':
1696 if filename == '-':
1697 raise util.Abort(_('-e is incompatible with import from -'))
1697 raise util.Abort(_('-e is incompatible with import from -'))
1698 filename = normname(filename)
1698 filename = normname(filename)
1699 self.check_reserved_name(filename)
1699 self.check_reserved_name(filename)
1700 originpath = self.join(filename)
1700 originpath = self.join(filename)
1701 if not os.path.isfile(originpath):
1701 if not os.path.isfile(originpath):
1702 raise util.Abort(_("patch %s does not exist") % filename)
1702 raise util.Abort(_("patch %s does not exist") % filename)
1703
1703
1704 if patchname:
1704 if patchname:
1705 self.check_reserved_name(patchname)
1705 self.check_reserved_name(patchname)
1706 checkfile(patchname)
1706 checkfile(patchname)
1707
1707
1708 self.ui.write(_('renaming %s to %s\n')
1708 self.ui.write(_('renaming %s to %s\n')
1709 % (filename, patchname))
1709 % (filename, patchname))
1710 util.rename(originpath, self.join(patchname))
1710 util.rename(originpath, self.join(patchname))
1711 else:
1711 else:
1712 patchname = filename
1712 patchname = filename
1713
1713
1714 else:
1714 else:
1715 try:
1715 try:
1716 if filename == '-':
1716 if filename == '-':
1717 if not patchname:
1717 if not patchname:
1718 raise util.Abort(
1718 raise util.Abort(
1719 _('need --name to import a patch from -'))
1719 _('need --name to import a patch from -'))
1720 text = sys.stdin.read()
1720 text = sys.stdin.read()
1721 else:
1721 else:
1722 text = url.open(self.ui, filename).read()
1722 text = url.open(self.ui, filename).read()
1723 except (OSError, IOError):
1723 except (OSError, IOError):
1724 raise util.Abort(_("unable to read file %s") % filename)
1724 raise util.Abort(_("unable to read file %s") % filename)
1725 if not patchname:
1725 if not patchname:
1726 patchname = normname(os.path.basename(filename))
1726 patchname = normname(os.path.basename(filename))
1727 self.check_reserved_name(patchname)
1727 self.check_reserved_name(patchname)
1728 checkfile(patchname)
1728 checkfile(patchname)
1729 patchf = self.opener(patchname, "w")
1729 patchf = self.opener(patchname, "w")
1730 patchf.write(text)
1730 patchf.write(text)
1731 if not force:
1731 if not force:
1732 checkseries(patchname)
1732 checkseries(patchname)
1733 if patchname not in self.series:
1733 if patchname not in self.series:
1734 index = self.full_series_end() + i
1734 index = self.full_series_end() + i
1735 self.full_series[index:index] = [patchname]
1735 self.full_series[index:index] = [patchname]
1736 self.parse_series()
1736 self.parse_series()
1737 self.series_dirty = True
1737 self.series_dirty = True
1738 self.ui.warn(_("adding %s to series file\n") % patchname)
1738 self.ui.warn(_("adding %s to series file\n") % patchname)
1739 self.added.append(patchname)
1739 self.added.append(patchname)
1740 patchname = None
1740 patchname = None
1741
1741
1742 def delete(ui, repo, *patches, **opts):
1742 def delete(ui, repo, *patches, **opts):
1743 """remove patches from queue
1743 """remove patches from queue
1744
1744
1745 The patches must not be applied, and at least one patch is required. With
1745 The patches must not be applied, and at least one patch is required. With
1746 -k/--keep, the patch files are preserved in the patch directory.
1746 -k/--keep, the patch files are preserved in the patch directory.
1747
1747
1748 To stop managing a patch and move it into permanent history,
1748 To stop managing a patch and move it into permanent history,
1749 use the :hg:`qfinish` command."""
1749 use the :hg:`qfinish` command."""
1750 q = repo.mq
1750 q = repo.mq
1751 q.delete(repo, patches, opts)
1751 q.delete(repo, patches, opts)
1752 q.save_dirty()
1752 q.save_dirty()
1753 return 0
1753 return 0
1754
1754
1755 def applied(ui, repo, patch=None, **opts):
1755 def applied(ui, repo, patch=None, **opts):
1756 """print the patches already applied"""
1756 """print the patches already applied"""
1757
1757
1758 q = repo.mq
1758 q = repo.mq
1759
1759
1760 if patch:
1760 if patch:
1761 if patch not in q.series:
1761 if patch not in q.series:
1762 raise util.Abort(_("patch %s is not in series file") % patch)
1762 raise util.Abort(_("patch %s is not in series file") % patch)
1763 end = q.series.index(patch) + 1
1763 end = q.series.index(patch) + 1
1764 else:
1764 else:
1765 end = q.series_end(True)
1765 end = q.series_end(True)
1766
1766
1767 if opts.get('last') and not end:
1767 if opts.get('last') and not end:
1768 ui.write(_("no patches applied\n"))
1768 ui.write(_("no patches applied\n"))
1769 return 1
1769 return 1
1770 elif opts.get('last') and end == 1:
1770 elif opts.get('last') and end == 1:
1771 ui.write(_("only one patch applied\n"))
1771 ui.write(_("only one patch applied\n"))
1772 return 1
1772 return 1
1773 elif opts.get('last'):
1773 elif opts.get('last'):
1774 start = end - 2
1774 start = end - 2
1775 end = 1
1775 end = 1
1776 else:
1776 else:
1777 start = 0
1777 start = 0
1778
1778
1779 return q.qseries(repo, length=end, start=start, status='A',
1779 return q.qseries(repo, length=end, start=start, status='A',
1780 summary=opts.get('summary'))
1780 summary=opts.get('summary'))
1781
1781
1782 def unapplied(ui, repo, patch=None, **opts):
1782 def unapplied(ui, repo, patch=None, **opts):
1783 """print the patches not yet applied"""
1783 """print the patches not yet applied"""
1784
1784
1785 q = repo.mq
1785 q = repo.mq
1786 if patch:
1786 if patch:
1787 if patch not in q.series:
1787 if patch not in q.series:
1788 raise util.Abort(_("patch %s is not in series file") % patch)
1788 raise util.Abort(_("patch %s is not in series file") % patch)
1789 start = q.series.index(patch) + 1
1789 start = q.series.index(patch) + 1
1790 else:
1790 else:
1791 start = q.series_end(True)
1791 start = q.series_end(True)
1792
1792
1793 if start == len(q.series) and opts.get('first'):
1793 if start == len(q.series) and opts.get('first'):
1794 ui.write(_("all patches applied\n"))
1794 ui.write(_("all patches applied\n"))
1795 return 1
1795 return 1
1796
1796
1797 length = opts.get('first') and 1 or None
1797 length = opts.get('first') and 1 or None
1798 return q.qseries(repo, start=start, length=length, status='U',
1798 return q.qseries(repo, start=start, length=length, status='U',
1799 summary=opts.get('summary'))
1799 summary=opts.get('summary'))
1800
1800
1801 def qimport(ui, repo, *filename, **opts):
1801 def qimport(ui, repo, *filename, **opts):
1802 """import a patch
1802 """import a patch
1803
1803
1804 The patch is inserted into the series after the last applied
1804 The patch is inserted into the series after the last applied
1805 patch. If no patches have been applied, qimport prepends the patch
1805 patch. If no patches have been applied, qimport prepends the patch
1806 to the series.
1806 to the series.
1807
1807
1808 The patch will have the same name as its source file unless you
1808 The patch will have the same name as its source file unless you
1809 give it a new one with -n/--name.
1809 give it a new one with -n/--name.
1810
1810
1811 You can register an existing patch inside the patch directory with
1811 You can register an existing patch inside the patch directory with
1812 the -e/--existing flag.
1812 the -e/--existing flag.
1813
1813
1814 With -f/--force, an existing patch of the same name will be
1814 With -f/--force, an existing patch of the same name will be
1815 overwritten.
1815 overwritten.
1816
1816
1817 An existing changeset may be placed under mq control with -r/--rev
1817 An existing changeset may be placed under mq control with -r/--rev
1818 (e.g. qimport --rev tip -n patch will place tip under mq control).
1818 (e.g. qimport --rev tip -n patch will place tip under mq control).
1819 With -g/--git, patches imported with --rev will use the git diff
1819 With -g/--git, patches imported with --rev will use the git diff
1820 format. See the diffs help topic for information on why this is
1820 format. See the diffs help topic for information on why this is
1821 important for preserving rename/copy information and permission
1821 important for preserving rename/copy information and permission
1822 changes.
1822 changes.
1823
1823
1824 To import a patch from standard input, pass - as the patch file.
1824 To import a patch from standard input, pass - as the patch file.
1825 When importing from standard input, a patch name must be specified
1825 When importing from standard input, a patch name must be specified
1826 using the --name flag.
1826 using the --name flag.
1827
1827
1828 To import an existing patch while renaming it::
1828 To import an existing patch while renaming it::
1829
1829
1830 hg qimport -e existing-patch -n new-name
1830 hg qimport -e existing-patch -n new-name
1831 """
1831 """
1832 q = repo.mq
1832 q = repo.mq
1833 try:
1833 try:
1834 q.qimport(repo, filename, patchname=opts['name'],
1834 q.qimport(repo, filename, patchname=opts['name'],
1835 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1835 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1836 git=opts['git'])
1836 git=opts['git'])
1837 finally:
1837 finally:
1838 q.save_dirty()
1838 q.save_dirty()
1839
1839
1840 if opts.get('push') and not opts.get('rev'):
1840 if opts.get('push') and not opts.get('rev'):
1841 return q.push(repo, None)
1841 return q.push(repo, None)
1842 return 0
1842 return 0
1843
1843
1844 def qinit(ui, repo, create):
1844 def qinit(ui, repo, create):
1845 """initialize a new queue repository
1845 """initialize a new queue repository
1846
1846
1847 This command also creates a series file for ordering patches, and
1847 This command also creates a series file for ordering patches, and
1848 an mq-specific .hgignore file in the queue repository, to exclude
1848 an mq-specific .hgignore file in the queue repository, to exclude
1849 the status and guards files (these contain mostly transient state)."""
1849 the status and guards files (these contain mostly transient state)."""
1850 q = repo.mq
1850 q = repo.mq
1851 r = q.init(repo, create)
1851 r = q.init(repo, create)
1852 q.save_dirty()
1852 q.save_dirty()
1853 if r:
1853 if r:
1854 if not os.path.exists(r.wjoin('.hgignore')):
1854 if not os.path.exists(r.wjoin('.hgignore')):
1855 fp = r.wopener('.hgignore', 'w')
1855 fp = r.wopener('.hgignore', 'w')
1856 fp.write('^\\.hg\n')
1856 fp.write('^\\.hg\n')
1857 fp.write('^\\.mq\n')
1857 fp.write('^\\.mq\n')
1858 fp.write('syntax: glob\n')
1858 fp.write('syntax: glob\n')
1859 fp.write('status\n')
1859 fp.write('status\n')
1860 fp.write('guards\n')
1860 fp.write('guards\n')
1861 fp.close()
1861 fp.close()
1862 if not os.path.exists(r.wjoin('series')):
1862 if not os.path.exists(r.wjoin('series')):
1863 r.wopener('series', 'w').close()
1863 r.wopener('series', 'w').close()
1864 r[None].add(['.hgignore', 'series'])
1864 r[None].add(['.hgignore', 'series'])
1865 commands.add(ui, r)
1865 commands.add(ui, r)
1866 return 0
1866 return 0
1867
1867
1868 def init(ui, repo, **opts):
1868 def init(ui, repo, **opts):
1869 """init a new queue repository (DEPRECATED)
1869 """init a new queue repository (DEPRECATED)
1870
1870
1871 The queue repository is unversioned by default. If
1871 The queue repository is unversioned by default. If
1872 -c/--create-repo is specified, qinit will create a separate nested
1872 -c/--create-repo is specified, qinit will create a separate nested
1873 repository for patches (qinit -c may also be run later to convert
1873 repository for patches (qinit -c may also be run later to convert
1874 an unversioned patch repository into a versioned one). You can use
1874 an unversioned patch repository into a versioned one). You can use
1875 qcommit to commit changes to this queue repository.
1875 qcommit to commit changes to this queue repository.
1876
1876
1877 This command is deprecated. Without -c, it's implied by other relevant
1877 This command is deprecated. Without -c, it's implied by other relevant
1878 commands. With -c, use :hg:`init --mq` instead."""
1878 commands. With -c, use :hg:`init --mq` instead."""
1879 return qinit(ui, repo, create=opts['create_repo'])
1879 return qinit(ui, repo, create=opts['create_repo'])
1880
1880
1881 def clone(ui, source, dest=None, **opts):
1881 def clone(ui, source, dest=None, **opts):
1882 '''clone main and patch repository at same time
1882 '''clone main and patch repository at same time
1883
1883
1884 If source is local, destination will have no patches applied. If
1884 If source is local, destination will have no patches applied. If
1885 source is remote, this command can not check if patches are
1885 source is remote, this command can not check if patches are
1886 applied in source, so cannot guarantee that patches are not
1886 applied in source, so cannot guarantee that patches are not
1887 applied in destination. If you clone remote repository, be sure
1887 applied in destination. If you clone remote repository, be sure
1888 before that it has no patches applied.
1888 before that it has no patches applied.
1889
1889
1890 Source patch repository is looked for in <src>/.hg/patches by
1890 Source patch repository is looked for in <src>/.hg/patches by
1891 default. Use -p <url> to change.
1891 default. Use -p <url> to change.
1892
1892
1893 The patch directory must be a nested Mercurial repository, as
1893 The patch directory must be a nested Mercurial repository, as
1894 would be created by :hg:`init --mq`.
1894 would be created by :hg:`init --mq`.
1895 '''
1895 '''
1896 def patchdir(repo):
1896 def patchdir(repo):
1897 url = repo.url()
1897 url = repo.url()
1898 if url.endswith('/'):
1898 if url.endswith('/'):
1899 url = url[:-1]
1899 url = url[:-1]
1900 return url + '/.hg/patches'
1900 return url + '/.hg/patches'
1901 if dest is None:
1901 if dest is None:
1902 dest = hg.defaultdest(source)
1902 dest = hg.defaultdest(source)
1903 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1903 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1904 if opts['patches']:
1904 if opts['patches']:
1905 patchespath = ui.expandpath(opts['patches'])
1905 patchespath = ui.expandpath(opts['patches'])
1906 else:
1906 else:
1907 patchespath = patchdir(sr)
1907 patchespath = patchdir(sr)
1908 try:
1908 try:
1909 hg.repository(ui, patchespath)
1909 hg.repository(ui, patchespath)
1910 except error.RepoError:
1910 except error.RepoError:
1911 raise util.Abort(_('versioned patch repository not found'
1911 raise util.Abort(_('versioned patch repository not found'
1912 ' (see init --mq)'))
1912 ' (see init --mq)'))
1913 qbase, destrev = None, None
1913 qbase, destrev = None, None
1914 if sr.local():
1914 if sr.local():
1915 if sr.mq.applied:
1915 if sr.mq.applied:
1916 qbase = sr.mq.applied[0].node
1916 qbase = sr.mq.applied[0].node
1917 if not hg.islocal(dest):
1917 if not hg.islocal(dest):
1918 heads = set(sr.heads())
1918 heads = set(sr.heads())
1919 destrev = list(heads.difference(sr.heads(qbase)))
1919 destrev = list(heads.difference(sr.heads(qbase)))
1920 destrev.append(sr.changelog.parents(qbase)[0])
1920 destrev.append(sr.changelog.parents(qbase)[0])
1921 elif sr.capable('lookup'):
1921 elif sr.capable('lookup'):
1922 try:
1922 try:
1923 qbase = sr.lookup('qbase')
1923 qbase = sr.lookup('qbase')
1924 except error.RepoError:
1924 except error.RepoError:
1925 pass
1925 pass
1926 ui.note(_('cloning main repository\n'))
1926 ui.note(_('cloning main repository\n'))
1927 sr, dr = hg.clone(ui, sr.url(), dest,
1927 sr, dr = hg.clone(ui, sr.url(), dest,
1928 pull=opts['pull'],
1928 pull=opts['pull'],
1929 rev=destrev,
1929 rev=destrev,
1930 update=False,
1930 update=False,
1931 stream=opts['uncompressed'])
1931 stream=opts['uncompressed'])
1932 ui.note(_('cloning patch repository\n'))
1932 ui.note(_('cloning patch repository\n'))
1933 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1933 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1934 pull=opts['pull'], update=not opts['noupdate'],
1934 pull=opts['pull'], update=not opts['noupdate'],
1935 stream=opts['uncompressed'])
1935 stream=opts['uncompressed'])
1936 if dr.local():
1936 if dr.local():
1937 if qbase:
1937 if qbase:
1938 ui.note(_('stripping applied patches from destination '
1938 ui.note(_('stripping applied patches from destination '
1939 'repository\n'))
1939 'repository\n'))
1940 dr.mq.strip(dr, [qbase], update=False, backup=None)
1940 dr.mq.strip(dr, [qbase], update=False, backup=None)
1941 if not opts['noupdate']:
1941 if not opts['noupdate']:
1942 ui.note(_('updating destination repository\n'))
1942 ui.note(_('updating destination repository\n'))
1943 hg.update(dr, dr.changelog.tip())
1943 hg.update(dr, dr.changelog.tip())
1944
1944
1945 def commit(ui, repo, *pats, **opts):
1945 def commit(ui, repo, *pats, **opts):
1946 """commit changes in the queue repository (DEPRECATED)
1946 """commit changes in the queue repository (DEPRECATED)
1947
1947
1948 This command is deprecated; use :hg:`commit --mq` instead."""
1948 This command is deprecated; use :hg:`commit --mq` instead."""
1949 q = repo.mq
1949 q = repo.mq
1950 r = q.qrepo()
1950 r = q.qrepo()
1951 if not r:
1951 if not r:
1952 raise util.Abort('no queue repository')
1952 raise util.Abort('no queue repository')
1953 commands.commit(r.ui, r, *pats, **opts)
1953 commands.commit(r.ui, r, *pats, **opts)
1954
1954
1955 def series(ui, repo, **opts):
1955 def series(ui, repo, **opts):
1956 """print the entire series file"""
1956 """print the entire series file"""
1957 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1957 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1958 return 0
1958 return 0
1959
1959
1960 def top(ui, repo, **opts):
1960 def top(ui, repo, **opts):
1961 """print the name of the current patch"""
1961 """print the name of the current patch"""
1962 q = repo.mq
1962 q = repo.mq
1963 t = q.applied and q.series_end(True) or 0
1963 t = q.applied and q.series_end(True) or 0
1964 if t:
1964 if t:
1965 return q.qseries(repo, start=t - 1, length=1, status='A',
1965 return q.qseries(repo, start=t - 1, length=1, status='A',
1966 summary=opts.get('summary'))
1966 summary=opts.get('summary'))
1967 else:
1967 else:
1968 ui.write(_("no patches applied\n"))
1968 ui.write(_("no patches applied\n"))
1969 return 1
1969 return 1
1970
1970
1971 def next(ui, repo, **opts):
1971 def next(ui, repo, **opts):
1972 """print the name of the next patch"""
1972 """print the name of the next patch"""
1973 q = repo.mq
1973 q = repo.mq
1974 end = q.series_end()
1974 end = q.series_end()
1975 if end == len(q.series):
1975 if end == len(q.series):
1976 ui.write(_("all patches applied\n"))
1976 ui.write(_("all patches applied\n"))
1977 return 1
1977 return 1
1978 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1978 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1979
1979
1980 def prev(ui, repo, **opts):
1980 def prev(ui, repo, **opts):
1981 """print the name of the previous patch"""
1981 """print the name of the previous patch"""
1982 q = repo.mq
1982 q = repo.mq
1983 l = len(q.applied)
1983 l = len(q.applied)
1984 if l == 1:
1984 if l == 1:
1985 ui.write(_("only one patch applied\n"))
1985 ui.write(_("only one patch applied\n"))
1986 return 1
1986 return 1
1987 if not l:
1987 if not l:
1988 ui.write(_("no patches applied\n"))
1988 ui.write(_("no patches applied\n"))
1989 return 1
1989 return 1
1990 return q.qseries(repo, start=l - 2, length=1, status='A',
1990 return q.qseries(repo, start=l - 2, length=1, status='A',
1991 summary=opts.get('summary'))
1991 summary=opts.get('summary'))
1992
1992
1993 def setupheaderopts(ui, opts):
1993 def setupheaderopts(ui, opts):
1994 if not opts.get('user') and opts.get('currentuser'):
1994 if not opts.get('user') and opts.get('currentuser'):
1995 opts['user'] = ui.username()
1995 opts['user'] = ui.username()
1996 if not opts.get('date') and opts.get('currentdate'):
1996 if not opts.get('date') and opts.get('currentdate'):
1997 opts['date'] = "%d %d" % util.makedate()
1997 opts['date'] = "%d %d" % util.makedate()
1998
1998
1999 def new(ui, repo, patch, *args, **opts):
1999 def new(ui, repo, patch, *args, **opts):
2000 """create a new patch
2000 """create a new patch
2001
2001
2002 qnew creates a new patch on top of the currently-applied patch (if
2002 qnew creates a new patch on top of the currently-applied patch (if
2003 any). The patch will be initialized with any outstanding changes
2003 any). The patch will be initialized with any outstanding changes
2004 in the working directory. You may also use -I/--include,
2004 in the working directory. You may also use -I/--include,
2005 -X/--exclude, and/or a list of files after the patch name to add
2005 -X/--exclude, and/or a list of files after the patch name to add
2006 only changes to matching files to the new patch, leaving the rest
2006 only changes to matching files to the new patch, leaving the rest
2007 as uncommitted modifications.
2007 as uncommitted modifications.
2008
2008
2009 -u/--user and -d/--date can be used to set the (given) user and
2009 -u/--user and -d/--date can be used to set the (given) user and
2010 date, respectively. -U/--currentuser and -D/--currentdate set user
2010 date, respectively. -U/--currentuser and -D/--currentdate set user
2011 to current user and date to current date.
2011 to current user and date to current date.
2012
2012
2013 -e/--edit, -m/--message or -l/--logfile set the patch header as
2013 -e/--edit, -m/--message or -l/--logfile set the patch header as
2014 well as the commit message. If none is specified, the header is
2014 well as the commit message. If none is specified, the header is
2015 empty and the commit message is '[mq]: PATCH'.
2015 empty and the commit message is '[mq]: PATCH'.
2016
2016
2017 Use the -g/--git option to keep the patch in the git extended diff
2017 Use the -g/--git option to keep the patch in the git extended diff
2018 format. Read the diffs help topic for more information on why this
2018 format. Read the diffs help topic for more information on why this
2019 is important for preserving permission changes and copy/rename
2019 is important for preserving permission changes and copy/rename
2020 information.
2020 information.
2021 """
2021 """
2022 msg = cmdutil.logmessage(opts)
2022 msg = cmdutil.logmessage(opts)
2023 def getmsg():
2023 def getmsg():
2024 return ui.edit(msg, opts['user'] or ui.username())
2024 return ui.edit(msg, opts['user'] or ui.username())
2025 q = repo.mq
2025 q = repo.mq
2026 opts['msg'] = msg
2026 opts['msg'] = msg
2027 if opts.get('edit'):
2027 if opts.get('edit'):
2028 opts['msg'] = getmsg
2028 opts['msg'] = getmsg
2029 else:
2029 else:
2030 opts['msg'] = msg
2030 opts['msg'] = msg
2031 setupheaderopts(ui, opts)
2031 setupheaderopts(ui, opts)
2032 q.new(repo, patch, *args, **opts)
2032 q.new(repo, patch, *args, **opts)
2033 q.save_dirty()
2033 q.save_dirty()
2034 return 0
2034 return 0
2035
2035
2036 def refresh(ui, repo, *pats, **opts):
2036 def refresh(ui, repo, *pats, **opts):
2037 """update the current patch
2037 """update the current patch
2038
2038
2039 If any file patterns are provided, the refreshed patch will
2039 If any file patterns are provided, the refreshed patch will
2040 contain only the modifications that match those patterns; the
2040 contain only the modifications that match those patterns; the
2041 remaining modifications will remain in the working directory.
2041 remaining modifications will remain in the working directory.
2042
2042
2043 If -s/--short is specified, files currently included in the patch
2043 If -s/--short is specified, files currently included in the patch
2044 will be refreshed just like matched files and remain in the patch.
2044 will be refreshed just like matched files and remain in the patch.
2045
2045
2046 If -e/--edit is specified, Mercurial will start your configured editor for
2046 If -e/--edit is specified, Mercurial will start your configured editor for
2047 you to enter a message. In case qrefresh fails, you will find a backup of
2047 you to enter a message. In case qrefresh fails, you will find a backup of
2048 your message in ``.hg/last-message.txt``.
2048 your message in ``.hg/last-message.txt``.
2049
2049
2050 hg add/remove/copy/rename work as usual, though you might want to
2050 hg add/remove/copy/rename work as usual, though you might want to
2051 use git-style patches (-g/--git or [diff] git=1) to track copies
2051 use git-style patches (-g/--git or [diff] git=1) to track copies
2052 and renames. See the diffs help topic for more information on the
2052 and renames. See the diffs help topic for more information on the
2053 git diff format.
2053 git diff format.
2054 """
2054 """
2055 q = repo.mq
2055 q = repo.mq
2056 message = cmdutil.logmessage(opts)
2056 message = cmdutil.logmessage(opts)
2057 if opts['edit']:
2057 if opts['edit']:
2058 if not q.applied:
2058 if not q.applied:
2059 ui.write(_("no patches applied\n"))
2059 ui.write(_("no patches applied\n"))
2060 return 1
2060 return 1
2061 if message:
2061 if message:
2062 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2062 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2063 patch = q.applied[-1].name
2063 patch = q.applied[-1].name
2064 ph = patchheader(q.join(patch), q.plainmode)
2064 ph = patchheader(q.join(patch), q.plainmode)
2065 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2065 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2066 # We don't want to lose the patch message if qrefresh fails (issue2062)
2066 # We don't want to lose the patch message if qrefresh fails (issue2062)
2067 msgfile = repo.opener('last-message.txt', 'wb')
2067 msgfile = repo.opener('last-message.txt', 'wb')
2068 msgfile.write(message)
2068 msgfile.write(message)
2069 msgfile.close()
2069 msgfile.close()
2070 setupheaderopts(ui, opts)
2070 setupheaderopts(ui, opts)
2071 ret = q.refresh(repo, pats, msg=message, **opts)
2071 ret = q.refresh(repo, pats, msg=message, **opts)
2072 q.save_dirty()
2072 q.save_dirty()
2073 return ret
2073 return ret
2074
2074
2075 def diff(ui, repo, *pats, **opts):
2075 def diff(ui, repo, *pats, **opts):
2076 """diff of the current patch and subsequent modifications
2076 """diff of the current patch and subsequent modifications
2077
2077
2078 Shows a diff which includes the current patch as well as any
2078 Shows a diff which includes the current patch as well as any
2079 changes which have been made in the working directory since the
2079 changes which have been made in the working directory since the
2080 last refresh (thus showing what the current patch would become
2080 last refresh (thus showing what the current patch would become
2081 after a qrefresh).
2081 after a qrefresh).
2082
2082
2083 Use :hg:`diff` if you only want to see the changes made since the
2083 Use :hg:`diff` if you only want to see the changes made since the
2084 last qrefresh, or :hg:`export qtip` if you want to see changes
2084 last qrefresh, or :hg:`export qtip` if you want to see changes
2085 made by the current patch without including changes made since the
2085 made by the current patch without including changes made since the
2086 qrefresh.
2086 qrefresh.
2087 """
2087 """
2088 repo.mq.diff(repo, pats, opts)
2088 repo.mq.diff(repo, pats, opts)
2089 return 0
2089 return 0
2090
2090
2091 def fold(ui, repo, *files, **opts):
2091 def fold(ui, repo, *files, **opts):
2092 """fold the named patches into the current patch
2092 """fold the named patches into the current patch
2093
2093
2094 Patches must not yet be applied. Each patch will be successively
2094 Patches must not yet be applied. Each patch will be successively
2095 applied to the current patch in the order given. If all the
2095 applied to the current patch in the order given. If all the
2096 patches apply successfully, the current patch will be refreshed
2096 patches apply successfully, the current patch will be refreshed
2097 with the new cumulative patch, and the folded patches will be
2097 with the new cumulative patch, and the folded patches will be
2098 deleted. With -k/--keep, the folded patch files will not be
2098 deleted. With -k/--keep, the folded patch files will not be
2099 removed afterwards.
2099 removed afterwards.
2100
2100
2101 The header for each folded patch will be concatenated with the
2101 The header for each folded patch will be concatenated with the
2102 current patch header, separated by a line of '* * *'."""
2102 current patch header, separated by a line of '* * *'."""
2103
2103
2104 q = repo.mq
2104 q = repo.mq
2105
2105
2106 if not files:
2106 if not files:
2107 raise util.Abort(_('qfold requires at least one patch name'))
2107 raise util.Abort(_('qfold requires at least one patch name'))
2108 if not q.check_toppatch(repo)[0]:
2108 if not q.check_toppatch(repo)[0]:
2109 raise util.Abort(_('no patches applied'))
2109 raise util.Abort(_('no patches applied'))
2110 q.check_localchanges(repo)
2110 q.check_localchanges(repo)
2111
2111
2112 message = cmdutil.logmessage(opts)
2112 message = cmdutil.logmessage(opts)
2113 if opts['edit']:
2113 if opts['edit']:
2114 if message:
2114 if message:
2115 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2115 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2116
2116
2117 parent = q.lookup('qtip')
2117 parent = q.lookup('qtip')
2118 patches = []
2118 patches = []
2119 messages = []
2119 messages = []
2120 for f in files:
2120 for f in files:
2121 p = q.lookup(f)
2121 p = q.lookup(f)
2122 if p in patches or p == parent:
2122 if p in patches or p == parent:
2123 ui.warn(_('Skipping already folded patch %s\n') % p)
2123 ui.warn(_('Skipping already folded patch %s\n') % p)
2124 if q.isapplied(p):
2124 if q.isapplied(p):
2125 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2125 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2126 patches.append(p)
2126 patches.append(p)
2127
2127
2128 for p in patches:
2128 for p in patches:
2129 if not message:
2129 if not message:
2130 ph = patchheader(q.join(p), q.plainmode)
2130 ph = patchheader(q.join(p), q.plainmode)
2131 if ph.message:
2131 if ph.message:
2132 messages.append(ph.message)
2132 messages.append(ph.message)
2133 pf = q.join(p)
2133 pf = q.join(p)
2134 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2134 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2135 if not patchsuccess:
2135 if not patchsuccess:
2136 raise util.Abort(_('error folding patch %s') % p)
2136 raise util.Abort(_('error folding patch %s') % p)
2137 patch.updatedir(ui, repo, files)
2137 cmdutil.updatedir(ui, repo, files)
2138
2138
2139 if not message:
2139 if not message:
2140 ph = patchheader(q.join(parent), q.plainmode)
2140 ph = patchheader(q.join(parent), q.plainmode)
2141 message, user = ph.message, ph.user
2141 message, user = ph.message, ph.user
2142 for msg in messages:
2142 for msg in messages:
2143 message.append('* * *')
2143 message.append('* * *')
2144 message.extend(msg)
2144 message.extend(msg)
2145 message = '\n'.join(message)
2145 message = '\n'.join(message)
2146
2146
2147 if opts['edit']:
2147 if opts['edit']:
2148 message = ui.edit(message, user or ui.username())
2148 message = ui.edit(message, user or ui.username())
2149
2149
2150 diffopts = q.patchopts(q.diffopts(), *patches)
2150 diffopts = q.patchopts(q.diffopts(), *patches)
2151 q.refresh(repo, msg=message, git=diffopts.git)
2151 q.refresh(repo, msg=message, git=diffopts.git)
2152 q.delete(repo, patches, opts)
2152 q.delete(repo, patches, opts)
2153 q.save_dirty()
2153 q.save_dirty()
2154
2154
2155 def goto(ui, repo, patch, **opts):
2155 def goto(ui, repo, patch, **opts):
2156 '''push or pop patches until named patch is at top of stack'''
2156 '''push or pop patches until named patch is at top of stack'''
2157 q = repo.mq
2157 q = repo.mq
2158 patch = q.lookup(patch)
2158 patch = q.lookup(patch)
2159 if q.isapplied(patch):
2159 if q.isapplied(patch):
2160 ret = q.pop(repo, patch, force=opts['force'])
2160 ret = q.pop(repo, patch, force=opts['force'])
2161 else:
2161 else:
2162 ret = q.push(repo, patch, force=opts['force'])
2162 ret = q.push(repo, patch, force=opts['force'])
2163 q.save_dirty()
2163 q.save_dirty()
2164 return ret
2164 return ret
2165
2165
2166 def guard(ui, repo, *args, **opts):
2166 def guard(ui, repo, *args, **opts):
2167 '''set or print guards for a patch
2167 '''set or print guards for a patch
2168
2168
2169 Guards control whether a patch can be pushed. A patch with no
2169 Guards control whether a patch can be pushed. A patch with no
2170 guards is always pushed. A patch with a positive guard ("+foo") is
2170 guards is always pushed. A patch with a positive guard ("+foo") is
2171 pushed only if the :hg:`qselect` command has activated it. A patch with
2171 pushed only if the :hg:`qselect` command has activated it. A patch with
2172 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2172 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2173 has activated it.
2173 has activated it.
2174
2174
2175 With no arguments, print the currently active guards.
2175 With no arguments, print the currently active guards.
2176 With arguments, set guards for the named patch.
2176 With arguments, set guards for the named patch.
2177 NOTE: Specifying negative guards now requires '--'.
2177 NOTE: Specifying negative guards now requires '--'.
2178
2178
2179 To set guards on another patch::
2179 To set guards on another patch::
2180
2180
2181 hg qguard other.patch -- +2.6.17 -stable
2181 hg qguard other.patch -- +2.6.17 -stable
2182 '''
2182 '''
2183 def status(idx):
2183 def status(idx):
2184 guards = q.series_guards[idx] or ['unguarded']
2184 guards = q.series_guards[idx] or ['unguarded']
2185 if q.series[idx] in applied:
2185 if q.series[idx] in applied:
2186 state = 'applied'
2186 state = 'applied'
2187 elif q.pushable(idx)[0]:
2187 elif q.pushable(idx)[0]:
2188 state = 'unapplied'
2188 state = 'unapplied'
2189 else:
2189 else:
2190 state = 'guarded'
2190 state = 'guarded'
2191 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2191 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2192 ui.write('%s: ' % ui.label(q.series[idx], label))
2192 ui.write('%s: ' % ui.label(q.series[idx], label))
2193
2193
2194 for i, guard in enumerate(guards):
2194 for i, guard in enumerate(guards):
2195 if guard.startswith('+'):
2195 if guard.startswith('+'):
2196 ui.write(guard, label='qguard.positive')
2196 ui.write(guard, label='qguard.positive')
2197 elif guard.startswith('-'):
2197 elif guard.startswith('-'):
2198 ui.write(guard, label='qguard.negative')
2198 ui.write(guard, label='qguard.negative')
2199 else:
2199 else:
2200 ui.write(guard, label='qguard.unguarded')
2200 ui.write(guard, label='qguard.unguarded')
2201 if i != len(guards) - 1:
2201 if i != len(guards) - 1:
2202 ui.write(' ')
2202 ui.write(' ')
2203 ui.write('\n')
2203 ui.write('\n')
2204 q = repo.mq
2204 q = repo.mq
2205 applied = set(p.name for p in q.applied)
2205 applied = set(p.name for p in q.applied)
2206 patch = None
2206 patch = None
2207 args = list(args)
2207 args = list(args)
2208 if opts['list']:
2208 if opts['list']:
2209 if args or opts['none']:
2209 if args or opts['none']:
2210 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2210 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2211 for i in xrange(len(q.series)):
2211 for i in xrange(len(q.series)):
2212 status(i)
2212 status(i)
2213 return
2213 return
2214 if not args or args[0][0:1] in '-+':
2214 if not args or args[0][0:1] in '-+':
2215 if not q.applied:
2215 if not q.applied:
2216 raise util.Abort(_('no patches applied'))
2216 raise util.Abort(_('no patches applied'))
2217 patch = q.applied[-1].name
2217 patch = q.applied[-1].name
2218 if patch is None and args[0][0:1] not in '-+':
2218 if patch is None and args[0][0:1] not in '-+':
2219 patch = args.pop(0)
2219 patch = args.pop(0)
2220 if patch is None:
2220 if patch is None:
2221 raise util.Abort(_('no patch to work with'))
2221 raise util.Abort(_('no patch to work with'))
2222 if args or opts['none']:
2222 if args or opts['none']:
2223 idx = q.find_series(patch)
2223 idx = q.find_series(patch)
2224 if idx is None:
2224 if idx is None:
2225 raise util.Abort(_('no patch named %s') % patch)
2225 raise util.Abort(_('no patch named %s') % patch)
2226 q.set_guards(idx, args)
2226 q.set_guards(idx, args)
2227 q.save_dirty()
2227 q.save_dirty()
2228 else:
2228 else:
2229 status(q.series.index(q.lookup(patch)))
2229 status(q.series.index(q.lookup(patch)))
2230
2230
2231 def header(ui, repo, patch=None):
2231 def header(ui, repo, patch=None):
2232 """print the header of the topmost or specified patch"""
2232 """print the header of the topmost or specified patch"""
2233 q = repo.mq
2233 q = repo.mq
2234
2234
2235 if patch:
2235 if patch:
2236 patch = q.lookup(patch)
2236 patch = q.lookup(patch)
2237 else:
2237 else:
2238 if not q.applied:
2238 if not q.applied:
2239 ui.write(_('no patches applied\n'))
2239 ui.write(_('no patches applied\n'))
2240 return 1
2240 return 1
2241 patch = q.lookup('qtip')
2241 patch = q.lookup('qtip')
2242 ph = patchheader(q.join(patch), q.plainmode)
2242 ph = patchheader(q.join(patch), q.plainmode)
2243
2243
2244 ui.write('\n'.join(ph.message) + '\n')
2244 ui.write('\n'.join(ph.message) + '\n')
2245
2245
2246 def lastsavename(path):
2246 def lastsavename(path):
2247 (directory, base) = os.path.split(path)
2247 (directory, base) = os.path.split(path)
2248 names = os.listdir(directory)
2248 names = os.listdir(directory)
2249 namere = re.compile("%s.([0-9]+)" % base)
2249 namere = re.compile("%s.([0-9]+)" % base)
2250 maxindex = None
2250 maxindex = None
2251 maxname = None
2251 maxname = None
2252 for f in names:
2252 for f in names:
2253 m = namere.match(f)
2253 m = namere.match(f)
2254 if m:
2254 if m:
2255 index = int(m.group(1))
2255 index = int(m.group(1))
2256 if maxindex is None or index > maxindex:
2256 if maxindex is None or index > maxindex:
2257 maxindex = index
2257 maxindex = index
2258 maxname = f
2258 maxname = f
2259 if maxname:
2259 if maxname:
2260 return (os.path.join(directory, maxname), maxindex)
2260 return (os.path.join(directory, maxname), maxindex)
2261 return (None, None)
2261 return (None, None)
2262
2262
2263 def savename(path):
2263 def savename(path):
2264 (last, index) = lastsavename(path)
2264 (last, index) = lastsavename(path)
2265 if last is None:
2265 if last is None:
2266 index = 0
2266 index = 0
2267 newpath = path + ".%d" % (index + 1)
2267 newpath = path + ".%d" % (index + 1)
2268 return newpath
2268 return newpath
2269
2269
2270 def push(ui, repo, patch=None, **opts):
2270 def push(ui, repo, patch=None, **opts):
2271 """push the next patch onto the stack
2271 """push the next patch onto the stack
2272
2272
2273 When -f/--force is applied, all local changes in patched files
2273 When -f/--force is applied, all local changes in patched files
2274 will be lost.
2274 will be lost.
2275 """
2275 """
2276 q = repo.mq
2276 q = repo.mq
2277 mergeq = None
2277 mergeq = None
2278
2278
2279 if opts['merge']:
2279 if opts['merge']:
2280 if opts['name']:
2280 if opts['name']:
2281 newpath = repo.join(opts['name'])
2281 newpath = repo.join(opts['name'])
2282 else:
2282 else:
2283 newpath, i = lastsavename(q.path)
2283 newpath, i = lastsavename(q.path)
2284 if not newpath:
2284 if not newpath:
2285 ui.warn(_("no saved queues found, please use -n\n"))
2285 ui.warn(_("no saved queues found, please use -n\n"))
2286 return 1
2286 return 1
2287 mergeq = queue(ui, repo.join(""), newpath)
2287 mergeq = queue(ui, repo.join(""), newpath)
2288 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2288 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2289 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2289 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2290 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2290 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2291 return ret
2291 return ret
2292
2292
2293 def pop(ui, repo, patch=None, **opts):
2293 def pop(ui, repo, patch=None, **opts):
2294 """pop the current patch off the stack
2294 """pop the current patch off the stack
2295
2295
2296 By default, pops off the top of the patch stack. If given a patch
2296 By default, pops off the top of the patch stack. If given a patch
2297 name, keeps popping off patches until the named patch is at the
2297 name, keeps popping off patches until the named patch is at the
2298 top of the stack.
2298 top of the stack.
2299 """
2299 """
2300 localupdate = True
2300 localupdate = True
2301 if opts['name']:
2301 if opts['name']:
2302 q = queue(ui, repo.join(""), repo.join(opts['name']))
2302 q = queue(ui, repo.join(""), repo.join(opts['name']))
2303 ui.warn(_('using patch queue: %s\n') % q.path)
2303 ui.warn(_('using patch queue: %s\n') % q.path)
2304 localupdate = False
2304 localupdate = False
2305 else:
2305 else:
2306 q = repo.mq
2306 q = repo.mq
2307 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2307 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2308 all=opts['all'])
2308 all=opts['all'])
2309 q.save_dirty()
2309 q.save_dirty()
2310 return ret
2310 return ret
2311
2311
2312 def rename(ui, repo, patch, name=None, **opts):
2312 def rename(ui, repo, patch, name=None, **opts):
2313 """rename a patch
2313 """rename a patch
2314
2314
2315 With one argument, renames the current patch to PATCH1.
2315 With one argument, renames the current patch to PATCH1.
2316 With two arguments, renames PATCH1 to PATCH2."""
2316 With two arguments, renames PATCH1 to PATCH2."""
2317
2317
2318 q = repo.mq
2318 q = repo.mq
2319
2319
2320 if not name:
2320 if not name:
2321 name = patch
2321 name = patch
2322 patch = None
2322 patch = None
2323
2323
2324 if patch:
2324 if patch:
2325 patch = q.lookup(patch)
2325 patch = q.lookup(patch)
2326 else:
2326 else:
2327 if not q.applied:
2327 if not q.applied:
2328 ui.write(_('no patches applied\n'))
2328 ui.write(_('no patches applied\n'))
2329 return
2329 return
2330 patch = q.lookup('qtip')
2330 patch = q.lookup('qtip')
2331 absdest = q.join(name)
2331 absdest = q.join(name)
2332 if os.path.isdir(absdest):
2332 if os.path.isdir(absdest):
2333 name = normname(os.path.join(name, os.path.basename(patch)))
2333 name = normname(os.path.join(name, os.path.basename(patch)))
2334 absdest = q.join(name)
2334 absdest = q.join(name)
2335 if os.path.exists(absdest):
2335 if os.path.exists(absdest):
2336 raise util.Abort(_('%s already exists') % absdest)
2336 raise util.Abort(_('%s already exists') % absdest)
2337
2337
2338 if name in q.series:
2338 if name in q.series:
2339 raise util.Abort(
2339 raise util.Abort(
2340 _('A patch named %s already exists in the series file') % name)
2340 _('A patch named %s already exists in the series file') % name)
2341
2341
2342 ui.note(_('renaming %s to %s\n') % (patch, name))
2342 ui.note(_('renaming %s to %s\n') % (patch, name))
2343 i = q.find_series(patch)
2343 i = q.find_series(patch)
2344 guards = q.guard_re.findall(q.full_series[i])
2344 guards = q.guard_re.findall(q.full_series[i])
2345 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2345 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2346 q.parse_series()
2346 q.parse_series()
2347 q.series_dirty = 1
2347 q.series_dirty = 1
2348
2348
2349 info = q.isapplied(patch)
2349 info = q.isapplied(patch)
2350 if info:
2350 if info:
2351 q.applied[info[0]] = statusentry(info[1], name)
2351 q.applied[info[0]] = statusentry(info[1], name)
2352 q.applied_dirty = 1
2352 q.applied_dirty = 1
2353
2353
2354 destdir = os.path.dirname(absdest)
2354 destdir = os.path.dirname(absdest)
2355 if not os.path.isdir(destdir):
2355 if not os.path.isdir(destdir):
2356 os.makedirs(destdir)
2356 os.makedirs(destdir)
2357 util.rename(q.join(patch), absdest)
2357 util.rename(q.join(patch), absdest)
2358 r = q.qrepo()
2358 r = q.qrepo()
2359 if r:
2359 if r:
2360 wctx = r[None]
2360 wctx = r[None]
2361 wlock = r.wlock()
2361 wlock = r.wlock()
2362 try:
2362 try:
2363 if r.dirstate[patch] == 'a':
2363 if r.dirstate[patch] == 'a':
2364 r.dirstate.forget(patch)
2364 r.dirstate.forget(patch)
2365 r.dirstate.add(name)
2365 r.dirstate.add(name)
2366 else:
2366 else:
2367 if r.dirstate[name] == 'r':
2367 if r.dirstate[name] == 'r':
2368 wctx.undelete([name])
2368 wctx.undelete([name])
2369 wctx.copy(patch, name)
2369 wctx.copy(patch, name)
2370 wctx.remove([patch], False)
2370 wctx.remove([patch], False)
2371 finally:
2371 finally:
2372 wlock.release()
2372 wlock.release()
2373
2373
2374 q.save_dirty()
2374 q.save_dirty()
2375
2375
2376 def restore(ui, repo, rev, **opts):
2376 def restore(ui, repo, rev, **opts):
2377 """restore the queue state saved by a revision (DEPRECATED)
2377 """restore the queue state saved by a revision (DEPRECATED)
2378
2378
2379 This command is deprecated, use rebase --mq instead."""
2379 This command is deprecated, use rebase --mq instead."""
2380 rev = repo.lookup(rev)
2380 rev = repo.lookup(rev)
2381 q = repo.mq
2381 q = repo.mq
2382 q.restore(repo, rev, delete=opts['delete'],
2382 q.restore(repo, rev, delete=opts['delete'],
2383 qupdate=opts['update'])
2383 qupdate=opts['update'])
2384 q.save_dirty()
2384 q.save_dirty()
2385 return 0
2385 return 0
2386
2386
2387 def save(ui, repo, **opts):
2387 def save(ui, repo, **opts):
2388 """save current queue state (DEPRECATED)
2388 """save current queue state (DEPRECATED)
2389
2389
2390 This command is deprecated, use rebase --mq instead."""
2390 This command is deprecated, use rebase --mq instead."""
2391 q = repo.mq
2391 q = repo.mq
2392 message = cmdutil.logmessage(opts)
2392 message = cmdutil.logmessage(opts)
2393 ret = q.save(repo, msg=message)
2393 ret = q.save(repo, msg=message)
2394 if ret:
2394 if ret:
2395 return ret
2395 return ret
2396 q.save_dirty()
2396 q.save_dirty()
2397 if opts['copy']:
2397 if opts['copy']:
2398 path = q.path
2398 path = q.path
2399 if opts['name']:
2399 if opts['name']:
2400 newpath = os.path.join(q.basepath, opts['name'])
2400 newpath = os.path.join(q.basepath, opts['name'])
2401 if os.path.exists(newpath):
2401 if os.path.exists(newpath):
2402 if not os.path.isdir(newpath):
2402 if not os.path.isdir(newpath):
2403 raise util.Abort(_('destination %s exists and is not '
2403 raise util.Abort(_('destination %s exists and is not '
2404 'a directory') % newpath)
2404 'a directory') % newpath)
2405 if not opts['force']:
2405 if not opts['force']:
2406 raise util.Abort(_('destination %s exists, '
2406 raise util.Abort(_('destination %s exists, '
2407 'use -f to force') % newpath)
2407 'use -f to force') % newpath)
2408 else:
2408 else:
2409 newpath = savename(path)
2409 newpath = savename(path)
2410 ui.warn(_("copy %s to %s\n") % (path, newpath))
2410 ui.warn(_("copy %s to %s\n") % (path, newpath))
2411 util.copyfiles(path, newpath)
2411 util.copyfiles(path, newpath)
2412 if opts['empty']:
2412 if opts['empty']:
2413 try:
2413 try:
2414 os.unlink(q.join(q.status_path))
2414 os.unlink(q.join(q.status_path))
2415 except:
2415 except:
2416 pass
2416 pass
2417 return 0
2417 return 0
2418
2418
2419 def strip(ui, repo, *revs, **opts):
2419 def strip(ui, repo, *revs, **opts):
2420 """strip changesets and all their descendants from the repository
2420 """strip changesets and all their descendants from the repository
2421
2421
2422 The strip command removes the specified changesets and all their
2422 The strip command removes the specified changesets and all their
2423 descendants. If the working directory has uncommitted changes,
2423 descendants. If the working directory has uncommitted changes,
2424 the operation is aborted unless the --force flag is supplied.
2424 the operation is aborted unless the --force flag is supplied.
2425
2425
2426 If a parent of the working directory is stripped, then the working
2426 If a parent of the working directory is stripped, then the working
2427 directory will automatically be updated to the most recent
2427 directory will automatically be updated to the most recent
2428 available ancestor of the stripped parent after the operation
2428 available ancestor of the stripped parent after the operation
2429 completes.
2429 completes.
2430
2430
2431 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2431 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2432 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2432 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2433 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2433 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2434 where BUNDLE is the bundle file created by the strip. Note that
2434 where BUNDLE is the bundle file created by the strip. Note that
2435 the local revision numbers will in general be different after the
2435 the local revision numbers will in general be different after the
2436 restore.
2436 restore.
2437
2437
2438 Use the --nobackup option to discard the backup bundle once the
2438 Use the --nobackup option to discard the backup bundle once the
2439 operation completes.
2439 operation completes.
2440 """
2440 """
2441 backup = 'all'
2441 backup = 'all'
2442 if opts['backup']:
2442 if opts['backup']:
2443 backup = 'strip'
2443 backup = 'strip'
2444 elif opts['nobackup']:
2444 elif opts['nobackup']:
2445 backup = 'none'
2445 backup = 'none'
2446
2446
2447 cl = repo.changelog
2447 cl = repo.changelog
2448 revs = set(cl.rev(repo.lookup(r)) for r in revs)
2448 revs = set(cl.rev(repo.lookup(r)) for r in revs)
2449
2449
2450 descendants = set(cl.descendants(*revs))
2450 descendants = set(cl.descendants(*revs))
2451 strippedrevs = revs.union(descendants)
2451 strippedrevs = revs.union(descendants)
2452 roots = revs.difference(descendants)
2452 roots = revs.difference(descendants)
2453
2453
2454 update = False
2454 update = False
2455 # if one of the wdir parent is stripped we'll need
2455 # if one of the wdir parent is stripped we'll need
2456 # to update away to an earlier revision
2456 # to update away to an earlier revision
2457 for p in repo.dirstate.parents():
2457 for p in repo.dirstate.parents():
2458 if p != nullid and cl.rev(p) in strippedrevs:
2458 if p != nullid and cl.rev(p) in strippedrevs:
2459 update = True
2459 update = True
2460 break
2460 break
2461
2461
2462 rootnodes = set(cl.node(r) for r in roots)
2462 rootnodes = set(cl.node(r) for r in roots)
2463
2463
2464 q = repo.mq
2464 q = repo.mq
2465 if q.applied:
2465 if q.applied:
2466 # refresh queue state if we're about to strip
2466 # refresh queue state if we're about to strip
2467 # applied patches
2467 # applied patches
2468 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2468 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2469 q.applied_dirty = True
2469 q.applied_dirty = True
2470 start = 0
2470 start = 0
2471 end = len(q.applied)
2471 end = len(q.applied)
2472 for i, statusentry in enumerate(q.applied):
2472 for i, statusentry in enumerate(q.applied):
2473 if statusentry.node in rootnodes:
2473 if statusentry.node in rootnodes:
2474 # if one of the stripped roots is an applied
2474 # if one of the stripped roots is an applied
2475 # patch, only part of the queue is stripped
2475 # patch, only part of the queue is stripped
2476 start = i
2476 start = i
2477 break
2477 break
2478 del q.applied[start:end]
2478 del q.applied[start:end]
2479 q.save_dirty()
2479 q.save_dirty()
2480
2480
2481 repo.mq.strip(repo, list(rootnodes), backup=backup, update=update,
2481 repo.mq.strip(repo, list(rootnodes), backup=backup, update=update,
2482 force=opts['force'])
2482 force=opts['force'])
2483 return 0
2483 return 0
2484
2484
2485 def select(ui, repo, *args, **opts):
2485 def select(ui, repo, *args, **opts):
2486 '''set or print guarded patches to push
2486 '''set or print guarded patches to push
2487
2487
2488 Use the :hg:`qguard` command to set or print guards on patch, then use
2488 Use the :hg:`qguard` command to set or print guards on patch, then use
2489 qselect to tell mq which guards to use. A patch will be pushed if
2489 qselect to tell mq which guards to use. A patch will be pushed if
2490 it has no guards or any positive guards match the currently
2490 it has no guards or any positive guards match the currently
2491 selected guard, but will not be pushed if any negative guards
2491 selected guard, but will not be pushed if any negative guards
2492 match the current guard. For example::
2492 match the current guard. For example::
2493
2493
2494 qguard foo.patch -stable (negative guard)
2494 qguard foo.patch -stable (negative guard)
2495 qguard bar.patch +stable (positive guard)
2495 qguard bar.patch +stable (positive guard)
2496 qselect stable
2496 qselect stable
2497
2497
2498 This activates the "stable" guard. mq will skip foo.patch (because
2498 This activates the "stable" guard. mq will skip foo.patch (because
2499 it has a negative match) but push bar.patch (because it has a
2499 it has a negative match) but push bar.patch (because it has a
2500 positive match).
2500 positive match).
2501
2501
2502 With no arguments, prints the currently active guards.
2502 With no arguments, prints the currently active guards.
2503 With one argument, sets the active guard.
2503 With one argument, sets the active guard.
2504
2504
2505 Use -n/--none to deactivate guards (no other arguments needed).
2505 Use -n/--none to deactivate guards (no other arguments needed).
2506 When no guards are active, patches with positive guards are
2506 When no guards are active, patches with positive guards are
2507 skipped and patches with negative guards are pushed.
2507 skipped and patches with negative guards are pushed.
2508
2508
2509 qselect can change the guards on applied patches. It does not pop
2509 qselect can change the guards on applied patches. It does not pop
2510 guarded patches by default. Use --pop to pop back to the last
2510 guarded patches by default. Use --pop to pop back to the last
2511 applied patch that is not guarded. Use --reapply (which implies
2511 applied patch that is not guarded. Use --reapply (which implies
2512 --pop) to push back to the current patch afterwards, but skip
2512 --pop) to push back to the current patch afterwards, but skip
2513 guarded patches.
2513 guarded patches.
2514
2514
2515 Use -s/--series to print a list of all guards in the series file
2515 Use -s/--series to print a list of all guards in the series file
2516 (no other arguments needed). Use -v for more information.'''
2516 (no other arguments needed). Use -v for more information.'''
2517
2517
2518 q = repo.mq
2518 q = repo.mq
2519 guards = q.active()
2519 guards = q.active()
2520 if args or opts['none']:
2520 if args or opts['none']:
2521 old_unapplied = q.unapplied(repo)
2521 old_unapplied = q.unapplied(repo)
2522 old_guarded = [i for i in xrange(len(q.applied)) if
2522 old_guarded = [i for i in xrange(len(q.applied)) if
2523 not q.pushable(i)[0]]
2523 not q.pushable(i)[0]]
2524 q.set_active(args)
2524 q.set_active(args)
2525 q.save_dirty()
2525 q.save_dirty()
2526 if not args:
2526 if not args:
2527 ui.status(_('guards deactivated\n'))
2527 ui.status(_('guards deactivated\n'))
2528 if not opts['pop'] and not opts['reapply']:
2528 if not opts['pop'] and not opts['reapply']:
2529 unapplied = q.unapplied(repo)
2529 unapplied = q.unapplied(repo)
2530 guarded = [i for i in xrange(len(q.applied))
2530 guarded = [i for i in xrange(len(q.applied))
2531 if not q.pushable(i)[0]]
2531 if not q.pushable(i)[0]]
2532 if len(unapplied) != len(old_unapplied):
2532 if len(unapplied) != len(old_unapplied):
2533 ui.status(_('number of unguarded, unapplied patches has '
2533 ui.status(_('number of unguarded, unapplied patches has '
2534 'changed from %d to %d\n') %
2534 'changed from %d to %d\n') %
2535 (len(old_unapplied), len(unapplied)))
2535 (len(old_unapplied), len(unapplied)))
2536 if len(guarded) != len(old_guarded):
2536 if len(guarded) != len(old_guarded):
2537 ui.status(_('number of guarded, applied patches has changed '
2537 ui.status(_('number of guarded, applied patches has changed '
2538 'from %d to %d\n') %
2538 'from %d to %d\n') %
2539 (len(old_guarded), len(guarded)))
2539 (len(old_guarded), len(guarded)))
2540 elif opts['series']:
2540 elif opts['series']:
2541 guards = {}
2541 guards = {}
2542 noguards = 0
2542 noguards = 0
2543 for gs in q.series_guards:
2543 for gs in q.series_guards:
2544 if not gs:
2544 if not gs:
2545 noguards += 1
2545 noguards += 1
2546 for g in gs:
2546 for g in gs:
2547 guards.setdefault(g, 0)
2547 guards.setdefault(g, 0)
2548 guards[g] += 1
2548 guards[g] += 1
2549 if ui.verbose:
2549 if ui.verbose:
2550 guards['NONE'] = noguards
2550 guards['NONE'] = noguards
2551 guards = guards.items()
2551 guards = guards.items()
2552 guards.sort(key=lambda x: x[0][1:])
2552 guards.sort(key=lambda x: x[0][1:])
2553 if guards:
2553 if guards:
2554 ui.note(_('guards in series file:\n'))
2554 ui.note(_('guards in series file:\n'))
2555 for guard, count in guards:
2555 for guard, count in guards:
2556 ui.note('%2d ' % count)
2556 ui.note('%2d ' % count)
2557 ui.write(guard, '\n')
2557 ui.write(guard, '\n')
2558 else:
2558 else:
2559 ui.note(_('no guards in series file\n'))
2559 ui.note(_('no guards in series file\n'))
2560 else:
2560 else:
2561 if guards:
2561 if guards:
2562 ui.note(_('active guards:\n'))
2562 ui.note(_('active guards:\n'))
2563 for g in guards:
2563 for g in guards:
2564 ui.write(g, '\n')
2564 ui.write(g, '\n')
2565 else:
2565 else:
2566 ui.write(_('no active guards\n'))
2566 ui.write(_('no active guards\n'))
2567 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2567 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2568 popped = False
2568 popped = False
2569 if opts['pop'] or opts['reapply']:
2569 if opts['pop'] or opts['reapply']:
2570 for i in xrange(len(q.applied)):
2570 for i in xrange(len(q.applied)):
2571 pushable, reason = q.pushable(i)
2571 pushable, reason = q.pushable(i)
2572 if not pushable:
2572 if not pushable:
2573 ui.status(_('popping guarded patches\n'))
2573 ui.status(_('popping guarded patches\n'))
2574 popped = True
2574 popped = True
2575 if i == 0:
2575 if i == 0:
2576 q.pop(repo, all=True)
2576 q.pop(repo, all=True)
2577 else:
2577 else:
2578 q.pop(repo, i - 1)
2578 q.pop(repo, i - 1)
2579 break
2579 break
2580 if popped:
2580 if popped:
2581 try:
2581 try:
2582 if reapply:
2582 if reapply:
2583 ui.status(_('reapplying unguarded patches\n'))
2583 ui.status(_('reapplying unguarded patches\n'))
2584 q.push(repo, reapply)
2584 q.push(repo, reapply)
2585 finally:
2585 finally:
2586 q.save_dirty()
2586 q.save_dirty()
2587
2587
2588 def finish(ui, repo, *revrange, **opts):
2588 def finish(ui, repo, *revrange, **opts):
2589 """move applied patches into repository history
2589 """move applied patches into repository history
2590
2590
2591 Finishes the specified revisions (corresponding to applied
2591 Finishes the specified revisions (corresponding to applied
2592 patches) by moving them out of mq control into regular repository
2592 patches) by moving them out of mq control into regular repository
2593 history.
2593 history.
2594
2594
2595 Accepts a revision range or the -a/--applied option. If --applied
2595 Accepts a revision range or the -a/--applied option. If --applied
2596 is specified, all applied mq revisions are removed from mq
2596 is specified, all applied mq revisions are removed from mq
2597 control. Otherwise, the given revisions must be at the base of the
2597 control. Otherwise, the given revisions must be at the base of the
2598 stack of applied patches.
2598 stack of applied patches.
2599
2599
2600 This can be especially useful if your changes have been applied to
2600 This can be especially useful if your changes have been applied to
2601 an upstream repository, or if you are about to push your changes
2601 an upstream repository, or if you are about to push your changes
2602 to upstream.
2602 to upstream.
2603 """
2603 """
2604 if not opts['applied'] and not revrange:
2604 if not opts['applied'] and not revrange:
2605 raise util.Abort(_('no revisions specified'))
2605 raise util.Abort(_('no revisions specified'))
2606 elif opts['applied']:
2606 elif opts['applied']:
2607 revrange = ('qbase::qtip',) + revrange
2607 revrange = ('qbase::qtip',) + revrange
2608
2608
2609 q = repo.mq
2609 q = repo.mq
2610 if not q.applied:
2610 if not q.applied:
2611 ui.status(_('no patches applied\n'))
2611 ui.status(_('no patches applied\n'))
2612 return 0
2612 return 0
2613
2613
2614 revs = cmdutil.revrange(repo, revrange)
2614 revs = cmdutil.revrange(repo, revrange)
2615 q.finish(repo, revs)
2615 q.finish(repo, revs)
2616 q.save_dirty()
2616 q.save_dirty()
2617 return 0
2617 return 0
2618
2618
2619 def qqueue(ui, repo, name=None, **opts):
2619 def qqueue(ui, repo, name=None, **opts):
2620 '''manage multiple patch queues
2620 '''manage multiple patch queues
2621
2621
2622 Supports switching between different patch queues, as well as creating
2622 Supports switching between different patch queues, as well as creating
2623 new patch queues and deleting existing ones.
2623 new patch queues and deleting existing ones.
2624
2624
2625 Omitting a queue name or specifying -l/--list will show you the registered
2625 Omitting a queue name or specifying -l/--list will show you the registered
2626 queues - by default the "normal" patches queue is registered. The currently
2626 queues - by default the "normal" patches queue is registered. The currently
2627 active queue will be marked with "(active)".
2627 active queue will be marked with "(active)".
2628
2628
2629 To create a new queue, use -c/--create. The queue is automatically made
2629 To create a new queue, use -c/--create. The queue is automatically made
2630 active, except in the case where there are applied patches from the
2630 active, except in the case where there are applied patches from the
2631 currently active queue in the repository. Then the queue will only be
2631 currently active queue in the repository. Then the queue will only be
2632 created and switching will fail.
2632 created and switching will fail.
2633
2633
2634 To delete an existing queue, use --delete. You cannot delete the currently
2634 To delete an existing queue, use --delete. You cannot delete the currently
2635 active queue.
2635 active queue.
2636 '''
2636 '''
2637
2637
2638 q = repo.mq
2638 q = repo.mq
2639
2639
2640 _defaultqueue = 'patches'
2640 _defaultqueue = 'patches'
2641 _allqueues = 'patches.queues'
2641 _allqueues = 'patches.queues'
2642 _activequeue = 'patches.queue'
2642 _activequeue = 'patches.queue'
2643
2643
2644 def _getcurrent():
2644 def _getcurrent():
2645 cur = os.path.basename(q.path)
2645 cur = os.path.basename(q.path)
2646 if cur.startswith('patches-'):
2646 if cur.startswith('patches-'):
2647 cur = cur[8:]
2647 cur = cur[8:]
2648 return cur
2648 return cur
2649
2649
2650 def _noqueues():
2650 def _noqueues():
2651 try:
2651 try:
2652 fh = repo.opener(_allqueues, 'r')
2652 fh = repo.opener(_allqueues, 'r')
2653 fh.close()
2653 fh.close()
2654 except IOError:
2654 except IOError:
2655 return True
2655 return True
2656
2656
2657 return False
2657 return False
2658
2658
2659 def _getqueues():
2659 def _getqueues():
2660 current = _getcurrent()
2660 current = _getcurrent()
2661
2661
2662 try:
2662 try:
2663 fh = repo.opener(_allqueues, 'r')
2663 fh = repo.opener(_allqueues, 'r')
2664 queues = [queue.strip() for queue in fh if queue.strip()]
2664 queues = [queue.strip() for queue in fh if queue.strip()]
2665 if current not in queues:
2665 if current not in queues:
2666 queues.append(current)
2666 queues.append(current)
2667 except IOError:
2667 except IOError:
2668 queues = [_defaultqueue]
2668 queues = [_defaultqueue]
2669
2669
2670 return sorted(queues)
2670 return sorted(queues)
2671
2671
2672 def _setactive(name):
2672 def _setactive(name):
2673 if q.applied:
2673 if q.applied:
2674 raise util.Abort(_('patches applied - cannot set new queue active'))
2674 raise util.Abort(_('patches applied - cannot set new queue active'))
2675 _setactivenocheck(name)
2675 _setactivenocheck(name)
2676
2676
2677 def _setactivenocheck(name):
2677 def _setactivenocheck(name):
2678 fh = repo.opener(_activequeue, 'w')
2678 fh = repo.opener(_activequeue, 'w')
2679 if name != 'patches':
2679 if name != 'patches':
2680 fh.write(name)
2680 fh.write(name)
2681 fh.close()
2681 fh.close()
2682
2682
2683 def _addqueue(name):
2683 def _addqueue(name):
2684 fh = repo.opener(_allqueues, 'a')
2684 fh = repo.opener(_allqueues, 'a')
2685 fh.write('%s\n' % (name,))
2685 fh.write('%s\n' % (name,))
2686 fh.close()
2686 fh.close()
2687
2687
2688 def _queuedir(name):
2688 def _queuedir(name):
2689 if name == 'patches':
2689 if name == 'patches':
2690 return repo.join('patches')
2690 return repo.join('patches')
2691 else:
2691 else:
2692 return repo.join('patches-' + name)
2692 return repo.join('patches-' + name)
2693
2693
2694 def _validname(name):
2694 def _validname(name):
2695 for n in name:
2695 for n in name:
2696 if n in ':\\/.':
2696 if n in ':\\/.':
2697 return False
2697 return False
2698 return True
2698 return True
2699
2699
2700 def _delete(name):
2700 def _delete(name):
2701 if name not in existing:
2701 if name not in existing:
2702 raise util.Abort(_('cannot delete queue that does not exist'))
2702 raise util.Abort(_('cannot delete queue that does not exist'))
2703
2703
2704 current = _getcurrent()
2704 current = _getcurrent()
2705
2705
2706 if name == current:
2706 if name == current:
2707 raise util.Abort(_('cannot delete currently active queue'))
2707 raise util.Abort(_('cannot delete currently active queue'))
2708
2708
2709 fh = repo.opener('patches.queues.new', 'w')
2709 fh = repo.opener('patches.queues.new', 'w')
2710 for queue in existing:
2710 for queue in existing:
2711 if queue == name:
2711 if queue == name:
2712 continue
2712 continue
2713 fh.write('%s\n' % (queue,))
2713 fh.write('%s\n' % (queue,))
2714 fh.close()
2714 fh.close()
2715 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2715 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2716
2716
2717 if not name or opts.get('list'):
2717 if not name or opts.get('list'):
2718 current = _getcurrent()
2718 current = _getcurrent()
2719 for queue in _getqueues():
2719 for queue in _getqueues():
2720 ui.write('%s' % (queue,))
2720 ui.write('%s' % (queue,))
2721 if queue == current and not ui.quiet:
2721 if queue == current and not ui.quiet:
2722 ui.write(_(' (active)\n'))
2722 ui.write(_(' (active)\n'))
2723 else:
2723 else:
2724 ui.write('\n')
2724 ui.write('\n')
2725 return
2725 return
2726
2726
2727 if not _validname(name):
2727 if not _validname(name):
2728 raise util.Abort(
2728 raise util.Abort(
2729 _('invalid queue name, may not contain the characters ":\\/."'))
2729 _('invalid queue name, may not contain the characters ":\\/."'))
2730
2730
2731 existing = _getqueues()
2731 existing = _getqueues()
2732
2732
2733 if opts.get('create'):
2733 if opts.get('create'):
2734 if name in existing:
2734 if name in existing:
2735 raise util.Abort(_('queue "%s" already exists') % name)
2735 raise util.Abort(_('queue "%s" already exists') % name)
2736 if _noqueues():
2736 if _noqueues():
2737 _addqueue(_defaultqueue)
2737 _addqueue(_defaultqueue)
2738 _addqueue(name)
2738 _addqueue(name)
2739 _setactive(name)
2739 _setactive(name)
2740 elif opts.get('rename'):
2740 elif opts.get('rename'):
2741 current = _getcurrent()
2741 current = _getcurrent()
2742 if name == current:
2742 if name == current:
2743 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2743 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2744 if name in existing:
2744 if name in existing:
2745 raise util.Abort(_('queue "%s" already exists') % name)
2745 raise util.Abort(_('queue "%s" already exists') % name)
2746
2746
2747 olddir = _queuedir(current)
2747 olddir = _queuedir(current)
2748 newdir = _queuedir(name)
2748 newdir = _queuedir(name)
2749
2749
2750 if os.path.exists(newdir):
2750 if os.path.exists(newdir):
2751 raise util.Abort(_('non-queue directory "%s" already exists') %
2751 raise util.Abort(_('non-queue directory "%s" already exists') %
2752 newdir)
2752 newdir)
2753
2753
2754 fh = repo.opener('patches.queues.new', 'w')
2754 fh = repo.opener('patches.queues.new', 'w')
2755 for queue in existing:
2755 for queue in existing:
2756 if queue == current:
2756 if queue == current:
2757 fh.write('%s\n' % (name,))
2757 fh.write('%s\n' % (name,))
2758 if os.path.exists(olddir):
2758 if os.path.exists(olddir):
2759 util.rename(olddir, newdir)
2759 util.rename(olddir, newdir)
2760 else:
2760 else:
2761 fh.write('%s\n' % (queue,))
2761 fh.write('%s\n' % (queue,))
2762 fh.close()
2762 fh.close()
2763 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2763 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2764 _setactivenocheck(name)
2764 _setactivenocheck(name)
2765 elif opts.get('delete'):
2765 elif opts.get('delete'):
2766 _delete(name)
2766 _delete(name)
2767 elif opts.get('purge'):
2767 elif opts.get('purge'):
2768 if name in existing:
2768 if name in existing:
2769 _delete(name)
2769 _delete(name)
2770 qdir = _queuedir(name)
2770 qdir = _queuedir(name)
2771 if os.path.exists(qdir):
2771 if os.path.exists(qdir):
2772 shutil.rmtree(qdir)
2772 shutil.rmtree(qdir)
2773 else:
2773 else:
2774 if name not in existing:
2774 if name not in existing:
2775 raise util.Abort(_('use --create to create a new queue'))
2775 raise util.Abort(_('use --create to create a new queue'))
2776 _setactive(name)
2776 _setactive(name)
2777
2777
2778 def reposetup(ui, repo):
2778 def reposetup(ui, repo):
2779 class mqrepo(repo.__class__):
2779 class mqrepo(repo.__class__):
2780 @util.propertycache
2780 @util.propertycache
2781 def mq(self):
2781 def mq(self):
2782 return queue(self.ui, self.join(""))
2782 return queue(self.ui, self.join(""))
2783
2783
2784 def abort_if_wdir_patched(self, errmsg, force=False):
2784 def abort_if_wdir_patched(self, errmsg, force=False):
2785 if self.mq.applied and not force:
2785 if self.mq.applied and not force:
2786 parent = self.dirstate.parents()[0]
2786 parent = self.dirstate.parents()[0]
2787 if parent in [s.node for s in self.mq.applied]:
2787 if parent in [s.node for s in self.mq.applied]:
2788 raise util.Abort(errmsg)
2788 raise util.Abort(errmsg)
2789
2789
2790 def commit(self, text="", user=None, date=None, match=None,
2790 def commit(self, text="", user=None, date=None, match=None,
2791 force=False, editor=False, extra={}):
2791 force=False, editor=False, extra={}):
2792 self.abort_if_wdir_patched(
2792 self.abort_if_wdir_patched(
2793 _('cannot commit over an applied mq patch'),
2793 _('cannot commit over an applied mq patch'),
2794 force)
2794 force)
2795
2795
2796 return super(mqrepo, self).commit(text, user, date, match, force,
2796 return super(mqrepo, self).commit(text, user, date, match, force,
2797 editor, extra)
2797 editor, extra)
2798
2798
2799 def push(self, remote, force=False, revs=None, newbranch=False):
2799 def push(self, remote, force=False, revs=None, newbranch=False):
2800 if self.mq.applied and not force and not revs:
2800 if self.mq.applied and not force and not revs:
2801 raise util.Abort(_('source has mq patches applied'))
2801 raise util.Abort(_('source has mq patches applied'))
2802 return super(mqrepo, self).push(remote, force, revs, newbranch)
2802 return super(mqrepo, self).push(remote, force, revs, newbranch)
2803
2803
2804 def _findtags(self):
2804 def _findtags(self):
2805 '''augment tags from base class with patch tags'''
2805 '''augment tags from base class with patch tags'''
2806 result = super(mqrepo, self)._findtags()
2806 result = super(mqrepo, self)._findtags()
2807
2807
2808 q = self.mq
2808 q = self.mq
2809 if not q.applied:
2809 if not q.applied:
2810 return result
2810 return result
2811
2811
2812 mqtags = [(patch.node, patch.name) for patch in q.applied]
2812 mqtags = [(patch.node, patch.name) for patch in q.applied]
2813
2813
2814 if mqtags[-1][0] not in self.changelog.nodemap:
2814 if mqtags[-1][0] not in self.changelog.nodemap:
2815 self.ui.warn(_('mq status file refers to unknown node %s\n')
2815 self.ui.warn(_('mq status file refers to unknown node %s\n')
2816 % short(mqtags[-1][0]))
2816 % short(mqtags[-1][0]))
2817 return result
2817 return result
2818
2818
2819 mqtags.append((mqtags[-1][0], 'qtip'))
2819 mqtags.append((mqtags[-1][0], 'qtip'))
2820 mqtags.append((mqtags[0][0], 'qbase'))
2820 mqtags.append((mqtags[0][0], 'qbase'))
2821 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2821 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2822 tags = result[0]
2822 tags = result[0]
2823 for patch in mqtags:
2823 for patch in mqtags:
2824 if patch[1] in tags:
2824 if patch[1] in tags:
2825 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2825 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2826 % patch[1])
2826 % patch[1])
2827 else:
2827 else:
2828 tags[patch[1]] = patch[0]
2828 tags[patch[1]] = patch[0]
2829
2829
2830 return result
2830 return result
2831
2831
2832 def _branchtags(self, partial, lrev):
2832 def _branchtags(self, partial, lrev):
2833 q = self.mq
2833 q = self.mq
2834 if not q.applied:
2834 if not q.applied:
2835 return super(mqrepo, self)._branchtags(partial, lrev)
2835 return super(mqrepo, self)._branchtags(partial, lrev)
2836
2836
2837 cl = self.changelog
2837 cl = self.changelog
2838 qbasenode = q.applied[0].node
2838 qbasenode = q.applied[0].node
2839 if qbasenode not in cl.nodemap:
2839 if qbasenode not in cl.nodemap:
2840 self.ui.warn(_('mq status file refers to unknown node %s\n')
2840 self.ui.warn(_('mq status file refers to unknown node %s\n')
2841 % short(qbasenode))
2841 % short(qbasenode))
2842 return super(mqrepo, self)._branchtags(partial, lrev)
2842 return super(mqrepo, self)._branchtags(partial, lrev)
2843
2843
2844 qbase = cl.rev(qbasenode)
2844 qbase = cl.rev(qbasenode)
2845 start = lrev + 1
2845 start = lrev + 1
2846 if start < qbase:
2846 if start < qbase:
2847 # update the cache (excluding the patches) and save it
2847 # update the cache (excluding the patches) and save it
2848 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2848 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2849 self._updatebranchcache(partial, ctxgen)
2849 self._updatebranchcache(partial, ctxgen)
2850 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2850 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2851 start = qbase
2851 start = qbase
2852 # if start = qbase, the cache is as updated as it should be.
2852 # if start = qbase, the cache is as updated as it should be.
2853 # if start > qbase, the cache includes (part of) the patches.
2853 # if start > qbase, the cache includes (part of) the patches.
2854 # we might as well use it, but we won't save it.
2854 # we might as well use it, but we won't save it.
2855
2855
2856 # update the cache up to the tip
2856 # update the cache up to the tip
2857 ctxgen = (self[r] for r in xrange(start, len(cl)))
2857 ctxgen = (self[r] for r in xrange(start, len(cl)))
2858 self._updatebranchcache(partial, ctxgen)
2858 self._updatebranchcache(partial, ctxgen)
2859
2859
2860 return partial
2860 return partial
2861
2861
2862 if repo.local():
2862 if repo.local():
2863 repo.__class__ = mqrepo
2863 repo.__class__ = mqrepo
2864
2864
2865 def mqimport(orig, ui, repo, *args, **kwargs):
2865 def mqimport(orig, ui, repo, *args, **kwargs):
2866 if (hasattr(repo, 'abort_if_wdir_patched')
2866 if (hasattr(repo, 'abort_if_wdir_patched')
2867 and not kwargs.get('no_commit', False)):
2867 and not kwargs.get('no_commit', False)):
2868 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2868 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2869 kwargs.get('force'))
2869 kwargs.get('force'))
2870 return orig(ui, repo, *args, **kwargs)
2870 return orig(ui, repo, *args, **kwargs)
2871
2871
2872 def mqinit(orig, ui, *args, **kwargs):
2872 def mqinit(orig, ui, *args, **kwargs):
2873 mq = kwargs.pop('mq', None)
2873 mq = kwargs.pop('mq', None)
2874
2874
2875 if not mq:
2875 if not mq:
2876 return orig(ui, *args, **kwargs)
2876 return orig(ui, *args, **kwargs)
2877
2877
2878 if args:
2878 if args:
2879 repopath = args[0]
2879 repopath = args[0]
2880 if not hg.islocal(repopath):
2880 if not hg.islocal(repopath):
2881 raise util.Abort(_('only a local queue repository '
2881 raise util.Abort(_('only a local queue repository '
2882 'may be initialized'))
2882 'may be initialized'))
2883 else:
2883 else:
2884 repopath = cmdutil.findrepo(os.getcwd())
2884 repopath = cmdutil.findrepo(os.getcwd())
2885 if not repopath:
2885 if not repopath:
2886 raise util.Abort(_('there is no Mercurial repository here '
2886 raise util.Abort(_('there is no Mercurial repository here '
2887 '(.hg not found)'))
2887 '(.hg not found)'))
2888 repo = hg.repository(ui, repopath)
2888 repo = hg.repository(ui, repopath)
2889 return qinit(ui, repo, True)
2889 return qinit(ui, repo, True)
2890
2890
2891 def mqcommand(orig, ui, repo, *args, **kwargs):
2891 def mqcommand(orig, ui, repo, *args, **kwargs):
2892 """Add --mq option to operate on patch repository instead of main"""
2892 """Add --mq option to operate on patch repository instead of main"""
2893
2893
2894 # some commands do not like getting unknown options
2894 # some commands do not like getting unknown options
2895 mq = kwargs.pop('mq', None)
2895 mq = kwargs.pop('mq', None)
2896
2896
2897 if not mq:
2897 if not mq:
2898 return orig(ui, repo, *args, **kwargs)
2898 return orig(ui, repo, *args, **kwargs)
2899
2899
2900 q = repo.mq
2900 q = repo.mq
2901 r = q.qrepo()
2901 r = q.qrepo()
2902 if not r:
2902 if not r:
2903 raise util.Abort(_('no queue repository'))
2903 raise util.Abort(_('no queue repository'))
2904 return orig(r.ui, r, *args, **kwargs)
2904 return orig(r.ui, r, *args, **kwargs)
2905
2905
2906 def summary(orig, ui, repo, *args, **kwargs):
2906 def summary(orig, ui, repo, *args, **kwargs):
2907 r = orig(ui, repo, *args, **kwargs)
2907 r = orig(ui, repo, *args, **kwargs)
2908 q = repo.mq
2908 q = repo.mq
2909 m = []
2909 m = []
2910 a, u = len(q.applied), len(q.unapplied(repo))
2910 a, u = len(q.applied), len(q.unapplied(repo))
2911 if a:
2911 if a:
2912 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2912 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2913 if u:
2913 if u:
2914 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2914 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2915 if m:
2915 if m:
2916 ui.write("mq: %s\n" % ', '.join(m))
2916 ui.write("mq: %s\n" % ', '.join(m))
2917 else:
2917 else:
2918 ui.note(_("mq: (empty queue)\n"))
2918 ui.note(_("mq: (empty queue)\n"))
2919 return r
2919 return r
2920
2920
2921 def uisetup(ui):
2921 def uisetup(ui):
2922 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2922 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2923
2923
2924 extensions.wrapcommand(commands.table, 'import', mqimport)
2924 extensions.wrapcommand(commands.table, 'import', mqimport)
2925 extensions.wrapcommand(commands.table, 'summary', summary)
2925 extensions.wrapcommand(commands.table, 'summary', summary)
2926
2926
2927 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2927 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2928 entry[1].extend(mqopt)
2928 entry[1].extend(mqopt)
2929
2929
2930 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
2930 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
2931
2931
2932 def dotable(cmdtable):
2932 def dotable(cmdtable):
2933 for cmd in cmdtable.keys():
2933 for cmd in cmdtable.keys():
2934 cmd = cmdutil.parsealiases(cmd)[0]
2934 cmd = cmdutil.parsealiases(cmd)[0]
2935 if cmd in nowrap:
2935 if cmd in nowrap:
2936 continue
2936 continue
2937 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
2937 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
2938 entry[1].extend(mqopt)
2938 entry[1].extend(mqopt)
2939
2939
2940 dotable(commands.table)
2940 dotable(commands.table)
2941
2941
2942 for extname, extmodule in extensions.extensions():
2942 for extname, extmodule in extensions.extensions():
2943 if extmodule.__file__ != __file__:
2943 if extmodule.__file__ != __file__:
2944 dotable(getattr(extmodule, 'cmdtable', {}))
2944 dotable(getattr(extmodule, 'cmdtable', {}))
2945
2945
2946 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2946 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2947
2947
2948 cmdtable = {
2948 cmdtable = {
2949 "qapplied":
2949 "qapplied":
2950 (applied,
2950 (applied,
2951 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2951 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2952 _('hg qapplied [-1] [-s] [PATCH]')),
2952 _('hg qapplied [-1] [-s] [PATCH]')),
2953 "qclone":
2953 "qclone":
2954 (clone,
2954 (clone,
2955 [('', 'pull', None, _('use pull protocol to copy metadata')),
2955 [('', 'pull', None, _('use pull protocol to copy metadata')),
2956 ('U', 'noupdate', None, _('do not update the new working directories')),
2956 ('U', 'noupdate', None, _('do not update the new working directories')),
2957 ('', 'uncompressed', None,
2957 ('', 'uncompressed', None,
2958 _('use uncompressed transfer (fast over LAN)')),
2958 _('use uncompressed transfer (fast over LAN)')),
2959 ('p', 'patches', '',
2959 ('p', 'patches', '',
2960 _('location of source patch repository'), _('REPO')),
2960 _('location of source patch repository'), _('REPO')),
2961 ] + commands.remoteopts,
2961 ] + commands.remoteopts,
2962 _('hg qclone [OPTION]... SOURCE [DEST]')),
2962 _('hg qclone [OPTION]... SOURCE [DEST]')),
2963 "qcommit|qci":
2963 "qcommit|qci":
2964 (commit,
2964 (commit,
2965 commands.table["^commit|ci"][1],
2965 commands.table["^commit|ci"][1],
2966 _('hg qcommit [OPTION]... [FILE]...')),
2966 _('hg qcommit [OPTION]... [FILE]...')),
2967 "^qdiff":
2967 "^qdiff":
2968 (diff,
2968 (diff,
2969 commands.diffopts + commands.diffopts2 + commands.walkopts,
2969 commands.diffopts + commands.diffopts2 + commands.walkopts,
2970 _('hg qdiff [OPTION]... [FILE]...')),
2970 _('hg qdiff [OPTION]... [FILE]...')),
2971 "qdelete|qremove|qrm":
2971 "qdelete|qremove|qrm":
2972 (delete,
2972 (delete,
2973 [('k', 'keep', None, _('keep patch file')),
2973 [('k', 'keep', None, _('keep patch file')),
2974 ('r', 'rev', [],
2974 ('r', 'rev', [],
2975 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2975 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2976 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2976 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2977 'qfold':
2977 'qfold':
2978 (fold,
2978 (fold,
2979 [('e', 'edit', None, _('edit patch header')),
2979 [('e', 'edit', None, _('edit patch header')),
2980 ('k', 'keep', None, _('keep folded patch files')),
2980 ('k', 'keep', None, _('keep folded patch files')),
2981 ] + commands.commitopts,
2981 ] + commands.commitopts,
2982 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2982 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2983 'qgoto':
2983 'qgoto':
2984 (goto,
2984 (goto,
2985 [('f', 'force', None, _('overwrite any local changes'))],
2985 [('f', 'force', None, _('overwrite any local changes'))],
2986 _('hg qgoto [OPTION]... PATCH')),
2986 _('hg qgoto [OPTION]... PATCH')),
2987 'qguard':
2987 'qguard':
2988 (guard,
2988 (guard,
2989 [('l', 'list', None, _('list all patches and guards')),
2989 [('l', 'list', None, _('list all patches and guards')),
2990 ('n', 'none', None, _('drop all guards'))],
2990 ('n', 'none', None, _('drop all guards'))],
2991 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2991 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2992 'qheader': (header, [], _('hg qheader [PATCH]')),
2992 'qheader': (header, [], _('hg qheader [PATCH]')),
2993 "qimport":
2993 "qimport":
2994 (qimport,
2994 (qimport,
2995 [('e', 'existing', None, _('import file in patch directory')),
2995 [('e', 'existing', None, _('import file in patch directory')),
2996 ('n', 'name', '',
2996 ('n', 'name', '',
2997 _('name of patch file'), _('NAME')),
2997 _('name of patch file'), _('NAME')),
2998 ('f', 'force', None, _('overwrite existing files')),
2998 ('f', 'force', None, _('overwrite existing files')),
2999 ('r', 'rev', [],
2999 ('r', 'rev', [],
3000 _('place existing revisions under mq control'), _('REV')),
3000 _('place existing revisions under mq control'), _('REV')),
3001 ('g', 'git', None, _('use git extended diff format')),
3001 ('g', 'git', None, _('use git extended diff format')),
3002 ('P', 'push', None, _('qpush after importing'))],
3002 ('P', 'push', None, _('qpush after importing'))],
3003 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3003 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3004 "^qinit":
3004 "^qinit":
3005 (init,
3005 (init,
3006 [('c', 'create-repo', None, _('create queue repository'))],
3006 [('c', 'create-repo', None, _('create queue repository'))],
3007 _('hg qinit [-c]')),
3007 _('hg qinit [-c]')),
3008 "^qnew":
3008 "^qnew":
3009 (new,
3009 (new,
3010 [('e', 'edit', None, _('edit commit message')),
3010 [('e', 'edit', None, _('edit commit message')),
3011 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3011 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3012 ('g', 'git', None, _('use git extended diff format')),
3012 ('g', 'git', None, _('use git extended diff format')),
3013 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3013 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3014 ('u', 'user', '',
3014 ('u', 'user', '',
3015 _('add "From: <USER>" to patch'), _('USER')),
3015 _('add "From: <USER>" to patch'), _('USER')),
3016 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3016 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3017 ('d', 'date', '',
3017 ('d', 'date', '',
3018 _('add "Date: <DATE>" to patch'), _('DATE'))
3018 _('add "Date: <DATE>" to patch'), _('DATE'))
3019 ] + commands.walkopts + commands.commitopts,
3019 ] + commands.walkopts + commands.commitopts,
3020 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3020 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3021 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3021 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3022 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3022 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3023 "^qpop":
3023 "^qpop":
3024 (pop,
3024 (pop,
3025 [('a', 'all', None, _('pop all patches')),
3025 [('a', 'all', None, _('pop all patches')),
3026 ('n', 'name', '',
3026 ('n', 'name', '',
3027 _('queue name to pop (DEPRECATED)'), _('NAME')),
3027 _('queue name to pop (DEPRECATED)'), _('NAME')),
3028 ('f', 'force', None, _('forget any local changes to patched files'))],
3028 ('f', 'force', None, _('forget any local changes to patched files'))],
3029 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
3029 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
3030 "^qpush":
3030 "^qpush":
3031 (push,
3031 (push,
3032 [('f', 'force', None, _('apply if the patch has rejects')),
3032 [('f', 'force', None, _('apply if the patch has rejects')),
3033 ('l', 'list', None, _('list patch name in commit text')),
3033 ('l', 'list', None, _('list patch name in commit text')),
3034 ('a', 'all', None, _('apply all patches')),
3034 ('a', 'all', None, _('apply all patches')),
3035 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3035 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3036 ('n', 'name', '',
3036 ('n', 'name', '',
3037 _('merge queue name (DEPRECATED)'), _('NAME')),
3037 _('merge queue name (DEPRECATED)'), _('NAME')),
3038 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3038 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3039 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
3039 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
3040 "^qrefresh":
3040 "^qrefresh":
3041 (refresh,
3041 (refresh,
3042 [('e', 'edit', None, _('edit commit message')),
3042 [('e', 'edit', None, _('edit commit message')),
3043 ('g', 'git', None, _('use git extended diff format')),
3043 ('g', 'git', None, _('use git extended diff format')),
3044 ('s', 'short', None,
3044 ('s', 'short', None,
3045 _('refresh only files already in the patch and specified files')),
3045 _('refresh only files already in the patch and specified files')),
3046 ('U', 'currentuser', None,
3046 ('U', 'currentuser', None,
3047 _('add/update author field in patch with current user')),
3047 _('add/update author field in patch with current user')),
3048 ('u', 'user', '',
3048 ('u', 'user', '',
3049 _('add/update author field in patch with given user'), _('USER')),
3049 _('add/update author field in patch with given user'), _('USER')),
3050 ('D', 'currentdate', None,
3050 ('D', 'currentdate', None,
3051 _('add/update date field in patch with current date')),
3051 _('add/update date field in patch with current date')),
3052 ('d', 'date', '',
3052 ('d', 'date', '',
3053 _('add/update date field in patch with given date'), _('DATE'))
3053 _('add/update date field in patch with given date'), _('DATE'))
3054 ] + commands.walkopts + commands.commitopts,
3054 ] + commands.walkopts + commands.commitopts,
3055 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3055 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3056 'qrename|qmv':
3056 'qrename|qmv':
3057 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3057 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3058 "qrestore":
3058 "qrestore":
3059 (restore,
3059 (restore,
3060 [('d', 'delete', None, _('delete save entry')),
3060 [('d', 'delete', None, _('delete save entry')),
3061 ('u', 'update', None, _('update queue working directory'))],
3061 ('u', 'update', None, _('update queue working directory'))],
3062 _('hg qrestore [-d] [-u] REV')),
3062 _('hg qrestore [-d] [-u] REV')),
3063 "qsave":
3063 "qsave":
3064 (save,
3064 (save,
3065 [('c', 'copy', None, _('copy patch directory')),
3065 [('c', 'copy', None, _('copy patch directory')),
3066 ('n', 'name', '',
3066 ('n', 'name', '',
3067 _('copy directory name'), _('NAME')),
3067 _('copy directory name'), _('NAME')),
3068 ('e', 'empty', None, _('clear queue status file')),
3068 ('e', 'empty', None, _('clear queue status file')),
3069 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3069 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3070 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3070 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3071 "qselect":
3071 "qselect":
3072 (select,
3072 (select,
3073 [('n', 'none', None, _('disable all guards')),
3073 [('n', 'none', None, _('disable all guards')),
3074 ('s', 'series', None, _('list all guards in series file')),
3074 ('s', 'series', None, _('list all guards in series file')),
3075 ('', 'pop', None, _('pop to before first guarded applied patch')),
3075 ('', 'pop', None, _('pop to before first guarded applied patch')),
3076 ('', 'reapply', None, _('pop, then reapply patches'))],
3076 ('', 'reapply', None, _('pop, then reapply patches'))],
3077 _('hg qselect [OPTION]... [GUARD]...')),
3077 _('hg qselect [OPTION]... [GUARD]...')),
3078 "qseries":
3078 "qseries":
3079 (series,
3079 (series,
3080 [('m', 'missing', None, _('print patches not in series')),
3080 [('m', 'missing', None, _('print patches not in series')),
3081 ] + seriesopts,
3081 ] + seriesopts,
3082 _('hg qseries [-ms]')),
3082 _('hg qseries [-ms]')),
3083 "strip":
3083 "strip":
3084 (strip,
3084 (strip,
3085 [('f', 'force', None, _('force removal of changesets even if the '
3085 [('f', 'force', None, _('force removal of changesets even if the '
3086 'working directory has uncommitted changes')),
3086 'working directory has uncommitted changes')),
3087 ('b', 'backup', None, _('bundle only changesets with local revision'
3087 ('b', 'backup', None, _('bundle only changesets with local revision'
3088 ' number greater than REV which are not'
3088 ' number greater than REV which are not'
3089 ' descendants of REV (DEPRECATED)')),
3089 ' descendants of REV (DEPRECATED)')),
3090 ('n', 'nobackup', None, _('no backups'))],
3090 ('n', 'nobackup', None, _('no backups'))],
3091 _('hg strip [-f] [-n] REV...')),
3091 _('hg strip [-f] [-n] REV...')),
3092 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3092 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3093 "qunapplied":
3093 "qunapplied":
3094 (unapplied,
3094 (unapplied,
3095 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3095 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3096 _('hg qunapplied [-1] [-s] [PATCH]')),
3096 _('hg qunapplied [-1] [-s] [PATCH]')),
3097 "qfinish":
3097 "qfinish":
3098 (finish,
3098 (finish,
3099 [('a', 'applied', None, _('finish all applied changesets'))],
3099 [('a', 'applied', None, _('finish all applied changesets'))],
3100 _('hg qfinish [-a] [REV]...')),
3100 _('hg qfinish [-a] [REV]...')),
3101 'qqueue':
3101 'qqueue':
3102 (qqueue,
3102 (qqueue,
3103 [
3103 [
3104 ('l', 'list', False, _('list all available queues')),
3104 ('l', 'list', False, _('list all available queues')),
3105 ('c', 'create', False, _('create new queue')),
3105 ('c', 'create', False, _('create new queue')),
3106 ('', 'rename', False, _('rename active queue')),
3106 ('', 'rename', False, _('rename active queue')),
3107 ('', 'delete', False, _('delete reference to queue')),
3107 ('', 'delete', False, _('delete reference to queue')),
3108 ('', 'purge', False, _('delete queue, and remove patch dir')),
3108 ('', 'purge', False, _('delete queue, and remove patch dir')),
3109 ],
3109 ],
3110 _('[OPTION] [QUEUE]')),
3110 _('[OPTION] [QUEUE]')),
3111 }
3111 }
3112
3112
3113 colortable = {'qguard.negative': 'red',
3113 colortable = {'qguard.negative': 'red',
3114 'qguard.positive': 'yellow',
3114 'qguard.positive': 'yellow',
3115 'qguard.unguarded': 'green',
3115 'qguard.unguarded': 'green',
3116 'qseries.applied': 'blue bold underline',
3116 'qseries.applied': 'blue bold underline',
3117 'qseries.guarded': 'black bold',
3117 'qseries.guarded': 'black bold',
3118 'qseries.missing': 'red bold',
3118 'qseries.missing': 'red bold',
3119 'qseries.unapplied': 'black bold'}
3119 'qseries.unapplied': 'black bold'}
@@ -1,573 +1,573 b''
1 # record.py
1 # record.py
2 #
2 #
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.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 '''commands to interactively select changes for commit/qrefresh'''
8 '''commands to interactively select changes for commit/qrefresh'''
9
9
10 from mercurial.i18n import gettext, _
10 from mercurial.i18n import gettext, _
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 from mercurial import util
12 from mercurial import util
13 import copy, cStringIO, errno, os, re, tempfile
13 import copy, cStringIO, errno, os, re, tempfile
14
14
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
16
16
17 def scanpatch(fp):
17 def scanpatch(fp):
18 """like patch.iterhunks, but yield different events
18 """like patch.iterhunks, but yield different events
19
19
20 - ('file', [header_lines + fromfile + tofile])
20 - ('file', [header_lines + fromfile + tofile])
21 - ('context', [context_lines])
21 - ('context', [context_lines])
22 - ('hunk', [hunk_lines])
22 - ('hunk', [hunk_lines])
23 - ('range', (-start,len, +start,len, diffp))
23 - ('range', (-start,len, +start,len, diffp))
24 """
24 """
25 lr = patch.linereader(fp)
25 lr = patch.linereader(fp)
26
26
27 def scanwhile(first, p):
27 def scanwhile(first, p):
28 """scan lr while predicate holds"""
28 """scan lr while predicate holds"""
29 lines = [first]
29 lines = [first]
30 while True:
30 while True:
31 line = lr.readline()
31 line = lr.readline()
32 if not line:
32 if not line:
33 break
33 break
34 if p(line):
34 if p(line):
35 lines.append(line)
35 lines.append(line)
36 else:
36 else:
37 lr.push(line)
37 lr.push(line)
38 break
38 break
39 return lines
39 return lines
40
40
41 while True:
41 while True:
42 line = lr.readline()
42 line = lr.readline()
43 if not line:
43 if not line:
44 break
44 break
45 if line.startswith('diff --git a/'):
45 if line.startswith('diff --git a/'):
46 def notheader(line):
46 def notheader(line):
47 s = line.split(None, 1)
47 s = line.split(None, 1)
48 return not s or s[0] not in ('---', 'diff')
48 return not s or s[0] not in ('---', 'diff')
49 header = scanwhile(line, notheader)
49 header = scanwhile(line, notheader)
50 fromfile = lr.readline()
50 fromfile = lr.readline()
51 if fromfile.startswith('---'):
51 if fromfile.startswith('---'):
52 tofile = lr.readline()
52 tofile = lr.readline()
53 header += [fromfile, tofile]
53 header += [fromfile, tofile]
54 else:
54 else:
55 lr.push(fromfile)
55 lr.push(fromfile)
56 yield 'file', header
56 yield 'file', header
57 elif line[0] == ' ':
57 elif line[0] == ' ':
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
59 elif line[0] in '-+':
59 elif line[0] in '-+':
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
61 else:
61 else:
62 m = lines_re.match(line)
62 m = lines_re.match(line)
63 if m:
63 if m:
64 yield 'range', m.groups()
64 yield 'range', m.groups()
65 else:
65 else:
66 raise patch.PatchError('unknown patch content: %r' % line)
66 raise patch.PatchError('unknown patch content: %r' % line)
67
67
68 class header(object):
68 class header(object):
69 """patch header
69 """patch header
70
70
71 XXX shoudn't we move this to mercurial/patch.py ?
71 XXX shoudn't we move this to mercurial/patch.py ?
72 """
72 """
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
77
77
78 def __init__(self, header):
78 def __init__(self, header):
79 self.header = header
79 self.header = header
80 self.hunks = []
80 self.hunks = []
81
81
82 def binary(self):
82 def binary(self):
83 for h in self.header:
83 for h in self.header:
84 if h.startswith('index '):
84 if h.startswith('index '):
85 return True
85 return True
86
86
87 def pretty(self, fp):
87 def pretty(self, fp):
88 for h in self.header:
88 for h in self.header:
89 if h.startswith('index '):
89 if h.startswith('index '):
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
91 break
91 break
92 if self.pretty_re.match(h):
92 if self.pretty_re.match(h):
93 fp.write(h)
93 fp.write(h)
94 if self.binary():
94 if self.binary():
95 fp.write(_('this is a binary file\n'))
95 fp.write(_('this is a binary file\n'))
96 break
96 break
97 if h.startswith('---'):
97 if h.startswith('---'):
98 fp.write(_('%d hunks, %d lines changed\n') %
98 fp.write(_('%d hunks, %d lines changed\n') %
99 (len(self.hunks),
99 (len(self.hunks),
100 sum([max(h.added, h.removed) for h in self.hunks])))
100 sum([max(h.added, h.removed) for h in self.hunks])))
101 break
101 break
102 fp.write(h)
102 fp.write(h)
103
103
104 def write(self, fp):
104 def write(self, fp):
105 fp.write(''.join(self.header))
105 fp.write(''.join(self.header))
106
106
107 def allhunks(self):
107 def allhunks(self):
108 for h in self.header:
108 for h in self.header:
109 if self.allhunks_re.match(h):
109 if self.allhunks_re.match(h):
110 return True
110 return True
111
111
112 def files(self):
112 def files(self):
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
114 if fromfile == tofile:
114 if fromfile == tofile:
115 return [fromfile]
115 return [fromfile]
116 return [fromfile, tofile]
116 return [fromfile, tofile]
117
117
118 def filename(self):
118 def filename(self):
119 return self.files()[-1]
119 return self.files()[-1]
120
120
121 def __repr__(self):
121 def __repr__(self):
122 return '<header %s>' % (' '.join(map(repr, self.files())))
122 return '<header %s>' % (' '.join(map(repr, self.files())))
123
123
124 def special(self):
124 def special(self):
125 for h in self.header:
125 for h in self.header:
126 if self.special_re.match(h):
126 if self.special_re.match(h):
127 return True
127 return True
128
128
129 def countchanges(hunk):
129 def countchanges(hunk):
130 """hunk -> (n+,n-)"""
130 """hunk -> (n+,n-)"""
131 add = len([h for h in hunk if h[0] == '+'])
131 add = len([h for h in hunk if h[0] == '+'])
132 rem = len([h for h in hunk if h[0] == '-'])
132 rem = len([h for h in hunk if h[0] == '-'])
133 return add, rem
133 return add, rem
134
134
135 class hunk(object):
135 class hunk(object):
136 """patch hunk
136 """patch hunk
137
137
138 XXX shouldn't we merge this with patch.hunk ?
138 XXX shouldn't we merge this with patch.hunk ?
139 """
139 """
140 maxcontext = 3
140 maxcontext = 3
141
141
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
143 def trimcontext(number, lines):
143 def trimcontext(number, lines):
144 delta = len(lines) - self.maxcontext
144 delta = len(lines) - self.maxcontext
145 if False and delta > 0:
145 if False and delta > 0:
146 return number + delta, lines[:self.maxcontext]
146 return number + delta, lines[:self.maxcontext]
147 return number, lines
147 return number, lines
148
148
149 self.header = header
149 self.header = header
150 self.fromline, self.before = trimcontext(fromline, before)
150 self.fromline, self.before = trimcontext(fromline, before)
151 self.toline, self.after = trimcontext(toline, after)
151 self.toline, self.after = trimcontext(toline, after)
152 self.proc = proc
152 self.proc = proc
153 self.hunk = hunk
153 self.hunk = hunk
154 self.added, self.removed = countchanges(self.hunk)
154 self.added, self.removed = countchanges(self.hunk)
155
155
156 def write(self, fp):
156 def write(self, fp):
157 delta = len(self.before) + len(self.after)
157 delta = len(self.before) + len(self.after)
158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
159 delta -= 1
159 delta -= 1
160 fromlen = delta + self.removed
160 fromlen = delta + self.removed
161 tolen = delta + self.added
161 tolen = delta + self.added
162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
163 (self.fromline, fromlen, self.toline, tolen,
163 (self.fromline, fromlen, self.toline, tolen,
164 self.proc and (' ' + self.proc)))
164 self.proc and (' ' + self.proc)))
165 fp.write(''.join(self.before + self.hunk + self.after))
165 fp.write(''.join(self.before + self.hunk + self.after))
166
166
167 pretty = write
167 pretty = write
168
168
169 def filename(self):
169 def filename(self):
170 return self.header.filename()
170 return self.header.filename()
171
171
172 def __repr__(self):
172 def __repr__(self):
173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
174
174
175 def parsepatch(fp):
175 def parsepatch(fp):
176 """patch -> [] of hunks """
176 """patch -> [] of hunks """
177 class parser(object):
177 class parser(object):
178 """patch parsing state machine"""
178 """patch parsing state machine"""
179 def __init__(self):
179 def __init__(self):
180 self.fromline = 0
180 self.fromline = 0
181 self.toline = 0
181 self.toline = 0
182 self.proc = ''
182 self.proc = ''
183 self.header = None
183 self.header = None
184 self.context = []
184 self.context = []
185 self.before = []
185 self.before = []
186 self.hunk = []
186 self.hunk = []
187 self.stream = []
187 self.stream = []
188
188
189 def addrange(self, limits):
189 def addrange(self, limits):
190 fromstart, fromend, tostart, toend, proc = limits
190 fromstart, fromend, tostart, toend, proc = limits
191 self.fromline = int(fromstart)
191 self.fromline = int(fromstart)
192 self.toline = int(tostart)
192 self.toline = int(tostart)
193 self.proc = proc
193 self.proc = proc
194
194
195 def addcontext(self, context):
195 def addcontext(self, context):
196 if self.hunk:
196 if self.hunk:
197 h = hunk(self.header, self.fromline, self.toline, self.proc,
197 h = hunk(self.header, self.fromline, self.toline, self.proc,
198 self.before, self.hunk, context)
198 self.before, self.hunk, context)
199 self.header.hunks.append(h)
199 self.header.hunks.append(h)
200 self.stream.append(h)
200 self.stream.append(h)
201 self.fromline += len(self.before) + h.removed
201 self.fromline += len(self.before) + h.removed
202 self.toline += len(self.before) + h.added
202 self.toline += len(self.before) + h.added
203 self.before = []
203 self.before = []
204 self.hunk = []
204 self.hunk = []
205 self.proc = ''
205 self.proc = ''
206 self.context = context
206 self.context = context
207
207
208 def addhunk(self, hunk):
208 def addhunk(self, hunk):
209 if self.context:
209 if self.context:
210 self.before = self.context
210 self.before = self.context
211 self.context = []
211 self.context = []
212 self.hunk = hunk
212 self.hunk = hunk
213
213
214 def newfile(self, hdr):
214 def newfile(self, hdr):
215 self.addcontext([])
215 self.addcontext([])
216 h = header(hdr)
216 h = header(hdr)
217 self.stream.append(h)
217 self.stream.append(h)
218 self.header = h
218 self.header = h
219
219
220 def finished(self):
220 def finished(self):
221 self.addcontext([])
221 self.addcontext([])
222 return self.stream
222 return self.stream
223
223
224 transitions = {
224 transitions = {
225 'file': {'context': addcontext,
225 'file': {'context': addcontext,
226 'file': newfile,
226 'file': newfile,
227 'hunk': addhunk,
227 'hunk': addhunk,
228 'range': addrange},
228 'range': addrange},
229 'context': {'file': newfile,
229 'context': {'file': newfile,
230 'hunk': addhunk,
230 'hunk': addhunk,
231 'range': addrange},
231 'range': addrange},
232 'hunk': {'context': addcontext,
232 'hunk': {'context': addcontext,
233 'file': newfile,
233 'file': newfile,
234 'range': addrange},
234 'range': addrange},
235 'range': {'context': addcontext,
235 'range': {'context': addcontext,
236 'hunk': addhunk},
236 'hunk': addhunk},
237 }
237 }
238
238
239 p = parser()
239 p = parser()
240
240
241 state = 'context'
241 state = 'context'
242 for newstate, data in scanpatch(fp):
242 for newstate, data in scanpatch(fp):
243 try:
243 try:
244 p.transitions[state][newstate](p, data)
244 p.transitions[state][newstate](p, data)
245 except KeyError:
245 except KeyError:
246 raise patch.PatchError('unhandled transition: %s -> %s' %
246 raise patch.PatchError('unhandled transition: %s -> %s' %
247 (state, newstate))
247 (state, newstate))
248 state = newstate
248 state = newstate
249 return p.finished()
249 return p.finished()
250
250
251 def filterpatch(ui, chunks):
251 def filterpatch(ui, chunks):
252 """Interactively filter patch chunks into applied-only chunks"""
252 """Interactively filter patch chunks into applied-only chunks"""
253 chunks = list(chunks)
253 chunks = list(chunks)
254 chunks.reverse()
254 chunks.reverse()
255 seen = set()
255 seen = set()
256 def consumefile():
256 def consumefile():
257 """fetch next portion from chunks until a 'header' is seen
257 """fetch next portion from chunks until a 'header' is seen
258 NB: header == new-file mark
258 NB: header == new-file mark
259 """
259 """
260 consumed = []
260 consumed = []
261 while chunks:
261 while chunks:
262 if isinstance(chunks[-1], header):
262 if isinstance(chunks[-1], header):
263 break
263 break
264 else:
264 else:
265 consumed.append(chunks.pop())
265 consumed.append(chunks.pop())
266 return consumed
266 return consumed
267
267
268 resp_all = [None] # this two are changed from inside prompt,
268 resp_all = [None] # this two are changed from inside prompt,
269 resp_file = [None] # so can't be usual variables
269 resp_file = [None] # so can't be usual variables
270 applied = {} # 'filename' -> [] of chunks
270 applied = {} # 'filename' -> [] of chunks
271 def prompt(query):
271 def prompt(query):
272 """prompt query, and process base inputs
272 """prompt query, and process base inputs
273
273
274 - y/n for the rest of file
274 - y/n for the rest of file
275 - y/n for the rest
275 - y/n for the rest
276 - ? (help)
276 - ? (help)
277 - q (quit)
277 - q (quit)
278
278
279 Returns True/False and sets reps_all and resp_file as
279 Returns True/False and sets reps_all and resp_file as
280 appropriate.
280 appropriate.
281 """
281 """
282 if resp_all[0] is not None:
282 if resp_all[0] is not None:
283 return resp_all[0]
283 return resp_all[0]
284 if resp_file[0] is not None:
284 if resp_file[0] is not None:
285 return resp_file[0]
285 return resp_file[0]
286 while True:
286 while True:
287 resps = _('[Ynsfdaq?]')
287 resps = _('[Ynsfdaq?]')
288 choices = (_('&Yes, record this change'),
288 choices = (_('&Yes, record this change'),
289 _('&No, skip this change'),
289 _('&No, skip this change'),
290 _('&Skip remaining changes to this file'),
290 _('&Skip remaining changes to this file'),
291 _('Record remaining changes to this &file'),
291 _('Record remaining changes to this &file'),
292 _('&Done, skip remaining changes and files'),
292 _('&Done, skip remaining changes and files'),
293 _('Record &all changes to all remaining files'),
293 _('Record &all changes to all remaining files'),
294 _('&Quit, recording no changes'),
294 _('&Quit, recording no changes'),
295 _('&?'))
295 _('&?'))
296 r = ui.promptchoice("%s %s" % (query, resps), choices)
296 r = ui.promptchoice("%s %s" % (query, resps), choices)
297 ui.write("\n")
297 ui.write("\n")
298 if r == 7: # ?
298 if r == 7: # ?
299 doc = gettext(record.__doc__)
299 doc = gettext(record.__doc__)
300 c = doc.find('::') + 2
300 c = doc.find('::') + 2
301 for l in doc[c:].splitlines():
301 for l in doc[c:].splitlines():
302 if l.startswith(' '):
302 if l.startswith(' '):
303 ui.write(l.strip(), '\n')
303 ui.write(l.strip(), '\n')
304 continue
304 continue
305 elif r == 0: # yes
305 elif r == 0: # yes
306 ret = True
306 ret = True
307 elif r == 1: # no
307 elif r == 1: # no
308 ret = False
308 ret = False
309 elif r == 2: # Skip
309 elif r == 2: # Skip
310 ret = resp_file[0] = False
310 ret = resp_file[0] = False
311 elif r == 3: # file (Record remaining)
311 elif r == 3: # file (Record remaining)
312 ret = resp_file[0] = True
312 ret = resp_file[0] = True
313 elif r == 4: # done, skip remaining
313 elif r == 4: # done, skip remaining
314 ret = resp_all[0] = False
314 ret = resp_all[0] = False
315 elif r == 5: # all
315 elif r == 5: # all
316 ret = resp_all[0] = True
316 ret = resp_all[0] = True
317 elif r == 6: # quit
317 elif r == 6: # quit
318 raise util.Abort(_('user quit'))
318 raise util.Abort(_('user quit'))
319 return ret
319 return ret
320 pos, total = 0, len(chunks) - 1
320 pos, total = 0, len(chunks) - 1
321 while chunks:
321 while chunks:
322 pos = total - len(chunks) + 1
322 pos = total - len(chunks) + 1
323 chunk = chunks.pop()
323 chunk = chunks.pop()
324 if isinstance(chunk, header):
324 if isinstance(chunk, header):
325 # new-file mark
325 # new-file mark
326 resp_file = [None]
326 resp_file = [None]
327 fixoffset = 0
327 fixoffset = 0
328 hdr = ''.join(chunk.header)
328 hdr = ''.join(chunk.header)
329 if hdr in seen:
329 if hdr in seen:
330 consumefile()
330 consumefile()
331 continue
331 continue
332 seen.add(hdr)
332 seen.add(hdr)
333 if resp_all[0] is None:
333 if resp_all[0] is None:
334 chunk.pretty(ui)
334 chunk.pretty(ui)
335 r = prompt(_('examine changes to %s?') %
335 r = prompt(_('examine changes to %s?') %
336 _(' and ').join(map(repr, chunk.files())))
336 _(' and ').join(map(repr, chunk.files())))
337 if r:
337 if r:
338 applied[chunk.filename()] = [chunk]
338 applied[chunk.filename()] = [chunk]
339 if chunk.allhunks():
339 if chunk.allhunks():
340 applied[chunk.filename()] += consumefile()
340 applied[chunk.filename()] += consumefile()
341 else:
341 else:
342 consumefile()
342 consumefile()
343 else:
343 else:
344 # new hunk
344 # new hunk
345 if resp_file[0] is None and resp_all[0] is None:
345 if resp_file[0] is None and resp_all[0] is None:
346 chunk.pretty(ui)
346 chunk.pretty(ui)
347 r = total == 1 and prompt(_('record this change to %r?') %
347 r = total == 1 and prompt(_('record this change to %r?') %
348 chunk.filename()) \
348 chunk.filename()) \
349 or prompt(_('record change %d/%d to %r?') %
349 or prompt(_('record change %d/%d to %r?') %
350 (pos, total, chunk.filename()))
350 (pos, total, chunk.filename()))
351 if r:
351 if r:
352 if fixoffset:
352 if fixoffset:
353 chunk = copy.copy(chunk)
353 chunk = copy.copy(chunk)
354 chunk.toline += fixoffset
354 chunk.toline += fixoffset
355 applied[chunk.filename()].append(chunk)
355 applied[chunk.filename()].append(chunk)
356 else:
356 else:
357 fixoffset += chunk.removed - chunk.added
357 fixoffset += chunk.removed - chunk.added
358 return sum([h for h in applied.itervalues()
358 return sum([h for h in applied.itervalues()
359 if h[0].special() or len(h) > 1], [])
359 if h[0].special() or len(h) > 1], [])
360
360
361 def record(ui, repo, *pats, **opts):
361 def record(ui, repo, *pats, **opts):
362 '''interactively select changes to commit
362 '''interactively select changes to commit
363
363
364 If a list of files is omitted, all changes reported by :hg:`status`
364 If a list of files is omitted, all changes reported by :hg:`status`
365 will be candidates for recording.
365 will be candidates for recording.
366
366
367 See :hg:`help dates` for a list of formats valid for -d/--date.
367 See :hg:`help dates` for a list of formats valid for -d/--date.
368
368
369 You will be prompted for whether to record changes to each
369 You will be prompted for whether to record changes to each
370 modified file, and for files with multiple changes, for each
370 modified file, and for files with multiple changes, for each
371 change to use. For each query, the following responses are
371 change to use. For each query, the following responses are
372 possible::
372 possible::
373
373
374 y - record this change
374 y - record this change
375 n - skip this change
375 n - skip this change
376
376
377 s - skip remaining changes to this file
377 s - skip remaining changes to this file
378 f - record remaining changes to this file
378 f - record remaining changes to this file
379
379
380 d - done, skip remaining changes and files
380 d - done, skip remaining changes and files
381 a - record all changes to all remaining files
381 a - record all changes to all remaining files
382 q - quit, recording no changes
382 q - quit, recording no changes
383
383
384 ? - display help
384 ? - display help
385
385
386 This command is not available when committing a merge.'''
386 This command is not available when committing a merge.'''
387
387
388 dorecord(ui, repo, commands.commit, *pats, **opts)
388 dorecord(ui, repo, commands.commit, *pats, **opts)
389
389
390
390
391 def qrecord(ui, repo, patch, *pats, **opts):
391 def qrecord(ui, repo, patch, *pats, **opts):
392 '''interactively record a new patch
392 '''interactively record a new patch
393
393
394 See :hg:`help qnew` & :hg:`help record` for more information and
394 See :hg:`help qnew` & :hg:`help record` for more information and
395 usage.
395 usage.
396 '''
396 '''
397
397
398 try:
398 try:
399 mq = extensions.find('mq')
399 mq = extensions.find('mq')
400 except KeyError:
400 except KeyError:
401 raise util.Abort(_("'mq' extension not loaded"))
401 raise util.Abort(_("'mq' extension not loaded"))
402
402
403 def committomq(ui, repo, *pats, **opts):
403 def committomq(ui, repo, *pats, **opts):
404 mq.new(ui, repo, patch, *pats, **opts)
404 mq.new(ui, repo, patch, *pats, **opts)
405
405
406 opts = opts.copy()
406 opts = opts.copy()
407 opts['force'] = True # always 'qnew -f'
407 opts['force'] = True # always 'qnew -f'
408 dorecord(ui, repo, committomq, *pats, **opts)
408 dorecord(ui, repo, committomq, *pats, **opts)
409
409
410
410
411 def dorecord(ui, repo, commitfunc, *pats, **opts):
411 def dorecord(ui, repo, commitfunc, *pats, **opts):
412 if not ui.interactive():
412 if not ui.interactive():
413 raise util.Abort(_('running non-interactively, use commit instead'))
413 raise util.Abort(_('running non-interactively, use commit instead'))
414
414
415 def recordfunc(ui, repo, message, match, opts):
415 def recordfunc(ui, repo, message, match, opts):
416 """This is generic record driver.
416 """This is generic record driver.
417
417
418 Its job is to interactively filter local changes, and accordingly
418 Its job is to interactively filter local changes, and accordingly
419 prepare working dir into a state, where the job can be delegated to
419 prepare working dir into a state, where the job can be delegated to
420 non-interactive commit command such as 'commit' or 'qrefresh'.
420 non-interactive commit command such as 'commit' or 'qrefresh'.
421
421
422 After the actual job is done by non-interactive command, working dir
422 After the actual job is done by non-interactive command, working dir
423 state is restored to original.
423 state is restored to original.
424
424
425 In the end we'll record interesting changes, and everything else will be
425 In the end we'll record interesting changes, and everything else will be
426 left in place, so the user can continue his work.
426 left in place, so the user can continue his work.
427 """
427 """
428
428
429 merge = len(repo[None].parents()) > 1
429 merge = len(repo[None].parents()) > 1
430 if merge:
430 if merge:
431 raise util.Abort(_('cannot partially commit a merge '
431 raise util.Abort(_('cannot partially commit a merge '
432 '(use hg commit instead)'))
432 '(use hg commit instead)'))
433
433
434 changes = repo.status(match=match)[:3]
434 changes = repo.status(match=match)[:3]
435 diffopts = mdiff.diffopts(git=True, nodates=True)
435 diffopts = mdiff.diffopts(git=True, nodates=True)
436 chunks = patch.diff(repo, changes=changes, opts=diffopts)
436 chunks = patch.diff(repo, changes=changes, opts=diffopts)
437 fp = cStringIO.StringIO()
437 fp = cStringIO.StringIO()
438 fp.write(''.join(chunks))
438 fp.write(''.join(chunks))
439 fp.seek(0)
439 fp.seek(0)
440
440
441 # 1. filter patch, so we have intending-to apply subset of it
441 # 1. filter patch, so we have intending-to apply subset of it
442 chunks = filterpatch(ui, parsepatch(fp))
442 chunks = filterpatch(ui, parsepatch(fp))
443 del fp
443 del fp
444
444
445 contenders = set()
445 contenders = set()
446 for h in chunks:
446 for h in chunks:
447 try:
447 try:
448 contenders.update(set(h.files()))
448 contenders.update(set(h.files()))
449 except AttributeError:
449 except AttributeError:
450 pass
450 pass
451
451
452 changed = changes[0] + changes[1] + changes[2]
452 changed = changes[0] + changes[1] + changes[2]
453 newfiles = [f for f in changed if f in contenders]
453 newfiles = [f for f in changed if f in contenders]
454 if not newfiles:
454 if not newfiles:
455 ui.status(_('no changes to record\n'))
455 ui.status(_('no changes to record\n'))
456 return 0
456 return 0
457
457
458 modified = set(changes[0])
458 modified = set(changes[0])
459
459
460 # 2. backup changed files, so we can restore them in the end
460 # 2. backup changed files, so we can restore them in the end
461 backups = {}
461 backups = {}
462 backupdir = repo.join('record-backups')
462 backupdir = repo.join('record-backups')
463 try:
463 try:
464 os.mkdir(backupdir)
464 os.mkdir(backupdir)
465 except OSError, err:
465 except OSError, err:
466 if err.errno != errno.EEXIST:
466 if err.errno != errno.EEXIST:
467 raise
467 raise
468 try:
468 try:
469 # backup continues
469 # backup continues
470 for f in newfiles:
470 for f in newfiles:
471 if f not in modified:
471 if f not in modified:
472 continue
472 continue
473 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
473 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
474 dir=backupdir)
474 dir=backupdir)
475 os.close(fd)
475 os.close(fd)
476 ui.debug('backup %r as %r\n' % (f, tmpname))
476 ui.debug('backup %r as %r\n' % (f, tmpname))
477 util.copyfile(repo.wjoin(f), tmpname)
477 util.copyfile(repo.wjoin(f), tmpname)
478 backups[f] = tmpname
478 backups[f] = tmpname
479
479
480 fp = cStringIO.StringIO()
480 fp = cStringIO.StringIO()
481 for c in chunks:
481 for c in chunks:
482 if c.filename() in backups:
482 if c.filename() in backups:
483 c.write(fp)
483 c.write(fp)
484 dopatch = fp.tell()
484 dopatch = fp.tell()
485 fp.seek(0)
485 fp.seek(0)
486
486
487 # 3a. apply filtered patch to clean repo (clean)
487 # 3a. apply filtered patch to clean repo (clean)
488 if backups:
488 if backups:
489 hg.revert(repo, repo.dirstate.parents()[0],
489 hg.revert(repo, repo.dirstate.parents()[0],
490 lambda key: key in backups)
490 lambda key: key in backups)
491
491
492 # 3b. (apply)
492 # 3b. (apply)
493 if dopatch:
493 if dopatch:
494 try:
494 try:
495 ui.debug('applying patch\n')
495 ui.debug('applying patch\n')
496 ui.debug(fp.getvalue())
496 ui.debug(fp.getvalue())
497 pfiles = {}
497 pfiles = {}
498 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
498 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
499 eolmode=None)
499 eolmode=None)
500 patch.updatedir(ui, repo, pfiles)
500 cmdutil.updatedir(ui, repo, pfiles)
501 except patch.PatchError, err:
501 except patch.PatchError, err:
502 s = str(err)
502 s = str(err)
503 if s:
503 if s:
504 raise util.Abort(s)
504 raise util.Abort(s)
505 else:
505 else:
506 raise util.Abort(_('patch failed to apply'))
506 raise util.Abort(_('patch failed to apply'))
507 del fp
507 del fp
508
508
509 # 4. We prepared working directory according to filtered patch.
509 # 4. We prepared working directory according to filtered patch.
510 # Now is the time to delegate the job to commit/qrefresh or the like!
510 # Now is the time to delegate the job to commit/qrefresh or the like!
511
511
512 # it is important to first chdir to repo root -- we'll call a
512 # it is important to first chdir to repo root -- we'll call a
513 # highlevel command with list of pathnames relative to repo root
513 # highlevel command with list of pathnames relative to repo root
514 cwd = os.getcwd()
514 cwd = os.getcwd()
515 os.chdir(repo.root)
515 os.chdir(repo.root)
516 try:
516 try:
517 commitfunc(ui, repo, *newfiles, **opts)
517 commitfunc(ui, repo, *newfiles, **opts)
518 finally:
518 finally:
519 os.chdir(cwd)
519 os.chdir(cwd)
520
520
521 return 0
521 return 0
522 finally:
522 finally:
523 # 5. finally restore backed-up files
523 # 5. finally restore backed-up files
524 try:
524 try:
525 for realname, tmpname in backups.iteritems():
525 for realname, tmpname in backups.iteritems():
526 ui.debug('restoring %r to %r\n' % (tmpname, realname))
526 ui.debug('restoring %r to %r\n' % (tmpname, realname))
527 util.copyfile(tmpname, repo.wjoin(realname))
527 util.copyfile(tmpname, repo.wjoin(realname))
528 os.unlink(tmpname)
528 os.unlink(tmpname)
529 os.rmdir(backupdir)
529 os.rmdir(backupdir)
530 except OSError:
530 except OSError:
531 pass
531 pass
532
532
533 # wrap ui.write so diff output can be labeled/colorized
533 # wrap ui.write so diff output can be labeled/colorized
534 def wrapwrite(orig, *args, **kw):
534 def wrapwrite(orig, *args, **kw):
535 label = kw.pop('label', '')
535 label = kw.pop('label', '')
536 for chunk, l in patch.difflabel(lambda: args):
536 for chunk, l in patch.difflabel(lambda: args):
537 orig(chunk, label=label + l)
537 orig(chunk, label=label + l)
538 oldwrite = ui.write
538 oldwrite = ui.write
539 extensions.wrapfunction(ui, 'write', wrapwrite)
539 extensions.wrapfunction(ui, 'write', wrapwrite)
540 try:
540 try:
541 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
541 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
542 finally:
542 finally:
543 ui.write = oldwrite
543 ui.write = oldwrite
544
544
545 cmdtable = {
545 cmdtable = {
546 "record":
546 "record":
547 (record,
547 (record,
548
548
549 # add commit options
549 # add commit options
550 commands.table['^commit|ci'][1],
550 commands.table['^commit|ci'][1],
551
551
552 _('hg record [OPTION]... [FILE]...')),
552 _('hg record [OPTION]... [FILE]...')),
553 }
553 }
554
554
555
555
556 def uisetup(ui):
556 def uisetup(ui):
557 try:
557 try:
558 mq = extensions.find('mq')
558 mq = extensions.find('mq')
559 except KeyError:
559 except KeyError:
560 return
560 return
561
561
562 qcmdtable = {
562 qcmdtable = {
563 "qrecord":
563 "qrecord":
564 (qrecord,
564 (qrecord,
565
565
566 # add qnew options, except '--force'
566 # add qnew options, except '--force'
567 [opt for opt in mq.cmdtable['^qnew'][1] if opt[1] != 'force'],
567 [opt for opt in mq.cmdtable['^qnew'][1] if opt[1] != 'force'],
568
568
569 _('hg qrecord [OPTION]... PATCH [FILE]...')),
569 _('hg qrecord [OPTION]... PATCH [FILE]...')),
570 }
570 }
571
571
572 cmdtable.update(qcmdtable)
572 cmdtable.update(qcmdtable)
573
573
@@ -1,620 +1,620 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
19 from mercurial import patch, revlog, util, error, discovery
19 from mercurial import patch, revlog, util, error, discovery
20
20
21 class transplantentry(object):
21 class transplantentry(object):
22 def __init__(self, lnode, rnode):
22 def __init__(self, lnode, rnode):
23 self.lnode = lnode
23 self.lnode = lnode
24 self.rnode = rnode
24 self.rnode = rnode
25
25
26 class transplants(object):
26 class transplants(object):
27 def __init__(self, path=None, transplantfile=None, opener=None):
27 def __init__(self, path=None, transplantfile=None, opener=None):
28 self.path = path
28 self.path = path
29 self.transplantfile = transplantfile
29 self.transplantfile = transplantfile
30 self.opener = opener
30 self.opener = opener
31
31
32 if not opener:
32 if not opener:
33 self.opener = util.opener(self.path)
33 self.opener = util.opener(self.path)
34 self.transplants = []
34 self.transplants = []
35 self.dirty = False
35 self.dirty = False
36 self.read()
36 self.read()
37
37
38 def read(self):
38 def read(self):
39 abspath = os.path.join(self.path, self.transplantfile)
39 abspath = os.path.join(self.path, self.transplantfile)
40 if self.transplantfile and os.path.exists(abspath):
40 if self.transplantfile and os.path.exists(abspath):
41 for line in self.opener(self.transplantfile).read().splitlines():
41 for line in self.opener(self.transplantfile).read().splitlines():
42 lnode, rnode = map(revlog.bin, line.split(':'))
42 lnode, rnode = map(revlog.bin, line.split(':'))
43 self.transplants.append(transplantentry(lnode, rnode))
43 self.transplants.append(transplantentry(lnode, rnode))
44
44
45 def write(self):
45 def write(self):
46 if self.dirty and self.transplantfile:
46 if self.dirty and self.transplantfile:
47 if not os.path.isdir(self.path):
47 if not os.path.isdir(self.path):
48 os.mkdir(self.path)
48 os.mkdir(self.path)
49 fp = self.opener(self.transplantfile, 'w')
49 fp = self.opener(self.transplantfile, 'w')
50 for c in self.transplants:
50 for c in self.transplants:
51 l, r = map(revlog.hex, (c.lnode, c.rnode))
51 l, r = map(revlog.hex, (c.lnode, c.rnode))
52 fp.write(l + ':' + r + '\n')
52 fp.write(l + ':' + r + '\n')
53 fp.close()
53 fp.close()
54 self.dirty = False
54 self.dirty = False
55
55
56 def get(self, rnode):
56 def get(self, rnode):
57 return [t for t in self.transplants if t.rnode == rnode]
57 return [t for t in self.transplants if t.rnode == rnode]
58
58
59 def set(self, lnode, rnode):
59 def set(self, lnode, rnode):
60 self.transplants.append(transplantentry(lnode, rnode))
60 self.transplants.append(transplantentry(lnode, rnode))
61 self.dirty = True
61 self.dirty = True
62
62
63 def remove(self, transplant):
63 def remove(self, transplant):
64 del self.transplants[self.transplants.index(transplant)]
64 del self.transplants[self.transplants.index(transplant)]
65 self.dirty = True
65 self.dirty = True
66
66
67 class transplanter(object):
67 class transplanter(object):
68 def __init__(self, ui, repo):
68 def __init__(self, ui, repo):
69 self.ui = ui
69 self.ui = ui
70 self.path = repo.join('transplant')
70 self.path = repo.join('transplant')
71 self.opener = util.opener(self.path)
71 self.opener = util.opener(self.path)
72 self.transplants = transplants(self.path, 'transplants',
72 self.transplants = transplants(self.path, 'transplants',
73 opener=self.opener)
73 opener=self.opener)
74
74
75 def applied(self, repo, node, parent):
75 def applied(self, repo, node, parent):
76 '''returns True if a node is already an ancestor of parent
76 '''returns True if a node is already an ancestor of parent
77 or has already been transplanted'''
77 or has already been transplanted'''
78 if hasnode(repo, node):
78 if hasnode(repo, node):
79 if node in repo.changelog.reachable(parent, stop=node):
79 if node in repo.changelog.reachable(parent, stop=node):
80 return True
80 return True
81 for t in self.transplants.get(node):
81 for t in self.transplants.get(node):
82 # it might have been stripped
82 # it might have been stripped
83 if not hasnode(repo, t.lnode):
83 if not hasnode(repo, t.lnode):
84 self.transplants.remove(t)
84 self.transplants.remove(t)
85 return False
85 return False
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 return True
87 return True
88 return False
88 return False
89
89
90 def apply(self, repo, source, revmap, merges, opts={}):
90 def apply(self, repo, source, revmap, merges, opts={}):
91 '''apply the revisions in revmap one by one in revision order'''
91 '''apply the revisions in revmap one by one in revision order'''
92 revs = sorted(revmap)
92 revs = sorted(revmap)
93 p1, p2 = repo.dirstate.parents()
93 p1, p2 = repo.dirstate.parents()
94 pulls = []
94 pulls = []
95 diffopts = patch.diffopts(self.ui, opts)
95 diffopts = patch.diffopts(self.ui, opts)
96 diffopts.git = True
96 diffopts.git = True
97
97
98 lock = wlock = None
98 lock = wlock = None
99 try:
99 try:
100 wlock = repo.wlock()
100 wlock = repo.wlock()
101 lock = repo.lock()
101 lock = repo.lock()
102 for rev in revs:
102 for rev in revs:
103 node = revmap[rev]
103 node = revmap[rev]
104 revstr = '%s:%s' % (rev, revlog.short(node))
104 revstr = '%s:%s' % (rev, revlog.short(node))
105
105
106 if self.applied(repo, node, p1):
106 if self.applied(repo, node, p1):
107 self.ui.warn(_('skipping already applied revision %s\n') %
107 self.ui.warn(_('skipping already applied revision %s\n') %
108 revstr)
108 revstr)
109 continue
109 continue
110
110
111 parents = source.changelog.parents(node)
111 parents = source.changelog.parents(node)
112 if not opts.get('filter'):
112 if not opts.get('filter'):
113 # If the changeset parent is the same as the
113 # If the changeset parent is the same as the
114 # wdir's parent, just pull it.
114 # wdir's parent, just pull it.
115 if parents[0] == p1:
115 if parents[0] == p1:
116 pulls.append(node)
116 pulls.append(node)
117 p1 = node
117 p1 = node
118 continue
118 continue
119 if pulls:
119 if pulls:
120 if source != repo:
120 if source != repo:
121 repo.pull(source, heads=pulls)
121 repo.pull(source, heads=pulls)
122 merge.update(repo, pulls[-1], False, False, None)
122 merge.update(repo, pulls[-1], False, False, None)
123 p1, p2 = repo.dirstate.parents()
123 p1, p2 = repo.dirstate.parents()
124 pulls = []
124 pulls = []
125
125
126 domerge = False
126 domerge = False
127 if node in merges:
127 if node in merges:
128 # pulling all the merge revs at once would mean we
128 # pulling all the merge revs at once would mean we
129 # couldn't transplant after the latest even if
129 # couldn't transplant after the latest even if
130 # transplants before them fail.
130 # transplants before them fail.
131 domerge = True
131 domerge = True
132 if not hasnode(repo, node):
132 if not hasnode(repo, node):
133 repo.pull(source, heads=[node])
133 repo.pull(source, heads=[node])
134
134
135 if parents[1] != revlog.nullid:
135 if parents[1] != revlog.nullid:
136 self.ui.note(_('skipping merge changeset %s:%s\n')
136 self.ui.note(_('skipping merge changeset %s:%s\n')
137 % (rev, revlog.short(node)))
137 % (rev, revlog.short(node)))
138 patchfile = None
138 patchfile = None
139 else:
139 else:
140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
141 fp = os.fdopen(fd, 'w')
141 fp = os.fdopen(fd, 'w')
142 gen = patch.diff(source, parents[0], node, opts=diffopts)
142 gen = patch.diff(source, parents[0], node, opts=diffopts)
143 for chunk in gen:
143 for chunk in gen:
144 fp.write(chunk)
144 fp.write(chunk)
145 fp.close()
145 fp.close()
146
146
147 del revmap[rev]
147 del revmap[rev]
148 if patchfile or domerge:
148 if patchfile or domerge:
149 try:
149 try:
150 n = self.applyone(repo, node,
150 n = self.applyone(repo, node,
151 source.changelog.read(node),
151 source.changelog.read(node),
152 patchfile, merge=domerge,
152 patchfile, merge=domerge,
153 log=opts.get('log'),
153 log=opts.get('log'),
154 filter=opts.get('filter'))
154 filter=opts.get('filter'))
155 if n and domerge:
155 if n and domerge:
156 self.ui.status(_('%s merged at %s\n') % (revstr,
156 self.ui.status(_('%s merged at %s\n') % (revstr,
157 revlog.short(n)))
157 revlog.short(n)))
158 elif n:
158 elif n:
159 self.ui.status(_('%s transplanted to %s\n')
159 self.ui.status(_('%s transplanted to %s\n')
160 % (revlog.short(node),
160 % (revlog.short(node),
161 revlog.short(n)))
161 revlog.short(n)))
162 finally:
162 finally:
163 if patchfile:
163 if patchfile:
164 os.unlink(patchfile)
164 os.unlink(patchfile)
165 if pulls:
165 if pulls:
166 repo.pull(source, heads=pulls)
166 repo.pull(source, heads=pulls)
167 merge.update(repo, pulls[-1], False, False, None)
167 merge.update(repo, pulls[-1], False, False, None)
168 finally:
168 finally:
169 self.saveseries(revmap, merges)
169 self.saveseries(revmap, merges)
170 self.transplants.write()
170 self.transplants.write()
171 lock.release()
171 lock.release()
172 wlock.release()
172 wlock.release()
173
173
174 def filter(self, filter, changelog, patchfile):
174 def filter(self, filter, changelog, patchfile):
175 '''arbitrarily rewrite changeset before applying it'''
175 '''arbitrarily rewrite changeset before applying it'''
176
176
177 self.ui.status(_('filtering %s\n') % patchfile)
177 self.ui.status(_('filtering %s\n') % patchfile)
178 user, date, msg = (changelog[1], changelog[2], changelog[4])
178 user, date, msg = (changelog[1], changelog[2], changelog[4])
179
179
180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
181 fp = os.fdopen(fd, 'w')
181 fp = os.fdopen(fd, 'w')
182 fp.write("# HG changeset patch\n")
182 fp.write("# HG changeset patch\n")
183 fp.write("# User %s\n" % user)
183 fp.write("# User %s\n" % user)
184 fp.write("# Date %d %d\n" % date)
184 fp.write("# Date %d %d\n" % date)
185 fp.write(msg + '\n')
185 fp.write(msg + '\n')
186 fp.close()
186 fp.close()
187
187
188 try:
188 try:
189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
190 util.shellquote(patchfile)),
190 util.shellquote(patchfile)),
191 environ={'HGUSER': changelog[1]},
191 environ={'HGUSER': changelog[1]},
192 onerr=util.Abort, errprefix=_('filter failed'))
192 onerr=util.Abort, errprefix=_('filter failed'))
193 user, date, msg = self.parselog(file(headerfile))[1:4]
193 user, date, msg = self.parselog(file(headerfile))[1:4]
194 finally:
194 finally:
195 os.unlink(headerfile)
195 os.unlink(headerfile)
196
196
197 return (user, date, msg)
197 return (user, date, msg)
198
198
199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
200 filter=None):
200 filter=None):
201 '''apply the patch in patchfile to the repository as a transplant'''
201 '''apply the patch in patchfile to the repository as a transplant'''
202 (manifest, user, (time, timezone), files, message) = cl[:5]
202 (manifest, user, (time, timezone), files, message) = cl[:5]
203 date = "%d %d" % (time, timezone)
203 date = "%d %d" % (time, timezone)
204 extra = {'transplant_source': node}
204 extra = {'transplant_source': node}
205 if filter:
205 if filter:
206 (user, date, message) = self.filter(filter, cl, patchfile)
206 (user, date, message) = self.filter(filter, cl, patchfile)
207
207
208 if log:
208 if log:
209 # we don't translate messages inserted into commits
209 # we don't translate messages inserted into commits
210 message += '\n(transplanted from %s)' % revlog.hex(node)
210 message += '\n(transplanted from %s)' % revlog.hex(node)
211
211
212 self.ui.status(_('applying %s\n') % revlog.short(node))
212 self.ui.status(_('applying %s\n') % revlog.short(node))
213 self.ui.note('%s %s\n%s\n' % (user, date, message))
213 self.ui.note('%s %s\n%s\n' % (user, date, message))
214
214
215 if not patchfile and not merge:
215 if not patchfile and not merge:
216 raise util.Abort(_('can only omit patchfile if merging'))
216 raise util.Abort(_('can only omit patchfile if merging'))
217 if patchfile:
217 if patchfile:
218 try:
218 try:
219 files = {}
219 files = {}
220 try:
220 try:
221 patch.patch(patchfile, self.ui, cwd=repo.root,
221 patch.patch(patchfile, self.ui, cwd=repo.root,
222 files=files, eolmode=None)
222 files=files, eolmode=None)
223 if not files:
223 if not files:
224 self.ui.warn(_('%s: empty changeset')
224 self.ui.warn(_('%s: empty changeset')
225 % revlog.hex(node))
225 % revlog.hex(node))
226 return None
226 return None
227 finally:
227 finally:
228 files = patch.updatedir(self.ui, repo, files)
228 files = cmdutil.updatedir(self.ui, repo, files)
229 except Exception, inst:
229 except Exception, inst:
230 seriespath = os.path.join(self.path, 'series')
230 seriespath = os.path.join(self.path, 'series')
231 if os.path.exists(seriespath):
231 if os.path.exists(seriespath):
232 os.unlink(seriespath)
232 os.unlink(seriespath)
233 p1 = repo.dirstate.parents()[0]
233 p1 = repo.dirstate.parents()[0]
234 p2 = node
234 p2 = node
235 self.log(user, date, message, p1, p2, merge=merge)
235 self.log(user, date, message, p1, p2, merge=merge)
236 self.ui.write(str(inst) + '\n')
236 self.ui.write(str(inst) + '\n')
237 raise util.Abort(_('fix up the merge and run '
237 raise util.Abort(_('fix up the merge and run '
238 'hg transplant --continue'))
238 'hg transplant --continue'))
239 else:
239 else:
240 files = None
240 files = None
241 if merge:
241 if merge:
242 p1, p2 = repo.dirstate.parents()
242 p1, p2 = repo.dirstate.parents()
243 repo.dirstate.setparents(p1, node)
243 repo.dirstate.setparents(p1, node)
244 m = match.always(repo.root, '')
244 m = match.always(repo.root, '')
245 else:
245 else:
246 m = match.exact(repo.root, '', files)
246 m = match.exact(repo.root, '', files)
247
247
248 n = repo.commit(message, user, date, extra=extra, match=m)
248 n = repo.commit(message, user, date, extra=extra, match=m)
249 if not n:
249 if not n:
250 # Crash here to prevent an unclear crash later, in
250 # Crash here to prevent an unclear crash later, in
251 # transplants.write(). This can happen if patch.patch()
251 # transplants.write(). This can happen if patch.patch()
252 # does nothing but claims success or if repo.status() fails
252 # does nothing but claims success or if repo.status() fails
253 # to report changes done by patch.patch(). These both
253 # to report changes done by patch.patch(). These both
254 # appear to be bugs in other parts of Mercurial, but dying
254 # appear to be bugs in other parts of Mercurial, but dying
255 # here, as soon as we can detect the problem, is preferable
255 # here, as soon as we can detect the problem, is preferable
256 # to silently dropping changesets on the floor.
256 # to silently dropping changesets on the floor.
257 raise RuntimeError('nothing committed after transplant')
257 raise RuntimeError('nothing committed after transplant')
258 if not merge:
258 if not merge:
259 self.transplants.set(n, node)
259 self.transplants.set(n, node)
260
260
261 return n
261 return n
262
262
263 def resume(self, repo, source, opts=None):
263 def resume(self, repo, source, opts=None):
264 '''recover last transaction and apply remaining changesets'''
264 '''recover last transaction and apply remaining changesets'''
265 if os.path.exists(os.path.join(self.path, 'journal')):
265 if os.path.exists(os.path.join(self.path, 'journal')):
266 n, node = self.recover(repo)
266 n, node = self.recover(repo)
267 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
267 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
268 revlog.short(n)))
268 revlog.short(n)))
269 seriespath = os.path.join(self.path, 'series')
269 seriespath = os.path.join(self.path, 'series')
270 if not os.path.exists(seriespath):
270 if not os.path.exists(seriespath):
271 self.transplants.write()
271 self.transplants.write()
272 return
272 return
273 nodes, merges = self.readseries()
273 nodes, merges = self.readseries()
274 revmap = {}
274 revmap = {}
275 for n in nodes:
275 for n in nodes:
276 revmap[source.changelog.rev(n)] = n
276 revmap[source.changelog.rev(n)] = n
277 os.unlink(seriespath)
277 os.unlink(seriespath)
278
278
279 self.apply(repo, source, revmap, merges, opts)
279 self.apply(repo, source, revmap, merges, opts)
280
280
281 def recover(self, repo):
281 def recover(self, repo):
282 '''commit working directory using journal metadata'''
282 '''commit working directory using journal metadata'''
283 node, user, date, message, parents = self.readlog()
283 node, user, date, message, parents = self.readlog()
284 merge = len(parents) == 2
284 merge = len(parents) == 2
285
285
286 if not user or not date or not message or not parents[0]:
286 if not user or not date or not message or not parents[0]:
287 raise util.Abort(_('transplant log file is corrupt'))
287 raise util.Abort(_('transplant log file is corrupt'))
288
288
289 extra = {'transplant_source': node}
289 extra = {'transplant_source': node}
290 wlock = repo.wlock()
290 wlock = repo.wlock()
291 try:
291 try:
292 p1, p2 = repo.dirstate.parents()
292 p1, p2 = repo.dirstate.parents()
293 if p1 != parents[0]:
293 if p1 != parents[0]:
294 raise util.Abort(
294 raise util.Abort(
295 _('working dir not at transplant parent %s') %
295 _('working dir not at transplant parent %s') %
296 revlog.hex(parents[0]))
296 revlog.hex(parents[0]))
297 if merge:
297 if merge:
298 repo.dirstate.setparents(p1, parents[1])
298 repo.dirstate.setparents(p1, parents[1])
299 n = repo.commit(message, user, date, extra=extra)
299 n = repo.commit(message, user, date, extra=extra)
300 if not n:
300 if not n:
301 raise util.Abort(_('commit failed'))
301 raise util.Abort(_('commit failed'))
302 if not merge:
302 if not merge:
303 self.transplants.set(n, node)
303 self.transplants.set(n, node)
304 self.unlog()
304 self.unlog()
305
305
306 return n, node
306 return n, node
307 finally:
307 finally:
308 wlock.release()
308 wlock.release()
309
309
310 def readseries(self):
310 def readseries(self):
311 nodes = []
311 nodes = []
312 merges = []
312 merges = []
313 cur = nodes
313 cur = nodes
314 for line in self.opener('series').read().splitlines():
314 for line in self.opener('series').read().splitlines():
315 if line.startswith('# Merges'):
315 if line.startswith('# Merges'):
316 cur = merges
316 cur = merges
317 continue
317 continue
318 cur.append(revlog.bin(line))
318 cur.append(revlog.bin(line))
319
319
320 return (nodes, merges)
320 return (nodes, merges)
321
321
322 def saveseries(self, revmap, merges):
322 def saveseries(self, revmap, merges):
323 if not revmap:
323 if not revmap:
324 return
324 return
325
325
326 if not os.path.isdir(self.path):
326 if not os.path.isdir(self.path):
327 os.mkdir(self.path)
327 os.mkdir(self.path)
328 series = self.opener('series', 'w')
328 series = self.opener('series', 'w')
329 for rev in sorted(revmap):
329 for rev in sorted(revmap):
330 series.write(revlog.hex(revmap[rev]) + '\n')
330 series.write(revlog.hex(revmap[rev]) + '\n')
331 if merges:
331 if merges:
332 series.write('# Merges\n')
332 series.write('# Merges\n')
333 for m in merges:
333 for m in merges:
334 series.write(revlog.hex(m) + '\n')
334 series.write(revlog.hex(m) + '\n')
335 series.close()
335 series.close()
336
336
337 def parselog(self, fp):
337 def parselog(self, fp):
338 parents = []
338 parents = []
339 message = []
339 message = []
340 node = revlog.nullid
340 node = revlog.nullid
341 inmsg = False
341 inmsg = False
342 for line in fp.read().splitlines():
342 for line in fp.read().splitlines():
343 if inmsg:
343 if inmsg:
344 message.append(line)
344 message.append(line)
345 elif line.startswith('# User '):
345 elif line.startswith('# User '):
346 user = line[7:]
346 user = line[7:]
347 elif line.startswith('# Date '):
347 elif line.startswith('# Date '):
348 date = line[7:]
348 date = line[7:]
349 elif line.startswith('# Node ID '):
349 elif line.startswith('# Node ID '):
350 node = revlog.bin(line[10:])
350 node = revlog.bin(line[10:])
351 elif line.startswith('# Parent '):
351 elif line.startswith('# Parent '):
352 parents.append(revlog.bin(line[9:]))
352 parents.append(revlog.bin(line[9:]))
353 elif not line.startswith('# '):
353 elif not line.startswith('# '):
354 inmsg = True
354 inmsg = True
355 message.append(line)
355 message.append(line)
356 return (node, user, date, '\n'.join(message), parents)
356 return (node, user, date, '\n'.join(message), parents)
357
357
358 def log(self, user, date, message, p1, p2, merge=False):
358 def log(self, user, date, message, p1, p2, merge=False):
359 '''journal changelog metadata for later recover'''
359 '''journal changelog metadata for later recover'''
360
360
361 if not os.path.isdir(self.path):
361 if not os.path.isdir(self.path):
362 os.mkdir(self.path)
362 os.mkdir(self.path)
363 fp = self.opener('journal', 'w')
363 fp = self.opener('journal', 'w')
364 fp.write('# User %s\n' % user)
364 fp.write('# User %s\n' % user)
365 fp.write('# Date %s\n' % date)
365 fp.write('# Date %s\n' % date)
366 fp.write('# Node ID %s\n' % revlog.hex(p2))
366 fp.write('# Node ID %s\n' % revlog.hex(p2))
367 fp.write('# Parent ' + revlog.hex(p1) + '\n')
367 fp.write('# Parent ' + revlog.hex(p1) + '\n')
368 if merge:
368 if merge:
369 fp.write('# Parent ' + revlog.hex(p2) + '\n')
369 fp.write('# Parent ' + revlog.hex(p2) + '\n')
370 fp.write(message.rstrip() + '\n')
370 fp.write(message.rstrip() + '\n')
371 fp.close()
371 fp.close()
372
372
373 def readlog(self):
373 def readlog(self):
374 return self.parselog(self.opener('journal'))
374 return self.parselog(self.opener('journal'))
375
375
376 def unlog(self):
376 def unlog(self):
377 '''remove changelog journal'''
377 '''remove changelog journal'''
378 absdst = os.path.join(self.path, 'journal')
378 absdst = os.path.join(self.path, 'journal')
379 if os.path.exists(absdst):
379 if os.path.exists(absdst):
380 os.unlink(absdst)
380 os.unlink(absdst)
381
381
382 def transplantfilter(self, repo, source, root):
382 def transplantfilter(self, repo, source, root):
383 def matchfn(node):
383 def matchfn(node):
384 if self.applied(repo, node, root):
384 if self.applied(repo, node, root):
385 return False
385 return False
386 if source.changelog.parents(node)[1] != revlog.nullid:
386 if source.changelog.parents(node)[1] != revlog.nullid:
387 return False
387 return False
388 extra = source.changelog.read(node)[5]
388 extra = source.changelog.read(node)[5]
389 cnode = extra.get('transplant_source')
389 cnode = extra.get('transplant_source')
390 if cnode and self.applied(repo, cnode, root):
390 if cnode and self.applied(repo, cnode, root):
391 return False
391 return False
392 return True
392 return True
393
393
394 return matchfn
394 return matchfn
395
395
396 def hasnode(repo, node):
396 def hasnode(repo, node):
397 try:
397 try:
398 return repo.changelog.rev(node) != None
398 return repo.changelog.rev(node) != None
399 except error.RevlogError:
399 except error.RevlogError:
400 return False
400 return False
401
401
402 def browserevs(ui, repo, nodes, opts):
402 def browserevs(ui, repo, nodes, opts):
403 '''interactively transplant changesets'''
403 '''interactively transplant changesets'''
404 def browsehelp(ui):
404 def browsehelp(ui):
405 ui.write(_('y: transplant this changeset\n'
405 ui.write(_('y: transplant this changeset\n'
406 'n: skip this changeset\n'
406 'n: skip this changeset\n'
407 'm: merge at this changeset\n'
407 'm: merge at this changeset\n'
408 'p: show patch\n'
408 'p: show patch\n'
409 'c: commit selected changesets\n'
409 'c: commit selected changesets\n'
410 'q: cancel transplant\n'
410 'q: cancel transplant\n'
411 '?: show this help\n'))
411 '?: show this help\n'))
412
412
413 displayer = cmdutil.show_changeset(ui, repo, opts)
413 displayer = cmdutil.show_changeset(ui, repo, opts)
414 transplants = []
414 transplants = []
415 merges = []
415 merges = []
416 for node in nodes:
416 for node in nodes:
417 displayer.show(repo[node])
417 displayer.show(repo[node])
418 action = None
418 action = None
419 while not action:
419 while not action:
420 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
420 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
421 if action == '?':
421 if action == '?':
422 browsehelp(ui)
422 browsehelp(ui)
423 action = None
423 action = None
424 elif action == 'p':
424 elif action == 'p':
425 parent = repo.changelog.parents(node)[0]
425 parent = repo.changelog.parents(node)[0]
426 for chunk in patch.diff(repo, parent, node):
426 for chunk in patch.diff(repo, parent, node):
427 ui.write(chunk)
427 ui.write(chunk)
428 action = None
428 action = None
429 elif action not in ('y', 'n', 'm', 'c', 'q'):
429 elif action not in ('y', 'n', 'm', 'c', 'q'):
430 ui.write(_('no such option\n'))
430 ui.write(_('no such option\n'))
431 action = None
431 action = None
432 if action == 'y':
432 if action == 'y':
433 transplants.append(node)
433 transplants.append(node)
434 elif action == 'm':
434 elif action == 'm':
435 merges.append(node)
435 merges.append(node)
436 elif action == 'c':
436 elif action == 'c':
437 break
437 break
438 elif action == 'q':
438 elif action == 'q':
439 transplants = ()
439 transplants = ()
440 merges = ()
440 merges = ()
441 break
441 break
442 displayer.close()
442 displayer.close()
443 return (transplants, merges)
443 return (transplants, merges)
444
444
445 def transplant(ui, repo, *revs, **opts):
445 def transplant(ui, repo, *revs, **opts):
446 '''transplant changesets from another branch
446 '''transplant changesets from another branch
447
447
448 Selected changesets will be applied on top of the current working
448 Selected changesets will be applied on top of the current working
449 directory with the log of the original changeset. If --log is
449 directory with the log of the original changeset. If --log is
450 specified, log messages will have a comment appended of the form::
450 specified, log messages will have a comment appended of the form::
451
451
452 (transplanted from CHANGESETHASH)
452 (transplanted from CHANGESETHASH)
453
453
454 You can rewrite the changelog message with the --filter option.
454 You can rewrite the changelog message with the --filter option.
455 Its argument will be invoked with the current changelog message as
455 Its argument will be invoked with the current changelog message as
456 $1 and the patch as $2.
456 $1 and the patch as $2.
457
457
458 If --source/-s is specified, selects changesets from the named
458 If --source/-s is specified, selects changesets from the named
459 repository. If --branch/-b is specified, selects changesets from
459 repository. If --branch/-b is specified, selects changesets from
460 the branch holding the named revision, up to that revision. If
460 the branch holding the named revision, up to that revision. If
461 --all/-a is specified, all changesets on the branch will be
461 --all/-a is specified, all changesets on the branch will be
462 transplanted, otherwise you will be prompted to select the
462 transplanted, otherwise you will be prompted to select the
463 changesets you want.
463 changesets you want.
464
464
465 :hg:`transplant --branch REVISION --all` will rebase the selected
465 :hg:`transplant --branch REVISION --all` will rebase the selected
466 branch (up to the named revision) onto your current working
466 branch (up to the named revision) onto your current working
467 directory.
467 directory.
468
468
469 You can optionally mark selected transplanted changesets as merge
469 You can optionally mark selected transplanted changesets as merge
470 changesets. You will not be prompted to transplant any ancestors
470 changesets. You will not be prompted to transplant any ancestors
471 of a merged transplant, and you can merge descendants of them
471 of a merged transplant, and you can merge descendants of them
472 normally instead of transplanting them.
472 normally instead of transplanting them.
473
473
474 If no merges or revisions are provided, :hg:`transplant` will
474 If no merges or revisions are provided, :hg:`transplant` will
475 start an interactive changeset browser.
475 start an interactive changeset browser.
476
476
477 If a changeset application fails, you can fix the merge by hand
477 If a changeset application fails, you can fix the merge by hand
478 and then resume where you left off by calling :hg:`transplant
478 and then resume where you left off by calling :hg:`transplant
479 --continue/-c`.
479 --continue/-c`.
480 '''
480 '''
481 def getremotechanges(repo, url):
481 def getremotechanges(repo, url):
482 sourcerepo = ui.expandpath(url)
482 sourcerepo = ui.expandpath(url)
483 source = hg.repository(ui, sourcerepo)
483 source = hg.repository(ui, sourcerepo)
484 tmp = discovery.findcommonincoming(repo, source, force=True)
484 tmp = discovery.findcommonincoming(repo, source, force=True)
485 common, incoming, rheads = tmp
485 common, incoming, rheads = tmp
486 if not incoming:
486 if not incoming:
487 return (source, None, None)
487 return (source, None, None)
488
488
489 bundle = None
489 bundle = None
490 if not source.local():
490 if not source.local():
491 if source.capable('changegroupsubset'):
491 if source.capable('changegroupsubset'):
492 cg = source.changegroupsubset(incoming, rheads, 'incoming')
492 cg = source.changegroupsubset(incoming, rheads, 'incoming')
493 else:
493 else:
494 cg = source.changegroup(incoming, 'incoming')
494 cg = source.changegroup(incoming, 'incoming')
495 bundle = changegroup.writebundle(cg, None, 'HG10UN')
495 bundle = changegroup.writebundle(cg, None, 'HG10UN')
496 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
496 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
497
497
498 return (source, incoming, bundle)
498 return (source, incoming, bundle)
499
499
500 def incwalk(repo, incoming, branches, match=util.always):
500 def incwalk(repo, incoming, branches, match=util.always):
501 if not branches:
501 if not branches:
502 branches = None
502 branches = None
503 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
503 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
504 if match(node):
504 if match(node):
505 yield node
505 yield node
506
506
507 def transplantwalk(repo, root, branches, match=util.always):
507 def transplantwalk(repo, root, branches, match=util.always):
508 if not branches:
508 if not branches:
509 branches = repo.heads()
509 branches = repo.heads()
510 ancestors = []
510 ancestors = []
511 for branch in branches:
511 for branch in branches:
512 ancestors.append(repo.changelog.ancestor(root, branch))
512 ancestors.append(repo.changelog.ancestor(root, branch))
513 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
513 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
514 if match(node):
514 if match(node):
515 yield node
515 yield node
516
516
517 def checkopts(opts, revs):
517 def checkopts(opts, revs):
518 if opts.get('continue'):
518 if opts.get('continue'):
519 if opts.get('branch') or opts.get('all') or opts.get('merge'):
519 if opts.get('branch') or opts.get('all') or opts.get('merge'):
520 raise util.Abort(_('--continue is incompatible with '
520 raise util.Abort(_('--continue is incompatible with '
521 'branch, all or merge'))
521 'branch, all or merge'))
522 return
522 return
523 if not (opts.get('source') or revs or
523 if not (opts.get('source') or revs or
524 opts.get('merge') or opts.get('branch')):
524 opts.get('merge') or opts.get('branch')):
525 raise util.Abort(_('no source URL, branch tag or revision '
525 raise util.Abort(_('no source URL, branch tag or revision '
526 'list provided'))
526 'list provided'))
527 if opts.get('all'):
527 if opts.get('all'):
528 if not opts.get('branch'):
528 if not opts.get('branch'):
529 raise util.Abort(_('--all requires a branch revision'))
529 raise util.Abort(_('--all requires a branch revision'))
530 if revs:
530 if revs:
531 raise util.Abort(_('--all is incompatible with a '
531 raise util.Abort(_('--all is incompatible with a '
532 'revision list'))
532 'revision list'))
533
533
534 checkopts(opts, revs)
534 checkopts(opts, revs)
535
535
536 if not opts.get('log'):
536 if not opts.get('log'):
537 opts['log'] = ui.config('transplant', 'log')
537 opts['log'] = ui.config('transplant', 'log')
538 if not opts.get('filter'):
538 if not opts.get('filter'):
539 opts['filter'] = ui.config('transplant', 'filter')
539 opts['filter'] = ui.config('transplant', 'filter')
540
540
541 tp = transplanter(ui, repo)
541 tp = transplanter(ui, repo)
542
542
543 p1, p2 = repo.dirstate.parents()
543 p1, p2 = repo.dirstate.parents()
544 if len(repo) > 0 and p1 == revlog.nullid:
544 if len(repo) > 0 and p1 == revlog.nullid:
545 raise util.Abort(_('no revision checked out'))
545 raise util.Abort(_('no revision checked out'))
546 if not opts.get('continue'):
546 if not opts.get('continue'):
547 if p2 != revlog.nullid:
547 if p2 != revlog.nullid:
548 raise util.Abort(_('outstanding uncommitted merges'))
548 raise util.Abort(_('outstanding uncommitted merges'))
549 m, a, r, d = repo.status()[:4]
549 m, a, r, d = repo.status()[:4]
550 if m or a or r or d:
550 if m or a or r or d:
551 raise util.Abort(_('outstanding local changes'))
551 raise util.Abort(_('outstanding local changes'))
552
552
553 bundle = None
553 bundle = None
554 source = opts.get('source')
554 source = opts.get('source')
555 if source:
555 if source:
556 (source, incoming, bundle) = getremotechanges(repo, source)
556 (source, incoming, bundle) = getremotechanges(repo, source)
557 else:
557 else:
558 source = repo
558 source = repo
559
559
560 try:
560 try:
561 if opts.get('continue'):
561 if opts.get('continue'):
562 tp.resume(repo, source, opts)
562 tp.resume(repo, source, opts)
563 return
563 return
564
564
565 tf = tp.transplantfilter(repo, source, p1)
565 tf = tp.transplantfilter(repo, source, p1)
566 if opts.get('prune'):
566 if opts.get('prune'):
567 prune = [source.lookup(r)
567 prune = [source.lookup(r)
568 for r in cmdutil.revrange(source, opts.get('prune'))]
568 for r in cmdutil.revrange(source, opts.get('prune'))]
569 matchfn = lambda x: tf(x) and x not in prune
569 matchfn = lambda x: tf(x) and x not in prune
570 else:
570 else:
571 matchfn = tf
571 matchfn = tf
572 branches = map(source.lookup, opts.get('branch', ()))
572 branches = map(source.lookup, opts.get('branch', ()))
573 merges = map(source.lookup, opts.get('merge', ()))
573 merges = map(source.lookup, opts.get('merge', ()))
574 revmap = {}
574 revmap = {}
575 if revs:
575 if revs:
576 for r in cmdutil.revrange(source, revs):
576 for r in cmdutil.revrange(source, revs):
577 revmap[int(r)] = source.lookup(r)
577 revmap[int(r)] = source.lookup(r)
578 elif opts.get('all') or not merges:
578 elif opts.get('all') or not merges:
579 if source != repo:
579 if source != repo:
580 alltransplants = incwalk(source, incoming, branches,
580 alltransplants = incwalk(source, incoming, branches,
581 match=matchfn)
581 match=matchfn)
582 else:
582 else:
583 alltransplants = transplantwalk(source, p1, branches,
583 alltransplants = transplantwalk(source, p1, branches,
584 match=matchfn)
584 match=matchfn)
585 if opts.get('all'):
585 if opts.get('all'):
586 revs = alltransplants
586 revs = alltransplants
587 else:
587 else:
588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
589 merges.extend(newmerges)
589 merges.extend(newmerges)
590 for r in revs:
590 for r in revs:
591 revmap[source.changelog.rev(r)] = r
591 revmap[source.changelog.rev(r)] = r
592 for r in merges:
592 for r in merges:
593 revmap[source.changelog.rev(r)] = r
593 revmap[source.changelog.rev(r)] = r
594
594
595 tp.apply(repo, source, revmap, merges, opts)
595 tp.apply(repo, source, revmap, merges, opts)
596 finally:
596 finally:
597 if bundle:
597 if bundle:
598 source.close()
598 source.close()
599 os.unlink(bundle)
599 os.unlink(bundle)
600
600
601 cmdtable = {
601 cmdtable = {
602 "transplant":
602 "transplant":
603 (transplant,
603 (transplant,
604 [('s', 'source', '',
604 [('s', 'source', '',
605 _('pull patches from REPO'), _('REPO')),
605 _('pull patches from REPO'), _('REPO')),
606 ('b', 'branch', [],
606 ('b', 'branch', [],
607 _('pull patches from branch BRANCH'), _('BRANCH')),
607 _('pull patches from branch BRANCH'), _('BRANCH')),
608 ('a', 'all', None, _('pull all changesets up to BRANCH')),
608 ('a', 'all', None, _('pull all changesets up to BRANCH')),
609 ('p', 'prune', [],
609 ('p', 'prune', [],
610 _('skip over REV'), _('REV')),
610 _('skip over REV'), _('REV')),
611 ('m', 'merge', [],
611 ('m', 'merge', [],
612 _('merge at REV'), _('REV')),
612 _('merge at REV'), _('REV')),
613 ('', 'log', None, _('append transplant info to log message')),
613 ('', 'log', None, _('append transplant info to log message')),
614 ('c', 'continue', None, _('continue last transplant session '
614 ('c', 'continue', None, _('continue last transplant session '
615 'after repair')),
615 'after repair')),
616 ('', 'filter', '',
616 ('', 'filter', '',
617 _('filter changesets through command'), _('CMD'))],
617 _('filter changesets through command'), _('CMD'))],
618 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
618 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
619 '[-m REV] [REV]...'))
619 '[-m REV] [REV]...'))
620 }
620 }
@@ -1,1293 +1,1336 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.parents()[1] != nullid:
75 if repo.dirstate.parents()[1] != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revpair(repo, revs):
114 def revpair(repo, revs):
115 '''return pair of nodes, given list of revisions. second item can
115 '''return pair of nodes, given list of revisions. second item can
116 be None, meaning use working dir.'''
116 be None, meaning use working dir.'''
117
117
118 def revfix(repo, val, defval):
118 def revfix(repo, val, defval):
119 if not val and val != 0 and defval is not None:
119 if not val and val != 0 and defval is not None:
120 val = defval
120 val = defval
121 return repo.lookup(val)
121 return repo.lookup(val)
122
122
123 if not revs:
123 if not revs:
124 return repo.dirstate.parents()[0], None
124 return repo.dirstate.parents()[0], None
125 end = None
125 end = None
126 if len(revs) == 1:
126 if len(revs) == 1:
127 if revrangesep in revs[0]:
127 if revrangesep in revs[0]:
128 start, end = revs[0].split(revrangesep, 1)
128 start, end = revs[0].split(revrangesep, 1)
129 start = revfix(repo, start, 0)
129 start = revfix(repo, start, 0)
130 end = revfix(repo, end, len(repo) - 1)
130 end = revfix(repo, end, len(repo) - 1)
131 else:
131 else:
132 start = revfix(repo, revs[0], None)
132 start = revfix(repo, revs[0], None)
133 elif len(revs) == 2:
133 elif len(revs) == 2:
134 if revrangesep in revs[0] or revrangesep in revs[1]:
134 if revrangesep in revs[0] or revrangesep in revs[1]:
135 raise util.Abort(_('too many revisions specified'))
135 raise util.Abort(_('too many revisions specified'))
136 start = revfix(repo, revs[0], None)
136 start = revfix(repo, revs[0], None)
137 end = revfix(repo, revs[1], None)
137 end = revfix(repo, revs[1], None)
138 else:
138 else:
139 raise util.Abort(_('too many revisions specified'))
139 raise util.Abort(_('too many revisions specified'))
140 return start, end
140 return start, end
141
141
142 def revrange(repo, revs):
142 def revrange(repo, revs):
143 """Yield revision as strings from a list of revision specifications."""
143 """Yield revision as strings from a list of revision specifications."""
144
144
145 def revfix(repo, val, defval):
145 def revfix(repo, val, defval):
146 if not val and val != 0 and defval is not None:
146 if not val and val != 0 and defval is not None:
147 return defval
147 return defval
148 return repo.changelog.rev(repo.lookup(val))
148 return repo.changelog.rev(repo.lookup(val))
149
149
150 seen, l = set(), []
150 seen, l = set(), []
151 for spec in revs:
151 for spec in revs:
152 # attempt to parse old-style ranges first to deal with
152 # attempt to parse old-style ranges first to deal with
153 # things like old-tag which contain query metacharacters
153 # things like old-tag which contain query metacharacters
154 try:
154 try:
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = 'w' in mode or 'a' in mode
233 writable = 'w' in mode or 'a' in mode
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 return writable and sys.stdout or sys.stdin
236 return writable and sys.stdout or sys.stdin
237 if hasattr(pat, 'write') and writable:
237 if hasattr(pat, 'write') and writable:
238 return pat
238 return pat
239 if hasattr(pat, 'read') and 'r' in mode:
239 if hasattr(pat, 'read') and 'r' in mode:
240 return pat
240 return pat
241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 pathname),
242 pathname),
243 mode)
243 mode)
244
244
245 def expandpats(pats):
245 def expandpats(pats):
246 if not util.expandglobs:
246 if not util.expandglobs:
247 return list(pats)
247 return list(pats)
248 ret = []
248 ret = []
249 for p in pats:
249 for p in pats:
250 kind, name = matchmod._patsplit(p, None)
250 kind, name = matchmod._patsplit(p, None)
251 if kind is None:
251 if kind is None:
252 try:
252 try:
253 globbed = glob.glob(name)
253 globbed = glob.glob(name)
254 except re.error:
254 except re.error:
255 globbed = [name]
255 globbed = [name]
256 if globbed:
256 if globbed:
257 ret.extend(globbed)
257 ret.extend(globbed)
258 continue
258 continue
259 ret.append(p)
259 ret.append(p)
260 return ret
260 return ret
261
261
262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 if not globbed and default == 'relpath':
263 if not globbed and default == 'relpath':
264 pats = expandpats(pats or [])
264 pats = expandpats(pats or [])
265 m = matchmod.match(repo.root, repo.getcwd(), pats,
265 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 opts.get('include'), opts.get('exclude'), default,
266 opts.get('include'), opts.get('exclude'), default,
267 auditor=repo.auditor)
267 auditor=repo.auditor)
268 def badfn(f, msg):
268 def badfn(f, msg):
269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 m.bad = badfn
270 m.bad = badfn
271 return m
271 return m
272
272
273 def matchall(repo):
273 def matchall(repo):
274 return matchmod.always(repo.root, repo.getcwd())
274 return matchmod.always(repo.root, repo.getcwd())
275
275
276 def matchfiles(repo, files):
276 def matchfiles(repo, files):
277 return matchmod.exact(repo.root, repo.getcwd(), files)
277 return matchmod.exact(repo.root, repo.getcwd(), files)
278
278
279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 if dry_run is None:
280 if dry_run is None:
281 dry_run = opts.get('dry_run')
281 dry_run = opts.get('dry_run')
282 if similarity is None:
282 if similarity is None:
283 similarity = float(opts.get('similarity') or 0)
283 similarity = float(opts.get('similarity') or 0)
284 # we'd use status here, except handling of symlinks and ignore is tricky
284 # we'd use status here, except handling of symlinks and ignore is tricky
285 added, unknown, deleted, removed = [], [], [], []
285 added, unknown, deleted, removed = [], [], [], []
286 audit_path = util.path_auditor(repo.root)
286 audit_path = util.path_auditor(repo.root)
287 m = match(repo, pats, opts)
287 m = match(repo, pats, opts)
288 for abs in repo.walk(m):
288 for abs in repo.walk(m):
289 target = repo.wjoin(abs)
289 target = repo.wjoin(abs)
290 good = True
290 good = True
291 try:
291 try:
292 audit_path(abs)
292 audit_path(abs)
293 except:
293 except:
294 good = False
294 good = False
295 rel = m.rel(abs)
295 rel = m.rel(abs)
296 exact = m.exact(abs)
296 exact = m.exact(abs)
297 if good and abs not in repo.dirstate:
297 if good and abs not in repo.dirstate:
298 unknown.append(abs)
298 unknown.append(abs)
299 if repo.ui.verbose or not exact:
299 if repo.ui.verbose or not exact:
300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 or (os.path.isdir(target) and not os.path.islink(target))):
302 or (os.path.isdir(target) and not os.path.islink(target))):
303 deleted.append(abs)
303 deleted.append(abs)
304 if repo.ui.verbose or not exact:
304 if repo.ui.verbose or not exact:
305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 # for finding renames
306 # for finding renames
307 elif repo.dirstate[abs] == 'r':
307 elif repo.dirstate[abs] == 'r':
308 removed.append(abs)
308 removed.append(abs)
309 elif repo.dirstate[abs] == 'a':
309 elif repo.dirstate[abs] == 'a':
310 added.append(abs)
310 added.append(abs)
311 copies = {}
311 copies = {}
312 if similarity > 0:
312 if similarity > 0:
313 for old, new, score in similar.findrenames(repo,
313 for old, new, score in similar.findrenames(repo,
314 added + unknown, removed + deleted, similarity):
314 added + unknown, removed + deleted, similarity):
315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 repo.ui.status(_('recording removal of %s as rename to %s '
316 repo.ui.status(_('recording removal of %s as rename to %s '
317 '(%d%% similar)\n') %
317 '(%d%% similar)\n') %
318 (m.rel(old), m.rel(new), score * 100))
318 (m.rel(old), m.rel(new), score * 100))
319 copies[new] = old
319 copies[new] = old
320
320
321 if not dry_run:
321 if not dry_run:
322 wctx = repo[None]
322 wctx = repo[None]
323 wlock = repo.wlock()
323 wlock = repo.wlock()
324 try:
324 try:
325 wctx.remove(deleted)
325 wctx.remove(deleted)
326 wctx.add(unknown)
326 wctx.add(unknown)
327 for new, old in copies.iteritems():
327 for new, old in copies.iteritems():
328 wctx.copy(old, new)
328 wctx.copy(old, new)
329 finally:
329 finally:
330 wlock.release()
330 wlock.release()
331
331
332 def updatedir(ui, repo, patches, similarity=0):
333 '''Update dirstate after patch application according to metadata'''
334 if not patches:
335 return
336 copies = []
337 removes = set()
338 cfiles = patches.keys()
339 cwd = repo.getcwd()
340 if cwd:
341 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
342 for f in patches:
343 gp = patches[f]
344 if not gp:
345 continue
346 if gp.op == 'RENAME':
347 copies.append((gp.oldpath, gp.path))
348 removes.add(gp.oldpath)
349 elif gp.op == 'COPY':
350 copies.append((gp.oldpath, gp.path))
351 elif gp.op == 'DELETE':
352 removes.add(gp.path)
353
354 wctx = repo[None]
355 for src, dst in copies:
356 wctx.copy(src, dst)
357 if (not similarity) and removes:
358 wctx.remove(sorted(removes), True)
359
360 for f in patches:
361 gp = patches[f]
362 if gp and gp.mode:
363 islink, isexec = gp.mode
364 dst = repo.wjoin(gp.path)
365 # patch won't create empty files
366 if gp.op == 'ADD' and not os.path.exists(dst):
367 flags = (isexec and 'x' or '') + (islink and 'l' or '')
368 repo.wwrite(gp.path, '', flags)
369 util.set_flags(dst, islink, isexec)
370 addremove(repo, cfiles, similarity=similarity)
371 files = patches.keys()
372 files.extend([r for r in removes if r not in files])
373 return sorted(files)
374
332 def copy(ui, repo, pats, opts, rename=False):
375 def copy(ui, repo, pats, opts, rename=False):
333 # called with the repo lock held
376 # called with the repo lock held
334 #
377 #
335 # hgsep => pathname that uses "/" to separate directories
378 # hgsep => pathname that uses "/" to separate directories
336 # ossep => pathname that uses os.sep to separate directories
379 # ossep => pathname that uses os.sep to separate directories
337 cwd = repo.getcwd()
380 cwd = repo.getcwd()
338 targets = {}
381 targets = {}
339 after = opts.get("after")
382 after = opts.get("after")
340 dryrun = opts.get("dry_run")
383 dryrun = opts.get("dry_run")
341 wctx = repo[None]
384 wctx = repo[None]
342
385
343 def walkpat(pat):
386 def walkpat(pat):
344 srcs = []
387 srcs = []
345 badstates = after and '?' or '?r'
388 badstates = after and '?' or '?r'
346 m = match(repo, [pat], opts, globbed=True)
389 m = match(repo, [pat], opts, globbed=True)
347 for abs in repo.walk(m):
390 for abs in repo.walk(m):
348 state = repo.dirstate[abs]
391 state = repo.dirstate[abs]
349 rel = m.rel(abs)
392 rel = m.rel(abs)
350 exact = m.exact(abs)
393 exact = m.exact(abs)
351 if state in badstates:
394 if state in badstates:
352 if exact and state == '?':
395 if exact and state == '?':
353 ui.warn(_('%s: not copying - file is not managed\n') % rel)
396 ui.warn(_('%s: not copying - file is not managed\n') % rel)
354 if exact and state == 'r':
397 if exact and state == 'r':
355 ui.warn(_('%s: not copying - file has been marked for'
398 ui.warn(_('%s: not copying - file has been marked for'
356 ' remove\n') % rel)
399 ' remove\n') % rel)
357 continue
400 continue
358 # abs: hgsep
401 # abs: hgsep
359 # rel: ossep
402 # rel: ossep
360 srcs.append((abs, rel, exact))
403 srcs.append((abs, rel, exact))
361 return srcs
404 return srcs
362
405
363 # abssrc: hgsep
406 # abssrc: hgsep
364 # relsrc: ossep
407 # relsrc: ossep
365 # otarget: ossep
408 # otarget: ossep
366 def copyfile(abssrc, relsrc, otarget, exact):
409 def copyfile(abssrc, relsrc, otarget, exact):
367 abstarget = util.canonpath(repo.root, cwd, otarget)
410 abstarget = util.canonpath(repo.root, cwd, otarget)
368 reltarget = repo.pathto(abstarget, cwd)
411 reltarget = repo.pathto(abstarget, cwd)
369 target = repo.wjoin(abstarget)
412 target = repo.wjoin(abstarget)
370 src = repo.wjoin(abssrc)
413 src = repo.wjoin(abssrc)
371 state = repo.dirstate[abstarget]
414 state = repo.dirstate[abstarget]
372
415
373 # check for collisions
416 # check for collisions
374 prevsrc = targets.get(abstarget)
417 prevsrc = targets.get(abstarget)
375 if prevsrc is not None:
418 if prevsrc is not None:
376 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
419 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
377 (reltarget, repo.pathto(abssrc, cwd),
420 (reltarget, repo.pathto(abssrc, cwd),
378 repo.pathto(prevsrc, cwd)))
421 repo.pathto(prevsrc, cwd)))
379 return
422 return
380
423
381 # check for overwrites
424 # check for overwrites
382 exists = os.path.exists(target)
425 exists = os.path.exists(target)
383 if not after and exists or after and state in 'mn':
426 if not after and exists or after and state in 'mn':
384 if not opts['force']:
427 if not opts['force']:
385 ui.warn(_('%s: not overwriting - file exists\n') %
428 ui.warn(_('%s: not overwriting - file exists\n') %
386 reltarget)
429 reltarget)
387 return
430 return
388
431
389 if after:
432 if after:
390 if not exists:
433 if not exists:
391 if rename:
434 if rename:
392 ui.warn(_('%s: not recording move - %s does not exist\n') %
435 ui.warn(_('%s: not recording move - %s does not exist\n') %
393 (relsrc, reltarget))
436 (relsrc, reltarget))
394 else:
437 else:
395 ui.warn(_('%s: not recording copy - %s does not exist\n') %
438 ui.warn(_('%s: not recording copy - %s does not exist\n') %
396 (relsrc, reltarget))
439 (relsrc, reltarget))
397 return
440 return
398 elif not dryrun:
441 elif not dryrun:
399 try:
442 try:
400 if exists:
443 if exists:
401 os.unlink(target)
444 os.unlink(target)
402 targetdir = os.path.dirname(target) or '.'
445 targetdir = os.path.dirname(target) or '.'
403 if not os.path.isdir(targetdir):
446 if not os.path.isdir(targetdir):
404 os.makedirs(targetdir)
447 os.makedirs(targetdir)
405 util.copyfile(src, target)
448 util.copyfile(src, target)
406 except IOError, inst:
449 except IOError, inst:
407 if inst.errno == errno.ENOENT:
450 if inst.errno == errno.ENOENT:
408 ui.warn(_('%s: deleted in working copy\n') % relsrc)
451 ui.warn(_('%s: deleted in working copy\n') % relsrc)
409 else:
452 else:
410 ui.warn(_('%s: cannot copy - %s\n') %
453 ui.warn(_('%s: cannot copy - %s\n') %
411 (relsrc, inst.strerror))
454 (relsrc, inst.strerror))
412 return True # report a failure
455 return True # report a failure
413
456
414 if ui.verbose or not exact:
457 if ui.verbose or not exact:
415 if rename:
458 if rename:
416 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
459 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
417 else:
460 else:
418 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
461 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
419
462
420 targets[abstarget] = abssrc
463 targets[abstarget] = abssrc
421
464
422 # fix up dirstate
465 # fix up dirstate
423 origsrc = repo.dirstate.copied(abssrc) or abssrc
466 origsrc = repo.dirstate.copied(abssrc) or abssrc
424 if abstarget == origsrc: # copying back a copy?
467 if abstarget == origsrc: # copying back a copy?
425 if state not in 'mn' and not dryrun:
468 if state not in 'mn' and not dryrun:
426 repo.dirstate.normallookup(abstarget)
469 repo.dirstate.normallookup(abstarget)
427 else:
470 else:
428 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
471 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
429 if not ui.quiet:
472 if not ui.quiet:
430 ui.warn(_("%s has not been committed yet, so no copy "
473 ui.warn(_("%s has not been committed yet, so no copy "
431 "data will be stored for %s.\n")
474 "data will be stored for %s.\n")
432 % (repo.pathto(origsrc, cwd), reltarget))
475 % (repo.pathto(origsrc, cwd), reltarget))
433 if repo.dirstate[abstarget] in '?r' and not dryrun:
476 if repo.dirstate[abstarget] in '?r' and not dryrun:
434 wctx.add([abstarget])
477 wctx.add([abstarget])
435 elif not dryrun:
478 elif not dryrun:
436 wctx.copy(origsrc, abstarget)
479 wctx.copy(origsrc, abstarget)
437
480
438 if rename and not dryrun:
481 if rename and not dryrun:
439 wctx.remove([abssrc], not after)
482 wctx.remove([abssrc], not after)
440
483
441 # pat: ossep
484 # pat: ossep
442 # dest ossep
485 # dest ossep
443 # srcs: list of (hgsep, hgsep, ossep, bool)
486 # srcs: list of (hgsep, hgsep, ossep, bool)
444 # return: function that takes hgsep and returns ossep
487 # return: function that takes hgsep and returns ossep
445 def targetpathfn(pat, dest, srcs):
488 def targetpathfn(pat, dest, srcs):
446 if os.path.isdir(pat):
489 if os.path.isdir(pat):
447 abspfx = util.canonpath(repo.root, cwd, pat)
490 abspfx = util.canonpath(repo.root, cwd, pat)
448 abspfx = util.localpath(abspfx)
491 abspfx = util.localpath(abspfx)
449 if destdirexists:
492 if destdirexists:
450 striplen = len(os.path.split(abspfx)[0])
493 striplen = len(os.path.split(abspfx)[0])
451 else:
494 else:
452 striplen = len(abspfx)
495 striplen = len(abspfx)
453 if striplen:
496 if striplen:
454 striplen += len(os.sep)
497 striplen += len(os.sep)
455 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
498 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
456 elif destdirexists:
499 elif destdirexists:
457 res = lambda p: os.path.join(dest,
500 res = lambda p: os.path.join(dest,
458 os.path.basename(util.localpath(p)))
501 os.path.basename(util.localpath(p)))
459 else:
502 else:
460 res = lambda p: dest
503 res = lambda p: dest
461 return res
504 return res
462
505
463 # pat: ossep
506 # pat: ossep
464 # dest ossep
507 # dest ossep
465 # srcs: list of (hgsep, hgsep, ossep, bool)
508 # srcs: list of (hgsep, hgsep, ossep, bool)
466 # return: function that takes hgsep and returns ossep
509 # return: function that takes hgsep and returns ossep
467 def targetpathafterfn(pat, dest, srcs):
510 def targetpathafterfn(pat, dest, srcs):
468 if matchmod.patkind(pat):
511 if matchmod.patkind(pat):
469 # a mercurial pattern
512 # a mercurial pattern
470 res = lambda p: os.path.join(dest,
513 res = lambda p: os.path.join(dest,
471 os.path.basename(util.localpath(p)))
514 os.path.basename(util.localpath(p)))
472 else:
515 else:
473 abspfx = util.canonpath(repo.root, cwd, pat)
516 abspfx = util.canonpath(repo.root, cwd, pat)
474 if len(abspfx) < len(srcs[0][0]):
517 if len(abspfx) < len(srcs[0][0]):
475 # A directory. Either the target path contains the last
518 # A directory. Either the target path contains the last
476 # component of the source path or it does not.
519 # component of the source path or it does not.
477 def evalpath(striplen):
520 def evalpath(striplen):
478 score = 0
521 score = 0
479 for s in srcs:
522 for s in srcs:
480 t = os.path.join(dest, util.localpath(s[0])[striplen:])
523 t = os.path.join(dest, util.localpath(s[0])[striplen:])
481 if os.path.exists(t):
524 if os.path.exists(t):
482 score += 1
525 score += 1
483 return score
526 return score
484
527
485 abspfx = util.localpath(abspfx)
528 abspfx = util.localpath(abspfx)
486 striplen = len(abspfx)
529 striplen = len(abspfx)
487 if striplen:
530 if striplen:
488 striplen += len(os.sep)
531 striplen += len(os.sep)
489 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
532 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
490 score = evalpath(striplen)
533 score = evalpath(striplen)
491 striplen1 = len(os.path.split(abspfx)[0])
534 striplen1 = len(os.path.split(abspfx)[0])
492 if striplen1:
535 if striplen1:
493 striplen1 += len(os.sep)
536 striplen1 += len(os.sep)
494 if evalpath(striplen1) > score:
537 if evalpath(striplen1) > score:
495 striplen = striplen1
538 striplen = striplen1
496 res = lambda p: os.path.join(dest,
539 res = lambda p: os.path.join(dest,
497 util.localpath(p)[striplen:])
540 util.localpath(p)[striplen:])
498 else:
541 else:
499 # a file
542 # a file
500 if destdirexists:
543 if destdirexists:
501 res = lambda p: os.path.join(dest,
544 res = lambda p: os.path.join(dest,
502 os.path.basename(util.localpath(p)))
545 os.path.basename(util.localpath(p)))
503 else:
546 else:
504 res = lambda p: dest
547 res = lambda p: dest
505 return res
548 return res
506
549
507
550
508 pats = expandpats(pats)
551 pats = expandpats(pats)
509 if not pats:
552 if not pats:
510 raise util.Abort(_('no source or destination specified'))
553 raise util.Abort(_('no source or destination specified'))
511 if len(pats) == 1:
554 if len(pats) == 1:
512 raise util.Abort(_('no destination specified'))
555 raise util.Abort(_('no destination specified'))
513 dest = pats.pop()
556 dest = pats.pop()
514 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
557 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
515 if not destdirexists:
558 if not destdirexists:
516 if len(pats) > 1 or matchmod.patkind(pats[0]):
559 if len(pats) > 1 or matchmod.patkind(pats[0]):
517 raise util.Abort(_('with multiple sources, destination must be an '
560 raise util.Abort(_('with multiple sources, destination must be an '
518 'existing directory'))
561 'existing directory'))
519 if util.endswithsep(dest):
562 if util.endswithsep(dest):
520 raise util.Abort(_('destination %s is not a directory') % dest)
563 raise util.Abort(_('destination %s is not a directory') % dest)
521
564
522 tfn = targetpathfn
565 tfn = targetpathfn
523 if after:
566 if after:
524 tfn = targetpathafterfn
567 tfn = targetpathafterfn
525 copylist = []
568 copylist = []
526 for pat in pats:
569 for pat in pats:
527 srcs = walkpat(pat)
570 srcs = walkpat(pat)
528 if not srcs:
571 if not srcs:
529 continue
572 continue
530 copylist.append((tfn(pat, dest, srcs), srcs))
573 copylist.append((tfn(pat, dest, srcs), srcs))
531 if not copylist:
574 if not copylist:
532 raise util.Abort(_('no files to copy'))
575 raise util.Abort(_('no files to copy'))
533
576
534 errors = 0
577 errors = 0
535 for targetpath, srcs in copylist:
578 for targetpath, srcs in copylist:
536 for abssrc, relsrc, exact in srcs:
579 for abssrc, relsrc, exact in srcs:
537 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
580 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
538 errors += 1
581 errors += 1
539
582
540 if errors:
583 if errors:
541 ui.warn(_('(consider using --after)\n'))
584 ui.warn(_('(consider using --after)\n'))
542
585
543 return errors != 0
586 return errors != 0
544
587
545 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
588 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
546 runargs=None, appendpid=False):
589 runargs=None, appendpid=False):
547 '''Run a command as a service.'''
590 '''Run a command as a service.'''
548
591
549 if opts['daemon'] and not opts['daemon_pipefds']:
592 if opts['daemon'] and not opts['daemon_pipefds']:
550 # Signal child process startup with file removal
593 # Signal child process startup with file removal
551 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
594 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
552 os.close(lockfd)
595 os.close(lockfd)
553 try:
596 try:
554 if not runargs:
597 if not runargs:
555 runargs = util.hgcmd() + sys.argv[1:]
598 runargs = util.hgcmd() + sys.argv[1:]
556 runargs.append('--daemon-pipefds=%s' % lockpath)
599 runargs.append('--daemon-pipefds=%s' % lockpath)
557 # Don't pass --cwd to the child process, because we've already
600 # Don't pass --cwd to the child process, because we've already
558 # changed directory.
601 # changed directory.
559 for i in xrange(1, len(runargs)):
602 for i in xrange(1, len(runargs)):
560 if runargs[i].startswith('--cwd='):
603 if runargs[i].startswith('--cwd='):
561 del runargs[i]
604 del runargs[i]
562 break
605 break
563 elif runargs[i].startswith('--cwd'):
606 elif runargs[i].startswith('--cwd'):
564 del runargs[i:i + 2]
607 del runargs[i:i + 2]
565 break
608 break
566 def condfn():
609 def condfn():
567 return not os.path.exists(lockpath)
610 return not os.path.exists(lockpath)
568 pid = util.rundetached(runargs, condfn)
611 pid = util.rundetached(runargs, condfn)
569 if pid < 0:
612 if pid < 0:
570 raise util.Abort(_('child process failed to start'))
613 raise util.Abort(_('child process failed to start'))
571 finally:
614 finally:
572 try:
615 try:
573 os.unlink(lockpath)
616 os.unlink(lockpath)
574 except OSError, e:
617 except OSError, e:
575 if e.errno != errno.ENOENT:
618 if e.errno != errno.ENOENT:
576 raise
619 raise
577 if parentfn:
620 if parentfn:
578 return parentfn(pid)
621 return parentfn(pid)
579 else:
622 else:
580 return
623 return
581
624
582 if initfn:
625 if initfn:
583 initfn()
626 initfn()
584
627
585 if opts['pid_file']:
628 if opts['pid_file']:
586 mode = appendpid and 'a' or 'w'
629 mode = appendpid and 'a' or 'w'
587 fp = open(opts['pid_file'], mode)
630 fp = open(opts['pid_file'], mode)
588 fp.write(str(os.getpid()) + '\n')
631 fp.write(str(os.getpid()) + '\n')
589 fp.close()
632 fp.close()
590
633
591 if opts['daemon_pipefds']:
634 if opts['daemon_pipefds']:
592 lockpath = opts['daemon_pipefds']
635 lockpath = opts['daemon_pipefds']
593 try:
636 try:
594 os.setsid()
637 os.setsid()
595 except AttributeError:
638 except AttributeError:
596 pass
639 pass
597 os.unlink(lockpath)
640 os.unlink(lockpath)
598 util.hidewindow()
641 util.hidewindow()
599 sys.stdout.flush()
642 sys.stdout.flush()
600 sys.stderr.flush()
643 sys.stderr.flush()
601
644
602 nullfd = os.open(util.nulldev, os.O_RDWR)
645 nullfd = os.open(util.nulldev, os.O_RDWR)
603 logfilefd = nullfd
646 logfilefd = nullfd
604 if logfile:
647 if logfile:
605 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
648 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
606 os.dup2(nullfd, 0)
649 os.dup2(nullfd, 0)
607 os.dup2(logfilefd, 1)
650 os.dup2(logfilefd, 1)
608 os.dup2(logfilefd, 2)
651 os.dup2(logfilefd, 2)
609 if nullfd not in (0, 1, 2):
652 if nullfd not in (0, 1, 2):
610 os.close(nullfd)
653 os.close(nullfd)
611 if logfile and logfilefd not in (0, 1, 2):
654 if logfile and logfilefd not in (0, 1, 2):
612 os.close(logfilefd)
655 os.close(logfilefd)
613
656
614 if runfn:
657 if runfn:
615 return runfn()
658 return runfn()
616
659
617 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
660 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
618 opts=None):
661 opts=None):
619 '''export changesets as hg patches.'''
662 '''export changesets as hg patches.'''
620
663
621 total = len(revs)
664 total = len(revs)
622 revwidth = max([len(str(rev)) for rev in revs])
665 revwidth = max([len(str(rev)) for rev in revs])
623
666
624 def single(rev, seqno, fp):
667 def single(rev, seqno, fp):
625 ctx = repo[rev]
668 ctx = repo[rev]
626 node = ctx.node()
669 node = ctx.node()
627 parents = [p.node() for p in ctx.parents() if p]
670 parents = [p.node() for p in ctx.parents() if p]
628 branch = ctx.branch()
671 branch = ctx.branch()
629 if switch_parent:
672 if switch_parent:
630 parents.reverse()
673 parents.reverse()
631 prev = (parents and parents[0]) or nullid
674 prev = (parents and parents[0]) or nullid
632
675
633 if not fp:
676 if not fp:
634 fp = make_file(repo, template, node, total=total, seqno=seqno,
677 fp = make_file(repo, template, node, total=total, seqno=seqno,
635 revwidth=revwidth, mode='ab')
678 revwidth=revwidth, mode='ab')
636 if fp != sys.stdout and hasattr(fp, 'name'):
679 if fp != sys.stdout and hasattr(fp, 'name'):
637 repo.ui.note("%s\n" % fp.name)
680 repo.ui.note("%s\n" % fp.name)
638
681
639 fp.write("# HG changeset patch\n")
682 fp.write("# HG changeset patch\n")
640 fp.write("# User %s\n" % ctx.user())
683 fp.write("# User %s\n" % ctx.user())
641 fp.write("# Date %d %d\n" % ctx.date())
684 fp.write("# Date %d %d\n" % ctx.date())
642 if branch and branch != 'default':
685 if branch and branch != 'default':
643 fp.write("# Branch %s\n" % branch)
686 fp.write("# Branch %s\n" % branch)
644 fp.write("# Node ID %s\n" % hex(node))
687 fp.write("# Node ID %s\n" % hex(node))
645 fp.write("# Parent %s\n" % hex(prev))
688 fp.write("# Parent %s\n" % hex(prev))
646 if len(parents) > 1:
689 if len(parents) > 1:
647 fp.write("# Parent %s\n" % hex(parents[1]))
690 fp.write("# Parent %s\n" % hex(parents[1]))
648 fp.write(ctx.description().rstrip())
691 fp.write(ctx.description().rstrip())
649 fp.write("\n\n")
692 fp.write("\n\n")
650
693
651 for chunk in patch.diff(repo, prev, node, opts=opts):
694 for chunk in patch.diff(repo, prev, node, opts=opts):
652 fp.write(chunk)
695 fp.write(chunk)
653
696
654 for seqno, rev in enumerate(revs):
697 for seqno, rev in enumerate(revs):
655 single(rev, seqno + 1, fp)
698 single(rev, seqno + 1, fp)
656
699
657 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
700 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
658 changes=None, stat=False, fp=None, prefix='',
701 changes=None, stat=False, fp=None, prefix='',
659 listsubrepos=False):
702 listsubrepos=False):
660 '''show diff or diffstat.'''
703 '''show diff or diffstat.'''
661 if fp is None:
704 if fp is None:
662 write = ui.write
705 write = ui.write
663 else:
706 else:
664 def write(s, **kw):
707 def write(s, **kw):
665 fp.write(s)
708 fp.write(s)
666
709
667 if stat:
710 if stat:
668 diffopts = diffopts.copy(context=0)
711 diffopts = diffopts.copy(context=0)
669 width = 80
712 width = 80
670 if not ui.plain():
713 if not ui.plain():
671 width = util.termwidth()
714 width = util.termwidth()
672 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
715 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
673 prefix=prefix)
716 prefix=prefix)
674 for chunk, label in patch.diffstatui(util.iterlines(chunks),
717 for chunk, label in patch.diffstatui(util.iterlines(chunks),
675 width=width,
718 width=width,
676 git=diffopts.git):
719 git=diffopts.git):
677 write(chunk, label=label)
720 write(chunk, label=label)
678 else:
721 else:
679 for chunk, label in patch.diffui(repo, node1, node2, match,
722 for chunk, label in patch.diffui(repo, node1, node2, match,
680 changes, diffopts, prefix=prefix):
723 changes, diffopts, prefix=prefix):
681 write(chunk, label=label)
724 write(chunk, label=label)
682
725
683 if listsubrepos:
726 if listsubrepos:
684 ctx1 = repo[node1]
727 ctx1 = repo[node1]
685 ctx2 = repo[node2]
728 ctx2 = repo[node2]
686 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
729 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
687 if node2 is not None:
730 if node2 is not None:
688 node2 = ctx2.substate[subpath][1]
731 node2 = ctx2.substate[subpath][1]
689 submatch = matchmod.narrowmatcher(subpath, match)
732 submatch = matchmod.narrowmatcher(subpath, match)
690 sub.diff(diffopts, node2, submatch, changes=changes,
733 sub.diff(diffopts, node2, submatch, changes=changes,
691 stat=stat, fp=fp, prefix=prefix)
734 stat=stat, fp=fp, prefix=prefix)
692
735
693 class changeset_printer(object):
736 class changeset_printer(object):
694 '''show changeset information when templating not requested.'''
737 '''show changeset information when templating not requested.'''
695
738
696 def __init__(self, ui, repo, patch, diffopts, buffered):
739 def __init__(self, ui, repo, patch, diffopts, buffered):
697 self.ui = ui
740 self.ui = ui
698 self.repo = repo
741 self.repo = repo
699 self.buffered = buffered
742 self.buffered = buffered
700 self.patch = patch
743 self.patch = patch
701 self.diffopts = diffopts
744 self.diffopts = diffopts
702 self.header = {}
745 self.header = {}
703 self.hunk = {}
746 self.hunk = {}
704 self.lastheader = None
747 self.lastheader = None
705 self.footer = None
748 self.footer = None
706
749
707 def flush(self, rev):
750 def flush(self, rev):
708 if rev in self.header:
751 if rev in self.header:
709 h = self.header[rev]
752 h = self.header[rev]
710 if h != self.lastheader:
753 if h != self.lastheader:
711 self.lastheader = h
754 self.lastheader = h
712 self.ui.write(h)
755 self.ui.write(h)
713 del self.header[rev]
756 del self.header[rev]
714 if rev in self.hunk:
757 if rev in self.hunk:
715 self.ui.write(self.hunk[rev])
758 self.ui.write(self.hunk[rev])
716 del self.hunk[rev]
759 del self.hunk[rev]
717 return 1
760 return 1
718 return 0
761 return 0
719
762
720 def close(self):
763 def close(self):
721 if self.footer:
764 if self.footer:
722 self.ui.write(self.footer)
765 self.ui.write(self.footer)
723
766
724 def show(self, ctx, copies=None, matchfn=None, **props):
767 def show(self, ctx, copies=None, matchfn=None, **props):
725 if self.buffered:
768 if self.buffered:
726 self.ui.pushbuffer()
769 self.ui.pushbuffer()
727 self._show(ctx, copies, matchfn, props)
770 self._show(ctx, copies, matchfn, props)
728 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
771 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
729 else:
772 else:
730 self._show(ctx, copies, matchfn, props)
773 self._show(ctx, copies, matchfn, props)
731
774
732 def _show(self, ctx, copies, matchfn, props):
775 def _show(self, ctx, copies, matchfn, props):
733 '''show a single changeset or file revision'''
776 '''show a single changeset or file revision'''
734 changenode = ctx.node()
777 changenode = ctx.node()
735 rev = ctx.rev()
778 rev = ctx.rev()
736
779
737 if self.ui.quiet:
780 if self.ui.quiet:
738 self.ui.write("%d:%s\n" % (rev, short(changenode)),
781 self.ui.write("%d:%s\n" % (rev, short(changenode)),
739 label='log.node')
782 label='log.node')
740 return
783 return
741
784
742 log = self.repo.changelog
785 log = self.repo.changelog
743 date = util.datestr(ctx.date())
786 date = util.datestr(ctx.date())
744
787
745 hexfunc = self.ui.debugflag and hex or short
788 hexfunc = self.ui.debugflag and hex or short
746
789
747 parents = [(p, hexfunc(log.node(p)))
790 parents = [(p, hexfunc(log.node(p)))
748 for p in self._meaningful_parentrevs(log, rev)]
791 for p in self._meaningful_parentrevs(log, rev)]
749
792
750 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
793 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
751 label='log.changeset')
794 label='log.changeset')
752
795
753 branch = ctx.branch()
796 branch = ctx.branch()
754 # don't show the default branch name
797 # don't show the default branch name
755 if branch != 'default':
798 if branch != 'default':
756 branch = encoding.tolocal(branch)
799 branch = encoding.tolocal(branch)
757 self.ui.write(_("branch: %s\n") % branch,
800 self.ui.write(_("branch: %s\n") % branch,
758 label='log.branch')
801 label='log.branch')
759 for tag in self.repo.nodetags(changenode):
802 for tag in self.repo.nodetags(changenode):
760 self.ui.write(_("tag: %s\n") % tag,
803 self.ui.write(_("tag: %s\n") % tag,
761 label='log.tag')
804 label='log.tag')
762 for parent in parents:
805 for parent in parents:
763 self.ui.write(_("parent: %d:%s\n") % parent,
806 self.ui.write(_("parent: %d:%s\n") % parent,
764 label='log.parent')
807 label='log.parent')
765
808
766 if self.ui.debugflag:
809 if self.ui.debugflag:
767 mnode = ctx.manifestnode()
810 mnode = ctx.manifestnode()
768 self.ui.write(_("manifest: %d:%s\n") %
811 self.ui.write(_("manifest: %d:%s\n") %
769 (self.repo.manifest.rev(mnode), hex(mnode)),
812 (self.repo.manifest.rev(mnode), hex(mnode)),
770 label='ui.debug log.manifest')
813 label='ui.debug log.manifest')
771 self.ui.write(_("user: %s\n") % ctx.user(),
814 self.ui.write(_("user: %s\n") % ctx.user(),
772 label='log.user')
815 label='log.user')
773 self.ui.write(_("date: %s\n") % date,
816 self.ui.write(_("date: %s\n") % date,
774 label='log.date')
817 label='log.date')
775
818
776 if self.ui.debugflag:
819 if self.ui.debugflag:
777 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
820 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
778 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
821 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
779 files):
822 files):
780 if value:
823 if value:
781 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
824 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
782 label='ui.debug log.files')
825 label='ui.debug log.files')
783 elif ctx.files() and self.ui.verbose:
826 elif ctx.files() and self.ui.verbose:
784 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
827 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
785 label='ui.note log.files')
828 label='ui.note log.files')
786 if copies and self.ui.verbose:
829 if copies and self.ui.verbose:
787 copies = ['%s (%s)' % c for c in copies]
830 copies = ['%s (%s)' % c for c in copies]
788 self.ui.write(_("copies: %s\n") % ' '.join(copies),
831 self.ui.write(_("copies: %s\n") % ' '.join(copies),
789 label='ui.note log.copies')
832 label='ui.note log.copies')
790
833
791 extra = ctx.extra()
834 extra = ctx.extra()
792 if extra and self.ui.debugflag:
835 if extra and self.ui.debugflag:
793 for key, value in sorted(extra.items()):
836 for key, value in sorted(extra.items()):
794 self.ui.write(_("extra: %s=%s\n")
837 self.ui.write(_("extra: %s=%s\n")
795 % (key, value.encode('string_escape')),
838 % (key, value.encode('string_escape')),
796 label='ui.debug log.extra')
839 label='ui.debug log.extra')
797
840
798 description = ctx.description().strip()
841 description = ctx.description().strip()
799 if description:
842 if description:
800 if self.ui.verbose:
843 if self.ui.verbose:
801 self.ui.write(_("description:\n"),
844 self.ui.write(_("description:\n"),
802 label='ui.note log.description')
845 label='ui.note log.description')
803 self.ui.write(description,
846 self.ui.write(description,
804 label='ui.note log.description')
847 label='ui.note log.description')
805 self.ui.write("\n\n")
848 self.ui.write("\n\n")
806 else:
849 else:
807 self.ui.write(_("summary: %s\n") %
850 self.ui.write(_("summary: %s\n") %
808 description.splitlines()[0],
851 description.splitlines()[0],
809 label='log.summary')
852 label='log.summary')
810 self.ui.write("\n")
853 self.ui.write("\n")
811
854
812 self.showpatch(changenode, matchfn)
855 self.showpatch(changenode, matchfn)
813
856
814 def showpatch(self, node, matchfn):
857 def showpatch(self, node, matchfn):
815 if not matchfn:
858 if not matchfn:
816 matchfn = self.patch
859 matchfn = self.patch
817 if matchfn:
860 if matchfn:
818 stat = self.diffopts.get('stat')
861 stat = self.diffopts.get('stat')
819 diff = self.diffopts.get('patch')
862 diff = self.diffopts.get('patch')
820 diffopts = patch.diffopts(self.ui, self.diffopts)
863 diffopts = patch.diffopts(self.ui, self.diffopts)
821 prev = self.repo.changelog.parents(node)[0]
864 prev = self.repo.changelog.parents(node)[0]
822 if stat:
865 if stat:
823 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
866 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
824 match=matchfn, stat=True)
867 match=matchfn, stat=True)
825 if diff:
868 if diff:
826 if stat:
869 if stat:
827 self.ui.write("\n")
870 self.ui.write("\n")
828 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
871 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
829 match=matchfn, stat=False)
872 match=matchfn, stat=False)
830 self.ui.write("\n")
873 self.ui.write("\n")
831
874
832 def _meaningful_parentrevs(self, log, rev):
875 def _meaningful_parentrevs(self, log, rev):
833 """Return list of meaningful (or all if debug) parentrevs for rev.
876 """Return list of meaningful (or all if debug) parentrevs for rev.
834
877
835 For merges (two non-nullrev revisions) both parents are meaningful.
878 For merges (two non-nullrev revisions) both parents are meaningful.
836 Otherwise the first parent revision is considered meaningful if it
879 Otherwise the first parent revision is considered meaningful if it
837 is not the preceding revision.
880 is not the preceding revision.
838 """
881 """
839 parents = log.parentrevs(rev)
882 parents = log.parentrevs(rev)
840 if not self.ui.debugflag and parents[1] == nullrev:
883 if not self.ui.debugflag and parents[1] == nullrev:
841 if parents[0] >= rev - 1:
884 if parents[0] >= rev - 1:
842 parents = []
885 parents = []
843 else:
886 else:
844 parents = [parents[0]]
887 parents = [parents[0]]
845 return parents
888 return parents
846
889
847
890
848 class changeset_templater(changeset_printer):
891 class changeset_templater(changeset_printer):
849 '''format changeset information.'''
892 '''format changeset information.'''
850
893
851 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
894 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
852 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
895 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
853 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
896 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
854 defaulttempl = {
897 defaulttempl = {
855 'parent': '{rev}:{node|formatnode} ',
898 'parent': '{rev}:{node|formatnode} ',
856 'manifest': '{rev}:{node|formatnode}',
899 'manifest': '{rev}:{node|formatnode}',
857 'file_copy': '{name} ({source})',
900 'file_copy': '{name} ({source})',
858 'extra': '{key}={value|stringescape}'
901 'extra': '{key}={value|stringescape}'
859 }
902 }
860 # filecopy is preserved for compatibility reasons
903 # filecopy is preserved for compatibility reasons
861 defaulttempl['filecopy'] = defaulttempl['file_copy']
904 defaulttempl['filecopy'] = defaulttempl['file_copy']
862 self.t = templater.templater(mapfile, {'formatnode': formatnode},
905 self.t = templater.templater(mapfile, {'formatnode': formatnode},
863 cache=defaulttempl)
906 cache=defaulttempl)
864 self.cache = {}
907 self.cache = {}
865
908
866 def use_template(self, t):
909 def use_template(self, t):
867 '''set template string to use'''
910 '''set template string to use'''
868 self.t.cache['changeset'] = t
911 self.t.cache['changeset'] = t
869
912
870 def _meaningful_parentrevs(self, ctx):
913 def _meaningful_parentrevs(self, ctx):
871 """Return list of meaningful (or all if debug) parentrevs for rev.
914 """Return list of meaningful (or all if debug) parentrevs for rev.
872 """
915 """
873 parents = ctx.parents()
916 parents = ctx.parents()
874 if len(parents) > 1:
917 if len(parents) > 1:
875 return parents
918 return parents
876 if self.ui.debugflag:
919 if self.ui.debugflag:
877 return [parents[0], self.repo['null']]
920 return [parents[0], self.repo['null']]
878 if parents[0].rev() >= ctx.rev() - 1:
921 if parents[0].rev() >= ctx.rev() - 1:
879 return []
922 return []
880 return parents
923 return parents
881
924
882 def _show(self, ctx, copies, matchfn, props):
925 def _show(self, ctx, copies, matchfn, props):
883 '''show a single changeset or file revision'''
926 '''show a single changeset or file revision'''
884
927
885 showlist = templatekw.showlist
928 showlist = templatekw.showlist
886
929
887 # showparents() behaviour depends on ui trace level which
930 # showparents() behaviour depends on ui trace level which
888 # causes unexpected behaviours at templating level and makes
931 # causes unexpected behaviours at templating level and makes
889 # it harder to extract it in a standalone function. Its
932 # it harder to extract it in a standalone function. Its
890 # behaviour cannot be changed so leave it here for now.
933 # behaviour cannot be changed so leave it here for now.
891 def showparents(**args):
934 def showparents(**args):
892 ctx = args['ctx']
935 ctx = args['ctx']
893 parents = [[('rev', p.rev()), ('node', p.hex())]
936 parents = [[('rev', p.rev()), ('node', p.hex())]
894 for p in self._meaningful_parentrevs(ctx)]
937 for p in self._meaningful_parentrevs(ctx)]
895 return showlist('parent', parents, **args)
938 return showlist('parent', parents, **args)
896
939
897 props = props.copy()
940 props = props.copy()
898 props.update(templatekw.keywords)
941 props.update(templatekw.keywords)
899 props['parents'] = showparents
942 props['parents'] = showparents
900 props['templ'] = self.t
943 props['templ'] = self.t
901 props['ctx'] = ctx
944 props['ctx'] = ctx
902 props['repo'] = self.repo
945 props['repo'] = self.repo
903 props['revcache'] = {'copies': copies}
946 props['revcache'] = {'copies': copies}
904 props['cache'] = self.cache
947 props['cache'] = self.cache
905
948
906 # find correct templates for current mode
949 # find correct templates for current mode
907
950
908 tmplmodes = [
951 tmplmodes = [
909 (True, None),
952 (True, None),
910 (self.ui.verbose, 'verbose'),
953 (self.ui.verbose, 'verbose'),
911 (self.ui.quiet, 'quiet'),
954 (self.ui.quiet, 'quiet'),
912 (self.ui.debugflag, 'debug'),
955 (self.ui.debugflag, 'debug'),
913 ]
956 ]
914
957
915 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
958 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
916 for mode, postfix in tmplmodes:
959 for mode, postfix in tmplmodes:
917 for type in types:
960 for type in types:
918 cur = postfix and ('%s_%s' % (type, postfix)) or type
961 cur = postfix and ('%s_%s' % (type, postfix)) or type
919 if mode and cur in self.t:
962 if mode and cur in self.t:
920 types[type] = cur
963 types[type] = cur
921
964
922 try:
965 try:
923
966
924 # write header
967 # write header
925 if types['header']:
968 if types['header']:
926 h = templater.stringify(self.t(types['header'], **props))
969 h = templater.stringify(self.t(types['header'], **props))
927 if self.buffered:
970 if self.buffered:
928 self.header[ctx.rev()] = h
971 self.header[ctx.rev()] = h
929 else:
972 else:
930 if self.lastheader != h:
973 if self.lastheader != h:
931 self.lastheader = h
974 self.lastheader = h
932 self.ui.write(h)
975 self.ui.write(h)
933
976
934 # write changeset metadata, then patch if requested
977 # write changeset metadata, then patch if requested
935 key = types['changeset']
978 key = types['changeset']
936 self.ui.write(templater.stringify(self.t(key, **props)))
979 self.ui.write(templater.stringify(self.t(key, **props)))
937 self.showpatch(ctx.node(), matchfn)
980 self.showpatch(ctx.node(), matchfn)
938
981
939 if types['footer']:
982 if types['footer']:
940 if not self.footer:
983 if not self.footer:
941 self.footer = templater.stringify(self.t(types['footer'],
984 self.footer = templater.stringify(self.t(types['footer'],
942 **props))
985 **props))
943
986
944 except KeyError, inst:
987 except KeyError, inst:
945 msg = _("%s: no key named '%s'")
988 msg = _("%s: no key named '%s'")
946 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
989 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
947 except SyntaxError, inst:
990 except SyntaxError, inst:
948 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
991 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
949
992
950 def show_changeset(ui, repo, opts, buffered=False):
993 def show_changeset(ui, repo, opts, buffered=False):
951 """show one changeset using template or regular display.
994 """show one changeset using template or regular display.
952
995
953 Display format will be the first non-empty hit of:
996 Display format will be the first non-empty hit of:
954 1. option 'template'
997 1. option 'template'
955 2. option 'style'
998 2. option 'style'
956 3. [ui] setting 'logtemplate'
999 3. [ui] setting 'logtemplate'
957 4. [ui] setting 'style'
1000 4. [ui] setting 'style'
958 If all of these values are either the unset or the empty string,
1001 If all of these values are either the unset or the empty string,
959 regular display via changeset_printer() is done.
1002 regular display via changeset_printer() is done.
960 """
1003 """
961 # options
1004 # options
962 patch = False
1005 patch = False
963 if opts.get('patch') or opts.get('stat'):
1006 if opts.get('patch') or opts.get('stat'):
964 patch = matchall(repo)
1007 patch = matchall(repo)
965
1008
966 tmpl = opts.get('template')
1009 tmpl = opts.get('template')
967 style = None
1010 style = None
968 if tmpl:
1011 if tmpl:
969 tmpl = templater.parsestring(tmpl, quoted=False)
1012 tmpl = templater.parsestring(tmpl, quoted=False)
970 else:
1013 else:
971 style = opts.get('style')
1014 style = opts.get('style')
972
1015
973 # ui settings
1016 # ui settings
974 if not (tmpl or style):
1017 if not (tmpl or style):
975 tmpl = ui.config('ui', 'logtemplate')
1018 tmpl = ui.config('ui', 'logtemplate')
976 if tmpl:
1019 if tmpl:
977 tmpl = templater.parsestring(tmpl)
1020 tmpl = templater.parsestring(tmpl)
978 else:
1021 else:
979 style = util.expandpath(ui.config('ui', 'style', ''))
1022 style = util.expandpath(ui.config('ui', 'style', ''))
980
1023
981 if not (tmpl or style):
1024 if not (tmpl or style):
982 return changeset_printer(ui, repo, patch, opts, buffered)
1025 return changeset_printer(ui, repo, patch, opts, buffered)
983
1026
984 mapfile = None
1027 mapfile = None
985 if style and not tmpl:
1028 if style and not tmpl:
986 mapfile = style
1029 mapfile = style
987 if not os.path.split(mapfile)[0]:
1030 if not os.path.split(mapfile)[0]:
988 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1031 mapname = (templater.templatepath('map-cmdline.' + mapfile)
989 or templater.templatepath(mapfile))
1032 or templater.templatepath(mapfile))
990 if mapname:
1033 if mapname:
991 mapfile = mapname
1034 mapfile = mapname
992
1035
993 try:
1036 try:
994 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1037 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
995 except SyntaxError, inst:
1038 except SyntaxError, inst:
996 raise util.Abort(inst.args[0])
1039 raise util.Abort(inst.args[0])
997 if tmpl:
1040 if tmpl:
998 t.use_template(tmpl)
1041 t.use_template(tmpl)
999 return t
1042 return t
1000
1043
1001 def finddate(ui, repo, date):
1044 def finddate(ui, repo, date):
1002 """Find the tipmost changeset that matches the given date spec"""
1045 """Find the tipmost changeset that matches the given date spec"""
1003
1046
1004 df = util.matchdate(date)
1047 df = util.matchdate(date)
1005 m = matchall(repo)
1048 m = matchall(repo)
1006 results = {}
1049 results = {}
1007
1050
1008 def prep(ctx, fns):
1051 def prep(ctx, fns):
1009 d = ctx.date()
1052 d = ctx.date()
1010 if df(d[0]):
1053 if df(d[0]):
1011 results[ctx.rev()] = d
1054 results[ctx.rev()] = d
1012
1055
1013 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1056 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1014 rev = ctx.rev()
1057 rev = ctx.rev()
1015 if rev in results:
1058 if rev in results:
1016 ui.status(_("Found revision %s from %s\n") %
1059 ui.status(_("Found revision %s from %s\n") %
1017 (rev, util.datestr(results[rev])))
1060 (rev, util.datestr(results[rev])))
1018 return str(rev)
1061 return str(rev)
1019
1062
1020 raise util.Abort(_("revision matching date not found"))
1063 raise util.Abort(_("revision matching date not found"))
1021
1064
1022 def walkchangerevs(repo, match, opts, prepare):
1065 def walkchangerevs(repo, match, opts, prepare):
1023 '''Iterate over files and the revs in which they changed.
1066 '''Iterate over files and the revs in which they changed.
1024
1067
1025 Callers most commonly need to iterate backwards over the history
1068 Callers most commonly need to iterate backwards over the history
1026 in which they are interested. Doing so has awful (quadratic-looking)
1069 in which they are interested. Doing so has awful (quadratic-looking)
1027 performance, so we use iterators in a "windowed" way.
1070 performance, so we use iterators in a "windowed" way.
1028
1071
1029 We walk a window of revisions in the desired order. Within the
1072 We walk a window of revisions in the desired order. Within the
1030 window, we first walk forwards to gather data, then in the desired
1073 window, we first walk forwards to gather data, then in the desired
1031 order (usually backwards) to display it.
1074 order (usually backwards) to display it.
1032
1075
1033 This function returns an iterator yielding contexts. Before
1076 This function returns an iterator yielding contexts. Before
1034 yielding each context, the iterator will first call the prepare
1077 yielding each context, the iterator will first call the prepare
1035 function on each context in the window in forward order.'''
1078 function on each context in the window in forward order.'''
1036
1079
1037 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1080 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1038 if start < end:
1081 if start < end:
1039 while start < end:
1082 while start < end:
1040 yield start, min(windowsize, end - start)
1083 yield start, min(windowsize, end - start)
1041 start += windowsize
1084 start += windowsize
1042 if windowsize < sizelimit:
1085 if windowsize < sizelimit:
1043 windowsize *= 2
1086 windowsize *= 2
1044 else:
1087 else:
1045 while start > end:
1088 while start > end:
1046 yield start, min(windowsize, start - end - 1)
1089 yield start, min(windowsize, start - end - 1)
1047 start -= windowsize
1090 start -= windowsize
1048 if windowsize < sizelimit:
1091 if windowsize < sizelimit:
1049 windowsize *= 2
1092 windowsize *= 2
1050
1093
1051 follow = opts.get('follow') or opts.get('follow_first')
1094 follow = opts.get('follow') or opts.get('follow_first')
1052
1095
1053 if not len(repo):
1096 if not len(repo):
1054 return []
1097 return []
1055
1098
1056 if follow:
1099 if follow:
1057 defrange = '%s:0' % repo['.'].rev()
1100 defrange = '%s:0' % repo['.'].rev()
1058 else:
1101 else:
1059 defrange = '-1:0'
1102 defrange = '-1:0'
1060 revs = revrange(repo, opts['rev'] or [defrange])
1103 revs = revrange(repo, opts['rev'] or [defrange])
1061 if not revs:
1104 if not revs:
1062 return []
1105 return []
1063 wanted = set()
1106 wanted = set()
1064 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1107 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1065 fncache = {}
1108 fncache = {}
1066 change = util.cachefunc(repo.changectx)
1109 change = util.cachefunc(repo.changectx)
1067
1110
1068 # First step is to fill wanted, the set of revisions that we want to yield.
1111 # First step is to fill wanted, the set of revisions that we want to yield.
1069 # When it does not induce extra cost, we also fill fncache for revisions in
1112 # When it does not induce extra cost, we also fill fncache for revisions in
1070 # wanted: a cache of filenames that were changed (ctx.files()) and that
1113 # wanted: a cache of filenames that were changed (ctx.files()) and that
1071 # match the file filtering conditions.
1114 # match the file filtering conditions.
1072
1115
1073 if not slowpath and not match.files():
1116 if not slowpath and not match.files():
1074 # No files, no patterns. Display all revs.
1117 # No files, no patterns. Display all revs.
1075 wanted = set(revs)
1118 wanted = set(revs)
1076 copies = []
1119 copies = []
1077
1120
1078 if not slowpath:
1121 if not slowpath:
1079 # We only have to read through the filelog to find wanted revisions
1122 # We only have to read through the filelog to find wanted revisions
1080
1123
1081 minrev, maxrev = min(revs), max(revs)
1124 minrev, maxrev = min(revs), max(revs)
1082 def filerevgen(filelog, last):
1125 def filerevgen(filelog, last):
1083 """
1126 """
1084 Only files, no patterns. Check the history of each file.
1127 Only files, no patterns. Check the history of each file.
1085
1128
1086 Examines filelog entries within minrev, maxrev linkrev range
1129 Examines filelog entries within minrev, maxrev linkrev range
1087 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1130 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1088 tuples in backwards order
1131 tuples in backwards order
1089 """
1132 """
1090 cl_count = len(repo)
1133 cl_count = len(repo)
1091 revs = []
1134 revs = []
1092 for j in xrange(0, last + 1):
1135 for j in xrange(0, last + 1):
1093 linkrev = filelog.linkrev(j)
1136 linkrev = filelog.linkrev(j)
1094 if linkrev < minrev:
1137 if linkrev < minrev:
1095 continue
1138 continue
1096 # only yield rev for which we have the changelog, it can
1139 # only yield rev for which we have the changelog, it can
1097 # happen while doing "hg log" during a pull or commit
1140 # happen while doing "hg log" during a pull or commit
1098 if linkrev > maxrev or linkrev >= cl_count:
1141 if linkrev > maxrev or linkrev >= cl_count:
1099 break
1142 break
1100
1143
1101 parentlinkrevs = []
1144 parentlinkrevs = []
1102 for p in filelog.parentrevs(j):
1145 for p in filelog.parentrevs(j):
1103 if p != nullrev:
1146 if p != nullrev:
1104 parentlinkrevs.append(filelog.linkrev(p))
1147 parentlinkrevs.append(filelog.linkrev(p))
1105 n = filelog.node(j)
1148 n = filelog.node(j)
1106 revs.append((linkrev, parentlinkrevs,
1149 revs.append((linkrev, parentlinkrevs,
1107 follow and filelog.renamed(n)))
1150 follow and filelog.renamed(n)))
1108
1151
1109 return reversed(revs)
1152 return reversed(revs)
1110 def iterfiles():
1153 def iterfiles():
1111 for filename in match.files():
1154 for filename in match.files():
1112 yield filename, None
1155 yield filename, None
1113 for filename_node in copies:
1156 for filename_node in copies:
1114 yield filename_node
1157 yield filename_node
1115 for file_, node in iterfiles():
1158 for file_, node in iterfiles():
1116 filelog = repo.file(file_)
1159 filelog = repo.file(file_)
1117 if not len(filelog):
1160 if not len(filelog):
1118 if node is None:
1161 if node is None:
1119 # A zero count may be a directory or deleted file, so
1162 # A zero count may be a directory or deleted file, so
1120 # try to find matching entries on the slow path.
1163 # try to find matching entries on the slow path.
1121 if follow:
1164 if follow:
1122 raise util.Abort(
1165 raise util.Abort(
1123 _('cannot follow nonexistent file: "%s"') % file_)
1166 _('cannot follow nonexistent file: "%s"') % file_)
1124 slowpath = True
1167 slowpath = True
1125 break
1168 break
1126 else:
1169 else:
1127 continue
1170 continue
1128
1171
1129 if node is None:
1172 if node is None:
1130 last = len(filelog) - 1
1173 last = len(filelog) - 1
1131 else:
1174 else:
1132 last = filelog.rev(node)
1175 last = filelog.rev(node)
1133
1176
1134
1177
1135 # keep track of all ancestors of the file
1178 # keep track of all ancestors of the file
1136 ancestors = set([filelog.linkrev(last)])
1179 ancestors = set([filelog.linkrev(last)])
1137
1180
1138 # iterate from latest to oldest revision
1181 # iterate from latest to oldest revision
1139 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1182 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1140 if rev not in ancestors:
1183 if rev not in ancestors:
1141 continue
1184 continue
1142 # XXX insert 1327 fix here
1185 # XXX insert 1327 fix here
1143 if flparentlinkrevs:
1186 if flparentlinkrevs:
1144 ancestors.update(flparentlinkrevs)
1187 ancestors.update(flparentlinkrevs)
1145
1188
1146 fncache.setdefault(rev, []).append(file_)
1189 fncache.setdefault(rev, []).append(file_)
1147 wanted.add(rev)
1190 wanted.add(rev)
1148 if copied:
1191 if copied:
1149 copies.append(copied)
1192 copies.append(copied)
1150 if slowpath:
1193 if slowpath:
1151 # We have to read the changelog to match filenames against
1194 # We have to read the changelog to match filenames against
1152 # changed files
1195 # changed files
1153
1196
1154 if follow:
1197 if follow:
1155 raise util.Abort(_('can only follow copies/renames for explicit '
1198 raise util.Abort(_('can only follow copies/renames for explicit '
1156 'filenames'))
1199 'filenames'))
1157
1200
1158 # The slow path checks files modified in every changeset.
1201 # The slow path checks files modified in every changeset.
1159 for i in sorted(revs):
1202 for i in sorted(revs):
1160 ctx = change(i)
1203 ctx = change(i)
1161 matches = filter(match, ctx.files())
1204 matches = filter(match, ctx.files())
1162 if matches:
1205 if matches:
1163 fncache[i] = matches
1206 fncache[i] = matches
1164 wanted.add(i)
1207 wanted.add(i)
1165
1208
1166 class followfilter(object):
1209 class followfilter(object):
1167 def __init__(self, onlyfirst=False):
1210 def __init__(self, onlyfirst=False):
1168 self.startrev = nullrev
1211 self.startrev = nullrev
1169 self.roots = set()
1212 self.roots = set()
1170 self.onlyfirst = onlyfirst
1213 self.onlyfirst = onlyfirst
1171
1214
1172 def match(self, rev):
1215 def match(self, rev):
1173 def realparents(rev):
1216 def realparents(rev):
1174 if self.onlyfirst:
1217 if self.onlyfirst:
1175 return repo.changelog.parentrevs(rev)[0:1]
1218 return repo.changelog.parentrevs(rev)[0:1]
1176 else:
1219 else:
1177 return filter(lambda x: x != nullrev,
1220 return filter(lambda x: x != nullrev,
1178 repo.changelog.parentrevs(rev))
1221 repo.changelog.parentrevs(rev))
1179
1222
1180 if self.startrev == nullrev:
1223 if self.startrev == nullrev:
1181 self.startrev = rev
1224 self.startrev = rev
1182 return True
1225 return True
1183
1226
1184 if rev > self.startrev:
1227 if rev > self.startrev:
1185 # forward: all descendants
1228 # forward: all descendants
1186 if not self.roots:
1229 if not self.roots:
1187 self.roots.add(self.startrev)
1230 self.roots.add(self.startrev)
1188 for parent in realparents(rev):
1231 for parent in realparents(rev):
1189 if parent in self.roots:
1232 if parent in self.roots:
1190 self.roots.add(rev)
1233 self.roots.add(rev)
1191 return True
1234 return True
1192 else:
1235 else:
1193 # backwards: all parents
1236 # backwards: all parents
1194 if not self.roots:
1237 if not self.roots:
1195 self.roots.update(realparents(self.startrev))
1238 self.roots.update(realparents(self.startrev))
1196 if rev in self.roots:
1239 if rev in self.roots:
1197 self.roots.remove(rev)
1240 self.roots.remove(rev)
1198 self.roots.update(realparents(rev))
1241 self.roots.update(realparents(rev))
1199 return True
1242 return True
1200
1243
1201 return False
1244 return False
1202
1245
1203 # it might be worthwhile to do this in the iterator if the rev range
1246 # it might be worthwhile to do this in the iterator if the rev range
1204 # is descending and the prune args are all within that range
1247 # is descending and the prune args are all within that range
1205 for rev in opts.get('prune', ()):
1248 for rev in opts.get('prune', ()):
1206 rev = repo.changelog.rev(repo.lookup(rev))
1249 rev = repo.changelog.rev(repo.lookup(rev))
1207 ff = followfilter()
1250 ff = followfilter()
1208 stop = min(revs[0], revs[-1])
1251 stop = min(revs[0], revs[-1])
1209 for x in xrange(rev, stop - 1, -1):
1252 for x in xrange(rev, stop - 1, -1):
1210 if ff.match(x):
1253 if ff.match(x):
1211 wanted.discard(x)
1254 wanted.discard(x)
1212
1255
1213 # Now that wanted is correctly initialized, we can iterate over the
1256 # Now that wanted is correctly initialized, we can iterate over the
1214 # revision range, yielding only revisions in wanted.
1257 # revision range, yielding only revisions in wanted.
1215 def iterate():
1258 def iterate():
1216 if follow and not match.files():
1259 if follow and not match.files():
1217 ff = followfilter(onlyfirst=opts.get('follow_first'))
1260 ff = followfilter(onlyfirst=opts.get('follow_first'))
1218 def want(rev):
1261 def want(rev):
1219 return ff.match(rev) and rev in wanted
1262 return ff.match(rev) and rev in wanted
1220 else:
1263 else:
1221 def want(rev):
1264 def want(rev):
1222 return rev in wanted
1265 return rev in wanted
1223
1266
1224 for i, window in increasing_windows(0, len(revs)):
1267 for i, window in increasing_windows(0, len(revs)):
1225 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1268 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1226 for rev in sorted(nrevs):
1269 for rev in sorted(nrevs):
1227 fns = fncache.get(rev)
1270 fns = fncache.get(rev)
1228 ctx = change(rev)
1271 ctx = change(rev)
1229 if not fns:
1272 if not fns:
1230 def fns_generator():
1273 def fns_generator():
1231 for f in ctx.files():
1274 for f in ctx.files():
1232 if match(f):
1275 if match(f):
1233 yield f
1276 yield f
1234 fns = fns_generator()
1277 fns = fns_generator()
1235 prepare(ctx, fns)
1278 prepare(ctx, fns)
1236 for rev in nrevs:
1279 for rev in nrevs:
1237 yield change(rev)
1280 yield change(rev)
1238 return iterate()
1281 return iterate()
1239
1282
1240 def commit(ui, repo, commitfunc, pats, opts):
1283 def commit(ui, repo, commitfunc, pats, opts):
1241 '''commit the specified files or all outstanding changes'''
1284 '''commit the specified files or all outstanding changes'''
1242 date = opts.get('date')
1285 date = opts.get('date')
1243 if date:
1286 if date:
1244 opts['date'] = util.parsedate(date)
1287 opts['date'] = util.parsedate(date)
1245 message = logmessage(opts)
1288 message = logmessage(opts)
1246
1289
1247 # extract addremove carefully -- this function can be called from a command
1290 # extract addremove carefully -- this function can be called from a command
1248 # that doesn't support addremove
1291 # that doesn't support addremove
1249 if opts.get('addremove'):
1292 if opts.get('addremove'):
1250 addremove(repo, pats, opts)
1293 addremove(repo, pats, opts)
1251
1294
1252 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1295 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1253
1296
1254 def commiteditor(repo, ctx, subs):
1297 def commiteditor(repo, ctx, subs):
1255 if ctx.description():
1298 if ctx.description():
1256 return ctx.description()
1299 return ctx.description()
1257 return commitforceeditor(repo, ctx, subs)
1300 return commitforceeditor(repo, ctx, subs)
1258
1301
1259 def commitforceeditor(repo, ctx, subs):
1302 def commitforceeditor(repo, ctx, subs):
1260 edittext = []
1303 edittext = []
1261 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1304 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1262 if ctx.description():
1305 if ctx.description():
1263 edittext.append(ctx.description())
1306 edittext.append(ctx.description())
1264 edittext.append("")
1307 edittext.append("")
1265 edittext.append("") # Empty line between message and comments.
1308 edittext.append("") # Empty line between message and comments.
1266 edittext.append(_("HG: Enter commit message."
1309 edittext.append(_("HG: Enter commit message."
1267 " Lines beginning with 'HG:' are removed."))
1310 " Lines beginning with 'HG:' are removed."))
1268 edittext.append(_("HG: Leave message empty to abort commit."))
1311 edittext.append(_("HG: Leave message empty to abort commit."))
1269 edittext.append("HG: --")
1312 edittext.append("HG: --")
1270 edittext.append(_("HG: user: %s") % ctx.user())
1313 edittext.append(_("HG: user: %s") % ctx.user())
1271 if ctx.p2():
1314 if ctx.p2():
1272 edittext.append(_("HG: branch merge"))
1315 edittext.append(_("HG: branch merge"))
1273 if ctx.branch():
1316 if ctx.branch():
1274 edittext.append(_("HG: branch '%s'")
1317 edittext.append(_("HG: branch '%s'")
1275 % encoding.tolocal(ctx.branch()))
1318 % encoding.tolocal(ctx.branch()))
1276 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1319 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1277 edittext.extend([_("HG: added %s") % f for f in added])
1320 edittext.extend([_("HG: added %s") % f for f in added])
1278 edittext.extend([_("HG: changed %s") % f for f in modified])
1321 edittext.extend([_("HG: changed %s") % f for f in modified])
1279 edittext.extend([_("HG: removed %s") % f for f in removed])
1322 edittext.extend([_("HG: removed %s") % f for f in removed])
1280 if not added and not modified and not removed:
1323 if not added and not modified and not removed:
1281 edittext.append(_("HG: no files changed"))
1324 edittext.append(_("HG: no files changed"))
1282 edittext.append("")
1325 edittext.append("")
1283 # run editor in the repository root
1326 # run editor in the repository root
1284 olddir = os.getcwd()
1327 olddir = os.getcwd()
1285 os.chdir(repo.root)
1328 os.chdir(repo.root)
1286 text = repo.ui.edit("\n".join(edittext), ctx.user())
1329 text = repo.ui.edit("\n".join(edittext), ctx.user())
1287 text = re.sub("(?m)^HG:.*\n", "", text)
1330 text = re.sub("(?m)^HG:.*\n", "", text)
1288 os.chdir(olddir)
1331 os.chdir(olddir)
1289
1332
1290 if not text.strip():
1333 if not text.strip():
1291 raise util.Abort(_("empty commit message"))
1334 raise util.Abort(_("empty commit message"))
1292
1335
1293 return text
1336 return text
@@ -1,4522 +1,4522 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, util, revlog, bundlerepo, extensions, copies, error
12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 import patch, help, mdiff, url, encoding, templatekw, discovery
13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset
16 import minirst, revset
17 import dagparser
17 import dagparser
18
18
19 # Commands start here, listed alphabetically
19 # Commands start here, listed alphabetically
20
20
21 def add(ui, repo, *pats, **opts):
21 def add(ui, repo, *pats, **opts):
22 """add the specified files on the next commit
22 """add the specified files on the next commit
23
23
24 Schedule files to be version controlled and added to the
24 Schedule files to be version controlled and added to the
25 repository.
25 repository.
26
26
27 The files will be added to the repository at the next commit. To
27 The files will be added to the repository at the next commit. To
28 undo an add before that, see :hg:`forget`.
28 undo an add before that, see :hg:`forget`.
29
29
30 If no names are given, add all files to the repository.
30 If no names are given, add all files to the repository.
31
31
32 .. container:: verbose
32 .. container:: verbose
33
33
34 An example showing how new (unknown) files are added
34 An example showing how new (unknown) files are added
35 automatically by :hg:`add`::
35 automatically by :hg:`add`::
36
36
37 $ ls
37 $ ls
38 foo.c
38 foo.c
39 $ hg status
39 $ hg status
40 ? foo.c
40 ? foo.c
41 $ hg add
41 $ hg add
42 adding foo.c
42 adding foo.c
43 $ hg status
43 $ hg status
44 A foo.c
44 A foo.c
45
45
46 Returns 0 if all files are successfully added.
46 Returns 0 if all files are successfully added.
47 """
47 """
48
48
49 bad = []
49 bad = []
50 names = []
50 names = []
51 m = cmdutil.match(repo, pats, opts)
51 m = cmdutil.match(repo, pats, opts)
52 oldbad = m.bad
52 oldbad = m.bad
53 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
53 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
54
54
55 for f in repo.walk(m):
55 for f in repo.walk(m):
56 exact = m.exact(f)
56 exact = m.exact(f)
57 if exact or f not in repo.dirstate:
57 if exact or f not in repo.dirstate:
58 names.append(f)
58 names.append(f)
59 if ui.verbose or not exact:
59 if ui.verbose or not exact:
60 ui.status(_('adding %s\n') % m.rel(f))
60 ui.status(_('adding %s\n') % m.rel(f))
61 if not opts.get('dry_run'):
61 if not opts.get('dry_run'):
62 rejected = repo[None].add(names)
62 rejected = repo[None].add(names)
63 bad += [f for f in rejected if f in m.files()]
63 bad += [f for f in rejected if f in m.files()]
64 return bad and 1 or 0
64 return bad and 1 or 0
65
65
66 def addremove(ui, repo, *pats, **opts):
66 def addremove(ui, repo, *pats, **opts):
67 """add all new files, delete all missing files
67 """add all new files, delete all missing files
68
68
69 Add all new files and remove all missing files from the
69 Add all new files and remove all missing files from the
70 repository.
70 repository.
71
71
72 New files are ignored if they match any of the patterns in
72 New files are ignored if they match any of the patterns in
73 .hgignore. As with add, these changes take effect at the next
73 .hgignore. As with add, these changes take effect at the next
74 commit.
74 commit.
75
75
76 Use the -s/--similarity option to detect renamed files. With a
76 Use the -s/--similarity option to detect renamed files. With a
77 parameter greater than 0, this compares every removed file with
77 parameter greater than 0, this compares every removed file with
78 every added file and records those similar enough as renames. This
78 every added file and records those similar enough as renames. This
79 option takes a percentage between 0 (disabled) and 100 (files must
79 option takes a percentage between 0 (disabled) and 100 (files must
80 be identical) as its parameter. Detecting renamed files this way
80 be identical) as its parameter. Detecting renamed files this way
81 can be expensive. After using this option, :hg:`status -C` can be
81 can be expensive. After using this option, :hg:`status -C` can be
82 used to check which files were identified as moved or renamed.
82 used to check which files were identified as moved or renamed.
83
83
84 Returns 0 if all files are successfully added.
84 Returns 0 if all files are successfully added.
85 """
85 """
86 try:
86 try:
87 sim = float(opts.get('similarity') or 100)
87 sim = float(opts.get('similarity') or 100)
88 except ValueError:
88 except ValueError:
89 raise util.Abort(_('similarity must be a number'))
89 raise util.Abort(_('similarity must be a number'))
90 if sim < 0 or sim > 100:
90 if sim < 0 or sim > 100:
91 raise util.Abort(_('similarity must be between 0 and 100'))
91 raise util.Abort(_('similarity must be between 0 and 100'))
92 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
92 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
93
93
94 def annotate(ui, repo, *pats, **opts):
94 def annotate(ui, repo, *pats, **opts):
95 """show changeset information by line for each file
95 """show changeset information by line for each file
96
96
97 List changes in files, showing the revision id responsible for
97 List changes in files, showing the revision id responsible for
98 each line
98 each line
99
99
100 This command is useful for discovering when a change was made and
100 This command is useful for discovering when a change was made and
101 by whom.
101 by whom.
102
102
103 Without the -a/--text option, annotate will avoid processing files
103 Without the -a/--text option, annotate will avoid processing files
104 it detects as binary. With -a, annotate will annotate the file
104 it detects as binary. With -a, annotate will annotate the file
105 anyway, although the results will probably be neither useful
105 anyway, although the results will probably be neither useful
106 nor desirable.
106 nor desirable.
107
107
108 Returns 0 on success.
108 Returns 0 on success.
109 """
109 """
110 if opts.get('follow'):
110 if opts.get('follow'):
111 # --follow is deprecated and now just an alias for -f/--file
111 # --follow is deprecated and now just an alias for -f/--file
112 # to mimic the behavior of Mercurial before version 1.5
112 # to mimic the behavior of Mercurial before version 1.5
113 opts['file'] = 1
113 opts['file'] = 1
114
114
115 datefunc = ui.quiet and util.shortdate or util.datestr
115 datefunc = ui.quiet and util.shortdate or util.datestr
116 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
116 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
117
117
118 if not pats:
118 if not pats:
119 raise util.Abort(_('at least one filename or pattern is required'))
119 raise util.Abort(_('at least one filename or pattern is required'))
120
120
121 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
121 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
122 ('number', lambda x: str(x[0].rev())),
122 ('number', lambda x: str(x[0].rev())),
123 ('changeset', lambda x: short(x[0].node())),
123 ('changeset', lambda x: short(x[0].node())),
124 ('date', getdate),
124 ('date', getdate),
125 ('file', lambda x: x[0].path()),
125 ('file', lambda x: x[0].path()),
126 ]
126 ]
127
127
128 if (not opts.get('user') and not opts.get('changeset')
128 if (not opts.get('user') and not opts.get('changeset')
129 and not opts.get('date') and not opts.get('file')):
129 and not opts.get('date') and not opts.get('file')):
130 opts['number'] = 1
130 opts['number'] = 1
131
131
132 linenumber = opts.get('line_number') is not None
132 linenumber = opts.get('line_number') is not None
133 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
133 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
134 raise util.Abort(_('at least one of -n/-c is required for -l'))
134 raise util.Abort(_('at least one of -n/-c is required for -l'))
135
135
136 funcmap = [func for op, func in opmap if opts.get(op)]
136 funcmap = [func for op, func in opmap if opts.get(op)]
137 if linenumber:
137 if linenumber:
138 lastfunc = funcmap[-1]
138 lastfunc = funcmap[-1]
139 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
139 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
140
140
141 ctx = repo[opts.get('rev')]
141 ctx = repo[opts.get('rev')]
142 m = cmdutil.match(repo, pats, opts)
142 m = cmdutil.match(repo, pats, opts)
143 follow = not opts.get('no_follow')
143 follow = not opts.get('no_follow')
144 for abs in ctx.walk(m):
144 for abs in ctx.walk(m):
145 fctx = ctx[abs]
145 fctx = ctx[abs]
146 if not opts.get('text') and util.binary(fctx.data()):
146 if not opts.get('text') and util.binary(fctx.data()):
147 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
147 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
148 continue
148 continue
149
149
150 lines = fctx.annotate(follow=follow, linenumber=linenumber)
150 lines = fctx.annotate(follow=follow, linenumber=linenumber)
151 pieces = []
151 pieces = []
152
152
153 for f in funcmap:
153 for f in funcmap:
154 l = [f(n) for n, dummy in lines]
154 l = [f(n) for n, dummy in lines]
155 if l:
155 if l:
156 sized = [(x, encoding.colwidth(x)) for x in l]
156 sized = [(x, encoding.colwidth(x)) for x in l]
157 ml = max([w for x, w in sized])
157 ml = max([w for x, w in sized])
158 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
158 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
159
159
160 if pieces:
160 if pieces:
161 for p, l in zip(zip(*pieces), lines):
161 for p, l in zip(zip(*pieces), lines):
162 ui.write("%s: %s" % (" ".join(p), l[1]))
162 ui.write("%s: %s" % (" ".join(p), l[1]))
163
163
164 def archive(ui, repo, dest, **opts):
164 def archive(ui, repo, dest, **opts):
165 '''create an unversioned archive of a repository revision
165 '''create an unversioned archive of a repository revision
166
166
167 By default, the revision used is the parent of the working
167 By default, the revision used is the parent of the working
168 directory; use -r/--rev to specify a different revision.
168 directory; use -r/--rev to specify a different revision.
169
169
170 The archive type is automatically detected based on file
170 The archive type is automatically detected based on file
171 extension (or override using -t/--type).
171 extension (or override using -t/--type).
172
172
173 Valid types are:
173 Valid types are:
174
174
175 :``files``: a directory full of files (default)
175 :``files``: a directory full of files (default)
176 :``tar``: tar archive, uncompressed
176 :``tar``: tar archive, uncompressed
177 :``tbz2``: tar archive, compressed using bzip2
177 :``tbz2``: tar archive, compressed using bzip2
178 :``tgz``: tar archive, compressed using gzip
178 :``tgz``: tar archive, compressed using gzip
179 :``uzip``: zip archive, uncompressed
179 :``uzip``: zip archive, uncompressed
180 :``zip``: zip archive, compressed using deflate
180 :``zip``: zip archive, compressed using deflate
181
181
182 The exact name of the destination archive or directory is given
182 The exact name of the destination archive or directory is given
183 using a format string; see :hg:`help export` for details.
183 using a format string; see :hg:`help export` for details.
184
184
185 Each member added to an archive file has a directory prefix
185 Each member added to an archive file has a directory prefix
186 prepended. Use -p/--prefix to specify a format string for the
186 prepended. Use -p/--prefix to specify a format string for the
187 prefix. The default is the basename of the archive, with suffixes
187 prefix. The default is the basename of the archive, with suffixes
188 removed.
188 removed.
189
189
190 Returns 0 on success.
190 Returns 0 on success.
191 '''
191 '''
192
192
193 ctx = repo[opts.get('rev')]
193 ctx = repo[opts.get('rev')]
194 if not ctx:
194 if not ctx:
195 raise util.Abort(_('no working directory: please specify a revision'))
195 raise util.Abort(_('no working directory: please specify a revision'))
196 node = ctx.node()
196 node = ctx.node()
197 dest = cmdutil.make_filename(repo, dest, node)
197 dest = cmdutil.make_filename(repo, dest, node)
198 if os.path.realpath(dest) == repo.root:
198 if os.path.realpath(dest) == repo.root:
199 raise util.Abort(_('repository root cannot be destination'))
199 raise util.Abort(_('repository root cannot be destination'))
200
200
201 kind = opts.get('type') or archival.guesskind(dest) or 'files'
201 kind = opts.get('type') or archival.guesskind(dest) or 'files'
202 prefix = opts.get('prefix')
202 prefix = opts.get('prefix')
203
203
204 if dest == '-':
204 if dest == '-':
205 if kind == 'files':
205 if kind == 'files':
206 raise util.Abort(_('cannot archive plain files to stdout'))
206 raise util.Abort(_('cannot archive plain files to stdout'))
207 dest = sys.stdout
207 dest = sys.stdout
208 if not prefix:
208 if not prefix:
209 prefix = os.path.basename(repo.root) + '-%h'
209 prefix = os.path.basename(repo.root) + '-%h'
210
210
211 prefix = cmdutil.make_filename(repo, prefix, node)
211 prefix = cmdutil.make_filename(repo, prefix, node)
212 matchfn = cmdutil.match(repo, [], opts)
212 matchfn = cmdutil.match(repo, [], opts)
213 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
213 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
214 matchfn, prefix)
214 matchfn, prefix)
215
215
216 def backout(ui, repo, node=None, rev=None, **opts):
216 def backout(ui, repo, node=None, rev=None, **opts):
217 '''reverse effect of earlier changeset
217 '''reverse effect of earlier changeset
218
218
219 Commit the backed out changes as a new changeset. The new
219 Commit the backed out changes as a new changeset. The new
220 changeset is a child of the backed out changeset.
220 changeset is a child of the backed out changeset.
221
221
222 If you backout a changeset other than the tip, a new head is
222 If you backout a changeset other than the tip, a new head is
223 created. This head will be the new tip and you should merge this
223 created. This head will be the new tip and you should merge this
224 backout changeset with another head.
224 backout changeset with another head.
225
225
226 The --merge option remembers the parent of the working directory
226 The --merge option remembers the parent of the working directory
227 before starting the backout, then merges the new head with that
227 before starting the backout, then merges the new head with that
228 changeset afterwards. This saves you from doing the merge by hand.
228 changeset afterwards. This saves you from doing the merge by hand.
229 The result of this merge is not committed, as with a normal merge.
229 The result of this merge is not committed, as with a normal merge.
230
230
231 See :hg:`help dates` for a list of formats valid for -d/--date.
231 See :hg:`help dates` for a list of formats valid for -d/--date.
232
232
233 Returns 0 on success.
233 Returns 0 on success.
234 '''
234 '''
235 if rev and node:
235 if rev and node:
236 raise util.Abort(_("please specify just one revision"))
236 raise util.Abort(_("please specify just one revision"))
237
237
238 if not rev:
238 if not rev:
239 rev = node
239 rev = node
240
240
241 if not rev:
241 if not rev:
242 raise util.Abort(_("please specify a revision to backout"))
242 raise util.Abort(_("please specify a revision to backout"))
243
243
244 date = opts.get('date')
244 date = opts.get('date')
245 if date:
245 if date:
246 opts['date'] = util.parsedate(date)
246 opts['date'] = util.parsedate(date)
247
247
248 cmdutil.bail_if_changed(repo)
248 cmdutil.bail_if_changed(repo)
249 node = repo.lookup(rev)
249 node = repo.lookup(rev)
250
250
251 op1, op2 = repo.dirstate.parents()
251 op1, op2 = repo.dirstate.parents()
252 a = repo.changelog.ancestor(op1, node)
252 a = repo.changelog.ancestor(op1, node)
253 if a != node:
253 if a != node:
254 raise util.Abort(_('cannot backout change on a different branch'))
254 raise util.Abort(_('cannot backout change on a different branch'))
255
255
256 p1, p2 = repo.changelog.parents(node)
256 p1, p2 = repo.changelog.parents(node)
257 if p1 == nullid:
257 if p1 == nullid:
258 raise util.Abort(_('cannot backout a change with no parents'))
258 raise util.Abort(_('cannot backout a change with no parents'))
259 if p2 != nullid:
259 if p2 != nullid:
260 if not opts.get('parent'):
260 if not opts.get('parent'):
261 raise util.Abort(_('cannot backout a merge changeset without '
261 raise util.Abort(_('cannot backout a merge changeset without '
262 '--parent'))
262 '--parent'))
263 p = repo.lookup(opts['parent'])
263 p = repo.lookup(opts['parent'])
264 if p not in (p1, p2):
264 if p not in (p1, p2):
265 raise util.Abort(_('%s is not a parent of %s') %
265 raise util.Abort(_('%s is not a parent of %s') %
266 (short(p), short(node)))
266 (short(p), short(node)))
267 parent = p
267 parent = p
268 else:
268 else:
269 if opts.get('parent'):
269 if opts.get('parent'):
270 raise util.Abort(_('cannot use --parent on non-merge changeset'))
270 raise util.Abort(_('cannot use --parent on non-merge changeset'))
271 parent = p1
271 parent = p1
272
272
273 # the backout should appear on the same branch
273 # the backout should appear on the same branch
274 branch = repo.dirstate.branch()
274 branch = repo.dirstate.branch()
275 hg.clean(repo, node, show_stats=False)
275 hg.clean(repo, node, show_stats=False)
276 repo.dirstate.setbranch(branch)
276 repo.dirstate.setbranch(branch)
277 revert_opts = opts.copy()
277 revert_opts = opts.copy()
278 revert_opts['date'] = None
278 revert_opts['date'] = None
279 revert_opts['all'] = True
279 revert_opts['all'] = True
280 revert_opts['rev'] = hex(parent)
280 revert_opts['rev'] = hex(parent)
281 revert_opts['no_backup'] = None
281 revert_opts['no_backup'] = None
282 revert(ui, repo, **revert_opts)
282 revert(ui, repo, **revert_opts)
283 commit_opts = opts.copy()
283 commit_opts = opts.copy()
284 commit_opts['addremove'] = False
284 commit_opts['addremove'] = False
285 if not commit_opts['message'] and not commit_opts['logfile']:
285 if not commit_opts['message'] and not commit_opts['logfile']:
286 # we don't translate commit messages
286 # we don't translate commit messages
287 commit_opts['message'] = "Backed out changeset %s" % short(node)
287 commit_opts['message'] = "Backed out changeset %s" % short(node)
288 commit_opts['force_editor'] = True
288 commit_opts['force_editor'] = True
289 commit(ui, repo, **commit_opts)
289 commit(ui, repo, **commit_opts)
290 def nice(node):
290 def nice(node):
291 return '%d:%s' % (repo.changelog.rev(node), short(node))
291 return '%d:%s' % (repo.changelog.rev(node), short(node))
292 ui.status(_('changeset %s backs out changeset %s\n') %
292 ui.status(_('changeset %s backs out changeset %s\n') %
293 (nice(repo.changelog.tip()), nice(node)))
293 (nice(repo.changelog.tip()), nice(node)))
294 if op1 != node:
294 if op1 != node:
295 hg.clean(repo, op1, show_stats=False)
295 hg.clean(repo, op1, show_stats=False)
296 if opts.get('merge'):
296 if opts.get('merge'):
297 ui.status(_('merging with changeset %s\n')
297 ui.status(_('merging with changeset %s\n')
298 % nice(repo.changelog.tip()))
298 % nice(repo.changelog.tip()))
299 hg.merge(repo, hex(repo.changelog.tip()))
299 hg.merge(repo, hex(repo.changelog.tip()))
300 else:
300 else:
301 ui.status(_('the backout changeset is a new head - '
301 ui.status(_('the backout changeset is a new head - '
302 'do not forget to merge\n'))
302 'do not forget to merge\n'))
303 ui.status(_('(use "backout --merge" '
303 ui.status(_('(use "backout --merge" '
304 'if you want to auto-merge)\n'))
304 'if you want to auto-merge)\n'))
305
305
306 def bisect(ui, repo, rev=None, extra=None, command=None,
306 def bisect(ui, repo, rev=None, extra=None, command=None,
307 reset=None, good=None, bad=None, skip=None, noupdate=None):
307 reset=None, good=None, bad=None, skip=None, noupdate=None):
308 """subdivision search of changesets
308 """subdivision search of changesets
309
309
310 This command helps to find changesets which introduce problems. To
310 This command helps to find changesets which introduce problems. To
311 use, mark the earliest changeset you know exhibits the problem as
311 use, mark the earliest changeset you know exhibits the problem as
312 bad, then mark the latest changeset which is free from the problem
312 bad, then mark the latest changeset which is free from the problem
313 as good. Bisect will update your working directory to a revision
313 as good. Bisect will update your working directory to a revision
314 for testing (unless the -U/--noupdate option is specified). Once
314 for testing (unless the -U/--noupdate option is specified). Once
315 you have performed tests, mark the working directory as good or
315 you have performed tests, mark the working directory as good or
316 bad, and bisect will either update to another candidate changeset
316 bad, and bisect will either update to another candidate changeset
317 or announce that it has found the bad revision.
317 or announce that it has found the bad revision.
318
318
319 As a shortcut, you can also use the revision argument to mark a
319 As a shortcut, you can also use the revision argument to mark a
320 revision as good or bad without checking it out first.
320 revision as good or bad without checking it out first.
321
321
322 If you supply a command, it will be used for automatic bisection.
322 If you supply a command, it will be used for automatic bisection.
323 Its exit status will be used to mark revisions as good or bad:
323 Its exit status will be used to mark revisions as good or bad:
324 status 0 means good, 125 means to skip the revision, 127
324 status 0 means good, 125 means to skip the revision, 127
325 (command not found) will abort the bisection, and any other
325 (command not found) will abort the bisection, and any other
326 non-zero exit status means the revision is bad.
326 non-zero exit status means the revision is bad.
327
327
328 Returns 0 on success.
328 Returns 0 on success.
329 """
329 """
330 def print_result(nodes, good):
330 def print_result(nodes, good):
331 displayer = cmdutil.show_changeset(ui, repo, {})
331 displayer = cmdutil.show_changeset(ui, repo, {})
332 if len(nodes) == 1:
332 if len(nodes) == 1:
333 # narrowed it down to a single revision
333 # narrowed it down to a single revision
334 if good:
334 if good:
335 ui.write(_("The first good revision is:\n"))
335 ui.write(_("The first good revision is:\n"))
336 else:
336 else:
337 ui.write(_("The first bad revision is:\n"))
337 ui.write(_("The first bad revision is:\n"))
338 displayer.show(repo[nodes[0]])
338 displayer.show(repo[nodes[0]])
339 parents = repo[nodes[0]].parents()
339 parents = repo[nodes[0]].parents()
340 if len(parents) > 1:
340 if len(parents) > 1:
341 side = good and state['bad'] or state['good']
341 side = good and state['bad'] or state['good']
342 num = len(set(i.node() for i in parents) & set(side))
342 num = len(set(i.node() for i in parents) & set(side))
343 if num == 1:
343 if num == 1:
344 common = parents[0].ancestor(parents[1])
344 common = parents[0].ancestor(parents[1])
345 ui.write(_('Not all ancestors of this changeset have been'
345 ui.write(_('Not all ancestors of this changeset have been'
346 ' checked.\nTo check the other ancestors, start'
346 ' checked.\nTo check the other ancestors, start'
347 ' from the common ancestor, %s.\n' % common))
347 ' from the common ancestor, %s.\n' % common))
348 else:
348 else:
349 # multiple possible revisions
349 # multiple possible revisions
350 if good:
350 if good:
351 ui.write(_("Due to skipped revisions, the first "
351 ui.write(_("Due to skipped revisions, the first "
352 "good revision could be any of:\n"))
352 "good revision could be any of:\n"))
353 else:
353 else:
354 ui.write(_("Due to skipped revisions, the first "
354 ui.write(_("Due to skipped revisions, the first "
355 "bad revision could be any of:\n"))
355 "bad revision could be any of:\n"))
356 for n in nodes:
356 for n in nodes:
357 displayer.show(repo[n])
357 displayer.show(repo[n])
358 displayer.close()
358 displayer.close()
359
359
360 def check_state(state, interactive=True):
360 def check_state(state, interactive=True):
361 if not state['good'] or not state['bad']:
361 if not state['good'] or not state['bad']:
362 if (good or bad or skip or reset) and interactive:
362 if (good or bad or skip or reset) and interactive:
363 return
363 return
364 if not state['good']:
364 if not state['good']:
365 raise util.Abort(_('cannot bisect (no known good revisions)'))
365 raise util.Abort(_('cannot bisect (no known good revisions)'))
366 else:
366 else:
367 raise util.Abort(_('cannot bisect (no known bad revisions)'))
367 raise util.Abort(_('cannot bisect (no known bad revisions)'))
368 return True
368 return True
369
369
370 # backward compatibility
370 # backward compatibility
371 if rev in "good bad reset init".split():
371 if rev in "good bad reset init".split():
372 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
372 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
373 cmd, rev, extra = rev, extra, None
373 cmd, rev, extra = rev, extra, None
374 if cmd == "good":
374 if cmd == "good":
375 good = True
375 good = True
376 elif cmd == "bad":
376 elif cmd == "bad":
377 bad = True
377 bad = True
378 else:
378 else:
379 reset = True
379 reset = True
380 elif extra or good + bad + skip + reset + bool(command) > 1:
380 elif extra or good + bad + skip + reset + bool(command) > 1:
381 raise util.Abort(_('incompatible arguments'))
381 raise util.Abort(_('incompatible arguments'))
382
382
383 if reset:
383 if reset:
384 p = repo.join("bisect.state")
384 p = repo.join("bisect.state")
385 if os.path.exists(p):
385 if os.path.exists(p):
386 os.unlink(p)
386 os.unlink(p)
387 return
387 return
388
388
389 state = hbisect.load_state(repo)
389 state = hbisect.load_state(repo)
390
390
391 if command:
391 if command:
392 changesets = 1
392 changesets = 1
393 try:
393 try:
394 while changesets:
394 while changesets:
395 # update state
395 # update state
396 status = util.system(command)
396 status = util.system(command)
397 if status == 125:
397 if status == 125:
398 transition = "skip"
398 transition = "skip"
399 elif status == 0:
399 elif status == 0:
400 transition = "good"
400 transition = "good"
401 # status < 0 means process was killed
401 # status < 0 means process was killed
402 elif status == 127:
402 elif status == 127:
403 raise util.Abort(_("failed to execute %s") % command)
403 raise util.Abort(_("failed to execute %s") % command)
404 elif status < 0:
404 elif status < 0:
405 raise util.Abort(_("%s killed") % command)
405 raise util.Abort(_("%s killed") % command)
406 else:
406 else:
407 transition = "bad"
407 transition = "bad"
408 ctx = repo[rev or '.']
408 ctx = repo[rev or '.']
409 state[transition].append(ctx.node())
409 state[transition].append(ctx.node())
410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
411 check_state(state, interactive=False)
411 check_state(state, interactive=False)
412 # bisect
412 # bisect
413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
414 # update to next check
414 # update to next check
415 cmdutil.bail_if_changed(repo)
415 cmdutil.bail_if_changed(repo)
416 hg.clean(repo, nodes[0], show_stats=False)
416 hg.clean(repo, nodes[0], show_stats=False)
417 finally:
417 finally:
418 hbisect.save_state(repo, state)
418 hbisect.save_state(repo, state)
419 print_result(nodes, good)
419 print_result(nodes, good)
420 return
420 return
421
421
422 # update state
422 # update state
423
423
424 if rev:
424 if rev:
425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
426 else:
426 else:
427 nodes = [repo.lookup('.')]
427 nodes = [repo.lookup('.')]
428
428
429 if good or bad or skip:
429 if good or bad or skip:
430 if good:
430 if good:
431 state['good'] += nodes
431 state['good'] += nodes
432 elif bad:
432 elif bad:
433 state['bad'] += nodes
433 state['bad'] += nodes
434 elif skip:
434 elif skip:
435 state['skip'] += nodes
435 state['skip'] += nodes
436 hbisect.save_state(repo, state)
436 hbisect.save_state(repo, state)
437
437
438 if not check_state(state):
438 if not check_state(state):
439 return
439 return
440
440
441 # actually bisect
441 # actually bisect
442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
443 if changesets == 0:
443 if changesets == 0:
444 print_result(nodes, good)
444 print_result(nodes, good)
445 else:
445 else:
446 assert len(nodes) == 1 # only a single node can be tested next
446 assert len(nodes) == 1 # only a single node can be tested next
447 node = nodes[0]
447 node = nodes[0]
448 # compute the approximate number of remaining tests
448 # compute the approximate number of remaining tests
449 tests, size = 0, 2
449 tests, size = 0, 2
450 while size <= changesets:
450 while size <= changesets:
451 tests, size = tests + 1, size * 2
451 tests, size = tests + 1, size * 2
452 rev = repo.changelog.rev(node)
452 rev = repo.changelog.rev(node)
453 ui.write(_("Testing changeset %d:%s "
453 ui.write(_("Testing changeset %d:%s "
454 "(%d changesets remaining, ~%d tests)\n")
454 "(%d changesets remaining, ~%d tests)\n")
455 % (rev, short(node), changesets, tests))
455 % (rev, short(node), changesets, tests))
456 if not noupdate:
456 if not noupdate:
457 cmdutil.bail_if_changed(repo)
457 cmdutil.bail_if_changed(repo)
458 return hg.clean(repo, node)
458 return hg.clean(repo, node)
459
459
460 def branch(ui, repo, label=None, **opts):
460 def branch(ui, repo, label=None, **opts):
461 """set or show the current branch name
461 """set or show the current branch name
462
462
463 With no argument, show the current branch name. With one argument,
463 With no argument, show the current branch name. With one argument,
464 set the working directory branch name (the branch will not exist
464 set the working directory branch name (the branch will not exist
465 in the repository until the next commit). Standard practice
465 in the repository until the next commit). Standard practice
466 recommends that primary development take place on the 'default'
466 recommends that primary development take place on the 'default'
467 branch.
467 branch.
468
468
469 Unless -f/--force is specified, branch will not let you set a
469 Unless -f/--force is specified, branch will not let you set a
470 branch name that already exists, even if it's inactive.
470 branch name that already exists, even if it's inactive.
471
471
472 Use -C/--clean to reset the working directory branch to that of
472 Use -C/--clean to reset the working directory branch to that of
473 the parent of the working directory, negating a previous branch
473 the parent of the working directory, negating a previous branch
474 change.
474 change.
475
475
476 Use the command :hg:`update` to switch to an existing branch. Use
476 Use the command :hg:`update` to switch to an existing branch. Use
477 :hg:`commit --close-branch` to mark this branch as closed.
477 :hg:`commit --close-branch` to mark this branch as closed.
478
478
479 Returns 0 on success.
479 Returns 0 on success.
480 """
480 """
481
481
482 if opts.get('clean'):
482 if opts.get('clean'):
483 label = repo[None].parents()[0].branch()
483 label = repo[None].parents()[0].branch()
484 repo.dirstate.setbranch(label)
484 repo.dirstate.setbranch(label)
485 ui.status(_('reset working directory to branch %s\n') % label)
485 ui.status(_('reset working directory to branch %s\n') % label)
486 elif label:
486 elif label:
487 utflabel = encoding.fromlocal(label)
487 utflabel = encoding.fromlocal(label)
488 if not opts.get('force') and utflabel in repo.branchtags():
488 if not opts.get('force') and utflabel in repo.branchtags():
489 if label not in [p.branch() for p in repo.parents()]:
489 if label not in [p.branch() for p in repo.parents()]:
490 raise util.Abort(_('a branch of the same name already exists'
490 raise util.Abort(_('a branch of the same name already exists'
491 " (use 'hg update' to switch to it)"))
491 " (use 'hg update' to switch to it)"))
492 repo.dirstate.setbranch(utflabel)
492 repo.dirstate.setbranch(utflabel)
493 ui.status(_('marked working directory as branch %s\n') % label)
493 ui.status(_('marked working directory as branch %s\n') % label)
494 else:
494 else:
495 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
495 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
496
496
497 def branches(ui, repo, active=False, closed=False):
497 def branches(ui, repo, active=False, closed=False):
498 """list repository named branches
498 """list repository named branches
499
499
500 List the repository's named branches, indicating which ones are
500 List the repository's named branches, indicating which ones are
501 inactive. If -c/--closed is specified, also list branches which have
501 inactive. If -c/--closed is specified, also list branches which have
502 been marked closed (see :hg:`commit --close-branch`).
502 been marked closed (see :hg:`commit --close-branch`).
503
503
504 If -a/--active is specified, only show active branches. A branch
504 If -a/--active is specified, only show active branches. A branch
505 is considered active if it contains repository heads.
505 is considered active if it contains repository heads.
506
506
507 Use the command :hg:`update` to switch to an existing branch.
507 Use the command :hg:`update` to switch to an existing branch.
508
508
509 Returns 0.
509 Returns 0.
510 """
510 """
511
511
512 hexfunc = ui.debugflag and hex or short
512 hexfunc = ui.debugflag and hex or short
513 activebranches = [repo[n].branch() for n in repo.heads()]
513 activebranches = [repo[n].branch() for n in repo.heads()]
514 def testactive(tag, node):
514 def testactive(tag, node):
515 realhead = tag in activebranches
515 realhead = tag in activebranches
516 open = node in repo.branchheads(tag, closed=False)
516 open = node in repo.branchheads(tag, closed=False)
517 return realhead and open
517 return realhead and open
518 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
518 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
519 for tag, node in repo.branchtags().items()],
519 for tag, node in repo.branchtags().items()],
520 reverse=True)
520 reverse=True)
521
521
522 for isactive, node, tag in branches:
522 for isactive, node, tag in branches:
523 if (not active) or isactive:
523 if (not active) or isactive:
524 encodedtag = encoding.tolocal(tag)
524 encodedtag = encoding.tolocal(tag)
525 if ui.quiet:
525 if ui.quiet:
526 ui.write("%s\n" % encodedtag)
526 ui.write("%s\n" % encodedtag)
527 else:
527 else:
528 hn = repo.lookup(node)
528 hn = repo.lookup(node)
529 if isactive:
529 if isactive:
530 label = 'branches.active'
530 label = 'branches.active'
531 notice = ''
531 notice = ''
532 elif hn not in repo.branchheads(tag, closed=False):
532 elif hn not in repo.branchheads(tag, closed=False):
533 if not closed:
533 if not closed:
534 continue
534 continue
535 label = 'branches.closed'
535 label = 'branches.closed'
536 notice = _(' (closed)')
536 notice = _(' (closed)')
537 else:
537 else:
538 label = 'branches.inactive'
538 label = 'branches.inactive'
539 notice = _(' (inactive)')
539 notice = _(' (inactive)')
540 if tag == repo.dirstate.branch():
540 if tag == repo.dirstate.branch():
541 label = 'branches.current'
541 label = 'branches.current'
542 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
542 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
543 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
543 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
544 encodedtag = ui.label(encodedtag, label)
544 encodedtag = ui.label(encodedtag, label)
545 ui.write("%s %s%s\n" % (encodedtag, rev, notice))
545 ui.write("%s %s%s\n" % (encodedtag, rev, notice))
546
546
547 def bundle(ui, repo, fname, dest=None, **opts):
547 def bundle(ui, repo, fname, dest=None, **opts):
548 """create a changegroup file
548 """create a changegroup file
549
549
550 Generate a compressed changegroup file collecting changesets not
550 Generate a compressed changegroup file collecting changesets not
551 known to be in another repository.
551 known to be in another repository.
552
552
553 If you omit the destination repository, then hg assumes the
553 If you omit the destination repository, then hg assumes the
554 destination will have all the nodes you specify with --base
554 destination will have all the nodes you specify with --base
555 parameters. To create a bundle containing all changesets, use
555 parameters. To create a bundle containing all changesets, use
556 -a/--all (or --base null).
556 -a/--all (or --base null).
557
557
558 You can change compression method with the -t/--type option.
558 You can change compression method with the -t/--type option.
559 The available compression methods are: none, bzip2, and
559 The available compression methods are: none, bzip2, and
560 gzip (by default, bundles are compressed using bzip2).
560 gzip (by default, bundles are compressed using bzip2).
561
561
562 The bundle file can then be transferred using conventional means
562 The bundle file can then be transferred using conventional means
563 and applied to another repository with the unbundle or pull
563 and applied to another repository with the unbundle or pull
564 command. This is useful when direct push and pull are not
564 command. This is useful when direct push and pull are not
565 available or when exporting an entire repository is undesirable.
565 available or when exporting an entire repository is undesirable.
566
566
567 Applying bundles preserves all changeset contents including
567 Applying bundles preserves all changeset contents including
568 permissions, copy/rename information, and revision history.
568 permissions, copy/rename information, and revision history.
569
569
570 Returns 0 on success, 1 if no changes found.
570 Returns 0 on success, 1 if no changes found.
571 """
571 """
572 revs = opts.get('rev') or None
572 revs = opts.get('rev') or None
573 if opts.get('all'):
573 if opts.get('all'):
574 base = ['null']
574 base = ['null']
575 else:
575 else:
576 base = opts.get('base')
576 base = opts.get('base')
577 if base:
577 if base:
578 if dest:
578 if dest:
579 raise util.Abort(_("--base is incompatible with specifying "
579 raise util.Abort(_("--base is incompatible with specifying "
580 "a destination"))
580 "a destination"))
581 base = [repo.lookup(rev) for rev in base]
581 base = [repo.lookup(rev) for rev in base]
582 # create the right base
582 # create the right base
583 # XXX: nodesbetween / changegroup* should be "fixed" instead
583 # XXX: nodesbetween / changegroup* should be "fixed" instead
584 o = []
584 o = []
585 has = set((nullid,))
585 has = set((nullid,))
586 for n in base:
586 for n in base:
587 has.update(repo.changelog.reachable(n))
587 has.update(repo.changelog.reachable(n))
588 if revs:
588 if revs:
589 revs = [repo.lookup(rev) for rev in revs]
589 revs = [repo.lookup(rev) for rev in revs]
590 visit = revs[:]
590 visit = revs[:]
591 has.difference_update(visit)
591 has.difference_update(visit)
592 else:
592 else:
593 visit = repo.changelog.heads()
593 visit = repo.changelog.heads()
594 seen = {}
594 seen = {}
595 while visit:
595 while visit:
596 n = visit.pop(0)
596 n = visit.pop(0)
597 parents = [p for p in repo.changelog.parents(n) if p not in has]
597 parents = [p for p in repo.changelog.parents(n) if p not in has]
598 if len(parents) == 0:
598 if len(parents) == 0:
599 if n not in has:
599 if n not in has:
600 o.append(n)
600 o.append(n)
601 else:
601 else:
602 for p in parents:
602 for p in parents:
603 if p not in seen:
603 if p not in seen:
604 seen[p] = 1
604 seen[p] = 1
605 visit.append(p)
605 visit.append(p)
606 else:
606 else:
607 dest = ui.expandpath(dest or 'default-push', dest or 'default')
607 dest = ui.expandpath(dest or 'default-push', dest or 'default')
608 dest, branches = hg.parseurl(dest, opts.get('branch'))
608 dest, branches = hg.parseurl(dest, opts.get('branch'))
609 other = hg.repository(hg.remoteui(repo, opts), dest)
609 other = hg.repository(hg.remoteui(repo, opts), dest)
610 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
610 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
611 if revs:
611 if revs:
612 revs = [repo.lookup(rev) for rev in revs]
612 revs = [repo.lookup(rev) for rev in revs]
613 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
613 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
614
614
615 if not o:
615 if not o:
616 ui.status(_("no changes found\n"))
616 ui.status(_("no changes found\n"))
617 return 1
617 return 1
618
618
619 if revs:
619 if revs:
620 cg = repo.changegroupsubset(o, revs, 'bundle')
620 cg = repo.changegroupsubset(o, revs, 'bundle')
621 else:
621 else:
622 cg = repo.changegroup(o, 'bundle')
622 cg = repo.changegroup(o, 'bundle')
623
623
624 bundletype = opts.get('type', 'bzip2').lower()
624 bundletype = opts.get('type', 'bzip2').lower()
625 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
625 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
626 bundletype = btypes.get(bundletype)
626 bundletype = btypes.get(bundletype)
627 if bundletype not in changegroup.bundletypes:
627 if bundletype not in changegroup.bundletypes:
628 raise util.Abort(_('unknown bundle type specified with --type'))
628 raise util.Abort(_('unknown bundle type specified with --type'))
629
629
630 changegroup.writebundle(cg, fname, bundletype)
630 changegroup.writebundle(cg, fname, bundletype)
631
631
632 def cat(ui, repo, file1, *pats, **opts):
632 def cat(ui, repo, file1, *pats, **opts):
633 """output the current or given revision of files
633 """output the current or given revision of files
634
634
635 Print the specified files as they were at the given revision. If
635 Print the specified files as they were at the given revision. If
636 no revision is given, the parent of the working directory is used,
636 no revision is given, the parent of the working directory is used,
637 or tip if no revision is checked out.
637 or tip if no revision is checked out.
638
638
639 Output may be to a file, in which case the name of the file is
639 Output may be to a file, in which case the name of the file is
640 given using a format string. The formatting rules are the same as
640 given using a format string. The formatting rules are the same as
641 for the export command, with the following additions:
641 for the export command, with the following additions:
642
642
643 :``%s``: basename of file being printed
643 :``%s``: basename of file being printed
644 :``%d``: dirname of file being printed, or '.' if in repository root
644 :``%d``: dirname of file being printed, or '.' if in repository root
645 :``%p``: root-relative path name of file being printed
645 :``%p``: root-relative path name of file being printed
646
646
647 Returns 0 on success.
647 Returns 0 on success.
648 """
648 """
649 ctx = repo[opts.get('rev')]
649 ctx = repo[opts.get('rev')]
650 err = 1
650 err = 1
651 m = cmdutil.match(repo, (file1,) + pats, opts)
651 m = cmdutil.match(repo, (file1,) + pats, opts)
652 for abs in ctx.walk(m):
652 for abs in ctx.walk(m):
653 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
653 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
654 data = ctx[abs].data()
654 data = ctx[abs].data()
655 if opts.get('decode'):
655 if opts.get('decode'):
656 data = repo.wwritedata(abs, data)
656 data = repo.wwritedata(abs, data)
657 fp.write(data)
657 fp.write(data)
658 err = 0
658 err = 0
659 return err
659 return err
660
660
661 def clone(ui, source, dest=None, **opts):
661 def clone(ui, source, dest=None, **opts):
662 """make a copy of an existing repository
662 """make a copy of an existing repository
663
663
664 Create a copy of an existing repository in a new directory.
664 Create a copy of an existing repository in a new directory.
665
665
666 If no destination directory name is specified, it defaults to the
666 If no destination directory name is specified, it defaults to the
667 basename of the source.
667 basename of the source.
668
668
669 The location of the source is added to the new repository's
669 The location of the source is added to the new repository's
670 .hg/hgrc file, as the default to be used for future pulls.
670 .hg/hgrc file, as the default to be used for future pulls.
671
671
672 See :hg:`help urls` for valid source format details.
672 See :hg:`help urls` for valid source format details.
673
673
674 It is possible to specify an ``ssh://`` URL as the destination, but no
674 It is possible to specify an ``ssh://`` URL as the destination, but no
675 .hg/hgrc and working directory will be created on the remote side.
675 .hg/hgrc and working directory will be created on the remote side.
676 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
676 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
677
677
678 A set of changesets (tags, or branch names) to pull may be specified
678 A set of changesets (tags, or branch names) to pull may be specified
679 by listing each changeset (tag, or branch name) with -r/--rev.
679 by listing each changeset (tag, or branch name) with -r/--rev.
680 If -r/--rev is used, the cloned repository will contain only a subset
680 If -r/--rev is used, the cloned repository will contain only a subset
681 of the changesets of the source repository. Only the set of changesets
681 of the changesets of the source repository. Only the set of changesets
682 defined by all -r/--rev options (including all their ancestors)
682 defined by all -r/--rev options (including all their ancestors)
683 will be pulled into the destination repository.
683 will be pulled into the destination repository.
684 No subsequent changesets (including subsequent tags) will be present
684 No subsequent changesets (including subsequent tags) will be present
685 in the destination.
685 in the destination.
686
686
687 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
687 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
688 local source repositories.
688 local source repositories.
689
689
690 For efficiency, hardlinks are used for cloning whenever the source
690 For efficiency, hardlinks are used for cloning whenever the source
691 and destination are on the same filesystem (note this applies only
691 and destination are on the same filesystem (note this applies only
692 to the repository data, not to the working directory). Some
692 to the repository data, not to the working directory). Some
693 filesystems, such as AFS, implement hardlinking incorrectly, but
693 filesystems, such as AFS, implement hardlinking incorrectly, but
694 do not report errors. In these cases, use the --pull option to
694 do not report errors. In these cases, use the --pull option to
695 avoid hardlinking.
695 avoid hardlinking.
696
696
697 In some cases, you can clone repositories and the working directory
697 In some cases, you can clone repositories and the working directory
698 using full hardlinks with ::
698 using full hardlinks with ::
699
699
700 $ cp -al REPO REPOCLONE
700 $ cp -al REPO REPOCLONE
701
701
702 This is the fastest way to clone, but it is not always safe. The
702 This is the fastest way to clone, but it is not always safe. The
703 operation is not atomic (making sure REPO is not modified during
703 operation is not atomic (making sure REPO is not modified during
704 the operation is up to you) and you have to make sure your editor
704 the operation is up to you) and you have to make sure your editor
705 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
705 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
706 this is not compatible with certain extensions that place their
706 this is not compatible with certain extensions that place their
707 metadata under the .hg directory, such as mq.
707 metadata under the .hg directory, such as mq.
708
708
709 Mercurial will update the working directory to the first applicable
709 Mercurial will update the working directory to the first applicable
710 revision from this list:
710 revision from this list:
711
711
712 a) null if -U or the source repository has no changesets
712 a) null if -U or the source repository has no changesets
713 b) if -u . and the source repository is local, the first parent of
713 b) if -u . and the source repository is local, the first parent of
714 the source repository's working directory
714 the source repository's working directory
715 c) the changeset specified with -u (if a branch name, this means the
715 c) the changeset specified with -u (if a branch name, this means the
716 latest head of that branch)
716 latest head of that branch)
717 d) the changeset specified with -r
717 d) the changeset specified with -r
718 e) the tipmost head specified with -b
718 e) the tipmost head specified with -b
719 f) the tipmost head specified with the url#branch source syntax
719 f) the tipmost head specified with the url#branch source syntax
720 g) the tipmost head of the default branch
720 g) the tipmost head of the default branch
721 h) tip
721 h) tip
722
722
723 Returns 0 on success.
723 Returns 0 on success.
724 """
724 """
725 if opts.get('noupdate') and opts.get('updaterev'):
725 if opts.get('noupdate') and opts.get('updaterev'):
726 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
726 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
727
727
728 r = hg.clone(hg.remoteui(ui, opts), source, dest,
728 r = hg.clone(hg.remoteui(ui, opts), source, dest,
729 pull=opts.get('pull'),
729 pull=opts.get('pull'),
730 stream=opts.get('uncompressed'),
730 stream=opts.get('uncompressed'),
731 rev=opts.get('rev'),
731 rev=opts.get('rev'),
732 update=opts.get('updaterev') or not opts.get('noupdate'),
732 update=opts.get('updaterev') or not opts.get('noupdate'),
733 branch=opts.get('branch'))
733 branch=opts.get('branch'))
734
734
735 return r is None
735 return r is None
736
736
737 def commit(ui, repo, *pats, **opts):
737 def commit(ui, repo, *pats, **opts):
738 """commit the specified files or all outstanding changes
738 """commit the specified files or all outstanding changes
739
739
740 Commit changes to the given files into the repository. Unlike a
740 Commit changes to the given files into the repository. Unlike a
741 centralized RCS, this operation is a local operation. See
741 centralized RCS, this operation is a local operation. See
742 :hg:`push` for a way to actively distribute your changes.
742 :hg:`push` for a way to actively distribute your changes.
743
743
744 If a list of files is omitted, all changes reported by :hg:`status`
744 If a list of files is omitted, all changes reported by :hg:`status`
745 will be committed.
745 will be committed.
746
746
747 If you are committing the result of a merge, do not provide any
747 If you are committing the result of a merge, do not provide any
748 filenames or -I/-X filters.
748 filenames or -I/-X filters.
749
749
750 If no commit message is specified, Mercurial starts your
750 If no commit message is specified, Mercurial starts your
751 configured editor where you can enter a message. In case your
751 configured editor where you can enter a message. In case your
752 commit fails, you will find a backup of your message in
752 commit fails, you will find a backup of your message in
753 ``.hg/last-message.txt``.
753 ``.hg/last-message.txt``.
754
754
755 See :hg:`help dates` for a list of formats valid for -d/--date.
755 See :hg:`help dates` for a list of formats valid for -d/--date.
756
756
757 Returns 0 on success, 1 if nothing changed.
757 Returns 0 on success, 1 if nothing changed.
758 """
758 """
759 extra = {}
759 extra = {}
760 if opts.get('close_branch'):
760 if opts.get('close_branch'):
761 if repo['.'].node() not in repo.branchheads():
761 if repo['.'].node() not in repo.branchheads():
762 # The topo heads set is included in the branch heads set of the
762 # The topo heads set is included in the branch heads set of the
763 # current branch, so it's sufficient to test branchheads
763 # current branch, so it's sufficient to test branchheads
764 raise util.Abort(_('can only close branch heads'))
764 raise util.Abort(_('can only close branch heads'))
765 extra['close'] = 1
765 extra['close'] = 1
766 e = cmdutil.commiteditor
766 e = cmdutil.commiteditor
767 if opts.get('force_editor'):
767 if opts.get('force_editor'):
768 e = cmdutil.commitforceeditor
768 e = cmdutil.commitforceeditor
769
769
770 def commitfunc(ui, repo, message, match, opts):
770 def commitfunc(ui, repo, message, match, opts):
771 return repo.commit(message, opts.get('user'), opts.get('date'), match,
771 return repo.commit(message, opts.get('user'), opts.get('date'), match,
772 editor=e, extra=extra)
772 editor=e, extra=extra)
773
773
774 branch = repo[None].branch()
774 branch = repo[None].branch()
775 bheads = repo.branchheads(branch)
775 bheads = repo.branchheads(branch)
776
776
777 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
777 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
778 if not node:
778 if not node:
779 ui.status(_("nothing changed\n"))
779 ui.status(_("nothing changed\n"))
780 return 1
780 return 1
781
781
782 ctx = repo[node]
782 ctx = repo[node]
783 parents = ctx.parents()
783 parents = ctx.parents()
784
784
785 if bheads and not [x for x in parents
785 if bheads and not [x for x in parents
786 if x.node() in bheads and x.branch() == branch]:
786 if x.node() in bheads and x.branch() == branch]:
787 ui.status(_('created new head\n'))
787 ui.status(_('created new head\n'))
788 # The message is not printed for initial roots. For the other
788 # The message is not printed for initial roots. For the other
789 # changesets, it is printed in the following situations:
789 # changesets, it is printed in the following situations:
790 #
790 #
791 # Par column: for the 2 parents with ...
791 # Par column: for the 2 parents with ...
792 # N: null or no parent
792 # N: null or no parent
793 # B: parent is on another named branch
793 # B: parent is on another named branch
794 # C: parent is a regular non head changeset
794 # C: parent is a regular non head changeset
795 # H: parent was a branch head of the current branch
795 # H: parent was a branch head of the current branch
796 # Msg column: whether we print "created new head" message
796 # Msg column: whether we print "created new head" message
797 # In the following, it is assumed that there already exists some
797 # In the following, it is assumed that there already exists some
798 # initial branch heads of the current branch, otherwise nothing is
798 # initial branch heads of the current branch, otherwise nothing is
799 # printed anyway.
799 # printed anyway.
800 #
800 #
801 # Par Msg Comment
801 # Par Msg Comment
802 # NN y additional topo root
802 # NN y additional topo root
803 #
803 #
804 # BN y additional branch root
804 # BN y additional branch root
805 # CN y additional topo head
805 # CN y additional topo head
806 # HN n usual case
806 # HN n usual case
807 #
807 #
808 # BB y weird additional branch root
808 # BB y weird additional branch root
809 # CB y branch merge
809 # CB y branch merge
810 # HB n merge with named branch
810 # HB n merge with named branch
811 #
811 #
812 # CC y additional head from merge
812 # CC y additional head from merge
813 # CH n merge with a head
813 # CH n merge with a head
814 #
814 #
815 # HH n head merge: head count decreases
815 # HH n head merge: head count decreases
816
816
817 if not opts.get('close_branch'):
817 if not opts.get('close_branch'):
818 for r in parents:
818 for r in parents:
819 if r.extra().get('close') and r.branch() == branch:
819 if r.extra().get('close') and r.branch() == branch:
820 ui.status(_('reopening closed branch head %d\n') % r)
820 ui.status(_('reopening closed branch head %d\n') % r)
821
821
822 if ui.debugflag:
822 if ui.debugflag:
823 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
823 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
824 elif ui.verbose:
824 elif ui.verbose:
825 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
825 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
826
826
827 def copy(ui, repo, *pats, **opts):
827 def copy(ui, repo, *pats, **opts):
828 """mark files as copied for the next commit
828 """mark files as copied for the next commit
829
829
830 Mark dest as having copies of source files. If dest is a
830 Mark dest as having copies of source files. If dest is a
831 directory, copies are put in that directory. If dest is a file,
831 directory, copies are put in that directory. If dest is a file,
832 the source must be a single file.
832 the source must be a single file.
833
833
834 By default, this command copies the contents of files as they
834 By default, this command copies the contents of files as they
835 exist in the working directory. If invoked with -A/--after, the
835 exist in the working directory. If invoked with -A/--after, the
836 operation is recorded, but no copying is performed.
836 operation is recorded, but no copying is performed.
837
837
838 This command takes effect with the next commit. To undo a copy
838 This command takes effect with the next commit. To undo a copy
839 before that, see :hg:`revert`.
839 before that, see :hg:`revert`.
840
840
841 Returns 0 on success, 1 if errors are encountered.
841 Returns 0 on success, 1 if errors are encountered.
842 """
842 """
843 wlock = repo.wlock(False)
843 wlock = repo.wlock(False)
844 try:
844 try:
845 return cmdutil.copy(ui, repo, pats, opts)
845 return cmdutil.copy(ui, repo, pats, opts)
846 finally:
846 finally:
847 wlock.release()
847 wlock.release()
848
848
849 def debugancestor(ui, repo, *args):
849 def debugancestor(ui, repo, *args):
850 """find the ancestor revision of two revisions in a given index"""
850 """find the ancestor revision of two revisions in a given index"""
851 if len(args) == 3:
851 if len(args) == 3:
852 index, rev1, rev2 = args
852 index, rev1, rev2 = args
853 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
853 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
854 lookup = r.lookup
854 lookup = r.lookup
855 elif len(args) == 2:
855 elif len(args) == 2:
856 if not repo:
856 if not repo:
857 raise util.Abort(_("there is no Mercurial repository here "
857 raise util.Abort(_("there is no Mercurial repository here "
858 "(.hg not found)"))
858 "(.hg not found)"))
859 rev1, rev2 = args
859 rev1, rev2 = args
860 r = repo.changelog
860 r = repo.changelog
861 lookup = repo.lookup
861 lookup = repo.lookup
862 else:
862 else:
863 raise util.Abort(_('either two or three arguments required'))
863 raise util.Abort(_('either two or three arguments required'))
864 a = r.ancestor(lookup(rev1), lookup(rev2))
864 a = r.ancestor(lookup(rev1), lookup(rev2))
865 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
865 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
866
866
867 def debugbuilddag(ui, repo, text,
867 def debugbuilddag(ui, repo, text,
868 mergeable_file=False,
868 mergeable_file=False,
869 appended_file=False,
869 appended_file=False,
870 overwritten_file=False,
870 overwritten_file=False,
871 new_file=False):
871 new_file=False):
872 """builds a repo with a given dag from scratch in the current empty repo
872 """builds a repo with a given dag from scratch in the current empty repo
873
873
874 Elements:
874 Elements:
875
875
876 - "+n" is a linear run of n nodes based on the current default parent
876 - "+n" is a linear run of n nodes based on the current default parent
877 - "." is a single node based on the current default parent
877 - "." is a single node based on the current default parent
878 - "$" resets the default parent to null (implied at the start);
878 - "$" resets the default parent to null (implied at the start);
879 otherwise the default parent is always the last node created
879 otherwise the default parent is always the last node created
880 - "<p" sets the default parent to the backref p
880 - "<p" sets the default parent to the backref p
881 - "*p" is a fork at parent p, which is a backref
881 - "*p" is a fork at parent p, which is a backref
882 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
882 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
883 - "/p2" is a merge of the preceding node and p2
883 - "/p2" is a merge of the preceding node and p2
884 - ":tag" defines a local tag for the preceding node
884 - ":tag" defines a local tag for the preceding node
885 - "@branch" sets the named branch for subsequent nodes
885 - "@branch" sets the named branch for subsequent nodes
886 - "!command" runs the command using your shell
886 - "!command" runs the command using your shell
887 - "!!my command\\n" is like "!", but to the end of the line
887 - "!!my command\\n" is like "!", but to the end of the line
888 - "#...\\n" is a comment up to the end of the line
888 - "#...\\n" is a comment up to the end of the line
889
889
890 Whitespace between the above elements is ignored.
890 Whitespace between the above elements is ignored.
891
891
892 A backref is either
892 A backref is either
893
893
894 - a number n, which references the node curr-n, where curr is the current
894 - a number n, which references the node curr-n, where curr is the current
895 node, or
895 node, or
896 - the name of a local tag you placed earlier using ":tag", or
896 - the name of a local tag you placed earlier using ":tag", or
897 - empty to denote the default parent.
897 - empty to denote the default parent.
898
898
899 All string valued-elements are either strictly alphanumeric, or must
899 All string valued-elements are either strictly alphanumeric, or must
900 be enclosed in double quotes ("..."), with "\\" as escape character.
900 be enclosed in double quotes ("..."), with "\\" as escape character.
901
901
902 Note that the --overwritten-file and --appended-file options imply the
902 Note that the --overwritten-file and --appended-file options imply the
903 use of "HGMERGE=internal:local" during DAG buildup.
903 use of "HGMERGE=internal:local" during DAG buildup.
904 """
904 """
905
905
906 if not (mergeable_file or appended_file or overwritten_file or new_file):
906 if not (mergeable_file or appended_file or overwritten_file or new_file):
907 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
907 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
908
908
909 if len(repo.changelog) > 0:
909 if len(repo.changelog) > 0:
910 raise util.Abort(_('repository is not empty'))
910 raise util.Abort(_('repository is not empty'))
911
911
912 if overwritten_file or appended_file:
912 if overwritten_file or appended_file:
913 # we don't want to fail in merges during buildup
913 # we don't want to fail in merges during buildup
914 os.environ['HGMERGE'] = 'internal:local'
914 os.environ['HGMERGE'] = 'internal:local'
915
915
916 def writefile(fname, text, fmode="wb"):
916 def writefile(fname, text, fmode="wb"):
917 f = open(fname, fmode)
917 f = open(fname, fmode)
918 try:
918 try:
919 f.write(text)
919 f.write(text)
920 finally:
920 finally:
921 f.close()
921 f.close()
922
922
923 if mergeable_file:
923 if mergeable_file:
924 linesperrev = 2
924 linesperrev = 2
925 # determine number of revs in DAG
925 # determine number of revs in DAG
926 n = 0
926 n = 0
927 for type, data in dagparser.parsedag(text):
927 for type, data in dagparser.parsedag(text):
928 if type == 'n':
928 if type == 'n':
929 n += 1
929 n += 1
930 # make a file with k lines per rev
930 # make a file with k lines per rev
931 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
931 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
932 + "\n")
932 + "\n")
933
933
934 at = -1
934 at = -1
935 atbranch = 'default'
935 atbranch = 'default'
936 for type, data in dagparser.parsedag(text):
936 for type, data in dagparser.parsedag(text):
937 if type == 'n':
937 if type == 'n':
938 ui.status('node %s\n' % str(data))
938 ui.status('node %s\n' % str(data))
939 id, ps = data
939 id, ps = data
940 p1 = ps[0]
940 p1 = ps[0]
941 if p1 != at:
941 if p1 != at:
942 update(ui, repo, node=p1, clean=True)
942 update(ui, repo, node=p1, clean=True)
943 at = p1
943 at = p1
944 if repo.dirstate.branch() != atbranch:
944 if repo.dirstate.branch() != atbranch:
945 branch(ui, repo, atbranch, force=True)
945 branch(ui, repo, atbranch, force=True)
946 if len(ps) > 1:
946 if len(ps) > 1:
947 p2 = ps[1]
947 p2 = ps[1]
948 merge(ui, repo, node=p2)
948 merge(ui, repo, node=p2)
949
949
950 if mergeable_file:
950 if mergeable_file:
951 f = open("mf", "rb+")
951 f = open("mf", "rb+")
952 try:
952 try:
953 lines = f.read().split("\n")
953 lines = f.read().split("\n")
954 lines[id * linesperrev] += " r%i" % id
954 lines[id * linesperrev] += " r%i" % id
955 f.seek(0)
955 f.seek(0)
956 f.write("\n".join(lines))
956 f.write("\n".join(lines))
957 finally:
957 finally:
958 f.close()
958 f.close()
959
959
960 if appended_file:
960 if appended_file:
961 writefile("af", "r%i\n" % id, "ab")
961 writefile("af", "r%i\n" % id, "ab")
962
962
963 if overwritten_file:
963 if overwritten_file:
964 writefile("of", "r%i\n" % id)
964 writefile("of", "r%i\n" % id)
965
965
966 if new_file:
966 if new_file:
967 writefile("nf%i" % id, "r%i\n" % id)
967 writefile("nf%i" % id, "r%i\n" % id)
968
968
969 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
969 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
970 at = id
970 at = id
971 elif type == 'l':
971 elif type == 'l':
972 id, name = data
972 id, name = data
973 ui.status('tag %s\n' % name)
973 ui.status('tag %s\n' % name)
974 tag(ui, repo, name, local=True)
974 tag(ui, repo, name, local=True)
975 elif type == 'a':
975 elif type == 'a':
976 ui.status('branch %s\n' % data)
976 ui.status('branch %s\n' % data)
977 atbranch = data
977 atbranch = data
978 elif type in 'cC':
978 elif type in 'cC':
979 r = util.system(data, cwd=repo.root)
979 r = util.system(data, cwd=repo.root)
980 if r:
980 if r:
981 desc, r = util.explain_exit(r)
981 desc, r = util.explain_exit(r)
982 raise util.Abort(_('%s command %s') % (data, desc))
982 raise util.Abort(_('%s command %s') % (data, desc))
983
983
984 def debugcommands(ui, cmd='', *args):
984 def debugcommands(ui, cmd='', *args):
985 """list all available commands and options"""
985 """list all available commands and options"""
986 for cmd, vals in sorted(table.iteritems()):
986 for cmd, vals in sorted(table.iteritems()):
987 cmd = cmd.split('|')[0].strip('^')
987 cmd = cmd.split('|')[0].strip('^')
988 opts = ', '.join([i[1] for i in vals[1]])
988 opts = ', '.join([i[1] for i in vals[1]])
989 ui.write('%s: %s\n' % (cmd, opts))
989 ui.write('%s: %s\n' % (cmd, opts))
990
990
991 def debugcomplete(ui, cmd='', **opts):
991 def debugcomplete(ui, cmd='', **opts):
992 """returns the completion list associated with the given command"""
992 """returns the completion list associated with the given command"""
993
993
994 if opts.get('options'):
994 if opts.get('options'):
995 options = []
995 options = []
996 otables = [globalopts]
996 otables = [globalopts]
997 if cmd:
997 if cmd:
998 aliases, entry = cmdutil.findcmd(cmd, table, False)
998 aliases, entry = cmdutil.findcmd(cmd, table, False)
999 otables.append(entry[1])
999 otables.append(entry[1])
1000 for t in otables:
1000 for t in otables:
1001 for o in t:
1001 for o in t:
1002 if "(DEPRECATED)" in o[3]:
1002 if "(DEPRECATED)" in o[3]:
1003 continue
1003 continue
1004 if o[0]:
1004 if o[0]:
1005 options.append('-%s' % o[0])
1005 options.append('-%s' % o[0])
1006 options.append('--%s' % o[1])
1006 options.append('--%s' % o[1])
1007 ui.write("%s\n" % "\n".join(options))
1007 ui.write("%s\n" % "\n".join(options))
1008 return
1008 return
1009
1009
1010 cmdlist = cmdutil.findpossible(cmd, table)
1010 cmdlist = cmdutil.findpossible(cmd, table)
1011 if ui.verbose:
1011 if ui.verbose:
1012 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1012 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1013 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1013 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1014
1014
1015 def debugfsinfo(ui, path = "."):
1015 def debugfsinfo(ui, path = "."):
1016 """show information detected about current filesystem"""
1016 """show information detected about current filesystem"""
1017 open('.debugfsinfo', 'w').write('')
1017 open('.debugfsinfo', 'w').write('')
1018 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1018 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1019 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1019 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1020 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1020 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1021 and 'yes' or 'no'))
1021 and 'yes' or 'no'))
1022 os.unlink('.debugfsinfo')
1022 os.unlink('.debugfsinfo')
1023
1023
1024 def debugrebuildstate(ui, repo, rev="tip"):
1024 def debugrebuildstate(ui, repo, rev="tip"):
1025 """rebuild the dirstate as it would look like for the given revision"""
1025 """rebuild the dirstate as it would look like for the given revision"""
1026 ctx = repo[rev]
1026 ctx = repo[rev]
1027 wlock = repo.wlock()
1027 wlock = repo.wlock()
1028 try:
1028 try:
1029 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1029 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1030 finally:
1030 finally:
1031 wlock.release()
1031 wlock.release()
1032
1032
1033 def debugcheckstate(ui, repo):
1033 def debugcheckstate(ui, repo):
1034 """validate the correctness of the current dirstate"""
1034 """validate the correctness of the current dirstate"""
1035 parent1, parent2 = repo.dirstate.parents()
1035 parent1, parent2 = repo.dirstate.parents()
1036 m1 = repo[parent1].manifest()
1036 m1 = repo[parent1].manifest()
1037 m2 = repo[parent2].manifest()
1037 m2 = repo[parent2].manifest()
1038 errors = 0
1038 errors = 0
1039 for f in repo.dirstate:
1039 for f in repo.dirstate:
1040 state = repo.dirstate[f]
1040 state = repo.dirstate[f]
1041 if state in "nr" and f not in m1:
1041 if state in "nr" and f not in m1:
1042 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1042 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1043 errors += 1
1043 errors += 1
1044 if state in "a" and f in m1:
1044 if state in "a" and f in m1:
1045 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1045 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1046 errors += 1
1046 errors += 1
1047 if state in "m" and f not in m1 and f not in m2:
1047 if state in "m" and f not in m1 and f not in m2:
1048 ui.warn(_("%s in state %s, but not in either manifest\n") %
1048 ui.warn(_("%s in state %s, but not in either manifest\n") %
1049 (f, state))
1049 (f, state))
1050 errors += 1
1050 errors += 1
1051 for f in m1:
1051 for f in m1:
1052 state = repo.dirstate[f]
1052 state = repo.dirstate[f]
1053 if state not in "nrm":
1053 if state not in "nrm":
1054 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1054 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1055 errors += 1
1055 errors += 1
1056 if errors:
1056 if errors:
1057 error = _(".hg/dirstate inconsistent with current parent's manifest")
1057 error = _(".hg/dirstate inconsistent with current parent's manifest")
1058 raise util.Abort(error)
1058 raise util.Abort(error)
1059
1059
1060 def showconfig(ui, repo, *values, **opts):
1060 def showconfig(ui, repo, *values, **opts):
1061 """show combined config settings from all hgrc files
1061 """show combined config settings from all hgrc files
1062
1062
1063 With no arguments, print names and values of all config items.
1063 With no arguments, print names and values of all config items.
1064
1064
1065 With one argument of the form section.name, print just the value
1065 With one argument of the form section.name, print just the value
1066 of that config item.
1066 of that config item.
1067
1067
1068 With multiple arguments, print names and values of all config
1068 With multiple arguments, print names and values of all config
1069 items with matching section names.
1069 items with matching section names.
1070
1070
1071 With --debug, the source (filename and line number) is printed
1071 With --debug, the source (filename and line number) is printed
1072 for each config item.
1072 for each config item.
1073
1073
1074 Returns 0 on success.
1074 Returns 0 on success.
1075 """
1075 """
1076
1076
1077 for f in util.rcpath():
1077 for f in util.rcpath():
1078 ui.debug(_('read config from: %s\n') % f)
1078 ui.debug(_('read config from: %s\n') % f)
1079 untrusted = bool(opts.get('untrusted'))
1079 untrusted = bool(opts.get('untrusted'))
1080 if values:
1080 if values:
1081 if len([v for v in values if '.' in v]) > 1:
1081 if len([v for v in values if '.' in v]) > 1:
1082 raise util.Abort(_('only one config item permitted'))
1082 raise util.Abort(_('only one config item permitted'))
1083 for section, name, value in ui.walkconfig(untrusted=untrusted):
1083 for section, name, value in ui.walkconfig(untrusted=untrusted):
1084 sectname = section + '.' + name
1084 sectname = section + '.' + name
1085 if values:
1085 if values:
1086 for v in values:
1086 for v in values:
1087 if v == section:
1087 if v == section:
1088 ui.debug('%s: ' %
1088 ui.debug('%s: ' %
1089 ui.configsource(section, name, untrusted))
1089 ui.configsource(section, name, untrusted))
1090 ui.write('%s=%s\n' % (sectname, value))
1090 ui.write('%s=%s\n' % (sectname, value))
1091 elif v == sectname:
1091 elif v == sectname:
1092 ui.debug('%s: ' %
1092 ui.debug('%s: ' %
1093 ui.configsource(section, name, untrusted))
1093 ui.configsource(section, name, untrusted))
1094 ui.write(value, '\n')
1094 ui.write(value, '\n')
1095 else:
1095 else:
1096 ui.debug('%s: ' %
1096 ui.debug('%s: ' %
1097 ui.configsource(section, name, untrusted))
1097 ui.configsource(section, name, untrusted))
1098 ui.write('%s=%s\n' % (sectname, value))
1098 ui.write('%s=%s\n' % (sectname, value))
1099
1099
1100 def debugpushkey(ui, repopath, namespace, *keyinfo):
1100 def debugpushkey(ui, repopath, namespace, *keyinfo):
1101 '''access the pushkey key/value protocol
1101 '''access the pushkey key/value protocol
1102
1102
1103 With two args, list the keys in the given namespace.
1103 With two args, list the keys in the given namespace.
1104
1104
1105 With five args, set a key to new if it currently is set to old.
1105 With five args, set a key to new if it currently is set to old.
1106 Reports success or failure.
1106 Reports success or failure.
1107 '''
1107 '''
1108
1108
1109 target = hg.repository(ui, repopath)
1109 target = hg.repository(ui, repopath)
1110 if keyinfo:
1110 if keyinfo:
1111 key, old, new = keyinfo
1111 key, old, new = keyinfo
1112 r = target.pushkey(namespace, key, old, new)
1112 r = target.pushkey(namespace, key, old, new)
1113 ui.status(str(r) + '\n')
1113 ui.status(str(r) + '\n')
1114 return not(r)
1114 return not(r)
1115 else:
1115 else:
1116 for k, v in target.listkeys(namespace).iteritems():
1116 for k, v in target.listkeys(namespace).iteritems():
1117 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1117 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1118 v.encode('string-escape')))
1118 v.encode('string-escape')))
1119
1119
1120 def debugrevspec(ui, repo, expr):
1120 def debugrevspec(ui, repo, expr):
1121 '''parse and apply a revision specification'''
1121 '''parse and apply a revision specification'''
1122 if ui.verbose:
1122 if ui.verbose:
1123 tree = revset.parse(expr)
1123 tree = revset.parse(expr)
1124 ui.note(tree, "\n")
1124 ui.note(tree, "\n")
1125 func = revset.match(expr)
1125 func = revset.match(expr)
1126 for c in func(repo, range(len(repo))):
1126 for c in func(repo, range(len(repo))):
1127 ui.write("%s\n" % c)
1127 ui.write("%s\n" % c)
1128
1128
1129 def debugsetparents(ui, repo, rev1, rev2=None):
1129 def debugsetparents(ui, repo, rev1, rev2=None):
1130 """manually set the parents of the current working directory
1130 """manually set the parents of the current working directory
1131
1131
1132 This is useful for writing repository conversion tools, but should
1132 This is useful for writing repository conversion tools, but should
1133 be used with care.
1133 be used with care.
1134
1134
1135 Returns 0 on success.
1135 Returns 0 on success.
1136 """
1136 """
1137
1137
1138 if not rev2:
1138 if not rev2:
1139 rev2 = hex(nullid)
1139 rev2 = hex(nullid)
1140
1140
1141 wlock = repo.wlock()
1141 wlock = repo.wlock()
1142 try:
1142 try:
1143 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1143 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1144 finally:
1144 finally:
1145 wlock.release()
1145 wlock.release()
1146
1146
1147 def debugstate(ui, repo, nodates=None):
1147 def debugstate(ui, repo, nodates=None):
1148 """show the contents of the current dirstate"""
1148 """show the contents of the current dirstate"""
1149 timestr = ""
1149 timestr = ""
1150 showdate = not nodates
1150 showdate = not nodates
1151 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1151 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1152 if showdate:
1152 if showdate:
1153 if ent[3] == -1:
1153 if ent[3] == -1:
1154 # Pad or slice to locale representation
1154 # Pad or slice to locale representation
1155 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1155 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1156 time.localtime(0)))
1156 time.localtime(0)))
1157 timestr = 'unset'
1157 timestr = 'unset'
1158 timestr = (timestr[:locale_len] +
1158 timestr = (timestr[:locale_len] +
1159 ' ' * (locale_len - len(timestr)))
1159 ' ' * (locale_len - len(timestr)))
1160 else:
1160 else:
1161 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1161 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1162 time.localtime(ent[3]))
1162 time.localtime(ent[3]))
1163 if ent[1] & 020000:
1163 if ent[1] & 020000:
1164 mode = 'lnk'
1164 mode = 'lnk'
1165 else:
1165 else:
1166 mode = '%3o' % (ent[1] & 0777)
1166 mode = '%3o' % (ent[1] & 0777)
1167 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1167 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1168 for f in repo.dirstate.copies():
1168 for f in repo.dirstate.copies():
1169 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1169 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1170
1170
1171 def debugsub(ui, repo, rev=None):
1171 def debugsub(ui, repo, rev=None):
1172 if rev == '':
1172 if rev == '':
1173 rev = None
1173 rev = None
1174 for k, v in sorted(repo[rev].substate.items()):
1174 for k, v in sorted(repo[rev].substate.items()):
1175 ui.write('path %s\n' % k)
1175 ui.write('path %s\n' % k)
1176 ui.write(' source %s\n' % v[0])
1176 ui.write(' source %s\n' % v[0])
1177 ui.write(' revision %s\n' % v[1])
1177 ui.write(' revision %s\n' % v[1])
1178
1178
1179 def debugdag(ui, repo, file_=None, *revs, **opts):
1179 def debugdag(ui, repo, file_=None, *revs, **opts):
1180 """format the changelog or an index DAG as a concise textual description
1180 """format the changelog or an index DAG as a concise textual description
1181
1181
1182 If you pass a revlog index, the revlog's DAG is emitted. If you list
1182 If you pass a revlog index, the revlog's DAG is emitted. If you list
1183 revision numbers, they get labelled in the output as rN.
1183 revision numbers, they get labelled in the output as rN.
1184
1184
1185 Otherwise, the changelog DAG of the current repo is emitted.
1185 Otherwise, the changelog DAG of the current repo is emitted.
1186 """
1186 """
1187 spaces = opts.get('spaces')
1187 spaces = opts.get('spaces')
1188 dots = opts.get('dots')
1188 dots = opts.get('dots')
1189 if file_:
1189 if file_:
1190 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1190 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1191 revs = set((int(r) for r in revs))
1191 revs = set((int(r) for r in revs))
1192 def events():
1192 def events():
1193 for r in rlog:
1193 for r in rlog:
1194 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1194 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1195 if r in revs:
1195 if r in revs:
1196 yield 'l', (r, "r%i" % r)
1196 yield 'l', (r, "r%i" % r)
1197 elif repo:
1197 elif repo:
1198 cl = repo.changelog
1198 cl = repo.changelog
1199 tags = opts.get('tags')
1199 tags = opts.get('tags')
1200 branches = opts.get('branches')
1200 branches = opts.get('branches')
1201 if tags:
1201 if tags:
1202 labels = {}
1202 labels = {}
1203 for l, n in repo.tags().items():
1203 for l, n in repo.tags().items():
1204 labels.setdefault(cl.rev(n), []).append(l)
1204 labels.setdefault(cl.rev(n), []).append(l)
1205 def events():
1205 def events():
1206 b = "default"
1206 b = "default"
1207 for r in cl:
1207 for r in cl:
1208 if branches:
1208 if branches:
1209 newb = cl.read(cl.node(r))[5]['branch']
1209 newb = cl.read(cl.node(r))[5]['branch']
1210 if newb != b:
1210 if newb != b:
1211 yield 'a', newb
1211 yield 'a', newb
1212 b = newb
1212 b = newb
1213 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1213 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1214 if tags:
1214 if tags:
1215 ls = labels.get(r)
1215 ls = labels.get(r)
1216 if ls:
1216 if ls:
1217 for l in ls:
1217 for l in ls:
1218 yield 'l', (r, l)
1218 yield 'l', (r, l)
1219 else:
1219 else:
1220 raise util.Abort(_('need repo for changelog dag'))
1220 raise util.Abort(_('need repo for changelog dag'))
1221
1221
1222 for line in dagparser.dagtextlines(events(),
1222 for line in dagparser.dagtextlines(events(),
1223 addspaces=spaces,
1223 addspaces=spaces,
1224 wraplabels=True,
1224 wraplabels=True,
1225 wrapannotations=True,
1225 wrapannotations=True,
1226 wrapnonlinear=dots,
1226 wrapnonlinear=dots,
1227 usedots=dots,
1227 usedots=dots,
1228 maxlinewidth=70):
1228 maxlinewidth=70):
1229 ui.write(line)
1229 ui.write(line)
1230 ui.write("\n")
1230 ui.write("\n")
1231
1231
1232 def debugdata(ui, repo, file_, rev):
1232 def debugdata(ui, repo, file_, rev):
1233 """dump the contents of a data file revision"""
1233 """dump the contents of a data file revision"""
1234 r = None
1234 r = None
1235 if repo:
1235 if repo:
1236 filelog = repo.file(file_)
1236 filelog = repo.file(file_)
1237 if len(filelog):
1237 if len(filelog):
1238 r = filelog
1238 r = filelog
1239 if not r:
1239 if not r:
1240 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1240 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1241 try:
1241 try:
1242 ui.write(r.revision(r.lookup(rev)))
1242 ui.write(r.revision(r.lookup(rev)))
1243 except KeyError:
1243 except KeyError:
1244 raise util.Abort(_('invalid revision identifier %s') % rev)
1244 raise util.Abort(_('invalid revision identifier %s') % rev)
1245
1245
1246 def debugdate(ui, date, range=None, **opts):
1246 def debugdate(ui, date, range=None, **opts):
1247 """parse and display a date"""
1247 """parse and display a date"""
1248 if opts["extended"]:
1248 if opts["extended"]:
1249 d = util.parsedate(date, util.extendeddateformats)
1249 d = util.parsedate(date, util.extendeddateformats)
1250 else:
1250 else:
1251 d = util.parsedate(date)
1251 d = util.parsedate(date)
1252 ui.write("internal: %s %s\n" % d)
1252 ui.write("internal: %s %s\n" % d)
1253 ui.write("standard: %s\n" % util.datestr(d))
1253 ui.write("standard: %s\n" % util.datestr(d))
1254 if range:
1254 if range:
1255 m = util.matchdate(range)
1255 m = util.matchdate(range)
1256 ui.write("match: %s\n" % m(d[0]))
1256 ui.write("match: %s\n" % m(d[0]))
1257
1257
1258 def debugindex(ui, repo, file_):
1258 def debugindex(ui, repo, file_):
1259 """dump the contents of an index file"""
1259 """dump the contents of an index file"""
1260 r = None
1260 r = None
1261 if repo:
1261 if repo:
1262 filelog = repo.file(file_)
1262 filelog = repo.file(file_)
1263 if len(filelog):
1263 if len(filelog):
1264 r = filelog
1264 r = filelog
1265 if not r:
1265 if not r:
1266 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1266 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1267 ui.write(" rev offset length base linkrev"
1267 ui.write(" rev offset length base linkrev"
1268 " nodeid p1 p2\n")
1268 " nodeid p1 p2\n")
1269 for i in r:
1269 for i in r:
1270 node = r.node(i)
1270 node = r.node(i)
1271 try:
1271 try:
1272 pp = r.parents(node)
1272 pp = r.parents(node)
1273 except:
1273 except:
1274 pp = [nullid, nullid]
1274 pp = [nullid, nullid]
1275 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1275 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1276 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1276 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1277 short(node), short(pp[0]), short(pp[1])))
1277 short(node), short(pp[0]), short(pp[1])))
1278
1278
1279 def debugindexdot(ui, repo, file_):
1279 def debugindexdot(ui, repo, file_):
1280 """dump an index DAG as a graphviz dot file"""
1280 """dump an index DAG as a graphviz dot file"""
1281 r = None
1281 r = None
1282 if repo:
1282 if repo:
1283 filelog = repo.file(file_)
1283 filelog = repo.file(file_)
1284 if len(filelog):
1284 if len(filelog):
1285 r = filelog
1285 r = filelog
1286 if not r:
1286 if not r:
1287 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1287 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1288 ui.write("digraph G {\n")
1288 ui.write("digraph G {\n")
1289 for i in r:
1289 for i in r:
1290 node = r.node(i)
1290 node = r.node(i)
1291 pp = r.parents(node)
1291 pp = r.parents(node)
1292 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1292 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1293 if pp[1] != nullid:
1293 if pp[1] != nullid:
1294 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1294 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1295 ui.write("}\n")
1295 ui.write("}\n")
1296
1296
1297 def debuginstall(ui):
1297 def debuginstall(ui):
1298 '''test Mercurial installation
1298 '''test Mercurial installation
1299
1299
1300 Returns 0 on success.
1300 Returns 0 on success.
1301 '''
1301 '''
1302
1302
1303 def writetemp(contents):
1303 def writetemp(contents):
1304 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1304 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1305 f = os.fdopen(fd, "wb")
1305 f = os.fdopen(fd, "wb")
1306 f.write(contents)
1306 f.write(contents)
1307 f.close()
1307 f.close()
1308 return name
1308 return name
1309
1309
1310 problems = 0
1310 problems = 0
1311
1311
1312 # encoding
1312 # encoding
1313 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1313 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1314 try:
1314 try:
1315 encoding.fromlocal("test")
1315 encoding.fromlocal("test")
1316 except util.Abort, inst:
1316 except util.Abort, inst:
1317 ui.write(" %s\n" % inst)
1317 ui.write(" %s\n" % inst)
1318 ui.write(_(" (check that your locale is properly set)\n"))
1318 ui.write(_(" (check that your locale is properly set)\n"))
1319 problems += 1
1319 problems += 1
1320
1320
1321 # compiled modules
1321 # compiled modules
1322 ui.status(_("Checking installed modules (%s)...\n")
1322 ui.status(_("Checking installed modules (%s)...\n")
1323 % os.path.dirname(__file__))
1323 % os.path.dirname(__file__))
1324 try:
1324 try:
1325 import bdiff, mpatch, base85, osutil
1325 import bdiff, mpatch, base85, osutil
1326 except Exception, inst:
1326 except Exception, inst:
1327 ui.write(" %s\n" % inst)
1327 ui.write(" %s\n" % inst)
1328 ui.write(_(" One or more extensions could not be found"))
1328 ui.write(_(" One or more extensions could not be found"))
1329 ui.write(_(" (check that you compiled the extensions)\n"))
1329 ui.write(_(" (check that you compiled the extensions)\n"))
1330 problems += 1
1330 problems += 1
1331
1331
1332 # templates
1332 # templates
1333 ui.status(_("Checking templates...\n"))
1333 ui.status(_("Checking templates...\n"))
1334 try:
1334 try:
1335 import templater
1335 import templater
1336 templater.templater(templater.templatepath("map-cmdline.default"))
1336 templater.templater(templater.templatepath("map-cmdline.default"))
1337 except Exception, inst:
1337 except Exception, inst:
1338 ui.write(" %s\n" % inst)
1338 ui.write(" %s\n" % inst)
1339 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1339 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1340 problems += 1
1340 problems += 1
1341
1341
1342 # patch
1342 # patch
1343 ui.status(_("Checking patch...\n"))
1343 ui.status(_("Checking patch...\n"))
1344 patchproblems = 0
1344 patchproblems = 0
1345 a = "1\n2\n3\n4\n"
1345 a = "1\n2\n3\n4\n"
1346 b = "1\n2\n3\ninsert\n4\n"
1346 b = "1\n2\n3\ninsert\n4\n"
1347 fa = writetemp(a)
1347 fa = writetemp(a)
1348 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1348 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1349 os.path.basename(fa))
1349 os.path.basename(fa))
1350 fd = writetemp(d)
1350 fd = writetemp(d)
1351
1351
1352 files = {}
1352 files = {}
1353 try:
1353 try:
1354 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1354 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1355 except util.Abort, e:
1355 except util.Abort, e:
1356 ui.write(_(" patch call failed:\n"))
1356 ui.write(_(" patch call failed:\n"))
1357 ui.write(" " + str(e) + "\n")
1357 ui.write(" " + str(e) + "\n")
1358 patchproblems += 1
1358 patchproblems += 1
1359 else:
1359 else:
1360 if list(files) != [os.path.basename(fa)]:
1360 if list(files) != [os.path.basename(fa)]:
1361 ui.write(_(" unexpected patch output!\n"))
1361 ui.write(_(" unexpected patch output!\n"))
1362 patchproblems += 1
1362 patchproblems += 1
1363 a = open(fa).read()
1363 a = open(fa).read()
1364 if a != b:
1364 if a != b:
1365 ui.write(_(" patch test failed!\n"))
1365 ui.write(_(" patch test failed!\n"))
1366 patchproblems += 1
1366 patchproblems += 1
1367
1367
1368 if patchproblems:
1368 if patchproblems:
1369 if ui.config('ui', 'patch'):
1369 if ui.config('ui', 'patch'):
1370 ui.write(_(" (Current patch tool may be incompatible with patch,"
1370 ui.write(_(" (Current patch tool may be incompatible with patch,"
1371 " or misconfigured. Please check your configuration"
1371 " or misconfigured. Please check your configuration"
1372 " file)\n"))
1372 " file)\n"))
1373 else:
1373 else:
1374 ui.write(_(" Internal patcher failure, please report this error"
1374 ui.write(_(" Internal patcher failure, please report this error"
1375 " to http://mercurial.selenic.com/bts/\n"))
1375 " to http://mercurial.selenic.com/bts/\n"))
1376 problems += patchproblems
1376 problems += patchproblems
1377
1377
1378 os.unlink(fa)
1378 os.unlink(fa)
1379 os.unlink(fd)
1379 os.unlink(fd)
1380
1380
1381 # editor
1381 # editor
1382 ui.status(_("Checking commit editor...\n"))
1382 ui.status(_("Checking commit editor...\n"))
1383 editor = ui.geteditor()
1383 editor = ui.geteditor()
1384 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1384 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1385 if not cmdpath:
1385 if not cmdpath:
1386 if editor == 'vi':
1386 if editor == 'vi':
1387 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1387 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1388 ui.write(_(" (specify a commit editor in your configuration"
1388 ui.write(_(" (specify a commit editor in your configuration"
1389 " file)\n"))
1389 " file)\n"))
1390 else:
1390 else:
1391 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1391 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1392 ui.write(_(" (specify a commit editor in your configuration"
1392 ui.write(_(" (specify a commit editor in your configuration"
1393 " file)\n"))
1393 " file)\n"))
1394 problems += 1
1394 problems += 1
1395
1395
1396 # check username
1396 # check username
1397 ui.status(_("Checking username...\n"))
1397 ui.status(_("Checking username...\n"))
1398 try:
1398 try:
1399 ui.username()
1399 ui.username()
1400 except util.Abort, e:
1400 except util.Abort, e:
1401 ui.write(" %s\n" % e)
1401 ui.write(" %s\n" % e)
1402 ui.write(_(" (specify a username in your configuration file)\n"))
1402 ui.write(_(" (specify a username in your configuration file)\n"))
1403 problems += 1
1403 problems += 1
1404
1404
1405 if not problems:
1405 if not problems:
1406 ui.status(_("No problems detected\n"))
1406 ui.status(_("No problems detected\n"))
1407 else:
1407 else:
1408 ui.write(_("%s problems detected,"
1408 ui.write(_("%s problems detected,"
1409 " please check your install!\n") % problems)
1409 " please check your install!\n") % problems)
1410
1410
1411 return problems
1411 return problems
1412
1412
1413 def debugrename(ui, repo, file1, *pats, **opts):
1413 def debugrename(ui, repo, file1, *pats, **opts):
1414 """dump rename information"""
1414 """dump rename information"""
1415
1415
1416 ctx = repo[opts.get('rev')]
1416 ctx = repo[opts.get('rev')]
1417 m = cmdutil.match(repo, (file1,) + pats, opts)
1417 m = cmdutil.match(repo, (file1,) + pats, opts)
1418 for abs in ctx.walk(m):
1418 for abs in ctx.walk(m):
1419 fctx = ctx[abs]
1419 fctx = ctx[abs]
1420 o = fctx.filelog().renamed(fctx.filenode())
1420 o = fctx.filelog().renamed(fctx.filenode())
1421 rel = m.rel(abs)
1421 rel = m.rel(abs)
1422 if o:
1422 if o:
1423 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1423 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1424 else:
1424 else:
1425 ui.write(_("%s not renamed\n") % rel)
1425 ui.write(_("%s not renamed\n") % rel)
1426
1426
1427 def debugwalk(ui, repo, *pats, **opts):
1427 def debugwalk(ui, repo, *pats, **opts):
1428 """show how files match on given patterns"""
1428 """show how files match on given patterns"""
1429 m = cmdutil.match(repo, pats, opts)
1429 m = cmdutil.match(repo, pats, opts)
1430 items = list(repo.walk(m))
1430 items = list(repo.walk(m))
1431 if not items:
1431 if not items:
1432 return
1432 return
1433 fmt = 'f %%-%ds %%-%ds %%s' % (
1433 fmt = 'f %%-%ds %%-%ds %%s' % (
1434 max([len(abs) for abs in items]),
1434 max([len(abs) for abs in items]),
1435 max([len(m.rel(abs)) for abs in items]))
1435 max([len(m.rel(abs)) for abs in items]))
1436 for abs in items:
1436 for abs in items:
1437 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1437 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1438 ui.write("%s\n" % line.rstrip())
1438 ui.write("%s\n" % line.rstrip())
1439
1439
1440 def diff(ui, repo, *pats, **opts):
1440 def diff(ui, repo, *pats, **opts):
1441 """diff repository (or selected files)
1441 """diff repository (or selected files)
1442
1442
1443 Show differences between revisions for the specified files.
1443 Show differences between revisions for the specified files.
1444
1444
1445 Differences between files are shown using the unified diff format.
1445 Differences between files are shown using the unified diff format.
1446
1446
1447 NOTE: diff may generate unexpected results for merges, as it will
1447 NOTE: diff may generate unexpected results for merges, as it will
1448 default to comparing against the working directory's first parent
1448 default to comparing against the working directory's first parent
1449 changeset if no revisions are specified.
1449 changeset if no revisions are specified.
1450
1450
1451 When two revision arguments are given, then changes are shown
1451 When two revision arguments are given, then changes are shown
1452 between those revisions. If only one revision is specified then
1452 between those revisions. If only one revision is specified then
1453 that revision is compared to the working directory, and, when no
1453 that revision is compared to the working directory, and, when no
1454 revisions are specified, the working directory files are compared
1454 revisions are specified, the working directory files are compared
1455 to its parent.
1455 to its parent.
1456
1456
1457 Alternatively you can specify -c/--change with a revision to see
1457 Alternatively you can specify -c/--change with a revision to see
1458 the changes in that changeset relative to its first parent.
1458 the changes in that changeset relative to its first parent.
1459
1459
1460 Without the -a/--text option, diff will avoid generating diffs of
1460 Without the -a/--text option, diff will avoid generating diffs of
1461 files it detects as binary. With -a, diff will generate a diff
1461 files it detects as binary. With -a, diff will generate a diff
1462 anyway, probably with undesirable results.
1462 anyway, probably with undesirable results.
1463
1463
1464 Use the -g/--git option to generate diffs in the git extended diff
1464 Use the -g/--git option to generate diffs in the git extended diff
1465 format. For more information, read :hg:`help diffs`.
1465 format. For more information, read :hg:`help diffs`.
1466
1466
1467 Returns 0 on success.
1467 Returns 0 on success.
1468 """
1468 """
1469
1469
1470 revs = opts.get('rev')
1470 revs = opts.get('rev')
1471 change = opts.get('change')
1471 change = opts.get('change')
1472 stat = opts.get('stat')
1472 stat = opts.get('stat')
1473 reverse = opts.get('reverse')
1473 reverse = opts.get('reverse')
1474
1474
1475 if revs and change:
1475 if revs and change:
1476 msg = _('cannot specify --rev and --change at the same time')
1476 msg = _('cannot specify --rev and --change at the same time')
1477 raise util.Abort(msg)
1477 raise util.Abort(msg)
1478 elif change:
1478 elif change:
1479 node2 = repo.lookup(change)
1479 node2 = repo.lookup(change)
1480 node1 = repo[node2].parents()[0].node()
1480 node1 = repo[node2].parents()[0].node()
1481 else:
1481 else:
1482 node1, node2 = cmdutil.revpair(repo, revs)
1482 node1, node2 = cmdutil.revpair(repo, revs)
1483
1483
1484 if reverse:
1484 if reverse:
1485 node1, node2 = node2, node1
1485 node1, node2 = node2, node1
1486
1486
1487 diffopts = patch.diffopts(ui, opts)
1487 diffopts = patch.diffopts(ui, opts)
1488 m = cmdutil.match(repo, pats, opts)
1488 m = cmdutil.match(repo, pats, opts)
1489 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1489 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1490 listsubrepos=opts.get('subrepos'))
1490 listsubrepos=opts.get('subrepos'))
1491
1491
1492 def export(ui, repo, *changesets, **opts):
1492 def export(ui, repo, *changesets, **opts):
1493 """dump the header and diffs for one or more changesets
1493 """dump the header and diffs for one or more changesets
1494
1494
1495 Print the changeset header and diffs for one or more revisions.
1495 Print the changeset header and diffs for one or more revisions.
1496
1496
1497 The information shown in the changeset header is: author, date,
1497 The information shown in the changeset header is: author, date,
1498 branch name (if non-default), changeset hash, parent(s) and commit
1498 branch name (if non-default), changeset hash, parent(s) and commit
1499 comment.
1499 comment.
1500
1500
1501 NOTE: export may generate unexpected diff output for merge
1501 NOTE: export may generate unexpected diff output for merge
1502 changesets, as it will compare the merge changeset against its
1502 changesets, as it will compare the merge changeset against its
1503 first parent only.
1503 first parent only.
1504
1504
1505 Output may be to a file, in which case the name of the file is
1505 Output may be to a file, in which case the name of the file is
1506 given using a format string. The formatting rules are as follows:
1506 given using a format string. The formatting rules are as follows:
1507
1507
1508 :``%%``: literal "%" character
1508 :``%%``: literal "%" character
1509 :``%H``: changeset hash (40 hexadecimal digits)
1509 :``%H``: changeset hash (40 hexadecimal digits)
1510 :``%N``: number of patches being generated
1510 :``%N``: number of patches being generated
1511 :``%R``: changeset revision number
1511 :``%R``: changeset revision number
1512 :``%b``: basename of the exporting repository
1512 :``%b``: basename of the exporting repository
1513 :``%h``: short-form changeset hash (12 hexadecimal digits)
1513 :``%h``: short-form changeset hash (12 hexadecimal digits)
1514 :``%n``: zero-padded sequence number, starting at 1
1514 :``%n``: zero-padded sequence number, starting at 1
1515 :``%r``: zero-padded changeset revision number
1515 :``%r``: zero-padded changeset revision number
1516
1516
1517 Without the -a/--text option, export will avoid generating diffs
1517 Without the -a/--text option, export will avoid generating diffs
1518 of files it detects as binary. With -a, export will generate a
1518 of files it detects as binary. With -a, export will generate a
1519 diff anyway, probably with undesirable results.
1519 diff anyway, probably with undesirable results.
1520
1520
1521 Use the -g/--git option to generate diffs in the git extended diff
1521 Use the -g/--git option to generate diffs in the git extended diff
1522 format. See :hg:`help diffs` for more information.
1522 format. See :hg:`help diffs` for more information.
1523
1523
1524 With the --switch-parent option, the diff will be against the
1524 With the --switch-parent option, the diff will be against the
1525 second parent. It can be useful to review a merge.
1525 second parent. It can be useful to review a merge.
1526
1526
1527 Returns 0 on success.
1527 Returns 0 on success.
1528 """
1528 """
1529 changesets += tuple(opts.get('rev', []))
1529 changesets += tuple(opts.get('rev', []))
1530 if not changesets:
1530 if not changesets:
1531 raise util.Abort(_("export requires at least one changeset"))
1531 raise util.Abort(_("export requires at least one changeset"))
1532 revs = cmdutil.revrange(repo, changesets)
1532 revs = cmdutil.revrange(repo, changesets)
1533 if len(revs) > 1:
1533 if len(revs) > 1:
1534 ui.note(_('exporting patches:\n'))
1534 ui.note(_('exporting patches:\n'))
1535 else:
1535 else:
1536 ui.note(_('exporting patch:\n'))
1536 ui.note(_('exporting patch:\n'))
1537 cmdutil.export(repo, revs, template=opts.get('output'),
1537 cmdutil.export(repo, revs, template=opts.get('output'),
1538 switch_parent=opts.get('switch_parent'),
1538 switch_parent=opts.get('switch_parent'),
1539 opts=patch.diffopts(ui, opts))
1539 opts=patch.diffopts(ui, opts))
1540
1540
1541 def forget(ui, repo, *pats, **opts):
1541 def forget(ui, repo, *pats, **opts):
1542 """forget the specified files on the next commit
1542 """forget the specified files on the next commit
1543
1543
1544 Mark the specified files so they will no longer be tracked
1544 Mark the specified files so they will no longer be tracked
1545 after the next commit.
1545 after the next commit.
1546
1546
1547 This only removes files from the current branch, not from the
1547 This only removes files from the current branch, not from the
1548 entire project history, and it does not delete them from the
1548 entire project history, and it does not delete them from the
1549 working directory.
1549 working directory.
1550
1550
1551 To undo a forget before the next commit, see :hg:`add`.
1551 To undo a forget before the next commit, see :hg:`add`.
1552
1552
1553 Returns 0 on success.
1553 Returns 0 on success.
1554 """
1554 """
1555
1555
1556 if not pats:
1556 if not pats:
1557 raise util.Abort(_('no files specified'))
1557 raise util.Abort(_('no files specified'))
1558
1558
1559 m = cmdutil.match(repo, pats, opts)
1559 m = cmdutil.match(repo, pats, opts)
1560 s = repo.status(match=m, clean=True)
1560 s = repo.status(match=m, clean=True)
1561 forget = sorted(s[0] + s[1] + s[3] + s[6])
1561 forget = sorted(s[0] + s[1] + s[3] + s[6])
1562 errs = 0
1562 errs = 0
1563
1563
1564 for f in m.files():
1564 for f in m.files():
1565 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1565 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1566 ui.warn(_('not removing %s: file is already untracked\n')
1566 ui.warn(_('not removing %s: file is already untracked\n')
1567 % m.rel(f))
1567 % m.rel(f))
1568 errs = 1
1568 errs = 1
1569
1569
1570 for f in forget:
1570 for f in forget:
1571 if ui.verbose or not m.exact(f):
1571 if ui.verbose or not m.exact(f):
1572 ui.status(_('removing %s\n') % m.rel(f))
1572 ui.status(_('removing %s\n') % m.rel(f))
1573
1573
1574 repo[None].remove(forget, unlink=False)
1574 repo[None].remove(forget, unlink=False)
1575 return errs
1575 return errs
1576
1576
1577 def grep(ui, repo, pattern, *pats, **opts):
1577 def grep(ui, repo, pattern, *pats, **opts):
1578 """search for a pattern in specified files and revisions
1578 """search for a pattern in specified files and revisions
1579
1579
1580 Search revisions of files for a regular expression.
1580 Search revisions of files for a regular expression.
1581
1581
1582 This command behaves differently than Unix grep. It only accepts
1582 This command behaves differently than Unix grep. It only accepts
1583 Python/Perl regexps. It searches repository history, not the
1583 Python/Perl regexps. It searches repository history, not the
1584 working directory. It always prints the revision number in which a
1584 working directory. It always prints the revision number in which a
1585 match appears.
1585 match appears.
1586
1586
1587 By default, grep only prints output for the first revision of a
1587 By default, grep only prints output for the first revision of a
1588 file in which it finds a match. To get it to print every revision
1588 file in which it finds a match. To get it to print every revision
1589 that contains a change in match status ("-" for a match that
1589 that contains a change in match status ("-" for a match that
1590 becomes a non-match, or "+" for a non-match that becomes a match),
1590 becomes a non-match, or "+" for a non-match that becomes a match),
1591 use the --all flag.
1591 use the --all flag.
1592
1592
1593 Returns 0 if a match is found, 1 otherwise.
1593 Returns 0 if a match is found, 1 otherwise.
1594 """
1594 """
1595 reflags = 0
1595 reflags = 0
1596 if opts.get('ignore_case'):
1596 if opts.get('ignore_case'):
1597 reflags |= re.I
1597 reflags |= re.I
1598 try:
1598 try:
1599 regexp = re.compile(pattern, reflags)
1599 regexp = re.compile(pattern, reflags)
1600 except Exception, inst:
1600 except Exception, inst:
1601 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1601 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1602 return 1
1602 return 1
1603 sep, eol = ':', '\n'
1603 sep, eol = ':', '\n'
1604 if opts.get('print0'):
1604 if opts.get('print0'):
1605 sep = eol = '\0'
1605 sep = eol = '\0'
1606
1606
1607 getfile = util.lrucachefunc(repo.file)
1607 getfile = util.lrucachefunc(repo.file)
1608
1608
1609 def matchlines(body):
1609 def matchlines(body):
1610 begin = 0
1610 begin = 0
1611 linenum = 0
1611 linenum = 0
1612 while True:
1612 while True:
1613 match = regexp.search(body, begin)
1613 match = regexp.search(body, begin)
1614 if not match:
1614 if not match:
1615 break
1615 break
1616 mstart, mend = match.span()
1616 mstart, mend = match.span()
1617 linenum += body.count('\n', begin, mstart) + 1
1617 linenum += body.count('\n', begin, mstart) + 1
1618 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1618 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1619 begin = body.find('\n', mend) + 1 or len(body)
1619 begin = body.find('\n', mend) + 1 or len(body)
1620 lend = begin - 1
1620 lend = begin - 1
1621 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1621 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1622
1622
1623 class linestate(object):
1623 class linestate(object):
1624 def __init__(self, line, linenum, colstart, colend):
1624 def __init__(self, line, linenum, colstart, colend):
1625 self.line = line
1625 self.line = line
1626 self.linenum = linenum
1626 self.linenum = linenum
1627 self.colstart = colstart
1627 self.colstart = colstart
1628 self.colend = colend
1628 self.colend = colend
1629
1629
1630 def __hash__(self):
1630 def __hash__(self):
1631 return hash((self.linenum, self.line))
1631 return hash((self.linenum, self.line))
1632
1632
1633 def __eq__(self, other):
1633 def __eq__(self, other):
1634 return self.line == other.line
1634 return self.line == other.line
1635
1635
1636 matches = {}
1636 matches = {}
1637 copies = {}
1637 copies = {}
1638 def grepbody(fn, rev, body):
1638 def grepbody(fn, rev, body):
1639 matches[rev].setdefault(fn, [])
1639 matches[rev].setdefault(fn, [])
1640 m = matches[rev][fn]
1640 m = matches[rev][fn]
1641 for lnum, cstart, cend, line in matchlines(body):
1641 for lnum, cstart, cend, line in matchlines(body):
1642 s = linestate(line, lnum, cstart, cend)
1642 s = linestate(line, lnum, cstart, cend)
1643 m.append(s)
1643 m.append(s)
1644
1644
1645 def difflinestates(a, b):
1645 def difflinestates(a, b):
1646 sm = difflib.SequenceMatcher(None, a, b)
1646 sm = difflib.SequenceMatcher(None, a, b)
1647 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1647 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1648 if tag == 'insert':
1648 if tag == 'insert':
1649 for i in xrange(blo, bhi):
1649 for i in xrange(blo, bhi):
1650 yield ('+', b[i])
1650 yield ('+', b[i])
1651 elif tag == 'delete':
1651 elif tag == 'delete':
1652 for i in xrange(alo, ahi):
1652 for i in xrange(alo, ahi):
1653 yield ('-', a[i])
1653 yield ('-', a[i])
1654 elif tag == 'replace':
1654 elif tag == 'replace':
1655 for i in xrange(alo, ahi):
1655 for i in xrange(alo, ahi):
1656 yield ('-', a[i])
1656 yield ('-', a[i])
1657 for i in xrange(blo, bhi):
1657 for i in xrange(blo, bhi):
1658 yield ('+', b[i])
1658 yield ('+', b[i])
1659
1659
1660 def display(fn, ctx, pstates, states):
1660 def display(fn, ctx, pstates, states):
1661 rev = ctx.rev()
1661 rev = ctx.rev()
1662 datefunc = ui.quiet and util.shortdate or util.datestr
1662 datefunc = ui.quiet and util.shortdate or util.datestr
1663 found = False
1663 found = False
1664 filerevmatches = {}
1664 filerevmatches = {}
1665 if opts.get('all'):
1665 if opts.get('all'):
1666 iter = difflinestates(pstates, states)
1666 iter = difflinestates(pstates, states)
1667 else:
1667 else:
1668 iter = [('', l) for l in states]
1668 iter = [('', l) for l in states]
1669 for change, l in iter:
1669 for change, l in iter:
1670 cols = [fn, str(rev)]
1670 cols = [fn, str(rev)]
1671 before, match, after = None, None, None
1671 before, match, after = None, None, None
1672 if opts.get('line_number'):
1672 if opts.get('line_number'):
1673 cols.append(str(l.linenum))
1673 cols.append(str(l.linenum))
1674 if opts.get('all'):
1674 if opts.get('all'):
1675 cols.append(change)
1675 cols.append(change)
1676 if opts.get('user'):
1676 if opts.get('user'):
1677 cols.append(ui.shortuser(ctx.user()))
1677 cols.append(ui.shortuser(ctx.user()))
1678 if opts.get('date'):
1678 if opts.get('date'):
1679 cols.append(datefunc(ctx.date()))
1679 cols.append(datefunc(ctx.date()))
1680 if opts.get('files_with_matches'):
1680 if opts.get('files_with_matches'):
1681 c = (fn, rev)
1681 c = (fn, rev)
1682 if c in filerevmatches:
1682 if c in filerevmatches:
1683 continue
1683 continue
1684 filerevmatches[c] = 1
1684 filerevmatches[c] = 1
1685 else:
1685 else:
1686 before = l.line[:l.colstart]
1686 before = l.line[:l.colstart]
1687 match = l.line[l.colstart:l.colend]
1687 match = l.line[l.colstart:l.colend]
1688 after = l.line[l.colend:]
1688 after = l.line[l.colend:]
1689 ui.write(sep.join(cols))
1689 ui.write(sep.join(cols))
1690 if before is not None:
1690 if before is not None:
1691 ui.write(sep + before)
1691 ui.write(sep + before)
1692 ui.write(match, label='grep.match')
1692 ui.write(match, label='grep.match')
1693 ui.write(after)
1693 ui.write(after)
1694 ui.write(eol)
1694 ui.write(eol)
1695 found = True
1695 found = True
1696 return found
1696 return found
1697
1697
1698 skip = {}
1698 skip = {}
1699 revfiles = {}
1699 revfiles = {}
1700 matchfn = cmdutil.match(repo, pats, opts)
1700 matchfn = cmdutil.match(repo, pats, opts)
1701 found = False
1701 found = False
1702 follow = opts.get('follow')
1702 follow = opts.get('follow')
1703
1703
1704 def prep(ctx, fns):
1704 def prep(ctx, fns):
1705 rev = ctx.rev()
1705 rev = ctx.rev()
1706 pctx = ctx.parents()[0]
1706 pctx = ctx.parents()[0]
1707 parent = pctx.rev()
1707 parent = pctx.rev()
1708 matches.setdefault(rev, {})
1708 matches.setdefault(rev, {})
1709 matches.setdefault(parent, {})
1709 matches.setdefault(parent, {})
1710 files = revfiles.setdefault(rev, [])
1710 files = revfiles.setdefault(rev, [])
1711 for fn in fns:
1711 for fn in fns:
1712 flog = getfile(fn)
1712 flog = getfile(fn)
1713 try:
1713 try:
1714 fnode = ctx.filenode(fn)
1714 fnode = ctx.filenode(fn)
1715 except error.LookupError:
1715 except error.LookupError:
1716 continue
1716 continue
1717
1717
1718 copied = flog.renamed(fnode)
1718 copied = flog.renamed(fnode)
1719 copy = follow and copied and copied[0]
1719 copy = follow and copied and copied[0]
1720 if copy:
1720 if copy:
1721 copies.setdefault(rev, {})[fn] = copy
1721 copies.setdefault(rev, {})[fn] = copy
1722 if fn in skip:
1722 if fn in skip:
1723 if copy:
1723 if copy:
1724 skip[copy] = True
1724 skip[copy] = True
1725 continue
1725 continue
1726 files.append(fn)
1726 files.append(fn)
1727
1727
1728 if fn not in matches[rev]:
1728 if fn not in matches[rev]:
1729 grepbody(fn, rev, flog.read(fnode))
1729 grepbody(fn, rev, flog.read(fnode))
1730
1730
1731 pfn = copy or fn
1731 pfn = copy or fn
1732 if pfn not in matches[parent]:
1732 if pfn not in matches[parent]:
1733 try:
1733 try:
1734 fnode = pctx.filenode(pfn)
1734 fnode = pctx.filenode(pfn)
1735 grepbody(pfn, parent, flog.read(fnode))
1735 grepbody(pfn, parent, flog.read(fnode))
1736 except error.LookupError:
1736 except error.LookupError:
1737 pass
1737 pass
1738
1738
1739 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1739 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1740 rev = ctx.rev()
1740 rev = ctx.rev()
1741 parent = ctx.parents()[0].rev()
1741 parent = ctx.parents()[0].rev()
1742 for fn in sorted(revfiles.get(rev, [])):
1742 for fn in sorted(revfiles.get(rev, [])):
1743 states = matches[rev][fn]
1743 states = matches[rev][fn]
1744 copy = copies.get(rev, {}).get(fn)
1744 copy = copies.get(rev, {}).get(fn)
1745 if fn in skip:
1745 if fn in skip:
1746 if copy:
1746 if copy:
1747 skip[copy] = True
1747 skip[copy] = True
1748 continue
1748 continue
1749 pstates = matches.get(parent, {}).get(copy or fn, [])
1749 pstates = matches.get(parent, {}).get(copy or fn, [])
1750 if pstates or states:
1750 if pstates or states:
1751 r = display(fn, ctx, pstates, states)
1751 r = display(fn, ctx, pstates, states)
1752 found = found or r
1752 found = found or r
1753 if r and not opts.get('all'):
1753 if r and not opts.get('all'):
1754 skip[fn] = True
1754 skip[fn] = True
1755 if copy:
1755 if copy:
1756 skip[copy] = True
1756 skip[copy] = True
1757 del matches[rev]
1757 del matches[rev]
1758 del revfiles[rev]
1758 del revfiles[rev]
1759
1759
1760 return not found
1760 return not found
1761
1761
1762 def heads(ui, repo, *branchrevs, **opts):
1762 def heads(ui, repo, *branchrevs, **opts):
1763 """show current repository heads or show branch heads
1763 """show current repository heads or show branch heads
1764
1764
1765 With no arguments, show all repository branch heads.
1765 With no arguments, show all repository branch heads.
1766
1766
1767 Repository "heads" are changesets with no child changesets. They are
1767 Repository "heads" are changesets with no child changesets. They are
1768 where development generally takes place and are the usual targets
1768 where development generally takes place and are the usual targets
1769 for update and merge operations. Branch heads are changesets that have
1769 for update and merge operations. Branch heads are changesets that have
1770 no child changeset on the same branch.
1770 no child changeset on the same branch.
1771
1771
1772 If one or more REVs are given, only branch heads on the branches
1772 If one or more REVs are given, only branch heads on the branches
1773 associated with the specified changesets are shown.
1773 associated with the specified changesets are shown.
1774
1774
1775 If -c/--closed is specified, also show branch heads marked closed
1775 If -c/--closed is specified, also show branch heads marked closed
1776 (see :hg:`commit --close-branch`).
1776 (see :hg:`commit --close-branch`).
1777
1777
1778 If STARTREV is specified, only those heads that are descendants of
1778 If STARTREV is specified, only those heads that are descendants of
1779 STARTREV will be displayed.
1779 STARTREV will be displayed.
1780
1780
1781 If -t/--topo is specified, named branch mechanics will be ignored and only
1781 If -t/--topo is specified, named branch mechanics will be ignored and only
1782 changesets without children will be shown.
1782 changesets without children will be shown.
1783
1783
1784 Returns 0 if matching heads are found, 1 if not.
1784 Returns 0 if matching heads are found, 1 if not.
1785 """
1785 """
1786
1786
1787 if opts.get('rev'):
1787 if opts.get('rev'):
1788 start = repo.lookup(opts['rev'])
1788 start = repo.lookup(opts['rev'])
1789 else:
1789 else:
1790 start = None
1790 start = None
1791
1791
1792 if opts.get('topo'):
1792 if opts.get('topo'):
1793 heads = [repo[h] for h in repo.heads(start)]
1793 heads = [repo[h] for h in repo.heads(start)]
1794 else:
1794 else:
1795 heads = []
1795 heads = []
1796 for b, ls in repo.branchmap().iteritems():
1796 for b, ls in repo.branchmap().iteritems():
1797 if start is None:
1797 if start is None:
1798 heads += [repo[h] for h in ls]
1798 heads += [repo[h] for h in ls]
1799 continue
1799 continue
1800 startrev = repo.changelog.rev(start)
1800 startrev = repo.changelog.rev(start)
1801 descendants = set(repo.changelog.descendants(startrev))
1801 descendants = set(repo.changelog.descendants(startrev))
1802 descendants.add(startrev)
1802 descendants.add(startrev)
1803 rev = repo.changelog.rev
1803 rev = repo.changelog.rev
1804 heads += [repo[h] for h in ls if rev(h) in descendants]
1804 heads += [repo[h] for h in ls if rev(h) in descendants]
1805
1805
1806 if branchrevs:
1806 if branchrevs:
1807 decode, encode = encoding.fromlocal, encoding.tolocal
1807 decode, encode = encoding.fromlocal, encoding.tolocal
1808 branches = set(repo[decode(br)].branch() for br in branchrevs)
1808 branches = set(repo[decode(br)].branch() for br in branchrevs)
1809 heads = [h for h in heads if h.branch() in branches]
1809 heads = [h for h in heads if h.branch() in branches]
1810
1810
1811 if not opts.get('closed'):
1811 if not opts.get('closed'):
1812 heads = [h for h in heads if not h.extra().get('close')]
1812 heads = [h for h in heads if not h.extra().get('close')]
1813
1813
1814 if opts.get('active') and branchrevs:
1814 if opts.get('active') and branchrevs:
1815 dagheads = repo.heads(start)
1815 dagheads = repo.heads(start)
1816 heads = [h for h in heads if h.node() in dagheads]
1816 heads = [h for h in heads if h.node() in dagheads]
1817
1817
1818 if branchrevs:
1818 if branchrevs:
1819 haveheads = set(h.branch() for h in heads)
1819 haveheads = set(h.branch() for h in heads)
1820 if branches - haveheads:
1820 if branches - haveheads:
1821 headless = ', '.join(encode(b) for b in branches - haveheads)
1821 headless = ', '.join(encode(b) for b in branches - haveheads)
1822 msg = _('no open branch heads found on branches %s')
1822 msg = _('no open branch heads found on branches %s')
1823 if opts.get('rev'):
1823 if opts.get('rev'):
1824 msg += _(' (started at %s)' % opts['rev'])
1824 msg += _(' (started at %s)' % opts['rev'])
1825 ui.warn((msg + '\n') % headless)
1825 ui.warn((msg + '\n') % headless)
1826
1826
1827 if not heads:
1827 if not heads:
1828 return 1
1828 return 1
1829
1829
1830 heads = sorted(heads, key=lambda x: -x.rev())
1830 heads = sorted(heads, key=lambda x: -x.rev())
1831 displayer = cmdutil.show_changeset(ui, repo, opts)
1831 displayer = cmdutil.show_changeset(ui, repo, opts)
1832 for ctx in heads:
1832 for ctx in heads:
1833 displayer.show(ctx)
1833 displayer.show(ctx)
1834 displayer.close()
1834 displayer.close()
1835
1835
1836 def help_(ui, name=None, with_version=False, unknowncmd=False):
1836 def help_(ui, name=None, with_version=False, unknowncmd=False):
1837 """show help for a given topic or a help overview
1837 """show help for a given topic or a help overview
1838
1838
1839 With no arguments, print a list of commands with short help messages.
1839 With no arguments, print a list of commands with short help messages.
1840
1840
1841 Given a topic, extension, or command name, print help for that
1841 Given a topic, extension, or command name, print help for that
1842 topic.
1842 topic.
1843
1843
1844 Returns 0 if successful.
1844 Returns 0 if successful.
1845 """
1845 """
1846 option_lists = []
1846 option_lists = []
1847 textwidth = util.termwidth() - 2
1847 textwidth = util.termwidth() - 2
1848
1848
1849 def addglobalopts(aliases):
1849 def addglobalopts(aliases):
1850 if ui.verbose:
1850 if ui.verbose:
1851 option_lists.append((_("global options:"), globalopts))
1851 option_lists.append((_("global options:"), globalopts))
1852 if name == 'shortlist':
1852 if name == 'shortlist':
1853 option_lists.append((_('use "hg help" for the full list '
1853 option_lists.append((_('use "hg help" for the full list '
1854 'of commands'), ()))
1854 'of commands'), ()))
1855 else:
1855 else:
1856 if name == 'shortlist':
1856 if name == 'shortlist':
1857 msg = _('use "hg help" for the full list of commands '
1857 msg = _('use "hg help" for the full list of commands '
1858 'or "hg -v" for details')
1858 'or "hg -v" for details')
1859 elif aliases:
1859 elif aliases:
1860 msg = _('use "hg -v help%s" to show aliases and '
1860 msg = _('use "hg -v help%s" to show aliases and '
1861 'global options') % (name and " " + name or "")
1861 'global options') % (name and " " + name or "")
1862 else:
1862 else:
1863 msg = _('use "hg -v help %s" to show global options') % name
1863 msg = _('use "hg -v help %s" to show global options') % name
1864 option_lists.append((msg, ()))
1864 option_lists.append((msg, ()))
1865
1865
1866 def helpcmd(name):
1866 def helpcmd(name):
1867 if with_version:
1867 if with_version:
1868 version_(ui)
1868 version_(ui)
1869 ui.write('\n')
1869 ui.write('\n')
1870
1870
1871 try:
1871 try:
1872 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1872 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1873 except error.AmbiguousCommand, inst:
1873 except error.AmbiguousCommand, inst:
1874 # py3k fix: except vars can't be used outside the scope of the
1874 # py3k fix: except vars can't be used outside the scope of the
1875 # except block, nor can be used inside a lambda. python issue4617
1875 # except block, nor can be used inside a lambda. python issue4617
1876 prefix = inst.args[0]
1876 prefix = inst.args[0]
1877 select = lambda c: c.lstrip('^').startswith(prefix)
1877 select = lambda c: c.lstrip('^').startswith(prefix)
1878 helplist(_('list of commands:\n\n'), select)
1878 helplist(_('list of commands:\n\n'), select)
1879 return
1879 return
1880
1880
1881 # check if it's an invalid alias and display its error if it is
1881 # check if it's an invalid alias and display its error if it is
1882 if getattr(entry[0], 'badalias', False):
1882 if getattr(entry[0], 'badalias', False):
1883 if not unknowncmd:
1883 if not unknowncmd:
1884 entry[0](ui)
1884 entry[0](ui)
1885 return
1885 return
1886
1886
1887 # synopsis
1887 # synopsis
1888 if len(entry) > 2:
1888 if len(entry) > 2:
1889 if entry[2].startswith('hg'):
1889 if entry[2].startswith('hg'):
1890 ui.write("%s\n" % entry[2])
1890 ui.write("%s\n" % entry[2])
1891 else:
1891 else:
1892 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1892 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1893 else:
1893 else:
1894 ui.write('hg %s\n' % aliases[0])
1894 ui.write('hg %s\n' % aliases[0])
1895
1895
1896 # aliases
1896 # aliases
1897 if not ui.quiet and len(aliases) > 1:
1897 if not ui.quiet and len(aliases) > 1:
1898 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1898 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1899
1899
1900 # description
1900 # description
1901 doc = gettext(entry[0].__doc__)
1901 doc = gettext(entry[0].__doc__)
1902 if not doc:
1902 if not doc:
1903 doc = _("(no help text available)")
1903 doc = _("(no help text available)")
1904 if hasattr(entry[0], 'definition'): # aliased command
1904 if hasattr(entry[0], 'definition'): # aliased command
1905 if entry[0].definition.startswith('!'): # shell alias
1905 if entry[0].definition.startswith('!'): # shell alias
1906 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
1906 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
1907 else:
1907 else:
1908 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1908 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1909 if ui.quiet:
1909 if ui.quiet:
1910 doc = doc.splitlines()[0]
1910 doc = doc.splitlines()[0]
1911 keep = ui.verbose and ['verbose'] or []
1911 keep = ui.verbose and ['verbose'] or []
1912 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1912 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1913 ui.write("\n%s\n" % formatted)
1913 ui.write("\n%s\n" % formatted)
1914 if pruned:
1914 if pruned:
1915 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1915 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1916
1916
1917 if not ui.quiet:
1917 if not ui.quiet:
1918 # options
1918 # options
1919 if entry[1]:
1919 if entry[1]:
1920 option_lists.append((_("options:\n"), entry[1]))
1920 option_lists.append((_("options:\n"), entry[1]))
1921
1921
1922 addglobalopts(False)
1922 addglobalopts(False)
1923
1923
1924 def helplist(header, select=None):
1924 def helplist(header, select=None):
1925 h = {}
1925 h = {}
1926 cmds = {}
1926 cmds = {}
1927 for c, e in table.iteritems():
1927 for c, e in table.iteritems():
1928 f = c.split("|", 1)[0]
1928 f = c.split("|", 1)[0]
1929 if select and not select(f):
1929 if select and not select(f):
1930 continue
1930 continue
1931 if (not select and name != 'shortlist' and
1931 if (not select and name != 'shortlist' and
1932 e[0].__module__ != __name__):
1932 e[0].__module__ != __name__):
1933 continue
1933 continue
1934 if name == "shortlist" and not f.startswith("^"):
1934 if name == "shortlist" and not f.startswith("^"):
1935 continue
1935 continue
1936 f = f.lstrip("^")
1936 f = f.lstrip("^")
1937 if not ui.debugflag and f.startswith("debug"):
1937 if not ui.debugflag and f.startswith("debug"):
1938 continue
1938 continue
1939 doc = e[0].__doc__
1939 doc = e[0].__doc__
1940 if doc and 'DEPRECATED' in doc and not ui.verbose:
1940 if doc and 'DEPRECATED' in doc and not ui.verbose:
1941 continue
1941 continue
1942 doc = gettext(doc)
1942 doc = gettext(doc)
1943 if not doc:
1943 if not doc:
1944 doc = _("(no help text available)")
1944 doc = _("(no help text available)")
1945 h[f] = doc.splitlines()[0].rstrip()
1945 h[f] = doc.splitlines()[0].rstrip()
1946 cmds[f] = c.lstrip("^")
1946 cmds[f] = c.lstrip("^")
1947
1947
1948 if not h:
1948 if not h:
1949 ui.status(_('no commands defined\n'))
1949 ui.status(_('no commands defined\n'))
1950 return
1950 return
1951
1951
1952 ui.status(header)
1952 ui.status(header)
1953 fns = sorted(h)
1953 fns = sorted(h)
1954 m = max(map(len, fns))
1954 m = max(map(len, fns))
1955 for f in fns:
1955 for f in fns:
1956 if ui.verbose:
1956 if ui.verbose:
1957 commands = cmds[f].replace("|",", ")
1957 commands = cmds[f].replace("|",", ")
1958 ui.write(" %s:\n %s\n"%(commands, h[f]))
1958 ui.write(" %s:\n %s\n"%(commands, h[f]))
1959 else:
1959 else:
1960 ui.write('%s\n' % (util.wrap(h[f],
1960 ui.write('%s\n' % (util.wrap(h[f],
1961 initindent=' %-*s ' % (m, f),
1961 initindent=' %-*s ' % (m, f),
1962 hangindent=' ' * (m + 4))))
1962 hangindent=' ' * (m + 4))))
1963
1963
1964 if not ui.quiet:
1964 if not ui.quiet:
1965 addglobalopts(True)
1965 addglobalopts(True)
1966
1966
1967 def helptopic(name):
1967 def helptopic(name):
1968 for names, header, doc in help.helptable:
1968 for names, header, doc in help.helptable:
1969 if name in names:
1969 if name in names:
1970 break
1970 break
1971 else:
1971 else:
1972 raise error.UnknownCommand(name)
1972 raise error.UnknownCommand(name)
1973
1973
1974 # description
1974 # description
1975 if not doc:
1975 if not doc:
1976 doc = _("(no help text available)")
1976 doc = _("(no help text available)")
1977 if hasattr(doc, '__call__'):
1977 if hasattr(doc, '__call__'):
1978 doc = doc()
1978 doc = doc()
1979
1979
1980 ui.write("%s\n\n" % header)
1980 ui.write("%s\n\n" % header)
1981 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1981 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1982
1982
1983 def helpext(name):
1983 def helpext(name):
1984 try:
1984 try:
1985 mod = extensions.find(name)
1985 mod = extensions.find(name)
1986 doc = gettext(mod.__doc__) or _('no help text available')
1986 doc = gettext(mod.__doc__) or _('no help text available')
1987 except KeyError:
1987 except KeyError:
1988 mod = None
1988 mod = None
1989 doc = extensions.disabledext(name)
1989 doc = extensions.disabledext(name)
1990 if not doc:
1990 if not doc:
1991 raise error.UnknownCommand(name)
1991 raise error.UnknownCommand(name)
1992
1992
1993 if '\n' not in doc:
1993 if '\n' not in doc:
1994 head, tail = doc, ""
1994 head, tail = doc, ""
1995 else:
1995 else:
1996 head, tail = doc.split('\n', 1)
1996 head, tail = doc.split('\n', 1)
1997 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1997 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1998 if tail:
1998 if tail:
1999 ui.write(minirst.format(tail, textwidth))
1999 ui.write(minirst.format(tail, textwidth))
2000 ui.status('\n\n')
2000 ui.status('\n\n')
2001
2001
2002 if mod:
2002 if mod:
2003 try:
2003 try:
2004 ct = mod.cmdtable
2004 ct = mod.cmdtable
2005 except AttributeError:
2005 except AttributeError:
2006 ct = {}
2006 ct = {}
2007 modcmds = set([c.split('|', 1)[0] for c in ct])
2007 modcmds = set([c.split('|', 1)[0] for c in ct])
2008 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2008 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2009 else:
2009 else:
2010 ui.write(_('use "hg help extensions" for information on enabling '
2010 ui.write(_('use "hg help extensions" for information on enabling '
2011 'extensions\n'))
2011 'extensions\n'))
2012
2012
2013 def helpextcmd(name):
2013 def helpextcmd(name):
2014 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
2014 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
2015 doc = gettext(mod.__doc__).splitlines()[0]
2015 doc = gettext(mod.__doc__).splitlines()[0]
2016
2016
2017 msg = help.listexts(_("'%s' is provided by the following "
2017 msg = help.listexts(_("'%s' is provided by the following "
2018 "extension:") % cmd, {ext: doc}, len(ext),
2018 "extension:") % cmd, {ext: doc}, len(ext),
2019 indent=4)
2019 indent=4)
2020 ui.write(minirst.format(msg, textwidth))
2020 ui.write(minirst.format(msg, textwidth))
2021 ui.write('\n\n')
2021 ui.write('\n\n')
2022 ui.write(_('use "hg help extensions" for information on enabling '
2022 ui.write(_('use "hg help extensions" for information on enabling '
2023 'extensions\n'))
2023 'extensions\n'))
2024
2024
2025 if name and name != 'shortlist':
2025 if name and name != 'shortlist':
2026 i = None
2026 i = None
2027 if unknowncmd:
2027 if unknowncmd:
2028 queries = (helpextcmd,)
2028 queries = (helpextcmd,)
2029 else:
2029 else:
2030 queries = (helptopic, helpcmd, helpext, helpextcmd)
2030 queries = (helptopic, helpcmd, helpext, helpextcmd)
2031 for f in queries:
2031 for f in queries:
2032 try:
2032 try:
2033 f(name)
2033 f(name)
2034 i = None
2034 i = None
2035 break
2035 break
2036 except error.UnknownCommand, inst:
2036 except error.UnknownCommand, inst:
2037 i = inst
2037 i = inst
2038 if i:
2038 if i:
2039 raise i
2039 raise i
2040
2040
2041 else:
2041 else:
2042 # program name
2042 # program name
2043 if ui.verbose or with_version:
2043 if ui.verbose or with_version:
2044 version_(ui)
2044 version_(ui)
2045 else:
2045 else:
2046 ui.status(_("Mercurial Distributed SCM\n"))
2046 ui.status(_("Mercurial Distributed SCM\n"))
2047 ui.status('\n')
2047 ui.status('\n')
2048
2048
2049 # list of commands
2049 # list of commands
2050 if name == "shortlist":
2050 if name == "shortlist":
2051 header = _('basic commands:\n\n')
2051 header = _('basic commands:\n\n')
2052 else:
2052 else:
2053 header = _('list of commands:\n\n')
2053 header = _('list of commands:\n\n')
2054
2054
2055 helplist(header)
2055 helplist(header)
2056 if name != 'shortlist':
2056 if name != 'shortlist':
2057 exts, maxlength = extensions.enabled()
2057 exts, maxlength = extensions.enabled()
2058 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2058 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2059 if text:
2059 if text:
2060 ui.write("\n%s\n" % minirst.format(text, textwidth))
2060 ui.write("\n%s\n" % minirst.format(text, textwidth))
2061
2061
2062 # list all option lists
2062 # list all option lists
2063 opt_output = []
2063 opt_output = []
2064 multioccur = False
2064 multioccur = False
2065 for title, options in option_lists:
2065 for title, options in option_lists:
2066 opt_output.append(("\n%s" % title, None))
2066 opt_output.append(("\n%s" % title, None))
2067 for option in options:
2067 for option in options:
2068 if len(option) == 5:
2068 if len(option) == 5:
2069 shortopt, longopt, default, desc, optlabel = option
2069 shortopt, longopt, default, desc, optlabel = option
2070 else:
2070 else:
2071 shortopt, longopt, default, desc = option
2071 shortopt, longopt, default, desc = option
2072 optlabel = _("VALUE") # default label
2072 optlabel = _("VALUE") # default label
2073
2073
2074 if _("DEPRECATED") in desc and not ui.verbose:
2074 if _("DEPRECATED") in desc and not ui.verbose:
2075 continue
2075 continue
2076 if isinstance(default, list):
2076 if isinstance(default, list):
2077 numqualifier = " %s [+]" % optlabel
2077 numqualifier = " %s [+]" % optlabel
2078 multioccur = True
2078 multioccur = True
2079 elif (default is not None) and not isinstance(default, bool):
2079 elif (default is not None) and not isinstance(default, bool):
2080 numqualifier = " %s" % optlabel
2080 numqualifier = " %s" % optlabel
2081 else:
2081 else:
2082 numqualifier = ""
2082 numqualifier = ""
2083 opt_output.append(("%2s%s" %
2083 opt_output.append(("%2s%s" %
2084 (shortopt and "-%s" % shortopt,
2084 (shortopt and "-%s" % shortopt,
2085 longopt and " --%s%s" %
2085 longopt and " --%s%s" %
2086 (longopt, numqualifier)),
2086 (longopt, numqualifier)),
2087 "%s%s" % (desc,
2087 "%s%s" % (desc,
2088 default
2088 default
2089 and _(" (default: %s)") % default
2089 and _(" (default: %s)") % default
2090 or "")))
2090 or "")))
2091 if multioccur:
2091 if multioccur:
2092 msg = _("\n[+] marked option can be specified multiple times")
2092 msg = _("\n[+] marked option can be specified multiple times")
2093 if ui.verbose and name != 'shortlist':
2093 if ui.verbose and name != 'shortlist':
2094 opt_output.append((msg, None))
2094 opt_output.append((msg, None))
2095 else:
2095 else:
2096 opt_output.insert(-1, (msg, None))
2096 opt_output.insert(-1, (msg, None))
2097
2097
2098 if not name:
2098 if not name:
2099 ui.write(_("\nadditional help topics:\n\n"))
2099 ui.write(_("\nadditional help topics:\n\n"))
2100 topics = []
2100 topics = []
2101 for names, header, doc in help.helptable:
2101 for names, header, doc in help.helptable:
2102 topics.append((sorted(names, key=len, reverse=True)[0], header))
2102 topics.append((sorted(names, key=len, reverse=True)[0], header))
2103 topics_len = max([len(s[0]) for s in topics])
2103 topics_len = max([len(s[0]) for s in topics])
2104 for t, desc in topics:
2104 for t, desc in topics:
2105 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2105 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2106
2106
2107 if opt_output:
2107 if opt_output:
2108 colwidth = encoding.colwidth
2108 colwidth = encoding.colwidth
2109 # normalize: (opt or message, desc or None, width of opt)
2109 # normalize: (opt or message, desc or None, width of opt)
2110 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2110 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2111 for opt, desc in opt_output]
2111 for opt, desc in opt_output]
2112 hanging = max([e[2] for e in entries])
2112 hanging = max([e[2] for e in entries])
2113 for opt, desc, width in entries:
2113 for opt, desc, width in entries:
2114 if desc:
2114 if desc:
2115 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2115 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2116 hangindent = ' ' * (hanging + 3)
2116 hangindent = ' ' * (hanging + 3)
2117 ui.write('%s\n' % (util.wrap(desc,
2117 ui.write('%s\n' % (util.wrap(desc,
2118 initindent=initindent,
2118 initindent=initindent,
2119 hangindent=hangindent)))
2119 hangindent=hangindent)))
2120 else:
2120 else:
2121 ui.write("%s\n" % opt)
2121 ui.write("%s\n" % opt)
2122
2122
2123 def identify(ui, repo, source=None,
2123 def identify(ui, repo, source=None,
2124 rev=None, num=None, id=None, branch=None, tags=None):
2124 rev=None, num=None, id=None, branch=None, tags=None):
2125 """identify the working copy or specified revision
2125 """identify the working copy or specified revision
2126
2126
2127 With no revision, print a summary of the current state of the
2127 With no revision, print a summary of the current state of the
2128 repository.
2128 repository.
2129
2129
2130 Specifying a path to a repository root or Mercurial bundle will
2130 Specifying a path to a repository root or Mercurial bundle will
2131 cause lookup to operate on that repository/bundle.
2131 cause lookup to operate on that repository/bundle.
2132
2132
2133 This summary identifies the repository state using one or two
2133 This summary identifies the repository state using one or two
2134 parent hash identifiers, followed by a "+" if there are
2134 parent hash identifiers, followed by a "+" if there are
2135 uncommitted changes in the working directory, a list of tags for
2135 uncommitted changes in the working directory, a list of tags for
2136 this revision and a branch name for non-default branches.
2136 this revision and a branch name for non-default branches.
2137
2137
2138 Returns 0 if successful.
2138 Returns 0 if successful.
2139 """
2139 """
2140
2140
2141 if not repo and not source:
2141 if not repo and not source:
2142 raise util.Abort(_("there is no Mercurial repository here "
2142 raise util.Abort(_("there is no Mercurial repository here "
2143 "(.hg not found)"))
2143 "(.hg not found)"))
2144
2144
2145 hexfunc = ui.debugflag and hex or short
2145 hexfunc = ui.debugflag and hex or short
2146 default = not (num or id or branch or tags)
2146 default = not (num or id or branch or tags)
2147 output = []
2147 output = []
2148
2148
2149 revs = []
2149 revs = []
2150 if source:
2150 if source:
2151 source, branches = hg.parseurl(ui.expandpath(source))
2151 source, branches = hg.parseurl(ui.expandpath(source))
2152 repo = hg.repository(ui, source)
2152 repo = hg.repository(ui, source)
2153 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2153 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2154
2154
2155 if not repo.local():
2155 if not repo.local():
2156 if not rev and revs:
2156 if not rev and revs:
2157 rev = revs[0]
2157 rev = revs[0]
2158 if not rev:
2158 if not rev:
2159 rev = "tip"
2159 rev = "tip"
2160 if num or branch or tags:
2160 if num or branch or tags:
2161 raise util.Abort(
2161 raise util.Abort(
2162 "can't query remote revision number, branch, or tags")
2162 "can't query remote revision number, branch, or tags")
2163 output = [hexfunc(repo.lookup(rev))]
2163 output = [hexfunc(repo.lookup(rev))]
2164 elif not rev:
2164 elif not rev:
2165 ctx = repo[None]
2165 ctx = repo[None]
2166 parents = ctx.parents()
2166 parents = ctx.parents()
2167 changed = False
2167 changed = False
2168 if default or id or num:
2168 if default or id or num:
2169 changed = util.any(repo.status())
2169 changed = util.any(repo.status())
2170 if default or id:
2170 if default or id:
2171 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2171 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2172 (changed) and "+" or "")]
2172 (changed) and "+" or "")]
2173 if num:
2173 if num:
2174 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2174 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2175 (changed) and "+" or ""))
2175 (changed) and "+" or ""))
2176 else:
2176 else:
2177 ctx = repo[rev]
2177 ctx = repo[rev]
2178 if default or id:
2178 if default or id:
2179 output = [hexfunc(ctx.node())]
2179 output = [hexfunc(ctx.node())]
2180 if num:
2180 if num:
2181 output.append(str(ctx.rev()))
2181 output.append(str(ctx.rev()))
2182
2182
2183 if repo.local() and default and not ui.quiet:
2183 if repo.local() and default and not ui.quiet:
2184 b = encoding.tolocal(ctx.branch())
2184 b = encoding.tolocal(ctx.branch())
2185 if b != 'default':
2185 if b != 'default':
2186 output.append("(%s)" % b)
2186 output.append("(%s)" % b)
2187
2187
2188 # multiple tags for a single parent separated by '/'
2188 # multiple tags for a single parent separated by '/'
2189 t = "/".join(ctx.tags())
2189 t = "/".join(ctx.tags())
2190 if t:
2190 if t:
2191 output.append(t)
2191 output.append(t)
2192
2192
2193 if branch:
2193 if branch:
2194 output.append(encoding.tolocal(ctx.branch()))
2194 output.append(encoding.tolocal(ctx.branch()))
2195
2195
2196 if tags:
2196 if tags:
2197 output.extend(ctx.tags())
2197 output.extend(ctx.tags())
2198
2198
2199 ui.write("%s\n" % ' '.join(output))
2199 ui.write("%s\n" % ' '.join(output))
2200
2200
2201 def import_(ui, repo, patch1, *patches, **opts):
2201 def import_(ui, repo, patch1, *patches, **opts):
2202 """import an ordered set of patches
2202 """import an ordered set of patches
2203
2203
2204 Import a list of patches and commit them individually (unless
2204 Import a list of patches and commit them individually (unless
2205 --no-commit is specified).
2205 --no-commit is specified).
2206
2206
2207 If there are outstanding changes in the working directory, import
2207 If there are outstanding changes in the working directory, import
2208 will abort unless given the -f/--force flag.
2208 will abort unless given the -f/--force flag.
2209
2209
2210 You can import a patch straight from a mail message. Even patches
2210 You can import a patch straight from a mail message. Even patches
2211 as attachments work (to use the body part, it must have type
2211 as attachments work (to use the body part, it must have type
2212 text/plain or text/x-patch). From and Subject headers of email
2212 text/plain or text/x-patch). From and Subject headers of email
2213 message are used as default committer and commit message. All
2213 message are used as default committer and commit message. All
2214 text/plain body parts before first diff are added to commit
2214 text/plain body parts before first diff are added to commit
2215 message.
2215 message.
2216
2216
2217 If the imported patch was generated by :hg:`export`, user and
2217 If the imported patch was generated by :hg:`export`, user and
2218 description from patch override values from message headers and
2218 description from patch override values from message headers and
2219 body. Values given on command line with -m/--message and -u/--user
2219 body. Values given on command line with -m/--message and -u/--user
2220 override these.
2220 override these.
2221
2221
2222 If --exact is specified, import will set the working directory to
2222 If --exact is specified, import will set the working directory to
2223 the parent of each patch before applying it, and will abort if the
2223 the parent of each patch before applying it, and will abort if the
2224 resulting changeset has a different ID than the one recorded in
2224 resulting changeset has a different ID than the one recorded in
2225 the patch. This may happen due to character set problems or other
2225 the patch. This may happen due to character set problems or other
2226 deficiencies in the text patch format.
2226 deficiencies in the text patch format.
2227
2227
2228 With -s/--similarity, hg will attempt to discover renames and
2228 With -s/--similarity, hg will attempt to discover renames and
2229 copies in the patch in the same way as 'addremove'.
2229 copies in the patch in the same way as 'addremove'.
2230
2230
2231 To read a patch from standard input, use "-" as the patch name. If
2231 To read a patch from standard input, use "-" as the patch name. If
2232 a URL is specified, the patch will be downloaded from it.
2232 a URL is specified, the patch will be downloaded from it.
2233 See :hg:`help dates` for a list of formats valid for -d/--date.
2233 See :hg:`help dates` for a list of formats valid for -d/--date.
2234
2234
2235 Returns 0 on success.
2235 Returns 0 on success.
2236 """
2236 """
2237 patches = (patch1,) + patches
2237 patches = (patch1,) + patches
2238
2238
2239 date = opts.get('date')
2239 date = opts.get('date')
2240 if date:
2240 if date:
2241 opts['date'] = util.parsedate(date)
2241 opts['date'] = util.parsedate(date)
2242
2242
2243 try:
2243 try:
2244 sim = float(opts.get('similarity') or 0)
2244 sim = float(opts.get('similarity') or 0)
2245 except ValueError:
2245 except ValueError:
2246 raise util.Abort(_('similarity must be a number'))
2246 raise util.Abort(_('similarity must be a number'))
2247 if sim < 0 or sim > 100:
2247 if sim < 0 or sim > 100:
2248 raise util.Abort(_('similarity must be between 0 and 100'))
2248 raise util.Abort(_('similarity must be between 0 and 100'))
2249
2249
2250 if opts.get('exact') or not opts.get('force'):
2250 if opts.get('exact') or not opts.get('force'):
2251 cmdutil.bail_if_changed(repo)
2251 cmdutil.bail_if_changed(repo)
2252
2252
2253 d = opts["base"]
2253 d = opts["base"]
2254 strip = opts["strip"]
2254 strip = opts["strip"]
2255 wlock = lock = None
2255 wlock = lock = None
2256
2256
2257 def tryone(ui, hunk):
2257 def tryone(ui, hunk):
2258 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2258 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2259 patch.extract(ui, hunk)
2259 patch.extract(ui, hunk)
2260
2260
2261 if not tmpname:
2261 if not tmpname:
2262 return None
2262 return None
2263 commitid = _('to working directory')
2263 commitid = _('to working directory')
2264
2264
2265 try:
2265 try:
2266 cmdline_message = cmdutil.logmessage(opts)
2266 cmdline_message = cmdutil.logmessage(opts)
2267 if cmdline_message:
2267 if cmdline_message:
2268 # pickup the cmdline msg
2268 # pickup the cmdline msg
2269 message = cmdline_message
2269 message = cmdline_message
2270 elif message:
2270 elif message:
2271 # pickup the patch msg
2271 # pickup the patch msg
2272 message = message.strip()
2272 message = message.strip()
2273 else:
2273 else:
2274 # launch the editor
2274 # launch the editor
2275 message = None
2275 message = None
2276 ui.debug('message:\n%s\n' % message)
2276 ui.debug('message:\n%s\n' % message)
2277
2277
2278 wp = repo.parents()
2278 wp = repo.parents()
2279 if opts.get('exact'):
2279 if opts.get('exact'):
2280 if not nodeid or not p1:
2280 if not nodeid or not p1:
2281 raise util.Abort(_('not a Mercurial patch'))
2281 raise util.Abort(_('not a Mercurial patch'))
2282 p1 = repo.lookup(p1)
2282 p1 = repo.lookup(p1)
2283 p2 = repo.lookup(p2 or hex(nullid))
2283 p2 = repo.lookup(p2 or hex(nullid))
2284
2284
2285 if p1 != wp[0].node():
2285 if p1 != wp[0].node():
2286 hg.clean(repo, p1)
2286 hg.clean(repo, p1)
2287 repo.dirstate.setparents(p1, p2)
2287 repo.dirstate.setparents(p1, p2)
2288 elif p2:
2288 elif p2:
2289 try:
2289 try:
2290 p1 = repo.lookup(p1)
2290 p1 = repo.lookup(p1)
2291 p2 = repo.lookup(p2)
2291 p2 = repo.lookup(p2)
2292 if p1 == wp[0].node():
2292 if p1 == wp[0].node():
2293 repo.dirstate.setparents(p1, p2)
2293 repo.dirstate.setparents(p1, p2)
2294 except error.RepoError:
2294 except error.RepoError:
2295 pass
2295 pass
2296 if opts.get('exact') or opts.get('import_branch'):
2296 if opts.get('exact') or opts.get('import_branch'):
2297 repo.dirstate.setbranch(branch or 'default')
2297 repo.dirstate.setbranch(branch or 'default')
2298
2298
2299 files = {}
2299 files = {}
2300 try:
2300 try:
2301 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2301 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2302 files=files, eolmode=None)
2302 files=files, eolmode=None)
2303 finally:
2303 finally:
2304 files = patch.updatedir(ui, repo, files,
2304 files = cmdutil.updatedir(ui, repo, files,
2305 similarity=sim / 100.0)
2305 similarity=sim / 100.0)
2306 if not opts.get('no_commit'):
2306 if not opts.get('no_commit'):
2307 if opts.get('exact'):
2307 if opts.get('exact'):
2308 m = None
2308 m = None
2309 else:
2309 else:
2310 m = cmdutil.matchfiles(repo, files or [])
2310 m = cmdutil.matchfiles(repo, files or [])
2311 n = repo.commit(message, opts.get('user') or user,
2311 n = repo.commit(message, opts.get('user') or user,
2312 opts.get('date') or date, match=m,
2312 opts.get('date') or date, match=m,
2313 editor=cmdutil.commiteditor)
2313 editor=cmdutil.commiteditor)
2314 if opts.get('exact'):
2314 if opts.get('exact'):
2315 if hex(n) != nodeid:
2315 if hex(n) != nodeid:
2316 repo.rollback()
2316 repo.rollback()
2317 raise util.Abort(_('patch is damaged'
2317 raise util.Abort(_('patch is damaged'
2318 ' or loses information'))
2318 ' or loses information'))
2319 # Force a dirstate write so that the next transaction
2319 # Force a dirstate write so that the next transaction
2320 # backups an up-do-date file.
2320 # backups an up-do-date file.
2321 repo.dirstate.write()
2321 repo.dirstate.write()
2322 if n:
2322 if n:
2323 commitid = short(n)
2323 commitid = short(n)
2324
2324
2325 return commitid
2325 return commitid
2326 finally:
2326 finally:
2327 os.unlink(tmpname)
2327 os.unlink(tmpname)
2328
2328
2329 try:
2329 try:
2330 wlock = repo.wlock()
2330 wlock = repo.wlock()
2331 lock = repo.lock()
2331 lock = repo.lock()
2332 lastcommit = None
2332 lastcommit = None
2333 for p in patches:
2333 for p in patches:
2334 pf = os.path.join(d, p)
2334 pf = os.path.join(d, p)
2335
2335
2336 if pf == '-':
2336 if pf == '-':
2337 ui.status(_("applying patch from stdin\n"))
2337 ui.status(_("applying patch from stdin\n"))
2338 pf = sys.stdin
2338 pf = sys.stdin
2339 else:
2339 else:
2340 ui.status(_("applying %s\n") % p)
2340 ui.status(_("applying %s\n") % p)
2341 pf = url.open(ui, pf)
2341 pf = url.open(ui, pf)
2342
2342
2343 haspatch = False
2343 haspatch = False
2344 for hunk in patch.split(pf):
2344 for hunk in patch.split(pf):
2345 commitid = tryone(ui, hunk)
2345 commitid = tryone(ui, hunk)
2346 if commitid:
2346 if commitid:
2347 haspatch = True
2347 haspatch = True
2348 if lastcommit:
2348 if lastcommit:
2349 ui.status(_('applied %s\n') % lastcommit)
2349 ui.status(_('applied %s\n') % lastcommit)
2350 lastcommit = commitid
2350 lastcommit = commitid
2351
2351
2352 if not haspatch:
2352 if not haspatch:
2353 raise util.Abort(_('no diffs found'))
2353 raise util.Abort(_('no diffs found'))
2354
2354
2355 finally:
2355 finally:
2356 release(lock, wlock)
2356 release(lock, wlock)
2357
2357
2358 def incoming(ui, repo, source="default", **opts):
2358 def incoming(ui, repo, source="default", **opts):
2359 """show new changesets found in source
2359 """show new changesets found in source
2360
2360
2361 Show new changesets found in the specified path/URL or the default
2361 Show new changesets found in the specified path/URL or the default
2362 pull location. These are the changesets that would have been pulled
2362 pull location. These are the changesets that would have been pulled
2363 if a pull at the time you issued this command.
2363 if a pull at the time you issued this command.
2364
2364
2365 For remote repository, using --bundle avoids downloading the
2365 For remote repository, using --bundle avoids downloading the
2366 changesets twice if the incoming is followed by a pull.
2366 changesets twice if the incoming is followed by a pull.
2367
2367
2368 See pull for valid source format details.
2368 See pull for valid source format details.
2369
2369
2370 Returns 0 if there are incoming changes, 1 otherwise.
2370 Returns 0 if there are incoming changes, 1 otherwise.
2371 """
2371 """
2372 limit = cmdutil.loglimit(opts)
2372 limit = cmdutil.loglimit(opts)
2373 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2373 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2374 other = hg.repository(hg.remoteui(repo, opts), source)
2374 other = hg.repository(hg.remoteui(repo, opts), source)
2375 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2375 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2376 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2376 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2377 if revs:
2377 if revs:
2378 revs = [other.lookup(rev) for rev in revs]
2378 revs = [other.lookup(rev) for rev in revs]
2379
2379
2380 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2380 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2381 force=opts.get('force'))
2381 force=opts.get('force'))
2382 common, incoming, rheads = tmp
2382 common, incoming, rheads = tmp
2383 if not incoming:
2383 if not incoming:
2384 try:
2384 try:
2385 os.unlink(opts["bundle"])
2385 os.unlink(opts["bundle"])
2386 except:
2386 except:
2387 pass
2387 pass
2388 ui.status(_("no changes found\n"))
2388 ui.status(_("no changes found\n"))
2389 return 1
2389 return 1
2390
2390
2391 cleanup = None
2391 cleanup = None
2392 try:
2392 try:
2393 fname = opts["bundle"]
2393 fname = opts["bundle"]
2394 if fname or not other.local():
2394 if fname or not other.local():
2395 # create a bundle (uncompressed if other repo is not local)
2395 # create a bundle (uncompressed if other repo is not local)
2396
2396
2397 if revs is None and other.capable('changegroupsubset'):
2397 if revs is None and other.capable('changegroupsubset'):
2398 revs = rheads
2398 revs = rheads
2399
2399
2400 if revs is None:
2400 if revs is None:
2401 cg = other.changegroup(incoming, "incoming")
2401 cg = other.changegroup(incoming, "incoming")
2402 else:
2402 else:
2403 cg = other.changegroupsubset(incoming, revs, 'incoming')
2403 cg = other.changegroupsubset(incoming, revs, 'incoming')
2404 bundletype = other.local() and "HG10BZ" or "HG10UN"
2404 bundletype = other.local() and "HG10BZ" or "HG10UN"
2405 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2405 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2406 # keep written bundle?
2406 # keep written bundle?
2407 if opts["bundle"]:
2407 if opts["bundle"]:
2408 cleanup = None
2408 cleanup = None
2409 if not other.local():
2409 if not other.local():
2410 # use the created uncompressed bundlerepo
2410 # use the created uncompressed bundlerepo
2411 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2411 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2412
2412
2413 o = other.changelog.nodesbetween(incoming, revs)[0]
2413 o = other.changelog.nodesbetween(incoming, revs)[0]
2414 if opts.get('newest_first'):
2414 if opts.get('newest_first'):
2415 o.reverse()
2415 o.reverse()
2416 displayer = cmdutil.show_changeset(ui, other, opts)
2416 displayer = cmdutil.show_changeset(ui, other, opts)
2417 count = 0
2417 count = 0
2418 for n in o:
2418 for n in o:
2419 if limit is not None and count >= limit:
2419 if limit is not None and count >= limit:
2420 break
2420 break
2421 parents = [p for p in other.changelog.parents(n) if p != nullid]
2421 parents = [p for p in other.changelog.parents(n) if p != nullid]
2422 if opts.get('no_merges') and len(parents) == 2:
2422 if opts.get('no_merges') and len(parents) == 2:
2423 continue
2423 continue
2424 count += 1
2424 count += 1
2425 displayer.show(other[n])
2425 displayer.show(other[n])
2426 displayer.close()
2426 displayer.close()
2427 finally:
2427 finally:
2428 if hasattr(other, 'close'):
2428 if hasattr(other, 'close'):
2429 other.close()
2429 other.close()
2430 if cleanup:
2430 if cleanup:
2431 os.unlink(cleanup)
2431 os.unlink(cleanup)
2432
2432
2433 def init(ui, dest=".", **opts):
2433 def init(ui, dest=".", **opts):
2434 """create a new repository in the given directory
2434 """create a new repository in the given directory
2435
2435
2436 Initialize a new repository in the given directory. If the given
2436 Initialize a new repository in the given directory. If the given
2437 directory does not exist, it will be created.
2437 directory does not exist, it will be created.
2438
2438
2439 If no directory is given, the current directory is used.
2439 If no directory is given, the current directory is used.
2440
2440
2441 It is possible to specify an ``ssh://`` URL as the destination.
2441 It is possible to specify an ``ssh://`` URL as the destination.
2442 See :hg:`help urls` for more information.
2442 See :hg:`help urls` for more information.
2443
2443
2444 Returns 0 on success.
2444 Returns 0 on success.
2445 """
2445 """
2446 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2446 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2447
2447
2448 def locate(ui, repo, *pats, **opts):
2448 def locate(ui, repo, *pats, **opts):
2449 """locate files matching specific patterns
2449 """locate files matching specific patterns
2450
2450
2451 Print files under Mercurial control in the working directory whose
2451 Print files under Mercurial control in the working directory whose
2452 names match the given patterns.
2452 names match the given patterns.
2453
2453
2454 By default, this command searches all directories in the working
2454 By default, this command searches all directories in the working
2455 directory. To search just the current directory and its
2455 directory. To search just the current directory and its
2456 subdirectories, use "--include .".
2456 subdirectories, use "--include .".
2457
2457
2458 If no patterns are given to match, this command prints the names
2458 If no patterns are given to match, this command prints the names
2459 of all files under Mercurial control in the working directory.
2459 of all files under Mercurial control in the working directory.
2460
2460
2461 If you want to feed the output of this command into the "xargs"
2461 If you want to feed the output of this command into the "xargs"
2462 command, use the -0 option to both this command and "xargs". This
2462 command, use the -0 option to both this command and "xargs". This
2463 will avoid the problem of "xargs" treating single filenames that
2463 will avoid the problem of "xargs" treating single filenames that
2464 contain whitespace as multiple filenames.
2464 contain whitespace as multiple filenames.
2465
2465
2466 Returns 0 if a match is found, 1 otherwise.
2466 Returns 0 if a match is found, 1 otherwise.
2467 """
2467 """
2468 end = opts.get('print0') and '\0' or '\n'
2468 end = opts.get('print0') and '\0' or '\n'
2469 rev = opts.get('rev') or None
2469 rev = opts.get('rev') or None
2470
2470
2471 ret = 1
2471 ret = 1
2472 m = cmdutil.match(repo, pats, opts, default='relglob')
2472 m = cmdutil.match(repo, pats, opts, default='relglob')
2473 m.bad = lambda x, y: False
2473 m.bad = lambda x, y: False
2474 for abs in repo[rev].walk(m):
2474 for abs in repo[rev].walk(m):
2475 if not rev and abs not in repo.dirstate:
2475 if not rev and abs not in repo.dirstate:
2476 continue
2476 continue
2477 if opts.get('fullpath'):
2477 if opts.get('fullpath'):
2478 ui.write(repo.wjoin(abs), end)
2478 ui.write(repo.wjoin(abs), end)
2479 else:
2479 else:
2480 ui.write(((pats and m.rel(abs)) or abs), end)
2480 ui.write(((pats and m.rel(abs)) or abs), end)
2481 ret = 0
2481 ret = 0
2482
2482
2483 return ret
2483 return ret
2484
2484
2485 def log(ui, repo, *pats, **opts):
2485 def log(ui, repo, *pats, **opts):
2486 """show revision history of entire repository or files
2486 """show revision history of entire repository or files
2487
2487
2488 Print the revision history of the specified files or the entire
2488 Print the revision history of the specified files or the entire
2489 project.
2489 project.
2490
2490
2491 File history is shown without following rename or copy history of
2491 File history is shown without following rename or copy history of
2492 files. Use -f/--follow with a filename to follow history across
2492 files. Use -f/--follow with a filename to follow history across
2493 renames and copies. --follow without a filename will only show
2493 renames and copies. --follow without a filename will only show
2494 ancestors or descendants of the starting revision. --follow-first
2494 ancestors or descendants of the starting revision. --follow-first
2495 only follows the first parent of merge revisions.
2495 only follows the first parent of merge revisions.
2496
2496
2497 If no revision range is specified, the default is tip:0 unless
2497 If no revision range is specified, the default is tip:0 unless
2498 --follow is set, in which case the working directory parent is
2498 --follow is set, in which case the working directory parent is
2499 used as the starting revision. You can specify a revision set for
2499 used as the starting revision. You can specify a revision set for
2500 log, see :hg:`help revsets` for more information.
2500 log, see :hg:`help revsets` for more information.
2501
2501
2502 See :hg:`help dates` for a list of formats valid for -d/--date.
2502 See :hg:`help dates` for a list of formats valid for -d/--date.
2503
2503
2504 By default this command prints revision number and changeset id,
2504 By default this command prints revision number and changeset id,
2505 tags, non-trivial parents, user, date and time, and a summary for
2505 tags, non-trivial parents, user, date and time, and a summary for
2506 each commit. When the -v/--verbose switch is used, the list of
2506 each commit. When the -v/--verbose switch is used, the list of
2507 changed files and full commit message are shown.
2507 changed files and full commit message are shown.
2508
2508
2509 NOTE: log -p/--patch may generate unexpected diff output for merge
2509 NOTE: log -p/--patch may generate unexpected diff output for merge
2510 changesets, as it will only compare the merge changeset against
2510 changesets, as it will only compare the merge changeset against
2511 its first parent. Also, only files different from BOTH parents
2511 its first parent. Also, only files different from BOTH parents
2512 will appear in files:.
2512 will appear in files:.
2513
2513
2514 Returns 0 on success.
2514 Returns 0 on success.
2515 """
2515 """
2516
2516
2517 matchfn = cmdutil.match(repo, pats, opts)
2517 matchfn = cmdutil.match(repo, pats, opts)
2518 limit = cmdutil.loglimit(opts)
2518 limit = cmdutil.loglimit(opts)
2519 count = 0
2519 count = 0
2520
2520
2521 endrev = None
2521 endrev = None
2522 if opts.get('copies') and opts.get('rev'):
2522 if opts.get('copies') and opts.get('rev'):
2523 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2523 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2524
2524
2525 df = False
2525 df = False
2526 if opts["date"]:
2526 if opts["date"]:
2527 df = util.matchdate(opts["date"])
2527 df = util.matchdate(opts["date"])
2528
2528
2529 branches = opts.get('branch', []) + opts.get('only_branch', [])
2529 branches = opts.get('branch', []) + opts.get('only_branch', [])
2530 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2530 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2531
2531
2532 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2532 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2533 def prep(ctx, fns):
2533 def prep(ctx, fns):
2534 rev = ctx.rev()
2534 rev = ctx.rev()
2535 parents = [p for p in repo.changelog.parentrevs(rev)
2535 parents = [p for p in repo.changelog.parentrevs(rev)
2536 if p != nullrev]
2536 if p != nullrev]
2537 if opts.get('no_merges') and len(parents) == 2:
2537 if opts.get('no_merges') and len(parents) == 2:
2538 return
2538 return
2539 if opts.get('only_merges') and len(parents) != 2:
2539 if opts.get('only_merges') and len(parents) != 2:
2540 return
2540 return
2541 if opts.get('branch') and ctx.branch() not in opts['branch']:
2541 if opts.get('branch') and ctx.branch() not in opts['branch']:
2542 return
2542 return
2543 if df and not df(ctx.date()[0]):
2543 if df and not df(ctx.date()[0]):
2544 return
2544 return
2545 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2545 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2546 return
2546 return
2547 if opts.get('keyword'):
2547 if opts.get('keyword'):
2548 for k in [kw.lower() for kw in opts['keyword']]:
2548 for k in [kw.lower() for kw in opts['keyword']]:
2549 if (k in ctx.user().lower() or
2549 if (k in ctx.user().lower() or
2550 k in ctx.description().lower() or
2550 k in ctx.description().lower() or
2551 k in " ".join(ctx.files()).lower()):
2551 k in " ".join(ctx.files()).lower()):
2552 break
2552 break
2553 else:
2553 else:
2554 return
2554 return
2555
2555
2556 copies = None
2556 copies = None
2557 if opts.get('copies') and rev:
2557 if opts.get('copies') and rev:
2558 copies = []
2558 copies = []
2559 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2559 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2560 for fn in ctx.files():
2560 for fn in ctx.files():
2561 rename = getrenamed(fn, rev)
2561 rename = getrenamed(fn, rev)
2562 if rename:
2562 if rename:
2563 copies.append((fn, rename[0]))
2563 copies.append((fn, rename[0]))
2564
2564
2565 revmatchfn = None
2565 revmatchfn = None
2566 if opts.get('patch') or opts.get('stat'):
2566 if opts.get('patch') or opts.get('stat'):
2567 revmatchfn = cmdutil.match(repo, fns, default='path')
2567 revmatchfn = cmdutil.match(repo, fns, default='path')
2568
2568
2569 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2569 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2570
2570
2571 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2571 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2572 if count == limit:
2572 if count == limit:
2573 break
2573 break
2574 if displayer.flush(ctx.rev()):
2574 if displayer.flush(ctx.rev()):
2575 count += 1
2575 count += 1
2576 displayer.close()
2576 displayer.close()
2577
2577
2578 def manifest(ui, repo, node=None, rev=None):
2578 def manifest(ui, repo, node=None, rev=None):
2579 """output the current or given revision of the project manifest
2579 """output the current or given revision of the project manifest
2580
2580
2581 Print a list of version controlled files for the given revision.
2581 Print a list of version controlled files for the given revision.
2582 If no revision is given, the first parent of the working directory
2582 If no revision is given, the first parent of the working directory
2583 is used, or the null revision if no revision is checked out.
2583 is used, or the null revision if no revision is checked out.
2584
2584
2585 With -v, print file permissions, symlink and executable bits.
2585 With -v, print file permissions, symlink and executable bits.
2586 With --debug, print file revision hashes.
2586 With --debug, print file revision hashes.
2587
2587
2588 Returns 0 on success.
2588 Returns 0 on success.
2589 """
2589 """
2590
2590
2591 if rev and node:
2591 if rev and node:
2592 raise util.Abort(_("please specify just one revision"))
2592 raise util.Abort(_("please specify just one revision"))
2593
2593
2594 if not node:
2594 if not node:
2595 node = rev
2595 node = rev
2596
2596
2597 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2597 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2598 ctx = repo[node]
2598 ctx = repo[node]
2599 for f in ctx:
2599 for f in ctx:
2600 if ui.debugflag:
2600 if ui.debugflag:
2601 ui.write("%40s " % hex(ctx.manifest()[f]))
2601 ui.write("%40s " % hex(ctx.manifest()[f]))
2602 if ui.verbose:
2602 if ui.verbose:
2603 ui.write(decor[ctx.flags(f)])
2603 ui.write(decor[ctx.flags(f)])
2604 ui.write("%s\n" % f)
2604 ui.write("%s\n" % f)
2605
2605
2606 def merge(ui, repo, node=None, **opts):
2606 def merge(ui, repo, node=None, **opts):
2607 """merge working directory with another revision
2607 """merge working directory with another revision
2608
2608
2609 The current working directory is updated with all changes made in
2609 The current working directory is updated with all changes made in
2610 the requested revision since the last common predecessor revision.
2610 the requested revision since the last common predecessor revision.
2611
2611
2612 Files that changed between either parent are marked as changed for
2612 Files that changed between either parent are marked as changed for
2613 the next commit and a commit must be performed before any further
2613 the next commit and a commit must be performed before any further
2614 updates to the repository are allowed. The next commit will have
2614 updates to the repository are allowed. The next commit will have
2615 two parents.
2615 two parents.
2616
2616
2617 If no revision is specified, the working directory's parent is a
2617 If no revision is specified, the working directory's parent is a
2618 head revision, and the current branch contains exactly one other
2618 head revision, and the current branch contains exactly one other
2619 head, the other head is merged with by default. Otherwise, an
2619 head, the other head is merged with by default. Otherwise, an
2620 explicit revision with which to merge with must be provided.
2620 explicit revision with which to merge with must be provided.
2621
2621
2622 To undo an uncommitted merge, use :hg:`update --clean .` which
2622 To undo an uncommitted merge, use :hg:`update --clean .` which
2623 will check out a clean copy of the original merge parent, losing
2623 will check out a clean copy of the original merge parent, losing
2624 all changes.
2624 all changes.
2625
2625
2626 Returns 0 on success, 1 if there are unresolved files.
2626 Returns 0 on success, 1 if there are unresolved files.
2627 """
2627 """
2628
2628
2629 if opts.get('rev') and node:
2629 if opts.get('rev') and node:
2630 raise util.Abort(_("please specify just one revision"))
2630 raise util.Abort(_("please specify just one revision"))
2631 if not node:
2631 if not node:
2632 node = opts.get('rev')
2632 node = opts.get('rev')
2633
2633
2634 if not node:
2634 if not node:
2635 branch = repo.changectx(None).branch()
2635 branch = repo.changectx(None).branch()
2636 bheads = repo.branchheads(branch)
2636 bheads = repo.branchheads(branch)
2637 if len(bheads) > 2:
2637 if len(bheads) > 2:
2638 raise util.Abort(_(
2638 raise util.Abort(_(
2639 'branch \'%s\' has %d heads - '
2639 'branch \'%s\' has %d heads - '
2640 'please merge with an explicit rev\n'
2640 'please merge with an explicit rev\n'
2641 '(run \'hg heads .\' to see heads)')
2641 '(run \'hg heads .\' to see heads)')
2642 % (branch, len(bheads)))
2642 % (branch, len(bheads)))
2643
2643
2644 parent = repo.dirstate.parents()[0]
2644 parent = repo.dirstate.parents()[0]
2645 if len(bheads) == 1:
2645 if len(bheads) == 1:
2646 if len(repo.heads()) > 1:
2646 if len(repo.heads()) > 1:
2647 raise util.Abort(_(
2647 raise util.Abort(_(
2648 'branch \'%s\' has one head - '
2648 'branch \'%s\' has one head - '
2649 'please merge with an explicit rev\n'
2649 'please merge with an explicit rev\n'
2650 '(run \'hg heads\' to see all heads)')
2650 '(run \'hg heads\' to see all heads)')
2651 % branch)
2651 % branch)
2652 msg = _('there is nothing to merge')
2652 msg = _('there is nothing to merge')
2653 if parent != repo.lookup(repo[None].branch()):
2653 if parent != repo.lookup(repo[None].branch()):
2654 msg = _('%s - use "hg update" instead') % msg
2654 msg = _('%s - use "hg update" instead') % msg
2655 raise util.Abort(msg)
2655 raise util.Abort(msg)
2656
2656
2657 if parent not in bheads:
2657 if parent not in bheads:
2658 raise util.Abort(_('working dir not at a head rev - '
2658 raise util.Abort(_('working dir not at a head rev - '
2659 'use "hg update" or merge with an explicit rev'))
2659 'use "hg update" or merge with an explicit rev'))
2660 node = parent == bheads[0] and bheads[-1] or bheads[0]
2660 node = parent == bheads[0] and bheads[-1] or bheads[0]
2661
2661
2662 if opts.get('preview'):
2662 if opts.get('preview'):
2663 # find nodes that are ancestors of p2 but not of p1
2663 # find nodes that are ancestors of p2 but not of p1
2664 p1 = repo.lookup('.')
2664 p1 = repo.lookup('.')
2665 p2 = repo.lookup(node)
2665 p2 = repo.lookup(node)
2666 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2666 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2667
2667
2668 displayer = cmdutil.show_changeset(ui, repo, opts)
2668 displayer = cmdutil.show_changeset(ui, repo, opts)
2669 for node in nodes:
2669 for node in nodes:
2670 displayer.show(repo[node])
2670 displayer.show(repo[node])
2671 displayer.close()
2671 displayer.close()
2672 return 0
2672 return 0
2673
2673
2674 return hg.merge(repo, node, force=opts.get('force'))
2674 return hg.merge(repo, node, force=opts.get('force'))
2675
2675
2676 def outgoing(ui, repo, dest=None, **opts):
2676 def outgoing(ui, repo, dest=None, **opts):
2677 """show changesets not found in the destination
2677 """show changesets not found in the destination
2678
2678
2679 Show changesets not found in the specified destination repository
2679 Show changesets not found in the specified destination repository
2680 or the default push location. These are the changesets that would
2680 or the default push location. These are the changesets that would
2681 be pushed if a push was requested.
2681 be pushed if a push was requested.
2682
2682
2683 See pull for details of valid destination formats.
2683 See pull for details of valid destination formats.
2684
2684
2685 Returns 0 if there are outgoing changes, 1 otherwise.
2685 Returns 0 if there are outgoing changes, 1 otherwise.
2686 """
2686 """
2687 limit = cmdutil.loglimit(opts)
2687 limit = cmdutil.loglimit(opts)
2688 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2688 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2689 dest, branches = hg.parseurl(dest, opts.get('branch'))
2689 dest, branches = hg.parseurl(dest, opts.get('branch'))
2690 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2690 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2691 if revs:
2691 if revs:
2692 revs = [repo.lookup(rev) for rev in revs]
2692 revs = [repo.lookup(rev) for rev in revs]
2693
2693
2694 other = hg.repository(hg.remoteui(repo, opts), dest)
2694 other = hg.repository(hg.remoteui(repo, opts), dest)
2695 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2695 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2696 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2696 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2697 if not o:
2697 if not o:
2698 ui.status(_("no changes found\n"))
2698 ui.status(_("no changes found\n"))
2699 return 1
2699 return 1
2700 o = repo.changelog.nodesbetween(o, revs)[0]
2700 o = repo.changelog.nodesbetween(o, revs)[0]
2701 if opts.get('newest_first'):
2701 if opts.get('newest_first'):
2702 o.reverse()
2702 o.reverse()
2703 displayer = cmdutil.show_changeset(ui, repo, opts)
2703 displayer = cmdutil.show_changeset(ui, repo, opts)
2704 count = 0
2704 count = 0
2705 for n in o:
2705 for n in o:
2706 if limit is not None and count >= limit:
2706 if limit is not None and count >= limit:
2707 break
2707 break
2708 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2708 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2709 if opts.get('no_merges') and len(parents) == 2:
2709 if opts.get('no_merges') and len(parents) == 2:
2710 continue
2710 continue
2711 count += 1
2711 count += 1
2712 displayer.show(repo[n])
2712 displayer.show(repo[n])
2713 displayer.close()
2713 displayer.close()
2714
2714
2715 def parents(ui, repo, file_=None, **opts):
2715 def parents(ui, repo, file_=None, **opts):
2716 """show the parents of the working directory or revision
2716 """show the parents of the working directory or revision
2717
2717
2718 Print the working directory's parent revisions. If a revision is
2718 Print the working directory's parent revisions. If a revision is
2719 given via -r/--rev, the parent of that revision will be printed.
2719 given via -r/--rev, the parent of that revision will be printed.
2720 If a file argument is given, the revision in which the file was
2720 If a file argument is given, the revision in which the file was
2721 last changed (before the working directory revision or the
2721 last changed (before the working directory revision or the
2722 argument to --rev if given) is printed.
2722 argument to --rev if given) is printed.
2723
2723
2724 Returns 0 on success.
2724 Returns 0 on success.
2725 """
2725 """
2726 rev = opts.get('rev')
2726 rev = opts.get('rev')
2727 if rev:
2727 if rev:
2728 ctx = repo[rev]
2728 ctx = repo[rev]
2729 else:
2729 else:
2730 ctx = repo[None]
2730 ctx = repo[None]
2731
2731
2732 if file_:
2732 if file_:
2733 m = cmdutil.match(repo, (file_,), opts)
2733 m = cmdutil.match(repo, (file_,), opts)
2734 if m.anypats() or len(m.files()) != 1:
2734 if m.anypats() or len(m.files()) != 1:
2735 raise util.Abort(_('can only specify an explicit filename'))
2735 raise util.Abort(_('can only specify an explicit filename'))
2736 file_ = m.files()[0]
2736 file_ = m.files()[0]
2737 filenodes = []
2737 filenodes = []
2738 for cp in ctx.parents():
2738 for cp in ctx.parents():
2739 if not cp:
2739 if not cp:
2740 continue
2740 continue
2741 try:
2741 try:
2742 filenodes.append(cp.filenode(file_))
2742 filenodes.append(cp.filenode(file_))
2743 except error.LookupError:
2743 except error.LookupError:
2744 pass
2744 pass
2745 if not filenodes:
2745 if not filenodes:
2746 raise util.Abort(_("'%s' not found in manifest!") % file_)
2746 raise util.Abort(_("'%s' not found in manifest!") % file_)
2747 fl = repo.file(file_)
2747 fl = repo.file(file_)
2748 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2748 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2749 else:
2749 else:
2750 p = [cp.node() for cp in ctx.parents()]
2750 p = [cp.node() for cp in ctx.parents()]
2751
2751
2752 displayer = cmdutil.show_changeset(ui, repo, opts)
2752 displayer = cmdutil.show_changeset(ui, repo, opts)
2753 for n in p:
2753 for n in p:
2754 if n != nullid:
2754 if n != nullid:
2755 displayer.show(repo[n])
2755 displayer.show(repo[n])
2756 displayer.close()
2756 displayer.close()
2757
2757
2758 def paths(ui, repo, search=None):
2758 def paths(ui, repo, search=None):
2759 """show aliases for remote repositories
2759 """show aliases for remote repositories
2760
2760
2761 Show definition of symbolic path name NAME. If no name is given,
2761 Show definition of symbolic path name NAME. If no name is given,
2762 show definition of all available names.
2762 show definition of all available names.
2763
2763
2764 Path names are defined in the [paths] section of your
2764 Path names are defined in the [paths] section of your
2765 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2765 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2766 repository, ``.hg/hgrc`` is used, too.
2766 repository, ``.hg/hgrc`` is used, too.
2767
2767
2768 The path names ``default`` and ``default-push`` have a special
2768 The path names ``default`` and ``default-push`` have a special
2769 meaning. When performing a push or pull operation, they are used
2769 meaning. When performing a push or pull operation, they are used
2770 as fallbacks if no location is specified on the command-line.
2770 as fallbacks if no location is specified on the command-line.
2771 When ``default-push`` is set, it will be used for push and
2771 When ``default-push`` is set, it will be used for push and
2772 ``default`` will be used for pull; otherwise ``default`` is used
2772 ``default`` will be used for pull; otherwise ``default`` is used
2773 as the fallback for both. When cloning a repository, the clone
2773 as the fallback for both. When cloning a repository, the clone
2774 source is written as ``default`` in ``.hg/hgrc``. Note that
2774 source is written as ``default`` in ``.hg/hgrc``. Note that
2775 ``default`` and ``default-push`` apply to all inbound (e.g.
2775 ``default`` and ``default-push`` apply to all inbound (e.g.
2776 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2776 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2777 :hg:`bundle`) operations.
2777 :hg:`bundle`) operations.
2778
2778
2779 See :hg:`help urls` for more information.
2779 See :hg:`help urls` for more information.
2780
2780
2781 Returns 0 on success.
2781 Returns 0 on success.
2782 """
2782 """
2783 if search:
2783 if search:
2784 for name, path in ui.configitems("paths"):
2784 for name, path in ui.configitems("paths"):
2785 if name == search:
2785 if name == search:
2786 ui.write("%s\n" % url.hidepassword(path))
2786 ui.write("%s\n" % url.hidepassword(path))
2787 return
2787 return
2788 ui.warn(_("not found!\n"))
2788 ui.warn(_("not found!\n"))
2789 return 1
2789 return 1
2790 else:
2790 else:
2791 for name, path in ui.configitems("paths"):
2791 for name, path in ui.configitems("paths"):
2792 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2792 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2793
2793
2794 def postincoming(ui, repo, modheads, optupdate, checkout):
2794 def postincoming(ui, repo, modheads, optupdate, checkout):
2795 if modheads == 0:
2795 if modheads == 0:
2796 return
2796 return
2797 if optupdate:
2797 if optupdate:
2798 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2798 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2799 return hg.update(repo, checkout)
2799 return hg.update(repo, checkout)
2800 else:
2800 else:
2801 ui.status(_("not updating, since new heads added\n"))
2801 ui.status(_("not updating, since new heads added\n"))
2802 if modheads > 1:
2802 if modheads > 1:
2803 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2803 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2804 else:
2804 else:
2805 ui.status(_("(run 'hg update' to get a working copy)\n"))
2805 ui.status(_("(run 'hg update' to get a working copy)\n"))
2806
2806
2807 def pull(ui, repo, source="default", **opts):
2807 def pull(ui, repo, source="default", **opts):
2808 """pull changes from the specified source
2808 """pull changes from the specified source
2809
2809
2810 Pull changes from a remote repository to a local one.
2810 Pull changes from a remote repository to a local one.
2811
2811
2812 This finds all changes from the repository at the specified path
2812 This finds all changes from the repository at the specified path
2813 or URL and adds them to a local repository (the current one unless
2813 or URL and adds them to a local repository (the current one unless
2814 -R is specified). By default, this does not update the copy of the
2814 -R is specified). By default, this does not update the copy of the
2815 project in the working directory.
2815 project in the working directory.
2816
2816
2817 Use :hg:`incoming` if you want to see what would have been added
2817 Use :hg:`incoming` if you want to see what would have been added
2818 by a pull at the time you issued this command. If you then decide
2818 by a pull at the time you issued this command. If you then decide
2819 to add those changes to the repository, you should use :hg:`pull
2819 to add those changes to the repository, you should use :hg:`pull
2820 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2820 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2821
2821
2822 If SOURCE is omitted, the 'default' path will be used.
2822 If SOURCE is omitted, the 'default' path will be used.
2823 See :hg:`help urls` for more information.
2823 See :hg:`help urls` for more information.
2824
2824
2825 Returns 0 on success, 1 if an update had unresolved files.
2825 Returns 0 on success, 1 if an update had unresolved files.
2826 """
2826 """
2827 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2827 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2828 other = hg.repository(hg.remoteui(repo, opts), source)
2828 other = hg.repository(hg.remoteui(repo, opts), source)
2829 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2829 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2830 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2830 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2831 if revs:
2831 if revs:
2832 try:
2832 try:
2833 revs = [other.lookup(rev) for rev in revs]
2833 revs = [other.lookup(rev) for rev in revs]
2834 except error.CapabilityError:
2834 except error.CapabilityError:
2835 err = _("other repository doesn't support revision lookup, "
2835 err = _("other repository doesn't support revision lookup, "
2836 "so a rev cannot be specified.")
2836 "so a rev cannot be specified.")
2837 raise util.Abort(err)
2837 raise util.Abort(err)
2838
2838
2839 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2839 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2840 if checkout:
2840 if checkout:
2841 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2841 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2842 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2842 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2843
2843
2844 def push(ui, repo, dest=None, **opts):
2844 def push(ui, repo, dest=None, **opts):
2845 """push changes to the specified destination
2845 """push changes to the specified destination
2846
2846
2847 Push changesets from the local repository to the specified
2847 Push changesets from the local repository to the specified
2848 destination.
2848 destination.
2849
2849
2850 This operation is symmetrical to pull: it is identical to a pull
2850 This operation is symmetrical to pull: it is identical to a pull
2851 in the destination repository from the current one.
2851 in the destination repository from the current one.
2852
2852
2853 By default, push will not allow creation of new heads at the
2853 By default, push will not allow creation of new heads at the
2854 destination, since multiple heads would make it unclear which head
2854 destination, since multiple heads would make it unclear which head
2855 to use. In this situation, it is recommended to pull and merge
2855 to use. In this situation, it is recommended to pull and merge
2856 before pushing.
2856 before pushing.
2857
2857
2858 Use --new-branch if you want to allow push to create a new named
2858 Use --new-branch if you want to allow push to create a new named
2859 branch that is not present at the destination. This allows you to
2859 branch that is not present at the destination. This allows you to
2860 only create a new branch without forcing other changes.
2860 only create a new branch without forcing other changes.
2861
2861
2862 Use -f/--force to override the default behavior and push all
2862 Use -f/--force to override the default behavior and push all
2863 changesets on all branches.
2863 changesets on all branches.
2864
2864
2865 If -r/--rev is used, the specified revision and all its ancestors
2865 If -r/--rev is used, the specified revision and all its ancestors
2866 will be pushed to the remote repository.
2866 will be pushed to the remote repository.
2867
2867
2868 Please see :hg:`help urls` for important details about ``ssh://``
2868 Please see :hg:`help urls` for important details about ``ssh://``
2869 URLs. If DESTINATION is omitted, a default path will be used.
2869 URLs. If DESTINATION is omitted, a default path will be used.
2870
2870
2871 Returns 0 if push was successful, 1 if nothing to push.
2871 Returns 0 if push was successful, 1 if nothing to push.
2872 """
2872 """
2873 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2873 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2874 dest, branches = hg.parseurl(dest, opts.get('branch'))
2874 dest, branches = hg.parseurl(dest, opts.get('branch'))
2875 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2875 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2876 other = hg.repository(hg.remoteui(repo, opts), dest)
2876 other = hg.repository(hg.remoteui(repo, opts), dest)
2877 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2877 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2878 if revs:
2878 if revs:
2879 revs = [repo.lookup(rev) for rev in revs]
2879 revs = [repo.lookup(rev) for rev in revs]
2880
2880
2881 # push subrepos depth-first for coherent ordering
2881 # push subrepos depth-first for coherent ordering
2882 c = repo['']
2882 c = repo['']
2883 subs = c.substate # only repos that are committed
2883 subs = c.substate # only repos that are committed
2884 for s in sorted(subs):
2884 for s in sorted(subs):
2885 if not c.sub(s).push(opts.get('force')):
2885 if not c.sub(s).push(opts.get('force')):
2886 return False
2886 return False
2887
2887
2888 r = repo.push(other, opts.get('force'), revs=revs,
2888 r = repo.push(other, opts.get('force'), revs=revs,
2889 newbranch=opts.get('new_branch'))
2889 newbranch=opts.get('new_branch'))
2890 return r == 0
2890 return r == 0
2891
2891
2892 def recover(ui, repo):
2892 def recover(ui, repo):
2893 """roll back an interrupted transaction
2893 """roll back an interrupted transaction
2894
2894
2895 Recover from an interrupted commit or pull.
2895 Recover from an interrupted commit or pull.
2896
2896
2897 This command tries to fix the repository status after an
2897 This command tries to fix the repository status after an
2898 interrupted operation. It should only be necessary when Mercurial
2898 interrupted operation. It should only be necessary when Mercurial
2899 suggests it.
2899 suggests it.
2900
2900
2901 Returns 0 if successful, 1 if nothing to recover or verify fails.
2901 Returns 0 if successful, 1 if nothing to recover or verify fails.
2902 """
2902 """
2903 if repo.recover():
2903 if repo.recover():
2904 return hg.verify(repo)
2904 return hg.verify(repo)
2905 return 1
2905 return 1
2906
2906
2907 def remove(ui, repo, *pats, **opts):
2907 def remove(ui, repo, *pats, **opts):
2908 """remove the specified files on the next commit
2908 """remove the specified files on the next commit
2909
2909
2910 Schedule the indicated files for removal from the repository.
2910 Schedule the indicated files for removal from the repository.
2911
2911
2912 This only removes files from the current branch, not from the
2912 This only removes files from the current branch, not from the
2913 entire project history. -A/--after can be used to remove only
2913 entire project history. -A/--after can be used to remove only
2914 files that have already been deleted, -f/--force can be used to
2914 files that have already been deleted, -f/--force can be used to
2915 force deletion, and -Af can be used to remove files from the next
2915 force deletion, and -Af can be used to remove files from the next
2916 revision without deleting them from the working directory.
2916 revision without deleting them from the working directory.
2917
2917
2918 The following table details the behavior of remove for different
2918 The following table details the behavior of remove for different
2919 file states (columns) and option combinations (rows). The file
2919 file states (columns) and option combinations (rows). The file
2920 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2920 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2921 reported by :hg:`status`). The actions are Warn, Remove (from
2921 reported by :hg:`status`). The actions are Warn, Remove (from
2922 branch) and Delete (from disk)::
2922 branch) and Delete (from disk)::
2923
2923
2924 A C M !
2924 A C M !
2925 none W RD W R
2925 none W RD W R
2926 -f R RD RD R
2926 -f R RD RD R
2927 -A W W W R
2927 -A W W W R
2928 -Af R R R R
2928 -Af R R R R
2929
2929
2930 This command schedules the files to be removed at the next commit.
2930 This command schedules the files to be removed at the next commit.
2931 To undo a remove before that, see :hg:`revert`.
2931 To undo a remove before that, see :hg:`revert`.
2932
2932
2933 Returns 0 on success, 1 if any warnings encountered.
2933 Returns 0 on success, 1 if any warnings encountered.
2934 """
2934 """
2935
2935
2936 ret = 0
2936 ret = 0
2937 after, force = opts.get('after'), opts.get('force')
2937 after, force = opts.get('after'), opts.get('force')
2938 if not pats and not after:
2938 if not pats and not after:
2939 raise util.Abort(_('no files specified'))
2939 raise util.Abort(_('no files specified'))
2940
2940
2941 m = cmdutil.match(repo, pats, opts)
2941 m = cmdutil.match(repo, pats, opts)
2942 s = repo.status(match=m, clean=True)
2942 s = repo.status(match=m, clean=True)
2943 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2943 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2944
2944
2945 for f in m.files():
2945 for f in m.files():
2946 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2946 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2947 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2947 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2948 ret = 1
2948 ret = 1
2949
2949
2950 if force:
2950 if force:
2951 remove, forget = modified + deleted + clean, added
2951 remove, forget = modified + deleted + clean, added
2952 elif after:
2952 elif after:
2953 remove, forget = deleted, []
2953 remove, forget = deleted, []
2954 for f in modified + added + clean:
2954 for f in modified + added + clean:
2955 ui.warn(_('not removing %s: file still exists (use -f'
2955 ui.warn(_('not removing %s: file still exists (use -f'
2956 ' to force removal)\n') % m.rel(f))
2956 ' to force removal)\n') % m.rel(f))
2957 ret = 1
2957 ret = 1
2958 else:
2958 else:
2959 remove, forget = deleted + clean, []
2959 remove, forget = deleted + clean, []
2960 for f in modified:
2960 for f in modified:
2961 ui.warn(_('not removing %s: file is modified (use -f'
2961 ui.warn(_('not removing %s: file is modified (use -f'
2962 ' to force removal)\n') % m.rel(f))
2962 ' to force removal)\n') % m.rel(f))
2963 ret = 1
2963 ret = 1
2964 for f in added:
2964 for f in added:
2965 ui.warn(_('not removing %s: file has been marked for add (use -f'
2965 ui.warn(_('not removing %s: file has been marked for add (use -f'
2966 ' to force removal)\n') % m.rel(f))
2966 ' to force removal)\n') % m.rel(f))
2967 ret = 1
2967 ret = 1
2968
2968
2969 for f in sorted(remove + forget):
2969 for f in sorted(remove + forget):
2970 if ui.verbose or not m.exact(f):
2970 if ui.verbose or not m.exact(f):
2971 ui.status(_('removing %s\n') % m.rel(f))
2971 ui.status(_('removing %s\n') % m.rel(f))
2972
2972
2973 repo[None].forget(forget)
2973 repo[None].forget(forget)
2974 repo[None].remove(remove, unlink=not after)
2974 repo[None].remove(remove, unlink=not after)
2975 return ret
2975 return ret
2976
2976
2977 def rename(ui, repo, *pats, **opts):
2977 def rename(ui, repo, *pats, **opts):
2978 """rename files; equivalent of copy + remove
2978 """rename files; equivalent of copy + remove
2979
2979
2980 Mark dest as copies of sources; mark sources for deletion. If dest
2980 Mark dest as copies of sources; mark sources for deletion. If dest
2981 is a directory, copies are put in that directory. If dest is a
2981 is a directory, copies are put in that directory. If dest is a
2982 file, there can only be one source.
2982 file, there can only be one source.
2983
2983
2984 By default, this command copies the contents of files as they
2984 By default, this command copies the contents of files as they
2985 exist in the working directory. If invoked with -A/--after, the
2985 exist in the working directory. If invoked with -A/--after, the
2986 operation is recorded, but no copying is performed.
2986 operation is recorded, but no copying is performed.
2987
2987
2988 This command takes effect at the next commit. To undo a rename
2988 This command takes effect at the next commit. To undo a rename
2989 before that, see :hg:`revert`.
2989 before that, see :hg:`revert`.
2990
2990
2991 Returns 0 on success, 1 if errors are encountered.
2991 Returns 0 on success, 1 if errors are encountered.
2992 """
2992 """
2993 wlock = repo.wlock(False)
2993 wlock = repo.wlock(False)
2994 try:
2994 try:
2995 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2995 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2996 finally:
2996 finally:
2997 wlock.release()
2997 wlock.release()
2998
2998
2999 def resolve(ui, repo, *pats, **opts):
2999 def resolve(ui, repo, *pats, **opts):
3000 """redo merges or set/view the merge status of files
3000 """redo merges or set/view the merge status of files
3001
3001
3002 Merges with unresolved conflicts are often the result of
3002 Merges with unresolved conflicts are often the result of
3003 non-interactive merging using the ``internal:merge`` configuration
3003 non-interactive merging using the ``internal:merge`` configuration
3004 setting, or a command-line merge tool like ``diff3``. The resolve
3004 setting, or a command-line merge tool like ``diff3``. The resolve
3005 command is used to manage the files involved in a merge, after
3005 command is used to manage the files involved in a merge, after
3006 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3006 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3007 working directory must have two parents).
3007 working directory must have two parents).
3008
3008
3009 The resolve command can be used in the following ways:
3009 The resolve command can be used in the following ways:
3010
3010
3011 - :hg:`resolve FILE...`: attempt to re-merge the specified files,
3011 - :hg:`resolve FILE...`: attempt to re-merge the specified files,
3012 discarding any previous merge attempts. Re-merging is not
3012 discarding any previous merge attempts. Re-merging is not
3013 performed for files already marked as resolved. Use ``--all/-a``
3013 performed for files already marked as resolved. Use ``--all/-a``
3014 to selects all unresolved files.
3014 to selects all unresolved files.
3015
3015
3016 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3016 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3017 (e.g. after having manually fixed-up the files). The default is
3017 (e.g. after having manually fixed-up the files). The default is
3018 to mark all unresolved files.
3018 to mark all unresolved files.
3019
3019
3020 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3020 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3021 default is to mark all resolved files.
3021 default is to mark all resolved files.
3022
3022
3023 - :hg:`resolve -l`: list files which had or still have conflicts.
3023 - :hg:`resolve -l`: list files which had or still have conflicts.
3024 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3024 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3025
3025
3026 Note that Mercurial will not let you commit files with unresolved
3026 Note that Mercurial will not let you commit files with unresolved
3027 merge conflicts. You must use :hg:`resolve -m ...` before you can
3027 merge conflicts. You must use :hg:`resolve -m ...` before you can
3028 commit after a conflicting merge.
3028 commit after a conflicting merge.
3029
3029
3030 Returns 0 on success, 1 if any files fail a resolve attempt.
3030 Returns 0 on success, 1 if any files fail a resolve attempt.
3031 """
3031 """
3032
3032
3033 all, mark, unmark, show, nostatus = \
3033 all, mark, unmark, show, nostatus = \
3034 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3034 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3035
3035
3036 if (show and (mark or unmark)) or (mark and unmark):
3036 if (show and (mark or unmark)) or (mark and unmark):
3037 raise util.Abort(_("too many options specified"))
3037 raise util.Abort(_("too many options specified"))
3038 if pats and all:
3038 if pats and all:
3039 raise util.Abort(_("can't specify --all and patterns"))
3039 raise util.Abort(_("can't specify --all and patterns"))
3040 if not (all or pats or show or mark or unmark):
3040 if not (all or pats or show or mark or unmark):
3041 raise util.Abort(_('no files or directories specified; '
3041 raise util.Abort(_('no files or directories specified; '
3042 'use --all to remerge all files'))
3042 'use --all to remerge all files'))
3043
3043
3044 ms = mergemod.mergestate(repo)
3044 ms = mergemod.mergestate(repo)
3045 m = cmdutil.match(repo, pats, opts)
3045 m = cmdutil.match(repo, pats, opts)
3046 ret = 0
3046 ret = 0
3047
3047
3048 for f in ms:
3048 for f in ms:
3049 if m(f):
3049 if m(f):
3050 if show:
3050 if show:
3051 if nostatus:
3051 if nostatus:
3052 ui.write("%s\n" % f)
3052 ui.write("%s\n" % f)
3053 else:
3053 else:
3054 ui.write("%s %s\n" % (ms[f].upper(), f),
3054 ui.write("%s %s\n" % (ms[f].upper(), f),
3055 label='resolve.' +
3055 label='resolve.' +
3056 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3056 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3057 elif mark:
3057 elif mark:
3058 ms.mark(f, "r")
3058 ms.mark(f, "r")
3059 elif unmark:
3059 elif unmark:
3060 ms.mark(f, "u")
3060 ms.mark(f, "u")
3061 else:
3061 else:
3062 wctx = repo[None]
3062 wctx = repo[None]
3063 mctx = wctx.parents()[-1]
3063 mctx = wctx.parents()[-1]
3064
3064
3065 # backup pre-resolve (merge uses .orig for its own purposes)
3065 # backup pre-resolve (merge uses .orig for its own purposes)
3066 a = repo.wjoin(f)
3066 a = repo.wjoin(f)
3067 util.copyfile(a, a + ".resolve")
3067 util.copyfile(a, a + ".resolve")
3068
3068
3069 # resolve file
3069 # resolve file
3070 if ms.resolve(f, wctx, mctx):
3070 if ms.resolve(f, wctx, mctx):
3071 ret = 1
3071 ret = 1
3072
3072
3073 # replace filemerge's .orig file with our resolve file
3073 # replace filemerge's .orig file with our resolve file
3074 util.rename(a + ".resolve", a + ".orig")
3074 util.rename(a + ".resolve", a + ".orig")
3075 return ret
3075 return ret
3076
3076
3077 def revert(ui, repo, *pats, **opts):
3077 def revert(ui, repo, *pats, **opts):
3078 """restore individual files or directories to an earlier state
3078 """restore individual files or directories to an earlier state
3079
3079
3080 NOTE: This command is most likely not what you are looking for. revert
3080 NOTE: This command is most likely not what you are looking for. revert
3081 will partially overwrite content in the working directory without changing
3081 will partially overwrite content in the working directory without changing
3082 the working directory parents. Use :hg:`update -r rev` to check out earlier
3082 the working directory parents. Use :hg:`update -r rev` to check out earlier
3083 revisions, or :hg:`update --clean .` to undo a merge which has added
3083 revisions, or :hg:`update --clean .` to undo a merge which has added
3084 another parent.
3084 another parent.
3085
3085
3086 With no revision specified, revert the named files or directories
3086 With no revision specified, revert the named files or directories
3087 to the contents they had in the parent of the working directory.
3087 to the contents they had in the parent of the working directory.
3088 This restores the contents of the affected files to an unmodified
3088 This restores the contents of the affected files to an unmodified
3089 state and unschedules adds, removes, copies, and renames. If the
3089 state and unschedules adds, removes, copies, and renames. If the
3090 working directory has two parents, you must explicitly specify a
3090 working directory has two parents, you must explicitly specify a
3091 revision.
3091 revision.
3092
3092
3093 Using the -r/--rev option, revert the given files or directories
3093 Using the -r/--rev option, revert the given files or directories
3094 to their contents as of a specific revision. This can be helpful
3094 to their contents as of a specific revision. This can be helpful
3095 to "roll back" some or all of an earlier change. See :hg:`help
3095 to "roll back" some or all of an earlier change. See :hg:`help
3096 dates` for a list of formats valid for -d/--date.
3096 dates` for a list of formats valid for -d/--date.
3097
3097
3098 Revert modifies the working directory. It does not commit any
3098 Revert modifies the working directory. It does not commit any
3099 changes, or change the parent of the working directory. If you
3099 changes, or change the parent of the working directory. If you
3100 revert to a revision other than the parent of the working
3100 revert to a revision other than the parent of the working
3101 directory, the reverted files will thus appear modified
3101 directory, the reverted files will thus appear modified
3102 afterwards.
3102 afterwards.
3103
3103
3104 If a file has been deleted, it is restored. If the executable mode
3104 If a file has been deleted, it is restored. If the executable mode
3105 of a file was changed, it is reset.
3105 of a file was changed, it is reset.
3106
3106
3107 If names are given, all files matching the names are reverted.
3107 If names are given, all files matching the names are reverted.
3108 If no arguments are given, no files are reverted.
3108 If no arguments are given, no files are reverted.
3109
3109
3110 Modified files are saved with a .orig suffix before reverting.
3110 Modified files are saved with a .orig suffix before reverting.
3111 To disable these backups, use --no-backup.
3111 To disable these backups, use --no-backup.
3112
3112
3113 Returns 0 on success.
3113 Returns 0 on success.
3114 """
3114 """
3115
3115
3116 if opts.get("date"):
3116 if opts.get("date"):
3117 if opts.get("rev"):
3117 if opts.get("rev"):
3118 raise util.Abort(_("you can't specify a revision and a date"))
3118 raise util.Abort(_("you can't specify a revision and a date"))
3119 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3119 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3120
3120
3121 if not pats and not opts.get('all'):
3121 if not pats and not opts.get('all'):
3122 raise util.Abort(_('no files or directories specified; '
3122 raise util.Abort(_('no files or directories specified; '
3123 'use --all to revert the whole repo'))
3123 'use --all to revert the whole repo'))
3124
3124
3125 parent, p2 = repo.dirstate.parents()
3125 parent, p2 = repo.dirstate.parents()
3126 if not opts.get('rev') and p2 != nullid:
3126 if not opts.get('rev') and p2 != nullid:
3127 raise util.Abort(_('uncommitted merge - please provide a '
3127 raise util.Abort(_('uncommitted merge - please provide a '
3128 'specific revision'))
3128 'specific revision'))
3129 ctx = repo[opts.get('rev')]
3129 ctx = repo[opts.get('rev')]
3130 node = ctx.node()
3130 node = ctx.node()
3131 mf = ctx.manifest()
3131 mf = ctx.manifest()
3132 if node == parent:
3132 if node == parent:
3133 pmf = mf
3133 pmf = mf
3134 else:
3134 else:
3135 pmf = None
3135 pmf = None
3136
3136
3137 # need all matching names in dirstate and manifest of target rev,
3137 # need all matching names in dirstate and manifest of target rev,
3138 # so have to walk both. do not print errors if files exist in one
3138 # so have to walk both. do not print errors if files exist in one
3139 # but not other.
3139 # but not other.
3140
3140
3141 names = {}
3141 names = {}
3142
3142
3143 wlock = repo.wlock()
3143 wlock = repo.wlock()
3144 try:
3144 try:
3145 # walk dirstate.
3145 # walk dirstate.
3146
3146
3147 m = cmdutil.match(repo, pats, opts)
3147 m = cmdutil.match(repo, pats, opts)
3148 m.bad = lambda x, y: False
3148 m.bad = lambda x, y: False
3149 for abs in repo.walk(m):
3149 for abs in repo.walk(m):
3150 names[abs] = m.rel(abs), m.exact(abs)
3150 names[abs] = m.rel(abs), m.exact(abs)
3151
3151
3152 # walk target manifest.
3152 # walk target manifest.
3153
3153
3154 def badfn(path, msg):
3154 def badfn(path, msg):
3155 if path in names:
3155 if path in names:
3156 return
3156 return
3157 path_ = path + '/'
3157 path_ = path + '/'
3158 for f in names:
3158 for f in names:
3159 if f.startswith(path_):
3159 if f.startswith(path_):
3160 return
3160 return
3161 ui.warn("%s: %s\n" % (m.rel(path), msg))
3161 ui.warn("%s: %s\n" % (m.rel(path), msg))
3162
3162
3163 m = cmdutil.match(repo, pats, opts)
3163 m = cmdutil.match(repo, pats, opts)
3164 m.bad = badfn
3164 m.bad = badfn
3165 for abs in repo[node].walk(m):
3165 for abs in repo[node].walk(m):
3166 if abs not in names:
3166 if abs not in names:
3167 names[abs] = m.rel(abs), m.exact(abs)
3167 names[abs] = m.rel(abs), m.exact(abs)
3168
3168
3169 m = cmdutil.matchfiles(repo, names)
3169 m = cmdutil.matchfiles(repo, names)
3170 changes = repo.status(match=m)[:4]
3170 changes = repo.status(match=m)[:4]
3171 modified, added, removed, deleted = map(set, changes)
3171 modified, added, removed, deleted = map(set, changes)
3172
3172
3173 # if f is a rename, also revert the source
3173 # if f is a rename, also revert the source
3174 cwd = repo.getcwd()
3174 cwd = repo.getcwd()
3175 for f in added:
3175 for f in added:
3176 src = repo.dirstate.copied(f)
3176 src = repo.dirstate.copied(f)
3177 if src and src not in names and repo.dirstate[src] == 'r':
3177 if src and src not in names and repo.dirstate[src] == 'r':
3178 removed.add(src)
3178 removed.add(src)
3179 names[src] = (repo.pathto(src, cwd), True)
3179 names[src] = (repo.pathto(src, cwd), True)
3180
3180
3181 def removeforget(abs):
3181 def removeforget(abs):
3182 if repo.dirstate[abs] == 'a':
3182 if repo.dirstate[abs] == 'a':
3183 return _('forgetting %s\n')
3183 return _('forgetting %s\n')
3184 return _('removing %s\n')
3184 return _('removing %s\n')
3185
3185
3186 revert = ([], _('reverting %s\n'))
3186 revert = ([], _('reverting %s\n'))
3187 add = ([], _('adding %s\n'))
3187 add = ([], _('adding %s\n'))
3188 remove = ([], removeforget)
3188 remove = ([], removeforget)
3189 undelete = ([], _('undeleting %s\n'))
3189 undelete = ([], _('undeleting %s\n'))
3190
3190
3191 disptable = (
3191 disptable = (
3192 # dispatch table:
3192 # dispatch table:
3193 # file state
3193 # file state
3194 # action if in target manifest
3194 # action if in target manifest
3195 # action if not in target manifest
3195 # action if not in target manifest
3196 # make backup if in target manifest
3196 # make backup if in target manifest
3197 # make backup if not in target manifest
3197 # make backup if not in target manifest
3198 (modified, revert, remove, True, True),
3198 (modified, revert, remove, True, True),
3199 (added, revert, remove, True, False),
3199 (added, revert, remove, True, False),
3200 (removed, undelete, None, False, False),
3200 (removed, undelete, None, False, False),
3201 (deleted, revert, remove, False, False),
3201 (deleted, revert, remove, False, False),
3202 )
3202 )
3203
3203
3204 for abs, (rel, exact) in sorted(names.items()):
3204 for abs, (rel, exact) in sorted(names.items()):
3205 mfentry = mf.get(abs)
3205 mfentry = mf.get(abs)
3206 target = repo.wjoin(abs)
3206 target = repo.wjoin(abs)
3207 def handle(xlist, dobackup):
3207 def handle(xlist, dobackup):
3208 xlist[0].append(abs)
3208 xlist[0].append(abs)
3209 if (dobackup and not opts.get('no_backup') and
3209 if (dobackup and not opts.get('no_backup') and
3210 os.path.lexists(target)):
3210 os.path.lexists(target)):
3211 bakname = "%s.orig" % rel
3211 bakname = "%s.orig" % rel
3212 ui.note(_('saving current version of %s as %s\n') %
3212 ui.note(_('saving current version of %s as %s\n') %
3213 (rel, bakname))
3213 (rel, bakname))
3214 if not opts.get('dry_run'):
3214 if not opts.get('dry_run'):
3215 util.rename(target, bakname)
3215 util.rename(target, bakname)
3216 if ui.verbose or not exact:
3216 if ui.verbose or not exact:
3217 msg = xlist[1]
3217 msg = xlist[1]
3218 if not isinstance(msg, basestring):
3218 if not isinstance(msg, basestring):
3219 msg = msg(abs)
3219 msg = msg(abs)
3220 ui.status(msg % rel)
3220 ui.status(msg % rel)
3221 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3221 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3222 if abs not in table:
3222 if abs not in table:
3223 continue
3223 continue
3224 # file has changed in dirstate
3224 # file has changed in dirstate
3225 if mfentry:
3225 if mfentry:
3226 handle(hitlist, backuphit)
3226 handle(hitlist, backuphit)
3227 elif misslist is not None:
3227 elif misslist is not None:
3228 handle(misslist, backupmiss)
3228 handle(misslist, backupmiss)
3229 break
3229 break
3230 else:
3230 else:
3231 if abs not in repo.dirstate:
3231 if abs not in repo.dirstate:
3232 if mfentry:
3232 if mfentry:
3233 handle(add, True)
3233 handle(add, True)
3234 elif exact:
3234 elif exact:
3235 ui.warn(_('file not managed: %s\n') % rel)
3235 ui.warn(_('file not managed: %s\n') % rel)
3236 continue
3236 continue
3237 # file has not changed in dirstate
3237 # file has not changed in dirstate
3238 if node == parent:
3238 if node == parent:
3239 if exact:
3239 if exact:
3240 ui.warn(_('no changes needed to %s\n') % rel)
3240 ui.warn(_('no changes needed to %s\n') % rel)
3241 continue
3241 continue
3242 if pmf is None:
3242 if pmf is None:
3243 # only need parent manifest in this unlikely case,
3243 # only need parent manifest in this unlikely case,
3244 # so do not read by default
3244 # so do not read by default
3245 pmf = repo[parent].manifest()
3245 pmf = repo[parent].manifest()
3246 if abs in pmf:
3246 if abs in pmf:
3247 if mfentry:
3247 if mfentry:
3248 # if version of file is same in parent and target
3248 # if version of file is same in parent and target
3249 # manifests, do nothing
3249 # manifests, do nothing
3250 if (pmf[abs] != mfentry or
3250 if (pmf[abs] != mfentry or
3251 pmf.flags(abs) != mf.flags(abs)):
3251 pmf.flags(abs) != mf.flags(abs)):
3252 handle(revert, False)
3252 handle(revert, False)
3253 else:
3253 else:
3254 handle(remove, False)
3254 handle(remove, False)
3255
3255
3256 if not opts.get('dry_run'):
3256 if not opts.get('dry_run'):
3257 def checkout(f):
3257 def checkout(f):
3258 fc = ctx[f]
3258 fc = ctx[f]
3259 repo.wwrite(f, fc.data(), fc.flags())
3259 repo.wwrite(f, fc.data(), fc.flags())
3260
3260
3261 audit_path = util.path_auditor(repo.root)
3261 audit_path = util.path_auditor(repo.root)
3262 for f in remove[0]:
3262 for f in remove[0]:
3263 if repo.dirstate[f] == 'a':
3263 if repo.dirstate[f] == 'a':
3264 repo.dirstate.forget(f)
3264 repo.dirstate.forget(f)
3265 continue
3265 continue
3266 audit_path(f)
3266 audit_path(f)
3267 try:
3267 try:
3268 util.unlink(repo.wjoin(f))
3268 util.unlink(repo.wjoin(f))
3269 except OSError:
3269 except OSError:
3270 pass
3270 pass
3271 repo.dirstate.remove(f)
3271 repo.dirstate.remove(f)
3272
3272
3273 normal = None
3273 normal = None
3274 if node == parent:
3274 if node == parent:
3275 # We're reverting to our parent. If possible, we'd like status
3275 # We're reverting to our parent. If possible, we'd like status
3276 # to report the file as clean. We have to use normallookup for
3276 # to report the file as clean. We have to use normallookup for
3277 # merges to avoid losing information about merged/dirty files.
3277 # merges to avoid losing information about merged/dirty files.
3278 if p2 != nullid:
3278 if p2 != nullid:
3279 normal = repo.dirstate.normallookup
3279 normal = repo.dirstate.normallookup
3280 else:
3280 else:
3281 normal = repo.dirstate.normal
3281 normal = repo.dirstate.normal
3282 for f in revert[0]:
3282 for f in revert[0]:
3283 checkout(f)
3283 checkout(f)
3284 if normal:
3284 if normal:
3285 normal(f)
3285 normal(f)
3286
3286
3287 for f in add[0]:
3287 for f in add[0]:
3288 checkout(f)
3288 checkout(f)
3289 repo.dirstate.add(f)
3289 repo.dirstate.add(f)
3290
3290
3291 normal = repo.dirstate.normallookup
3291 normal = repo.dirstate.normallookup
3292 if node == parent and p2 == nullid:
3292 if node == parent and p2 == nullid:
3293 normal = repo.dirstate.normal
3293 normal = repo.dirstate.normal
3294 for f in undelete[0]:
3294 for f in undelete[0]:
3295 checkout(f)
3295 checkout(f)
3296 normal(f)
3296 normal(f)
3297
3297
3298 finally:
3298 finally:
3299 wlock.release()
3299 wlock.release()
3300
3300
3301 def rollback(ui, repo, **opts):
3301 def rollback(ui, repo, **opts):
3302 """roll back the last transaction (dangerous)
3302 """roll back the last transaction (dangerous)
3303
3303
3304 This command should be used with care. There is only one level of
3304 This command should be used with care. There is only one level of
3305 rollback, and there is no way to undo a rollback. It will also
3305 rollback, and there is no way to undo a rollback. It will also
3306 restore the dirstate at the time of the last transaction, losing
3306 restore the dirstate at the time of the last transaction, losing
3307 any dirstate changes since that time. This command does not alter
3307 any dirstate changes since that time. This command does not alter
3308 the working directory.
3308 the working directory.
3309
3309
3310 Transactions are used to encapsulate the effects of all commands
3310 Transactions are used to encapsulate the effects of all commands
3311 that create new changesets or propagate existing changesets into a
3311 that create new changesets or propagate existing changesets into a
3312 repository. For example, the following commands are transactional,
3312 repository. For example, the following commands are transactional,
3313 and their effects can be rolled back:
3313 and their effects can be rolled back:
3314
3314
3315 - commit
3315 - commit
3316 - import
3316 - import
3317 - pull
3317 - pull
3318 - push (with this repository as the destination)
3318 - push (with this repository as the destination)
3319 - unbundle
3319 - unbundle
3320
3320
3321 This command is not intended for use on public repositories. Once
3321 This command is not intended for use on public repositories. Once
3322 changes are visible for pull by other users, rolling a transaction
3322 changes are visible for pull by other users, rolling a transaction
3323 back locally is ineffective (someone else may already have pulled
3323 back locally is ineffective (someone else may already have pulled
3324 the changes). Furthermore, a race is possible with readers of the
3324 the changes). Furthermore, a race is possible with readers of the
3325 repository; for example an in-progress pull from the repository
3325 repository; for example an in-progress pull from the repository
3326 may fail if a rollback is performed.
3326 may fail if a rollback is performed.
3327
3327
3328 Returns 0 on success, 1 if no rollback data is available.
3328 Returns 0 on success, 1 if no rollback data is available.
3329 """
3329 """
3330 return repo.rollback(opts.get('dry_run'))
3330 return repo.rollback(opts.get('dry_run'))
3331
3331
3332 def root(ui, repo):
3332 def root(ui, repo):
3333 """print the root (top) of the current working directory
3333 """print the root (top) of the current working directory
3334
3334
3335 Print the root directory of the current repository.
3335 Print the root directory of the current repository.
3336
3336
3337 Returns 0 on success.
3337 Returns 0 on success.
3338 """
3338 """
3339 ui.write(repo.root + "\n")
3339 ui.write(repo.root + "\n")
3340
3340
3341 def serve(ui, repo, **opts):
3341 def serve(ui, repo, **opts):
3342 """start stand-alone webserver
3342 """start stand-alone webserver
3343
3343
3344 Start a local HTTP repository browser and pull server. You can use
3344 Start a local HTTP repository browser and pull server. You can use
3345 this for ad-hoc sharing and browing of repositories. It is
3345 this for ad-hoc sharing and browing of repositories. It is
3346 recommended to use a real web server to serve a repository for
3346 recommended to use a real web server to serve a repository for
3347 longer periods of time.
3347 longer periods of time.
3348
3348
3349 Please note that the server does not implement access control.
3349 Please note that the server does not implement access control.
3350 This means that, by default, anybody can read from the server and
3350 This means that, by default, anybody can read from the server and
3351 nobody can write to it by default. Set the ``web.allow_push``
3351 nobody can write to it by default. Set the ``web.allow_push``
3352 option to ``*`` to allow everybody to push to the server. You
3352 option to ``*`` to allow everybody to push to the server. You
3353 should use a real web server if you need to authenticate users.
3353 should use a real web server if you need to authenticate users.
3354
3354
3355 By default, the server logs accesses to stdout and errors to
3355 By default, the server logs accesses to stdout and errors to
3356 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3356 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3357 files.
3357 files.
3358
3358
3359 To have the server choose a free port number to listen on, specify
3359 To have the server choose a free port number to listen on, specify
3360 a port number of 0; in this case, the server will print the port
3360 a port number of 0; in this case, the server will print the port
3361 number it uses.
3361 number it uses.
3362
3362
3363 Returns 0 on success.
3363 Returns 0 on success.
3364 """
3364 """
3365
3365
3366 if opts["stdio"]:
3366 if opts["stdio"]:
3367 if repo is None:
3367 if repo is None:
3368 raise error.RepoError(_("There is no Mercurial repository here"
3368 raise error.RepoError(_("There is no Mercurial repository here"
3369 " (.hg not found)"))
3369 " (.hg not found)"))
3370 s = sshserver.sshserver(ui, repo)
3370 s = sshserver.sshserver(ui, repo)
3371 s.serve_forever()
3371 s.serve_forever()
3372
3372
3373 # this way we can check if something was given in the command-line
3373 # this way we can check if something was given in the command-line
3374 if opts.get('port'):
3374 if opts.get('port'):
3375 opts['port'] = util.getport(opts.get('port'))
3375 opts['port'] = util.getport(opts.get('port'))
3376
3376
3377 baseui = repo and repo.baseui or ui
3377 baseui = repo and repo.baseui or ui
3378 optlist = ("name templates style address port prefix ipv6"
3378 optlist = ("name templates style address port prefix ipv6"
3379 " accesslog errorlog certificate encoding")
3379 " accesslog errorlog certificate encoding")
3380 for o in optlist.split():
3380 for o in optlist.split():
3381 val = opts.get(o, '')
3381 val = opts.get(o, '')
3382 if val in (None, ''): # should check against default options instead
3382 if val in (None, ''): # should check against default options instead
3383 continue
3383 continue
3384 baseui.setconfig("web", o, val)
3384 baseui.setconfig("web", o, val)
3385 if repo and repo.ui != baseui:
3385 if repo and repo.ui != baseui:
3386 repo.ui.setconfig("web", o, val)
3386 repo.ui.setconfig("web", o, val)
3387
3387
3388 o = opts.get('web_conf') or opts.get('webdir_conf')
3388 o = opts.get('web_conf') or opts.get('webdir_conf')
3389 if not o:
3389 if not o:
3390 if not repo:
3390 if not repo:
3391 raise error.RepoError(_("There is no Mercurial repository"
3391 raise error.RepoError(_("There is no Mercurial repository"
3392 " here (.hg not found)"))
3392 " here (.hg not found)"))
3393 o = repo.root
3393 o = repo.root
3394
3394
3395 app = hgweb.hgweb(o, baseui=ui)
3395 app = hgweb.hgweb(o, baseui=ui)
3396
3396
3397 class service(object):
3397 class service(object):
3398 def init(self):
3398 def init(self):
3399 util.set_signal_handler()
3399 util.set_signal_handler()
3400 self.httpd = hgweb.server.create_server(ui, app)
3400 self.httpd = hgweb.server.create_server(ui, app)
3401
3401
3402 if opts['port'] and not ui.verbose:
3402 if opts['port'] and not ui.verbose:
3403 return
3403 return
3404
3404
3405 if self.httpd.prefix:
3405 if self.httpd.prefix:
3406 prefix = self.httpd.prefix.strip('/') + '/'
3406 prefix = self.httpd.prefix.strip('/') + '/'
3407 else:
3407 else:
3408 prefix = ''
3408 prefix = ''
3409
3409
3410 port = ':%d' % self.httpd.port
3410 port = ':%d' % self.httpd.port
3411 if port == ':80':
3411 if port == ':80':
3412 port = ''
3412 port = ''
3413
3413
3414 bindaddr = self.httpd.addr
3414 bindaddr = self.httpd.addr
3415 if bindaddr == '0.0.0.0':
3415 if bindaddr == '0.0.0.0':
3416 bindaddr = '*'
3416 bindaddr = '*'
3417 elif ':' in bindaddr: # IPv6
3417 elif ':' in bindaddr: # IPv6
3418 bindaddr = '[%s]' % bindaddr
3418 bindaddr = '[%s]' % bindaddr
3419
3419
3420 fqaddr = self.httpd.fqaddr
3420 fqaddr = self.httpd.fqaddr
3421 if ':' in fqaddr:
3421 if ':' in fqaddr:
3422 fqaddr = '[%s]' % fqaddr
3422 fqaddr = '[%s]' % fqaddr
3423 if opts['port']:
3423 if opts['port']:
3424 write = ui.status
3424 write = ui.status
3425 else:
3425 else:
3426 write = ui.write
3426 write = ui.write
3427 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3427 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3428 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3428 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3429
3429
3430 def run(self):
3430 def run(self):
3431 self.httpd.serve_forever()
3431 self.httpd.serve_forever()
3432
3432
3433 service = service()
3433 service = service()
3434
3434
3435 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3435 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3436
3436
3437 def status(ui, repo, *pats, **opts):
3437 def status(ui, repo, *pats, **opts):
3438 """show changed files in the working directory
3438 """show changed files in the working directory
3439
3439
3440 Show status of files in the repository. If names are given, only
3440 Show status of files in the repository. If names are given, only
3441 files that match are shown. Files that are clean or ignored or
3441 files that match are shown. Files that are clean or ignored or
3442 the source of a copy/move operation, are not listed unless
3442 the source of a copy/move operation, are not listed unless
3443 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3443 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3444 Unless options described with "show only ..." are given, the
3444 Unless options described with "show only ..." are given, the
3445 options -mardu are used.
3445 options -mardu are used.
3446
3446
3447 Option -q/--quiet hides untracked (unknown and ignored) files
3447 Option -q/--quiet hides untracked (unknown and ignored) files
3448 unless explicitly requested with -u/--unknown or -i/--ignored.
3448 unless explicitly requested with -u/--unknown or -i/--ignored.
3449
3449
3450 NOTE: status may appear to disagree with diff if permissions have
3450 NOTE: status may appear to disagree with diff if permissions have
3451 changed or a merge has occurred. The standard diff format does not
3451 changed or a merge has occurred. The standard diff format does not
3452 report permission changes and diff only reports changes relative
3452 report permission changes and diff only reports changes relative
3453 to one merge parent.
3453 to one merge parent.
3454
3454
3455 If one revision is given, it is used as the base revision.
3455 If one revision is given, it is used as the base revision.
3456 If two revisions are given, the differences between them are
3456 If two revisions are given, the differences between them are
3457 shown. The --change option can also be used as a shortcut to list
3457 shown. The --change option can also be used as a shortcut to list
3458 the changed files of a revision from its first parent.
3458 the changed files of a revision from its first parent.
3459
3459
3460 The codes used to show the status of files are::
3460 The codes used to show the status of files are::
3461
3461
3462 M = modified
3462 M = modified
3463 A = added
3463 A = added
3464 R = removed
3464 R = removed
3465 C = clean
3465 C = clean
3466 ! = missing (deleted by non-hg command, but still tracked)
3466 ! = missing (deleted by non-hg command, but still tracked)
3467 ? = not tracked
3467 ? = not tracked
3468 I = ignored
3468 I = ignored
3469 = origin of the previous file listed as A (added)
3469 = origin of the previous file listed as A (added)
3470
3470
3471 Returns 0 on success.
3471 Returns 0 on success.
3472 """
3472 """
3473
3473
3474 revs = opts.get('rev')
3474 revs = opts.get('rev')
3475 change = opts.get('change')
3475 change = opts.get('change')
3476
3476
3477 if revs and change:
3477 if revs and change:
3478 msg = _('cannot specify --rev and --change at the same time')
3478 msg = _('cannot specify --rev and --change at the same time')
3479 raise util.Abort(msg)
3479 raise util.Abort(msg)
3480 elif change:
3480 elif change:
3481 node2 = repo.lookup(change)
3481 node2 = repo.lookup(change)
3482 node1 = repo[node2].parents()[0].node()
3482 node1 = repo[node2].parents()[0].node()
3483 else:
3483 else:
3484 node1, node2 = cmdutil.revpair(repo, revs)
3484 node1, node2 = cmdutil.revpair(repo, revs)
3485
3485
3486 cwd = (pats and repo.getcwd()) or ''
3486 cwd = (pats and repo.getcwd()) or ''
3487 end = opts.get('print0') and '\0' or '\n'
3487 end = opts.get('print0') and '\0' or '\n'
3488 copy = {}
3488 copy = {}
3489 states = 'modified added removed deleted unknown ignored clean'.split()
3489 states = 'modified added removed deleted unknown ignored clean'.split()
3490 show = [k for k in states if opts.get(k)]
3490 show = [k for k in states if opts.get(k)]
3491 if opts.get('all'):
3491 if opts.get('all'):
3492 show += ui.quiet and (states[:4] + ['clean']) or states
3492 show += ui.quiet and (states[:4] + ['clean']) or states
3493 if not show:
3493 if not show:
3494 show = ui.quiet and states[:4] or states[:5]
3494 show = ui.quiet and states[:4] or states[:5]
3495
3495
3496 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3496 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3497 'ignored' in show, 'clean' in show, 'unknown' in show,
3497 'ignored' in show, 'clean' in show, 'unknown' in show,
3498 opts.get('subrepos'))
3498 opts.get('subrepos'))
3499 changestates = zip(states, 'MAR!?IC', stat)
3499 changestates = zip(states, 'MAR!?IC', stat)
3500
3500
3501 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3501 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3502 ctxn = repo[nullid]
3502 ctxn = repo[nullid]
3503 ctx1 = repo[node1]
3503 ctx1 = repo[node1]
3504 ctx2 = repo[node2]
3504 ctx2 = repo[node2]
3505 added = stat[1]
3505 added = stat[1]
3506 if node2 is None:
3506 if node2 is None:
3507 added = stat[0] + stat[1] # merged?
3507 added = stat[0] + stat[1] # merged?
3508
3508
3509 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3509 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3510 if k in added:
3510 if k in added:
3511 copy[k] = v
3511 copy[k] = v
3512 elif v in added:
3512 elif v in added:
3513 copy[v] = k
3513 copy[v] = k
3514
3514
3515 for state, char, files in changestates:
3515 for state, char, files in changestates:
3516 if state in show:
3516 if state in show:
3517 format = "%s %%s%s" % (char, end)
3517 format = "%s %%s%s" % (char, end)
3518 if opts.get('no_status'):
3518 if opts.get('no_status'):
3519 format = "%%s%s" % end
3519 format = "%%s%s" % end
3520
3520
3521 for f in files:
3521 for f in files:
3522 ui.write(format % repo.pathto(f, cwd),
3522 ui.write(format % repo.pathto(f, cwd),
3523 label='status.' + state)
3523 label='status.' + state)
3524 if f in copy:
3524 if f in copy:
3525 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3525 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3526 label='status.copied')
3526 label='status.copied')
3527
3527
3528 def summary(ui, repo, **opts):
3528 def summary(ui, repo, **opts):
3529 """summarize working directory state
3529 """summarize working directory state
3530
3530
3531 This generates a brief summary of the working directory state,
3531 This generates a brief summary of the working directory state,
3532 including parents, branch, commit status, and available updates.
3532 including parents, branch, commit status, and available updates.
3533
3533
3534 With the --remote option, this will check the default paths for
3534 With the --remote option, this will check the default paths for
3535 incoming and outgoing changes. This can be time-consuming.
3535 incoming and outgoing changes. This can be time-consuming.
3536
3536
3537 Returns 0 on success.
3537 Returns 0 on success.
3538 """
3538 """
3539
3539
3540 ctx = repo[None]
3540 ctx = repo[None]
3541 parents = ctx.parents()
3541 parents = ctx.parents()
3542 pnode = parents[0].node()
3542 pnode = parents[0].node()
3543
3543
3544 for p in parents:
3544 for p in parents:
3545 # label with log.changeset (instead of log.parent) since this
3545 # label with log.changeset (instead of log.parent) since this
3546 # shows a working directory parent *changeset*:
3546 # shows a working directory parent *changeset*:
3547 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3547 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3548 label='log.changeset')
3548 label='log.changeset')
3549 ui.write(' '.join(p.tags()), label='log.tag')
3549 ui.write(' '.join(p.tags()), label='log.tag')
3550 if p.rev() == -1:
3550 if p.rev() == -1:
3551 if not len(repo):
3551 if not len(repo):
3552 ui.write(_(' (empty repository)'))
3552 ui.write(_(' (empty repository)'))
3553 else:
3553 else:
3554 ui.write(_(' (no revision checked out)'))
3554 ui.write(_(' (no revision checked out)'))
3555 ui.write('\n')
3555 ui.write('\n')
3556 if p.description():
3556 if p.description():
3557 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3557 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3558 label='log.summary')
3558 label='log.summary')
3559
3559
3560 branch = ctx.branch()
3560 branch = ctx.branch()
3561 bheads = repo.branchheads(branch)
3561 bheads = repo.branchheads(branch)
3562 m = _('branch: %s\n') % branch
3562 m = _('branch: %s\n') % branch
3563 if branch != 'default':
3563 if branch != 'default':
3564 ui.write(m, label='log.branch')
3564 ui.write(m, label='log.branch')
3565 else:
3565 else:
3566 ui.status(m, label='log.branch')
3566 ui.status(m, label='log.branch')
3567
3567
3568 st = list(repo.status(unknown=True))[:6]
3568 st = list(repo.status(unknown=True))[:6]
3569
3569
3570 c = repo.dirstate.copies()
3570 c = repo.dirstate.copies()
3571 copied, renamed = [], []
3571 copied, renamed = [], []
3572 for d, s in c.iteritems():
3572 for d, s in c.iteritems():
3573 if s in st[2]:
3573 if s in st[2]:
3574 st[2].remove(s)
3574 st[2].remove(s)
3575 renamed.append(d)
3575 renamed.append(d)
3576 else:
3576 else:
3577 copied.append(d)
3577 copied.append(d)
3578 if d in st[1]:
3578 if d in st[1]:
3579 st[1].remove(d)
3579 st[1].remove(d)
3580 st.insert(3, renamed)
3580 st.insert(3, renamed)
3581 st.insert(4, copied)
3581 st.insert(4, copied)
3582
3582
3583 ms = mergemod.mergestate(repo)
3583 ms = mergemod.mergestate(repo)
3584 st.append([f for f in ms if ms[f] == 'u'])
3584 st.append([f for f in ms if ms[f] == 'u'])
3585
3585
3586 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3586 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3587 st.append(subs)
3587 st.append(subs)
3588
3588
3589 labels = [ui.label(_('%d modified'), 'status.modified'),
3589 labels = [ui.label(_('%d modified'), 'status.modified'),
3590 ui.label(_('%d added'), 'status.added'),
3590 ui.label(_('%d added'), 'status.added'),
3591 ui.label(_('%d removed'), 'status.removed'),
3591 ui.label(_('%d removed'), 'status.removed'),
3592 ui.label(_('%d renamed'), 'status.copied'),
3592 ui.label(_('%d renamed'), 'status.copied'),
3593 ui.label(_('%d copied'), 'status.copied'),
3593 ui.label(_('%d copied'), 'status.copied'),
3594 ui.label(_('%d deleted'), 'status.deleted'),
3594 ui.label(_('%d deleted'), 'status.deleted'),
3595 ui.label(_('%d unknown'), 'status.unknown'),
3595 ui.label(_('%d unknown'), 'status.unknown'),
3596 ui.label(_('%d ignored'), 'status.ignored'),
3596 ui.label(_('%d ignored'), 'status.ignored'),
3597 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3597 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3598 ui.label(_('%d subrepos'), 'status.modified')]
3598 ui.label(_('%d subrepos'), 'status.modified')]
3599 t = []
3599 t = []
3600 for s, l in zip(st, labels):
3600 for s, l in zip(st, labels):
3601 if s:
3601 if s:
3602 t.append(l % len(s))
3602 t.append(l % len(s))
3603
3603
3604 t = ', '.join(t)
3604 t = ', '.join(t)
3605 cleanworkdir = False
3605 cleanworkdir = False
3606
3606
3607 if len(parents) > 1:
3607 if len(parents) > 1:
3608 t += _(' (merge)')
3608 t += _(' (merge)')
3609 elif branch != parents[0].branch():
3609 elif branch != parents[0].branch():
3610 t += _(' (new branch)')
3610 t += _(' (new branch)')
3611 elif (parents[0].extra().get('close') and
3611 elif (parents[0].extra().get('close') and
3612 pnode in repo.branchheads(branch, closed=True)):
3612 pnode in repo.branchheads(branch, closed=True)):
3613 t += _(' (head closed)')
3613 t += _(' (head closed)')
3614 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3614 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3615 t += _(' (clean)')
3615 t += _(' (clean)')
3616 cleanworkdir = True
3616 cleanworkdir = True
3617 elif pnode not in bheads:
3617 elif pnode not in bheads:
3618 t += _(' (new branch head)')
3618 t += _(' (new branch head)')
3619
3619
3620 if cleanworkdir:
3620 if cleanworkdir:
3621 ui.status(_('commit: %s\n') % t.strip())
3621 ui.status(_('commit: %s\n') % t.strip())
3622 else:
3622 else:
3623 ui.write(_('commit: %s\n') % t.strip())
3623 ui.write(_('commit: %s\n') % t.strip())
3624
3624
3625 # all ancestors of branch heads - all ancestors of parent = new csets
3625 # all ancestors of branch heads - all ancestors of parent = new csets
3626 new = [0] * len(repo)
3626 new = [0] * len(repo)
3627 cl = repo.changelog
3627 cl = repo.changelog
3628 for a in [cl.rev(n) for n in bheads]:
3628 for a in [cl.rev(n) for n in bheads]:
3629 new[a] = 1
3629 new[a] = 1
3630 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3630 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3631 new[a] = 1
3631 new[a] = 1
3632 for a in [p.rev() for p in parents]:
3632 for a in [p.rev() for p in parents]:
3633 if a >= 0:
3633 if a >= 0:
3634 new[a] = 0
3634 new[a] = 0
3635 for a in cl.ancestors(*[p.rev() for p in parents]):
3635 for a in cl.ancestors(*[p.rev() for p in parents]):
3636 new[a] = 0
3636 new[a] = 0
3637 new = sum(new)
3637 new = sum(new)
3638
3638
3639 if new == 0:
3639 if new == 0:
3640 ui.status(_('update: (current)\n'))
3640 ui.status(_('update: (current)\n'))
3641 elif pnode not in bheads:
3641 elif pnode not in bheads:
3642 ui.write(_('update: %d new changesets (update)\n') % new)
3642 ui.write(_('update: %d new changesets (update)\n') % new)
3643 else:
3643 else:
3644 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3644 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3645 (new, len(bheads)))
3645 (new, len(bheads)))
3646
3646
3647 if opts.get('remote'):
3647 if opts.get('remote'):
3648 t = []
3648 t = []
3649 source, branches = hg.parseurl(ui.expandpath('default'))
3649 source, branches = hg.parseurl(ui.expandpath('default'))
3650 other = hg.repository(hg.remoteui(repo, {}), source)
3650 other = hg.repository(hg.remoteui(repo, {}), source)
3651 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3651 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3652 ui.debug('comparing with %s\n' % url.hidepassword(source))
3652 ui.debug('comparing with %s\n' % url.hidepassword(source))
3653 repo.ui.pushbuffer()
3653 repo.ui.pushbuffer()
3654 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3654 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3655 repo.ui.popbuffer()
3655 repo.ui.popbuffer()
3656 if incoming:
3656 if incoming:
3657 t.append(_('1 or more incoming'))
3657 t.append(_('1 or more incoming'))
3658
3658
3659 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3659 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3660 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3660 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3661 other = hg.repository(hg.remoteui(repo, {}), dest)
3661 other = hg.repository(hg.remoteui(repo, {}), dest)
3662 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3662 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3663 repo.ui.pushbuffer()
3663 repo.ui.pushbuffer()
3664 o = discovery.findoutgoing(repo, other)
3664 o = discovery.findoutgoing(repo, other)
3665 repo.ui.popbuffer()
3665 repo.ui.popbuffer()
3666 o = repo.changelog.nodesbetween(o, None)[0]
3666 o = repo.changelog.nodesbetween(o, None)[0]
3667 if o:
3667 if o:
3668 t.append(_('%d outgoing') % len(o))
3668 t.append(_('%d outgoing') % len(o))
3669
3669
3670 if t:
3670 if t:
3671 ui.write(_('remote: %s\n') % (', '.join(t)))
3671 ui.write(_('remote: %s\n') % (', '.join(t)))
3672 else:
3672 else:
3673 ui.status(_('remote: (synced)\n'))
3673 ui.status(_('remote: (synced)\n'))
3674
3674
3675 def tag(ui, repo, name1, *names, **opts):
3675 def tag(ui, repo, name1, *names, **opts):
3676 """add one or more tags for the current or given revision
3676 """add one or more tags for the current or given revision
3677
3677
3678 Name a particular revision using <name>.
3678 Name a particular revision using <name>.
3679
3679
3680 Tags are used to name particular revisions of the repository and are
3680 Tags are used to name particular revisions of the repository and are
3681 very useful to compare different revisions, to go back to significant
3681 very useful to compare different revisions, to go back to significant
3682 earlier versions or to mark branch points as releases, etc.
3682 earlier versions or to mark branch points as releases, etc.
3683
3683
3684 If no revision is given, the parent of the working directory is
3684 If no revision is given, the parent of the working directory is
3685 used, or tip if no revision is checked out.
3685 used, or tip if no revision is checked out.
3686
3686
3687 To facilitate version control, distribution, and merging of tags,
3687 To facilitate version control, distribution, and merging of tags,
3688 they are stored as a file named ".hgtags" which is managed
3688 they are stored as a file named ".hgtags" which is managed
3689 similarly to other project files and can be hand-edited if
3689 similarly to other project files and can be hand-edited if
3690 necessary. The file '.hg/localtags' is used for local tags (not
3690 necessary. The file '.hg/localtags' is used for local tags (not
3691 shared among repositories).
3691 shared among repositories).
3692
3692
3693 See :hg:`help dates` for a list of formats valid for -d/--date.
3693 See :hg:`help dates` for a list of formats valid for -d/--date.
3694
3694
3695 Since tag names have priority over branch names during revision
3695 Since tag names have priority over branch names during revision
3696 lookup, using an existing branch name as a tag name is discouraged.
3696 lookup, using an existing branch name as a tag name is discouraged.
3697
3697
3698 Returns 0 on success.
3698 Returns 0 on success.
3699 """
3699 """
3700
3700
3701 rev_ = "."
3701 rev_ = "."
3702 names = [t.strip() for t in (name1,) + names]
3702 names = [t.strip() for t in (name1,) + names]
3703 if len(names) != len(set(names)):
3703 if len(names) != len(set(names)):
3704 raise util.Abort(_('tag names must be unique'))
3704 raise util.Abort(_('tag names must be unique'))
3705 for n in names:
3705 for n in names:
3706 if n in ['tip', '.', 'null']:
3706 if n in ['tip', '.', 'null']:
3707 raise util.Abort(_('the name \'%s\' is reserved') % n)
3707 raise util.Abort(_('the name \'%s\' is reserved') % n)
3708 if not n:
3708 if not n:
3709 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3709 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3710 if opts.get('rev') and opts.get('remove'):
3710 if opts.get('rev') and opts.get('remove'):
3711 raise util.Abort(_("--rev and --remove are incompatible"))
3711 raise util.Abort(_("--rev and --remove are incompatible"))
3712 if opts.get('rev'):
3712 if opts.get('rev'):
3713 rev_ = opts['rev']
3713 rev_ = opts['rev']
3714 message = opts.get('message')
3714 message = opts.get('message')
3715 if opts.get('remove'):
3715 if opts.get('remove'):
3716 expectedtype = opts.get('local') and 'local' or 'global'
3716 expectedtype = opts.get('local') and 'local' or 'global'
3717 for n in names:
3717 for n in names:
3718 if not repo.tagtype(n):
3718 if not repo.tagtype(n):
3719 raise util.Abort(_('tag \'%s\' does not exist') % n)
3719 raise util.Abort(_('tag \'%s\' does not exist') % n)
3720 if repo.tagtype(n) != expectedtype:
3720 if repo.tagtype(n) != expectedtype:
3721 if expectedtype == 'global':
3721 if expectedtype == 'global':
3722 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3722 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3723 else:
3723 else:
3724 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3724 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3725 rev_ = nullid
3725 rev_ = nullid
3726 if not message:
3726 if not message:
3727 # we don't translate commit messages
3727 # we don't translate commit messages
3728 message = 'Removed tag %s' % ', '.join(names)
3728 message = 'Removed tag %s' % ', '.join(names)
3729 elif not opts.get('force'):
3729 elif not opts.get('force'):
3730 for n in names:
3730 for n in names:
3731 if n in repo.tags():
3731 if n in repo.tags():
3732 raise util.Abort(_('tag \'%s\' already exists '
3732 raise util.Abort(_('tag \'%s\' already exists '
3733 '(use -f to force)') % n)
3733 '(use -f to force)') % n)
3734 if not rev_ and repo.dirstate.parents()[1] != nullid:
3734 if not rev_ and repo.dirstate.parents()[1] != nullid:
3735 raise util.Abort(_('uncommitted merge - please provide a '
3735 raise util.Abort(_('uncommitted merge - please provide a '
3736 'specific revision'))
3736 'specific revision'))
3737 r = repo[rev_].node()
3737 r = repo[rev_].node()
3738
3738
3739 if not message:
3739 if not message:
3740 # we don't translate commit messages
3740 # we don't translate commit messages
3741 message = ('Added tag %s for changeset %s' %
3741 message = ('Added tag %s for changeset %s' %
3742 (', '.join(names), short(r)))
3742 (', '.join(names), short(r)))
3743
3743
3744 date = opts.get('date')
3744 date = opts.get('date')
3745 if date:
3745 if date:
3746 date = util.parsedate(date)
3746 date = util.parsedate(date)
3747
3747
3748 if opts.get('edit'):
3748 if opts.get('edit'):
3749 message = ui.edit(message, ui.username())
3749 message = ui.edit(message, ui.username())
3750
3750
3751 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3751 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3752
3752
3753 def tags(ui, repo):
3753 def tags(ui, repo):
3754 """list repository tags
3754 """list repository tags
3755
3755
3756 This lists both regular and local tags. When the -v/--verbose
3756 This lists both regular and local tags. When the -v/--verbose
3757 switch is used, a third column "local" is printed for local tags.
3757 switch is used, a third column "local" is printed for local tags.
3758
3758
3759 Returns 0 on success.
3759 Returns 0 on success.
3760 """
3760 """
3761
3761
3762 hexfunc = ui.debugflag and hex or short
3762 hexfunc = ui.debugflag and hex or short
3763 tagtype = ""
3763 tagtype = ""
3764
3764
3765 for t, n in reversed(repo.tagslist()):
3765 for t, n in reversed(repo.tagslist()):
3766 if ui.quiet:
3766 if ui.quiet:
3767 ui.write("%s\n" % t)
3767 ui.write("%s\n" % t)
3768 continue
3768 continue
3769
3769
3770 try:
3770 try:
3771 hn = hexfunc(n)
3771 hn = hexfunc(n)
3772 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3772 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3773 except error.LookupError:
3773 except error.LookupError:
3774 r = " ?:%s" % hn
3774 r = " ?:%s" % hn
3775 else:
3775 else:
3776 spaces = " " * (30 - encoding.colwidth(t))
3776 spaces = " " * (30 - encoding.colwidth(t))
3777 if ui.verbose:
3777 if ui.verbose:
3778 if repo.tagtype(t) == 'local':
3778 if repo.tagtype(t) == 'local':
3779 tagtype = " local"
3779 tagtype = " local"
3780 else:
3780 else:
3781 tagtype = ""
3781 tagtype = ""
3782 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3782 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3783
3783
3784 def tip(ui, repo, **opts):
3784 def tip(ui, repo, **opts):
3785 """show the tip revision
3785 """show the tip revision
3786
3786
3787 The tip revision (usually just called the tip) is the changeset
3787 The tip revision (usually just called the tip) is the changeset
3788 most recently added to the repository (and therefore the most
3788 most recently added to the repository (and therefore the most
3789 recently changed head).
3789 recently changed head).
3790
3790
3791 If you have just made a commit, that commit will be the tip. If
3791 If you have just made a commit, that commit will be the tip. If
3792 you have just pulled changes from another repository, the tip of
3792 you have just pulled changes from another repository, the tip of
3793 that repository becomes the current tip. The "tip" tag is special
3793 that repository becomes the current tip. The "tip" tag is special
3794 and cannot be renamed or assigned to a different changeset.
3794 and cannot be renamed or assigned to a different changeset.
3795
3795
3796 Returns 0 on success.
3796 Returns 0 on success.
3797 """
3797 """
3798 displayer = cmdutil.show_changeset(ui, repo, opts)
3798 displayer = cmdutil.show_changeset(ui, repo, opts)
3799 displayer.show(repo[len(repo) - 1])
3799 displayer.show(repo[len(repo) - 1])
3800 displayer.close()
3800 displayer.close()
3801
3801
3802 def unbundle(ui, repo, fname1, *fnames, **opts):
3802 def unbundle(ui, repo, fname1, *fnames, **opts):
3803 """apply one or more changegroup files
3803 """apply one or more changegroup files
3804
3804
3805 Apply one or more compressed changegroup files generated by the
3805 Apply one or more compressed changegroup files generated by the
3806 bundle command.
3806 bundle command.
3807
3807
3808 Returns 0 on success, 1 if an update has unresolved files.
3808 Returns 0 on success, 1 if an update has unresolved files.
3809 """
3809 """
3810 fnames = (fname1,) + fnames
3810 fnames = (fname1,) + fnames
3811
3811
3812 lock = repo.lock()
3812 lock = repo.lock()
3813 try:
3813 try:
3814 for fname in fnames:
3814 for fname in fnames:
3815 f = url.open(ui, fname)
3815 f = url.open(ui, fname)
3816 gen = changegroup.readbundle(f, fname)
3816 gen = changegroup.readbundle(f, fname)
3817 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3817 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3818 lock=lock)
3818 lock=lock)
3819 finally:
3819 finally:
3820 lock.release()
3820 lock.release()
3821
3821
3822 return postincoming(ui, repo, modheads, opts.get('update'), None)
3822 return postincoming(ui, repo, modheads, opts.get('update'), None)
3823
3823
3824 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3824 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3825 """update working directory (or switch revisions)
3825 """update working directory (or switch revisions)
3826
3826
3827 Update the repository's working directory to the specified
3827 Update the repository's working directory to the specified
3828 changeset.
3828 changeset.
3829
3829
3830 If no changeset is specified, attempt to update to the tip of the
3830 If no changeset is specified, attempt to update to the tip of the
3831 current branch. If this changeset is a descendant of the working
3831 current branch. If this changeset is a descendant of the working
3832 directory's parent, update to it, otherwise abort.
3832 directory's parent, update to it, otherwise abort.
3833
3833
3834 The following rules apply when the working directory contains
3834 The following rules apply when the working directory contains
3835 uncommitted changes:
3835 uncommitted changes:
3836
3836
3837 1. If neither -c/--check nor -C/--clean is specified, and if
3837 1. If neither -c/--check nor -C/--clean is specified, and if
3838 the requested changeset is an ancestor or descendant of
3838 the requested changeset is an ancestor or descendant of
3839 the working directory's parent, the uncommitted changes
3839 the working directory's parent, the uncommitted changes
3840 are merged into the requested changeset and the merged
3840 are merged into the requested changeset and the merged
3841 result is left uncommitted. If the requested changeset is
3841 result is left uncommitted. If the requested changeset is
3842 not an ancestor or descendant (that is, it is on another
3842 not an ancestor or descendant (that is, it is on another
3843 branch), the update is aborted and the uncommitted changes
3843 branch), the update is aborted and the uncommitted changes
3844 are preserved.
3844 are preserved.
3845
3845
3846 2. With the -c/--check option, the update is aborted and the
3846 2. With the -c/--check option, the update is aborted and the
3847 uncommitted changes are preserved.
3847 uncommitted changes are preserved.
3848
3848
3849 3. With the -C/--clean option, uncommitted changes are discarded and
3849 3. With the -C/--clean option, uncommitted changes are discarded and
3850 the working directory is updated to the requested changeset.
3850 the working directory is updated to the requested changeset.
3851
3851
3852 Use null as the changeset to remove the working directory (like
3852 Use null as the changeset to remove the working directory (like
3853 :hg:`clone -U`).
3853 :hg:`clone -U`).
3854
3854
3855 If you want to update just one file to an older changeset, use :hg:`revert`.
3855 If you want to update just one file to an older changeset, use :hg:`revert`.
3856
3856
3857 See :hg:`help dates` for a list of formats valid for -d/--date.
3857 See :hg:`help dates` for a list of formats valid for -d/--date.
3858
3858
3859 Returns 0 on success, 1 if there are unresolved files.
3859 Returns 0 on success, 1 if there are unresolved files.
3860 """
3860 """
3861 if rev and node:
3861 if rev and node:
3862 raise util.Abort(_("please specify just one revision"))
3862 raise util.Abort(_("please specify just one revision"))
3863
3863
3864 if not rev:
3864 if not rev:
3865 rev = node
3865 rev = node
3866
3866
3867 if check and clean:
3867 if check and clean:
3868 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3868 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3869
3869
3870 if check:
3870 if check:
3871 # we could use dirty() but we can ignore merge and branch trivia
3871 # we could use dirty() but we can ignore merge and branch trivia
3872 c = repo[None]
3872 c = repo[None]
3873 if c.modified() or c.added() or c.removed():
3873 if c.modified() or c.added() or c.removed():
3874 raise util.Abort(_("uncommitted local changes"))
3874 raise util.Abort(_("uncommitted local changes"))
3875
3875
3876 if date:
3876 if date:
3877 if rev:
3877 if rev:
3878 raise util.Abort(_("you can't specify a revision and a date"))
3878 raise util.Abort(_("you can't specify a revision and a date"))
3879 rev = cmdutil.finddate(ui, repo, date)
3879 rev = cmdutil.finddate(ui, repo, date)
3880
3880
3881 if clean or check:
3881 if clean or check:
3882 return hg.clean(repo, rev)
3882 return hg.clean(repo, rev)
3883 else:
3883 else:
3884 return hg.update(repo, rev)
3884 return hg.update(repo, rev)
3885
3885
3886 def verify(ui, repo):
3886 def verify(ui, repo):
3887 """verify the integrity of the repository
3887 """verify the integrity of the repository
3888
3888
3889 Verify the integrity of the current repository.
3889 Verify the integrity of the current repository.
3890
3890
3891 This will perform an extensive check of the repository's
3891 This will perform an extensive check of the repository's
3892 integrity, validating the hashes and checksums of each entry in
3892 integrity, validating the hashes and checksums of each entry in
3893 the changelog, manifest, and tracked files, as well as the
3893 the changelog, manifest, and tracked files, as well as the
3894 integrity of their crosslinks and indices.
3894 integrity of their crosslinks and indices.
3895
3895
3896 Returns 0 on success, 1 if errors are encountered.
3896 Returns 0 on success, 1 if errors are encountered.
3897 """
3897 """
3898 return hg.verify(repo)
3898 return hg.verify(repo)
3899
3899
3900 def version_(ui):
3900 def version_(ui):
3901 """output version and copyright information"""
3901 """output version and copyright information"""
3902 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3902 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3903 % util.version())
3903 % util.version())
3904 ui.status(_(
3904 ui.status(_(
3905 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3905 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3906 "This is free software; see the source for copying conditions. "
3906 "This is free software; see the source for copying conditions. "
3907 "There is NO\nwarranty; "
3907 "There is NO\nwarranty; "
3908 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3908 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3909 ))
3909 ))
3910
3910
3911 # Command options and aliases are listed here, alphabetically
3911 # Command options and aliases are listed here, alphabetically
3912
3912
3913 globalopts = [
3913 globalopts = [
3914 ('R', 'repository', '',
3914 ('R', 'repository', '',
3915 _('repository root directory or name of overlay bundle file'),
3915 _('repository root directory or name of overlay bundle file'),
3916 _('REPO')),
3916 _('REPO')),
3917 ('', 'cwd', '',
3917 ('', 'cwd', '',
3918 _('change working directory'), _('DIR')),
3918 _('change working directory'), _('DIR')),
3919 ('y', 'noninteractive', None,
3919 ('y', 'noninteractive', None,
3920 _('do not prompt, assume \'yes\' for any required answers')),
3920 _('do not prompt, assume \'yes\' for any required answers')),
3921 ('q', 'quiet', None, _('suppress output')),
3921 ('q', 'quiet', None, _('suppress output')),
3922 ('v', 'verbose', None, _('enable additional output')),
3922 ('v', 'verbose', None, _('enable additional output')),
3923 ('', 'config', [],
3923 ('', 'config', [],
3924 _('set/override config option (use \'section.name=value\')'),
3924 _('set/override config option (use \'section.name=value\')'),
3925 _('CONFIG')),
3925 _('CONFIG')),
3926 ('', 'debug', None, _('enable debugging output')),
3926 ('', 'debug', None, _('enable debugging output')),
3927 ('', 'debugger', None, _('start debugger')),
3927 ('', 'debugger', None, _('start debugger')),
3928 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3928 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3929 _('ENCODE')),
3929 _('ENCODE')),
3930 ('', 'encodingmode', encoding.encodingmode,
3930 ('', 'encodingmode', encoding.encodingmode,
3931 _('set the charset encoding mode'), _('MODE')),
3931 _('set the charset encoding mode'), _('MODE')),
3932 ('', 'traceback', None, _('always print a traceback on exception')),
3932 ('', 'traceback', None, _('always print a traceback on exception')),
3933 ('', 'time', None, _('time how long the command takes')),
3933 ('', 'time', None, _('time how long the command takes')),
3934 ('', 'profile', None, _('print command execution profile')),
3934 ('', 'profile', None, _('print command execution profile')),
3935 ('', 'version', None, _('output version information and exit')),
3935 ('', 'version', None, _('output version information and exit')),
3936 ('h', 'help', None, _('display help and exit')),
3936 ('h', 'help', None, _('display help and exit')),
3937 ]
3937 ]
3938
3938
3939 dryrunopts = [('n', 'dry-run', None,
3939 dryrunopts = [('n', 'dry-run', None,
3940 _('do not perform actions, just print output'))]
3940 _('do not perform actions, just print output'))]
3941
3941
3942 remoteopts = [
3942 remoteopts = [
3943 ('e', 'ssh', '',
3943 ('e', 'ssh', '',
3944 _('specify ssh command to use'), _('CMD')),
3944 _('specify ssh command to use'), _('CMD')),
3945 ('', 'remotecmd', '',
3945 ('', 'remotecmd', '',
3946 _('specify hg command to run on the remote side'), _('CMD')),
3946 _('specify hg command to run on the remote side'), _('CMD')),
3947 ]
3947 ]
3948
3948
3949 walkopts = [
3949 walkopts = [
3950 ('I', 'include', [],
3950 ('I', 'include', [],
3951 _('include names matching the given patterns'), _('PATTERN')),
3951 _('include names matching the given patterns'), _('PATTERN')),
3952 ('X', 'exclude', [],
3952 ('X', 'exclude', [],
3953 _('exclude names matching the given patterns'), _('PATTERN')),
3953 _('exclude names matching the given patterns'), _('PATTERN')),
3954 ]
3954 ]
3955
3955
3956 commitopts = [
3956 commitopts = [
3957 ('m', 'message', '',
3957 ('m', 'message', '',
3958 _('use text as commit message'), _('TEXT')),
3958 _('use text as commit message'), _('TEXT')),
3959 ('l', 'logfile', '',
3959 ('l', 'logfile', '',
3960 _('read commit message from file'), _('FILE')),
3960 _('read commit message from file'), _('FILE')),
3961 ]
3961 ]
3962
3962
3963 commitopts2 = [
3963 commitopts2 = [
3964 ('d', 'date', '',
3964 ('d', 'date', '',
3965 _('record datecode as commit date'), _('DATE')),
3965 _('record datecode as commit date'), _('DATE')),
3966 ('u', 'user', '',
3966 ('u', 'user', '',
3967 _('record the specified user as committer'), _('USER')),
3967 _('record the specified user as committer'), _('USER')),
3968 ]
3968 ]
3969
3969
3970 templateopts = [
3970 templateopts = [
3971 ('', 'style', '',
3971 ('', 'style', '',
3972 _('display using template map file'), _('STYLE')),
3972 _('display using template map file'), _('STYLE')),
3973 ('', 'template', '',
3973 ('', 'template', '',
3974 _('display with template'), _('TEMPLATE')),
3974 _('display with template'), _('TEMPLATE')),
3975 ]
3975 ]
3976
3976
3977 logopts = [
3977 logopts = [
3978 ('p', 'patch', None, _('show patch')),
3978 ('p', 'patch', None, _('show patch')),
3979 ('g', 'git', None, _('use git extended diff format')),
3979 ('g', 'git', None, _('use git extended diff format')),
3980 ('l', 'limit', '',
3980 ('l', 'limit', '',
3981 _('limit number of changes displayed'), _('NUM')),
3981 _('limit number of changes displayed'), _('NUM')),
3982 ('M', 'no-merges', None, _('do not show merges')),
3982 ('M', 'no-merges', None, _('do not show merges')),
3983 ('', 'stat', None, _('output diffstat-style summary of changes')),
3983 ('', 'stat', None, _('output diffstat-style summary of changes')),
3984 ] + templateopts
3984 ] + templateopts
3985
3985
3986 diffopts = [
3986 diffopts = [
3987 ('a', 'text', None, _('treat all files as text')),
3987 ('a', 'text', None, _('treat all files as text')),
3988 ('g', 'git', None, _('use git extended diff format')),
3988 ('g', 'git', None, _('use git extended diff format')),
3989 ('', 'nodates', None, _('omit dates from diff headers'))
3989 ('', 'nodates', None, _('omit dates from diff headers'))
3990 ]
3990 ]
3991
3991
3992 diffopts2 = [
3992 diffopts2 = [
3993 ('p', 'show-function', None, _('show which function each change is in')),
3993 ('p', 'show-function', None, _('show which function each change is in')),
3994 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3994 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3995 ('w', 'ignore-all-space', None,
3995 ('w', 'ignore-all-space', None,
3996 _('ignore white space when comparing lines')),
3996 _('ignore white space when comparing lines')),
3997 ('b', 'ignore-space-change', None,
3997 ('b', 'ignore-space-change', None,
3998 _('ignore changes in the amount of white space')),
3998 _('ignore changes in the amount of white space')),
3999 ('B', 'ignore-blank-lines', None,
3999 ('B', 'ignore-blank-lines', None,
4000 _('ignore changes whose lines are all blank')),
4000 _('ignore changes whose lines are all blank')),
4001 ('U', 'unified', '',
4001 ('U', 'unified', '',
4002 _('number of lines of context to show'), _('NUM')),
4002 _('number of lines of context to show'), _('NUM')),
4003 ('', 'stat', None, _('output diffstat-style summary of changes')),
4003 ('', 'stat', None, _('output diffstat-style summary of changes')),
4004 ]
4004 ]
4005
4005
4006 similarityopts = [
4006 similarityopts = [
4007 ('s', 'similarity', '',
4007 ('s', 'similarity', '',
4008 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4008 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4009 ]
4009 ]
4010
4010
4011 subrepoopts = [
4011 subrepoopts = [
4012 ('S', 'subrepos', None,
4012 ('S', 'subrepos', None,
4013 _('recurse into subrepositories'))
4013 _('recurse into subrepositories'))
4014 ]
4014 ]
4015
4015
4016 table = {
4016 table = {
4017 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
4017 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
4018 "addremove":
4018 "addremove":
4019 (addremove, similarityopts + walkopts + dryrunopts,
4019 (addremove, similarityopts + walkopts + dryrunopts,
4020 _('[OPTION]... [FILE]...')),
4020 _('[OPTION]... [FILE]...')),
4021 "^annotate|blame":
4021 "^annotate|blame":
4022 (annotate,
4022 (annotate,
4023 [('r', 'rev', '',
4023 [('r', 'rev', '',
4024 _('annotate the specified revision'), _('REV')),
4024 _('annotate the specified revision'), _('REV')),
4025 ('', 'follow', None,
4025 ('', 'follow', None,
4026 _('follow copies/renames and list the filename (DEPRECATED)')),
4026 _('follow copies/renames and list the filename (DEPRECATED)')),
4027 ('', 'no-follow', None, _("don't follow copies and renames")),
4027 ('', 'no-follow', None, _("don't follow copies and renames")),
4028 ('a', 'text', None, _('treat all files as text')),
4028 ('a', 'text', None, _('treat all files as text')),
4029 ('u', 'user', None, _('list the author (long with -v)')),
4029 ('u', 'user', None, _('list the author (long with -v)')),
4030 ('f', 'file', None, _('list the filename')),
4030 ('f', 'file', None, _('list the filename')),
4031 ('d', 'date', None, _('list the date (short with -q)')),
4031 ('d', 'date', None, _('list the date (short with -q)')),
4032 ('n', 'number', None, _('list the revision number (default)')),
4032 ('n', 'number', None, _('list the revision number (default)')),
4033 ('c', 'changeset', None, _('list the changeset')),
4033 ('c', 'changeset', None, _('list the changeset')),
4034 ('l', 'line-number', None,
4034 ('l', 'line-number', None,
4035 _('show line number at the first appearance'))
4035 _('show line number at the first appearance'))
4036 ] + walkopts,
4036 ] + walkopts,
4037 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4037 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4038 "archive":
4038 "archive":
4039 (archive,
4039 (archive,
4040 [('', 'no-decode', None, _('do not pass files through decoders')),
4040 [('', 'no-decode', None, _('do not pass files through decoders')),
4041 ('p', 'prefix', '',
4041 ('p', 'prefix', '',
4042 _('directory prefix for files in archive'), _('PREFIX')),
4042 _('directory prefix for files in archive'), _('PREFIX')),
4043 ('r', 'rev', '',
4043 ('r', 'rev', '',
4044 _('revision to distribute'), _('REV')),
4044 _('revision to distribute'), _('REV')),
4045 ('t', 'type', '',
4045 ('t', 'type', '',
4046 _('type of distribution to create'), _('TYPE')),
4046 _('type of distribution to create'), _('TYPE')),
4047 ] + walkopts,
4047 ] + walkopts,
4048 _('[OPTION]... DEST')),
4048 _('[OPTION]... DEST')),
4049 "backout":
4049 "backout":
4050 (backout,
4050 (backout,
4051 [('', 'merge', None,
4051 [('', 'merge', None,
4052 _('merge with old dirstate parent after backout')),
4052 _('merge with old dirstate parent after backout')),
4053 ('', 'parent', '',
4053 ('', 'parent', '',
4054 _('parent to choose when backing out merge'), _('REV')),
4054 _('parent to choose when backing out merge'), _('REV')),
4055 ('r', 'rev', '',
4055 ('r', 'rev', '',
4056 _('revision to backout'), _('REV')),
4056 _('revision to backout'), _('REV')),
4057 ] + walkopts + commitopts + commitopts2,
4057 ] + walkopts + commitopts + commitopts2,
4058 _('[OPTION]... [-r] REV')),
4058 _('[OPTION]... [-r] REV')),
4059 "bisect":
4059 "bisect":
4060 (bisect,
4060 (bisect,
4061 [('r', 'reset', False, _('reset bisect state')),
4061 [('r', 'reset', False, _('reset bisect state')),
4062 ('g', 'good', False, _('mark changeset good')),
4062 ('g', 'good', False, _('mark changeset good')),
4063 ('b', 'bad', False, _('mark changeset bad')),
4063 ('b', 'bad', False, _('mark changeset bad')),
4064 ('s', 'skip', False, _('skip testing changeset')),
4064 ('s', 'skip', False, _('skip testing changeset')),
4065 ('c', 'command', '',
4065 ('c', 'command', '',
4066 _('use command to check changeset state'), _('CMD')),
4066 _('use command to check changeset state'), _('CMD')),
4067 ('U', 'noupdate', False, _('do not update to target'))],
4067 ('U', 'noupdate', False, _('do not update to target'))],
4068 _("[-gbsr] [-U] [-c CMD] [REV]")),
4068 _("[-gbsr] [-U] [-c CMD] [REV]")),
4069 "branch":
4069 "branch":
4070 (branch,
4070 (branch,
4071 [('f', 'force', None,
4071 [('f', 'force', None,
4072 _('set branch name even if it shadows an existing branch')),
4072 _('set branch name even if it shadows an existing branch')),
4073 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4073 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4074 _('[-fC] [NAME]')),
4074 _('[-fC] [NAME]')),
4075 "branches":
4075 "branches":
4076 (branches,
4076 (branches,
4077 [('a', 'active', False,
4077 [('a', 'active', False,
4078 _('show only branches that have unmerged heads')),
4078 _('show only branches that have unmerged heads')),
4079 ('c', 'closed', False,
4079 ('c', 'closed', False,
4080 _('show normal and closed branches'))],
4080 _('show normal and closed branches'))],
4081 _('[-ac]')),
4081 _('[-ac]')),
4082 "bundle":
4082 "bundle":
4083 (bundle,
4083 (bundle,
4084 [('f', 'force', None,
4084 [('f', 'force', None,
4085 _('run even when the destination is unrelated')),
4085 _('run even when the destination is unrelated')),
4086 ('r', 'rev', [],
4086 ('r', 'rev', [],
4087 _('a changeset intended to be added to the destination'),
4087 _('a changeset intended to be added to the destination'),
4088 _('REV')),
4088 _('REV')),
4089 ('b', 'branch', [],
4089 ('b', 'branch', [],
4090 _('a specific branch you would like to bundle'),
4090 _('a specific branch you would like to bundle'),
4091 _('BRANCH')),
4091 _('BRANCH')),
4092 ('', 'base', [],
4092 ('', 'base', [],
4093 _('a base changeset assumed to be available at the destination'),
4093 _('a base changeset assumed to be available at the destination'),
4094 _('REV')),
4094 _('REV')),
4095 ('a', 'all', None, _('bundle all changesets in the repository')),
4095 ('a', 'all', None, _('bundle all changesets in the repository')),
4096 ('t', 'type', 'bzip2',
4096 ('t', 'type', 'bzip2',
4097 _('bundle compression type to use'), _('TYPE')),
4097 _('bundle compression type to use'), _('TYPE')),
4098 ] + remoteopts,
4098 ] + remoteopts,
4099 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4099 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4100 "cat":
4100 "cat":
4101 (cat,
4101 (cat,
4102 [('o', 'output', '',
4102 [('o', 'output', '',
4103 _('print output to file with formatted name'), _('FORMAT')),
4103 _('print output to file with formatted name'), _('FORMAT')),
4104 ('r', 'rev', '',
4104 ('r', 'rev', '',
4105 _('print the given revision'), _('REV')),
4105 _('print the given revision'), _('REV')),
4106 ('', 'decode', None, _('apply any matching decode filter')),
4106 ('', 'decode', None, _('apply any matching decode filter')),
4107 ] + walkopts,
4107 ] + walkopts,
4108 _('[OPTION]... FILE...')),
4108 _('[OPTION]... FILE...')),
4109 "^clone":
4109 "^clone":
4110 (clone,
4110 (clone,
4111 [('U', 'noupdate', None,
4111 [('U', 'noupdate', None,
4112 _('the clone will include an empty working copy (only a repository)')),
4112 _('the clone will include an empty working copy (only a repository)')),
4113 ('u', 'updaterev', '',
4113 ('u', 'updaterev', '',
4114 _('revision, tag or branch to check out'), _('REV')),
4114 _('revision, tag or branch to check out'), _('REV')),
4115 ('r', 'rev', [],
4115 ('r', 'rev', [],
4116 _('include the specified changeset'), _('REV')),
4116 _('include the specified changeset'), _('REV')),
4117 ('b', 'branch', [],
4117 ('b', 'branch', [],
4118 _('clone only the specified branch'), _('BRANCH')),
4118 _('clone only the specified branch'), _('BRANCH')),
4119 ('', 'pull', None, _('use pull protocol to copy metadata')),
4119 ('', 'pull', None, _('use pull protocol to copy metadata')),
4120 ('', 'uncompressed', None,
4120 ('', 'uncompressed', None,
4121 _('use uncompressed transfer (fast over LAN)')),
4121 _('use uncompressed transfer (fast over LAN)')),
4122 ] + remoteopts,
4122 ] + remoteopts,
4123 _('[OPTION]... SOURCE [DEST]')),
4123 _('[OPTION]... SOURCE [DEST]')),
4124 "^commit|ci":
4124 "^commit|ci":
4125 (commit,
4125 (commit,
4126 [('A', 'addremove', None,
4126 [('A', 'addremove', None,
4127 _('mark new/missing files as added/removed before committing')),
4127 _('mark new/missing files as added/removed before committing')),
4128 ('', 'close-branch', None,
4128 ('', 'close-branch', None,
4129 _('mark a branch as closed, hiding it from the branch list')),
4129 _('mark a branch as closed, hiding it from the branch list')),
4130 ] + walkopts + commitopts + commitopts2,
4130 ] + walkopts + commitopts + commitopts2,
4131 _('[OPTION]... [FILE]...')),
4131 _('[OPTION]... [FILE]...')),
4132 "copy|cp":
4132 "copy|cp":
4133 (copy,
4133 (copy,
4134 [('A', 'after', None, _('record a copy that has already occurred')),
4134 [('A', 'after', None, _('record a copy that has already occurred')),
4135 ('f', 'force', None,
4135 ('f', 'force', None,
4136 _('forcibly copy over an existing managed file')),
4136 _('forcibly copy over an existing managed file')),
4137 ] + walkopts + dryrunopts,
4137 ] + walkopts + dryrunopts,
4138 _('[OPTION]... [SOURCE]... DEST')),
4138 _('[OPTION]... [SOURCE]... DEST')),
4139 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4139 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4140 "debugbuilddag":
4140 "debugbuilddag":
4141 (debugbuilddag,
4141 (debugbuilddag,
4142 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4142 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4143 ('a', 'appended-file', None, _('add single file all revs append to')),
4143 ('a', 'appended-file', None, _('add single file all revs append to')),
4144 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4144 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4145 ('n', 'new-file', None, _('add new file at each rev')),
4145 ('n', 'new-file', None, _('add new file at each rev')),
4146 ],
4146 ],
4147 _('[OPTION]... TEXT')),
4147 _('[OPTION]... TEXT')),
4148 "debugcheckstate": (debugcheckstate, [], ''),
4148 "debugcheckstate": (debugcheckstate, [], ''),
4149 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4149 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4150 "debugcomplete":
4150 "debugcomplete":
4151 (debugcomplete,
4151 (debugcomplete,
4152 [('o', 'options', None, _('show the command options'))],
4152 [('o', 'options', None, _('show the command options'))],
4153 _('[-o] CMD')),
4153 _('[-o] CMD')),
4154 "debugdag":
4154 "debugdag":
4155 (debugdag,
4155 (debugdag,
4156 [('t', 'tags', None, _('use tags as labels')),
4156 [('t', 'tags', None, _('use tags as labels')),
4157 ('b', 'branches', None, _('annotate with branch names')),
4157 ('b', 'branches', None, _('annotate with branch names')),
4158 ('', 'dots', None, _('use dots for runs')),
4158 ('', 'dots', None, _('use dots for runs')),
4159 ('s', 'spaces', None, _('separate elements by spaces')),
4159 ('s', 'spaces', None, _('separate elements by spaces')),
4160 ],
4160 ],
4161 _('[OPTION]... [FILE [REV]...]')),
4161 _('[OPTION]... [FILE [REV]...]')),
4162 "debugdate":
4162 "debugdate":
4163 (debugdate,
4163 (debugdate,
4164 [('e', 'extended', None, _('try extended date formats'))],
4164 [('e', 'extended', None, _('try extended date formats'))],
4165 _('[-e] DATE [RANGE]')),
4165 _('[-e] DATE [RANGE]')),
4166 "debugdata": (debugdata, [], _('FILE REV')),
4166 "debugdata": (debugdata, [], _('FILE REV')),
4167 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4167 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4168 "debugindex": (debugindex, [], _('FILE')),
4168 "debugindex": (debugindex, [], _('FILE')),
4169 "debugindexdot": (debugindexdot, [], _('FILE')),
4169 "debugindexdot": (debugindexdot, [], _('FILE')),
4170 "debuginstall": (debuginstall, [], ''),
4170 "debuginstall": (debuginstall, [], ''),
4171 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4171 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4172 "debugrebuildstate":
4172 "debugrebuildstate":
4173 (debugrebuildstate,
4173 (debugrebuildstate,
4174 [('r', 'rev', '',
4174 [('r', 'rev', '',
4175 _('revision to rebuild to'), _('REV'))],
4175 _('revision to rebuild to'), _('REV'))],
4176 _('[-r REV] [REV]')),
4176 _('[-r REV] [REV]')),
4177 "debugrename":
4177 "debugrename":
4178 (debugrename,
4178 (debugrename,
4179 [('r', 'rev', '',
4179 [('r', 'rev', '',
4180 _('revision to debug'), _('REV'))],
4180 _('revision to debug'), _('REV'))],
4181 _('[-r REV] FILE')),
4181 _('[-r REV] FILE')),
4182 "debugrevspec":
4182 "debugrevspec":
4183 (debugrevspec, [], ('REVSPEC')),
4183 (debugrevspec, [], ('REVSPEC')),
4184 "debugsetparents":
4184 "debugsetparents":
4185 (debugsetparents, [], _('REV1 [REV2]')),
4185 (debugsetparents, [], _('REV1 [REV2]')),
4186 "debugstate":
4186 "debugstate":
4187 (debugstate,
4187 (debugstate,
4188 [('', 'nodates', None, _('do not display the saved mtime'))],
4188 [('', 'nodates', None, _('do not display the saved mtime'))],
4189 _('[OPTION]...')),
4189 _('[OPTION]...')),
4190 "debugsub":
4190 "debugsub":
4191 (debugsub,
4191 (debugsub,
4192 [('r', 'rev', '',
4192 [('r', 'rev', '',
4193 _('revision to check'), _('REV'))],
4193 _('revision to check'), _('REV'))],
4194 _('[-r REV] [REV]')),
4194 _('[-r REV] [REV]')),
4195 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4195 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4196 "^diff":
4196 "^diff":
4197 (diff,
4197 (diff,
4198 [('r', 'rev', [],
4198 [('r', 'rev', [],
4199 _('revision'), _('REV')),
4199 _('revision'), _('REV')),
4200 ('c', 'change', '',
4200 ('c', 'change', '',
4201 _('change made by revision'), _('REV'))
4201 _('change made by revision'), _('REV'))
4202 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4202 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4203 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4203 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4204 "^export":
4204 "^export":
4205 (export,
4205 (export,
4206 [('o', 'output', '',
4206 [('o', 'output', '',
4207 _('print output to file with formatted name'), _('FORMAT')),
4207 _('print output to file with formatted name'), _('FORMAT')),
4208 ('', 'switch-parent', None, _('diff against the second parent')),
4208 ('', 'switch-parent', None, _('diff against the second parent')),
4209 ('r', 'rev', [],
4209 ('r', 'rev', [],
4210 _('revisions to export'), _('REV')),
4210 _('revisions to export'), _('REV')),
4211 ] + diffopts,
4211 ] + diffopts,
4212 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4212 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4213 "^forget":
4213 "^forget":
4214 (forget,
4214 (forget,
4215 [] + walkopts,
4215 [] + walkopts,
4216 _('[OPTION]... FILE...')),
4216 _('[OPTION]... FILE...')),
4217 "grep":
4217 "grep":
4218 (grep,
4218 (grep,
4219 [('0', 'print0', None, _('end fields with NUL')),
4219 [('0', 'print0', None, _('end fields with NUL')),
4220 ('', 'all', None, _('print all revisions that match')),
4220 ('', 'all', None, _('print all revisions that match')),
4221 ('f', 'follow', None,
4221 ('f', 'follow', None,
4222 _('follow changeset history,'
4222 _('follow changeset history,'
4223 ' or file history across copies and renames')),
4223 ' or file history across copies and renames')),
4224 ('i', 'ignore-case', None, _('ignore case when matching')),
4224 ('i', 'ignore-case', None, _('ignore case when matching')),
4225 ('l', 'files-with-matches', None,
4225 ('l', 'files-with-matches', None,
4226 _('print only filenames and revisions that match')),
4226 _('print only filenames and revisions that match')),
4227 ('n', 'line-number', None, _('print matching line numbers')),
4227 ('n', 'line-number', None, _('print matching line numbers')),
4228 ('r', 'rev', [],
4228 ('r', 'rev', [],
4229 _('only search files changed within revision range'), _('REV')),
4229 _('only search files changed within revision range'), _('REV')),
4230 ('u', 'user', None, _('list the author (long with -v)')),
4230 ('u', 'user', None, _('list the author (long with -v)')),
4231 ('d', 'date', None, _('list the date (short with -q)')),
4231 ('d', 'date', None, _('list the date (short with -q)')),
4232 ] + walkopts,
4232 ] + walkopts,
4233 _('[OPTION]... PATTERN [FILE]...')),
4233 _('[OPTION]... PATTERN [FILE]...')),
4234 "heads":
4234 "heads":
4235 (heads,
4235 (heads,
4236 [('r', 'rev', '',
4236 [('r', 'rev', '',
4237 _('show only heads which are descendants of REV'), _('REV')),
4237 _('show only heads which are descendants of REV'), _('REV')),
4238 ('t', 'topo', False, _('show topological heads only')),
4238 ('t', 'topo', False, _('show topological heads only')),
4239 ('a', 'active', False,
4239 ('a', 'active', False,
4240 _('show active branchheads only (DEPRECATED)')),
4240 _('show active branchheads only (DEPRECATED)')),
4241 ('c', 'closed', False,
4241 ('c', 'closed', False,
4242 _('show normal and closed branch heads')),
4242 _('show normal and closed branch heads')),
4243 ] + templateopts,
4243 ] + templateopts,
4244 _('[-ac] [-r REV] [REV]...')),
4244 _('[-ac] [-r REV] [REV]...')),
4245 "help": (help_, [], _('[TOPIC]')),
4245 "help": (help_, [], _('[TOPIC]')),
4246 "identify|id":
4246 "identify|id":
4247 (identify,
4247 (identify,
4248 [('r', 'rev', '',
4248 [('r', 'rev', '',
4249 _('identify the specified revision'), _('REV')),
4249 _('identify the specified revision'), _('REV')),
4250 ('n', 'num', None, _('show local revision number')),
4250 ('n', 'num', None, _('show local revision number')),
4251 ('i', 'id', None, _('show global revision id')),
4251 ('i', 'id', None, _('show global revision id')),
4252 ('b', 'branch', None, _('show branch')),
4252 ('b', 'branch', None, _('show branch')),
4253 ('t', 'tags', None, _('show tags'))],
4253 ('t', 'tags', None, _('show tags'))],
4254 _('[-nibt] [-r REV] [SOURCE]')),
4254 _('[-nibt] [-r REV] [SOURCE]')),
4255 "import|patch":
4255 "import|patch":
4256 (import_,
4256 (import_,
4257 [('p', 'strip', 1,
4257 [('p', 'strip', 1,
4258 _('directory strip option for patch. This has the same '
4258 _('directory strip option for patch. This has the same '
4259 'meaning as the corresponding patch option'),
4259 'meaning as the corresponding patch option'),
4260 _('NUM')),
4260 _('NUM')),
4261 ('b', 'base', '',
4261 ('b', 'base', '',
4262 _('base path'), _('PATH')),
4262 _('base path'), _('PATH')),
4263 ('f', 'force', None,
4263 ('f', 'force', None,
4264 _('skip check for outstanding uncommitted changes')),
4264 _('skip check for outstanding uncommitted changes')),
4265 ('', 'no-commit', None,
4265 ('', 'no-commit', None,
4266 _("don't commit, just update the working directory")),
4266 _("don't commit, just update the working directory")),
4267 ('', 'exact', None,
4267 ('', 'exact', None,
4268 _('apply patch to the nodes from which it was generated')),
4268 _('apply patch to the nodes from which it was generated')),
4269 ('', 'import-branch', None,
4269 ('', 'import-branch', None,
4270 _('use any branch information in patch (implied by --exact)'))] +
4270 _('use any branch information in patch (implied by --exact)'))] +
4271 commitopts + commitopts2 + similarityopts,
4271 commitopts + commitopts2 + similarityopts,
4272 _('[OPTION]... PATCH...')),
4272 _('[OPTION]... PATCH...')),
4273 "incoming|in":
4273 "incoming|in":
4274 (incoming,
4274 (incoming,
4275 [('f', 'force', None,
4275 [('f', 'force', None,
4276 _('run even if remote repository is unrelated')),
4276 _('run even if remote repository is unrelated')),
4277 ('n', 'newest-first', None, _('show newest record first')),
4277 ('n', 'newest-first', None, _('show newest record first')),
4278 ('', 'bundle', '',
4278 ('', 'bundle', '',
4279 _('file to store the bundles into'), _('FILE')),
4279 _('file to store the bundles into'), _('FILE')),
4280 ('r', 'rev', [],
4280 ('r', 'rev', [],
4281 _('a remote changeset intended to be added'), _('REV')),
4281 _('a remote changeset intended to be added'), _('REV')),
4282 ('b', 'branch', [],
4282 ('b', 'branch', [],
4283 _('a specific branch you would like to pull'), _('BRANCH')),
4283 _('a specific branch you would like to pull'), _('BRANCH')),
4284 ] + logopts + remoteopts,
4284 ] + logopts + remoteopts,
4285 _('[-p] [-n] [-M] [-f] [-r REV]...'
4285 _('[-p] [-n] [-M] [-f] [-r REV]...'
4286 ' [--bundle FILENAME] [SOURCE]')),
4286 ' [--bundle FILENAME] [SOURCE]')),
4287 "^init":
4287 "^init":
4288 (init,
4288 (init,
4289 remoteopts,
4289 remoteopts,
4290 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4290 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4291 "locate":
4291 "locate":
4292 (locate,
4292 (locate,
4293 [('r', 'rev', '',
4293 [('r', 'rev', '',
4294 _('search the repository as it is in REV'), _('REV')),
4294 _('search the repository as it is in REV'), _('REV')),
4295 ('0', 'print0', None,
4295 ('0', 'print0', None,
4296 _('end filenames with NUL, for use with xargs')),
4296 _('end filenames with NUL, for use with xargs')),
4297 ('f', 'fullpath', None,
4297 ('f', 'fullpath', None,
4298 _('print complete paths from the filesystem root')),
4298 _('print complete paths from the filesystem root')),
4299 ] + walkopts,
4299 ] + walkopts,
4300 _('[OPTION]... [PATTERN]...')),
4300 _('[OPTION]... [PATTERN]...')),
4301 "^log|history":
4301 "^log|history":
4302 (log,
4302 (log,
4303 [('f', 'follow', None,
4303 [('f', 'follow', None,
4304 _('follow changeset history,'
4304 _('follow changeset history,'
4305 ' or file history across copies and renames')),
4305 ' or file history across copies and renames')),
4306 ('', 'follow-first', None,
4306 ('', 'follow-first', None,
4307 _('only follow the first parent of merge changesets')),
4307 _('only follow the first parent of merge changesets')),
4308 ('d', 'date', '',
4308 ('d', 'date', '',
4309 _('show revisions matching date spec'), _('DATE')),
4309 _('show revisions matching date spec'), _('DATE')),
4310 ('C', 'copies', None, _('show copied files')),
4310 ('C', 'copies', None, _('show copied files')),
4311 ('k', 'keyword', [],
4311 ('k', 'keyword', [],
4312 _('do case-insensitive search for a given text'), _('TEXT')),
4312 _('do case-insensitive search for a given text'), _('TEXT')),
4313 ('r', 'rev', [],
4313 ('r', 'rev', [],
4314 _('show the specified revision or range'), _('REV')),
4314 _('show the specified revision or range'), _('REV')),
4315 ('', 'removed', None, _('include revisions where files were removed')),
4315 ('', 'removed', None, _('include revisions where files were removed')),
4316 ('m', 'only-merges', None, _('show only merges')),
4316 ('m', 'only-merges', None, _('show only merges')),
4317 ('u', 'user', [],
4317 ('u', 'user', [],
4318 _('revisions committed by user'), _('USER')),
4318 _('revisions committed by user'), _('USER')),
4319 ('', 'only-branch', [],
4319 ('', 'only-branch', [],
4320 _('show only changesets within the given named branch (DEPRECATED)'),
4320 _('show only changesets within the given named branch (DEPRECATED)'),
4321 _('BRANCH')),
4321 _('BRANCH')),
4322 ('b', 'branch', [],
4322 ('b', 'branch', [],
4323 _('show changesets within the given named branch'), _('BRANCH')),
4323 _('show changesets within the given named branch'), _('BRANCH')),
4324 ('P', 'prune', [],
4324 ('P', 'prune', [],
4325 _('do not display revision or any of its ancestors'), _('REV')),
4325 _('do not display revision or any of its ancestors'), _('REV')),
4326 ] + logopts + walkopts,
4326 ] + logopts + walkopts,
4327 _('[OPTION]... [FILE]')),
4327 _('[OPTION]... [FILE]')),
4328 "manifest":
4328 "manifest":
4329 (manifest,
4329 (manifest,
4330 [('r', 'rev', '',
4330 [('r', 'rev', '',
4331 _('revision to display'), _('REV'))],
4331 _('revision to display'), _('REV'))],
4332 _('[-r REV]')),
4332 _('[-r REV]')),
4333 "^merge":
4333 "^merge":
4334 (merge,
4334 (merge,
4335 [('f', 'force', None, _('force a merge with outstanding changes')),
4335 [('f', 'force', None, _('force a merge with outstanding changes')),
4336 ('r', 'rev', '',
4336 ('r', 'rev', '',
4337 _('revision to merge'), _('REV')),
4337 _('revision to merge'), _('REV')),
4338 ('P', 'preview', None,
4338 ('P', 'preview', None,
4339 _('review revisions to merge (no merge is performed)'))],
4339 _('review revisions to merge (no merge is performed)'))],
4340 _('[-P] [-f] [[-r] REV]')),
4340 _('[-P] [-f] [[-r] REV]')),
4341 "outgoing|out":
4341 "outgoing|out":
4342 (outgoing,
4342 (outgoing,
4343 [('f', 'force', None,
4343 [('f', 'force', None,
4344 _('run even when the destination is unrelated')),
4344 _('run even when the destination is unrelated')),
4345 ('r', 'rev', [],
4345 ('r', 'rev', [],
4346 _('a changeset intended to be included in the destination'),
4346 _('a changeset intended to be included in the destination'),
4347 _('REV')),
4347 _('REV')),
4348 ('n', 'newest-first', None, _('show newest record first')),
4348 ('n', 'newest-first', None, _('show newest record first')),
4349 ('b', 'branch', [],
4349 ('b', 'branch', [],
4350 _('a specific branch you would like to push'), _('BRANCH')),
4350 _('a specific branch you would like to push'), _('BRANCH')),
4351 ] + logopts + remoteopts,
4351 ] + logopts + remoteopts,
4352 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4352 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4353 "parents":
4353 "parents":
4354 (parents,
4354 (parents,
4355 [('r', 'rev', '',
4355 [('r', 'rev', '',
4356 _('show parents of the specified revision'), _('REV')),
4356 _('show parents of the specified revision'), _('REV')),
4357 ] + templateopts,
4357 ] + templateopts,
4358 _('[-r REV] [FILE]')),
4358 _('[-r REV] [FILE]')),
4359 "paths": (paths, [], _('[NAME]')),
4359 "paths": (paths, [], _('[NAME]')),
4360 "^pull":
4360 "^pull":
4361 (pull,
4361 (pull,
4362 [('u', 'update', None,
4362 [('u', 'update', None,
4363 _('update to new branch head if changesets were pulled')),
4363 _('update to new branch head if changesets were pulled')),
4364 ('f', 'force', None,
4364 ('f', 'force', None,
4365 _('run even when remote repository is unrelated')),
4365 _('run even when remote repository is unrelated')),
4366 ('r', 'rev', [],
4366 ('r', 'rev', [],
4367 _('a remote changeset intended to be added'), _('REV')),
4367 _('a remote changeset intended to be added'), _('REV')),
4368 ('b', 'branch', [],
4368 ('b', 'branch', [],
4369 _('a specific branch you would like to pull'), _('BRANCH')),
4369 _('a specific branch you would like to pull'), _('BRANCH')),
4370 ] + remoteopts,
4370 ] + remoteopts,
4371 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4371 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4372 "^push":
4372 "^push":
4373 (push,
4373 (push,
4374 [('f', 'force', None, _('force push')),
4374 [('f', 'force', None, _('force push')),
4375 ('r', 'rev', [],
4375 ('r', 'rev', [],
4376 _('a changeset intended to be included in the destination'),
4376 _('a changeset intended to be included in the destination'),
4377 _('REV')),
4377 _('REV')),
4378 ('b', 'branch', [],
4378 ('b', 'branch', [],
4379 _('a specific branch you would like to push'), _('BRANCH')),
4379 _('a specific branch you would like to push'), _('BRANCH')),
4380 ('', 'new-branch', False, _('allow pushing a new branch')),
4380 ('', 'new-branch', False, _('allow pushing a new branch')),
4381 ] + remoteopts,
4381 ] + remoteopts,
4382 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4382 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4383 "recover": (recover, []),
4383 "recover": (recover, []),
4384 "^remove|rm":
4384 "^remove|rm":
4385 (remove,
4385 (remove,
4386 [('A', 'after', None, _('record delete for missing files')),
4386 [('A', 'after', None, _('record delete for missing files')),
4387 ('f', 'force', None,
4387 ('f', 'force', None,
4388 _('remove (and delete) file even if added or modified')),
4388 _('remove (and delete) file even if added or modified')),
4389 ] + walkopts,
4389 ] + walkopts,
4390 _('[OPTION]... FILE...')),
4390 _('[OPTION]... FILE...')),
4391 "rename|mv":
4391 "rename|mv":
4392 (rename,
4392 (rename,
4393 [('A', 'after', None, _('record a rename that has already occurred')),
4393 [('A', 'after', None, _('record a rename that has already occurred')),
4394 ('f', 'force', None,
4394 ('f', 'force', None,
4395 _('forcibly copy over an existing managed file')),
4395 _('forcibly copy over an existing managed file')),
4396 ] + walkopts + dryrunopts,
4396 ] + walkopts + dryrunopts,
4397 _('[OPTION]... SOURCE... DEST')),
4397 _('[OPTION]... SOURCE... DEST')),
4398 "resolve":
4398 "resolve":
4399 (resolve,
4399 (resolve,
4400 [('a', 'all', None, _('select all unresolved files')),
4400 [('a', 'all', None, _('select all unresolved files')),
4401 ('l', 'list', None, _('list state of files needing merge')),
4401 ('l', 'list', None, _('list state of files needing merge')),
4402 ('m', 'mark', None, _('mark files as resolved')),
4402 ('m', 'mark', None, _('mark files as resolved')),
4403 ('u', 'unmark', None, _('mark files as unresolved')),
4403 ('u', 'unmark', None, _('mark files as unresolved')),
4404 ('n', 'no-status', None, _('hide status prefix'))]
4404 ('n', 'no-status', None, _('hide status prefix'))]
4405 + walkopts,
4405 + walkopts,
4406 _('[OPTION]... [FILE]...')),
4406 _('[OPTION]... [FILE]...')),
4407 "revert":
4407 "revert":
4408 (revert,
4408 (revert,
4409 [('a', 'all', None, _('revert all changes when no arguments given')),
4409 [('a', 'all', None, _('revert all changes when no arguments given')),
4410 ('d', 'date', '',
4410 ('d', 'date', '',
4411 _('tipmost revision matching date'), _('DATE')),
4411 _('tipmost revision matching date'), _('DATE')),
4412 ('r', 'rev', '',
4412 ('r', 'rev', '',
4413 _('revert to the specified revision'), _('REV')),
4413 _('revert to the specified revision'), _('REV')),
4414 ('', 'no-backup', None, _('do not save backup copies of files')),
4414 ('', 'no-backup', None, _('do not save backup copies of files')),
4415 ] + walkopts + dryrunopts,
4415 ] + walkopts + dryrunopts,
4416 _('[OPTION]... [-r REV] [NAME]...')),
4416 _('[OPTION]... [-r REV] [NAME]...')),
4417 "rollback": (rollback, dryrunopts),
4417 "rollback": (rollback, dryrunopts),
4418 "root": (root, []),
4418 "root": (root, []),
4419 "^serve":
4419 "^serve":
4420 (serve,
4420 (serve,
4421 [('A', 'accesslog', '',
4421 [('A', 'accesslog', '',
4422 _('name of access log file to write to'), _('FILE')),
4422 _('name of access log file to write to'), _('FILE')),
4423 ('d', 'daemon', None, _('run server in background')),
4423 ('d', 'daemon', None, _('run server in background')),
4424 ('', 'daemon-pipefds', '',
4424 ('', 'daemon-pipefds', '',
4425 _('used internally by daemon mode'), _('NUM')),
4425 _('used internally by daemon mode'), _('NUM')),
4426 ('E', 'errorlog', '',
4426 ('E', 'errorlog', '',
4427 _('name of error log file to write to'), _('FILE')),
4427 _('name of error log file to write to'), _('FILE')),
4428 # use string type, then we can check if something was passed
4428 # use string type, then we can check if something was passed
4429 ('p', 'port', '',
4429 ('p', 'port', '',
4430 _('port to listen on (default: 8000)'), _('PORT')),
4430 _('port to listen on (default: 8000)'), _('PORT')),
4431 ('a', 'address', '',
4431 ('a', 'address', '',
4432 _('address to listen on (default: all interfaces)'), _('ADDR')),
4432 _('address to listen on (default: all interfaces)'), _('ADDR')),
4433 ('', 'prefix', '',
4433 ('', 'prefix', '',
4434 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4434 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4435 ('n', 'name', '',
4435 ('n', 'name', '',
4436 _('name to show in web pages (default: working directory)'),
4436 _('name to show in web pages (default: working directory)'),
4437 _('NAME')),
4437 _('NAME')),
4438 ('', 'web-conf', '',
4438 ('', 'web-conf', '',
4439 _('name of the hgweb config file (serve more than one repository)'),
4439 _('name of the hgweb config file (serve more than one repository)'),
4440 _('FILE')),
4440 _('FILE')),
4441 ('', 'webdir-conf', '',
4441 ('', 'webdir-conf', '',
4442 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4442 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4443 ('', 'pid-file', '',
4443 ('', 'pid-file', '',
4444 _('name of file to write process ID to'), _('FILE')),
4444 _('name of file to write process ID to'), _('FILE')),
4445 ('', 'stdio', None, _('for remote clients')),
4445 ('', 'stdio', None, _('for remote clients')),
4446 ('t', 'templates', '',
4446 ('t', 'templates', '',
4447 _('web templates to use'), _('TEMPLATE')),
4447 _('web templates to use'), _('TEMPLATE')),
4448 ('', 'style', '',
4448 ('', 'style', '',
4449 _('template style to use'), _('STYLE')),
4449 _('template style to use'), _('STYLE')),
4450 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4450 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4451 ('', 'certificate', '',
4451 ('', 'certificate', '',
4452 _('SSL certificate file'), _('FILE'))],
4452 _('SSL certificate file'), _('FILE'))],
4453 _('[OPTION]...')),
4453 _('[OPTION]...')),
4454 "showconfig|debugconfig":
4454 "showconfig|debugconfig":
4455 (showconfig,
4455 (showconfig,
4456 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4456 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4457 _('[-u] [NAME]...')),
4457 _('[-u] [NAME]...')),
4458 "^summary|sum":
4458 "^summary|sum":
4459 (summary,
4459 (summary,
4460 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4460 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4461 "^status|st":
4461 "^status|st":
4462 (status,
4462 (status,
4463 [('A', 'all', None, _('show status of all files')),
4463 [('A', 'all', None, _('show status of all files')),
4464 ('m', 'modified', None, _('show only modified files')),
4464 ('m', 'modified', None, _('show only modified files')),
4465 ('a', 'added', None, _('show only added files')),
4465 ('a', 'added', None, _('show only added files')),
4466 ('r', 'removed', None, _('show only removed files')),
4466 ('r', 'removed', None, _('show only removed files')),
4467 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4467 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4468 ('c', 'clean', None, _('show only files without changes')),
4468 ('c', 'clean', None, _('show only files without changes')),
4469 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4469 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4470 ('i', 'ignored', None, _('show only ignored files')),
4470 ('i', 'ignored', None, _('show only ignored files')),
4471 ('n', 'no-status', None, _('hide status prefix')),
4471 ('n', 'no-status', None, _('hide status prefix')),
4472 ('C', 'copies', None, _('show source of copied files')),
4472 ('C', 'copies', None, _('show source of copied files')),
4473 ('0', 'print0', None,
4473 ('0', 'print0', None,
4474 _('end filenames with NUL, for use with xargs')),
4474 _('end filenames with NUL, for use with xargs')),
4475 ('', 'rev', [],
4475 ('', 'rev', [],
4476 _('show difference from revision'), _('REV')),
4476 _('show difference from revision'), _('REV')),
4477 ('', 'change', '',
4477 ('', 'change', '',
4478 _('list the changed files of a revision'), _('REV')),
4478 _('list the changed files of a revision'), _('REV')),
4479 ] + walkopts + subrepoopts,
4479 ] + walkopts + subrepoopts,
4480 _('[OPTION]... [FILE]...')),
4480 _('[OPTION]... [FILE]...')),
4481 "tag":
4481 "tag":
4482 (tag,
4482 (tag,
4483 [('f', 'force', None, _('replace existing tag')),
4483 [('f', 'force', None, _('replace existing tag')),
4484 ('l', 'local', None, _('make the tag local')),
4484 ('l', 'local', None, _('make the tag local')),
4485 ('r', 'rev', '',
4485 ('r', 'rev', '',
4486 _('revision to tag'), _('REV')),
4486 _('revision to tag'), _('REV')),
4487 ('', 'remove', None, _('remove a tag')),
4487 ('', 'remove', None, _('remove a tag')),
4488 # -l/--local is already there, commitopts cannot be used
4488 # -l/--local is already there, commitopts cannot be used
4489 ('e', 'edit', None, _('edit commit message')),
4489 ('e', 'edit', None, _('edit commit message')),
4490 ('m', 'message', '',
4490 ('m', 'message', '',
4491 _('use <text> as commit message'), _('TEXT')),
4491 _('use <text> as commit message'), _('TEXT')),
4492 ] + commitopts2,
4492 ] + commitopts2,
4493 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4493 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4494 "tags": (tags, [], ''),
4494 "tags": (tags, [], ''),
4495 "tip":
4495 "tip":
4496 (tip,
4496 (tip,
4497 [('p', 'patch', None, _('show patch')),
4497 [('p', 'patch', None, _('show patch')),
4498 ('g', 'git', None, _('use git extended diff format')),
4498 ('g', 'git', None, _('use git extended diff format')),
4499 ] + templateopts,
4499 ] + templateopts,
4500 _('[-p] [-g]')),
4500 _('[-p] [-g]')),
4501 "unbundle":
4501 "unbundle":
4502 (unbundle,
4502 (unbundle,
4503 [('u', 'update', None,
4503 [('u', 'update', None,
4504 _('update to new branch head if changesets were unbundled'))],
4504 _('update to new branch head if changesets were unbundled'))],
4505 _('[-u] FILE...')),
4505 _('[-u] FILE...')),
4506 "^update|up|checkout|co":
4506 "^update|up|checkout|co":
4507 (update,
4507 (update,
4508 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4508 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4509 ('c', 'check', None, _('check for uncommitted changes')),
4509 ('c', 'check', None, _('check for uncommitted changes')),
4510 ('d', 'date', '',
4510 ('d', 'date', '',
4511 _('tipmost revision matching date'), _('DATE')),
4511 _('tipmost revision matching date'), _('DATE')),
4512 ('r', 'rev', '',
4512 ('r', 'rev', '',
4513 _('revision'), _('REV'))],
4513 _('revision'), _('REV'))],
4514 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4514 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4515 "verify": (verify, []),
4515 "verify": (verify, []),
4516 "version": (version_, []),
4516 "version": (version_, []),
4517 }
4517 }
4518
4518
4519 norepo = ("clone init version help debugcommands debugcomplete"
4519 norepo = ("clone init version help debugcommands debugcomplete"
4520 " debugdate debuginstall debugfsinfo debugpushkey")
4520 " debugdate debuginstall debugfsinfo debugpushkey")
4521 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4521 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4522 " debugdata debugindex debugindexdot")
4522 " debugdata debugindex debugindexdot")
@@ -1,1715 +1,1672 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, re
9 import cStringIO, email.Parser, os, re
10 import tempfile, zlib
10 import tempfile, zlib
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, cmdutil, mdiff, util, diffhelpers, copies, encoding
14 import base85, mdiff, util, diffhelpers, copies, encoding
15
15
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
17
17
18 class PatchError(Exception):
18 class PatchError(Exception):
19 pass
19 pass
20
20
21 class NoHunks(PatchError):
21 class NoHunks(PatchError):
22 pass
22 pass
23
23
24 # helper functions
24 # helper functions
25
25
26 def copyfile(src, dst, basedir):
26 def copyfile(src, dst, basedir):
27 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
28 if os.path.exists(absdst):
28 if os.path.exists(absdst):
29 raise util.Abort(_("cannot create %s: destination already exists") %
29 raise util.Abort(_("cannot create %s: destination already exists") %
30 dst)
30 dst)
31
31
32 dstdir = os.path.dirname(absdst)
32 dstdir = os.path.dirname(absdst)
33 if dstdir and not os.path.isdir(dstdir):
33 if dstdir and not os.path.isdir(dstdir):
34 try:
34 try:
35 os.makedirs(dstdir)
35 os.makedirs(dstdir)
36 except IOError:
36 except IOError:
37 raise util.Abort(
37 raise util.Abort(
38 _("cannot create %s: unable to create destination directory")
38 _("cannot create %s: unable to create destination directory")
39 % dst)
39 % dst)
40
40
41 util.copyfile(abssrc, absdst)
41 util.copyfile(abssrc, absdst)
42
42
43 # public functions
43 # public functions
44
44
45 def split(stream):
45 def split(stream):
46 '''return an iterator of individual patches from a stream'''
46 '''return an iterator of individual patches from a stream'''
47 def isheader(line, inheader):
47 def isheader(line, inheader):
48 if inheader and line[0] in (' ', '\t'):
48 if inheader and line[0] in (' ', '\t'):
49 # continuation
49 # continuation
50 return True
50 return True
51 if line[0] in (' ', '-', '+'):
51 if line[0] in (' ', '-', '+'):
52 # diff line - don't check for header pattern in there
52 # diff line - don't check for header pattern in there
53 return False
53 return False
54 l = line.split(': ', 1)
54 l = line.split(': ', 1)
55 return len(l) == 2 and ' ' not in l[0]
55 return len(l) == 2 and ' ' not in l[0]
56
56
57 def chunk(lines):
57 def chunk(lines):
58 return cStringIO.StringIO(''.join(lines))
58 return cStringIO.StringIO(''.join(lines))
59
59
60 def hgsplit(stream, cur):
60 def hgsplit(stream, cur):
61 inheader = True
61 inheader = True
62
62
63 for line in stream:
63 for line in stream:
64 if not line.strip():
64 if not line.strip():
65 inheader = False
65 inheader = False
66 if not inheader and line.startswith('# HG changeset patch'):
66 if not inheader and line.startswith('# HG changeset patch'):
67 yield chunk(cur)
67 yield chunk(cur)
68 cur = []
68 cur = []
69 inheader = True
69 inheader = True
70
70
71 cur.append(line)
71 cur.append(line)
72
72
73 if cur:
73 if cur:
74 yield chunk(cur)
74 yield chunk(cur)
75
75
76 def mboxsplit(stream, cur):
76 def mboxsplit(stream, cur):
77 for line in stream:
77 for line in stream:
78 if line.startswith('From '):
78 if line.startswith('From '):
79 for c in split(chunk(cur[1:])):
79 for c in split(chunk(cur[1:])):
80 yield c
80 yield c
81 cur = []
81 cur = []
82
82
83 cur.append(line)
83 cur.append(line)
84
84
85 if cur:
85 if cur:
86 for c in split(chunk(cur[1:])):
86 for c in split(chunk(cur[1:])):
87 yield c
87 yield c
88
88
89 def mimesplit(stream, cur):
89 def mimesplit(stream, cur):
90 def msgfp(m):
90 def msgfp(m):
91 fp = cStringIO.StringIO()
91 fp = cStringIO.StringIO()
92 g = email.Generator.Generator(fp, mangle_from_=False)
92 g = email.Generator.Generator(fp, mangle_from_=False)
93 g.flatten(m)
93 g.flatten(m)
94 fp.seek(0)
94 fp.seek(0)
95 return fp
95 return fp
96
96
97 for line in stream:
97 for line in stream:
98 cur.append(line)
98 cur.append(line)
99 c = chunk(cur)
99 c = chunk(cur)
100
100
101 m = email.Parser.Parser().parse(c)
101 m = email.Parser.Parser().parse(c)
102 if not m.is_multipart():
102 if not m.is_multipart():
103 yield msgfp(m)
103 yield msgfp(m)
104 else:
104 else:
105 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
105 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
106 for part in m.walk():
106 for part in m.walk():
107 ct = part.get_content_type()
107 ct = part.get_content_type()
108 if ct not in ok_types:
108 if ct not in ok_types:
109 continue
109 continue
110 yield msgfp(part)
110 yield msgfp(part)
111
111
112 def headersplit(stream, cur):
112 def headersplit(stream, cur):
113 inheader = False
113 inheader = False
114
114
115 for line in stream:
115 for line in stream:
116 if not inheader and isheader(line, inheader):
116 if not inheader and isheader(line, inheader):
117 yield chunk(cur)
117 yield chunk(cur)
118 cur = []
118 cur = []
119 inheader = True
119 inheader = True
120 if inheader and not isheader(line, inheader):
120 if inheader and not isheader(line, inheader):
121 inheader = False
121 inheader = False
122
122
123 cur.append(line)
123 cur.append(line)
124
124
125 if cur:
125 if cur:
126 yield chunk(cur)
126 yield chunk(cur)
127
127
128 def remainder(cur):
128 def remainder(cur):
129 yield chunk(cur)
129 yield chunk(cur)
130
130
131 class fiter(object):
131 class fiter(object):
132 def __init__(self, fp):
132 def __init__(self, fp):
133 self.fp = fp
133 self.fp = fp
134
134
135 def __iter__(self):
135 def __iter__(self):
136 return self
136 return self
137
137
138 def next(self):
138 def next(self):
139 l = self.fp.readline()
139 l = self.fp.readline()
140 if not l:
140 if not l:
141 raise StopIteration
141 raise StopIteration
142 return l
142 return l
143
143
144 inheader = False
144 inheader = False
145 cur = []
145 cur = []
146
146
147 mimeheaders = ['content-type']
147 mimeheaders = ['content-type']
148
148
149 if not hasattr(stream, 'next'):
149 if not hasattr(stream, 'next'):
150 # http responses, for example, have readline but not next
150 # http responses, for example, have readline but not next
151 stream = fiter(stream)
151 stream = fiter(stream)
152
152
153 for line in stream:
153 for line in stream:
154 cur.append(line)
154 cur.append(line)
155 if line.startswith('# HG changeset patch'):
155 if line.startswith('# HG changeset patch'):
156 return hgsplit(stream, cur)
156 return hgsplit(stream, cur)
157 elif line.startswith('From '):
157 elif line.startswith('From '):
158 return mboxsplit(stream, cur)
158 return mboxsplit(stream, cur)
159 elif isheader(line, inheader):
159 elif isheader(line, inheader):
160 inheader = True
160 inheader = True
161 if line.split(':', 1)[0].lower() in mimeheaders:
161 if line.split(':', 1)[0].lower() in mimeheaders:
162 # let email parser handle this
162 # let email parser handle this
163 return mimesplit(stream, cur)
163 return mimesplit(stream, cur)
164 elif line.startswith('--- ') and inheader:
164 elif line.startswith('--- ') and inheader:
165 # No evil headers seen by diff start, split by hand
165 # No evil headers seen by diff start, split by hand
166 return headersplit(stream, cur)
166 return headersplit(stream, cur)
167 # Not enough info, keep reading
167 # Not enough info, keep reading
168
168
169 # if we are here, we have a very plain patch
169 # if we are here, we have a very plain patch
170 return remainder(cur)
170 return remainder(cur)
171
171
172 def extract(ui, fileobj):
172 def extract(ui, fileobj):
173 '''extract patch from data read from fileobj.
173 '''extract patch from data read from fileobj.
174
174
175 patch can be a normal patch or contained in an email message.
175 patch can be a normal patch or contained in an email message.
176
176
177 return tuple (filename, message, user, date, branch, node, p1, p2).
177 return tuple (filename, message, user, date, branch, node, p1, p2).
178 Any item in the returned tuple can be None. If filename is None,
178 Any item in the returned tuple can be None. If filename is None,
179 fileobj did not contain a patch. Caller must unlink filename when done.'''
179 fileobj did not contain a patch. Caller must unlink filename when done.'''
180
180
181 # attempt to detect the start of a patch
181 # attempt to detect the start of a patch
182 # (this heuristic is borrowed from quilt)
182 # (this heuristic is borrowed from quilt)
183 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
183 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
184 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
184 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
185 r'---[ \t].*?^\+\+\+[ \t]|'
185 r'---[ \t].*?^\+\+\+[ \t]|'
186 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
186 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
187
187
188 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
188 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
189 tmpfp = os.fdopen(fd, 'w')
189 tmpfp = os.fdopen(fd, 'w')
190 try:
190 try:
191 msg = email.Parser.Parser().parse(fileobj)
191 msg = email.Parser.Parser().parse(fileobj)
192
192
193 subject = msg['Subject']
193 subject = msg['Subject']
194 user = msg['From']
194 user = msg['From']
195 if not subject and not user:
195 if not subject and not user:
196 # Not an email, restore parsed headers if any
196 # Not an email, restore parsed headers if any
197 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
197 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
198
198
199 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
199 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
200 # should try to parse msg['Date']
200 # should try to parse msg['Date']
201 date = None
201 date = None
202 nodeid = None
202 nodeid = None
203 branch = None
203 branch = None
204 parents = []
204 parents = []
205
205
206 if subject:
206 if subject:
207 if subject.startswith('[PATCH'):
207 if subject.startswith('[PATCH'):
208 pend = subject.find(']')
208 pend = subject.find(']')
209 if pend >= 0:
209 if pend >= 0:
210 subject = subject[pend + 1:].lstrip()
210 subject = subject[pend + 1:].lstrip()
211 subject = subject.replace('\n\t', ' ')
211 subject = subject.replace('\n\t', ' ')
212 ui.debug('Subject: %s\n' % subject)
212 ui.debug('Subject: %s\n' % subject)
213 if user:
213 if user:
214 ui.debug('From: %s\n' % user)
214 ui.debug('From: %s\n' % user)
215 diffs_seen = 0
215 diffs_seen = 0
216 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
216 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
217 message = ''
217 message = ''
218 for part in msg.walk():
218 for part in msg.walk():
219 content_type = part.get_content_type()
219 content_type = part.get_content_type()
220 ui.debug('Content-Type: %s\n' % content_type)
220 ui.debug('Content-Type: %s\n' % content_type)
221 if content_type not in ok_types:
221 if content_type not in ok_types:
222 continue
222 continue
223 payload = part.get_payload(decode=True)
223 payload = part.get_payload(decode=True)
224 m = diffre.search(payload)
224 m = diffre.search(payload)
225 if m:
225 if m:
226 hgpatch = False
226 hgpatch = False
227 ignoretext = False
227 ignoretext = False
228
228
229 ui.debug('found patch at byte %d\n' % m.start(0))
229 ui.debug('found patch at byte %d\n' % m.start(0))
230 diffs_seen += 1
230 diffs_seen += 1
231 cfp = cStringIO.StringIO()
231 cfp = cStringIO.StringIO()
232 for line in payload[:m.start(0)].splitlines():
232 for line in payload[:m.start(0)].splitlines():
233 if line.startswith('# HG changeset patch'):
233 if line.startswith('# HG changeset patch'):
234 ui.debug('patch generated by hg export\n')
234 ui.debug('patch generated by hg export\n')
235 hgpatch = True
235 hgpatch = True
236 # drop earlier commit message content
236 # drop earlier commit message content
237 cfp.seek(0)
237 cfp.seek(0)
238 cfp.truncate()
238 cfp.truncate()
239 subject = None
239 subject = None
240 elif hgpatch:
240 elif hgpatch:
241 if line.startswith('# User '):
241 if line.startswith('# User '):
242 user = line[7:]
242 user = line[7:]
243 ui.debug('From: %s\n' % user)
243 ui.debug('From: %s\n' % user)
244 elif line.startswith("# Date "):
244 elif line.startswith("# Date "):
245 date = line[7:]
245 date = line[7:]
246 elif line.startswith("# Branch "):
246 elif line.startswith("# Branch "):
247 branch = line[9:]
247 branch = line[9:]
248 elif line.startswith("# Node ID "):
248 elif line.startswith("# Node ID "):
249 nodeid = line[10:]
249 nodeid = line[10:]
250 elif line.startswith("# Parent "):
250 elif line.startswith("# Parent "):
251 parents.append(line[10:])
251 parents.append(line[10:])
252 elif line == '---' and gitsendmail:
252 elif line == '---' and gitsendmail:
253 ignoretext = True
253 ignoretext = True
254 if not line.startswith('# ') and not ignoretext:
254 if not line.startswith('# ') and not ignoretext:
255 cfp.write(line)
255 cfp.write(line)
256 cfp.write('\n')
256 cfp.write('\n')
257 message = cfp.getvalue()
257 message = cfp.getvalue()
258 if tmpfp:
258 if tmpfp:
259 tmpfp.write(payload)
259 tmpfp.write(payload)
260 if not payload.endswith('\n'):
260 if not payload.endswith('\n'):
261 tmpfp.write('\n')
261 tmpfp.write('\n')
262 elif not diffs_seen and message and content_type == 'text/plain':
262 elif not diffs_seen and message and content_type == 'text/plain':
263 message += '\n' + payload
263 message += '\n' + payload
264 except:
264 except:
265 tmpfp.close()
265 tmpfp.close()
266 os.unlink(tmpname)
266 os.unlink(tmpname)
267 raise
267 raise
268
268
269 if subject and not message.startswith(subject):
269 if subject and not message.startswith(subject):
270 message = '%s\n%s' % (subject, message)
270 message = '%s\n%s' % (subject, message)
271 tmpfp.close()
271 tmpfp.close()
272 if not diffs_seen:
272 if not diffs_seen:
273 os.unlink(tmpname)
273 os.unlink(tmpname)
274 return None, message, user, date, branch, None, None, None
274 return None, message, user, date, branch, None, None, None
275 p1 = parents and parents.pop(0) or None
275 p1 = parents and parents.pop(0) or None
276 p2 = parents and parents.pop(0) or None
276 p2 = parents and parents.pop(0) or None
277 return tmpname, message, user, date, branch, nodeid, p1, p2
277 return tmpname, message, user, date, branch, nodeid, p1, p2
278
278
279 GP_PATCH = 1 << 0 # we have to run patch
279 GP_PATCH = 1 << 0 # we have to run patch
280 GP_FILTER = 1 << 1 # there's some copy/rename operation
280 GP_FILTER = 1 << 1 # there's some copy/rename operation
281 GP_BINARY = 1 << 2 # there's a binary patch
281 GP_BINARY = 1 << 2 # there's a binary patch
282
282
283 class patchmeta(object):
283 class patchmeta(object):
284 """Patched file metadata
284 """Patched file metadata
285
285
286 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
286 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
287 or COPY. 'path' is patched file path. 'oldpath' is set to the
287 or COPY. 'path' is patched file path. 'oldpath' is set to the
288 origin file when 'op' is either COPY or RENAME, None otherwise. If
288 origin file when 'op' is either COPY or RENAME, None otherwise. If
289 file mode is changed, 'mode' is a tuple (islink, isexec) where
289 file mode is changed, 'mode' is a tuple (islink, isexec) where
290 'islink' is True if the file is a symlink and 'isexec' is True if
290 'islink' is True if the file is a symlink and 'isexec' is True if
291 the file is executable. Otherwise, 'mode' is None.
291 the file is executable. Otherwise, 'mode' is None.
292 """
292 """
293 def __init__(self, path):
293 def __init__(self, path):
294 self.path = path
294 self.path = path
295 self.oldpath = None
295 self.oldpath = None
296 self.mode = None
296 self.mode = None
297 self.op = 'MODIFY'
297 self.op = 'MODIFY'
298 self.lineno = 0
298 self.lineno = 0
299 self.binary = False
299 self.binary = False
300
300
301 def setmode(self, mode):
301 def setmode(self, mode):
302 islink = mode & 020000
302 islink = mode & 020000
303 isexec = mode & 0100
303 isexec = mode & 0100
304 self.mode = (islink, isexec)
304 self.mode = (islink, isexec)
305
305
306 def __repr__(self):
306 def __repr__(self):
307 return "<patchmeta %s %r>" % (self.op, self.path)
307 return "<patchmeta %s %r>" % (self.op, self.path)
308
308
309 def readgitpatch(lr):
309 def readgitpatch(lr):
310 """extract git-style metadata about patches from <patchname>"""
310 """extract git-style metadata about patches from <patchname>"""
311
311
312 # Filter patch for git information
312 # Filter patch for git information
313 gp = None
313 gp = None
314 gitpatches = []
314 gitpatches = []
315 # Can have a git patch with only metadata, causing patch to complain
315 # Can have a git patch with only metadata, causing patch to complain
316 dopatch = 0
316 dopatch = 0
317
317
318 lineno = 0
318 lineno = 0
319 for line in lr:
319 for line in lr:
320 lineno += 1
320 lineno += 1
321 line = line.rstrip(' \r\n')
321 line = line.rstrip(' \r\n')
322 if line.startswith('diff --git'):
322 if line.startswith('diff --git'):
323 m = gitre.match(line)
323 m = gitre.match(line)
324 if m:
324 if m:
325 if gp:
325 if gp:
326 gitpatches.append(gp)
326 gitpatches.append(gp)
327 dst = m.group(2)
327 dst = m.group(2)
328 gp = patchmeta(dst)
328 gp = patchmeta(dst)
329 gp.lineno = lineno
329 gp.lineno = lineno
330 elif gp:
330 elif gp:
331 if line.startswith('--- '):
331 if line.startswith('--- '):
332 if gp.op in ('COPY', 'RENAME'):
332 if gp.op in ('COPY', 'RENAME'):
333 dopatch |= GP_FILTER
333 dopatch |= GP_FILTER
334 gitpatches.append(gp)
334 gitpatches.append(gp)
335 gp = None
335 gp = None
336 dopatch |= GP_PATCH
336 dopatch |= GP_PATCH
337 continue
337 continue
338 if line.startswith('rename from '):
338 if line.startswith('rename from '):
339 gp.op = 'RENAME'
339 gp.op = 'RENAME'
340 gp.oldpath = line[12:]
340 gp.oldpath = line[12:]
341 elif line.startswith('rename to '):
341 elif line.startswith('rename to '):
342 gp.path = line[10:]
342 gp.path = line[10:]
343 elif line.startswith('copy from '):
343 elif line.startswith('copy from '):
344 gp.op = 'COPY'
344 gp.op = 'COPY'
345 gp.oldpath = line[10:]
345 gp.oldpath = line[10:]
346 elif line.startswith('copy to '):
346 elif line.startswith('copy to '):
347 gp.path = line[8:]
347 gp.path = line[8:]
348 elif line.startswith('deleted file'):
348 elif line.startswith('deleted file'):
349 gp.op = 'DELETE'
349 gp.op = 'DELETE'
350 elif line.startswith('new file mode '):
350 elif line.startswith('new file mode '):
351 gp.op = 'ADD'
351 gp.op = 'ADD'
352 gp.setmode(int(line[-6:], 8))
352 gp.setmode(int(line[-6:], 8))
353 elif line.startswith('new mode '):
353 elif line.startswith('new mode '):
354 gp.setmode(int(line[-6:], 8))
354 gp.setmode(int(line[-6:], 8))
355 elif line.startswith('GIT binary patch'):
355 elif line.startswith('GIT binary patch'):
356 dopatch |= GP_BINARY
356 dopatch |= GP_BINARY
357 gp.binary = True
357 gp.binary = True
358 if gp:
358 if gp:
359 gitpatches.append(gp)
359 gitpatches.append(gp)
360
360
361 if not gitpatches:
361 if not gitpatches:
362 dopatch = GP_PATCH
362 dopatch = GP_PATCH
363
363
364 return (dopatch, gitpatches)
364 return (dopatch, gitpatches)
365
365
366 class linereader(object):
366 class linereader(object):
367 # simple class to allow pushing lines back into the input stream
367 # simple class to allow pushing lines back into the input stream
368 def __init__(self, fp, textmode=False):
368 def __init__(self, fp, textmode=False):
369 self.fp = fp
369 self.fp = fp
370 self.buf = []
370 self.buf = []
371 self.textmode = textmode
371 self.textmode = textmode
372 self.eol = None
372 self.eol = None
373
373
374 def push(self, line):
374 def push(self, line):
375 if line is not None:
375 if line is not None:
376 self.buf.append(line)
376 self.buf.append(line)
377
377
378 def readline(self):
378 def readline(self):
379 if self.buf:
379 if self.buf:
380 l = self.buf[0]
380 l = self.buf[0]
381 del self.buf[0]
381 del self.buf[0]
382 return l
382 return l
383 l = self.fp.readline()
383 l = self.fp.readline()
384 if not self.eol:
384 if not self.eol:
385 if l.endswith('\r\n'):
385 if l.endswith('\r\n'):
386 self.eol = '\r\n'
386 self.eol = '\r\n'
387 elif l.endswith('\n'):
387 elif l.endswith('\n'):
388 self.eol = '\n'
388 self.eol = '\n'
389 if self.textmode and l.endswith('\r\n'):
389 if self.textmode and l.endswith('\r\n'):
390 l = l[:-2] + '\n'
390 l = l[:-2] + '\n'
391 return l
391 return l
392
392
393 def __iter__(self):
393 def __iter__(self):
394 while 1:
394 while 1:
395 l = self.readline()
395 l = self.readline()
396 if not l:
396 if not l:
397 break
397 break
398 yield l
398 yield l
399
399
400 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
400 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
401 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
401 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
402 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
402 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
403 eolmodes = ['strict', 'crlf', 'lf', 'auto']
403 eolmodes = ['strict', 'crlf', 'lf', 'auto']
404
404
405 class patchfile(object):
405 class patchfile(object):
406 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
406 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
407 self.fname = fname
407 self.fname = fname
408 self.eolmode = eolmode
408 self.eolmode = eolmode
409 self.eol = None
409 self.eol = None
410 self.opener = opener
410 self.opener = opener
411 self.ui = ui
411 self.ui = ui
412 self.lines = []
412 self.lines = []
413 self.exists = False
413 self.exists = False
414 self.missing = missing
414 self.missing = missing
415 if not missing:
415 if not missing:
416 try:
416 try:
417 self.lines = self.readlines(fname)
417 self.lines = self.readlines(fname)
418 self.exists = True
418 self.exists = True
419 except IOError:
419 except IOError:
420 pass
420 pass
421 else:
421 else:
422 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
422 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
423
423
424 self.hash = {}
424 self.hash = {}
425 self.dirty = 0
425 self.dirty = 0
426 self.offset = 0
426 self.offset = 0
427 self.skew = 0
427 self.skew = 0
428 self.rej = []
428 self.rej = []
429 self.fileprinted = False
429 self.fileprinted = False
430 self.printfile(False)
430 self.printfile(False)
431 self.hunks = 0
431 self.hunks = 0
432
432
433 def readlines(self, fname):
433 def readlines(self, fname):
434 if os.path.islink(fname):
434 if os.path.islink(fname):
435 return [os.readlink(fname)]
435 return [os.readlink(fname)]
436 fp = self.opener(fname, 'r')
436 fp = self.opener(fname, 'r')
437 try:
437 try:
438 lr = linereader(fp, self.eolmode != 'strict')
438 lr = linereader(fp, self.eolmode != 'strict')
439 lines = list(lr)
439 lines = list(lr)
440 self.eol = lr.eol
440 self.eol = lr.eol
441 return lines
441 return lines
442 finally:
442 finally:
443 fp.close()
443 fp.close()
444
444
445 def writelines(self, fname, lines):
445 def writelines(self, fname, lines):
446 # Ensure supplied data ends in fname, being a regular file or
446 # Ensure supplied data ends in fname, being a regular file or
447 # a symlink. updatedir() will -too magically- take care of
447 # a symlink. cmdutil.updatedir will -too magically- take care
448 # setting it to the proper type afterwards.
448 # of setting it to the proper type afterwards.
449 islink = os.path.islink(fname)
449 islink = os.path.islink(fname)
450 if islink:
450 if islink:
451 fp = cStringIO.StringIO()
451 fp = cStringIO.StringIO()
452 else:
452 else:
453 fp = self.opener(fname, 'w')
453 fp = self.opener(fname, 'w')
454 try:
454 try:
455 if self.eolmode == 'auto':
455 if self.eolmode == 'auto':
456 eol = self.eol
456 eol = self.eol
457 elif self.eolmode == 'crlf':
457 elif self.eolmode == 'crlf':
458 eol = '\r\n'
458 eol = '\r\n'
459 else:
459 else:
460 eol = '\n'
460 eol = '\n'
461
461
462 if self.eolmode != 'strict' and eol and eol != '\n':
462 if self.eolmode != 'strict' and eol and eol != '\n':
463 for l in lines:
463 for l in lines:
464 if l and l[-1] == '\n':
464 if l and l[-1] == '\n':
465 l = l[:-1] + eol
465 l = l[:-1] + eol
466 fp.write(l)
466 fp.write(l)
467 else:
467 else:
468 fp.writelines(lines)
468 fp.writelines(lines)
469 if islink:
469 if islink:
470 self.opener.symlink(fp.getvalue(), fname)
470 self.opener.symlink(fp.getvalue(), fname)
471 finally:
471 finally:
472 fp.close()
472 fp.close()
473
473
474 def unlink(self, fname):
474 def unlink(self, fname):
475 os.unlink(fname)
475 os.unlink(fname)
476
476
477 def printfile(self, warn):
477 def printfile(self, warn):
478 if self.fileprinted:
478 if self.fileprinted:
479 return
479 return
480 if warn or self.ui.verbose:
480 if warn or self.ui.verbose:
481 self.fileprinted = True
481 self.fileprinted = True
482 s = _("patching file %s\n") % self.fname
482 s = _("patching file %s\n") % self.fname
483 if warn:
483 if warn:
484 self.ui.warn(s)
484 self.ui.warn(s)
485 else:
485 else:
486 self.ui.note(s)
486 self.ui.note(s)
487
487
488
488
489 def findlines(self, l, linenum):
489 def findlines(self, l, linenum):
490 # looks through the hash and finds candidate lines. The
490 # looks through the hash and finds candidate lines. The
491 # result is a list of line numbers sorted based on distance
491 # result is a list of line numbers sorted based on distance
492 # from linenum
492 # from linenum
493
493
494 cand = self.hash.get(l, [])
494 cand = self.hash.get(l, [])
495 if len(cand) > 1:
495 if len(cand) > 1:
496 # resort our list of potentials forward then back.
496 # resort our list of potentials forward then back.
497 cand.sort(key=lambda x: abs(x - linenum))
497 cand.sort(key=lambda x: abs(x - linenum))
498 return cand
498 return cand
499
499
500 def hashlines(self):
500 def hashlines(self):
501 self.hash = {}
501 self.hash = {}
502 for x, s in enumerate(self.lines):
502 for x, s in enumerate(self.lines):
503 self.hash.setdefault(s, []).append(x)
503 self.hash.setdefault(s, []).append(x)
504
504
505 def write_rej(self):
505 def write_rej(self):
506 # our rejects are a little different from patch(1). This always
506 # our rejects are a little different from patch(1). This always
507 # creates rejects in the same form as the original patch. A file
507 # creates rejects in the same form as the original patch. A file
508 # header is inserted so that you can run the reject through patch again
508 # header is inserted so that you can run the reject through patch again
509 # without having to type the filename.
509 # without having to type the filename.
510
510
511 if not self.rej:
511 if not self.rej:
512 return
512 return
513
513
514 fname = self.fname + ".rej"
514 fname = self.fname + ".rej"
515 self.ui.warn(
515 self.ui.warn(
516 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
516 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
517 (len(self.rej), self.hunks, fname))
517 (len(self.rej), self.hunks, fname))
518
518
519 def rejlines():
519 def rejlines():
520 base = os.path.basename(self.fname)
520 base = os.path.basename(self.fname)
521 yield "--- %s\n+++ %s\n" % (base, base)
521 yield "--- %s\n+++ %s\n" % (base, base)
522 for x in self.rej:
522 for x in self.rej:
523 for l in x.hunk:
523 for l in x.hunk:
524 yield l
524 yield l
525 if l[-1] != '\n':
525 if l[-1] != '\n':
526 yield "\n\ No newline at end of file\n"
526 yield "\n\ No newline at end of file\n"
527
527
528 self.writelines(fname, rejlines())
528 self.writelines(fname, rejlines())
529
529
530 def apply(self, h):
530 def apply(self, h):
531 if not h.complete():
531 if not h.complete():
532 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
532 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
533 (h.number, h.desc, len(h.a), h.lena, len(h.b),
533 (h.number, h.desc, len(h.a), h.lena, len(h.b),
534 h.lenb))
534 h.lenb))
535
535
536 self.hunks += 1
536 self.hunks += 1
537
537
538 if self.missing:
538 if self.missing:
539 self.rej.append(h)
539 self.rej.append(h)
540 return -1
540 return -1
541
541
542 if self.exists and h.createfile():
542 if self.exists and h.createfile():
543 self.ui.warn(_("file %s already exists\n") % self.fname)
543 self.ui.warn(_("file %s already exists\n") % self.fname)
544 self.rej.append(h)
544 self.rej.append(h)
545 return -1
545 return -1
546
546
547 if isinstance(h, binhunk):
547 if isinstance(h, binhunk):
548 if h.rmfile():
548 if h.rmfile():
549 self.unlink(self.fname)
549 self.unlink(self.fname)
550 else:
550 else:
551 self.lines[:] = h.new()
551 self.lines[:] = h.new()
552 self.offset += len(h.new())
552 self.offset += len(h.new())
553 self.dirty = 1
553 self.dirty = 1
554 return 0
554 return 0
555
555
556 horig = h
556 horig = h
557 if (self.eolmode in ('crlf', 'lf')
557 if (self.eolmode in ('crlf', 'lf')
558 or self.eolmode == 'auto' and self.eol):
558 or self.eolmode == 'auto' and self.eol):
559 # If new eols are going to be normalized, then normalize
559 # If new eols are going to be normalized, then normalize
560 # hunk data before patching. Otherwise, preserve input
560 # hunk data before patching. Otherwise, preserve input
561 # line-endings.
561 # line-endings.
562 h = h.getnormalized()
562 h = h.getnormalized()
563
563
564 # fast case first, no offsets, no fuzz
564 # fast case first, no offsets, no fuzz
565 old = h.old()
565 old = h.old()
566 # patch starts counting at 1 unless we are adding the file
566 # patch starts counting at 1 unless we are adding the file
567 if h.starta == 0:
567 if h.starta == 0:
568 start = 0
568 start = 0
569 else:
569 else:
570 start = h.starta + self.offset - 1
570 start = h.starta + self.offset - 1
571 orig_start = start
571 orig_start = start
572 # if there's skew we want to emit the "(offset %d lines)" even
572 # if there's skew we want to emit the "(offset %d lines)" even
573 # when the hunk cleanly applies at start + skew, so skip the
573 # when the hunk cleanly applies at start + skew, so skip the
574 # fast case code
574 # fast case code
575 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
575 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
576 if h.rmfile():
576 if h.rmfile():
577 self.unlink(self.fname)
577 self.unlink(self.fname)
578 else:
578 else:
579 self.lines[start : start + h.lena] = h.new()
579 self.lines[start : start + h.lena] = h.new()
580 self.offset += h.lenb - h.lena
580 self.offset += h.lenb - h.lena
581 self.dirty = 1
581 self.dirty = 1
582 return 0
582 return 0
583
583
584 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
584 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
585 self.hashlines()
585 self.hashlines()
586 if h.hunk[-1][0] != ' ':
586 if h.hunk[-1][0] != ' ':
587 # if the hunk tried to put something at the bottom of the file
587 # if the hunk tried to put something at the bottom of the file
588 # override the start line and use eof here
588 # override the start line and use eof here
589 search_start = len(self.lines)
589 search_start = len(self.lines)
590 else:
590 else:
591 search_start = orig_start + self.skew
591 search_start = orig_start + self.skew
592
592
593 for fuzzlen in xrange(3):
593 for fuzzlen in xrange(3):
594 for toponly in [True, False]:
594 for toponly in [True, False]:
595 old = h.old(fuzzlen, toponly)
595 old = h.old(fuzzlen, toponly)
596
596
597 cand = self.findlines(old[0][1:], search_start)
597 cand = self.findlines(old[0][1:], search_start)
598 for l in cand:
598 for l in cand:
599 if diffhelpers.testhunk(old, self.lines, l) == 0:
599 if diffhelpers.testhunk(old, self.lines, l) == 0:
600 newlines = h.new(fuzzlen, toponly)
600 newlines = h.new(fuzzlen, toponly)
601 self.lines[l : l + len(old)] = newlines
601 self.lines[l : l + len(old)] = newlines
602 self.offset += len(newlines) - len(old)
602 self.offset += len(newlines) - len(old)
603 self.skew = l - orig_start
603 self.skew = l - orig_start
604 self.dirty = 1
604 self.dirty = 1
605 offset = l - orig_start - fuzzlen
605 offset = l - orig_start - fuzzlen
606 if fuzzlen:
606 if fuzzlen:
607 msg = _("Hunk #%d succeeded at %d "
607 msg = _("Hunk #%d succeeded at %d "
608 "with fuzz %d "
608 "with fuzz %d "
609 "(offset %d lines).\n")
609 "(offset %d lines).\n")
610 self.printfile(True)
610 self.printfile(True)
611 self.ui.warn(msg %
611 self.ui.warn(msg %
612 (h.number, l + 1, fuzzlen, offset))
612 (h.number, l + 1, fuzzlen, offset))
613 else:
613 else:
614 msg = _("Hunk #%d succeeded at %d "
614 msg = _("Hunk #%d succeeded at %d "
615 "(offset %d lines).\n")
615 "(offset %d lines).\n")
616 self.ui.note(msg % (h.number, l + 1, offset))
616 self.ui.note(msg % (h.number, l + 1, offset))
617 return fuzzlen
617 return fuzzlen
618 self.printfile(True)
618 self.printfile(True)
619 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
619 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
620 self.rej.append(horig)
620 self.rej.append(horig)
621 return -1
621 return -1
622
622
623 class hunk(object):
623 class hunk(object):
624 def __init__(self, desc, num, lr, context, create=False, remove=False):
624 def __init__(self, desc, num, lr, context, create=False, remove=False):
625 self.number = num
625 self.number = num
626 self.desc = desc
626 self.desc = desc
627 self.hunk = [desc]
627 self.hunk = [desc]
628 self.a = []
628 self.a = []
629 self.b = []
629 self.b = []
630 self.starta = self.lena = None
630 self.starta = self.lena = None
631 self.startb = self.lenb = None
631 self.startb = self.lenb = None
632 if lr is not None:
632 if lr is not None:
633 if context:
633 if context:
634 self.read_context_hunk(lr)
634 self.read_context_hunk(lr)
635 else:
635 else:
636 self.read_unified_hunk(lr)
636 self.read_unified_hunk(lr)
637 self.create = create
637 self.create = create
638 self.remove = remove and not create
638 self.remove = remove and not create
639
639
640 def getnormalized(self):
640 def getnormalized(self):
641 """Return a copy with line endings normalized to LF."""
641 """Return a copy with line endings normalized to LF."""
642
642
643 def normalize(lines):
643 def normalize(lines):
644 nlines = []
644 nlines = []
645 for line in lines:
645 for line in lines:
646 if line.endswith('\r\n'):
646 if line.endswith('\r\n'):
647 line = line[:-2] + '\n'
647 line = line[:-2] + '\n'
648 nlines.append(line)
648 nlines.append(line)
649 return nlines
649 return nlines
650
650
651 # Dummy object, it is rebuilt manually
651 # Dummy object, it is rebuilt manually
652 nh = hunk(self.desc, self.number, None, None, False, False)
652 nh = hunk(self.desc, self.number, None, None, False, False)
653 nh.number = self.number
653 nh.number = self.number
654 nh.desc = self.desc
654 nh.desc = self.desc
655 nh.hunk = self.hunk
655 nh.hunk = self.hunk
656 nh.a = normalize(self.a)
656 nh.a = normalize(self.a)
657 nh.b = normalize(self.b)
657 nh.b = normalize(self.b)
658 nh.starta = self.starta
658 nh.starta = self.starta
659 nh.startb = self.startb
659 nh.startb = self.startb
660 nh.lena = self.lena
660 nh.lena = self.lena
661 nh.lenb = self.lenb
661 nh.lenb = self.lenb
662 nh.create = self.create
662 nh.create = self.create
663 nh.remove = self.remove
663 nh.remove = self.remove
664 return nh
664 return nh
665
665
666 def read_unified_hunk(self, lr):
666 def read_unified_hunk(self, lr):
667 m = unidesc.match(self.desc)
667 m = unidesc.match(self.desc)
668 if not m:
668 if not m:
669 raise PatchError(_("bad hunk #%d") % self.number)
669 raise PatchError(_("bad hunk #%d") % self.number)
670 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
670 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
671 if self.lena is None:
671 if self.lena is None:
672 self.lena = 1
672 self.lena = 1
673 else:
673 else:
674 self.lena = int(self.lena)
674 self.lena = int(self.lena)
675 if self.lenb is None:
675 if self.lenb is None:
676 self.lenb = 1
676 self.lenb = 1
677 else:
677 else:
678 self.lenb = int(self.lenb)
678 self.lenb = int(self.lenb)
679 self.starta = int(self.starta)
679 self.starta = int(self.starta)
680 self.startb = int(self.startb)
680 self.startb = int(self.startb)
681 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
681 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
682 # if we hit eof before finishing out the hunk, the last line will
682 # if we hit eof before finishing out the hunk, the last line will
683 # be zero length. Lets try to fix it up.
683 # be zero length. Lets try to fix it up.
684 while len(self.hunk[-1]) == 0:
684 while len(self.hunk[-1]) == 0:
685 del self.hunk[-1]
685 del self.hunk[-1]
686 del self.a[-1]
686 del self.a[-1]
687 del self.b[-1]
687 del self.b[-1]
688 self.lena -= 1
688 self.lena -= 1
689 self.lenb -= 1
689 self.lenb -= 1
690
690
691 def read_context_hunk(self, lr):
691 def read_context_hunk(self, lr):
692 self.desc = lr.readline()
692 self.desc = lr.readline()
693 m = contextdesc.match(self.desc)
693 m = contextdesc.match(self.desc)
694 if not m:
694 if not m:
695 raise PatchError(_("bad hunk #%d") % self.number)
695 raise PatchError(_("bad hunk #%d") % self.number)
696 foo, self.starta, foo2, aend, foo3 = m.groups()
696 foo, self.starta, foo2, aend, foo3 = m.groups()
697 self.starta = int(self.starta)
697 self.starta = int(self.starta)
698 if aend is None:
698 if aend is None:
699 aend = self.starta
699 aend = self.starta
700 self.lena = int(aend) - self.starta
700 self.lena = int(aend) - self.starta
701 if self.starta:
701 if self.starta:
702 self.lena += 1
702 self.lena += 1
703 for x in xrange(self.lena):
703 for x in xrange(self.lena):
704 l = lr.readline()
704 l = lr.readline()
705 if l.startswith('---'):
705 if l.startswith('---'):
706 lr.push(l)
706 lr.push(l)
707 break
707 break
708 s = l[2:]
708 s = l[2:]
709 if l.startswith('- ') or l.startswith('! '):
709 if l.startswith('- ') or l.startswith('! '):
710 u = '-' + s
710 u = '-' + s
711 elif l.startswith(' '):
711 elif l.startswith(' '):
712 u = ' ' + s
712 u = ' ' + s
713 else:
713 else:
714 raise PatchError(_("bad hunk #%d old text line %d") %
714 raise PatchError(_("bad hunk #%d old text line %d") %
715 (self.number, x))
715 (self.number, x))
716 self.a.append(u)
716 self.a.append(u)
717 self.hunk.append(u)
717 self.hunk.append(u)
718
718
719 l = lr.readline()
719 l = lr.readline()
720 if l.startswith('\ '):
720 if l.startswith('\ '):
721 s = self.a[-1][:-1]
721 s = self.a[-1][:-1]
722 self.a[-1] = s
722 self.a[-1] = s
723 self.hunk[-1] = s
723 self.hunk[-1] = s
724 l = lr.readline()
724 l = lr.readline()
725 m = contextdesc.match(l)
725 m = contextdesc.match(l)
726 if not m:
726 if not m:
727 raise PatchError(_("bad hunk #%d") % self.number)
727 raise PatchError(_("bad hunk #%d") % self.number)
728 foo, self.startb, foo2, bend, foo3 = m.groups()
728 foo, self.startb, foo2, bend, foo3 = m.groups()
729 self.startb = int(self.startb)
729 self.startb = int(self.startb)
730 if bend is None:
730 if bend is None:
731 bend = self.startb
731 bend = self.startb
732 self.lenb = int(bend) - self.startb
732 self.lenb = int(bend) - self.startb
733 if self.startb:
733 if self.startb:
734 self.lenb += 1
734 self.lenb += 1
735 hunki = 1
735 hunki = 1
736 for x in xrange(self.lenb):
736 for x in xrange(self.lenb):
737 l = lr.readline()
737 l = lr.readline()
738 if l.startswith('\ '):
738 if l.startswith('\ '):
739 s = self.b[-1][:-1]
739 s = self.b[-1][:-1]
740 self.b[-1] = s
740 self.b[-1] = s
741 self.hunk[hunki - 1] = s
741 self.hunk[hunki - 1] = s
742 continue
742 continue
743 if not l:
743 if not l:
744 lr.push(l)
744 lr.push(l)
745 break
745 break
746 s = l[2:]
746 s = l[2:]
747 if l.startswith('+ ') or l.startswith('! '):
747 if l.startswith('+ ') or l.startswith('! '):
748 u = '+' + s
748 u = '+' + s
749 elif l.startswith(' '):
749 elif l.startswith(' '):
750 u = ' ' + s
750 u = ' ' + s
751 elif len(self.b) == 0:
751 elif len(self.b) == 0:
752 # this can happen when the hunk does not add any lines
752 # this can happen when the hunk does not add any lines
753 lr.push(l)
753 lr.push(l)
754 break
754 break
755 else:
755 else:
756 raise PatchError(_("bad hunk #%d old text line %d") %
756 raise PatchError(_("bad hunk #%d old text line %d") %
757 (self.number, x))
757 (self.number, x))
758 self.b.append(s)
758 self.b.append(s)
759 while True:
759 while True:
760 if hunki >= len(self.hunk):
760 if hunki >= len(self.hunk):
761 h = ""
761 h = ""
762 else:
762 else:
763 h = self.hunk[hunki]
763 h = self.hunk[hunki]
764 hunki += 1
764 hunki += 1
765 if h == u:
765 if h == u:
766 break
766 break
767 elif h.startswith('-'):
767 elif h.startswith('-'):
768 continue
768 continue
769 else:
769 else:
770 self.hunk.insert(hunki - 1, u)
770 self.hunk.insert(hunki - 1, u)
771 break
771 break
772
772
773 if not self.a:
773 if not self.a:
774 # this happens when lines were only added to the hunk
774 # this happens when lines were only added to the hunk
775 for x in self.hunk:
775 for x in self.hunk:
776 if x.startswith('-') or x.startswith(' '):
776 if x.startswith('-') or x.startswith(' '):
777 self.a.append(x)
777 self.a.append(x)
778 if not self.b:
778 if not self.b:
779 # this happens when lines were only deleted from the hunk
779 # this happens when lines were only deleted from the hunk
780 for x in self.hunk:
780 for x in self.hunk:
781 if x.startswith('+') or x.startswith(' '):
781 if x.startswith('+') or x.startswith(' '):
782 self.b.append(x[1:])
782 self.b.append(x[1:])
783 # @@ -start,len +start,len @@
783 # @@ -start,len +start,len @@
784 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
784 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
785 self.startb, self.lenb)
785 self.startb, self.lenb)
786 self.hunk[0] = self.desc
786 self.hunk[0] = self.desc
787
787
788 def fix_newline(self):
788 def fix_newline(self):
789 diffhelpers.fix_newline(self.hunk, self.a, self.b)
789 diffhelpers.fix_newline(self.hunk, self.a, self.b)
790
790
791 def complete(self):
791 def complete(self):
792 return len(self.a) == self.lena and len(self.b) == self.lenb
792 return len(self.a) == self.lena and len(self.b) == self.lenb
793
793
794 def createfile(self):
794 def createfile(self):
795 return self.starta == 0 and self.lena == 0 and self.create
795 return self.starta == 0 and self.lena == 0 and self.create
796
796
797 def rmfile(self):
797 def rmfile(self):
798 return self.startb == 0 and self.lenb == 0 and self.remove
798 return self.startb == 0 and self.lenb == 0 and self.remove
799
799
800 def fuzzit(self, l, fuzz, toponly):
800 def fuzzit(self, l, fuzz, toponly):
801 # this removes context lines from the top and bottom of list 'l'. It
801 # this removes context lines from the top and bottom of list 'l'. It
802 # checks the hunk to make sure only context lines are removed, and then
802 # checks the hunk to make sure only context lines are removed, and then
803 # returns a new shortened list of lines.
803 # returns a new shortened list of lines.
804 fuzz = min(fuzz, len(l)-1)
804 fuzz = min(fuzz, len(l)-1)
805 if fuzz:
805 if fuzz:
806 top = 0
806 top = 0
807 bot = 0
807 bot = 0
808 hlen = len(self.hunk)
808 hlen = len(self.hunk)
809 for x in xrange(hlen - 1):
809 for x in xrange(hlen - 1):
810 # the hunk starts with the @@ line, so use x+1
810 # the hunk starts with the @@ line, so use x+1
811 if self.hunk[x + 1][0] == ' ':
811 if self.hunk[x + 1][0] == ' ':
812 top += 1
812 top += 1
813 else:
813 else:
814 break
814 break
815 if not toponly:
815 if not toponly:
816 for x in xrange(hlen - 1):
816 for x in xrange(hlen - 1):
817 if self.hunk[hlen - bot - 1][0] == ' ':
817 if self.hunk[hlen - bot - 1][0] == ' ':
818 bot += 1
818 bot += 1
819 else:
819 else:
820 break
820 break
821
821
822 # top and bot now count context in the hunk
822 # top and bot now count context in the hunk
823 # adjust them if either one is short
823 # adjust them if either one is short
824 context = max(top, bot, 3)
824 context = max(top, bot, 3)
825 if bot < context:
825 if bot < context:
826 bot = max(0, fuzz - (context - bot))
826 bot = max(0, fuzz - (context - bot))
827 else:
827 else:
828 bot = min(fuzz, bot)
828 bot = min(fuzz, bot)
829 if top < context:
829 if top < context:
830 top = max(0, fuzz - (context - top))
830 top = max(0, fuzz - (context - top))
831 else:
831 else:
832 top = min(fuzz, top)
832 top = min(fuzz, top)
833
833
834 return l[top:len(l)-bot]
834 return l[top:len(l)-bot]
835 return l
835 return l
836
836
837 def old(self, fuzz=0, toponly=False):
837 def old(self, fuzz=0, toponly=False):
838 return self.fuzzit(self.a, fuzz, toponly)
838 return self.fuzzit(self.a, fuzz, toponly)
839
839
840 def new(self, fuzz=0, toponly=False):
840 def new(self, fuzz=0, toponly=False):
841 return self.fuzzit(self.b, fuzz, toponly)
841 return self.fuzzit(self.b, fuzz, toponly)
842
842
843 class binhunk:
843 class binhunk:
844 'A binary patch file. Only understands literals so far.'
844 'A binary patch file. Only understands literals so far.'
845 def __init__(self, gitpatch):
845 def __init__(self, gitpatch):
846 self.gitpatch = gitpatch
846 self.gitpatch = gitpatch
847 self.text = None
847 self.text = None
848 self.hunk = ['GIT binary patch\n']
848 self.hunk = ['GIT binary patch\n']
849
849
850 def createfile(self):
850 def createfile(self):
851 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
851 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
852
852
853 def rmfile(self):
853 def rmfile(self):
854 return self.gitpatch.op == 'DELETE'
854 return self.gitpatch.op == 'DELETE'
855
855
856 def complete(self):
856 def complete(self):
857 return self.text is not None
857 return self.text is not None
858
858
859 def new(self):
859 def new(self):
860 return [self.text]
860 return [self.text]
861
861
862 def extract(self, lr):
862 def extract(self, lr):
863 line = lr.readline()
863 line = lr.readline()
864 self.hunk.append(line)
864 self.hunk.append(line)
865 while line and not line.startswith('literal '):
865 while line and not line.startswith('literal '):
866 line = lr.readline()
866 line = lr.readline()
867 self.hunk.append(line)
867 self.hunk.append(line)
868 if not line:
868 if not line:
869 raise PatchError(_('could not extract binary patch'))
869 raise PatchError(_('could not extract binary patch'))
870 size = int(line[8:].rstrip())
870 size = int(line[8:].rstrip())
871 dec = []
871 dec = []
872 line = lr.readline()
872 line = lr.readline()
873 self.hunk.append(line)
873 self.hunk.append(line)
874 while len(line) > 1:
874 while len(line) > 1:
875 l = line[0]
875 l = line[0]
876 if l <= 'Z' and l >= 'A':
876 if l <= 'Z' and l >= 'A':
877 l = ord(l) - ord('A') + 1
877 l = ord(l) - ord('A') + 1
878 else:
878 else:
879 l = ord(l) - ord('a') + 27
879 l = ord(l) - ord('a') + 27
880 dec.append(base85.b85decode(line[1:-1])[:l])
880 dec.append(base85.b85decode(line[1:-1])[:l])
881 line = lr.readline()
881 line = lr.readline()
882 self.hunk.append(line)
882 self.hunk.append(line)
883 text = zlib.decompress(''.join(dec))
883 text = zlib.decompress(''.join(dec))
884 if len(text) != size:
884 if len(text) != size:
885 raise PatchError(_('binary patch is %d bytes, not %d') %
885 raise PatchError(_('binary patch is %d bytes, not %d') %
886 len(text), size)
886 len(text), size)
887 self.text = text
887 self.text = text
888
888
889 def parsefilename(str):
889 def parsefilename(str):
890 # --- filename \t|space stuff
890 # --- filename \t|space stuff
891 s = str[4:].rstrip('\r\n')
891 s = str[4:].rstrip('\r\n')
892 i = s.find('\t')
892 i = s.find('\t')
893 if i < 0:
893 if i < 0:
894 i = s.find(' ')
894 i = s.find(' ')
895 if i < 0:
895 if i < 0:
896 return s
896 return s
897 return s[:i]
897 return s[:i]
898
898
899 def pathstrip(path, strip):
899 def pathstrip(path, strip):
900 pathlen = len(path)
900 pathlen = len(path)
901 i = 0
901 i = 0
902 if strip == 0:
902 if strip == 0:
903 return '', path.rstrip()
903 return '', path.rstrip()
904 count = strip
904 count = strip
905 while count > 0:
905 while count > 0:
906 i = path.find('/', i)
906 i = path.find('/', i)
907 if i == -1:
907 if i == -1:
908 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
908 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
909 (count, strip, path))
909 (count, strip, path))
910 i += 1
910 i += 1
911 # consume '//' in the path
911 # consume '//' in the path
912 while i < pathlen - 1 and path[i] == '/':
912 while i < pathlen - 1 and path[i] == '/':
913 i += 1
913 i += 1
914 count -= 1
914 count -= 1
915 return path[:i].lstrip(), path[i:].rstrip()
915 return path[:i].lstrip(), path[i:].rstrip()
916
916
917 def selectfile(afile_orig, bfile_orig, hunk, strip):
917 def selectfile(afile_orig, bfile_orig, hunk, strip):
918 nulla = afile_orig == "/dev/null"
918 nulla = afile_orig == "/dev/null"
919 nullb = bfile_orig == "/dev/null"
919 nullb = bfile_orig == "/dev/null"
920 abase, afile = pathstrip(afile_orig, strip)
920 abase, afile = pathstrip(afile_orig, strip)
921 gooda = not nulla and os.path.lexists(afile)
921 gooda = not nulla and os.path.lexists(afile)
922 bbase, bfile = pathstrip(bfile_orig, strip)
922 bbase, bfile = pathstrip(bfile_orig, strip)
923 if afile == bfile:
923 if afile == bfile:
924 goodb = gooda
924 goodb = gooda
925 else:
925 else:
926 goodb = not nullb and os.path.exists(bfile)
926 goodb = not nullb and os.path.exists(bfile)
927 createfunc = hunk.createfile
927 createfunc = hunk.createfile
928 missing = not goodb and not gooda and not createfunc()
928 missing = not goodb and not gooda and not createfunc()
929
929
930 # some diff programs apparently produce patches where the afile is
930 # some diff programs apparently produce patches where the afile is
931 # not /dev/null, but afile starts with bfile
931 # not /dev/null, but afile starts with bfile
932 abasedir = afile[:afile.rfind('/') + 1]
932 abasedir = afile[:afile.rfind('/') + 1]
933 bbasedir = bfile[:bfile.rfind('/') + 1]
933 bbasedir = bfile[:bfile.rfind('/') + 1]
934 if missing and abasedir == bbasedir and afile.startswith(bfile):
934 if missing and abasedir == bbasedir and afile.startswith(bfile):
935 # this isn't very pretty
935 # this isn't very pretty
936 hunk.create = True
936 hunk.create = True
937 if createfunc():
937 if createfunc():
938 missing = False
938 missing = False
939 else:
939 else:
940 hunk.create = False
940 hunk.create = False
941
941
942 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
942 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
943 # diff is between a file and its backup. In this case, the original
943 # diff is between a file and its backup. In this case, the original
944 # file should be patched (see original mpatch code).
944 # file should be patched (see original mpatch code).
945 isbackup = (abase == bbase and bfile.startswith(afile))
945 isbackup = (abase == bbase and bfile.startswith(afile))
946 fname = None
946 fname = None
947 if not missing:
947 if not missing:
948 if gooda and goodb:
948 if gooda and goodb:
949 fname = isbackup and afile or bfile
949 fname = isbackup and afile or bfile
950 elif gooda:
950 elif gooda:
951 fname = afile
951 fname = afile
952
952
953 if not fname:
953 if not fname:
954 if not nullb:
954 if not nullb:
955 fname = isbackup and afile or bfile
955 fname = isbackup and afile or bfile
956 elif not nulla:
956 elif not nulla:
957 fname = afile
957 fname = afile
958 else:
958 else:
959 raise PatchError(_("undefined source and destination files"))
959 raise PatchError(_("undefined source and destination files"))
960
960
961 return fname, missing
961 return fname, missing
962
962
963 def scangitpatch(lr, firstline):
963 def scangitpatch(lr, firstline):
964 """
964 """
965 Git patches can emit:
965 Git patches can emit:
966 - rename a to b
966 - rename a to b
967 - change b
967 - change b
968 - copy a to c
968 - copy a to c
969 - change c
969 - change c
970
970
971 We cannot apply this sequence as-is, the renamed 'a' could not be
971 We cannot apply this sequence as-is, the renamed 'a' could not be
972 found for it would have been renamed already. And we cannot copy
972 found for it would have been renamed already. And we cannot copy
973 from 'b' instead because 'b' would have been changed already. So
973 from 'b' instead because 'b' would have been changed already. So
974 we scan the git patch for copy and rename commands so we can
974 we scan the git patch for copy and rename commands so we can
975 perform the copies ahead of time.
975 perform the copies ahead of time.
976 """
976 """
977 pos = 0
977 pos = 0
978 try:
978 try:
979 pos = lr.fp.tell()
979 pos = lr.fp.tell()
980 fp = lr.fp
980 fp = lr.fp
981 except IOError:
981 except IOError:
982 fp = cStringIO.StringIO(lr.fp.read())
982 fp = cStringIO.StringIO(lr.fp.read())
983 gitlr = linereader(fp, lr.textmode)
983 gitlr = linereader(fp, lr.textmode)
984 gitlr.push(firstline)
984 gitlr.push(firstline)
985 (dopatch, gitpatches) = readgitpatch(gitlr)
985 (dopatch, gitpatches) = readgitpatch(gitlr)
986 fp.seek(pos)
986 fp.seek(pos)
987 return dopatch, gitpatches
987 return dopatch, gitpatches
988
988
989 def iterhunks(ui, fp, sourcefile=None):
989 def iterhunks(ui, fp, sourcefile=None):
990 """Read a patch and yield the following events:
990 """Read a patch and yield the following events:
991 - ("file", afile, bfile, firsthunk): select a new target file.
991 - ("file", afile, bfile, firsthunk): select a new target file.
992 - ("hunk", hunk): a new hunk is ready to be applied, follows a
992 - ("hunk", hunk): a new hunk is ready to be applied, follows a
993 "file" event.
993 "file" event.
994 - ("git", gitchanges): current diff is in git format, gitchanges
994 - ("git", gitchanges): current diff is in git format, gitchanges
995 maps filenames to gitpatch records. Unique event.
995 maps filenames to gitpatch records. Unique event.
996 """
996 """
997 changed = {}
997 changed = {}
998 current_hunk = None
998 current_hunk = None
999 afile = ""
999 afile = ""
1000 bfile = ""
1000 bfile = ""
1001 state = None
1001 state = None
1002 hunknum = 0
1002 hunknum = 0
1003 emitfile = False
1003 emitfile = False
1004 git = False
1004 git = False
1005
1005
1006 # our states
1006 # our states
1007 BFILE = 1
1007 BFILE = 1
1008 context = None
1008 context = None
1009 lr = linereader(fp)
1009 lr = linereader(fp)
1010 # gitworkdone is True if a git operation (copy, rename, ...) was
1010 # gitworkdone is True if a git operation (copy, rename, ...) was
1011 # performed already for the current file. Useful when the file
1011 # performed already for the current file. Useful when the file
1012 # section may have no hunk.
1012 # section may have no hunk.
1013 gitworkdone = False
1013 gitworkdone = False
1014 empty = None
1014 empty = None
1015
1015
1016 while True:
1016 while True:
1017 newfile = newgitfile = False
1017 newfile = newgitfile = False
1018 x = lr.readline()
1018 x = lr.readline()
1019 if not x:
1019 if not x:
1020 break
1020 break
1021 if current_hunk:
1021 if current_hunk:
1022 if x.startswith('\ '):
1022 if x.startswith('\ '):
1023 current_hunk.fix_newline()
1023 current_hunk.fix_newline()
1024 yield 'hunk', current_hunk
1024 yield 'hunk', current_hunk
1025 current_hunk = None
1025 current_hunk = None
1026 empty = False
1026 empty = False
1027 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
1027 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
1028 ((context is not False) and x.startswith('***************')))):
1028 ((context is not False) and x.startswith('***************')))):
1029 try:
1029 try:
1030 if context is None and x.startswith('***************'):
1030 if context is None and x.startswith('***************'):
1031 context = True
1031 context = True
1032 gpatch = changed.get(bfile)
1032 gpatch = changed.get(bfile)
1033 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1033 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1034 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1034 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1035 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
1035 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
1036 except PatchError, err:
1036 except PatchError, err:
1037 ui.debug(err)
1037 ui.debug(err)
1038 current_hunk = None
1038 current_hunk = None
1039 continue
1039 continue
1040 hunknum += 1
1040 hunknum += 1
1041 if emitfile:
1041 if emitfile:
1042 emitfile = False
1042 emitfile = False
1043 yield 'file', (afile, bfile, current_hunk)
1043 yield 'file', (afile, bfile, current_hunk)
1044 empty = False
1044 empty = False
1045 elif state == BFILE and x.startswith('GIT binary patch'):
1045 elif state == BFILE and x.startswith('GIT binary patch'):
1046 current_hunk = binhunk(changed[bfile])
1046 current_hunk = binhunk(changed[bfile])
1047 hunknum += 1
1047 hunknum += 1
1048 if emitfile:
1048 if emitfile:
1049 emitfile = False
1049 emitfile = False
1050 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
1050 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
1051 empty = False
1051 empty = False
1052 current_hunk.extract(lr)
1052 current_hunk.extract(lr)
1053 elif x.startswith('diff --git'):
1053 elif x.startswith('diff --git'):
1054 # check for git diff, scanning the whole patch file if needed
1054 # check for git diff, scanning the whole patch file if needed
1055 m = gitre.match(x)
1055 m = gitre.match(x)
1056 gitworkdone = False
1056 gitworkdone = False
1057 if m:
1057 if m:
1058 afile, bfile = m.group(1, 2)
1058 afile, bfile = m.group(1, 2)
1059 if not git:
1059 if not git:
1060 git = True
1060 git = True
1061 gitpatches = scangitpatch(lr, x)[1]
1061 gitpatches = scangitpatch(lr, x)[1]
1062 yield 'git', gitpatches
1062 yield 'git', gitpatches
1063 for gp in gitpatches:
1063 for gp in gitpatches:
1064 changed[gp.path] = gp
1064 changed[gp.path] = gp
1065 # else error?
1065 # else error?
1066 # copy/rename + modify should modify target, not source
1066 # copy/rename + modify should modify target, not source
1067 gp = changed.get(bfile)
1067 gp = changed.get(bfile)
1068 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1068 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1069 or gp.mode):
1069 or gp.mode):
1070 afile = bfile
1070 afile = bfile
1071 gitworkdone = True
1071 gitworkdone = True
1072 newgitfile = True
1072 newgitfile = True
1073 elif x.startswith('---'):
1073 elif x.startswith('---'):
1074 # check for a unified diff
1074 # check for a unified diff
1075 l2 = lr.readline()
1075 l2 = lr.readline()
1076 if not l2.startswith('+++'):
1076 if not l2.startswith('+++'):
1077 lr.push(l2)
1077 lr.push(l2)
1078 continue
1078 continue
1079 newfile = True
1079 newfile = True
1080 context = False
1080 context = False
1081 afile = parsefilename(x)
1081 afile = parsefilename(x)
1082 bfile = parsefilename(l2)
1082 bfile = parsefilename(l2)
1083 elif x.startswith('***'):
1083 elif x.startswith('***'):
1084 # check for a context diff
1084 # check for a context diff
1085 l2 = lr.readline()
1085 l2 = lr.readline()
1086 if not l2.startswith('---'):
1086 if not l2.startswith('---'):
1087 lr.push(l2)
1087 lr.push(l2)
1088 continue
1088 continue
1089 l3 = lr.readline()
1089 l3 = lr.readline()
1090 lr.push(l3)
1090 lr.push(l3)
1091 if not l3.startswith("***************"):
1091 if not l3.startswith("***************"):
1092 lr.push(l2)
1092 lr.push(l2)
1093 continue
1093 continue
1094 newfile = True
1094 newfile = True
1095 context = True
1095 context = True
1096 afile = parsefilename(x)
1096 afile = parsefilename(x)
1097 bfile = parsefilename(l2)
1097 bfile = parsefilename(l2)
1098
1098
1099 if newfile:
1099 if newfile:
1100 if empty:
1100 if empty:
1101 raise NoHunks
1101 raise NoHunks
1102 empty = not gitworkdone
1102 empty = not gitworkdone
1103 gitworkdone = False
1103 gitworkdone = False
1104
1104
1105 if newgitfile or newfile:
1105 if newgitfile or newfile:
1106 emitfile = True
1106 emitfile = True
1107 state = BFILE
1107 state = BFILE
1108 hunknum = 0
1108 hunknum = 0
1109 if current_hunk:
1109 if current_hunk:
1110 if current_hunk.complete():
1110 if current_hunk.complete():
1111 yield 'hunk', current_hunk
1111 yield 'hunk', current_hunk
1112 empty = False
1112 empty = False
1113 else:
1113 else:
1114 raise PatchError(_("malformed patch %s %s") % (afile,
1114 raise PatchError(_("malformed patch %s %s") % (afile,
1115 current_hunk.desc))
1115 current_hunk.desc))
1116
1116
1117 if (empty is None and not gitworkdone) or empty:
1117 if (empty is None and not gitworkdone) or empty:
1118 raise NoHunks
1118 raise NoHunks
1119
1119
1120
1120
1121 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1121 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1122 """Reads a patch from fp and tries to apply it.
1122 """Reads a patch from fp and tries to apply it.
1123
1123
1124 The dict 'changed' is filled in with all of the filenames changed
1124 The dict 'changed' is filled in with all of the filenames changed
1125 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1125 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1126 found and 1 if there was any fuzz.
1126 found and 1 if there was any fuzz.
1127
1127
1128 If 'eolmode' is 'strict', the patch content and patched file are
1128 If 'eolmode' is 'strict', the patch content and patched file are
1129 read in binary mode. Otherwise, line endings are ignored when
1129 read in binary mode. Otherwise, line endings are ignored when
1130 patching then normalized according to 'eolmode'.
1130 patching then normalized according to 'eolmode'.
1131
1131
1132 Callers probably want to call 'updatedir' after this to apply
1132 Callers probably want to call 'cmdutil.updatedir' after this to
1133 certain categories of changes not done by this function.
1133 apply certain categories of changes not done by this function.
1134 """
1134 """
1135 return _applydiff(
1135 return _applydiff(
1136 ui, fp, patchfile, copyfile,
1136 ui, fp, patchfile, copyfile,
1137 changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
1137 changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
1138
1138
1139
1139
1140 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
1140 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
1141 sourcefile=None, eolmode='strict'):
1141 sourcefile=None, eolmode='strict'):
1142 rejects = 0
1142 rejects = 0
1143 err = 0
1143 err = 0
1144 current_file = None
1144 current_file = None
1145 cwd = os.getcwd()
1145 cwd = os.getcwd()
1146 opener = util.opener(cwd)
1146 opener = util.opener(cwd)
1147
1147
1148 def closefile():
1148 def closefile():
1149 if not current_file:
1149 if not current_file:
1150 return 0
1150 return 0
1151 if current_file.dirty:
1151 if current_file.dirty:
1152 current_file.writelines(current_file.fname, current_file.lines)
1152 current_file.writelines(current_file.fname, current_file.lines)
1153 current_file.write_rej()
1153 current_file.write_rej()
1154 return len(current_file.rej)
1154 return len(current_file.rej)
1155
1155
1156 for state, values in iterhunks(ui, fp, sourcefile):
1156 for state, values in iterhunks(ui, fp, sourcefile):
1157 if state == 'hunk':
1157 if state == 'hunk':
1158 if not current_file:
1158 if not current_file:
1159 continue
1159 continue
1160 ret = current_file.apply(values)
1160 ret = current_file.apply(values)
1161 if ret >= 0:
1161 if ret >= 0:
1162 changed.setdefault(current_file.fname, None)
1162 changed.setdefault(current_file.fname, None)
1163 if ret > 0:
1163 if ret > 0:
1164 err = 1
1164 err = 1
1165 elif state == 'file':
1165 elif state == 'file':
1166 rejects += closefile()
1166 rejects += closefile()
1167 afile, bfile, first_hunk = values
1167 afile, bfile, first_hunk = values
1168 try:
1168 try:
1169 if sourcefile:
1169 if sourcefile:
1170 current_file = patcher(ui, sourcefile, opener,
1170 current_file = patcher(ui, sourcefile, opener,
1171 eolmode=eolmode)
1171 eolmode=eolmode)
1172 else:
1172 else:
1173 current_file, missing = selectfile(afile, bfile,
1173 current_file, missing = selectfile(afile, bfile,
1174 first_hunk, strip)
1174 first_hunk, strip)
1175 current_file = patcher(ui, current_file, opener,
1175 current_file = patcher(ui, current_file, opener,
1176 missing=missing, eolmode=eolmode)
1176 missing=missing, eolmode=eolmode)
1177 except PatchError, err:
1177 except PatchError, err:
1178 ui.warn(str(err) + '\n')
1178 ui.warn(str(err) + '\n')
1179 current_file = None
1179 current_file = None
1180 rejects += 1
1180 rejects += 1
1181 continue
1181 continue
1182 elif state == 'git':
1182 elif state == 'git':
1183 for gp in values:
1183 for gp in values:
1184 gp.path = pathstrip(gp.path, strip - 1)[1]
1184 gp.path = pathstrip(gp.path, strip - 1)[1]
1185 if gp.oldpath:
1185 if gp.oldpath:
1186 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1186 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1187 if gp.op in ('COPY', 'RENAME'):
1187 if gp.op in ('COPY', 'RENAME'):
1188 copyfn(gp.oldpath, gp.path, cwd)
1188 copyfn(gp.oldpath, gp.path, cwd)
1189 changed[gp.path] = gp
1189 changed[gp.path] = gp
1190 else:
1190 else:
1191 raise util.Abort(_('unsupported parser state: %s') % state)
1191 raise util.Abort(_('unsupported parser state: %s') % state)
1192
1192
1193 rejects += closefile()
1193 rejects += closefile()
1194
1194
1195 if rejects:
1195 if rejects:
1196 return -1
1196 return -1
1197 return err
1197 return err
1198
1198
1199 def updatedir(ui, repo, patches, similarity=0):
1200 '''Update dirstate after patch application according to metadata'''
1201 if not patches:
1202 return
1203 copies = []
1204 removes = set()
1205 cfiles = patches.keys()
1206 cwd = repo.getcwd()
1207 if cwd:
1208 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1209 for f in patches:
1210 gp = patches[f]
1211 if not gp:
1212 continue
1213 if gp.op == 'RENAME':
1214 copies.append((gp.oldpath, gp.path))
1215 removes.add(gp.oldpath)
1216 elif gp.op == 'COPY':
1217 copies.append((gp.oldpath, gp.path))
1218 elif gp.op == 'DELETE':
1219 removes.add(gp.path)
1220
1221 wctx = repo[None]
1222 for src, dst in copies:
1223 wctx.copy(src, dst)
1224 if (not similarity) and removes:
1225 wctx.remove(sorted(removes), True)
1226
1227 for f in patches:
1228 gp = patches[f]
1229 if gp and gp.mode:
1230 islink, isexec = gp.mode
1231 dst = repo.wjoin(gp.path)
1232 # patch won't create empty files
1233 if gp.op == 'ADD' and not os.path.exists(dst):
1234 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1235 repo.wwrite(gp.path, '', flags)
1236 util.set_flags(dst, islink, isexec)
1237 cmdutil.addremove(repo, cfiles, similarity=similarity)
1238 files = patches.keys()
1239 files.extend([r for r in removes if r not in files])
1240 return sorted(files)
1241
1242 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1199 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1243 """use <patcher> to apply <patchname> to the working directory.
1200 """use <patcher> to apply <patchname> to the working directory.
1244 returns whether patch was applied with fuzz factor."""
1201 returns whether patch was applied with fuzz factor."""
1245
1202
1246 fuzz = False
1203 fuzz = False
1247 if cwd:
1204 if cwd:
1248 args.append('-d %s' % util.shellquote(cwd))
1205 args.append('-d %s' % util.shellquote(cwd))
1249 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1206 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1250 util.shellquote(patchname)))
1207 util.shellquote(patchname)))
1251
1208
1252 for line in fp:
1209 for line in fp:
1253 line = line.rstrip()
1210 line = line.rstrip()
1254 ui.note(line + '\n')
1211 ui.note(line + '\n')
1255 if line.startswith('patching file '):
1212 if line.startswith('patching file '):
1256 pf = util.parse_patch_output(line)
1213 pf = util.parse_patch_output(line)
1257 printed_file = False
1214 printed_file = False
1258 files.setdefault(pf, None)
1215 files.setdefault(pf, None)
1259 elif line.find('with fuzz') >= 0:
1216 elif line.find('with fuzz') >= 0:
1260 fuzz = True
1217 fuzz = True
1261 if not printed_file:
1218 if not printed_file:
1262 ui.warn(pf + '\n')
1219 ui.warn(pf + '\n')
1263 printed_file = True
1220 printed_file = True
1264 ui.warn(line + '\n')
1221 ui.warn(line + '\n')
1265 elif line.find('saving rejects to file') >= 0:
1222 elif line.find('saving rejects to file') >= 0:
1266 ui.warn(line + '\n')
1223 ui.warn(line + '\n')
1267 elif line.find('FAILED') >= 0:
1224 elif line.find('FAILED') >= 0:
1268 if not printed_file:
1225 if not printed_file:
1269 ui.warn(pf + '\n')
1226 ui.warn(pf + '\n')
1270 printed_file = True
1227 printed_file = True
1271 ui.warn(line + '\n')
1228 ui.warn(line + '\n')
1272 code = fp.close()
1229 code = fp.close()
1273 if code:
1230 if code:
1274 raise PatchError(_("patch command failed: %s") %
1231 raise PatchError(_("patch command failed: %s") %
1275 util.explain_exit(code)[0])
1232 util.explain_exit(code)[0])
1276 return fuzz
1233 return fuzz
1277
1234
1278 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1235 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1279 """use builtin patch to apply <patchobj> to the working directory.
1236 """use builtin patch to apply <patchobj> to the working directory.
1280 returns whether patch was applied with fuzz factor."""
1237 returns whether patch was applied with fuzz factor."""
1281
1238
1282 if files is None:
1239 if files is None:
1283 files = {}
1240 files = {}
1284 if eolmode is None:
1241 if eolmode is None:
1285 eolmode = ui.config('patch', 'eol', 'strict')
1242 eolmode = ui.config('patch', 'eol', 'strict')
1286 if eolmode.lower() not in eolmodes:
1243 if eolmode.lower() not in eolmodes:
1287 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1244 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1288 eolmode = eolmode.lower()
1245 eolmode = eolmode.lower()
1289
1246
1290 try:
1247 try:
1291 fp = open(patchobj, 'rb')
1248 fp = open(patchobj, 'rb')
1292 except TypeError:
1249 except TypeError:
1293 fp = patchobj
1250 fp = patchobj
1294 if cwd:
1251 if cwd:
1295 curdir = os.getcwd()
1252 curdir = os.getcwd()
1296 os.chdir(cwd)
1253 os.chdir(cwd)
1297 try:
1254 try:
1298 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1255 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1299 finally:
1256 finally:
1300 if cwd:
1257 if cwd:
1301 os.chdir(curdir)
1258 os.chdir(curdir)
1302 if fp != patchobj:
1259 if fp != patchobj:
1303 fp.close()
1260 fp.close()
1304 if ret < 0:
1261 if ret < 0:
1305 raise PatchError
1262 raise PatchError
1306 return ret > 0
1263 return ret > 0
1307
1264
1308 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1265 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1309 """Apply <patchname> to the working directory.
1266 """Apply <patchname> to the working directory.
1310
1267
1311 'eolmode' specifies how end of lines should be handled. It can be:
1268 'eolmode' specifies how end of lines should be handled. It can be:
1312 - 'strict': inputs are read in binary mode, EOLs are preserved
1269 - 'strict': inputs are read in binary mode, EOLs are preserved
1313 - 'crlf': EOLs are ignored when patching and reset to CRLF
1270 - 'crlf': EOLs are ignored when patching and reset to CRLF
1314 - 'lf': EOLs are ignored when patching and reset to LF
1271 - 'lf': EOLs are ignored when patching and reset to LF
1315 - None: get it from user settings, default to 'strict'
1272 - None: get it from user settings, default to 'strict'
1316 'eolmode' is ignored when using an external patcher program.
1273 'eolmode' is ignored when using an external patcher program.
1317
1274
1318 Returns whether patch was applied with fuzz factor.
1275 Returns whether patch was applied with fuzz factor.
1319 """
1276 """
1320 patcher = ui.config('ui', 'patch')
1277 patcher = ui.config('ui', 'patch')
1321 args = []
1278 args = []
1322 if files is None:
1279 if files is None:
1323 files = {}
1280 files = {}
1324 try:
1281 try:
1325 if patcher:
1282 if patcher:
1326 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1283 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1327 files)
1284 files)
1328 else:
1285 else:
1329 try:
1286 try:
1330 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1287 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1331 except NoHunks:
1288 except NoHunks:
1332 ui.warn(_('internal patcher failed\n'
1289 ui.warn(_('internal patcher failed\n'
1333 'please report details to '
1290 'please report details to '
1334 'http://mercurial.selenic.com/bts/\n'
1291 'http://mercurial.selenic.com/bts/\n'
1335 'or mercurial@selenic.com\n'))
1292 'or mercurial@selenic.com\n'))
1336 patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1293 patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1337 or 'patch')
1294 or 'patch')
1338 ui.debug('no valid hunks found; trying with %r instead\n' %
1295 ui.debug('no valid hunks found; trying with %r instead\n' %
1339 patcher)
1296 patcher)
1340 if util.needbinarypatch():
1297 if util.needbinarypatch():
1341 args.append('--binary')
1298 args.append('--binary')
1342 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1299 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1343 files)
1300 files)
1344 except PatchError, err:
1301 except PatchError, err:
1345 s = str(err)
1302 s = str(err)
1346 if s:
1303 if s:
1347 raise util.Abort(s)
1304 raise util.Abort(s)
1348 else:
1305 else:
1349 raise util.Abort(_('patch failed to apply'))
1306 raise util.Abort(_('patch failed to apply'))
1350
1307
1351 def b85diff(to, tn):
1308 def b85diff(to, tn):
1352 '''print base85-encoded binary diff'''
1309 '''print base85-encoded binary diff'''
1353 def gitindex(text):
1310 def gitindex(text):
1354 if not text:
1311 if not text:
1355 return hex(nullid)
1312 return hex(nullid)
1356 l = len(text)
1313 l = len(text)
1357 s = util.sha1('blob %d\0' % l)
1314 s = util.sha1('blob %d\0' % l)
1358 s.update(text)
1315 s.update(text)
1359 return s.hexdigest()
1316 return s.hexdigest()
1360
1317
1361 def fmtline(line):
1318 def fmtline(line):
1362 l = len(line)
1319 l = len(line)
1363 if l <= 26:
1320 if l <= 26:
1364 l = chr(ord('A') + l - 1)
1321 l = chr(ord('A') + l - 1)
1365 else:
1322 else:
1366 l = chr(l - 26 + ord('a') - 1)
1323 l = chr(l - 26 + ord('a') - 1)
1367 return '%c%s\n' % (l, base85.b85encode(line, True))
1324 return '%c%s\n' % (l, base85.b85encode(line, True))
1368
1325
1369 def chunk(text, csize=52):
1326 def chunk(text, csize=52):
1370 l = len(text)
1327 l = len(text)
1371 i = 0
1328 i = 0
1372 while i < l:
1329 while i < l:
1373 yield text[i:i + csize]
1330 yield text[i:i + csize]
1374 i += csize
1331 i += csize
1375
1332
1376 tohash = gitindex(to)
1333 tohash = gitindex(to)
1377 tnhash = gitindex(tn)
1334 tnhash = gitindex(tn)
1378 if tohash == tnhash:
1335 if tohash == tnhash:
1379 return ""
1336 return ""
1380
1337
1381 # TODO: deltas
1338 # TODO: deltas
1382 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1339 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1383 (tohash, tnhash, len(tn))]
1340 (tohash, tnhash, len(tn))]
1384 for l in chunk(zlib.compress(tn)):
1341 for l in chunk(zlib.compress(tn)):
1385 ret.append(fmtline(l))
1342 ret.append(fmtline(l))
1386 ret.append('\n')
1343 ret.append('\n')
1387 return ''.join(ret)
1344 return ''.join(ret)
1388
1345
1389 class GitDiffRequired(Exception):
1346 class GitDiffRequired(Exception):
1390 pass
1347 pass
1391
1348
1392 def diffopts(ui, opts=None, untrusted=False):
1349 def diffopts(ui, opts=None, untrusted=False):
1393 def get(key, name=None, getter=ui.configbool):
1350 def get(key, name=None, getter=ui.configbool):
1394 return ((opts and opts.get(key)) or
1351 return ((opts and opts.get(key)) or
1395 getter('diff', name or key, None, untrusted=untrusted))
1352 getter('diff', name or key, None, untrusted=untrusted))
1396 return mdiff.diffopts(
1353 return mdiff.diffopts(
1397 text=opts and opts.get('text'),
1354 text=opts and opts.get('text'),
1398 git=get('git'),
1355 git=get('git'),
1399 nodates=get('nodates'),
1356 nodates=get('nodates'),
1400 showfunc=get('show_function', 'showfunc'),
1357 showfunc=get('show_function', 'showfunc'),
1401 ignorews=get('ignore_all_space', 'ignorews'),
1358 ignorews=get('ignore_all_space', 'ignorews'),
1402 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1359 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1403 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1360 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1404 context=get('unified', getter=ui.config))
1361 context=get('unified', getter=ui.config))
1405
1362
1406 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1363 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1407 losedatafn=None, prefix=''):
1364 losedatafn=None, prefix=''):
1408 '''yields diff of changes to files between two nodes, or node and
1365 '''yields diff of changes to files between two nodes, or node and
1409 working directory.
1366 working directory.
1410
1367
1411 if node1 is None, use first dirstate parent instead.
1368 if node1 is None, use first dirstate parent instead.
1412 if node2 is None, compare node1 with working directory.
1369 if node2 is None, compare node1 with working directory.
1413
1370
1414 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1371 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1415 every time some change cannot be represented with the current
1372 every time some change cannot be represented with the current
1416 patch format. Return False to upgrade to git patch format, True to
1373 patch format. Return False to upgrade to git patch format, True to
1417 accept the loss or raise an exception to abort the diff. It is
1374 accept the loss or raise an exception to abort the diff. It is
1418 called with the name of current file being diffed as 'fn'. If set
1375 called with the name of current file being diffed as 'fn'. If set
1419 to None, patches will always be upgraded to git format when
1376 to None, patches will always be upgraded to git format when
1420 necessary.
1377 necessary.
1421
1378
1422 prefix is a filename prefix that is prepended to all filenames on
1379 prefix is a filename prefix that is prepended to all filenames on
1423 display (used for subrepos).
1380 display (used for subrepos).
1424 '''
1381 '''
1425
1382
1426 if opts is None:
1383 if opts is None:
1427 opts = mdiff.defaultopts
1384 opts = mdiff.defaultopts
1428
1385
1429 if not node1 and not node2:
1386 if not node1 and not node2:
1430 node1 = repo.dirstate.parents()[0]
1387 node1 = repo.dirstate.parents()[0]
1431
1388
1432 def lrugetfilectx():
1389 def lrugetfilectx():
1433 cache = {}
1390 cache = {}
1434 order = []
1391 order = []
1435 def getfilectx(f, ctx):
1392 def getfilectx(f, ctx):
1436 fctx = ctx.filectx(f, filelog=cache.get(f))
1393 fctx = ctx.filectx(f, filelog=cache.get(f))
1437 if f not in cache:
1394 if f not in cache:
1438 if len(cache) > 20:
1395 if len(cache) > 20:
1439 del cache[order.pop(0)]
1396 del cache[order.pop(0)]
1440 cache[f] = fctx.filelog()
1397 cache[f] = fctx.filelog()
1441 else:
1398 else:
1442 order.remove(f)
1399 order.remove(f)
1443 order.append(f)
1400 order.append(f)
1444 return fctx
1401 return fctx
1445 return getfilectx
1402 return getfilectx
1446 getfilectx = lrugetfilectx()
1403 getfilectx = lrugetfilectx()
1447
1404
1448 ctx1 = repo[node1]
1405 ctx1 = repo[node1]
1449 ctx2 = repo[node2]
1406 ctx2 = repo[node2]
1450
1407
1451 if not changes:
1408 if not changes:
1452 changes = repo.status(ctx1, ctx2, match=match)
1409 changes = repo.status(ctx1, ctx2, match=match)
1453 modified, added, removed = changes[:3]
1410 modified, added, removed = changes[:3]
1454
1411
1455 if not modified and not added and not removed:
1412 if not modified and not added and not removed:
1456 return []
1413 return []
1457
1414
1458 revs = None
1415 revs = None
1459 if not repo.ui.quiet:
1416 if not repo.ui.quiet:
1460 hexfunc = repo.ui.debugflag and hex or short
1417 hexfunc = repo.ui.debugflag and hex or short
1461 revs = [hexfunc(node) for node in [node1, node2] if node]
1418 revs = [hexfunc(node) for node in [node1, node2] if node]
1462
1419
1463 copy = {}
1420 copy = {}
1464 if opts.git or opts.upgrade:
1421 if opts.git or opts.upgrade:
1465 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1422 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1466
1423
1467 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1424 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1468 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1425 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1469 if opts.upgrade and not opts.git:
1426 if opts.upgrade and not opts.git:
1470 try:
1427 try:
1471 def losedata(fn):
1428 def losedata(fn):
1472 if not losedatafn or not losedatafn(fn=fn):
1429 if not losedatafn or not losedatafn(fn=fn):
1473 raise GitDiffRequired()
1430 raise GitDiffRequired()
1474 # Buffer the whole output until we are sure it can be generated
1431 # Buffer the whole output until we are sure it can be generated
1475 return list(difffn(opts.copy(git=False), losedata))
1432 return list(difffn(opts.copy(git=False), losedata))
1476 except GitDiffRequired:
1433 except GitDiffRequired:
1477 return difffn(opts.copy(git=True), None)
1434 return difffn(opts.copy(git=True), None)
1478 else:
1435 else:
1479 return difffn(opts, None)
1436 return difffn(opts, None)
1480
1437
1481 def difflabel(func, *args, **kw):
1438 def difflabel(func, *args, **kw):
1482 '''yields 2-tuples of (output, label) based on the output of func()'''
1439 '''yields 2-tuples of (output, label) based on the output of func()'''
1483 prefixes = [('diff', 'diff.diffline'),
1440 prefixes = [('diff', 'diff.diffline'),
1484 ('copy', 'diff.extended'),
1441 ('copy', 'diff.extended'),
1485 ('rename', 'diff.extended'),
1442 ('rename', 'diff.extended'),
1486 ('old', 'diff.extended'),
1443 ('old', 'diff.extended'),
1487 ('new', 'diff.extended'),
1444 ('new', 'diff.extended'),
1488 ('deleted', 'diff.extended'),
1445 ('deleted', 'diff.extended'),
1489 ('---', 'diff.file_a'),
1446 ('---', 'diff.file_a'),
1490 ('+++', 'diff.file_b'),
1447 ('+++', 'diff.file_b'),
1491 ('@@', 'diff.hunk'),
1448 ('@@', 'diff.hunk'),
1492 ('-', 'diff.deleted'),
1449 ('-', 'diff.deleted'),
1493 ('+', 'diff.inserted')]
1450 ('+', 'diff.inserted')]
1494
1451
1495 for chunk in func(*args, **kw):
1452 for chunk in func(*args, **kw):
1496 lines = chunk.split('\n')
1453 lines = chunk.split('\n')
1497 for i, line in enumerate(lines):
1454 for i, line in enumerate(lines):
1498 if i != 0:
1455 if i != 0:
1499 yield ('\n', '')
1456 yield ('\n', '')
1500 stripline = line
1457 stripline = line
1501 if line and line[0] in '+-':
1458 if line and line[0] in '+-':
1502 # highlight trailing whitespace, but only in changed lines
1459 # highlight trailing whitespace, but only in changed lines
1503 stripline = line.rstrip()
1460 stripline = line.rstrip()
1504 for prefix, label in prefixes:
1461 for prefix, label in prefixes:
1505 if stripline.startswith(prefix):
1462 if stripline.startswith(prefix):
1506 yield (stripline, label)
1463 yield (stripline, label)
1507 break
1464 break
1508 else:
1465 else:
1509 yield (line, '')
1466 yield (line, '')
1510 if line != stripline:
1467 if line != stripline:
1511 yield (line[len(stripline):], 'diff.trailingwhitespace')
1468 yield (line[len(stripline):], 'diff.trailingwhitespace')
1512
1469
1513 def diffui(*args, **kw):
1470 def diffui(*args, **kw):
1514 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1471 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1515 return difflabel(diff, *args, **kw)
1472 return difflabel(diff, *args, **kw)
1516
1473
1517
1474
1518 def _addmodehdr(header, omode, nmode):
1475 def _addmodehdr(header, omode, nmode):
1519 if omode != nmode:
1476 if omode != nmode:
1520 header.append('old mode %s\n' % omode)
1477 header.append('old mode %s\n' % omode)
1521 header.append('new mode %s\n' % nmode)
1478 header.append('new mode %s\n' % nmode)
1522
1479
1523 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1480 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1524 copy, getfilectx, opts, losedatafn, prefix):
1481 copy, getfilectx, opts, losedatafn, prefix):
1525
1482
1526 def join(f):
1483 def join(f):
1527 return os.path.join(prefix, f)
1484 return os.path.join(prefix, f)
1528
1485
1529 date1 = util.datestr(ctx1.date())
1486 date1 = util.datestr(ctx1.date())
1530 man1 = ctx1.manifest()
1487 man1 = ctx1.manifest()
1531
1488
1532 gone = set()
1489 gone = set()
1533 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1490 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1534
1491
1535 copyto = dict([(v, k) for k, v in copy.items()])
1492 copyto = dict([(v, k) for k, v in copy.items()])
1536
1493
1537 if opts.git:
1494 if opts.git:
1538 revs = None
1495 revs = None
1539
1496
1540 for f in sorted(modified + added + removed):
1497 for f in sorted(modified + added + removed):
1541 to = None
1498 to = None
1542 tn = None
1499 tn = None
1543 dodiff = True
1500 dodiff = True
1544 header = []
1501 header = []
1545 if f in man1:
1502 if f in man1:
1546 to = getfilectx(f, ctx1).data()
1503 to = getfilectx(f, ctx1).data()
1547 if f not in removed:
1504 if f not in removed:
1548 tn = getfilectx(f, ctx2).data()
1505 tn = getfilectx(f, ctx2).data()
1549 a, b = f, f
1506 a, b = f, f
1550 if opts.git or losedatafn:
1507 if opts.git or losedatafn:
1551 if f in added:
1508 if f in added:
1552 mode = gitmode[ctx2.flags(f)]
1509 mode = gitmode[ctx2.flags(f)]
1553 if f in copy or f in copyto:
1510 if f in copy or f in copyto:
1554 if opts.git:
1511 if opts.git:
1555 if f in copy:
1512 if f in copy:
1556 a = copy[f]
1513 a = copy[f]
1557 else:
1514 else:
1558 a = copyto[f]
1515 a = copyto[f]
1559 omode = gitmode[man1.flags(a)]
1516 omode = gitmode[man1.flags(a)]
1560 _addmodehdr(header, omode, mode)
1517 _addmodehdr(header, omode, mode)
1561 if a in removed and a not in gone:
1518 if a in removed and a not in gone:
1562 op = 'rename'
1519 op = 'rename'
1563 gone.add(a)
1520 gone.add(a)
1564 else:
1521 else:
1565 op = 'copy'
1522 op = 'copy'
1566 header.append('%s from %s\n' % (op, join(a)))
1523 header.append('%s from %s\n' % (op, join(a)))
1567 header.append('%s to %s\n' % (op, join(f)))
1524 header.append('%s to %s\n' % (op, join(f)))
1568 to = getfilectx(a, ctx1).data()
1525 to = getfilectx(a, ctx1).data()
1569 else:
1526 else:
1570 losedatafn(f)
1527 losedatafn(f)
1571 else:
1528 else:
1572 if opts.git:
1529 if opts.git:
1573 header.append('new file mode %s\n' % mode)
1530 header.append('new file mode %s\n' % mode)
1574 elif ctx2.flags(f):
1531 elif ctx2.flags(f):
1575 losedatafn(f)
1532 losedatafn(f)
1576 if util.binary(tn):
1533 if util.binary(tn):
1577 if opts.git:
1534 if opts.git:
1578 dodiff = 'binary'
1535 dodiff = 'binary'
1579 else:
1536 else:
1580 losedatafn(f)
1537 losedatafn(f)
1581 if not opts.git and not tn:
1538 if not opts.git and not tn:
1582 # regular diffs cannot represent new empty file
1539 # regular diffs cannot represent new empty file
1583 losedatafn(f)
1540 losedatafn(f)
1584 elif f in removed:
1541 elif f in removed:
1585 if opts.git:
1542 if opts.git:
1586 # have we already reported a copy above?
1543 # have we already reported a copy above?
1587 if ((f in copy and copy[f] in added
1544 if ((f in copy and copy[f] in added
1588 and copyto[copy[f]] == f) or
1545 and copyto[copy[f]] == f) or
1589 (f in copyto and copyto[f] in added
1546 (f in copyto and copyto[f] in added
1590 and copy[copyto[f]] == f)):
1547 and copy[copyto[f]] == f)):
1591 dodiff = False
1548 dodiff = False
1592 else:
1549 else:
1593 header.append('deleted file mode %s\n' %
1550 header.append('deleted file mode %s\n' %
1594 gitmode[man1.flags(f)])
1551 gitmode[man1.flags(f)])
1595 elif not to:
1552 elif not to:
1596 # regular diffs cannot represent empty file deletion
1553 # regular diffs cannot represent empty file deletion
1597 losedatafn(f)
1554 losedatafn(f)
1598 else:
1555 else:
1599 oflag = man1.flags(f)
1556 oflag = man1.flags(f)
1600 nflag = ctx2.flags(f)
1557 nflag = ctx2.flags(f)
1601 binary = util.binary(to) or util.binary(tn)
1558 binary = util.binary(to) or util.binary(tn)
1602 if opts.git:
1559 if opts.git:
1603 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1560 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1604 if binary:
1561 if binary:
1605 dodiff = 'binary'
1562 dodiff = 'binary'
1606 elif binary or nflag != oflag:
1563 elif binary or nflag != oflag:
1607 losedatafn(f)
1564 losedatafn(f)
1608 if opts.git:
1565 if opts.git:
1609 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1566 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1610
1567
1611 if dodiff:
1568 if dodiff:
1612 if dodiff == 'binary':
1569 if dodiff == 'binary':
1613 text = b85diff(to, tn)
1570 text = b85diff(to, tn)
1614 else:
1571 else:
1615 text = mdiff.unidiff(to, date1,
1572 text = mdiff.unidiff(to, date1,
1616 # ctx2 date may be dynamic
1573 # ctx2 date may be dynamic
1617 tn, util.datestr(ctx2.date()),
1574 tn, util.datestr(ctx2.date()),
1618 join(a), join(b), revs, opts=opts)
1575 join(a), join(b), revs, opts=opts)
1619 if header and (text or len(header) > 1):
1576 if header and (text or len(header) > 1):
1620 yield ''.join(header)
1577 yield ''.join(header)
1621 if text:
1578 if text:
1622 yield text
1579 yield text
1623
1580
1624 def diffstatdata(lines):
1581 def diffstatdata(lines):
1625 filename, adds, removes = None, 0, 0
1582 filename, adds, removes = None, 0, 0
1626 for line in lines:
1583 for line in lines:
1627 if line.startswith('diff'):
1584 if line.startswith('diff'):
1628 if filename:
1585 if filename:
1629 isbinary = adds == 0 and removes == 0
1586 isbinary = adds == 0 and removes == 0
1630 yield (filename, adds, removes, isbinary)
1587 yield (filename, adds, removes, isbinary)
1631 # set numbers to 0 anyway when starting new file
1588 # set numbers to 0 anyway when starting new file
1632 adds, removes = 0, 0
1589 adds, removes = 0, 0
1633 if line.startswith('diff --git'):
1590 if line.startswith('diff --git'):
1634 filename = gitre.search(line).group(1)
1591 filename = gitre.search(line).group(1)
1635 else:
1592 else:
1636 # format: "diff -r ... -r ... filename"
1593 # format: "diff -r ... -r ... filename"
1637 filename = line.split(None, 5)[-1]
1594 filename = line.split(None, 5)[-1]
1638 elif line.startswith('+') and not line.startswith('+++'):
1595 elif line.startswith('+') and not line.startswith('+++'):
1639 adds += 1
1596 adds += 1
1640 elif line.startswith('-') and not line.startswith('---'):
1597 elif line.startswith('-') and not line.startswith('---'):
1641 removes += 1
1598 removes += 1
1642 if filename:
1599 if filename:
1643 isbinary = adds == 0 and removes == 0
1600 isbinary = adds == 0 and removes == 0
1644 yield (filename, adds, removes, isbinary)
1601 yield (filename, adds, removes, isbinary)
1645
1602
1646 def diffstat(lines, width=80, git=False):
1603 def diffstat(lines, width=80, git=False):
1647 output = []
1604 output = []
1648 stats = list(diffstatdata(lines))
1605 stats = list(diffstatdata(lines))
1649
1606
1650 maxtotal, maxname = 0, 0
1607 maxtotal, maxname = 0, 0
1651 totaladds, totalremoves = 0, 0
1608 totaladds, totalremoves = 0, 0
1652 hasbinary = False
1609 hasbinary = False
1653
1610
1654 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1611 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1655 for filename, adds, removes, isbinary in stats]
1612 for filename, adds, removes, isbinary in stats]
1656
1613
1657 for filename, adds, removes, isbinary, namewidth in sized:
1614 for filename, adds, removes, isbinary, namewidth in sized:
1658 totaladds += adds
1615 totaladds += adds
1659 totalremoves += removes
1616 totalremoves += removes
1660 maxname = max(maxname, namewidth)
1617 maxname = max(maxname, namewidth)
1661 maxtotal = max(maxtotal, adds + removes)
1618 maxtotal = max(maxtotal, adds + removes)
1662 if isbinary:
1619 if isbinary:
1663 hasbinary = True
1620 hasbinary = True
1664
1621
1665 countwidth = len(str(maxtotal))
1622 countwidth = len(str(maxtotal))
1666 if hasbinary and countwidth < 3:
1623 if hasbinary and countwidth < 3:
1667 countwidth = 3
1624 countwidth = 3
1668 graphwidth = width - countwidth - maxname - 6
1625 graphwidth = width - countwidth - maxname - 6
1669 if graphwidth < 10:
1626 if graphwidth < 10:
1670 graphwidth = 10
1627 graphwidth = 10
1671
1628
1672 def scale(i):
1629 def scale(i):
1673 if maxtotal <= graphwidth:
1630 if maxtotal <= graphwidth:
1674 return i
1631 return i
1675 # If diffstat runs out of room it doesn't print anything,
1632 # If diffstat runs out of room it doesn't print anything,
1676 # which isn't very useful, so always print at least one + or -
1633 # which isn't very useful, so always print at least one + or -
1677 # if there were at least some changes.
1634 # if there were at least some changes.
1678 return max(i * graphwidth // maxtotal, int(bool(i)))
1635 return max(i * graphwidth // maxtotal, int(bool(i)))
1679
1636
1680 for filename, adds, removes, isbinary, namewidth in sized:
1637 for filename, adds, removes, isbinary, namewidth in sized:
1681 if git and isbinary:
1638 if git and isbinary:
1682 count = 'Bin'
1639 count = 'Bin'
1683 else:
1640 else:
1684 count = adds + removes
1641 count = adds + removes
1685 pluses = '+' * scale(adds)
1642 pluses = '+' * scale(adds)
1686 minuses = '-' * scale(removes)
1643 minuses = '-' * scale(removes)
1687 output.append(' %s%s | %*s %s%s\n' %
1644 output.append(' %s%s | %*s %s%s\n' %
1688 (filename, ' ' * (maxname - namewidth),
1645 (filename, ' ' * (maxname - namewidth),
1689 countwidth, count,
1646 countwidth, count,
1690 pluses, minuses))
1647 pluses, minuses))
1691
1648
1692 if stats:
1649 if stats:
1693 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1650 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1694 % (len(stats), totaladds, totalremoves))
1651 % (len(stats), totaladds, totalremoves))
1695
1652
1696 return ''.join(output)
1653 return ''.join(output)
1697
1654
1698 def diffstatui(*args, **kw):
1655 def diffstatui(*args, **kw):
1699 '''like diffstat(), but yields 2-tuples of (output, label) for
1656 '''like diffstat(), but yields 2-tuples of (output, label) for
1700 ui.write()
1657 ui.write()
1701 '''
1658 '''
1702
1659
1703 for line in diffstat(*args, **kw).splitlines():
1660 for line in diffstat(*args, **kw).splitlines():
1704 if line and line[-1] in '+-':
1661 if line and line[-1] in '+-':
1705 name, graph = line.rsplit(' ', 1)
1662 name, graph = line.rsplit(' ', 1)
1706 yield (name + ' ', '')
1663 yield (name + ' ', '')
1707 m = re.search(r'\++', graph)
1664 m = re.search(r'\++', graph)
1708 if m:
1665 if m:
1709 yield (m.group(0), 'diffstat.inserted')
1666 yield (m.group(0), 'diffstat.inserted')
1710 m = re.search(r'-+', graph)
1667 m = re.search(r'-+', graph)
1711 if m:
1668 if m:
1712 yield (m.group(0), 'diffstat.deleted')
1669 yield (m.group(0), 'diffstat.deleted')
1713 else:
1670 else:
1714 yield (line, '')
1671 yield (line, '')
1715 yield ('\n', '')
1672 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now