##// END OF EJS Templates
mq: promote qnew, demote qinit in short help
Matt Mackall -
r10890:30163375 default
parent child Browse files
Show More
@@ -1,2818 +1,2818 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
41
42 from mercurial.i18n import _
42 from mercurial.i18n import _
43 from mercurial.node import bin, hex, short, nullid, nullrev
43 from mercurial.node import bin, hex, short, nullid, nullrev
44 from mercurial.lock import release
44 from mercurial.lock import release
45 from mercurial import commands, cmdutil, hg, patch, util
45 from mercurial import commands, cmdutil, hg, patch, util
46 from mercurial import repair, extensions, url, error
46 from mercurial import repair, extensions, url, error
47 import os, sys, re, errno
47 import os, sys, re, errno
48
48
49 commands.norepo += " qclone"
49 commands.norepo += " qclone"
50
50
51 # Patch names looks like unix-file names.
51 # Patch names looks like unix-file names.
52 # They must be joinable with queue directory and result in the patch path.
52 # They must be joinable with queue directory and result in the patch path.
53 normname = util.normpath
53 normname = util.normpath
54
54
55 class statusentry(object):
55 class statusentry(object):
56 def __init__(self, node, name):
56 def __init__(self, node, name):
57 self.node, self.name = node, name
57 self.node, self.name = node, name
58
58
59 def __str__(self):
59 def __str__(self):
60 return hex(self.node) + ':' + self.name
60 return hex(self.node) + ':' + self.name
61
61
62 class patchheader(object):
62 class patchheader(object):
63 def __init__(self, pf, plainmode=False):
63 def __init__(self, pf, plainmode=False):
64 def eatdiff(lines):
64 def eatdiff(lines):
65 while lines:
65 while lines:
66 l = lines[-1]
66 l = lines[-1]
67 if (l.startswith("diff -") or
67 if (l.startswith("diff -") or
68 l.startswith("Index:") or
68 l.startswith("Index:") or
69 l.startswith("===========")):
69 l.startswith("===========")):
70 del lines[-1]
70 del lines[-1]
71 else:
71 else:
72 break
72 break
73 def eatempty(lines):
73 def eatempty(lines):
74 while lines:
74 while lines:
75 if not lines[-1].strip():
75 if not lines[-1].strip():
76 del lines[-1]
76 del lines[-1]
77 else:
77 else:
78 break
78 break
79
79
80 message = []
80 message = []
81 comments = []
81 comments = []
82 user = None
82 user = None
83 date = None
83 date = None
84 parent = None
84 parent = None
85 format = None
85 format = None
86 subject = None
86 subject = None
87 diffstart = 0
87 diffstart = 0
88
88
89 for line in file(pf):
89 for line in file(pf):
90 line = line.rstrip()
90 line = line.rstrip()
91 if (line.startswith('diff --git')
91 if (line.startswith('diff --git')
92 or (diffstart and line.startswith('+++ '))):
92 or (diffstart and line.startswith('+++ '))):
93 diffstart = 2
93 diffstart = 2
94 break
94 break
95 diffstart = 0 # reset
95 diffstart = 0 # reset
96 if line.startswith("--- "):
96 if line.startswith("--- "):
97 diffstart = 1
97 diffstart = 1
98 continue
98 continue
99 elif format == "hgpatch":
99 elif format == "hgpatch":
100 # parse values when importing the result of an hg export
100 # parse values when importing the result of an hg export
101 if line.startswith("# User "):
101 if line.startswith("# User "):
102 user = line[7:]
102 user = line[7:]
103 elif line.startswith("# Date "):
103 elif line.startswith("# Date "):
104 date = line[7:]
104 date = line[7:]
105 elif line.startswith("# Parent "):
105 elif line.startswith("# Parent "):
106 parent = line[9:]
106 parent = line[9:]
107 elif not line.startswith("# ") and line:
107 elif not line.startswith("# ") and line:
108 message.append(line)
108 message.append(line)
109 format = None
109 format = None
110 elif line == '# HG changeset patch':
110 elif line == '# HG changeset patch':
111 message = []
111 message = []
112 format = "hgpatch"
112 format = "hgpatch"
113 elif (format != "tagdone" and (line.startswith("Subject: ") or
113 elif (format != "tagdone" and (line.startswith("Subject: ") or
114 line.startswith("subject: "))):
114 line.startswith("subject: "))):
115 subject = line[9:]
115 subject = line[9:]
116 format = "tag"
116 format = "tag"
117 elif (format != "tagdone" and (line.startswith("From: ") or
117 elif (format != "tagdone" and (line.startswith("From: ") or
118 line.startswith("from: "))):
118 line.startswith("from: "))):
119 user = line[6:]
119 user = line[6:]
120 format = "tag"
120 format = "tag"
121 elif (format != "tagdone" and (line.startswith("Date: ") or
121 elif (format != "tagdone" and (line.startswith("Date: ") or
122 line.startswith("date: "))):
122 line.startswith("date: "))):
123 date = line[6:]
123 date = line[6:]
124 format = "tag"
124 format = "tag"
125 elif format == "tag" and line == "":
125 elif format == "tag" and line == "":
126 # when looking for tags (subject: from: etc) they
126 # when looking for tags (subject: from: etc) they
127 # end once you find a blank line in the source
127 # end once you find a blank line in the source
128 format = "tagdone"
128 format = "tagdone"
129 elif message or line:
129 elif message or line:
130 message.append(line)
130 message.append(line)
131 comments.append(line)
131 comments.append(line)
132
132
133 eatdiff(message)
133 eatdiff(message)
134 eatdiff(comments)
134 eatdiff(comments)
135 eatempty(message)
135 eatempty(message)
136 eatempty(comments)
136 eatempty(comments)
137
137
138 # make sure message isn't empty
138 # make sure message isn't empty
139 if format and format.startswith("tag") and subject:
139 if format and format.startswith("tag") and subject:
140 message.insert(0, "")
140 message.insert(0, "")
141 message.insert(0, subject)
141 message.insert(0, subject)
142
142
143 self.message = message
143 self.message = message
144 self.comments = comments
144 self.comments = comments
145 self.user = user
145 self.user = user
146 self.date = date
146 self.date = date
147 self.parent = parent
147 self.parent = parent
148 self.haspatch = diffstart > 1
148 self.haspatch = diffstart > 1
149 self.plainmode = plainmode
149 self.plainmode = plainmode
150
150
151 def setuser(self, user):
151 def setuser(self, user):
152 if not self.updateheader(['From: ', '# User '], user):
152 if not self.updateheader(['From: ', '# User '], user):
153 try:
153 try:
154 patchheaderat = self.comments.index('# HG changeset patch')
154 patchheaderat = self.comments.index('# HG changeset patch')
155 self.comments.insert(patchheaderat + 1, '# User ' + user)
155 self.comments.insert(patchheaderat + 1, '# User ' + user)
156 except ValueError:
156 except ValueError:
157 if self.plainmode or self._hasheader(['Date: ']):
157 if self.plainmode or self._hasheader(['Date: ']):
158 self.comments = ['From: ' + user] + self.comments
158 self.comments = ['From: ' + user] + self.comments
159 else:
159 else:
160 tmp = ['# HG changeset patch', '# User ' + user, '']
160 tmp = ['# HG changeset patch', '# User ' + user, '']
161 self.comments = tmp + self.comments
161 self.comments = tmp + self.comments
162 self.user = user
162 self.user = user
163
163
164 def setdate(self, date):
164 def setdate(self, date):
165 if not self.updateheader(['Date: ', '# Date '], date):
165 if not self.updateheader(['Date: ', '# Date '], date):
166 try:
166 try:
167 patchheaderat = self.comments.index('# HG changeset patch')
167 patchheaderat = self.comments.index('# HG changeset patch')
168 self.comments.insert(patchheaderat + 1, '# Date ' + date)
168 self.comments.insert(patchheaderat + 1, '# Date ' + date)
169 except ValueError:
169 except ValueError:
170 if self.plainmode or self._hasheader(['From: ']):
170 if self.plainmode or self._hasheader(['From: ']):
171 self.comments = ['Date: ' + date] + self.comments
171 self.comments = ['Date: ' + date] + self.comments
172 else:
172 else:
173 tmp = ['# HG changeset patch', '# Date ' + date, '']
173 tmp = ['# HG changeset patch', '# Date ' + date, '']
174 self.comments = tmp + self.comments
174 self.comments = tmp + self.comments
175 self.date = date
175 self.date = date
176
176
177 def setparent(self, parent):
177 def setparent(self, parent):
178 if not self.updateheader(['# Parent '], parent):
178 if not self.updateheader(['# Parent '], parent):
179 try:
179 try:
180 patchheaderat = self.comments.index('# HG changeset patch')
180 patchheaderat = self.comments.index('# HG changeset patch')
181 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
181 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
182 except ValueError:
182 except ValueError:
183 pass
183 pass
184 self.parent = parent
184 self.parent = parent
185
185
186 def setmessage(self, message):
186 def setmessage(self, message):
187 if self.comments:
187 if self.comments:
188 self._delmsg()
188 self._delmsg()
189 self.message = [message]
189 self.message = [message]
190 self.comments += self.message
190 self.comments += self.message
191
191
192 def updateheader(self, prefixes, new):
192 def updateheader(self, prefixes, new):
193 '''Update all references to a field in the patch header.
193 '''Update all references to a field in the patch header.
194 Return whether the field is present.'''
194 Return whether the field is present.'''
195 res = False
195 res = False
196 for prefix in prefixes:
196 for prefix in prefixes:
197 for i in xrange(len(self.comments)):
197 for i in xrange(len(self.comments)):
198 if self.comments[i].startswith(prefix):
198 if self.comments[i].startswith(prefix):
199 self.comments[i] = prefix + new
199 self.comments[i] = prefix + new
200 res = True
200 res = True
201 break
201 break
202 return res
202 return res
203
203
204 def _hasheader(self, prefixes):
204 def _hasheader(self, prefixes):
205 '''Check if a header starts with any of the given prefixes.'''
205 '''Check if a header starts with any of the given prefixes.'''
206 for prefix in prefixes:
206 for prefix in prefixes:
207 for comment in self.comments:
207 for comment in self.comments:
208 if comment.startswith(prefix):
208 if comment.startswith(prefix):
209 return True
209 return True
210 return False
210 return False
211
211
212 def __str__(self):
212 def __str__(self):
213 if not self.comments:
213 if not self.comments:
214 return ''
214 return ''
215 return '\n'.join(self.comments) + '\n\n'
215 return '\n'.join(self.comments) + '\n\n'
216
216
217 def _delmsg(self):
217 def _delmsg(self):
218 '''Remove existing message, keeping the rest of the comments fields.
218 '''Remove existing message, keeping the rest of the comments fields.
219 If comments contains 'subject: ', message will prepend
219 If comments contains 'subject: ', message will prepend
220 the field and a blank line.'''
220 the field and a blank line.'''
221 if self.message:
221 if self.message:
222 subj = 'subject: ' + self.message[0].lower()
222 subj = 'subject: ' + self.message[0].lower()
223 for i in xrange(len(self.comments)):
223 for i in xrange(len(self.comments)):
224 if subj == self.comments[i].lower():
224 if subj == self.comments[i].lower():
225 del self.comments[i]
225 del self.comments[i]
226 self.message = self.message[2:]
226 self.message = self.message[2:]
227 break
227 break
228 ci = 0
228 ci = 0
229 for mi in self.message:
229 for mi in self.message:
230 while mi != self.comments[ci]:
230 while mi != self.comments[ci]:
231 ci += 1
231 ci += 1
232 del self.comments[ci]
232 del self.comments[ci]
233
233
234 class queue(object):
234 class queue(object):
235 def __init__(self, ui, path, patchdir=None):
235 def __init__(self, ui, path, patchdir=None):
236 self.basepath = path
236 self.basepath = path
237 self.path = patchdir or os.path.join(path, "patches")
237 self.path = patchdir or os.path.join(path, "patches")
238 self.opener = util.opener(self.path)
238 self.opener = util.opener(self.path)
239 self.ui = ui
239 self.ui = ui
240 self.applied_dirty = 0
240 self.applied_dirty = 0
241 self.series_dirty = 0
241 self.series_dirty = 0
242 self.series_path = "series"
242 self.series_path = "series"
243 self.status_path = "status"
243 self.status_path = "status"
244 self.guards_path = "guards"
244 self.guards_path = "guards"
245 self.active_guards = None
245 self.active_guards = None
246 self.guards_dirty = False
246 self.guards_dirty = False
247 # Handle mq.git as a bool with extended values
247 # Handle mq.git as a bool with extended values
248 try:
248 try:
249 gitmode = ui.configbool('mq', 'git', None)
249 gitmode = ui.configbool('mq', 'git', None)
250 if gitmode is None:
250 if gitmode is None:
251 raise error.ConfigError()
251 raise error.ConfigError()
252 self.gitmode = gitmode and 'yes' or 'no'
252 self.gitmode = gitmode and 'yes' or 'no'
253 except error.ConfigError:
253 except error.ConfigError:
254 self.gitmode = ui.config('mq', 'git', 'auto').lower()
254 self.gitmode = ui.config('mq', 'git', 'auto').lower()
255 self.plainmode = ui.configbool('mq', 'plain', False)
255 self.plainmode = ui.configbool('mq', 'plain', False)
256
256
257 @util.propertycache
257 @util.propertycache
258 def applied(self):
258 def applied(self):
259 if os.path.exists(self.join(self.status_path)):
259 if os.path.exists(self.join(self.status_path)):
260 def parse(l):
260 def parse(l):
261 n, name = l.split(':', 1)
261 n, name = l.split(':', 1)
262 return statusentry(bin(n), name)
262 return statusentry(bin(n), name)
263 lines = self.opener(self.status_path).read().splitlines()
263 lines = self.opener(self.status_path).read().splitlines()
264 return [parse(l) for l in lines]
264 return [parse(l) for l in lines]
265 return []
265 return []
266
266
267 @util.propertycache
267 @util.propertycache
268 def full_series(self):
268 def full_series(self):
269 if os.path.exists(self.join(self.series_path)):
269 if os.path.exists(self.join(self.series_path)):
270 return self.opener(self.series_path).read().splitlines()
270 return self.opener(self.series_path).read().splitlines()
271 return []
271 return []
272
272
273 @util.propertycache
273 @util.propertycache
274 def series(self):
274 def series(self):
275 self.parse_series()
275 self.parse_series()
276 return self.series
276 return self.series
277
277
278 @util.propertycache
278 @util.propertycache
279 def series_guards(self):
279 def series_guards(self):
280 self.parse_series()
280 self.parse_series()
281 return self.series_guards
281 return self.series_guards
282
282
283 def invalidate(self):
283 def invalidate(self):
284 for a in 'applied full_series series series_guards'.split():
284 for a in 'applied full_series series series_guards'.split():
285 if a in self.__dict__:
285 if a in self.__dict__:
286 delattr(self, a)
286 delattr(self, a)
287 self.applied_dirty = 0
287 self.applied_dirty = 0
288 self.series_dirty = 0
288 self.series_dirty = 0
289 self.guards_dirty = False
289 self.guards_dirty = False
290 self.active_guards = None
290 self.active_guards = None
291
291
292 def diffopts(self, opts={}, patchfn=None):
292 def diffopts(self, opts={}, patchfn=None):
293 diffopts = patch.diffopts(self.ui, opts)
293 diffopts = patch.diffopts(self.ui, opts)
294 if self.gitmode == 'auto':
294 if self.gitmode == 'auto':
295 diffopts.upgrade = True
295 diffopts.upgrade = True
296 elif self.gitmode == 'keep':
296 elif self.gitmode == 'keep':
297 pass
297 pass
298 elif self.gitmode in ('yes', 'no'):
298 elif self.gitmode in ('yes', 'no'):
299 diffopts.git = self.gitmode == 'yes'
299 diffopts.git = self.gitmode == 'yes'
300 else:
300 else:
301 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
301 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
302 ' got %s') % self.gitmode)
302 ' got %s') % self.gitmode)
303 if patchfn:
303 if patchfn:
304 diffopts = self.patchopts(diffopts, patchfn)
304 diffopts = self.patchopts(diffopts, patchfn)
305 return diffopts
305 return diffopts
306
306
307 def patchopts(self, diffopts, *patches):
307 def patchopts(self, diffopts, *patches):
308 """Return a copy of input diff options with git set to true if
308 """Return a copy of input diff options with git set to true if
309 referenced patch is a git patch and should be preserved as such.
309 referenced patch is a git patch and should be preserved as such.
310 """
310 """
311 diffopts = diffopts.copy()
311 diffopts = diffopts.copy()
312 if not diffopts.git and self.gitmode == 'keep':
312 if not diffopts.git and self.gitmode == 'keep':
313 for patchfn in patches:
313 for patchfn in patches:
314 patchf = self.opener(patchfn, 'r')
314 patchf = self.opener(patchfn, 'r')
315 # if the patch was a git patch, refresh it as a git patch
315 # if the patch was a git patch, refresh it as a git patch
316 for line in patchf:
316 for line in patchf:
317 if line.startswith('diff --git'):
317 if line.startswith('diff --git'):
318 diffopts.git = True
318 diffopts.git = True
319 break
319 break
320 patchf.close()
320 patchf.close()
321 return diffopts
321 return diffopts
322
322
323 def join(self, *p):
323 def join(self, *p):
324 return os.path.join(self.path, *p)
324 return os.path.join(self.path, *p)
325
325
326 def find_series(self, patch):
326 def find_series(self, patch):
327 def matchpatch(l):
327 def matchpatch(l):
328 l = l.split('#', 1)[0]
328 l = l.split('#', 1)[0]
329 return l.strip() == patch
329 return l.strip() == patch
330 for index, l in enumerate(self.full_series):
330 for index, l in enumerate(self.full_series):
331 if matchpatch(l):
331 if matchpatch(l):
332 return index
332 return index
333 return None
333 return None
334
334
335 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
335 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
336
336
337 def parse_series(self):
337 def parse_series(self):
338 self.series = []
338 self.series = []
339 self.series_guards = []
339 self.series_guards = []
340 for l in self.full_series:
340 for l in self.full_series:
341 h = l.find('#')
341 h = l.find('#')
342 if h == -1:
342 if h == -1:
343 patch = l
343 patch = l
344 comment = ''
344 comment = ''
345 elif h == 0:
345 elif h == 0:
346 continue
346 continue
347 else:
347 else:
348 patch = l[:h]
348 patch = l[:h]
349 comment = l[h:]
349 comment = l[h:]
350 patch = patch.strip()
350 patch = patch.strip()
351 if patch:
351 if patch:
352 if patch in self.series:
352 if patch in self.series:
353 raise util.Abort(_('%s appears more than once in %s') %
353 raise util.Abort(_('%s appears more than once in %s') %
354 (patch, self.join(self.series_path)))
354 (patch, self.join(self.series_path)))
355 self.series.append(patch)
355 self.series.append(patch)
356 self.series_guards.append(self.guard_re.findall(comment))
356 self.series_guards.append(self.guard_re.findall(comment))
357
357
358 def check_guard(self, guard):
358 def check_guard(self, guard):
359 if not guard:
359 if not guard:
360 return _('guard cannot be an empty string')
360 return _('guard cannot be an empty string')
361 bad_chars = '# \t\r\n\f'
361 bad_chars = '# \t\r\n\f'
362 first = guard[0]
362 first = guard[0]
363 if first in '-+':
363 if first in '-+':
364 return (_('guard %r starts with invalid character: %r') %
364 return (_('guard %r starts with invalid character: %r') %
365 (guard, first))
365 (guard, first))
366 for c in bad_chars:
366 for c in bad_chars:
367 if c in guard:
367 if c in guard:
368 return _('invalid character in guard %r: %r') % (guard, c)
368 return _('invalid character in guard %r: %r') % (guard, c)
369
369
370 def set_active(self, guards):
370 def set_active(self, guards):
371 for guard in guards:
371 for guard in guards:
372 bad = self.check_guard(guard)
372 bad = self.check_guard(guard)
373 if bad:
373 if bad:
374 raise util.Abort(bad)
374 raise util.Abort(bad)
375 guards = sorted(set(guards))
375 guards = sorted(set(guards))
376 self.ui.debug('active guards: %s\n' % ' '.join(guards))
376 self.ui.debug('active guards: %s\n' % ' '.join(guards))
377 self.active_guards = guards
377 self.active_guards = guards
378 self.guards_dirty = True
378 self.guards_dirty = True
379
379
380 def active(self):
380 def active(self):
381 if self.active_guards is None:
381 if self.active_guards is None:
382 self.active_guards = []
382 self.active_guards = []
383 try:
383 try:
384 guards = self.opener(self.guards_path).read().split()
384 guards = self.opener(self.guards_path).read().split()
385 except IOError, err:
385 except IOError, err:
386 if err.errno != errno.ENOENT:
386 if err.errno != errno.ENOENT:
387 raise
387 raise
388 guards = []
388 guards = []
389 for i, guard in enumerate(guards):
389 for i, guard in enumerate(guards):
390 bad = self.check_guard(guard)
390 bad = self.check_guard(guard)
391 if bad:
391 if bad:
392 self.ui.warn('%s:%d: %s\n' %
392 self.ui.warn('%s:%d: %s\n' %
393 (self.join(self.guards_path), i + 1, bad))
393 (self.join(self.guards_path), i + 1, bad))
394 else:
394 else:
395 self.active_guards.append(guard)
395 self.active_guards.append(guard)
396 return self.active_guards
396 return self.active_guards
397
397
398 def set_guards(self, idx, guards):
398 def set_guards(self, idx, guards):
399 for g in guards:
399 for g in guards:
400 if len(g) < 2:
400 if len(g) < 2:
401 raise util.Abort(_('guard %r too short') % g)
401 raise util.Abort(_('guard %r too short') % g)
402 if g[0] not in '-+':
402 if g[0] not in '-+':
403 raise util.Abort(_('guard %r starts with invalid char') % g)
403 raise util.Abort(_('guard %r starts with invalid char') % g)
404 bad = self.check_guard(g[1:])
404 bad = self.check_guard(g[1:])
405 if bad:
405 if bad:
406 raise util.Abort(bad)
406 raise util.Abort(bad)
407 drop = self.guard_re.sub('', self.full_series[idx])
407 drop = self.guard_re.sub('', self.full_series[idx])
408 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
408 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
409 self.parse_series()
409 self.parse_series()
410 self.series_dirty = True
410 self.series_dirty = True
411
411
412 def pushable(self, idx):
412 def pushable(self, idx):
413 if isinstance(idx, str):
413 if isinstance(idx, str):
414 idx = self.series.index(idx)
414 idx = self.series.index(idx)
415 patchguards = self.series_guards[idx]
415 patchguards = self.series_guards[idx]
416 if not patchguards:
416 if not patchguards:
417 return True, None
417 return True, None
418 guards = self.active()
418 guards = self.active()
419 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
419 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
420 if exactneg:
420 if exactneg:
421 return False, exactneg[0]
421 return False, exactneg[0]
422 pos = [g for g in patchguards if g[0] == '+']
422 pos = [g for g in patchguards if g[0] == '+']
423 exactpos = [g for g in pos if g[1:] in guards]
423 exactpos = [g for g in pos if g[1:] in guards]
424 if pos:
424 if pos:
425 if exactpos:
425 if exactpos:
426 return True, exactpos[0]
426 return True, exactpos[0]
427 return False, pos
427 return False, pos
428 return True, ''
428 return True, ''
429
429
430 def explain_pushable(self, idx, all_patches=False):
430 def explain_pushable(self, idx, all_patches=False):
431 write = all_patches and self.ui.write or self.ui.warn
431 write = all_patches and self.ui.write or self.ui.warn
432 if all_patches or self.ui.verbose:
432 if all_patches or self.ui.verbose:
433 if isinstance(idx, str):
433 if isinstance(idx, str):
434 idx = self.series.index(idx)
434 idx = self.series.index(idx)
435 pushable, why = self.pushable(idx)
435 pushable, why = self.pushable(idx)
436 if all_patches and pushable:
436 if all_patches and pushable:
437 if why is None:
437 if why is None:
438 write(_('allowing %s - no guards in effect\n') %
438 write(_('allowing %s - no guards in effect\n') %
439 self.series[idx])
439 self.series[idx])
440 else:
440 else:
441 if not why:
441 if not why:
442 write(_('allowing %s - no matching negative guards\n') %
442 write(_('allowing %s - no matching negative guards\n') %
443 self.series[idx])
443 self.series[idx])
444 else:
444 else:
445 write(_('allowing %s - guarded by %r\n') %
445 write(_('allowing %s - guarded by %r\n') %
446 (self.series[idx], why))
446 (self.series[idx], why))
447 if not pushable:
447 if not pushable:
448 if why:
448 if why:
449 write(_('skipping %s - guarded by %r\n') %
449 write(_('skipping %s - guarded by %r\n') %
450 (self.series[idx], why))
450 (self.series[idx], why))
451 else:
451 else:
452 write(_('skipping %s - no matching guards\n') %
452 write(_('skipping %s - no matching guards\n') %
453 self.series[idx])
453 self.series[idx])
454
454
455 def save_dirty(self):
455 def save_dirty(self):
456 def write_list(items, path):
456 def write_list(items, path):
457 fp = self.opener(path, 'w')
457 fp = self.opener(path, 'w')
458 for i in items:
458 for i in items:
459 fp.write("%s\n" % i)
459 fp.write("%s\n" % i)
460 fp.close()
460 fp.close()
461 if self.applied_dirty:
461 if self.applied_dirty:
462 write_list(map(str, self.applied), self.status_path)
462 write_list(map(str, self.applied), self.status_path)
463 if self.series_dirty:
463 if self.series_dirty:
464 write_list(self.full_series, self.series_path)
464 write_list(self.full_series, self.series_path)
465 if self.guards_dirty:
465 if self.guards_dirty:
466 write_list(self.active_guards, self.guards_path)
466 write_list(self.active_guards, self.guards_path)
467
467
468 def removeundo(self, repo):
468 def removeundo(self, repo):
469 undo = repo.sjoin('undo')
469 undo = repo.sjoin('undo')
470 if not os.path.exists(undo):
470 if not os.path.exists(undo):
471 return
471 return
472 try:
472 try:
473 os.unlink(undo)
473 os.unlink(undo)
474 except OSError, inst:
474 except OSError, inst:
475 self.ui.warn(_('error removing undo: %s\n') % str(inst))
475 self.ui.warn(_('error removing undo: %s\n') % str(inst))
476
476
477 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
477 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
478 fp=None, changes=None, opts={}):
478 fp=None, changes=None, opts={}):
479 stat = opts.get('stat')
479 stat = opts.get('stat')
480 if stat:
480 if stat:
481 opts['unified'] = '0'
481 opts['unified'] = '0'
482
482
483 m = cmdutil.match(repo, files, opts)
483 m = cmdutil.match(repo, files, opts)
484 if fp is None:
484 if fp is None:
485 write = repo.ui.write
485 write = repo.ui.write
486 else:
486 else:
487 def write(s, **kw):
487 def write(s, **kw):
488 fp.write(s)
488 fp.write(s)
489 if stat:
489 if stat:
490 width = self.ui.interactive() and util.termwidth() or 80
490 width = self.ui.interactive() and util.termwidth() or 80
491 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
491 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
492 for chunk, label in patch.diffstatui(util.iterlines(chunks),
492 for chunk, label in patch.diffstatui(util.iterlines(chunks),
493 width=width,
493 width=width,
494 git=diffopts.git):
494 git=diffopts.git):
495 write(chunk, label=label)
495 write(chunk, label=label)
496 else:
496 else:
497 for chunk, label in patch.diffui(repo, node1, node2, m, changes,
497 for chunk, label in patch.diffui(repo, node1, node2, m, changes,
498 diffopts):
498 diffopts):
499 write(chunk, label=label)
499 write(chunk, label=label)
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 del tr
628 del tr
629 release(lock, wlock)
629 release(lock, wlock)
630 self.removeundo(repo)
630 self.removeundo(repo)
631
631
632 def _apply(self, repo, series, list=False, update_status=True,
632 def _apply(self, repo, series, list=False, update_status=True,
633 strict=False, patchdir=None, merge=None, all_files=None):
633 strict=False, patchdir=None, merge=None, all_files=None):
634 '''returns (error, hash)
634 '''returns (error, hash)
635 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
635 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
636 # TODO unify with commands.py
636 # TODO unify with commands.py
637 if not patchdir:
637 if not patchdir:
638 patchdir = self.path
638 patchdir = self.path
639 err = 0
639 err = 0
640 n = None
640 n = None
641 for patchname in series:
641 for patchname in series:
642 pushable, reason = self.pushable(patchname)
642 pushable, reason = self.pushable(patchname)
643 if not pushable:
643 if not pushable:
644 self.explain_pushable(patchname, all_patches=True)
644 self.explain_pushable(patchname, all_patches=True)
645 continue
645 continue
646 self.ui.status(_("applying %s\n") % patchname)
646 self.ui.status(_("applying %s\n") % patchname)
647 pf = os.path.join(patchdir, patchname)
647 pf = os.path.join(patchdir, patchname)
648
648
649 try:
649 try:
650 ph = patchheader(self.join(patchname), self.plainmode)
650 ph = patchheader(self.join(patchname), self.plainmode)
651 except:
651 except:
652 self.ui.warn(_("unable to read %s\n") % patchname)
652 self.ui.warn(_("unable to read %s\n") % patchname)
653 err = 1
653 err = 1
654 break
654 break
655
655
656 message = ph.message
656 message = ph.message
657 if not message:
657 if not message:
658 message = "imported patch %s\n" % patchname
658 message = "imported patch %s\n" % patchname
659 else:
659 else:
660 if list:
660 if list:
661 message.append("\nimported patch %s" % patchname)
661 message.append("\nimported patch %s" % patchname)
662 message = '\n'.join(message)
662 message = '\n'.join(message)
663
663
664 if ph.haspatch:
664 if ph.haspatch:
665 (patcherr, files, fuzz) = self.patch(repo, pf)
665 (patcherr, files, fuzz) = self.patch(repo, pf)
666 if all_files is not None:
666 if all_files is not None:
667 all_files.update(files)
667 all_files.update(files)
668 patcherr = not patcherr
668 patcherr = not patcherr
669 else:
669 else:
670 self.ui.warn(_("patch %s is empty\n") % patchname)
670 self.ui.warn(_("patch %s is empty\n") % patchname)
671 patcherr, files, fuzz = 0, [], 0
671 patcherr, files, fuzz = 0, [], 0
672
672
673 if merge and files:
673 if merge and files:
674 # Mark as removed/merged and update dirstate parent info
674 # Mark as removed/merged and update dirstate parent info
675 removed = []
675 removed = []
676 merged = []
676 merged = []
677 for f in files:
677 for f in files:
678 if os.path.exists(repo.wjoin(f)):
678 if os.path.exists(repo.wjoin(f)):
679 merged.append(f)
679 merged.append(f)
680 else:
680 else:
681 removed.append(f)
681 removed.append(f)
682 for f in removed:
682 for f in removed:
683 repo.dirstate.remove(f)
683 repo.dirstate.remove(f)
684 for f in merged:
684 for f in merged:
685 repo.dirstate.merge(f)
685 repo.dirstate.merge(f)
686 p1, p2 = repo.dirstate.parents()
686 p1, p2 = repo.dirstate.parents()
687 repo.dirstate.setparents(p1, merge)
687 repo.dirstate.setparents(p1, merge)
688
688
689 files = patch.updatedir(self.ui, repo, files)
689 files = patch.updatedir(self.ui, repo, files)
690 match = cmdutil.matchfiles(repo, files or [])
690 match = cmdutil.matchfiles(repo, files or [])
691 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
691 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
692
692
693 if n is None:
693 if n is None:
694 raise util.Abort(_("repo commit failed"))
694 raise util.Abort(_("repo commit failed"))
695
695
696 if update_status:
696 if update_status:
697 self.applied.append(statusentry(n, patchname))
697 self.applied.append(statusentry(n, patchname))
698
698
699 if patcherr:
699 if patcherr:
700 self.ui.warn(_("patch failed, rejects left in working dir\n"))
700 self.ui.warn(_("patch failed, rejects left in working dir\n"))
701 err = 2
701 err = 2
702 break
702 break
703
703
704 if fuzz and strict:
704 if fuzz and strict:
705 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
705 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
706 err = 3
706 err = 3
707 break
707 break
708 return (err, n)
708 return (err, n)
709
709
710 def _cleanup(self, patches, numrevs, keep=False):
710 def _cleanup(self, patches, numrevs, keep=False):
711 if not keep:
711 if not keep:
712 r = self.qrepo()
712 r = self.qrepo()
713 if r:
713 if r:
714 r.remove(patches, True)
714 r.remove(patches, True)
715 else:
715 else:
716 for p in patches:
716 for p in patches:
717 os.unlink(self.join(p))
717 os.unlink(self.join(p))
718
718
719 if numrevs:
719 if numrevs:
720 del self.applied[:numrevs]
720 del self.applied[:numrevs]
721 self.applied_dirty = 1
721 self.applied_dirty = 1
722
722
723 for i in sorted([self.find_series(p) for p in patches], reverse=True):
723 for i in sorted([self.find_series(p) for p in patches], reverse=True):
724 del self.full_series[i]
724 del self.full_series[i]
725 self.parse_series()
725 self.parse_series()
726 self.series_dirty = 1
726 self.series_dirty = 1
727
727
728 def _revpatches(self, repo, revs):
728 def _revpatches(self, repo, revs):
729 firstrev = repo[self.applied[0].node].rev()
729 firstrev = repo[self.applied[0].node].rev()
730 patches = []
730 patches = []
731 for i, rev in enumerate(revs):
731 for i, rev in enumerate(revs):
732
732
733 if rev < firstrev:
733 if rev < firstrev:
734 raise util.Abort(_('revision %d is not managed') % rev)
734 raise util.Abort(_('revision %d is not managed') % rev)
735
735
736 ctx = repo[rev]
736 ctx = repo[rev]
737 base = self.applied[i].node
737 base = self.applied[i].node
738 if ctx.node() != base:
738 if ctx.node() != base:
739 msg = _('cannot delete revision %d above applied patches')
739 msg = _('cannot delete revision %d above applied patches')
740 raise util.Abort(msg % rev)
740 raise util.Abort(msg % rev)
741
741
742 patch = self.applied[i].name
742 patch = self.applied[i].name
743 for fmt in ('[mq]: %s', 'imported patch %s'):
743 for fmt in ('[mq]: %s', 'imported patch %s'):
744 if ctx.description() == fmt % patch:
744 if ctx.description() == fmt % patch:
745 msg = _('patch %s finalized without changeset message\n')
745 msg = _('patch %s finalized without changeset message\n')
746 repo.ui.status(msg % patch)
746 repo.ui.status(msg % patch)
747 break
747 break
748
748
749 patches.append(patch)
749 patches.append(patch)
750 return patches
750 return patches
751
751
752 def finish(self, repo, revs):
752 def finish(self, repo, revs):
753 patches = self._revpatches(repo, sorted(revs))
753 patches = self._revpatches(repo, sorted(revs))
754 self._cleanup(patches, len(patches))
754 self._cleanup(patches, len(patches))
755
755
756 def delete(self, repo, patches, opts):
756 def delete(self, repo, patches, opts):
757 if not patches and not opts.get('rev'):
757 if not patches and not opts.get('rev'):
758 raise util.Abort(_('qdelete requires at least one revision or '
758 raise util.Abort(_('qdelete requires at least one revision or '
759 'patch name'))
759 'patch name'))
760
760
761 realpatches = []
761 realpatches = []
762 for patch in patches:
762 for patch in patches:
763 patch = self.lookup(patch, strict=True)
763 patch = self.lookup(patch, strict=True)
764 info = self.isapplied(patch)
764 info = self.isapplied(patch)
765 if info:
765 if info:
766 raise util.Abort(_("cannot delete applied patch %s") % patch)
766 raise util.Abort(_("cannot delete applied patch %s") % patch)
767 if patch not in self.series:
767 if patch not in self.series:
768 raise util.Abort(_("patch %s not in series file") % patch)
768 raise util.Abort(_("patch %s not in series file") % patch)
769 realpatches.append(patch)
769 realpatches.append(patch)
770
770
771 numrevs = 0
771 numrevs = 0
772 if opts.get('rev'):
772 if opts.get('rev'):
773 if not self.applied:
773 if not self.applied:
774 raise util.Abort(_('no patches applied'))
774 raise util.Abort(_('no patches applied'))
775 revs = cmdutil.revrange(repo, opts['rev'])
775 revs = cmdutil.revrange(repo, opts['rev'])
776 if len(revs) > 1 and revs[0] > revs[1]:
776 if len(revs) > 1 and revs[0] > revs[1]:
777 revs.reverse()
777 revs.reverse()
778 revpatches = self._revpatches(repo, revs)
778 revpatches = self._revpatches(repo, revs)
779 realpatches += revpatches
779 realpatches += revpatches
780 numrevs = len(revpatches)
780 numrevs = len(revpatches)
781
781
782 self._cleanup(realpatches, numrevs, opts.get('keep'))
782 self._cleanup(realpatches, numrevs, opts.get('keep'))
783
783
784 def check_toppatch(self, repo):
784 def check_toppatch(self, repo):
785 if self.applied:
785 if self.applied:
786 top = self.applied[-1].node
786 top = self.applied[-1].node
787 patch = self.applied[-1].name
787 patch = self.applied[-1].name
788 pp = repo.dirstate.parents()
788 pp = repo.dirstate.parents()
789 if top not in pp:
789 if top not in pp:
790 raise util.Abort(_("working directory revision is not qtip"))
790 raise util.Abort(_("working directory revision is not qtip"))
791 return top, patch
791 return top, patch
792 return None, None
792 return None, None
793
793
794 def check_localchanges(self, repo, force=False, refresh=True):
794 def check_localchanges(self, repo, force=False, refresh=True):
795 m, a, r, d = repo.status()[:4]
795 m, a, r, d = repo.status()[:4]
796 if (m or a or r or d) and not force:
796 if (m or a or r or d) and not force:
797 if refresh:
797 if refresh:
798 raise util.Abort(_("local changes found, refresh first"))
798 raise util.Abort(_("local changes found, refresh first"))
799 else:
799 else:
800 raise util.Abort(_("local changes found"))
800 raise util.Abort(_("local changes found"))
801 return m, a, r, d
801 return m, a, r, d
802
802
803 _reserved = ('series', 'status', 'guards')
803 _reserved = ('series', 'status', 'guards')
804 def check_reserved_name(self, name):
804 def check_reserved_name(self, name):
805 if (name in self._reserved or name.startswith('.hg')
805 if (name in self._reserved or name.startswith('.hg')
806 or name.startswith('.mq') or '#' in name or ':' in name):
806 or name.startswith('.mq') or '#' in name or ':' in name):
807 raise util.Abort(_('"%s" cannot be used as the name of a patch')
807 raise util.Abort(_('"%s" cannot be used as the name of a patch')
808 % name)
808 % name)
809
809
810 def new(self, repo, patchfn, *pats, **opts):
810 def new(self, repo, patchfn, *pats, **opts):
811 """options:
811 """options:
812 msg: a string or a no-argument function returning a string
812 msg: a string or a no-argument function returning a string
813 """
813 """
814 msg = opts.get('msg')
814 msg = opts.get('msg')
815 user = opts.get('user')
815 user = opts.get('user')
816 date = opts.get('date')
816 date = opts.get('date')
817 if date:
817 if date:
818 date = util.parsedate(date)
818 date = util.parsedate(date)
819 diffopts = self.diffopts({'git': opts.get('git')})
819 diffopts = self.diffopts({'git': opts.get('git')})
820 self.check_reserved_name(patchfn)
820 self.check_reserved_name(patchfn)
821 if os.path.exists(self.join(patchfn)):
821 if os.path.exists(self.join(patchfn)):
822 raise util.Abort(_('patch "%s" already exists') % patchfn)
822 raise util.Abort(_('patch "%s" already exists') % patchfn)
823 if opts.get('include') or opts.get('exclude') or pats:
823 if opts.get('include') or opts.get('exclude') or pats:
824 match = cmdutil.match(repo, pats, opts)
824 match = cmdutil.match(repo, pats, opts)
825 # detect missing files in pats
825 # detect missing files in pats
826 def badfn(f, msg):
826 def badfn(f, msg):
827 raise util.Abort('%s: %s' % (f, msg))
827 raise util.Abort('%s: %s' % (f, msg))
828 match.bad = badfn
828 match.bad = badfn
829 m, a, r, d = repo.status(match=match)[:4]
829 m, a, r, d = repo.status(match=match)[:4]
830 else:
830 else:
831 m, a, r, d = self.check_localchanges(repo, force=True)
831 m, a, r, d = self.check_localchanges(repo, force=True)
832 match = cmdutil.matchfiles(repo, m + a + r)
832 match = cmdutil.matchfiles(repo, m + a + r)
833 if len(repo[None].parents()) > 1:
833 if len(repo[None].parents()) > 1:
834 raise util.Abort(_('cannot manage merge changesets'))
834 raise util.Abort(_('cannot manage merge changesets'))
835 commitfiles = m + a + r
835 commitfiles = m + a + r
836 self.check_toppatch(repo)
836 self.check_toppatch(repo)
837 insert = self.full_series_end()
837 insert = self.full_series_end()
838 wlock = repo.wlock()
838 wlock = repo.wlock()
839 try:
839 try:
840 # if patch file write fails, abort early
840 # if patch file write fails, abort early
841 p = self.opener(patchfn, "w")
841 p = self.opener(patchfn, "w")
842 try:
842 try:
843 if self.plainmode:
843 if self.plainmode:
844 if user:
844 if user:
845 p.write("From: " + user + "\n")
845 p.write("From: " + user + "\n")
846 if not date:
846 if not date:
847 p.write("\n")
847 p.write("\n")
848 if date:
848 if date:
849 p.write("Date: %d %d\n\n" % date)
849 p.write("Date: %d %d\n\n" % date)
850 else:
850 else:
851 p.write("# HG changeset patch\n")
851 p.write("# HG changeset patch\n")
852 p.write("# Parent "
852 p.write("# Parent "
853 + hex(repo[None].parents()[0].node()) + "\n")
853 + hex(repo[None].parents()[0].node()) + "\n")
854 if user:
854 if user:
855 p.write("# User " + user + "\n")
855 p.write("# User " + user + "\n")
856 if date:
856 if date:
857 p.write("# Date %s %s\n\n" % date)
857 p.write("# Date %s %s\n\n" % date)
858 if hasattr(msg, '__call__'):
858 if hasattr(msg, '__call__'):
859 msg = msg()
859 msg = msg()
860 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
860 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
861 n = repo.commit(commitmsg, user, date, match=match, force=True)
861 n = repo.commit(commitmsg, user, date, match=match, force=True)
862 if n is None:
862 if n is None:
863 raise util.Abort(_("repo commit failed"))
863 raise util.Abort(_("repo commit failed"))
864 try:
864 try:
865 self.full_series[insert:insert] = [patchfn]
865 self.full_series[insert:insert] = [patchfn]
866 self.applied.append(statusentry(n, patchfn))
866 self.applied.append(statusentry(n, patchfn))
867 self.parse_series()
867 self.parse_series()
868 self.series_dirty = 1
868 self.series_dirty = 1
869 self.applied_dirty = 1
869 self.applied_dirty = 1
870 if msg:
870 if msg:
871 msg = msg + "\n\n"
871 msg = msg + "\n\n"
872 p.write(msg)
872 p.write(msg)
873 if commitfiles:
873 if commitfiles:
874 parent = self.qparents(repo, n)
874 parent = self.qparents(repo, n)
875 chunks = patch.diff(repo, node1=parent, node2=n,
875 chunks = patch.diff(repo, node1=parent, node2=n,
876 match=match, opts=diffopts)
876 match=match, opts=diffopts)
877 for chunk in chunks:
877 for chunk in chunks:
878 p.write(chunk)
878 p.write(chunk)
879 p.close()
879 p.close()
880 wlock.release()
880 wlock.release()
881 wlock = None
881 wlock = None
882 r = self.qrepo()
882 r = self.qrepo()
883 if r:
883 if r:
884 r.add([patchfn])
884 r.add([patchfn])
885 except:
885 except:
886 repo.rollback()
886 repo.rollback()
887 raise
887 raise
888 except Exception:
888 except Exception:
889 patchpath = self.join(patchfn)
889 patchpath = self.join(patchfn)
890 try:
890 try:
891 os.unlink(patchpath)
891 os.unlink(patchpath)
892 except:
892 except:
893 self.ui.warn(_('error unlinking %s\n') % patchpath)
893 self.ui.warn(_('error unlinking %s\n') % patchpath)
894 raise
894 raise
895 self.removeundo(repo)
895 self.removeundo(repo)
896 finally:
896 finally:
897 release(wlock)
897 release(wlock)
898
898
899 def strip(self, repo, rev, update=True, backup="all", force=None):
899 def strip(self, repo, rev, update=True, backup="all", force=None):
900 wlock = lock = None
900 wlock = lock = None
901 try:
901 try:
902 wlock = repo.wlock()
902 wlock = repo.wlock()
903 lock = repo.lock()
903 lock = repo.lock()
904
904
905 if update:
905 if update:
906 self.check_localchanges(repo, force=force, refresh=False)
906 self.check_localchanges(repo, force=force, refresh=False)
907 urev = self.qparents(repo, rev)
907 urev = self.qparents(repo, rev)
908 hg.clean(repo, urev)
908 hg.clean(repo, urev)
909 repo.dirstate.write()
909 repo.dirstate.write()
910
910
911 self.removeundo(repo)
911 self.removeundo(repo)
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):
998 mergeq=None, all=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
1027 return
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 self.applied_dirty = 1
1054 self.applied_dirty = 1
1055 if start > 0:
1055 if start > 0:
1056 self.check_toppatch(repo)
1056 self.check_toppatch(repo)
1057 if not patch:
1057 if not patch:
1058 patch = self.series[start]
1058 patch = self.series[start]
1059 end = start + 1
1059 end = start + 1
1060 else:
1060 else:
1061 end = self.series.index(patch, start) + 1
1061 end = self.series.index(patch, start) + 1
1062
1062
1063 s = self.series[start:end]
1063 s = self.series[start:end]
1064 all_files = set()
1064 all_files = set()
1065 try:
1065 try:
1066 if mergeq:
1066 if mergeq:
1067 ret = self.mergepatch(repo, mergeq, s, diffopts)
1067 ret = self.mergepatch(repo, mergeq, s, diffopts)
1068 else:
1068 else:
1069 ret = self.apply(repo, s, list, all_files=all_files)
1069 ret = self.apply(repo, s, list, all_files=all_files)
1070 except:
1070 except:
1071 self.ui.warn(_('cleaning up working directory...'))
1071 self.ui.warn(_('cleaning up working directory...'))
1072 node = repo.dirstate.parents()[0]
1072 node = repo.dirstate.parents()[0]
1073 hg.revert(repo, node, None)
1073 hg.revert(repo, node, None)
1074 # only remove unknown files that we know we touched or
1074 # only remove unknown files that we know we touched or
1075 # created while patching
1075 # created while patching
1076 for f in all_files:
1076 for f in all_files:
1077 if f not in repo.dirstate:
1077 if f not in repo.dirstate:
1078 try:
1078 try:
1079 util.unlink(repo.wjoin(f))
1079 util.unlink(repo.wjoin(f))
1080 except OSError, inst:
1080 except OSError, inst:
1081 if inst.errno != errno.ENOENT:
1081 if inst.errno != errno.ENOENT:
1082 raise
1082 raise
1083 self.ui.warn(_('done\n'))
1083 self.ui.warn(_('done\n'))
1084 raise
1084 raise
1085
1085
1086 if not self.applied:
1086 if not self.applied:
1087 return ret[0]
1087 return ret[0]
1088 top = self.applied[-1].name
1088 top = self.applied[-1].name
1089 if ret[0] and ret[0] > 1:
1089 if ret[0] and ret[0] > 1:
1090 msg = _("errors during apply, please fix and refresh %s\n")
1090 msg = _("errors during apply, please fix and refresh %s\n")
1091 self.ui.write(msg % top)
1091 self.ui.write(msg % top)
1092 else:
1092 else:
1093 self.ui.write(_("now at: %s\n") % top)
1093 self.ui.write(_("now at: %s\n") % top)
1094 return ret[0]
1094 return ret[0]
1095
1095
1096 finally:
1096 finally:
1097 wlock.release()
1097 wlock.release()
1098
1098
1099 def pop(self, repo, patch=None, force=False, update=True, all=False):
1099 def pop(self, repo, patch=None, force=False, update=True, all=False):
1100 wlock = repo.wlock()
1100 wlock = repo.wlock()
1101 try:
1101 try:
1102 if patch:
1102 if patch:
1103 # index, rev, patch
1103 # index, rev, patch
1104 info = self.isapplied(patch)
1104 info = self.isapplied(patch)
1105 if not info:
1105 if not info:
1106 patch = self.lookup(patch)
1106 patch = self.lookup(patch)
1107 info = self.isapplied(patch)
1107 info = self.isapplied(patch)
1108 if not info:
1108 if not info:
1109 raise util.Abort(_("patch %s is not applied") % patch)
1109 raise util.Abort(_("patch %s is not applied") % patch)
1110
1110
1111 if not self.applied:
1111 if not self.applied:
1112 # Allow qpop -a to work repeatedly,
1112 # Allow qpop -a to work repeatedly,
1113 # but not qpop without an argument
1113 # but not qpop without an argument
1114 self.ui.warn(_("no patches applied\n"))
1114 self.ui.warn(_("no patches applied\n"))
1115 return not all
1115 return not all
1116
1116
1117 if all:
1117 if all:
1118 start = 0
1118 start = 0
1119 elif patch:
1119 elif patch:
1120 start = info[0] + 1
1120 start = info[0] + 1
1121 else:
1121 else:
1122 start = len(self.applied) - 1
1122 start = len(self.applied) - 1
1123
1123
1124 if start >= len(self.applied):
1124 if start >= len(self.applied):
1125 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1125 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1126 return
1126 return
1127
1127
1128 if not update:
1128 if not update:
1129 parents = repo.dirstate.parents()
1129 parents = repo.dirstate.parents()
1130 rr = [x.node for x in self.applied]
1130 rr = [x.node for x in self.applied]
1131 for p in parents:
1131 for p in parents:
1132 if p in rr:
1132 if p in rr:
1133 self.ui.warn(_("qpop: forcing dirstate update\n"))
1133 self.ui.warn(_("qpop: forcing dirstate update\n"))
1134 update = True
1134 update = True
1135 else:
1135 else:
1136 parents = [p.node() for p in repo[None].parents()]
1136 parents = [p.node() for p in repo[None].parents()]
1137 needupdate = False
1137 needupdate = False
1138 for entry in self.applied[start:]:
1138 for entry in self.applied[start:]:
1139 if entry.node in parents:
1139 if entry.node in parents:
1140 needupdate = True
1140 needupdate = True
1141 break
1141 break
1142 update = needupdate
1142 update = needupdate
1143
1143
1144 if not force and update:
1144 if not force and update:
1145 self.check_localchanges(repo)
1145 self.check_localchanges(repo)
1146
1146
1147 self.applied_dirty = 1
1147 self.applied_dirty = 1
1148 end = len(self.applied)
1148 end = len(self.applied)
1149 rev = self.applied[start].node
1149 rev = self.applied[start].node
1150 if update:
1150 if update:
1151 top = self.check_toppatch(repo)[0]
1151 top = self.check_toppatch(repo)[0]
1152
1152
1153 try:
1153 try:
1154 heads = repo.changelog.heads(rev)
1154 heads = repo.changelog.heads(rev)
1155 except error.LookupError:
1155 except error.LookupError:
1156 node = short(rev)
1156 node = short(rev)
1157 raise util.Abort(_('trying to pop unknown node %s') % node)
1157 raise util.Abort(_('trying to pop unknown node %s') % node)
1158
1158
1159 if heads != [self.applied[-1].node]:
1159 if heads != [self.applied[-1].node]:
1160 raise util.Abort(_("popping would remove a revision not "
1160 raise util.Abort(_("popping would remove a revision not "
1161 "managed by this patch queue"))
1161 "managed by this patch queue"))
1162
1162
1163 # we know there are no local changes, so we can make a simplified
1163 # we know there are no local changes, so we can make a simplified
1164 # form of hg.update.
1164 # form of hg.update.
1165 if update:
1165 if update:
1166 qp = self.qparents(repo, rev)
1166 qp = self.qparents(repo, rev)
1167 ctx = repo[qp]
1167 ctx = repo[qp]
1168 m, a, r, d = repo.status(qp, top)[:4]
1168 m, a, r, d = repo.status(qp, top)[:4]
1169 if d:
1169 if d:
1170 raise util.Abort(_("deletions found between repo revs"))
1170 raise util.Abort(_("deletions found between repo revs"))
1171 for f in a:
1171 for f in a:
1172 try:
1172 try:
1173 util.unlink(repo.wjoin(f))
1173 util.unlink(repo.wjoin(f))
1174 except OSError, e:
1174 except OSError, e:
1175 if e.errno != errno.ENOENT:
1175 if e.errno != errno.ENOENT:
1176 raise
1176 raise
1177 repo.dirstate.forget(f)
1177 repo.dirstate.forget(f)
1178 for f in m + r:
1178 for f in m + r:
1179 fctx = ctx[f]
1179 fctx = ctx[f]
1180 repo.wwrite(f, fctx.data(), fctx.flags())
1180 repo.wwrite(f, fctx.data(), fctx.flags())
1181 repo.dirstate.normal(f)
1181 repo.dirstate.normal(f)
1182 repo.dirstate.setparents(qp, nullid)
1182 repo.dirstate.setparents(qp, nullid)
1183 for patch in reversed(self.applied[start:end]):
1183 for patch in reversed(self.applied[start:end]):
1184 self.ui.status(_("popping %s\n") % patch.name)
1184 self.ui.status(_("popping %s\n") % patch.name)
1185 del self.applied[start:end]
1185 del self.applied[start:end]
1186 self.strip(repo, rev, update=False, backup='strip')
1186 self.strip(repo, rev, update=False, backup='strip')
1187 if self.applied:
1187 if self.applied:
1188 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1188 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1189 else:
1189 else:
1190 self.ui.write(_("patch queue now empty\n"))
1190 self.ui.write(_("patch queue now empty\n"))
1191 finally:
1191 finally:
1192 wlock.release()
1192 wlock.release()
1193
1193
1194 def diff(self, repo, pats, opts):
1194 def diff(self, repo, pats, opts):
1195 top, patch = self.check_toppatch(repo)
1195 top, patch = self.check_toppatch(repo)
1196 if not top:
1196 if not top:
1197 self.ui.write(_("no patches applied\n"))
1197 self.ui.write(_("no patches applied\n"))
1198 return
1198 return
1199 qp = self.qparents(repo, top)
1199 qp = self.qparents(repo, top)
1200 if opts.get('reverse'):
1200 if opts.get('reverse'):
1201 node1, node2 = None, qp
1201 node1, node2 = None, qp
1202 else:
1202 else:
1203 node1, node2 = qp, None
1203 node1, node2 = qp, None
1204 diffopts = self.diffopts(opts, patch)
1204 diffopts = self.diffopts(opts, patch)
1205 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1205 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1206
1206
1207 def refresh(self, repo, pats=None, **opts):
1207 def refresh(self, repo, pats=None, **opts):
1208 if not self.applied:
1208 if not self.applied:
1209 self.ui.write(_("no patches applied\n"))
1209 self.ui.write(_("no patches applied\n"))
1210 return 1
1210 return 1
1211 msg = opts.get('msg', '').rstrip()
1211 msg = opts.get('msg', '').rstrip()
1212 newuser = opts.get('user')
1212 newuser = opts.get('user')
1213 newdate = opts.get('date')
1213 newdate = opts.get('date')
1214 if newdate:
1214 if newdate:
1215 newdate = '%d %d' % util.parsedate(newdate)
1215 newdate = '%d %d' % util.parsedate(newdate)
1216 wlock = repo.wlock()
1216 wlock = repo.wlock()
1217
1217
1218 try:
1218 try:
1219 self.check_toppatch(repo)
1219 self.check_toppatch(repo)
1220 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1220 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1221 if repo.changelog.heads(top) != [top]:
1221 if repo.changelog.heads(top) != [top]:
1222 raise util.Abort(_("cannot refresh a revision with children"))
1222 raise util.Abort(_("cannot refresh a revision with children"))
1223
1223
1224 cparents = repo.changelog.parents(top)
1224 cparents = repo.changelog.parents(top)
1225 patchparent = self.qparents(repo, top)
1225 patchparent = self.qparents(repo, top)
1226 ph = patchheader(self.join(patchfn), self.plainmode)
1226 ph = patchheader(self.join(patchfn), self.plainmode)
1227 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1227 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1228 if msg:
1228 if msg:
1229 ph.setmessage(msg)
1229 ph.setmessage(msg)
1230 if newuser:
1230 if newuser:
1231 ph.setuser(newuser)
1231 ph.setuser(newuser)
1232 if newdate:
1232 if newdate:
1233 ph.setdate(newdate)
1233 ph.setdate(newdate)
1234 ph.setparent(hex(patchparent))
1234 ph.setparent(hex(patchparent))
1235
1235
1236 # only commit new patch when write is complete
1236 # only commit new patch when write is complete
1237 patchf = self.opener(patchfn, 'w', atomictemp=True)
1237 patchf = self.opener(patchfn, 'w', atomictemp=True)
1238
1238
1239 comments = str(ph)
1239 comments = str(ph)
1240 if comments:
1240 if comments:
1241 patchf.write(comments)
1241 patchf.write(comments)
1242
1242
1243 # update the dirstate in place, strip off the qtip commit
1243 # update the dirstate in place, strip off the qtip commit
1244 # and then commit.
1244 # and then commit.
1245 #
1245 #
1246 # this should really read:
1246 # this should really read:
1247 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1247 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1248 # but we do it backwards to take advantage of manifest/chlog
1248 # but we do it backwards to take advantage of manifest/chlog
1249 # caching against the next repo.status call
1249 # caching against the next repo.status call
1250 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1250 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1251 changes = repo.changelog.read(top)
1251 changes = repo.changelog.read(top)
1252 man = repo.manifest.read(changes[0])
1252 man = repo.manifest.read(changes[0])
1253 aaa = aa[:]
1253 aaa = aa[:]
1254 matchfn = cmdutil.match(repo, pats, opts)
1254 matchfn = cmdutil.match(repo, pats, opts)
1255 # in short mode, we only diff the files included in the
1255 # in short mode, we only diff the files included in the
1256 # patch already plus specified files
1256 # patch already plus specified files
1257 if opts.get('short'):
1257 if opts.get('short'):
1258 # if amending a patch, we start with existing
1258 # if amending a patch, we start with existing
1259 # files plus specified files - unfiltered
1259 # files plus specified files - unfiltered
1260 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1260 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1261 # filter with inc/exl options
1261 # filter with inc/exl options
1262 matchfn = cmdutil.match(repo, opts=opts)
1262 matchfn = cmdutil.match(repo, opts=opts)
1263 else:
1263 else:
1264 match = cmdutil.matchall(repo)
1264 match = cmdutil.matchall(repo)
1265 m, a, r, d = repo.status(match=match)[:4]
1265 m, a, r, d = repo.status(match=match)[:4]
1266
1266
1267 # we might end up with files that were added between
1267 # we might end up with files that were added between
1268 # qtip and the dirstate parent, but then changed in the
1268 # qtip and the dirstate parent, but then changed in the
1269 # local dirstate. in this case, we want them to only
1269 # local dirstate. in this case, we want them to only
1270 # show up in the added section
1270 # show up in the added section
1271 for x in m:
1271 for x in m:
1272 if x not in aa:
1272 if x not in aa:
1273 mm.append(x)
1273 mm.append(x)
1274 # we might end up with files added by the local dirstate that
1274 # we might end up with files added by the local dirstate that
1275 # were deleted by the patch. In this case, they should only
1275 # were deleted by the patch. In this case, they should only
1276 # show up in the changed section.
1276 # show up in the changed section.
1277 for x in a:
1277 for x in a:
1278 if x in dd:
1278 if x in dd:
1279 del dd[dd.index(x)]
1279 del dd[dd.index(x)]
1280 mm.append(x)
1280 mm.append(x)
1281 else:
1281 else:
1282 aa.append(x)
1282 aa.append(x)
1283 # make sure any files deleted in the local dirstate
1283 # make sure any files deleted in the local dirstate
1284 # are not in the add or change column of the patch
1284 # are not in the add or change column of the patch
1285 forget = []
1285 forget = []
1286 for x in d + r:
1286 for x in d + r:
1287 if x in aa:
1287 if x in aa:
1288 del aa[aa.index(x)]
1288 del aa[aa.index(x)]
1289 forget.append(x)
1289 forget.append(x)
1290 continue
1290 continue
1291 elif x in mm:
1291 elif x in mm:
1292 del mm[mm.index(x)]
1292 del mm[mm.index(x)]
1293 dd.append(x)
1293 dd.append(x)
1294
1294
1295 m = list(set(mm))
1295 m = list(set(mm))
1296 r = list(set(dd))
1296 r = list(set(dd))
1297 a = list(set(aa))
1297 a = list(set(aa))
1298 c = [filter(matchfn, l) for l in (m, a, r)]
1298 c = [filter(matchfn, l) for l in (m, a, r)]
1299 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1299 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1300 chunks = patch.diff(repo, patchparent, match=match,
1300 chunks = patch.diff(repo, patchparent, match=match,
1301 changes=c, opts=diffopts)
1301 changes=c, opts=diffopts)
1302 for chunk in chunks:
1302 for chunk in chunks:
1303 patchf.write(chunk)
1303 patchf.write(chunk)
1304
1304
1305 try:
1305 try:
1306 if diffopts.git or diffopts.upgrade:
1306 if diffopts.git or diffopts.upgrade:
1307 copies = {}
1307 copies = {}
1308 for dst in a:
1308 for dst in a:
1309 src = repo.dirstate.copied(dst)
1309 src = repo.dirstate.copied(dst)
1310 # during qfold, the source file for copies may
1310 # during qfold, the source file for copies may
1311 # be removed. Treat this as a simple add.
1311 # be removed. Treat this as a simple add.
1312 if src is not None and src in repo.dirstate:
1312 if src is not None and src in repo.dirstate:
1313 copies.setdefault(src, []).append(dst)
1313 copies.setdefault(src, []).append(dst)
1314 repo.dirstate.add(dst)
1314 repo.dirstate.add(dst)
1315 # remember the copies between patchparent and qtip
1315 # remember the copies between patchparent and qtip
1316 for dst in aaa:
1316 for dst in aaa:
1317 f = repo.file(dst)
1317 f = repo.file(dst)
1318 src = f.renamed(man[dst])
1318 src = f.renamed(man[dst])
1319 if src:
1319 if src:
1320 copies.setdefault(src[0], []).extend(
1320 copies.setdefault(src[0], []).extend(
1321 copies.get(dst, []))
1321 copies.get(dst, []))
1322 if dst in a:
1322 if dst in a:
1323 copies[src[0]].append(dst)
1323 copies[src[0]].append(dst)
1324 # we can't copy a file created by the patch itself
1324 # we can't copy a file created by the patch itself
1325 if dst in copies:
1325 if dst in copies:
1326 del copies[dst]
1326 del copies[dst]
1327 for src, dsts in copies.iteritems():
1327 for src, dsts in copies.iteritems():
1328 for dst in dsts:
1328 for dst in dsts:
1329 repo.dirstate.copy(src, dst)
1329 repo.dirstate.copy(src, dst)
1330 else:
1330 else:
1331 for dst in a:
1331 for dst in a:
1332 repo.dirstate.add(dst)
1332 repo.dirstate.add(dst)
1333 # Drop useless copy information
1333 # Drop useless copy information
1334 for f in list(repo.dirstate.copies()):
1334 for f in list(repo.dirstate.copies()):
1335 repo.dirstate.copy(None, f)
1335 repo.dirstate.copy(None, f)
1336 for f in r:
1336 for f in r:
1337 repo.dirstate.remove(f)
1337 repo.dirstate.remove(f)
1338 # if the patch excludes a modified file, mark that
1338 # if the patch excludes a modified file, mark that
1339 # file with mtime=0 so status can see it.
1339 # file with mtime=0 so status can see it.
1340 mm = []
1340 mm = []
1341 for i in xrange(len(m)-1, -1, -1):
1341 for i in xrange(len(m)-1, -1, -1):
1342 if not matchfn(m[i]):
1342 if not matchfn(m[i]):
1343 mm.append(m[i])
1343 mm.append(m[i])
1344 del m[i]
1344 del m[i]
1345 for f in m:
1345 for f in m:
1346 repo.dirstate.normal(f)
1346 repo.dirstate.normal(f)
1347 for f in mm:
1347 for f in mm:
1348 repo.dirstate.normallookup(f)
1348 repo.dirstate.normallookup(f)
1349 for f in forget:
1349 for f in forget:
1350 repo.dirstate.forget(f)
1350 repo.dirstate.forget(f)
1351
1351
1352 if not msg:
1352 if not msg:
1353 if not ph.message:
1353 if not ph.message:
1354 message = "[mq]: %s\n" % patchfn
1354 message = "[mq]: %s\n" % patchfn
1355 else:
1355 else:
1356 message = "\n".join(ph.message)
1356 message = "\n".join(ph.message)
1357 else:
1357 else:
1358 message = msg
1358 message = msg
1359
1359
1360 user = ph.user or changes[1]
1360 user = ph.user or changes[1]
1361
1361
1362 # assumes strip can roll itself back if interrupted
1362 # assumes strip can roll itself back if interrupted
1363 repo.dirstate.setparents(*cparents)
1363 repo.dirstate.setparents(*cparents)
1364 self.applied.pop()
1364 self.applied.pop()
1365 self.applied_dirty = 1
1365 self.applied_dirty = 1
1366 self.strip(repo, top, update=False,
1366 self.strip(repo, top, update=False,
1367 backup='strip')
1367 backup='strip')
1368 except:
1368 except:
1369 repo.dirstate.invalidate()
1369 repo.dirstate.invalidate()
1370 raise
1370 raise
1371
1371
1372 try:
1372 try:
1373 # might be nice to attempt to roll back strip after this
1373 # might be nice to attempt to roll back strip after this
1374 patchf.rename()
1374 patchf.rename()
1375 n = repo.commit(message, user, ph.date, match=match,
1375 n = repo.commit(message, user, ph.date, match=match,
1376 force=True)
1376 force=True)
1377 self.applied.append(statusentry(n, patchfn))
1377 self.applied.append(statusentry(n, patchfn))
1378 except:
1378 except:
1379 ctx = repo[cparents[0]]
1379 ctx = repo[cparents[0]]
1380 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1380 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1381 self.save_dirty()
1381 self.save_dirty()
1382 self.ui.warn(_('refresh interrupted while patch was popped! '
1382 self.ui.warn(_('refresh interrupted while patch was popped! '
1383 '(revert --all, qpush to recover)\n'))
1383 '(revert --all, qpush to recover)\n'))
1384 raise
1384 raise
1385 finally:
1385 finally:
1386 wlock.release()
1386 wlock.release()
1387 self.removeundo(repo)
1387 self.removeundo(repo)
1388
1388
1389 def init(self, repo, create=False):
1389 def init(self, repo, create=False):
1390 if not create and os.path.isdir(self.path):
1390 if not create and os.path.isdir(self.path):
1391 raise util.Abort(_("patch queue directory already exists"))
1391 raise util.Abort(_("patch queue directory already exists"))
1392 try:
1392 try:
1393 os.mkdir(self.path)
1393 os.mkdir(self.path)
1394 except OSError, inst:
1394 except OSError, inst:
1395 if inst.errno != errno.EEXIST or not create:
1395 if inst.errno != errno.EEXIST or not create:
1396 raise
1396 raise
1397 if create:
1397 if create:
1398 return self.qrepo(create=True)
1398 return self.qrepo(create=True)
1399
1399
1400 def unapplied(self, repo, patch=None):
1400 def unapplied(self, repo, patch=None):
1401 if patch and patch not in self.series:
1401 if patch and patch not in self.series:
1402 raise util.Abort(_("patch %s is not in series file") % patch)
1402 raise util.Abort(_("patch %s is not in series file") % patch)
1403 if not patch:
1403 if not patch:
1404 start = self.series_end()
1404 start = self.series_end()
1405 else:
1405 else:
1406 start = self.series.index(patch) + 1
1406 start = self.series.index(patch) + 1
1407 unapplied = []
1407 unapplied = []
1408 for i in xrange(start, len(self.series)):
1408 for i in xrange(start, len(self.series)):
1409 pushable, reason = self.pushable(i)
1409 pushable, reason = self.pushable(i)
1410 if pushable:
1410 if pushable:
1411 unapplied.append((i, self.series[i]))
1411 unapplied.append((i, self.series[i]))
1412 self.explain_pushable(i)
1412 self.explain_pushable(i)
1413 return unapplied
1413 return unapplied
1414
1414
1415 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1415 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1416 summary=False):
1416 summary=False):
1417 def displayname(pfx, patchname, state):
1417 def displayname(pfx, patchname, state):
1418 if summary:
1418 if summary:
1419 ph = patchheader(self.join(patchname), self.plainmode)
1419 ph = patchheader(self.join(patchname), self.plainmode)
1420 msg = ph.message and ph.message[0] or ''
1420 msg = ph.message and ph.message[0] or ''
1421 if self.ui.interactive():
1421 if self.ui.interactive():
1422 width = util.termwidth() - len(pfx) - len(patchname) - 2
1422 width = util.termwidth() - len(pfx) - len(patchname) - 2
1423 if width > 0:
1423 if width > 0:
1424 msg = util.ellipsis(msg, width)
1424 msg = util.ellipsis(msg, width)
1425 else:
1425 else:
1426 msg = ''
1426 msg = ''
1427 msg = "%s%s: %s" % (pfx, patchname, msg)
1427 msg = "%s%s: %s" % (pfx, patchname, msg)
1428 else:
1428 else:
1429 msg = pfx + patchname
1429 msg = pfx + patchname
1430 self.ui.write(msg + '\n', label='qseries.' + state)
1430 self.ui.write(msg + '\n', label='qseries.' + state)
1431
1431
1432 applied = set([p.name for p in self.applied])
1432 applied = set([p.name for p in self.applied])
1433 if length is None:
1433 if length is None:
1434 length = len(self.series) - start
1434 length = len(self.series) - start
1435 if not missing:
1435 if not missing:
1436 if self.ui.verbose:
1436 if self.ui.verbose:
1437 idxwidth = len(str(start + length - 1))
1437 idxwidth = len(str(start + length - 1))
1438 for i in xrange(start, start + length):
1438 for i in xrange(start, start + length):
1439 patch = self.series[i]
1439 patch = self.series[i]
1440 if patch in applied:
1440 if patch in applied:
1441 char, state = 'A', 'applied'
1441 char, state = 'A', 'applied'
1442 elif self.pushable(i)[0]:
1442 elif self.pushable(i)[0]:
1443 char, state = 'U', 'unapplied'
1443 char, state = 'U', 'unapplied'
1444 else:
1444 else:
1445 char, state = 'G', 'guarded'
1445 char, state = 'G', 'guarded'
1446 pfx = ''
1446 pfx = ''
1447 if self.ui.verbose:
1447 if self.ui.verbose:
1448 pfx = '%*d %s ' % (idxwidth, i, char)
1448 pfx = '%*d %s ' % (idxwidth, i, char)
1449 elif status and status != char:
1449 elif status and status != char:
1450 continue
1450 continue
1451 displayname(pfx, patch, state)
1451 displayname(pfx, patch, state)
1452 else:
1452 else:
1453 msng_list = []
1453 msng_list = []
1454 for root, dirs, files in os.walk(self.path):
1454 for root, dirs, files in os.walk(self.path):
1455 d = root[len(self.path) + 1:]
1455 d = root[len(self.path) + 1:]
1456 for f in files:
1456 for f in files:
1457 fl = os.path.join(d, f)
1457 fl = os.path.join(d, f)
1458 if (fl not in self.series and
1458 if (fl not in self.series and
1459 fl not in (self.status_path, self.series_path,
1459 fl not in (self.status_path, self.series_path,
1460 self.guards_path)
1460 self.guards_path)
1461 and not fl.startswith('.')):
1461 and not fl.startswith('.')):
1462 msng_list.append(fl)
1462 msng_list.append(fl)
1463 for x in sorted(msng_list):
1463 for x in sorted(msng_list):
1464 pfx = self.ui.verbose and ('D ') or ''
1464 pfx = self.ui.verbose and ('D ') or ''
1465 displayname(pfx, x, 'missing')
1465 displayname(pfx, x, 'missing')
1466
1466
1467 def issaveline(self, l):
1467 def issaveline(self, l):
1468 if l.name == '.hg.patches.save.line':
1468 if l.name == '.hg.patches.save.line':
1469 return True
1469 return True
1470
1470
1471 def qrepo(self, create=False):
1471 def qrepo(self, create=False):
1472 if create or os.path.isdir(self.join(".hg")):
1472 if create or os.path.isdir(self.join(".hg")):
1473 return hg.repository(self.ui, path=self.path, create=create)
1473 return hg.repository(self.ui, path=self.path, create=create)
1474
1474
1475 def restore(self, repo, rev, delete=None, qupdate=None):
1475 def restore(self, repo, rev, delete=None, qupdate=None):
1476 desc = repo[rev].description().strip()
1476 desc = repo[rev].description().strip()
1477 lines = desc.splitlines()
1477 lines = desc.splitlines()
1478 i = 0
1478 i = 0
1479 datastart = None
1479 datastart = None
1480 series = []
1480 series = []
1481 applied = []
1481 applied = []
1482 qpp = None
1482 qpp = None
1483 for i, line in enumerate(lines):
1483 for i, line in enumerate(lines):
1484 if line == 'Patch Data:':
1484 if line == 'Patch Data:':
1485 datastart = i + 1
1485 datastart = i + 1
1486 elif line.startswith('Dirstate:'):
1486 elif line.startswith('Dirstate:'):
1487 l = line.rstrip()
1487 l = line.rstrip()
1488 l = l[10:].split(' ')
1488 l = l[10:].split(' ')
1489 qpp = [bin(x) for x in l]
1489 qpp = [bin(x) for x in l]
1490 elif datastart != None:
1490 elif datastart != None:
1491 l = line.rstrip()
1491 l = line.rstrip()
1492 n, name = l.split(':', 1)
1492 n, name = l.split(':', 1)
1493 if n:
1493 if n:
1494 applied.append(statusentry(bin(n), name))
1494 applied.append(statusentry(bin(n), name))
1495 else:
1495 else:
1496 series.append(l)
1496 series.append(l)
1497 if datastart is None:
1497 if datastart is None:
1498 self.ui.warn(_("No saved patch data found\n"))
1498 self.ui.warn(_("No saved patch data found\n"))
1499 return 1
1499 return 1
1500 self.ui.warn(_("restoring status: %s\n") % lines[0])
1500 self.ui.warn(_("restoring status: %s\n") % lines[0])
1501 self.full_series = series
1501 self.full_series = series
1502 self.applied = applied
1502 self.applied = applied
1503 self.parse_series()
1503 self.parse_series()
1504 self.series_dirty = 1
1504 self.series_dirty = 1
1505 self.applied_dirty = 1
1505 self.applied_dirty = 1
1506 heads = repo.changelog.heads()
1506 heads = repo.changelog.heads()
1507 if delete:
1507 if delete:
1508 if rev not in heads:
1508 if rev not in heads:
1509 self.ui.warn(_("save entry has children, leaving it alone\n"))
1509 self.ui.warn(_("save entry has children, leaving it alone\n"))
1510 else:
1510 else:
1511 self.ui.warn(_("removing save entry %s\n") % short(rev))
1511 self.ui.warn(_("removing save entry %s\n") % short(rev))
1512 pp = repo.dirstate.parents()
1512 pp = repo.dirstate.parents()
1513 if rev in pp:
1513 if rev in pp:
1514 update = True
1514 update = True
1515 else:
1515 else:
1516 update = False
1516 update = False
1517 self.strip(repo, rev, update=update, backup='strip')
1517 self.strip(repo, rev, update=update, backup='strip')
1518 if qpp:
1518 if qpp:
1519 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1519 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1520 (short(qpp[0]), short(qpp[1])))
1520 (short(qpp[0]), short(qpp[1])))
1521 if qupdate:
1521 if qupdate:
1522 self.ui.status(_("queue directory updating\n"))
1522 self.ui.status(_("queue directory updating\n"))
1523 r = self.qrepo()
1523 r = self.qrepo()
1524 if not r:
1524 if not r:
1525 self.ui.warn(_("Unable to load queue repository\n"))
1525 self.ui.warn(_("Unable to load queue repository\n"))
1526 return 1
1526 return 1
1527 hg.clean(r, qpp[0])
1527 hg.clean(r, qpp[0])
1528
1528
1529 def save(self, repo, msg=None):
1529 def save(self, repo, msg=None):
1530 if not self.applied:
1530 if not self.applied:
1531 self.ui.warn(_("save: no patches applied, exiting\n"))
1531 self.ui.warn(_("save: no patches applied, exiting\n"))
1532 return 1
1532 return 1
1533 if self.issaveline(self.applied[-1]):
1533 if self.issaveline(self.applied[-1]):
1534 self.ui.warn(_("status is already saved\n"))
1534 self.ui.warn(_("status is already saved\n"))
1535 return 1
1535 return 1
1536
1536
1537 if not msg:
1537 if not msg:
1538 msg = _("hg patches saved state")
1538 msg = _("hg patches saved state")
1539 else:
1539 else:
1540 msg = "hg patches: " + msg.rstrip('\r\n')
1540 msg = "hg patches: " + msg.rstrip('\r\n')
1541 r = self.qrepo()
1541 r = self.qrepo()
1542 if r:
1542 if r:
1543 pp = r.dirstate.parents()
1543 pp = r.dirstate.parents()
1544 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1544 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1545 msg += "\n\nPatch Data:\n"
1545 msg += "\n\nPatch Data:\n"
1546 msg += ''.join('%s\n' % x for x in self.applied)
1546 msg += ''.join('%s\n' % x for x in self.applied)
1547 msg += ''.join(':%s\n' % x for x in self.full_series)
1547 msg += ''.join(':%s\n' % x for x in self.full_series)
1548 n = repo.commit(msg, force=True)
1548 n = repo.commit(msg, force=True)
1549 if not n:
1549 if not n:
1550 self.ui.warn(_("repo commit failed\n"))
1550 self.ui.warn(_("repo commit failed\n"))
1551 return 1
1551 return 1
1552 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1552 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1553 self.applied_dirty = 1
1553 self.applied_dirty = 1
1554 self.removeundo(repo)
1554 self.removeundo(repo)
1555
1555
1556 def full_series_end(self):
1556 def full_series_end(self):
1557 if self.applied:
1557 if self.applied:
1558 p = self.applied[-1].name
1558 p = self.applied[-1].name
1559 end = self.find_series(p)
1559 end = self.find_series(p)
1560 if end is None:
1560 if end is None:
1561 return len(self.full_series)
1561 return len(self.full_series)
1562 return end + 1
1562 return end + 1
1563 return 0
1563 return 0
1564
1564
1565 def series_end(self, all_patches=False):
1565 def series_end(self, all_patches=False):
1566 """If all_patches is False, return the index of the next pushable patch
1566 """If all_patches is False, return the index of the next pushable patch
1567 in the series, or the series length. If all_patches is True, return the
1567 in the series, or the series length. If all_patches is True, return the
1568 index of the first patch past the last applied one.
1568 index of the first patch past the last applied one.
1569 """
1569 """
1570 end = 0
1570 end = 0
1571 def next(start):
1571 def next(start):
1572 if all_patches or start >= len(self.series):
1572 if all_patches or start >= len(self.series):
1573 return start
1573 return start
1574 for i in xrange(start, len(self.series)):
1574 for i in xrange(start, len(self.series)):
1575 p, reason = self.pushable(i)
1575 p, reason = self.pushable(i)
1576 if p:
1576 if p:
1577 break
1577 break
1578 self.explain_pushable(i)
1578 self.explain_pushable(i)
1579 return i
1579 return i
1580 if self.applied:
1580 if self.applied:
1581 p = self.applied[-1].name
1581 p = self.applied[-1].name
1582 try:
1582 try:
1583 end = self.series.index(p)
1583 end = self.series.index(p)
1584 except ValueError:
1584 except ValueError:
1585 return 0
1585 return 0
1586 return next(end + 1)
1586 return next(end + 1)
1587 return next(end)
1587 return next(end)
1588
1588
1589 def appliedname(self, index):
1589 def appliedname(self, index):
1590 pname = self.applied[index].name
1590 pname = self.applied[index].name
1591 if not self.ui.verbose:
1591 if not self.ui.verbose:
1592 p = pname
1592 p = pname
1593 else:
1593 else:
1594 p = str(self.series.index(pname)) + " " + pname
1594 p = str(self.series.index(pname)) + " " + pname
1595 return p
1595 return p
1596
1596
1597 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1597 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1598 force=None, git=False):
1598 force=None, git=False):
1599 def checkseries(patchname):
1599 def checkseries(patchname):
1600 if patchname in self.series:
1600 if patchname in self.series:
1601 raise util.Abort(_('patch %s is already in the series file')
1601 raise util.Abort(_('patch %s is already in the series file')
1602 % patchname)
1602 % patchname)
1603 def checkfile(patchname):
1603 def checkfile(patchname):
1604 if not force and os.path.exists(self.join(patchname)):
1604 if not force and os.path.exists(self.join(patchname)):
1605 raise util.Abort(_('patch "%s" already exists')
1605 raise util.Abort(_('patch "%s" already exists')
1606 % patchname)
1606 % patchname)
1607
1607
1608 if rev:
1608 if rev:
1609 if files:
1609 if files:
1610 raise util.Abort(_('option "-r" not valid when importing '
1610 raise util.Abort(_('option "-r" not valid when importing '
1611 'files'))
1611 'files'))
1612 rev = cmdutil.revrange(repo, rev)
1612 rev = cmdutil.revrange(repo, rev)
1613 rev.sort(reverse=True)
1613 rev.sort(reverse=True)
1614 if (len(files) > 1 or len(rev) > 1) and patchname:
1614 if (len(files) > 1 or len(rev) > 1) and patchname:
1615 raise util.Abort(_('option "-n" not valid when importing multiple '
1615 raise util.Abort(_('option "-n" not valid when importing multiple '
1616 'patches'))
1616 'patches'))
1617 added = []
1617 added = []
1618 if rev:
1618 if rev:
1619 # If mq patches are applied, we can only import revisions
1619 # If mq patches are applied, we can only import revisions
1620 # that form a linear path to qbase.
1620 # that form a linear path to qbase.
1621 # Otherwise, they should form a linear path to a head.
1621 # Otherwise, they should form a linear path to a head.
1622 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1622 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1623 if len(heads) > 1:
1623 if len(heads) > 1:
1624 raise util.Abort(_('revision %d is the root of more than one '
1624 raise util.Abort(_('revision %d is the root of more than one '
1625 'branch') % rev[-1])
1625 'branch') % rev[-1])
1626 if self.applied:
1626 if self.applied:
1627 base = repo.changelog.node(rev[0])
1627 base = repo.changelog.node(rev[0])
1628 if base in [n.node for n in self.applied]:
1628 if base in [n.node for n in self.applied]:
1629 raise util.Abort(_('revision %d is already managed')
1629 raise util.Abort(_('revision %d is already managed')
1630 % rev[0])
1630 % rev[0])
1631 if heads != [self.applied[-1].node]:
1631 if heads != [self.applied[-1].node]:
1632 raise util.Abort(_('revision %d is not the parent of '
1632 raise util.Abort(_('revision %d is not the parent of '
1633 'the queue') % rev[0])
1633 'the queue') % rev[0])
1634 base = repo.changelog.rev(self.applied[0].node)
1634 base = repo.changelog.rev(self.applied[0].node)
1635 lastparent = repo.changelog.parentrevs(base)[0]
1635 lastparent = repo.changelog.parentrevs(base)[0]
1636 else:
1636 else:
1637 if heads != [repo.changelog.node(rev[0])]:
1637 if heads != [repo.changelog.node(rev[0])]:
1638 raise util.Abort(_('revision %d has unmanaged children')
1638 raise util.Abort(_('revision %d has unmanaged children')
1639 % rev[0])
1639 % rev[0])
1640 lastparent = None
1640 lastparent = None
1641
1641
1642 diffopts = self.diffopts({'git': git})
1642 diffopts = self.diffopts({'git': git})
1643 for r in rev:
1643 for r in rev:
1644 p1, p2 = repo.changelog.parentrevs(r)
1644 p1, p2 = repo.changelog.parentrevs(r)
1645 n = repo.changelog.node(r)
1645 n = repo.changelog.node(r)
1646 if p2 != nullrev:
1646 if p2 != nullrev:
1647 raise util.Abort(_('cannot import merge revision %d') % r)
1647 raise util.Abort(_('cannot import merge revision %d') % r)
1648 if lastparent and lastparent != r:
1648 if lastparent and lastparent != r:
1649 raise util.Abort(_('revision %d is not the parent of %d')
1649 raise util.Abort(_('revision %d is not the parent of %d')
1650 % (r, lastparent))
1650 % (r, lastparent))
1651 lastparent = p1
1651 lastparent = p1
1652
1652
1653 if not patchname:
1653 if not patchname:
1654 patchname = normname('%d.diff' % r)
1654 patchname = normname('%d.diff' % r)
1655 self.check_reserved_name(patchname)
1655 self.check_reserved_name(patchname)
1656 checkseries(patchname)
1656 checkseries(patchname)
1657 checkfile(patchname)
1657 checkfile(patchname)
1658 self.full_series.insert(0, patchname)
1658 self.full_series.insert(0, patchname)
1659
1659
1660 patchf = self.opener(patchname, "w")
1660 patchf = self.opener(patchname, "w")
1661 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1661 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1662 patchf.close()
1662 patchf.close()
1663
1663
1664 se = statusentry(n, patchname)
1664 se = statusentry(n, patchname)
1665 self.applied.insert(0, se)
1665 self.applied.insert(0, se)
1666
1666
1667 added.append(patchname)
1667 added.append(patchname)
1668 patchname = None
1668 patchname = None
1669 self.parse_series()
1669 self.parse_series()
1670 self.applied_dirty = 1
1670 self.applied_dirty = 1
1671
1671
1672 for i, filename in enumerate(files):
1672 for i, filename in enumerate(files):
1673 if existing:
1673 if existing:
1674 if filename == '-':
1674 if filename == '-':
1675 raise util.Abort(_('-e is incompatible with import from -'))
1675 raise util.Abort(_('-e is incompatible with import from -'))
1676 if not patchname:
1676 if not patchname:
1677 patchname = normname(filename)
1677 patchname = normname(filename)
1678 self.check_reserved_name(patchname)
1678 self.check_reserved_name(patchname)
1679 if not os.path.isfile(self.join(patchname)):
1679 if not os.path.isfile(self.join(patchname)):
1680 raise util.Abort(_("patch %s does not exist") % patchname)
1680 raise util.Abort(_("patch %s does not exist") % patchname)
1681 else:
1681 else:
1682 try:
1682 try:
1683 if filename == '-':
1683 if filename == '-':
1684 if not patchname:
1684 if not patchname:
1685 raise util.Abort(
1685 raise util.Abort(
1686 _('need --name to import a patch from -'))
1686 _('need --name to import a patch from -'))
1687 text = sys.stdin.read()
1687 text = sys.stdin.read()
1688 else:
1688 else:
1689 text = url.open(self.ui, filename).read()
1689 text = url.open(self.ui, filename).read()
1690 except (OSError, IOError):
1690 except (OSError, IOError):
1691 raise util.Abort(_("unable to read %s") % filename)
1691 raise util.Abort(_("unable to read %s") % filename)
1692 if not patchname:
1692 if not patchname:
1693 patchname = normname(os.path.basename(filename))
1693 patchname = normname(os.path.basename(filename))
1694 self.check_reserved_name(patchname)
1694 self.check_reserved_name(patchname)
1695 checkfile(patchname)
1695 checkfile(patchname)
1696 patchf = self.opener(patchname, "w")
1696 patchf = self.opener(patchname, "w")
1697 patchf.write(text)
1697 patchf.write(text)
1698 if not force:
1698 if not force:
1699 checkseries(patchname)
1699 checkseries(patchname)
1700 if patchname not in self.series:
1700 if patchname not in self.series:
1701 index = self.full_series_end() + i
1701 index = self.full_series_end() + i
1702 self.full_series[index:index] = [patchname]
1702 self.full_series[index:index] = [patchname]
1703 self.parse_series()
1703 self.parse_series()
1704 self.ui.warn(_("adding %s to series file\n") % patchname)
1704 self.ui.warn(_("adding %s to series file\n") % patchname)
1705 added.append(patchname)
1705 added.append(patchname)
1706 patchname = None
1706 patchname = None
1707 self.series_dirty = 1
1707 self.series_dirty = 1
1708 qrepo = self.qrepo()
1708 qrepo = self.qrepo()
1709 if qrepo:
1709 if qrepo:
1710 qrepo.add(added)
1710 qrepo.add(added)
1711
1711
1712 def delete(ui, repo, *patches, **opts):
1712 def delete(ui, repo, *patches, **opts):
1713 """remove patches from queue
1713 """remove patches from queue
1714
1714
1715 The patches must not be applied, and at least one patch is required. With
1715 The patches must not be applied, and at least one patch is required. With
1716 -k/--keep, the patch files are preserved in the patch directory.
1716 -k/--keep, the patch files are preserved in the patch directory.
1717
1717
1718 To stop managing a patch and move it into permanent history,
1718 To stop managing a patch and move it into permanent history,
1719 use the qfinish command."""
1719 use the qfinish command."""
1720 q = repo.mq
1720 q = repo.mq
1721 q.delete(repo, patches, opts)
1721 q.delete(repo, patches, opts)
1722 q.save_dirty()
1722 q.save_dirty()
1723 return 0
1723 return 0
1724
1724
1725 def applied(ui, repo, patch=None, **opts):
1725 def applied(ui, repo, patch=None, **opts):
1726 """print the patches already applied"""
1726 """print the patches already applied"""
1727
1727
1728 q = repo.mq
1728 q = repo.mq
1729 l = len(q.applied)
1729 l = len(q.applied)
1730
1730
1731 if patch:
1731 if patch:
1732 if patch not in q.series:
1732 if patch not in q.series:
1733 raise util.Abort(_("patch %s is not in series file") % patch)
1733 raise util.Abort(_("patch %s is not in series file") % patch)
1734 end = q.series.index(patch) + 1
1734 end = q.series.index(patch) + 1
1735 else:
1735 else:
1736 end = q.series_end(True)
1736 end = q.series_end(True)
1737
1737
1738 if opts.get('last') and not end:
1738 if opts.get('last') and not end:
1739 ui.write(_("no patches applied\n"))
1739 ui.write(_("no patches applied\n"))
1740 return 1
1740 return 1
1741 elif opts.get('last') and end == 1:
1741 elif opts.get('last') and end == 1:
1742 ui.write(_("only one patch applied\n"))
1742 ui.write(_("only one patch applied\n"))
1743 return 1
1743 return 1
1744 elif opts.get('last'):
1744 elif opts.get('last'):
1745 start = end - 2
1745 start = end - 2
1746 end = 1
1746 end = 1
1747 else:
1747 else:
1748 start = 0
1748 start = 0
1749
1749
1750 return q.qseries(repo, length=end, start=start, status='A',
1750 return q.qseries(repo, length=end, start=start, status='A',
1751 summary=opts.get('summary'))
1751 summary=opts.get('summary'))
1752
1752
1753 def unapplied(ui, repo, patch=None, **opts):
1753 def unapplied(ui, repo, patch=None, **opts):
1754 """print the patches not yet applied"""
1754 """print the patches not yet applied"""
1755
1755
1756 q = repo.mq
1756 q = repo.mq
1757 if patch:
1757 if patch:
1758 if patch not in q.series:
1758 if patch not in q.series:
1759 raise util.Abort(_("patch %s is not in series file") % patch)
1759 raise util.Abort(_("patch %s is not in series file") % patch)
1760 start = q.series.index(patch) + 1
1760 start = q.series.index(patch) + 1
1761 else:
1761 else:
1762 start = q.series_end(True)
1762 start = q.series_end(True)
1763
1763
1764 if start == len(q.series) and opts.get('first'):
1764 if start == len(q.series) and opts.get('first'):
1765 ui.write(_("all patches applied\n"))
1765 ui.write(_("all patches applied\n"))
1766 return 1
1766 return 1
1767
1767
1768 length = opts.get('first') and 1 or None
1768 length = opts.get('first') and 1 or None
1769 return q.qseries(repo, start=start, length=length, status='U',
1769 return q.qseries(repo, start=start, length=length, status='U',
1770 summary=opts.get('summary'))
1770 summary=opts.get('summary'))
1771
1771
1772 def qimport(ui, repo, *filename, **opts):
1772 def qimport(ui, repo, *filename, **opts):
1773 """import a patch
1773 """import a patch
1774
1774
1775 The patch is inserted into the series after the last applied
1775 The patch is inserted into the series after the last applied
1776 patch. If no patches have been applied, qimport prepends the patch
1776 patch. If no patches have been applied, qimport prepends the patch
1777 to the series.
1777 to the series.
1778
1778
1779 The patch will have the same name as its source file unless you
1779 The patch will have the same name as its source file unless you
1780 give it a new one with -n/--name.
1780 give it a new one with -n/--name.
1781
1781
1782 You can register an existing patch inside the patch directory with
1782 You can register an existing patch inside the patch directory with
1783 the -e/--existing flag.
1783 the -e/--existing flag.
1784
1784
1785 With -f/--force, an existing patch of the same name will be
1785 With -f/--force, an existing patch of the same name will be
1786 overwritten.
1786 overwritten.
1787
1787
1788 An existing changeset may be placed under mq control with -r/--rev
1788 An existing changeset may be placed under mq control with -r/--rev
1789 (e.g. qimport --rev tip -n patch will place tip under mq control).
1789 (e.g. qimport --rev tip -n patch will place tip under mq control).
1790 With -g/--git, patches imported with --rev will use the git diff
1790 With -g/--git, patches imported with --rev will use the git diff
1791 format. See the diffs help topic for information on why this is
1791 format. See the diffs help topic for information on why this is
1792 important for preserving rename/copy information and permission
1792 important for preserving rename/copy information and permission
1793 changes.
1793 changes.
1794
1794
1795 To import a patch from standard input, pass - as the patch file.
1795 To import a patch from standard input, pass - as the patch file.
1796 When importing from standard input, a patch name must be specified
1796 When importing from standard input, a patch name must be specified
1797 using the --name flag.
1797 using the --name flag.
1798 """
1798 """
1799 q = repo.mq
1799 q = repo.mq
1800 q.qimport(repo, filename, patchname=opts['name'],
1800 q.qimport(repo, filename, patchname=opts['name'],
1801 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1801 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1802 git=opts['git'])
1802 git=opts['git'])
1803 q.save_dirty()
1803 q.save_dirty()
1804
1804
1805 if opts.get('push') and not opts.get('rev'):
1805 if opts.get('push') and not opts.get('rev'):
1806 return q.push(repo, None)
1806 return q.push(repo, None)
1807 return 0
1807 return 0
1808
1808
1809 def qinit(ui, repo, create):
1809 def qinit(ui, repo, create):
1810 """initialize a new queue repository
1810 """initialize a new queue repository
1811
1811
1812 This command also creates a series file for ordering patches, and
1812 This command also creates a series file for ordering patches, and
1813 an mq-specific .hgignore file in the queue repository, to exclude
1813 an mq-specific .hgignore file in the queue repository, to exclude
1814 the status and guards files (these contain mostly transient state)."""
1814 the status and guards files (these contain mostly transient state)."""
1815 q = repo.mq
1815 q = repo.mq
1816 r = q.init(repo, create)
1816 r = q.init(repo, create)
1817 q.save_dirty()
1817 q.save_dirty()
1818 if r:
1818 if r:
1819 if not os.path.exists(r.wjoin('.hgignore')):
1819 if not os.path.exists(r.wjoin('.hgignore')):
1820 fp = r.wopener('.hgignore', 'w')
1820 fp = r.wopener('.hgignore', 'w')
1821 fp.write('^\\.hg\n')
1821 fp.write('^\\.hg\n')
1822 fp.write('^\\.mq\n')
1822 fp.write('^\\.mq\n')
1823 fp.write('syntax: glob\n')
1823 fp.write('syntax: glob\n')
1824 fp.write('status\n')
1824 fp.write('status\n')
1825 fp.write('guards\n')
1825 fp.write('guards\n')
1826 fp.close()
1826 fp.close()
1827 if not os.path.exists(r.wjoin('series')):
1827 if not os.path.exists(r.wjoin('series')):
1828 r.wopener('series', 'w').close()
1828 r.wopener('series', 'w').close()
1829 r.add(['.hgignore', 'series'])
1829 r.add(['.hgignore', 'series'])
1830 commands.add(ui, r)
1830 commands.add(ui, r)
1831 return 0
1831 return 0
1832
1832
1833 def init(ui, repo, **opts):
1833 def init(ui, repo, **opts):
1834 """init a new queue repository (DEPRECATED)
1834 """init a new queue repository (DEPRECATED)
1835
1835
1836 The queue repository is unversioned by default. If
1836 The queue repository is unversioned by default. If
1837 -c/--create-repo is specified, qinit will create a separate nested
1837 -c/--create-repo is specified, qinit will create a separate nested
1838 repository for patches (qinit -c may also be run later to convert
1838 repository for patches (qinit -c may also be run later to convert
1839 an unversioned patch repository into a versioned one). You can use
1839 an unversioned patch repository into a versioned one). You can use
1840 qcommit to commit changes to this queue repository.
1840 qcommit to commit changes to this queue repository.
1841
1841
1842 This command is deprecated. Without -c, it's implied by other relevant
1842 This command is deprecated. Without -c, it's implied by other relevant
1843 commands. With -c, use hg init --mq instead."""
1843 commands. With -c, use hg init --mq instead."""
1844 return qinit(ui, repo, create=opts['create_repo'])
1844 return qinit(ui, repo, create=opts['create_repo'])
1845
1845
1846 def clone(ui, source, dest=None, **opts):
1846 def clone(ui, source, dest=None, **opts):
1847 '''clone main and patch repository at same time
1847 '''clone main and patch repository at same time
1848
1848
1849 If source is local, destination will have no patches applied. If
1849 If source is local, destination will have no patches applied. If
1850 source is remote, this command can not check if patches are
1850 source is remote, this command can not check if patches are
1851 applied in source, so cannot guarantee that patches are not
1851 applied in source, so cannot guarantee that patches are not
1852 applied in destination. If you clone remote repository, be sure
1852 applied in destination. If you clone remote repository, be sure
1853 before that it has no patches applied.
1853 before that it has no patches applied.
1854
1854
1855 Source patch repository is looked for in <src>/.hg/patches by
1855 Source patch repository is looked for in <src>/.hg/patches by
1856 default. Use -p <url> to change.
1856 default. Use -p <url> to change.
1857
1857
1858 The patch directory must be a nested Mercurial repository, as
1858 The patch directory must be a nested Mercurial repository, as
1859 would be created by init --mq.
1859 would be created by init --mq.
1860 '''
1860 '''
1861 def patchdir(repo):
1861 def patchdir(repo):
1862 url = repo.url()
1862 url = repo.url()
1863 if url.endswith('/'):
1863 if url.endswith('/'):
1864 url = url[:-1]
1864 url = url[:-1]
1865 return url + '/.hg/patches'
1865 return url + '/.hg/patches'
1866 if dest is None:
1866 if dest is None:
1867 dest = hg.defaultdest(source)
1867 dest = hg.defaultdest(source)
1868 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1868 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1869 if opts['patches']:
1869 if opts['patches']:
1870 patchespath = ui.expandpath(opts['patches'])
1870 patchespath = ui.expandpath(opts['patches'])
1871 else:
1871 else:
1872 patchespath = patchdir(sr)
1872 patchespath = patchdir(sr)
1873 try:
1873 try:
1874 hg.repository(ui, patchespath)
1874 hg.repository(ui, patchespath)
1875 except error.RepoError:
1875 except error.RepoError:
1876 raise util.Abort(_('versioned patch repository not found'
1876 raise util.Abort(_('versioned patch repository not found'
1877 ' (see init --mq)'))
1877 ' (see init --mq)'))
1878 qbase, destrev = None, None
1878 qbase, destrev = None, None
1879 if sr.local():
1879 if sr.local():
1880 if sr.mq.applied:
1880 if sr.mq.applied:
1881 qbase = sr.mq.applied[0].node
1881 qbase = sr.mq.applied[0].node
1882 if not hg.islocal(dest):
1882 if not hg.islocal(dest):
1883 heads = set(sr.heads())
1883 heads = set(sr.heads())
1884 destrev = list(heads.difference(sr.heads(qbase)))
1884 destrev = list(heads.difference(sr.heads(qbase)))
1885 destrev.append(sr.changelog.parents(qbase)[0])
1885 destrev.append(sr.changelog.parents(qbase)[0])
1886 elif sr.capable('lookup'):
1886 elif sr.capable('lookup'):
1887 try:
1887 try:
1888 qbase = sr.lookup('qbase')
1888 qbase = sr.lookup('qbase')
1889 except error.RepoError:
1889 except error.RepoError:
1890 pass
1890 pass
1891 ui.note(_('cloning main repository\n'))
1891 ui.note(_('cloning main repository\n'))
1892 sr, dr = hg.clone(ui, sr.url(), dest,
1892 sr, dr = hg.clone(ui, sr.url(), dest,
1893 pull=opts['pull'],
1893 pull=opts['pull'],
1894 rev=destrev,
1894 rev=destrev,
1895 update=False,
1895 update=False,
1896 stream=opts['uncompressed'])
1896 stream=opts['uncompressed'])
1897 ui.note(_('cloning patch repository\n'))
1897 ui.note(_('cloning patch repository\n'))
1898 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1898 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1899 pull=opts['pull'], update=not opts['noupdate'],
1899 pull=opts['pull'], update=not opts['noupdate'],
1900 stream=opts['uncompressed'])
1900 stream=opts['uncompressed'])
1901 if dr.local():
1901 if dr.local():
1902 if qbase:
1902 if qbase:
1903 ui.note(_('stripping applied patches from destination '
1903 ui.note(_('stripping applied patches from destination '
1904 'repository\n'))
1904 'repository\n'))
1905 dr.mq.strip(dr, qbase, update=False, backup=None)
1905 dr.mq.strip(dr, qbase, update=False, backup=None)
1906 if not opts['noupdate']:
1906 if not opts['noupdate']:
1907 ui.note(_('updating destination repository\n'))
1907 ui.note(_('updating destination repository\n'))
1908 hg.update(dr, dr.changelog.tip())
1908 hg.update(dr, dr.changelog.tip())
1909
1909
1910 def commit(ui, repo, *pats, **opts):
1910 def commit(ui, repo, *pats, **opts):
1911 """commit changes in the queue repository (DEPRECATED)
1911 """commit changes in the queue repository (DEPRECATED)
1912
1912
1913 This command is deprecated; use hg commit --mq instead."""
1913 This command is deprecated; use hg commit --mq instead."""
1914 q = repo.mq
1914 q = repo.mq
1915 r = q.qrepo()
1915 r = q.qrepo()
1916 if not r:
1916 if not r:
1917 raise util.Abort('no queue repository')
1917 raise util.Abort('no queue repository')
1918 commands.commit(r.ui, r, *pats, **opts)
1918 commands.commit(r.ui, r, *pats, **opts)
1919
1919
1920 def series(ui, repo, **opts):
1920 def series(ui, repo, **opts):
1921 """print the entire series file"""
1921 """print the entire series file"""
1922 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1922 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1923 return 0
1923 return 0
1924
1924
1925 def top(ui, repo, **opts):
1925 def top(ui, repo, **opts):
1926 """print the name of the current patch"""
1926 """print the name of the current patch"""
1927 q = repo.mq
1927 q = repo.mq
1928 t = q.applied and q.series_end(True) or 0
1928 t = q.applied and q.series_end(True) or 0
1929 if t:
1929 if t:
1930 return q.qseries(repo, start=t - 1, length=1, status='A',
1930 return q.qseries(repo, start=t - 1, length=1, status='A',
1931 summary=opts.get('summary'))
1931 summary=opts.get('summary'))
1932 else:
1932 else:
1933 ui.write(_("no patches applied\n"))
1933 ui.write(_("no patches applied\n"))
1934 return 1
1934 return 1
1935
1935
1936 def next(ui, repo, **opts):
1936 def next(ui, repo, **opts):
1937 """print the name of the next patch"""
1937 """print the name of the next patch"""
1938 q = repo.mq
1938 q = repo.mq
1939 end = q.series_end()
1939 end = q.series_end()
1940 if end == len(q.series):
1940 if end == len(q.series):
1941 ui.write(_("all patches applied\n"))
1941 ui.write(_("all patches applied\n"))
1942 return 1
1942 return 1
1943 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1943 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1944
1944
1945 def prev(ui, repo, **opts):
1945 def prev(ui, repo, **opts):
1946 """print the name of the previous patch"""
1946 """print the name of the previous patch"""
1947 q = repo.mq
1947 q = repo.mq
1948 l = len(q.applied)
1948 l = len(q.applied)
1949 if l == 1:
1949 if l == 1:
1950 ui.write(_("only one patch applied\n"))
1950 ui.write(_("only one patch applied\n"))
1951 return 1
1951 return 1
1952 if not l:
1952 if not l:
1953 ui.write(_("no patches applied\n"))
1953 ui.write(_("no patches applied\n"))
1954 return 1
1954 return 1
1955 return q.qseries(repo, start=l - 2, length=1, status='A',
1955 return q.qseries(repo, start=l - 2, length=1, status='A',
1956 summary=opts.get('summary'))
1956 summary=opts.get('summary'))
1957
1957
1958 def setupheaderopts(ui, opts):
1958 def setupheaderopts(ui, opts):
1959 if not opts.get('user') and opts.get('currentuser'):
1959 if not opts.get('user') and opts.get('currentuser'):
1960 opts['user'] = ui.username()
1960 opts['user'] = ui.username()
1961 if not opts.get('date') and opts.get('currentdate'):
1961 if not opts.get('date') and opts.get('currentdate'):
1962 opts['date'] = "%d %d" % util.makedate()
1962 opts['date'] = "%d %d" % util.makedate()
1963
1963
1964 def new(ui, repo, patch, *args, **opts):
1964 def new(ui, repo, patch, *args, **opts):
1965 """create a new patch
1965 """create a new patch
1966
1966
1967 qnew creates a new patch on top of the currently-applied patch (if
1967 qnew creates a new patch on top of the currently-applied patch (if
1968 any). The patch will be initialized with any outstanding changes
1968 any). The patch will be initialized with any outstanding changes
1969 in the working directory. You may also use -I/--include,
1969 in the working directory. You may also use -I/--include,
1970 -X/--exclude, and/or a list of files after the patch name to add
1970 -X/--exclude, and/or a list of files after the patch name to add
1971 only changes to matching files to the new patch, leaving the rest
1971 only changes to matching files to the new patch, leaving the rest
1972 as uncommitted modifications.
1972 as uncommitted modifications.
1973
1973
1974 -u/--user and -d/--date can be used to set the (given) user and
1974 -u/--user and -d/--date can be used to set the (given) user and
1975 date, respectively. -U/--currentuser and -D/--currentdate set user
1975 date, respectively. -U/--currentuser and -D/--currentdate set user
1976 to current user and date to current date.
1976 to current user and date to current date.
1977
1977
1978 -e/--edit, -m/--message or -l/--logfile set the patch header as
1978 -e/--edit, -m/--message or -l/--logfile set the patch header as
1979 well as the commit message. If none is specified, the header is
1979 well as the commit message. If none is specified, the header is
1980 empty and the commit message is '[mq]: PATCH'.
1980 empty and the commit message is '[mq]: PATCH'.
1981
1981
1982 Use the -g/--git option to keep the patch in the git extended diff
1982 Use the -g/--git option to keep the patch in the git extended diff
1983 format. Read the diffs help topic for more information on why this
1983 format. Read the diffs help topic for more information on why this
1984 is important for preserving permission changes and copy/rename
1984 is important for preserving permission changes and copy/rename
1985 information.
1985 information.
1986 """
1986 """
1987 msg = cmdutil.logmessage(opts)
1987 msg = cmdutil.logmessage(opts)
1988 def getmsg():
1988 def getmsg():
1989 return ui.edit(msg, ui.username())
1989 return ui.edit(msg, ui.username())
1990 q = repo.mq
1990 q = repo.mq
1991 opts['msg'] = msg
1991 opts['msg'] = msg
1992 if opts.get('edit'):
1992 if opts.get('edit'):
1993 opts['msg'] = getmsg
1993 opts['msg'] = getmsg
1994 else:
1994 else:
1995 opts['msg'] = msg
1995 opts['msg'] = msg
1996 setupheaderopts(ui, opts)
1996 setupheaderopts(ui, opts)
1997 q.new(repo, patch, *args, **opts)
1997 q.new(repo, patch, *args, **opts)
1998 q.save_dirty()
1998 q.save_dirty()
1999 return 0
1999 return 0
2000
2000
2001 def refresh(ui, repo, *pats, **opts):
2001 def refresh(ui, repo, *pats, **opts):
2002 """update the current patch
2002 """update the current patch
2003
2003
2004 If any file patterns are provided, the refreshed patch will
2004 If any file patterns are provided, the refreshed patch will
2005 contain only the modifications that match those patterns; the
2005 contain only the modifications that match those patterns; the
2006 remaining modifications will remain in the working directory.
2006 remaining modifications will remain in the working directory.
2007
2007
2008 If -s/--short is specified, files currently included in the patch
2008 If -s/--short is specified, files currently included in the patch
2009 will be refreshed just like matched files and remain in the patch.
2009 will be refreshed just like matched files and remain in the patch.
2010
2010
2011 hg add/remove/copy/rename work as usual, though you might want to
2011 hg add/remove/copy/rename work as usual, though you might want to
2012 use git-style patches (-g/--git or [diff] git=1) to track copies
2012 use git-style patches (-g/--git or [diff] git=1) to track copies
2013 and renames. See the diffs help topic for more information on the
2013 and renames. See the diffs help topic for more information on the
2014 git diff format.
2014 git diff format.
2015 """
2015 """
2016 q = repo.mq
2016 q = repo.mq
2017 message = cmdutil.logmessage(opts)
2017 message = cmdutil.logmessage(opts)
2018 if opts['edit']:
2018 if opts['edit']:
2019 if not q.applied:
2019 if not q.applied:
2020 ui.write(_("no patches applied\n"))
2020 ui.write(_("no patches applied\n"))
2021 return 1
2021 return 1
2022 if message:
2022 if message:
2023 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2023 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2024 patch = q.applied[-1].name
2024 patch = q.applied[-1].name
2025 ph = patchheader(q.join(patch), q.plainmode)
2025 ph = patchheader(q.join(patch), q.plainmode)
2026 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2026 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2027 setupheaderopts(ui, opts)
2027 setupheaderopts(ui, opts)
2028 ret = q.refresh(repo, pats, msg=message, **opts)
2028 ret = q.refresh(repo, pats, msg=message, **opts)
2029 q.save_dirty()
2029 q.save_dirty()
2030 return ret
2030 return ret
2031
2031
2032 def diff(ui, repo, *pats, **opts):
2032 def diff(ui, repo, *pats, **opts):
2033 """diff of the current patch and subsequent modifications
2033 """diff of the current patch and subsequent modifications
2034
2034
2035 Shows a diff which includes the current patch as well as any
2035 Shows a diff which includes the current patch as well as any
2036 changes which have been made in the working directory since the
2036 changes which have been made in the working directory since the
2037 last refresh (thus showing what the current patch would become
2037 last refresh (thus showing what the current patch would become
2038 after a qrefresh).
2038 after a qrefresh).
2039
2039
2040 Use 'hg diff' if you only want to see the changes made since the
2040 Use 'hg diff' if you only want to see the changes made since the
2041 last qrefresh, or 'hg export qtip' if you want to see changes made
2041 last qrefresh, or 'hg export qtip' if you want to see changes made
2042 by the current patch without including changes made since the
2042 by the current patch without including changes made since the
2043 qrefresh.
2043 qrefresh.
2044 """
2044 """
2045 repo.mq.diff(repo, pats, opts)
2045 repo.mq.diff(repo, pats, opts)
2046 return 0
2046 return 0
2047
2047
2048 def fold(ui, repo, *files, **opts):
2048 def fold(ui, repo, *files, **opts):
2049 """fold the named patches into the current patch
2049 """fold the named patches into the current patch
2050
2050
2051 Patches must not yet be applied. Each patch will be successively
2051 Patches must not yet be applied. Each patch will be successively
2052 applied to the current patch in the order given. If all the
2052 applied to the current patch in the order given. If all the
2053 patches apply successfully, the current patch will be refreshed
2053 patches apply successfully, the current patch will be refreshed
2054 with the new cumulative patch, and the folded patches will be
2054 with the new cumulative patch, and the folded patches will be
2055 deleted. With -k/--keep, the folded patch files will not be
2055 deleted. With -k/--keep, the folded patch files will not be
2056 removed afterwards.
2056 removed afterwards.
2057
2057
2058 The header for each folded patch will be concatenated with the
2058 The header for each folded patch will be concatenated with the
2059 current patch header, separated by a line of '* * *'."""
2059 current patch header, separated by a line of '* * *'."""
2060
2060
2061 q = repo.mq
2061 q = repo.mq
2062
2062
2063 if not files:
2063 if not files:
2064 raise util.Abort(_('qfold requires at least one patch name'))
2064 raise util.Abort(_('qfold requires at least one patch name'))
2065 if not q.check_toppatch(repo)[0]:
2065 if not q.check_toppatch(repo)[0]:
2066 raise util.Abort(_('No patches applied'))
2066 raise util.Abort(_('No patches applied'))
2067 q.check_localchanges(repo)
2067 q.check_localchanges(repo)
2068
2068
2069 message = cmdutil.logmessage(opts)
2069 message = cmdutil.logmessage(opts)
2070 if opts['edit']:
2070 if opts['edit']:
2071 if message:
2071 if message:
2072 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2072 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2073
2073
2074 parent = q.lookup('qtip')
2074 parent = q.lookup('qtip')
2075 patches = []
2075 patches = []
2076 messages = []
2076 messages = []
2077 for f in files:
2077 for f in files:
2078 p = q.lookup(f)
2078 p = q.lookup(f)
2079 if p in patches or p == parent:
2079 if p in patches or p == parent:
2080 ui.warn(_('Skipping already folded patch %s') % p)
2080 ui.warn(_('Skipping already folded patch %s') % p)
2081 if q.isapplied(p):
2081 if q.isapplied(p):
2082 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2082 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2083 patches.append(p)
2083 patches.append(p)
2084
2084
2085 for p in patches:
2085 for p in patches:
2086 if not message:
2086 if not message:
2087 ph = patchheader(q.join(p), q.plainmode)
2087 ph = patchheader(q.join(p), q.plainmode)
2088 if ph.message:
2088 if ph.message:
2089 messages.append(ph.message)
2089 messages.append(ph.message)
2090 pf = q.join(p)
2090 pf = q.join(p)
2091 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2091 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2092 if not patchsuccess:
2092 if not patchsuccess:
2093 raise util.Abort(_('Error folding patch %s') % p)
2093 raise util.Abort(_('Error folding patch %s') % p)
2094 patch.updatedir(ui, repo, files)
2094 patch.updatedir(ui, repo, files)
2095
2095
2096 if not message:
2096 if not message:
2097 ph = patchheader(q.join(parent), q.plainmode)
2097 ph = patchheader(q.join(parent), q.plainmode)
2098 message, user = ph.message, ph.user
2098 message, user = ph.message, ph.user
2099 for msg in messages:
2099 for msg in messages:
2100 message.append('* * *')
2100 message.append('* * *')
2101 message.extend(msg)
2101 message.extend(msg)
2102 message = '\n'.join(message)
2102 message = '\n'.join(message)
2103
2103
2104 if opts['edit']:
2104 if opts['edit']:
2105 message = ui.edit(message, user or ui.username())
2105 message = ui.edit(message, user or ui.username())
2106
2106
2107 diffopts = q.patchopts(q.diffopts(), *patches)
2107 diffopts = q.patchopts(q.diffopts(), *patches)
2108 q.refresh(repo, msg=message, git=diffopts.git)
2108 q.refresh(repo, msg=message, git=diffopts.git)
2109 q.delete(repo, patches, opts)
2109 q.delete(repo, patches, opts)
2110 q.save_dirty()
2110 q.save_dirty()
2111
2111
2112 def goto(ui, repo, patch, **opts):
2112 def goto(ui, repo, patch, **opts):
2113 '''push or pop patches until named patch is at top of stack'''
2113 '''push or pop patches until named patch is at top of stack'''
2114 q = repo.mq
2114 q = repo.mq
2115 patch = q.lookup(patch)
2115 patch = q.lookup(patch)
2116 if q.isapplied(patch):
2116 if q.isapplied(patch):
2117 ret = q.pop(repo, patch, force=opts['force'])
2117 ret = q.pop(repo, patch, force=opts['force'])
2118 else:
2118 else:
2119 ret = q.push(repo, patch, force=opts['force'])
2119 ret = q.push(repo, patch, force=opts['force'])
2120 q.save_dirty()
2120 q.save_dirty()
2121 return ret
2121 return ret
2122
2122
2123 def guard(ui, repo, *args, **opts):
2123 def guard(ui, repo, *args, **opts):
2124 '''set or print guards for a patch
2124 '''set or print guards for a patch
2125
2125
2126 Guards control whether a patch can be pushed. A patch with no
2126 Guards control whether a patch can be pushed. A patch with no
2127 guards is always pushed. A patch with a positive guard ("+foo") is
2127 guards is always pushed. A patch with a positive guard ("+foo") is
2128 pushed only if the qselect command has activated it. A patch with
2128 pushed only if the qselect command has activated it. A patch with
2129 a negative guard ("-foo") is never pushed if the qselect command
2129 a negative guard ("-foo") is never pushed if the qselect command
2130 has activated it.
2130 has activated it.
2131
2131
2132 With no arguments, print the currently active guards.
2132 With no arguments, print the currently active guards.
2133 With arguments, set guards for the named patch.
2133 With arguments, set guards for the named patch.
2134 NOTE: Specifying negative guards now requires '--'.
2134 NOTE: Specifying negative guards now requires '--'.
2135
2135
2136 To set guards on another patch::
2136 To set guards on another patch::
2137
2137
2138 hg qguard other.patch -- +2.6.17 -stable
2138 hg qguard other.patch -- +2.6.17 -stable
2139 '''
2139 '''
2140 def status(idx):
2140 def status(idx):
2141 guards = q.series_guards[idx] or ['unguarded']
2141 guards = q.series_guards[idx] or ['unguarded']
2142 ui.write('%s: ' % ui.label(q.series[idx], 'qguard.patch'))
2142 ui.write('%s: ' % ui.label(q.series[idx], 'qguard.patch'))
2143 for i, guard in enumerate(guards):
2143 for i, guard in enumerate(guards):
2144 if guard.startswith('+'):
2144 if guard.startswith('+'):
2145 ui.write(guard, label='qguard.positive')
2145 ui.write(guard, label='qguard.positive')
2146 elif guard.startswith('-'):
2146 elif guard.startswith('-'):
2147 ui.write(guard, label='qguard.negative')
2147 ui.write(guard, label='qguard.negative')
2148 else:
2148 else:
2149 ui.write(guard, label='qguard.unguarded')
2149 ui.write(guard, label='qguard.unguarded')
2150 if i != len(guards) - 1:
2150 if i != len(guards) - 1:
2151 ui.write(' ')
2151 ui.write(' ')
2152 ui.write('\n')
2152 ui.write('\n')
2153 q = repo.mq
2153 q = repo.mq
2154 patch = None
2154 patch = None
2155 args = list(args)
2155 args = list(args)
2156 if opts['list']:
2156 if opts['list']:
2157 if args or opts['none']:
2157 if args or opts['none']:
2158 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2158 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2159 for i in xrange(len(q.series)):
2159 for i in xrange(len(q.series)):
2160 status(i)
2160 status(i)
2161 return
2161 return
2162 if not args or args[0][0:1] in '-+':
2162 if not args or args[0][0:1] in '-+':
2163 if not q.applied:
2163 if not q.applied:
2164 raise util.Abort(_('no patches applied'))
2164 raise util.Abort(_('no patches applied'))
2165 patch = q.applied[-1].name
2165 patch = q.applied[-1].name
2166 if patch is None and args[0][0:1] not in '-+':
2166 if patch is None and args[0][0:1] not in '-+':
2167 patch = args.pop(0)
2167 patch = args.pop(0)
2168 if patch is None:
2168 if patch is None:
2169 raise util.Abort(_('no patch to work with'))
2169 raise util.Abort(_('no patch to work with'))
2170 if args or opts['none']:
2170 if args or opts['none']:
2171 idx = q.find_series(patch)
2171 idx = q.find_series(patch)
2172 if idx is None:
2172 if idx is None:
2173 raise util.Abort(_('no patch named %s') % patch)
2173 raise util.Abort(_('no patch named %s') % patch)
2174 q.set_guards(idx, args)
2174 q.set_guards(idx, args)
2175 q.save_dirty()
2175 q.save_dirty()
2176 else:
2176 else:
2177 status(q.series.index(q.lookup(patch)))
2177 status(q.series.index(q.lookup(patch)))
2178
2178
2179 def header(ui, repo, patch=None):
2179 def header(ui, repo, patch=None):
2180 """print the header of the topmost or specified patch"""
2180 """print the header of the topmost or specified patch"""
2181 q = repo.mq
2181 q = repo.mq
2182
2182
2183 if patch:
2183 if patch:
2184 patch = q.lookup(patch)
2184 patch = q.lookup(patch)
2185 else:
2185 else:
2186 if not q.applied:
2186 if not q.applied:
2187 ui.write(_('no patches applied\n'))
2187 ui.write(_('no patches applied\n'))
2188 return 1
2188 return 1
2189 patch = q.lookup('qtip')
2189 patch = q.lookup('qtip')
2190 ph = patchheader(q.join(patch), q.plainmode)
2190 ph = patchheader(q.join(patch), q.plainmode)
2191
2191
2192 ui.write('\n'.join(ph.message) + '\n')
2192 ui.write('\n'.join(ph.message) + '\n')
2193
2193
2194 def lastsavename(path):
2194 def lastsavename(path):
2195 (directory, base) = os.path.split(path)
2195 (directory, base) = os.path.split(path)
2196 names = os.listdir(directory)
2196 names = os.listdir(directory)
2197 namere = re.compile("%s.([0-9]+)" % base)
2197 namere = re.compile("%s.([0-9]+)" % base)
2198 maxindex = None
2198 maxindex = None
2199 maxname = None
2199 maxname = None
2200 for f in names:
2200 for f in names:
2201 m = namere.match(f)
2201 m = namere.match(f)
2202 if m:
2202 if m:
2203 index = int(m.group(1))
2203 index = int(m.group(1))
2204 if maxindex is None or index > maxindex:
2204 if maxindex is None or index > maxindex:
2205 maxindex = index
2205 maxindex = index
2206 maxname = f
2206 maxname = f
2207 if maxname:
2207 if maxname:
2208 return (os.path.join(directory, maxname), maxindex)
2208 return (os.path.join(directory, maxname), maxindex)
2209 return (None, None)
2209 return (None, None)
2210
2210
2211 def savename(path):
2211 def savename(path):
2212 (last, index) = lastsavename(path)
2212 (last, index) = lastsavename(path)
2213 if last is None:
2213 if last is None:
2214 index = 0
2214 index = 0
2215 newpath = path + ".%d" % (index + 1)
2215 newpath = path + ".%d" % (index + 1)
2216 return newpath
2216 return newpath
2217
2217
2218 def push(ui, repo, patch=None, **opts):
2218 def push(ui, repo, patch=None, **opts):
2219 """push the next patch onto the stack
2219 """push the next patch onto the stack
2220
2220
2221 When -f/--force is applied, all local changes in patched files
2221 When -f/--force is applied, all local changes in patched files
2222 will be lost.
2222 will be lost.
2223 """
2223 """
2224 q = repo.mq
2224 q = repo.mq
2225 mergeq = None
2225 mergeq = None
2226
2226
2227 if opts['merge']:
2227 if opts['merge']:
2228 if opts['name']:
2228 if opts['name']:
2229 newpath = repo.join(opts['name'])
2229 newpath = repo.join(opts['name'])
2230 else:
2230 else:
2231 newpath, i = lastsavename(q.path)
2231 newpath, i = lastsavename(q.path)
2232 if not newpath:
2232 if not newpath:
2233 ui.warn(_("no saved queues found, please use -n\n"))
2233 ui.warn(_("no saved queues found, please use -n\n"))
2234 return 1
2234 return 1
2235 mergeq = queue(ui, repo.join(""), newpath)
2235 mergeq = queue(ui, repo.join(""), newpath)
2236 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2236 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2237 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2237 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2238 mergeq=mergeq, all=opts.get('all'))
2238 mergeq=mergeq, all=opts.get('all'))
2239 return ret
2239 return ret
2240
2240
2241 def pop(ui, repo, patch=None, **opts):
2241 def pop(ui, repo, patch=None, **opts):
2242 """pop the current patch off the stack
2242 """pop the current patch off the stack
2243
2243
2244 By default, pops off the top of the patch stack. If given a patch
2244 By default, pops off the top of the patch stack. If given a patch
2245 name, keeps popping off patches until the named patch is at the
2245 name, keeps popping off patches until the named patch is at the
2246 top of the stack.
2246 top of the stack.
2247 """
2247 """
2248 localupdate = True
2248 localupdate = True
2249 if opts['name']:
2249 if opts['name']:
2250 q = queue(ui, repo.join(""), repo.join(opts['name']))
2250 q = queue(ui, repo.join(""), repo.join(opts['name']))
2251 ui.warn(_('using patch queue: %s\n') % q.path)
2251 ui.warn(_('using patch queue: %s\n') % q.path)
2252 localupdate = False
2252 localupdate = False
2253 else:
2253 else:
2254 q = repo.mq
2254 q = repo.mq
2255 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2255 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2256 all=opts['all'])
2256 all=opts['all'])
2257 q.save_dirty()
2257 q.save_dirty()
2258 return ret
2258 return ret
2259
2259
2260 def rename(ui, repo, patch, name=None, **opts):
2260 def rename(ui, repo, patch, name=None, **opts):
2261 """rename a patch
2261 """rename a patch
2262
2262
2263 With one argument, renames the current patch to PATCH1.
2263 With one argument, renames the current patch to PATCH1.
2264 With two arguments, renames PATCH1 to PATCH2."""
2264 With two arguments, renames PATCH1 to PATCH2."""
2265
2265
2266 q = repo.mq
2266 q = repo.mq
2267
2267
2268 if not name:
2268 if not name:
2269 name = patch
2269 name = patch
2270 patch = None
2270 patch = None
2271
2271
2272 if patch:
2272 if patch:
2273 patch = q.lookup(patch)
2273 patch = q.lookup(patch)
2274 else:
2274 else:
2275 if not q.applied:
2275 if not q.applied:
2276 ui.write(_('no patches applied\n'))
2276 ui.write(_('no patches applied\n'))
2277 return
2277 return
2278 patch = q.lookup('qtip')
2278 patch = q.lookup('qtip')
2279 absdest = q.join(name)
2279 absdest = q.join(name)
2280 if os.path.isdir(absdest):
2280 if os.path.isdir(absdest):
2281 name = normname(os.path.join(name, os.path.basename(patch)))
2281 name = normname(os.path.join(name, os.path.basename(patch)))
2282 absdest = q.join(name)
2282 absdest = q.join(name)
2283 if os.path.exists(absdest):
2283 if os.path.exists(absdest):
2284 raise util.Abort(_('%s already exists') % absdest)
2284 raise util.Abort(_('%s already exists') % absdest)
2285
2285
2286 if name in q.series:
2286 if name in q.series:
2287 raise util.Abort(
2287 raise util.Abort(
2288 _('A patch named %s already exists in the series file') % name)
2288 _('A patch named %s already exists in the series file') % name)
2289
2289
2290 ui.note(_('renaming %s to %s\n') % (patch, name))
2290 ui.note(_('renaming %s to %s\n') % (patch, name))
2291 i = q.find_series(patch)
2291 i = q.find_series(patch)
2292 guards = q.guard_re.findall(q.full_series[i])
2292 guards = q.guard_re.findall(q.full_series[i])
2293 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2293 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2294 q.parse_series()
2294 q.parse_series()
2295 q.series_dirty = 1
2295 q.series_dirty = 1
2296
2296
2297 info = q.isapplied(patch)
2297 info = q.isapplied(patch)
2298 if info:
2298 if info:
2299 q.applied[info[0]] = statusentry(info[1], name)
2299 q.applied[info[0]] = statusentry(info[1], name)
2300 q.applied_dirty = 1
2300 q.applied_dirty = 1
2301
2301
2302 util.rename(q.join(patch), absdest)
2302 util.rename(q.join(patch), absdest)
2303 r = q.qrepo()
2303 r = q.qrepo()
2304 if r:
2304 if r:
2305 wlock = r.wlock()
2305 wlock = r.wlock()
2306 try:
2306 try:
2307 if r.dirstate[patch] == 'a':
2307 if r.dirstate[patch] == 'a':
2308 r.dirstate.forget(patch)
2308 r.dirstate.forget(patch)
2309 r.dirstate.add(name)
2309 r.dirstate.add(name)
2310 else:
2310 else:
2311 if r.dirstate[name] == 'r':
2311 if r.dirstate[name] == 'r':
2312 r.undelete([name])
2312 r.undelete([name])
2313 r.copy(patch, name)
2313 r.copy(patch, name)
2314 r.remove([patch], False)
2314 r.remove([patch], False)
2315 finally:
2315 finally:
2316 wlock.release()
2316 wlock.release()
2317
2317
2318 q.save_dirty()
2318 q.save_dirty()
2319
2319
2320 def restore(ui, repo, rev, **opts):
2320 def restore(ui, repo, rev, **opts):
2321 """restore the queue state saved by a revision (DEPRECATED)
2321 """restore the queue state saved by a revision (DEPRECATED)
2322
2322
2323 This command is deprecated, use rebase --mq instead."""
2323 This command is deprecated, use rebase --mq instead."""
2324 rev = repo.lookup(rev)
2324 rev = repo.lookup(rev)
2325 q = repo.mq
2325 q = repo.mq
2326 q.restore(repo, rev, delete=opts['delete'],
2326 q.restore(repo, rev, delete=opts['delete'],
2327 qupdate=opts['update'])
2327 qupdate=opts['update'])
2328 q.save_dirty()
2328 q.save_dirty()
2329 return 0
2329 return 0
2330
2330
2331 def save(ui, repo, **opts):
2331 def save(ui, repo, **opts):
2332 """save current queue state (DEPRECATED)
2332 """save current queue state (DEPRECATED)
2333
2333
2334 This command is deprecated, use rebase --mq instead."""
2334 This command is deprecated, use rebase --mq instead."""
2335 q = repo.mq
2335 q = repo.mq
2336 message = cmdutil.logmessage(opts)
2336 message = cmdutil.logmessage(opts)
2337 ret = q.save(repo, msg=message)
2337 ret = q.save(repo, msg=message)
2338 if ret:
2338 if ret:
2339 return ret
2339 return ret
2340 q.save_dirty()
2340 q.save_dirty()
2341 if opts['copy']:
2341 if opts['copy']:
2342 path = q.path
2342 path = q.path
2343 if opts['name']:
2343 if opts['name']:
2344 newpath = os.path.join(q.basepath, opts['name'])
2344 newpath = os.path.join(q.basepath, opts['name'])
2345 if os.path.exists(newpath):
2345 if os.path.exists(newpath):
2346 if not os.path.isdir(newpath):
2346 if not os.path.isdir(newpath):
2347 raise util.Abort(_('destination %s exists and is not '
2347 raise util.Abort(_('destination %s exists and is not '
2348 'a directory') % newpath)
2348 'a directory') % newpath)
2349 if not opts['force']:
2349 if not opts['force']:
2350 raise util.Abort(_('destination %s exists, '
2350 raise util.Abort(_('destination %s exists, '
2351 'use -f to force') % newpath)
2351 'use -f to force') % newpath)
2352 else:
2352 else:
2353 newpath = savename(path)
2353 newpath = savename(path)
2354 ui.warn(_("copy %s to %s\n") % (path, newpath))
2354 ui.warn(_("copy %s to %s\n") % (path, newpath))
2355 util.copyfiles(path, newpath)
2355 util.copyfiles(path, newpath)
2356 if opts['empty']:
2356 if opts['empty']:
2357 try:
2357 try:
2358 os.unlink(q.join(q.status_path))
2358 os.unlink(q.join(q.status_path))
2359 except:
2359 except:
2360 pass
2360 pass
2361 return 0
2361 return 0
2362
2362
2363 def strip(ui, repo, rev, **opts):
2363 def strip(ui, repo, rev, **opts):
2364 """strip a revision and all its descendants from the repository
2364 """strip a revision and all its descendants from the repository
2365
2365
2366 If one of the working directory's parent revisions is stripped, the
2366 If one of the working directory's parent revisions is stripped, the
2367 working directory will be updated to the parent of the stripped
2367 working directory will be updated to the parent of the stripped
2368 revision.
2368 revision.
2369 """
2369 """
2370 backup = 'all'
2370 backup = 'all'
2371 if opts['backup']:
2371 if opts['backup']:
2372 backup = 'strip'
2372 backup = 'strip'
2373 elif opts['nobackup']:
2373 elif opts['nobackup']:
2374 backup = 'none'
2374 backup = 'none'
2375
2375
2376 rev = repo.lookup(rev)
2376 rev = repo.lookup(rev)
2377 p = repo.dirstate.parents()
2377 p = repo.dirstate.parents()
2378 cl = repo.changelog
2378 cl = repo.changelog
2379 update = True
2379 update = True
2380 if p[0] == nullid:
2380 if p[0] == nullid:
2381 update = False
2381 update = False
2382 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2382 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2383 update = False
2383 update = False
2384 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2384 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2385 update = False
2385 update = False
2386
2386
2387 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2387 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2388 return 0
2388 return 0
2389
2389
2390 def select(ui, repo, *args, **opts):
2390 def select(ui, repo, *args, **opts):
2391 '''set or print guarded patches to push
2391 '''set or print guarded patches to push
2392
2392
2393 Use the qguard command to set or print guards on patch, then use
2393 Use the qguard command to set or print guards on patch, then use
2394 qselect to tell mq which guards to use. A patch will be pushed if
2394 qselect to tell mq which guards to use. A patch will be pushed if
2395 it has no guards or any positive guards match the currently
2395 it has no guards or any positive guards match the currently
2396 selected guard, but will not be pushed if any negative guards
2396 selected guard, but will not be pushed if any negative guards
2397 match the current guard. For example::
2397 match the current guard. For example::
2398
2398
2399 qguard foo.patch -stable (negative guard)
2399 qguard foo.patch -stable (negative guard)
2400 qguard bar.patch +stable (positive guard)
2400 qguard bar.patch +stable (positive guard)
2401 qselect stable
2401 qselect stable
2402
2402
2403 This activates the "stable" guard. mq will skip foo.patch (because
2403 This activates the "stable" guard. mq will skip foo.patch (because
2404 it has a negative match) but push bar.patch (because it has a
2404 it has a negative match) but push bar.patch (because it has a
2405 positive match).
2405 positive match).
2406
2406
2407 With no arguments, prints the currently active guards.
2407 With no arguments, prints the currently active guards.
2408 With one argument, sets the active guard.
2408 With one argument, sets the active guard.
2409
2409
2410 Use -n/--none to deactivate guards (no other arguments needed).
2410 Use -n/--none to deactivate guards (no other arguments needed).
2411 When no guards are active, patches with positive guards are
2411 When no guards are active, patches with positive guards are
2412 skipped and patches with negative guards are pushed.
2412 skipped and patches with negative guards are pushed.
2413
2413
2414 qselect can change the guards on applied patches. It does not pop
2414 qselect can change the guards on applied patches. It does not pop
2415 guarded patches by default. Use --pop to pop back to the last
2415 guarded patches by default. Use --pop to pop back to the last
2416 applied patch that is not guarded. Use --reapply (which implies
2416 applied patch that is not guarded. Use --reapply (which implies
2417 --pop) to push back to the current patch afterwards, but skip
2417 --pop) to push back to the current patch afterwards, but skip
2418 guarded patches.
2418 guarded patches.
2419
2419
2420 Use -s/--series to print a list of all guards in the series file
2420 Use -s/--series to print a list of all guards in the series file
2421 (no other arguments needed). Use -v for more information.'''
2421 (no other arguments needed). Use -v for more information.'''
2422
2422
2423 q = repo.mq
2423 q = repo.mq
2424 guards = q.active()
2424 guards = q.active()
2425 if args or opts['none']:
2425 if args or opts['none']:
2426 old_unapplied = q.unapplied(repo)
2426 old_unapplied = q.unapplied(repo)
2427 old_guarded = [i for i in xrange(len(q.applied)) if
2427 old_guarded = [i for i in xrange(len(q.applied)) if
2428 not q.pushable(i)[0]]
2428 not q.pushable(i)[0]]
2429 q.set_active(args)
2429 q.set_active(args)
2430 q.save_dirty()
2430 q.save_dirty()
2431 if not args:
2431 if not args:
2432 ui.status(_('guards deactivated\n'))
2432 ui.status(_('guards deactivated\n'))
2433 if not opts['pop'] and not opts['reapply']:
2433 if not opts['pop'] and not opts['reapply']:
2434 unapplied = q.unapplied(repo)
2434 unapplied = q.unapplied(repo)
2435 guarded = [i for i in xrange(len(q.applied))
2435 guarded = [i for i in xrange(len(q.applied))
2436 if not q.pushable(i)[0]]
2436 if not q.pushable(i)[0]]
2437 if len(unapplied) != len(old_unapplied):
2437 if len(unapplied) != len(old_unapplied):
2438 ui.status(_('number of unguarded, unapplied patches has '
2438 ui.status(_('number of unguarded, unapplied patches has '
2439 'changed from %d to %d\n') %
2439 'changed from %d to %d\n') %
2440 (len(old_unapplied), len(unapplied)))
2440 (len(old_unapplied), len(unapplied)))
2441 if len(guarded) != len(old_guarded):
2441 if len(guarded) != len(old_guarded):
2442 ui.status(_('number of guarded, applied patches has changed '
2442 ui.status(_('number of guarded, applied patches has changed '
2443 'from %d to %d\n') %
2443 'from %d to %d\n') %
2444 (len(old_guarded), len(guarded)))
2444 (len(old_guarded), len(guarded)))
2445 elif opts['series']:
2445 elif opts['series']:
2446 guards = {}
2446 guards = {}
2447 noguards = 0
2447 noguards = 0
2448 for gs in q.series_guards:
2448 for gs in q.series_guards:
2449 if not gs:
2449 if not gs:
2450 noguards += 1
2450 noguards += 1
2451 for g in gs:
2451 for g in gs:
2452 guards.setdefault(g, 0)
2452 guards.setdefault(g, 0)
2453 guards[g] += 1
2453 guards[g] += 1
2454 if ui.verbose:
2454 if ui.verbose:
2455 guards['NONE'] = noguards
2455 guards['NONE'] = noguards
2456 guards = guards.items()
2456 guards = guards.items()
2457 guards.sort(key=lambda x: x[0][1:])
2457 guards.sort(key=lambda x: x[0][1:])
2458 if guards:
2458 if guards:
2459 ui.note(_('guards in series file:\n'))
2459 ui.note(_('guards in series file:\n'))
2460 for guard, count in guards:
2460 for guard, count in guards:
2461 ui.note('%2d ' % count)
2461 ui.note('%2d ' % count)
2462 ui.write(guard, '\n')
2462 ui.write(guard, '\n')
2463 else:
2463 else:
2464 ui.note(_('no guards in series file\n'))
2464 ui.note(_('no guards in series file\n'))
2465 else:
2465 else:
2466 if guards:
2466 if guards:
2467 ui.note(_('active guards:\n'))
2467 ui.note(_('active guards:\n'))
2468 for g in guards:
2468 for g in guards:
2469 ui.write(g, '\n')
2469 ui.write(g, '\n')
2470 else:
2470 else:
2471 ui.write(_('no active guards\n'))
2471 ui.write(_('no active guards\n'))
2472 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2472 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2473 popped = False
2473 popped = False
2474 if opts['pop'] or opts['reapply']:
2474 if opts['pop'] or opts['reapply']:
2475 for i in xrange(len(q.applied)):
2475 for i in xrange(len(q.applied)):
2476 pushable, reason = q.pushable(i)
2476 pushable, reason = q.pushable(i)
2477 if not pushable:
2477 if not pushable:
2478 ui.status(_('popping guarded patches\n'))
2478 ui.status(_('popping guarded patches\n'))
2479 popped = True
2479 popped = True
2480 if i == 0:
2480 if i == 0:
2481 q.pop(repo, all=True)
2481 q.pop(repo, all=True)
2482 else:
2482 else:
2483 q.pop(repo, i - 1)
2483 q.pop(repo, i - 1)
2484 break
2484 break
2485 if popped:
2485 if popped:
2486 try:
2486 try:
2487 if reapply:
2487 if reapply:
2488 ui.status(_('reapplying unguarded patches\n'))
2488 ui.status(_('reapplying unguarded patches\n'))
2489 q.push(repo, reapply)
2489 q.push(repo, reapply)
2490 finally:
2490 finally:
2491 q.save_dirty()
2491 q.save_dirty()
2492
2492
2493 def finish(ui, repo, *revrange, **opts):
2493 def finish(ui, repo, *revrange, **opts):
2494 """move applied patches into repository history
2494 """move applied patches into repository history
2495
2495
2496 Finishes the specified revisions (corresponding to applied
2496 Finishes the specified revisions (corresponding to applied
2497 patches) by moving them out of mq control into regular repository
2497 patches) by moving them out of mq control into regular repository
2498 history.
2498 history.
2499
2499
2500 Accepts a revision range or the -a/--applied option. If --applied
2500 Accepts a revision range or the -a/--applied option. If --applied
2501 is specified, all applied mq revisions are removed from mq
2501 is specified, all applied mq revisions are removed from mq
2502 control. Otherwise, the given revisions must be at the base of the
2502 control. Otherwise, the given revisions must be at the base of the
2503 stack of applied patches.
2503 stack of applied patches.
2504
2504
2505 This can be especially useful if your changes have been applied to
2505 This can be especially useful if your changes have been applied to
2506 an upstream repository, or if you are about to push your changes
2506 an upstream repository, or if you are about to push your changes
2507 to upstream.
2507 to upstream.
2508 """
2508 """
2509 if not opts['applied'] and not revrange:
2509 if not opts['applied'] and not revrange:
2510 raise util.Abort(_('no revisions specified'))
2510 raise util.Abort(_('no revisions specified'))
2511 elif opts['applied']:
2511 elif opts['applied']:
2512 revrange = ('qbase:qtip',) + revrange
2512 revrange = ('qbase:qtip',) + revrange
2513
2513
2514 q = repo.mq
2514 q = repo.mq
2515 if not q.applied:
2515 if not q.applied:
2516 ui.status(_('no patches applied\n'))
2516 ui.status(_('no patches applied\n'))
2517 return 0
2517 return 0
2518
2518
2519 revs = cmdutil.revrange(repo, revrange)
2519 revs = cmdutil.revrange(repo, revrange)
2520 q.finish(repo, revs)
2520 q.finish(repo, revs)
2521 q.save_dirty()
2521 q.save_dirty()
2522 return 0
2522 return 0
2523
2523
2524 def reposetup(ui, repo):
2524 def reposetup(ui, repo):
2525 class mqrepo(repo.__class__):
2525 class mqrepo(repo.__class__):
2526 @util.propertycache
2526 @util.propertycache
2527 def mq(self):
2527 def mq(self):
2528 return queue(self.ui, self.join(""))
2528 return queue(self.ui, self.join(""))
2529
2529
2530 def abort_if_wdir_patched(self, errmsg, force=False):
2530 def abort_if_wdir_patched(self, errmsg, force=False):
2531 if self.mq.applied and not force:
2531 if self.mq.applied and not force:
2532 parent = self.dirstate.parents()[0]
2532 parent = self.dirstate.parents()[0]
2533 if parent in [s.node for s in self.mq.applied]:
2533 if parent in [s.node for s in self.mq.applied]:
2534 raise util.Abort(errmsg)
2534 raise util.Abort(errmsg)
2535
2535
2536 def commit(self, text="", user=None, date=None, match=None,
2536 def commit(self, text="", user=None, date=None, match=None,
2537 force=False, editor=False, extra={}):
2537 force=False, editor=False, extra={}):
2538 self.abort_if_wdir_patched(
2538 self.abort_if_wdir_patched(
2539 _('cannot commit over an applied mq patch'),
2539 _('cannot commit over an applied mq patch'),
2540 force)
2540 force)
2541
2541
2542 return super(mqrepo, self).commit(text, user, date, match, force,
2542 return super(mqrepo, self).commit(text, user, date, match, force,
2543 editor, extra)
2543 editor, extra)
2544
2544
2545 def push(self, remote, force=False, revs=None):
2545 def push(self, remote, force=False, revs=None):
2546 if self.mq.applied and not force and not revs:
2546 if self.mq.applied and not force and not revs:
2547 raise util.Abort(_('source has mq patches applied'))
2547 raise util.Abort(_('source has mq patches applied'))
2548 return super(mqrepo, self).push(remote, force, revs)
2548 return super(mqrepo, self).push(remote, force, revs)
2549
2549
2550 def _findtags(self):
2550 def _findtags(self):
2551 '''augment tags from base class with patch tags'''
2551 '''augment tags from base class with patch tags'''
2552 result = super(mqrepo, self)._findtags()
2552 result = super(mqrepo, self)._findtags()
2553
2553
2554 q = self.mq
2554 q = self.mq
2555 if not q.applied:
2555 if not q.applied:
2556 return result
2556 return result
2557
2557
2558 mqtags = [(patch.node, patch.name) for patch in q.applied]
2558 mqtags = [(patch.node, patch.name) for patch in q.applied]
2559
2559
2560 if mqtags[-1][0] not in self.changelog.nodemap:
2560 if mqtags[-1][0] not in self.changelog.nodemap:
2561 self.ui.warn(_('mq status file refers to unknown node %s\n')
2561 self.ui.warn(_('mq status file refers to unknown node %s\n')
2562 % short(mqtags[-1][0]))
2562 % short(mqtags[-1][0]))
2563 return result
2563 return result
2564
2564
2565 mqtags.append((mqtags[-1][0], 'qtip'))
2565 mqtags.append((mqtags[-1][0], 'qtip'))
2566 mqtags.append((mqtags[0][0], 'qbase'))
2566 mqtags.append((mqtags[0][0], 'qbase'))
2567 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2567 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2568 tags = result[0]
2568 tags = result[0]
2569 for patch in mqtags:
2569 for patch in mqtags:
2570 if patch[1] in tags:
2570 if patch[1] in tags:
2571 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2571 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2572 % patch[1])
2572 % patch[1])
2573 else:
2573 else:
2574 tags[patch[1]] = patch[0]
2574 tags[patch[1]] = patch[0]
2575
2575
2576 return result
2576 return result
2577
2577
2578 def _branchtags(self, partial, lrev):
2578 def _branchtags(self, partial, lrev):
2579 q = self.mq
2579 q = self.mq
2580 if not q.applied:
2580 if not q.applied:
2581 return super(mqrepo, self)._branchtags(partial, lrev)
2581 return super(mqrepo, self)._branchtags(partial, lrev)
2582
2582
2583 cl = self.changelog
2583 cl = self.changelog
2584 qbasenode = q.applied[0].node
2584 qbasenode = q.applied[0].node
2585 if qbasenode not in cl.nodemap:
2585 if qbasenode not in cl.nodemap:
2586 self.ui.warn(_('mq status file refers to unknown node %s\n')
2586 self.ui.warn(_('mq status file refers to unknown node %s\n')
2587 % short(qbasenode))
2587 % short(qbasenode))
2588 return super(mqrepo, self)._branchtags(partial, lrev)
2588 return super(mqrepo, self)._branchtags(partial, lrev)
2589
2589
2590 qbase = cl.rev(qbasenode)
2590 qbase = cl.rev(qbasenode)
2591 start = lrev + 1
2591 start = lrev + 1
2592 if start < qbase:
2592 if start < qbase:
2593 # update the cache (excluding the patches) and save it
2593 # update the cache (excluding the patches) and save it
2594 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2594 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2595 self._updatebranchcache(partial, ctxgen)
2595 self._updatebranchcache(partial, ctxgen)
2596 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2596 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2597 start = qbase
2597 start = qbase
2598 # if start = qbase, the cache is as updated as it should be.
2598 # if start = qbase, the cache is as updated as it should be.
2599 # if start > qbase, the cache includes (part of) the patches.
2599 # if start > qbase, the cache includes (part of) the patches.
2600 # we might as well use it, but we won't save it.
2600 # we might as well use it, but we won't save it.
2601
2601
2602 # update the cache up to the tip
2602 # update the cache up to the tip
2603 ctxgen = (self[r] for r in xrange(start, len(cl)))
2603 ctxgen = (self[r] for r in xrange(start, len(cl)))
2604 self._updatebranchcache(partial, ctxgen)
2604 self._updatebranchcache(partial, ctxgen)
2605
2605
2606 return partial
2606 return partial
2607
2607
2608 if repo.local():
2608 if repo.local():
2609 repo.__class__ = mqrepo
2609 repo.__class__ = mqrepo
2610
2610
2611 def mqimport(orig, ui, repo, *args, **kwargs):
2611 def mqimport(orig, ui, repo, *args, **kwargs):
2612 if (hasattr(repo, 'abort_if_wdir_patched')
2612 if (hasattr(repo, 'abort_if_wdir_patched')
2613 and not kwargs.get('no_commit', False)):
2613 and not kwargs.get('no_commit', False)):
2614 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2614 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2615 kwargs.get('force'))
2615 kwargs.get('force'))
2616 return orig(ui, repo, *args, **kwargs)
2616 return orig(ui, repo, *args, **kwargs)
2617
2617
2618 def mqinit(orig, ui, *args, **kwargs):
2618 def mqinit(orig, ui, *args, **kwargs):
2619 mq = kwargs.pop('mq', None)
2619 mq = kwargs.pop('mq', None)
2620
2620
2621 if not mq:
2621 if not mq:
2622 return orig(ui, *args, **kwargs)
2622 return orig(ui, *args, **kwargs)
2623
2623
2624 if args:
2624 if args:
2625 repopath = args[0]
2625 repopath = args[0]
2626 if not hg.islocal(repopath):
2626 if not hg.islocal(repopath):
2627 raise util.Abort(_('only a local queue repository '
2627 raise util.Abort(_('only a local queue repository '
2628 'may be initialized'))
2628 'may be initialized'))
2629 else:
2629 else:
2630 repopath = cmdutil.findrepo(os.getcwd())
2630 repopath = cmdutil.findrepo(os.getcwd())
2631 if not repopath:
2631 if not repopath:
2632 raise util.Abort(_('There is no Mercurial repository here '
2632 raise util.Abort(_('There is no Mercurial repository here '
2633 '(.hg not found)'))
2633 '(.hg not found)'))
2634 repo = hg.repository(ui, repopath)
2634 repo = hg.repository(ui, repopath)
2635 return qinit(ui, repo, True)
2635 return qinit(ui, repo, True)
2636
2636
2637 def mqcommand(orig, ui, repo, *args, **kwargs):
2637 def mqcommand(orig, ui, repo, *args, **kwargs):
2638 """Add --mq option to operate on patch repository instead of main"""
2638 """Add --mq option to operate on patch repository instead of main"""
2639
2639
2640 # some commands do not like getting unknown options
2640 # some commands do not like getting unknown options
2641 mq = kwargs.pop('mq', None)
2641 mq = kwargs.pop('mq', None)
2642
2642
2643 if not mq:
2643 if not mq:
2644 return orig(ui, repo, *args, **kwargs)
2644 return orig(ui, repo, *args, **kwargs)
2645
2645
2646 q = repo.mq
2646 q = repo.mq
2647 r = q.qrepo()
2647 r = q.qrepo()
2648 if not r:
2648 if not r:
2649 raise util.Abort('no queue repository')
2649 raise util.Abort('no queue repository')
2650 return orig(r.ui, r, *args, **kwargs)
2650 return orig(r.ui, r, *args, **kwargs)
2651
2651
2652 def uisetup(ui):
2652 def uisetup(ui):
2653 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2653 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2654
2654
2655 extensions.wrapcommand(commands.table, 'import', mqimport)
2655 extensions.wrapcommand(commands.table, 'import', mqimport)
2656
2656
2657 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2657 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2658 entry[1].extend(mqopt)
2658 entry[1].extend(mqopt)
2659
2659
2660 norepo = commands.norepo.split(" ")
2660 norepo = commands.norepo.split(" ")
2661 for cmd in commands.table.keys():
2661 for cmd in commands.table.keys():
2662 cmd = cmdutil.parsealiases(cmd)[0]
2662 cmd = cmdutil.parsealiases(cmd)[0]
2663 if cmd in norepo:
2663 if cmd in norepo:
2664 continue
2664 continue
2665 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2665 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2666 entry[1].extend(mqopt)
2666 entry[1].extend(mqopt)
2667
2667
2668 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2668 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2669
2669
2670 cmdtable = {
2670 cmdtable = {
2671 "qapplied":
2671 "qapplied":
2672 (applied,
2672 (applied,
2673 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2673 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2674 _('hg qapplied [-1] [-s] [PATCH]')),
2674 _('hg qapplied [-1] [-s] [PATCH]')),
2675 "qclone":
2675 "qclone":
2676 (clone,
2676 (clone,
2677 [('', 'pull', None, _('use pull protocol to copy metadata')),
2677 [('', 'pull', None, _('use pull protocol to copy metadata')),
2678 ('U', 'noupdate', None, _('do not update the new working directories')),
2678 ('U', 'noupdate', None, _('do not update the new working directories')),
2679 ('', 'uncompressed', None,
2679 ('', 'uncompressed', None,
2680 _('use uncompressed transfer (fast over LAN)')),
2680 _('use uncompressed transfer (fast over LAN)')),
2681 ('p', 'patches', '', _('location of source patch repository')),
2681 ('p', 'patches', '', _('location of source patch repository')),
2682 ] + commands.remoteopts,
2682 ] + commands.remoteopts,
2683 _('hg qclone [OPTION]... SOURCE [DEST]')),
2683 _('hg qclone [OPTION]... SOURCE [DEST]')),
2684 "qcommit|qci":
2684 "qcommit|qci":
2685 (commit,
2685 (commit,
2686 commands.table["^commit|ci"][1],
2686 commands.table["^commit|ci"][1],
2687 _('hg qcommit [OPTION]... [FILE]...')),
2687 _('hg qcommit [OPTION]... [FILE]...')),
2688 "^qdiff":
2688 "^qdiff":
2689 (diff,
2689 (diff,
2690 commands.diffopts + commands.diffopts2 + commands.walkopts,
2690 commands.diffopts + commands.diffopts2 + commands.walkopts,
2691 _('hg qdiff [OPTION]... [FILE]...')),
2691 _('hg qdiff [OPTION]... [FILE]...')),
2692 "qdelete|qremove|qrm":
2692 "qdelete|qremove|qrm":
2693 (delete,
2693 (delete,
2694 [('k', 'keep', None, _('keep patch file')),
2694 [('k', 'keep', None, _('keep patch file')),
2695 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2695 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2696 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2696 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2697 'qfold':
2697 'qfold':
2698 (fold,
2698 (fold,
2699 [('e', 'edit', None, _('edit patch header')),
2699 [('e', 'edit', None, _('edit patch header')),
2700 ('k', 'keep', None, _('keep folded patch files')),
2700 ('k', 'keep', None, _('keep folded patch files')),
2701 ] + commands.commitopts,
2701 ] + commands.commitopts,
2702 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2702 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2703 'qgoto':
2703 'qgoto':
2704 (goto,
2704 (goto,
2705 [('f', 'force', None, _('overwrite any local changes'))],
2705 [('f', 'force', None, _('overwrite any local changes'))],
2706 _('hg qgoto [OPTION]... PATCH')),
2706 _('hg qgoto [OPTION]... PATCH')),
2707 'qguard':
2707 'qguard':
2708 (guard,
2708 (guard,
2709 [('l', 'list', None, _('list all patches and guards')),
2709 [('l', 'list', None, _('list all patches and guards')),
2710 ('n', 'none', None, _('drop all guards'))],
2710 ('n', 'none', None, _('drop all guards'))],
2711 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2711 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2712 'qheader': (header, [], _('hg qheader [PATCH]')),
2712 'qheader': (header, [], _('hg qheader [PATCH]')),
2713 "^qimport":
2713 "qimport":
2714 (qimport,
2714 (qimport,
2715 [('e', 'existing', None, _('import file in patch directory')),
2715 [('e', 'existing', None, _('import file in patch directory')),
2716 ('n', 'name', '', _('name of patch file')),
2716 ('n', 'name', '', _('name of patch file')),
2717 ('f', 'force', None, _('overwrite existing files')),
2717 ('f', 'force', None, _('overwrite existing files')),
2718 ('r', 'rev', [], _('place existing revisions under mq control')),
2718 ('r', 'rev', [], _('place existing revisions under mq control')),
2719 ('g', 'git', None, _('use git extended diff format')),
2719 ('g', 'git', None, _('use git extended diff format')),
2720 ('P', 'push', None, _('qpush after importing'))],
2720 ('P', 'push', None, _('qpush after importing'))],
2721 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2721 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2722 "^qinit":
2722 "^qinit":
2723 (init,
2723 (init,
2724 [('c', 'create-repo', None, _('create queue repository'))],
2724 [('c', 'create-repo', None, _('create queue repository'))],
2725 _('hg qinit [-c]')),
2725 _('hg qinit [-c]')),
2726 "qnew":
2726 "^qnew":
2727 (new,
2727 (new,
2728 [('e', 'edit', None, _('edit commit message')),
2728 [('e', 'edit', None, _('edit commit message')),
2729 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2729 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2730 ('g', 'git', None, _('use git extended diff format')),
2730 ('g', 'git', None, _('use git extended diff format')),
2731 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2731 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2732 ('u', 'user', '', _('add "From: <given user>" to patch')),
2732 ('u', 'user', '', _('add "From: <given user>" to patch')),
2733 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2733 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2734 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2734 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2735 ] + commands.walkopts + commands.commitopts,
2735 ] + commands.walkopts + commands.commitopts,
2736 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2736 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2737 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2737 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2738 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2738 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2739 "^qpop":
2739 "^qpop":
2740 (pop,
2740 (pop,
2741 [('a', 'all', None, _('pop all patches')),
2741 [('a', 'all', None, _('pop all patches')),
2742 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2742 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2743 ('f', 'force', None, _('forget any local changes to patched files'))],
2743 ('f', 'force', None, _('forget any local changes to patched files'))],
2744 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2744 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2745 "^qpush":
2745 "^qpush":
2746 (push,
2746 (push,
2747 [('f', 'force', None, _('apply if the patch has rejects')),
2747 [('f', 'force', None, _('apply if the patch has rejects')),
2748 ('l', 'list', None, _('list patch name in commit text')),
2748 ('l', 'list', None, _('list patch name in commit text')),
2749 ('a', 'all', None, _('apply all patches')),
2749 ('a', 'all', None, _('apply all patches')),
2750 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2750 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2751 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2751 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2752 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2752 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2753 "^qrefresh":
2753 "^qrefresh":
2754 (refresh,
2754 (refresh,
2755 [('e', 'edit', None, _('edit commit message')),
2755 [('e', 'edit', None, _('edit commit message')),
2756 ('g', 'git', None, _('use git extended diff format')),
2756 ('g', 'git', None, _('use git extended diff format')),
2757 ('s', 'short', None,
2757 ('s', 'short', None,
2758 _('refresh only files already in the patch and specified files')),
2758 _('refresh only files already in the patch and specified files')),
2759 ('U', 'currentuser', None,
2759 ('U', 'currentuser', None,
2760 _('add/update author field in patch with current user')),
2760 _('add/update author field in patch with current user')),
2761 ('u', 'user', '',
2761 ('u', 'user', '',
2762 _('add/update author field in patch with given user')),
2762 _('add/update author field in patch with given user')),
2763 ('D', 'currentdate', None,
2763 ('D', 'currentdate', None,
2764 _('add/update date field in patch with current date')),
2764 _('add/update date field in patch with current date')),
2765 ('d', 'date', '',
2765 ('d', 'date', '',
2766 _('add/update date field in patch with given date'))
2766 _('add/update date field in patch with given date'))
2767 ] + commands.walkopts + commands.commitopts,
2767 ] + commands.walkopts + commands.commitopts,
2768 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2768 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2769 'qrename|qmv':
2769 'qrename|qmv':
2770 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2770 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2771 "qrestore":
2771 "qrestore":
2772 (restore,
2772 (restore,
2773 [('d', 'delete', None, _('delete save entry')),
2773 [('d', 'delete', None, _('delete save entry')),
2774 ('u', 'update', None, _('update queue working directory'))],
2774 ('u', 'update', None, _('update queue working directory'))],
2775 _('hg qrestore [-d] [-u] REV')),
2775 _('hg qrestore [-d] [-u] REV')),
2776 "qsave":
2776 "qsave":
2777 (save,
2777 (save,
2778 [('c', 'copy', None, _('copy patch directory')),
2778 [('c', 'copy', None, _('copy patch directory')),
2779 ('n', 'name', '', _('copy directory name')),
2779 ('n', 'name', '', _('copy directory name')),
2780 ('e', 'empty', None, _('clear queue status file')),
2780 ('e', 'empty', None, _('clear queue status file')),
2781 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2781 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2782 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2782 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2783 "qselect":
2783 "qselect":
2784 (select,
2784 (select,
2785 [('n', 'none', None, _('disable all guards')),
2785 [('n', 'none', None, _('disable all guards')),
2786 ('s', 'series', None, _('list all guards in series file')),
2786 ('s', 'series', None, _('list all guards in series file')),
2787 ('', 'pop', None, _('pop to before first guarded applied patch')),
2787 ('', 'pop', None, _('pop to before first guarded applied patch')),
2788 ('', 'reapply', None, _('pop, then reapply patches'))],
2788 ('', 'reapply', None, _('pop, then reapply patches'))],
2789 _('hg qselect [OPTION]... [GUARD]...')),
2789 _('hg qselect [OPTION]... [GUARD]...')),
2790 "qseries":
2790 "qseries":
2791 (series,
2791 (series,
2792 [('m', 'missing', None, _('print patches not in series')),
2792 [('m', 'missing', None, _('print patches not in series')),
2793 ] + seriesopts,
2793 ] + seriesopts,
2794 _('hg qseries [-ms]')),
2794 _('hg qseries [-ms]')),
2795 "^strip":
2795 "strip":
2796 (strip,
2796 (strip,
2797 [('f', 'force', None, _('force removal with local changes')),
2797 [('f', 'force', None, _('force removal with local changes')),
2798 ('b', 'backup', None, _('bundle unrelated changesets')),
2798 ('b', 'backup', None, _('bundle unrelated changesets')),
2799 ('n', 'nobackup', None, _('no backups'))],
2799 ('n', 'nobackup', None, _('no backups'))],
2800 _('hg strip [-f] [-b] [-n] REV')),
2800 _('hg strip [-f] [-b] [-n] REV')),
2801 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2801 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2802 "qunapplied":
2802 "qunapplied":
2803 (unapplied,
2803 (unapplied,
2804 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2804 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2805 _('hg qunapplied [-1] [-s] [PATCH]')),
2805 _('hg qunapplied [-1] [-s] [PATCH]')),
2806 "qfinish":
2806 "qfinish":
2807 (finish,
2807 (finish,
2808 [('a', 'applied', None, _('finish all applied changesets'))],
2808 [('a', 'applied', None, _('finish all applied changesets'))],
2809 _('hg qfinish [-a] [REV]...')),
2809 _('hg qfinish [-a] [REV]...')),
2810 }
2810 }
2811
2811
2812 colortable = {'qguard.negative': 'red',
2812 colortable = {'qguard.negative': 'red',
2813 'qguard.positive': 'yellow',
2813 'qguard.positive': 'yellow',
2814 'qguard.unguarded': 'green',
2814 'qguard.unguarded': 'green',
2815 'qseries.applied': 'blue bold underline',
2815 'qseries.applied': 'blue bold underline',
2816 'qseries.guarded': 'black bold',
2816 'qseries.guarded': 'black bold',
2817 'qseries.missing': 'red bold',
2817 'qseries.missing': 'red bold',
2818 'qseries.unapplied': 'black bold'}
2818 'qseries.unapplied': 'black bold'}
@@ -1,564 +1,564 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, operator, os, re, tempfile
13 import copy, cStringIO, errno, operator, 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([h.added + h.removed for h in self.hunks])))
100 sum([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, (fromstart, fromend, tostart, toend, proc)):
189 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
190 self.fromline = int(fromstart)
190 self.fromline = int(fromstart)
191 self.toline = int(tostart)
191 self.toline = int(tostart)
192 self.proc = proc
192 self.proc = proc
193
193
194 def addcontext(self, context):
194 def addcontext(self, context):
195 if self.hunk:
195 if self.hunk:
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
197 self.before, self.hunk, context)
197 self.before, self.hunk, context)
198 self.header.hunks.append(h)
198 self.header.hunks.append(h)
199 self.stream.append(h)
199 self.stream.append(h)
200 self.fromline += len(self.before) + h.removed
200 self.fromline += len(self.before) + h.removed
201 self.toline += len(self.before) + h.added
201 self.toline += len(self.before) + h.added
202 self.before = []
202 self.before = []
203 self.hunk = []
203 self.hunk = []
204 self.proc = ''
204 self.proc = ''
205 self.context = context
205 self.context = context
206
206
207 def addhunk(self, hunk):
207 def addhunk(self, hunk):
208 if self.context:
208 if self.context:
209 self.before = self.context
209 self.before = self.context
210 self.context = []
210 self.context = []
211 self.hunk = hunk
211 self.hunk = hunk
212
212
213 def newfile(self, hdr):
213 def newfile(self, hdr):
214 self.addcontext([])
214 self.addcontext([])
215 h = header(hdr)
215 h = header(hdr)
216 self.stream.append(h)
216 self.stream.append(h)
217 self.header = h
217 self.header = h
218
218
219 def finished(self):
219 def finished(self):
220 self.addcontext([])
220 self.addcontext([])
221 return self.stream
221 return self.stream
222
222
223 transitions = {
223 transitions = {
224 'file': {'context': addcontext,
224 'file': {'context': addcontext,
225 'file': newfile,
225 'file': newfile,
226 'hunk': addhunk,
226 'hunk': addhunk,
227 'range': addrange},
227 'range': addrange},
228 'context': {'file': newfile,
228 'context': {'file': newfile,
229 'hunk': addhunk,
229 'hunk': addhunk,
230 'range': addrange},
230 'range': addrange},
231 'hunk': {'context': addcontext,
231 'hunk': {'context': addcontext,
232 'file': newfile,
232 'file': newfile,
233 'range': addrange},
233 'range': addrange},
234 'range': {'context': addcontext,
234 'range': {'context': addcontext,
235 'hunk': addhunk},
235 'hunk': addhunk},
236 }
236 }
237
237
238 p = parser()
238 p = parser()
239
239
240 state = 'context'
240 state = 'context'
241 for newstate, data in scanpatch(fp):
241 for newstate, data in scanpatch(fp):
242 try:
242 try:
243 p.transitions[state][newstate](p, data)
243 p.transitions[state][newstate](p, data)
244 except KeyError:
244 except KeyError:
245 raise patch.PatchError('unhandled transition: %s -> %s' %
245 raise patch.PatchError('unhandled transition: %s -> %s' %
246 (state, newstate))
246 (state, newstate))
247 state = newstate
247 state = newstate
248 return p.finished()
248 return p.finished()
249
249
250 def filterpatch(ui, chunks):
250 def filterpatch(ui, chunks):
251 """Interactively filter patch chunks into applied-only chunks"""
251 """Interactively filter patch chunks into applied-only chunks"""
252 chunks = list(chunks)
252 chunks = list(chunks)
253 chunks.reverse()
253 chunks.reverse()
254 seen = set()
254 seen = set()
255 def consumefile():
255 def consumefile():
256 """fetch next portion from chunks until a 'header' is seen
256 """fetch next portion from chunks until a 'header' is seen
257 NB: header == new-file mark
257 NB: header == new-file mark
258 """
258 """
259 consumed = []
259 consumed = []
260 while chunks:
260 while chunks:
261 if isinstance(chunks[-1], header):
261 if isinstance(chunks[-1], header):
262 break
262 break
263 else:
263 else:
264 consumed.append(chunks.pop())
264 consumed.append(chunks.pop())
265 return consumed
265 return consumed
266
266
267 resp_all = [None] # this two are changed from inside prompt,
267 resp_all = [None] # this two are changed from inside prompt,
268 resp_file = [None] # so can't be usual variables
268 resp_file = [None] # so can't be usual variables
269 applied = {} # 'filename' -> [] of chunks
269 applied = {} # 'filename' -> [] of chunks
270 def prompt(query):
270 def prompt(query):
271 """prompt query, and process base inputs
271 """prompt query, and process base inputs
272
272
273 - y/n for the rest of file
273 - y/n for the rest of file
274 - y/n for the rest
274 - y/n for the rest
275 - ? (help)
275 - ? (help)
276 - q (quit)
276 - q (quit)
277
277
278 Returns True/False and sets reps_all and resp_file as
278 Returns True/False and sets reps_all and resp_file as
279 appropriate.
279 appropriate.
280 """
280 """
281 if resp_all[0] is not None:
281 if resp_all[0] is not None:
282 return resp_all[0]
282 return resp_all[0]
283 if resp_file[0] is not None:
283 if resp_file[0] is not None:
284 return resp_file[0]
284 return resp_file[0]
285 while True:
285 while True:
286 resps = _('[Ynsfdaq?]')
286 resps = _('[Ynsfdaq?]')
287 choices = (_('&Yes, record this change'),
287 choices = (_('&Yes, record this change'),
288 _('&No, skip this change'),
288 _('&No, skip this change'),
289 _('&Skip remaining changes to this file'),
289 _('&Skip remaining changes to this file'),
290 _('Record remaining changes to this &file'),
290 _('Record remaining changes to this &file'),
291 _('&Done, skip remaining changes and files'),
291 _('&Done, skip remaining changes and files'),
292 _('Record &all changes to all remaining files'),
292 _('Record &all changes to all remaining files'),
293 _('&Quit, recording no changes'),
293 _('&Quit, recording no changes'),
294 _('&?'))
294 _('&?'))
295 r = ui.promptchoice("%s %s" % (query, resps), choices)
295 r = ui.promptchoice("%s %s" % (query, resps), choices)
296 ui.write("\n")
296 ui.write("\n")
297 if r == 7: # ?
297 if r == 7: # ?
298 doc = gettext(record.__doc__)
298 doc = gettext(record.__doc__)
299 c = doc.find(_('y - record this change'))
299 c = doc.find(_('y - record this change'))
300 for l in doc[c:].splitlines():
300 for l in doc[c:].splitlines():
301 if l:
301 if l:
302 ui.write(l.strip(), '\n')
302 ui.write(l.strip(), '\n')
303 continue
303 continue
304 elif r == 0: # yes
304 elif r == 0: # yes
305 ret = True
305 ret = True
306 elif r == 1: # no
306 elif r == 1: # no
307 ret = False
307 ret = False
308 elif r == 2: # Skip
308 elif r == 2: # Skip
309 ret = resp_file[0] = False
309 ret = resp_file[0] = False
310 elif r == 3: # file (Record remaining)
310 elif r == 3: # file (Record remaining)
311 ret = resp_file[0] = True
311 ret = resp_file[0] = True
312 elif r == 4: # done, skip remaining
312 elif r == 4: # done, skip remaining
313 ret = resp_all[0] = False
313 ret = resp_all[0] = False
314 elif r == 5: # all
314 elif r == 5: # all
315 ret = resp_all[0] = True
315 ret = resp_all[0] = True
316 elif r == 6: # quit
316 elif r == 6: # quit
317 raise util.Abort(_('user quit'))
317 raise util.Abort(_('user quit'))
318 return ret
318 return ret
319 pos, total = 0, len(chunks) - 1
319 pos, total = 0, len(chunks) - 1
320 while chunks:
320 while chunks:
321 pos = total - len(chunks) + 1
321 pos = total - len(chunks) + 1
322 chunk = chunks.pop()
322 chunk = chunks.pop()
323 if isinstance(chunk, header):
323 if isinstance(chunk, header):
324 # new-file mark
324 # new-file mark
325 resp_file = [None]
325 resp_file = [None]
326 fixoffset = 0
326 fixoffset = 0
327 hdr = ''.join(chunk.header)
327 hdr = ''.join(chunk.header)
328 if hdr in seen:
328 if hdr in seen:
329 consumefile()
329 consumefile()
330 continue
330 continue
331 seen.add(hdr)
331 seen.add(hdr)
332 if resp_all[0] is None:
332 if resp_all[0] is None:
333 chunk.pretty(ui)
333 chunk.pretty(ui)
334 r = prompt(_('examine changes to %s?') %
334 r = prompt(_('examine changes to %s?') %
335 _(' and ').join(map(repr, chunk.files())))
335 _(' and ').join(map(repr, chunk.files())))
336 if r:
336 if r:
337 applied[chunk.filename()] = [chunk]
337 applied[chunk.filename()] = [chunk]
338 if chunk.allhunks():
338 if chunk.allhunks():
339 applied[chunk.filename()] += consumefile()
339 applied[chunk.filename()] += consumefile()
340 else:
340 else:
341 consumefile()
341 consumefile()
342 else:
342 else:
343 # new hunk
343 # new hunk
344 if resp_file[0] is None and resp_all[0] is None:
344 if resp_file[0] is None and resp_all[0] is None:
345 chunk.pretty(ui)
345 chunk.pretty(ui)
346 r = total == 1 and prompt(_('record this change to %r?') %
346 r = total == 1 and prompt(_('record this change to %r?') %
347 chunk.filename()) \
347 chunk.filename()) \
348 or prompt(_('record change %d/%d to %r?') %
348 or prompt(_('record change %d/%d to %r?') %
349 (pos, total, chunk.filename()))
349 (pos, total, chunk.filename()))
350 if r:
350 if r:
351 if fixoffset:
351 if fixoffset:
352 chunk = copy.copy(chunk)
352 chunk = copy.copy(chunk)
353 chunk.toline += fixoffset
353 chunk.toline += fixoffset
354 applied[chunk.filename()].append(chunk)
354 applied[chunk.filename()].append(chunk)
355 else:
355 else:
356 fixoffset += chunk.removed - chunk.added
356 fixoffset += chunk.removed - chunk.added
357 return reduce(operator.add, [h for h in applied.itervalues()
357 return reduce(operator.add, [h for h in applied.itervalues()
358 if h[0].special() or len(h) > 1], [])
358 if h[0].special() or len(h) > 1], [])
359
359
360 def record(ui, repo, *pats, **opts):
360 def record(ui, repo, *pats, **opts):
361 '''interactively select changes to commit
361 '''interactively select changes to commit
362
362
363 If a list of files is omitted, all changes reported by "hg status"
363 If a list of files is omitted, all changes reported by "hg status"
364 will be candidates for recording.
364 will be candidates for recording.
365
365
366 See 'hg help dates' for a list of formats valid for -d/--date.
366 See 'hg help dates' for a list of formats valid for -d/--date.
367
367
368 You will be prompted for whether to record changes to each
368 You will be prompted for whether to record changes to each
369 modified file, and for files with multiple changes, for each
369 modified file, and for files with multiple changes, for each
370 change to use. For each query, the following responses are
370 change to use. For each query, the following responses are
371 possible::
371 possible::
372
372
373 y - record this change
373 y - record this change
374 n - skip this change
374 n - skip this change
375
375
376 s - skip remaining changes to this file
376 s - skip remaining changes to this file
377 f - record remaining changes to this file
377 f - record remaining changes to this file
378
378
379 d - done, skip remaining changes and files
379 d - done, skip remaining changes and files
380 a - record all changes to all remaining files
380 a - record all changes to all remaining files
381 q - quit, recording no changes
381 q - quit, recording no changes
382
382
383 ? - display help'''
383 ? - display help'''
384
384
385 dorecord(ui, repo, commands.commit, *pats, **opts)
385 dorecord(ui, repo, commands.commit, *pats, **opts)
386
386
387
387
388 def qrecord(ui, repo, patch, *pats, **opts):
388 def qrecord(ui, repo, patch, *pats, **opts):
389 '''interactively record a new patch
389 '''interactively record a new patch
390
390
391 See 'hg help qnew' & 'hg help record' for more information and
391 See 'hg help qnew' & 'hg help record' for more information and
392 usage.
392 usage.
393 '''
393 '''
394
394
395 try:
395 try:
396 mq = extensions.find('mq')
396 mq = extensions.find('mq')
397 except KeyError:
397 except KeyError:
398 raise util.Abort(_("'mq' extension not loaded"))
398 raise util.Abort(_("'mq' extension not loaded"))
399
399
400 def committomq(ui, repo, *pats, **opts):
400 def committomq(ui, repo, *pats, **opts):
401 mq.new(ui, repo, patch, *pats, **opts)
401 mq.new(ui, repo, patch, *pats, **opts)
402
402
403 opts = opts.copy()
403 opts = opts.copy()
404 opts['force'] = True # always 'qnew -f'
404 opts['force'] = True # always 'qnew -f'
405 dorecord(ui, repo, committomq, *pats, **opts)
405 dorecord(ui, repo, committomq, *pats, **opts)
406
406
407
407
408 def dorecord(ui, repo, commitfunc, *pats, **opts):
408 def dorecord(ui, repo, commitfunc, *pats, **opts):
409 if not ui.interactive():
409 if not ui.interactive():
410 raise util.Abort(_('running non-interactively, use commit instead'))
410 raise util.Abort(_('running non-interactively, use commit instead'))
411
411
412 def recordfunc(ui, repo, message, match, opts):
412 def recordfunc(ui, repo, message, match, opts):
413 """This is generic record driver.
413 """This is generic record driver.
414
414
415 Its job is to interactively filter local changes, and accordingly
415 Its job is to interactively filter local changes, and accordingly
416 prepare working dir into a state, where the job can be delegated to
416 prepare working dir into a state, where the job can be delegated to
417 non-interactive commit command such as 'commit' or 'qrefresh'.
417 non-interactive commit command such as 'commit' or 'qrefresh'.
418
418
419 After the actual job is done by non-interactive command, working dir
419 After the actual job is done by non-interactive command, working dir
420 state is restored to original.
420 state is restored to original.
421
421
422 In the end we'll record intresting changes, and everything else will be
422 In the end we'll record intresting changes, and everything else will be
423 left in place, so the user can continue his work.
423 left in place, so the user can continue his work.
424 """
424 """
425
425
426 changes = repo.status(match=match)[:3]
426 changes = repo.status(match=match)[:3]
427 diffopts = mdiff.diffopts(git=True, nodates=True)
427 diffopts = mdiff.diffopts(git=True, nodates=True)
428 chunks = patch.diff(repo, changes=changes, opts=diffopts)
428 chunks = patch.diff(repo, changes=changes, opts=diffopts)
429 fp = cStringIO.StringIO()
429 fp = cStringIO.StringIO()
430 fp.write(''.join(chunks))
430 fp.write(''.join(chunks))
431 fp.seek(0)
431 fp.seek(0)
432
432
433 # 1. filter patch, so we have intending-to apply subset of it
433 # 1. filter patch, so we have intending-to apply subset of it
434 chunks = filterpatch(ui, parsepatch(fp))
434 chunks = filterpatch(ui, parsepatch(fp))
435 del fp
435 del fp
436
436
437 contenders = set()
437 contenders = set()
438 for h in chunks:
438 for h in chunks:
439 try:
439 try:
440 contenders.update(set(h.files()))
440 contenders.update(set(h.files()))
441 except AttributeError:
441 except AttributeError:
442 pass
442 pass
443
443
444 changed = changes[0] + changes[1] + changes[2]
444 changed = changes[0] + changes[1] + changes[2]
445 newfiles = [f for f in changed if f in contenders]
445 newfiles = [f for f in changed if f in contenders]
446 if not newfiles:
446 if not newfiles:
447 ui.status(_('no changes to record\n'))
447 ui.status(_('no changes to record\n'))
448 return 0
448 return 0
449
449
450 modified = set(changes[0])
450 modified = set(changes[0])
451
451
452 # 2. backup changed files, so we can restore them in the end
452 # 2. backup changed files, so we can restore them in the end
453 backups = {}
453 backups = {}
454 backupdir = repo.join('record-backups')
454 backupdir = repo.join('record-backups')
455 try:
455 try:
456 os.mkdir(backupdir)
456 os.mkdir(backupdir)
457 except OSError, err:
457 except OSError, err:
458 if err.errno != errno.EEXIST:
458 if err.errno != errno.EEXIST:
459 raise
459 raise
460 try:
460 try:
461 # backup continues
461 # backup continues
462 for f in newfiles:
462 for f in newfiles:
463 if f not in modified:
463 if f not in modified:
464 continue
464 continue
465 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
465 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
466 dir=backupdir)
466 dir=backupdir)
467 os.close(fd)
467 os.close(fd)
468 ui.debug('backup %r as %r\n' % (f, tmpname))
468 ui.debug('backup %r as %r\n' % (f, tmpname))
469 util.copyfile(repo.wjoin(f), tmpname)
469 util.copyfile(repo.wjoin(f), tmpname)
470 backups[f] = tmpname
470 backups[f] = tmpname
471
471
472 fp = cStringIO.StringIO()
472 fp = cStringIO.StringIO()
473 for c in chunks:
473 for c in chunks:
474 if c.filename() in backups:
474 if c.filename() in backups:
475 c.write(fp)
475 c.write(fp)
476 dopatch = fp.tell()
476 dopatch = fp.tell()
477 fp.seek(0)
477 fp.seek(0)
478
478
479 # 3a. apply filtered patch to clean repo (clean)
479 # 3a. apply filtered patch to clean repo (clean)
480 if backups:
480 if backups:
481 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
481 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
482
482
483 # 3b. (apply)
483 # 3b. (apply)
484 if dopatch:
484 if dopatch:
485 try:
485 try:
486 ui.debug('applying patch\n')
486 ui.debug('applying patch\n')
487 ui.debug(fp.getvalue())
487 ui.debug(fp.getvalue())
488 pfiles = {}
488 pfiles = {}
489 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
489 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
490 eolmode=None)
490 eolmode=None)
491 patch.updatedir(ui, repo, pfiles)
491 patch.updatedir(ui, repo, pfiles)
492 except patch.PatchError, err:
492 except patch.PatchError, err:
493 s = str(err)
493 s = str(err)
494 if s:
494 if s:
495 raise util.Abort(s)
495 raise util.Abort(s)
496 else:
496 else:
497 raise util.Abort(_('patch failed to apply'))
497 raise util.Abort(_('patch failed to apply'))
498 del fp
498 del fp
499
499
500 # 4. We prepared working directory according to filtered patch.
500 # 4. We prepared working directory according to filtered patch.
501 # Now is the time to delegate the job to commit/qrefresh or the like!
501 # Now is the time to delegate the job to commit/qrefresh or the like!
502
502
503 # it is important to first chdir to repo root -- we'll call a
503 # it is important to first chdir to repo root -- we'll call a
504 # highlevel command with list of pathnames relative to repo root
504 # highlevel command with list of pathnames relative to repo root
505 cwd = os.getcwd()
505 cwd = os.getcwd()
506 os.chdir(repo.root)
506 os.chdir(repo.root)
507 try:
507 try:
508 commitfunc(ui, repo, *newfiles, **opts)
508 commitfunc(ui, repo, *newfiles, **opts)
509 finally:
509 finally:
510 os.chdir(cwd)
510 os.chdir(cwd)
511
511
512 return 0
512 return 0
513 finally:
513 finally:
514 # 5. finally restore backed-up files
514 # 5. finally restore backed-up files
515 try:
515 try:
516 for realname, tmpname in backups.iteritems():
516 for realname, tmpname in backups.iteritems():
517 ui.debug('restoring %r to %r\n' % (tmpname, realname))
517 ui.debug('restoring %r to %r\n' % (tmpname, realname))
518 util.copyfile(tmpname, repo.wjoin(realname))
518 util.copyfile(tmpname, repo.wjoin(realname))
519 os.unlink(tmpname)
519 os.unlink(tmpname)
520 os.rmdir(backupdir)
520 os.rmdir(backupdir)
521 except OSError:
521 except OSError:
522 pass
522 pass
523
523
524 # wrap ui.write so diff output can be labeled/colorized
524 # wrap ui.write so diff output can be labeled/colorized
525 def wrapwrite(orig, *args, **kw):
525 def wrapwrite(orig, *args, **kw):
526 label = kw.pop('label', '')
526 label = kw.pop('label', '')
527 for chunk, l in patch.difflabel(lambda: args):
527 for chunk, l in patch.difflabel(lambda: args):
528 orig(chunk, label=label + l)
528 orig(chunk, label=label + l)
529 oldwrite = ui.write
529 oldwrite = ui.write
530 extensions.wrapfunction(ui, 'write', wrapwrite)
530 extensions.wrapfunction(ui, 'write', wrapwrite)
531 try:
531 try:
532 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
532 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
533 finally:
533 finally:
534 ui.write = oldwrite
534 ui.write = oldwrite
535
535
536 cmdtable = {
536 cmdtable = {
537 "record":
537 "record":
538 (record,
538 (record,
539
539
540 # add commit options
540 # add commit options
541 commands.table['^commit|ci'][1],
541 commands.table['^commit|ci'][1],
542
542
543 _('hg record [OPTION]... [FILE]...')),
543 _('hg record [OPTION]... [FILE]...')),
544 }
544 }
545
545
546
546
547 def uisetup(ui):
547 def uisetup(ui):
548 try:
548 try:
549 mq = extensions.find('mq')
549 mq = extensions.find('mq')
550 except KeyError:
550 except KeyError:
551 return
551 return
552
552
553 qcmdtable = {
553 qcmdtable = {
554 "qrecord":
554 "qrecord":
555 (qrecord,
555 (qrecord,
556
556
557 # add qnew options, except '--force'
557 # add qnew options, except '--force'
558 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
558 [opt for opt in mq.cmdtable['^qnew'][1] if opt[1] != 'force'],
559
559
560 _('hg qrecord [OPTION]... PATCH [FILE]...')),
560 _('hg qrecord [OPTION]... PATCH [FILE]...')),
561 }
561 }
562
562
563 cmdtable.update(qcmdtable)
563 cmdtable.update(qcmdtable)
564
564
General Comments 0
You need to be logged in to leave comments. Login now