##// END OF EJS Templates
patch: add lexists() to backends, use it in selectfile()...
Patrick Mezard -
r14351:d54f9bbc default
parent child Browse files
Show More
@@ -1,3307 +1,3307 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, scmutil, util, revset
48 from mercurial import commands, cmdutil, hg, scmutil, util, revset
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 from mercurial import patch as patchmod
50 from mercurial import patch as patchmod
51 import os, sys, re, errno, shutil
51 import os, sys, re, errno, shutil
52
52
53 commands.norepo += " qclone"
53 commands.norepo += " qclone"
54
54
55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
56
56
57 cmdtable = {}
57 cmdtable = {}
58 command = cmdutil.command(cmdtable)
58 command = cmdutil.command(cmdtable)
59
59
60 # Patch names looks like unix-file names.
60 # Patch names looks like unix-file names.
61 # They must be joinable with queue directory and result in the patch path.
61 # They must be joinable with queue directory and result in the patch path.
62 normname = util.normpath
62 normname = util.normpath
63
63
64 class statusentry(object):
64 class statusentry(object):
65 def __init__(self, node, name):
65 def __init__(self, node, name):
66 self.node, self.name = node, name
66 self.node, self.name = node, name
67 def __repr__(self):
67 def __repr__(self):
68 return hex(self.node) + ':' + self.name
68 return hex(self.node) + ':' + self.name
69
69
70 class patchheader(object):
70 class patchheader(object):
71 def __init__(self, pf, plainmode=False):
71 def __init__(self, pf, plainmode=False):
72 def eatdiff(lines):
72 def eatdiff(lines):
73 while lines:
73 while lines:
74 l = lines[-1]
74 l = lines[-1]
75 if (l.startswith("diff -") or
75 if (l.startswith("diff -") or
76 l.startswith("Index:") or
76 l.startswith("Index:") or
77 l.startswith("===========")):
77 l.startswith("===========")):
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81 def eatempty(lines):
81 def eatempty(lines):
82 while lines:
82 while lines:
83 if not lines[-1].strip():
83 if not lines[-1].strip():
84 del lines[-1]
84 del lines[-1]
85 else:
85 else:
86 break
86 break
87
87
88 message = []
88 message = []
89 comments = []
89 comments = []
90 user = None
90 user = None
91 date = None
91 date = None
92 parent = None
92 parent = None
93 format = None
93 format = None
94 subject = None
94 subject = None
95 branch = None
95 branch = None
96 nodeid = None
96 nodeid = None
97 diffstart = 0
97 diffstart = 0
98
98
99 for line in file(pf):
99 for line in file(pf):
100 line = line.rstrip()
100 line = line.rstrip()
101 if (line.startswith('diff --git')
101 if (line.startswith('diff --git')
102 or (diffstart and line.startswith('+++ '))):
102 or (diffstart and line.startswith('+++ '))):
103 diffstart = 2
103 diffstart = 2
104 break
104 break
105 diffstart = 0 # reset
105 diffstart = 0 # reset
106 if line.startswith("--- "):
106 if line.startswith("--- "):
107 diffstart = 1
107 diffstart = 1
108 continue
108 continue
109 elif format == "hgpatch":
109 elif format == "hgpatch":
110 # parse values when importing the result of an hg export
110 # parse values when importing the result of an hg export
111 if line.startswith("# User "):
111 if line.startswith("# User "):
112 user = line[7:]
112 user = line[7:]
113 elif line.startswith("# Date "):
113 elif line.startswith("# Date "):
114 date = line[7:]
114 date = line[7:]
115 elif line.startswith("# Parent "):
115 elif line.startswith("# Parent "):
116 parent = line[9:].lstrip()
116 parent = line[9:].lstrip()
117 elif line.startswith("# Branch "):
117 elif line.startswith("# Branch "):
118 branch = line[9:]
118 branch = line[9:]
119 elif line.startswith("# Node ID "):
119 elif line.startswith("# Node ID "):
120 nodeid = line[10:]
120 nodeid = line[10:]
121 elif not line.startswith("# ") and line:
121 elif not line.startswith("# ") and line:
122 message.append(line)
122 message.append(line)
123 format = None
123 format = None
124 elif line == '# HG changeset patch':
124 elif line == '# HG changeset patch':
125 message = []
125 message = []
126 format = "hgpatch"
126 format = "hgpatch"
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
128 line.startswith("subject: "))):
128 line.startswith("subject: "))):
129 subject = line[9:]
129 subject = line[9:]
130 format = "tag"
130 format = "tag"
131 elif (format != "tagdone" and (line.startswith("From: ") or
131 elif (format != "tagdone" and (line.startswith("From: ") or
132 line.startswith("from: "))):
132 line.startswith("from: "))):
133 user = line[6:]
133 user = line[6:]
134 format = "tag"
134 format = "tag"
135 elif (format != "tagdone" and (line.startswith("Date: ") or
135 elif (format != "tagdone" and (line.startswith("Date: ") or
136 line.startswith("date: "))):
136 line.startswith("date: "))):
137 date = line[6:]
137 date = line[6:]
138 format = "tag"
138 format = "tag"
139 elif format == "tag" and line == "":
139 elif format == "tag" and line == "":
140 # when looking for tags (subject: from: etc) they
140 # when looking for tags (subject: from: etc) they
141 # end once you find a blank line in the source
141 # end once you find a blank line in the source
142 format = "tagdone"
142 format = "tagdone"
143 elif message or line:
143 elif message or line:
144 message.append(line)
144 message.append(line)
145 comments.append(line)
145 comments.append(line)
146
146
147 eatdiff(message)
147 eatdiff(message)
148 eatdiff(comments)
148 eatdiff(comments)
149 # Remember the exact starting line of the patch diffs before consuming
149 # Remember the exact starting line of the patch diffs before consuming
150 # empty lines, for external use by TortoiseHg and others
150 # empty lines, for external use by TortoiseHg and others
151 self.diffstartline = len(comments)
151 self.diffstartline = len(comments)
152 eatempty(message)
152 eatempty(message)
153 eatempty(comments)
153 eatempty(comments)
154
154
155 # make sure message isn't empty
155 # make sure message isn't empty
156 if format and format.startswith("tag") and subject:
156 if format and format.startswith("tag") and subject:
157 message.insert(0, "")
157 message.insert(0, "")
158 message.insert(0, subject)
158 message.insert(0, subject)
159
159
160 self.message = message
160 self.message = message
161 self.comments = comments
161 self.comments = comments
162 self.user = user
162 self.user = user
163 self.date = date
163 self.date = date
164 self.parent = parent
164 self.parent = parent
165 # nodeid and branch are for external use by TortoiseHg and others
165 # nodeid and branch are for external use by TortoiseHg and others
166 self.nodeid = nodeid
166 self.nodeid = nodeid
167 self.branch = branch
167 self.branch = branch
168 self.haspatch = diffstart > 1
168 self.haspatch = diffstart > 1
169 self.plainmode = plainmode
169 self.plainmode = plainmode
170
170
171 def setuser(self, user):
171 def setuser(self, user):
172 if not self.updateheader(['From: ', '# User '], user):
172 if not self.updateheader(['From: ', '# User '], user):
173 try:
173 try:
174 patchheaderat = self.comments.index('# HG changeset patch')
174 patchheaderat = self.comments.index('# HG changeset patch')
175 self.comments.insert(patchheaderat + 1, '# User ' + user)
175 self.comments.insert(patchheaderat + 1, '# User ' + user)
176 except ValueError:
176 except ValueError:
177 if self.plainmode or self._hasheader(['Date: ']):
177 if self.plainmode or self._hasheader(['Date: ']):
178 self.comments = ['From: ' + user] + self.comments
178 self.comments = ['From: ' + user] + self.comments
179 else:
179 else:
180 tmp = ['# HG changeset patch', '# User ' + user, '']
180 tmp = ['# HG changeset patch', '# User ' + user, '']
181 self.comments = tmp + self.comments
181 self.comments = tmp + self.comments
182 self.user = user
182 self.user = user
183
183
184 def setdate(self, date):
184 def setdate(self, date):
185 if not self.updateheader(['Date: ', '# Date '], date):
185 if not self.updateheader(['Date: ', '# Date '], date):
186 try:
186 try:
187 patchheaderat = self.comments.index('# HG changeset patch')
187 patchheaderat = self.comments.index('# HG changeset patch')
188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
189 except ValueError:
189 except ValueError:
190 if self.plainmode or self._hasheader(['From: ']):
190 if self.plainmode or self._hasheader(['From: ']):
191 self.comments = ['Date: ' + date] + self.comments
191 self.comments = ['Date: ' + date] + self.comments
192 else:
192 else:
193 tmp = ['# HG changeset patch', '# Date ' + date, '']
193 tmp = ['# HG changeset patch', '# Date ' + date, '']
194 self.comments = tmp + self.comments
194 self.comments = tmp + self.comments
195 self.date = date
195 self.date = date
196
196
197 def setparent(self, parent):
197 def setparent(self, parent):
198 if not self.updateheader(['# Parent '], parent):
198 if not self.updateheader(['# Parent '], parent):
199 try:
199 try:
200 patchheaderat = self.comments.index('# HG changeset patch')
200 patchheaderat = self.comments.index('# HG changeset patch')
201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
202 except ValueError:
202 except ValueError:
203 pass
203 pass
204 self.parent = parent
204 self.parent = parent
205
205
206 def setmessage(self, message):
206 def setmessage(self, message):
207 if self.comments:
207 if self.comments:
208 self._delmsg()
208 self._delmsg()
209 self.message = [message]
209 self.message = [message]
210 self.comments += self.message
210 self.comments += self.message
211
211
212 def updateheader(self, prefixes, new):
212 def updateheader(self, prefixes, new):
213 '''Update all references to a field in the patch header.
213 '''Update all references to a field in the patch header.
214 Return whether the field is present.'''
214 Return whether the field is present.'''
215 res = False
215 res = False
216 for prefix in prefixes:
216 for prefix in prefixes:
217 for i in xrange(len(self.comments)):
217 for i in xrange(len(self.comments)):
218 if self.comments[i].startswith(prefix):
218 if self.comments[i].startswith(prefix):
219 self.comments[i] = prefix + new
219 self.comments[i] = prefix + new
220 res = True
220 res = True
221 break
221 break
222 return res
222 return res
223
223
224 def _hasheader(self, prefixes):
224 def _hasheader(self, prefixes):
225 '''Check if a header starts with any of the given prefixes.'''
225 '''Check if a header starts with any of the given prefixes.'''
226 for prefix in prefixes:
226 for prefix in prefixes:
227 for comment in self.comments:
227 for comment in self.comments:
228 if comment.startswith(prefix):
228 if comment.startswith(prefix):
229 return True
229 return True
230 return False
230 return False
231
231
232 def __str__(self):
232 def __str__(self):
233 if not self.comments:
233 if not self.comments:
234 return ''
234 return ''
235 return '\n'.join(self.comments) + '\n\n'
235 return '\n'.join(self.comments) + '\n\n'
236
236
237 def _delmsg(self):
237 def _delmsg(self):
238 '''Remove existing message, keeping the rest of the comments fields.
238 '''Remove existing message, keeping the rest of the comments fields.
239 If comments contains 'subject: ', message will prepend
239 If comments contains 'subject: ', message will prepend
240 the field and a blank line.'''
240 the field and a blank line.'''
241 if self.message:
241 if self.message:
242 subj = 'subject: ' + self.message[0].lower()
242 subj = 'subject: ' + self.message[0].lower()
243 for i in xrange(len(self.comments)):
243 for i in xrange(len(self.comments)):
244 if subj == self.comments[i].lower():
244 if subj == self.comments[i].lower():
245 del self.comments[i]
245 del self.comments[i]
246 self.message = self.message[2:]
246 self.message = self.message[2:]
247 break
247 break
248 ci = 0
248 ci = 0
249 for mi in self.message:
249 for mi in self.message:
250 while mi != self.comments[ci]:
250 while mi != self.comments[ci]:
251 ci += 1
251 ci += 1
252 del self.comments[ci]
252 del self.comments[ci]
253
253
254 class queue(object):
254 class queue(object):
255 def __init__(self, ui, path, patchdir=None):
255 def __init__(self, ui, path, patchdir=None):
256 self.basepath = path
256 self.basepath = path
257 try:
257 try:
258 fh = open(os.path.join(path, 'patches.queue'))
258 fh = open(os.path.join(path, 'patches.queue'))
259 cur = fh.read().rstrip()
259 cur = fh.read().rstrip()
260 fh.close()
260 fh.close()
261 if not cur:
261 if not cur:
262 curpath = os.path.join(path, 'patches')
262 curpath = os.path.join(path, 'patches')
263 else:
263 else:
264 curpath = os.path.join(path, 'patches-' + cur)
264 curpath = os.path.join(path, 'patches-' + cur)
265 except IOError:
265 except IOError:
266 curpath = os.path.join(path, 'patches')
266 curpath = os.path.join(path, 'patches')
267 self.path = patchdir or curpath
267 self.path = patchdir or curpath
268 self.opener = scmutil.opener(self.path)
268 self.opener = scmutil.opener(self.path)
269 self.ui = ui
269 self.ui = ui
270 self.applied_dirty = 0
270 self.applied_dirty = 0
271 self.series_dirty = 0
271 self.series_dirty = 0
272 self.added = []
272 self.added = []
273 self.series_path = "series"
273 self.series_path = "series"
274 self.status_path = "status"
274 self.status_path = "status"
275 self.guards_path = "guards"
275 self.guards_path = "guards"
276 self.active_guards = None
276 self.active_guards = None
277 self.guards_dirty = False
277 self.guards_dirty = False
278 # Handle mq.git as a bool with extended values
278 # Handle mq.git as a bool with extended values
279 try:
279 try:
280 gitmode = ui.configbool('mq', 'git', None)
280 gitmode = ui.configbool('mq', 'git', None)
281 if gitmode is None:
281 if gitmode is None:
282 raise error.ConfigError()
282 raise error.ConfigError()
283 self.gitmode = gitmode and 'yes' or 'no'
283 self.gitmode = gitmode and 'yes' or 'no'
284 except error.ConfigError:
284 except error.ConfigError:
285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
286 self.plainmode = ui.configbool('mq', 'plain', False)
286 self.plainmode = ui.configbool('mq', 'plain', False)
287
287
288 @util.propertycache
288 @util.propertycache
289 def applied(self):
289 def applied(self):
290 if os.path.exists(self.join(self.status_path)):
290 if os.path.exists(self.join(self.status_path)):
291 def parselines(lines):
291 def parselines(lines):
292 for l in lines:
292 for l in lines:
293 entry = l.split(':', 1)
293 entry = l.split(':', 1)
294 if len(entry) > 1:
294 if len(entry) > 1:
295 n, name = entry
295 n, name = entry
296 yield statusentry(bin(n), name)
296 yield statusentry(bin(n), name)
297 elif l.strip():
297 elif l.strip():
298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
299 # else we ignore empty lines
299 # else we ignore empty lines
300 lines = self.opener.read(self.status_path).splitlines()
300 lines = self.opener.read(self.status_path).splitlines()
301 return list(parselines(lines))
301 return list(parselines(lines))
302 return []
302 return []
303
303
304 @util.propertycache
304 @util.propertycache
305 def full_series(self):
305 def full_series(self):
306 if os.path.exists(self.join(self.series_path)):
306 if os.path.exists(self.join(self.series_path)):
307 return self.opener.read(self.series_path).splitlines()
307 return self.opener.read(self.series_path).splitlines()
308 return []
308 return []
309
309
310 @util.propertycache
310 @util.propertycache
311 def series(self):
311 def series(self):
312 self.parse_series()
312 self.parse_series()
313 return self.series
313 return self.series
314
314
315 @util.propertycache
315 @util.propertycache
316 def series_guards(self):
316 def series_guards(self):
317 self.parse_series()
317 self.parse_series()
318 return self.series_guards
318 return self.series_guards
319
319
320 def invalidate(self):
320 def invalidate(self):
321 for a in 'applied full_series series series_guards'.split():
321 for a in 'applied full_series series series_guards'.split():
322 if a in self.__dict__:
322 if a in self.__dict__:
323 delattr(self, a)
323 delattr(self, a)
324 self.applied_dirty = 0
324 self.applied_dirty = 0
325 self.series_dirty = 0
325 self.series_dirty = 0
326 self.guards_dirty = False
326 self.guards_dirty = False
327 self.active_guards = None
327 self.active_guards = None
328
328
329 def diffopts(self, opts={}, patchfn=None):
329 def diffopts(self, opts={}, patchfn=None):
330 diffopts = patchmod.diffopts(self.ui, opts)
330 diffopts = patchmod.diffopts(self.ui, opts)
331 if self.gitmode == 'auto':
331 if self.gitmode == 'auto':
332 diffopts.upgrade = True
332 diffopts.upgrade = True
333 elif self.gitmode == 'keep':
333 elif self.gitmode == 'keep':
334 pass
334 pass
335 elif self.gitmode in ('yes', 'no'):
335 elif self.gitmode in ('yes', 'no'):
336 diffopts.git = self.gitmode == 'yes'
336 diffopts.git = self.gitmode == 'yes'
337 else:
337 else:
338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
339 ' got %s') % self.gitmode)
339 ' got %s') % self.gitmode)
340 if patchfn:
340 if patchfn:
341 diffopts = self.patchopts(diffopts, patchfn)
341 diffopts = self.patchopts(diffopts, patchfn)
342 return diffopts
342 return diffopts
343
343
344 def patchopts(self, diffopts, *patches):
344 def patchopts(self, diffopts, *patches):
345 """Return a copy of input diff options with git set to true if
345 """Return a copy of input diff options with git set to true if
346 referenced patch is a git patch and should be preserved as such.
346 referenced patch is a git patch and should be preserved as such.
347 """
347 """
348 diffopts = diffopts.copy()
348 diffopts = diffopts.copy()
349 if not diffopts.git and self.gitmode == 'keep':
349 if not diffopts.git and self.gitmode == 'keep':
350 for patchfn in patches:
350 for patchfn in patches:
351 patchf = self.opener(patchfn, 'r')
351 patchf = self.opener(patchfn, 'r')
352 # if the patch was a git patch, refresh it as a git patch
352 # if the patch was a git patch, refresh it as a git patch
353 for line in patchf:
353 for line in patchf:
354 if line.startswith('diff --git'):
354 if line.startswith('diff --git'):
355 diffopts.git = True
355 diffopts.git = True
356 break
356 break
357 patchf.close()
357 patchf.close()
358 return diffopts
358 return diffopts
359
359
360 def join(self, *p):
360 def join(self, *p):
361 return os.path.join(self.path, *p)
361 return os.path.join(self.path, *p)
362
362
363 def find_series(self, patch):
363 def find_series(self, patch):
364 def matchpatch(l):
364 def matchpatch(l):
365 l = l.split('#', 1)[0]
365 l = l.split('#', 1)[0]
366 return l.strip() == patch
366 return l.strip() == patch
367 for index, l in enumerate(self.full_series):
367 for index, l in enumerate(self.full_series):
368 if matchpatch(l):
368 if matchpatch(l):
369 return index
369 return index
370 return None
370 return None
371
371
372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
373
373
374 def parse_series(self):
374 def parse_series(self):
375 self.series = []
375 self.series = []
376 self.series_guards = []
376 self.series_guards = []
377 for l in self.full_series:
377 for l in self.full_series:
378 h = l.find('#')
378 h = l.find('#')
379 if h == -1:
379 if h == -1:
380 patch = l
380 patch = l
381 comment = ''
381 comment = ''
382 elif h == 0:
382 elif h == 0:
383 continue
383 continue
384 else:
384 else:
385 patch = l[:h]
385 patch = l[:h]
386 comment = l[h:]
386 comment = l[h:]
387 patch = patch.strip()
387 patch = patch.strip()
388 if patch:
388 if patch:
389 if patch in self.series:
389 if patch in self.series:
390 raise util.Abort(_('%s appears more than once in %s') %
390 raise util.Abort(_('%s appears more than once in %s') %
391 (patch, self.join(self.series_path)))
391 (patch, self.join(self.series_path)))
392 self.series.append(patch)
392 self.series.append(patch)
393 self.series_guards.append(self.guard_re.findall(comment))
393 self.series_guards.append(self.guard_re.findall(comment))
394
394
395 def check_guard(self, guard):
395 def check_guard(self, guard):
396 if not guard:
396 if not guard:
397 return _('guard cannot be an empty string')
397 return _('guard cannot be an empty string')
398 bad_chars = '# \t\r\n\f'
398 bad_chars = '# \t\r\n\f'
399 first = guard[0]
399 first = guard[0]
400 if first in '-+':
400 if first in '-+':
401 return (_('guard %r starts with invalid character: %r') %
401 return (_('guard %r starts with invalid character: %r') %
402 (guard, first))
402 (guard, first))
403 for c in bad_chars:
403 for c in bad_chars:
404 if c in guard:
404 if c in guard:
405 return _('invalid character in guard %r: %r') % (guard, c)
405 return _('invalid character in guard %r: %r') % (guard, c)
406
406
407 def set_active(self, guards):
407 def set_active(self, guards):
408 for guard in guards:
408 for guard in guards:
409 bad = self.check_guard(guard)
409 bad = self.check_guard(guard)
410 if bad:
410 if bad:
411 raise util.Abort(bad)
411 raise util.Abort(bad)
412 guards = sorted(set(guards))
412 guards = sorted(set(guards))
413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
414 self.active_guards = guards
414 self.active_guards = guards
415 self.guards_dirty = True
415 self.guards_dirty = True
416
416
417 def active(self):
417 def active(self):
418 if self.active_guards is None:
418 if self.active_guards is None:
419 self.active_guards = []
419 self.active_guards = []
420 try:
420 try:
421 guards = self.opener.read(self.guards_path).split()
421 guards = self.opener.read(self.guards_path).split()
422 except IOError, err:
422 except IOError, err:
423 if err.errno != errno.ENOENT:
423 if err.errno != errno.ENOENT:
424 raise
424 raise
425 guards = []
425 guards = []
426 for i, guard in enumerate(guards):
426 for i, guard in enumerate(guards):
427 bad = self.check_guard(guard)
427 bad = self.check_guard(guard)
428 if bad:
428 if bad:
429 self.ui.warn('%s:%d: %s\n' %
429 self.ui.warn('%s:%d: %s\n' %
430 (self.join(self.guards_path), i + 1, bad))
430 (self.join(self.guards_path), i + 1, bad))
431 else:
431 else:
432 self.active_guards.append(guard)
432 self.active_guards.append(guard)
433 return self.active_guards
433 return self.active_guards
434
434
435 def set_guards(self, idx, guards):
435 def set_guards(self, idx, guards):
436 for g in guards:
436 for g in guards:
437 if len(g) < 2:
437 if len(g) < 2:
438 raise util.Abort(_('guard %r too short') % g)
438 raise util.Abort(_('guard %r too short') % g)
439 if g[0] not in '-+':
439 if g[0] not in '-+':
440 raise util.Abort(_('guard %r starts with invalid char') % g)
440 raise util.Abort(_('guard %r starts with invalid char') % g)
441 bad = self.check_guard(g[1:])
441 bad = self.check_guard(g[1:])
442 if bad:
442 if bad:
443 raise util.Abort(bad)
443 raise util.Abort(bad)
444 drop = self.guard_re.sub('', self.full_series[idx])
444 drop = self.guard_re.sub('', self.full_series[idx])
445 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
445 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
446 self.parse_series()
446 self.parse_series()
447 self.series_dirty = True
447 self.series_dirty = True
448
448
449 def pushable(self, idx):
449 def pushable(self, idx):
450 if isinstance(idx, str):
450 if isinstance(idx, str):
451 idx = self.series.index(idx)
451 idx = self.series.index(idx)
452 patchguards = self.series_guards[idx]
452 patchguards = self.series_guards[idx]
453 if not patchguards:
453 if not patchguards:
454 return True, None
454 return True, None
455 guards = self.active()
455 guards = self.active()
456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
457 if exactneg:
457 if exactneg:
458 return False, exactneg[0]
458 return False, exactneg[0]
459 pos = [g for g in patchguards if g[0] == '+']
459 pos = [g for g in patchguards if g[0] == '+']
460 exactpos = [g for g in pos if g[1:] in guards]
460 exactpos = [g for g in pos if g[1:] in guards]
461 if pos:
461 if pos:
462 if exactpos:
462 if exactpos:
463 return True, exactpos[0]
463 return True, exactpos[0]
464 return False, pos
464 return False, pos
465 return True, ''
465 return True, ''
466
466
467 def explain_pushable(self, idx, all_patches=False):
467 def explain_pushable(self, idx, all_patches=False):
468 write = all_patches and self.ui.write or self.ui.warn
468 write = all_patches and self.ui.write or self.ui.warn
469 if all_patches or self.ui.verbose:
469 if all_patches or self.ui.verbose:
470 if isinstance(idx, str):
470 if isinstance(idx, str):
471 idx = self.series.index(idx)
471 idx = self.series.index(idx)
472 pushable, why = self.pushable(idx)
472 pushable, why = self.pushable(idx)
473 if all_patches and pushable:
473 if all_patches and pushable:
474 if why is None:
474 if why is None:
475 write(_('allowing %s - no guards in effect\n') %
475 write(_('allowing %s - no guards in effect\n') %
476 self.series[idx])
476 self.series[idx])
477 else:
477 else:
478 if not why:
478 if not why:
479 write(_('allowing %s - no matching negative guards\n') %
479 write(_('allowing %s - no matching negative guards\n') %
480 self.series[idx])
480 self.series[idx])
481 else:
481 else:
482 write(_('allowing %s - guarded by %r\n') %
482 write(_('allowing %s - guarded by %r\n') %
483 (self.series[idx], why))
483 (self.series[idx], why))
484 if not pushable:
484 if not pushable:
485 if why:
485 if why:
486 write(_('skipping %s - guarded by %r\n') %
486 write(_('skipping %s - guarded by %r\n') %
487 (self.series[idx], why))
487 (self.series[idx], why))
488 else:
488 else:
489 write(_('skipping %s - no matching guards\n') %
489 write(_('skipping %s - no matching guards\n') %
490 self.series[idx])
490 self.series[idx])
491
491
492 def save_dirty(self):
492 def save_dirty(self):
493 def write_list(items, path):
493 def write_list(items, path):
494 fp = self.opener(path, 'w')
494 fp = self.opener(path, 'w')
495 for i in items:
495 for i in items:
496 fp.write("%s\n" % i)
496 fp.write("%s\n" % i)
497 fp.close()
497 fp.close()
498 if self.applied_dirty:
498 if self.applied_dirty:
499 write_list(map(str, self.applied), self.status_path)
499 write_list(map(str, self.applied), self.status_path)
500 if self.series_dirty:
500 if self.series_dirty:
501 write_list(self.full_series, self.series_path)
501 write_list(self.full_series, self.series_path)
502 if self.guards_dirty:
502 if self.guards_dirty:
503 write_list(self.active_guards, self.guards_path)
503 write_list(self.active_guards, self.guards_path)
504 if self.added:
504 if self.added:
505 qrepo = self.qrepo()
505 qrepo = self.qrepo()
506 if qrepo:
506 if qrepo:
507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
508 self.added = []
508 self.added = []
509
509
510 def removeundo(self, repo):
510 def removeundo(self, repo):
511 undo = repo.sjoin('undo')
511 undo = repo.sjoin('undo')
512 if not os.path.exists(undo):
512 if not os.path.exists(undo):
513 return
513 return
514 try:
514 try:
515 os.unlink(undo)
515 os.unlink(undo)
516 except OSError, inst:
516 except OSError, inst:
517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
518
518
519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
520 fp=None, changes=None, opts={}):
520 fp=None, changes=None, opts={}):
521 stat = opts.get('stat')
521 stat = opts.get('stat')
522 m = scmutil.match(repo, files, opts)
522 m = scmutil.match(repo, files, opts)
523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
524 changes, stat, fp)
524 changes, stat, fp)
525
525
526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
527 # first try just applying the patch
527 # first try just applying the patch
528 (err, n) = self.apply(repo, [patch], update_status=False,
528 (err, n) = self.apply(repo, [patch], update_status=False,
529 strict=True, merge=rev)
529 strict=True, merge=rev)
530
530
531 if err == 0:
531 if err == 0:
532 return (err, n)
532 return (err, n)
533
533
534 if n is None:
534 if n is None:
535 raise util.Abort(_("apply failed for patch %s") % patch)
535 raise util.Abort(_("apply failed for patch %s") % patch)
536
536
537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
538
538
539 # apply failed, strip away that rev and merge.
539 # apply failed, strip away that rev and merge.
540 hg.clean(repo, head)
540 hg.clean(repo, head)
541 self.strip(repo, [n], update=False, backup='strip')
541 self.strip(repo, [n], update=False, backup='strip')
542
542
543 ctx = repo[rev]
543 ctx = repo[rev]
544 ret = hg.merge(repo, rev)
544 ret = hg.merge(repo, rev)
545 if ret:
545 if ret:
546 raise util.Abort(_("update returned %d") % ret)
546 raise util.Abort(_("update returned %d") % ret)
547 n = repo.commit(ctx.description(), ctx.user(), force=True)
547 n = repo.commit(ctx.description(), ctx.user(), force=True)
548 if n is None:
548 if n is None:
549 raise util.Abort(_("repo commit failed"))
549 raise util.Abort(_("repo commit failed"))
550 try:
550 try:
551 ph = patchheader(mergeq.join(patch), self.plainmode)
551 ph = patchheader(mergeq.join(patch), self.plainmode)
552 except:
552 except:
553 raise util.Abort(_("unable to read %s") % patch)
553 raise util.Abort(_("unable to read %s") % patch)
554
554
555 diffopts = self.patchopts(diffopts, patch)
555 diffopts = self.patchopts(diffopts, patch)
556 patchf = self.opener(patch, "w")
556 patchf = self.opener(patch, "w")
557 comments = str(ph)
557 comments = str(ph)
558 if comments:
558 if comments:
559 patchf.write(comments)
559 patchf.write(comments)
560 self.printdiff(repo, diffopts, head, n, fp=patchf)
560 self.printdiff(repo, diffopts, head, n, fp=patchf)
561 patchf.close()
561 patchf.close()
562 self.removeundo(repo)
562 self.removeundo(repo)
563 return (0, n)
563 return (0, n)
564
564
565 def qparents(self, repo, rev=None):
565 def qparents(self, repo, rev=None):
566 if rev is None:
566 if rev is None:
567 (p1, p2) = repo.dirstate.parents()
567 (p1, p2) = repo.dirstate.parents()
568 if p2 == nullid:
568 if p2 == nullid:
569 return p1
569 return p1
570 if not self.applied:
570 if not self.applied:
571 return None
571 return None
572 return self.applied[-1].node
572 return self.applied[-1].node
573 p1, p2 = repo.changelog.parents(rev)
573 p1, p2 = repo.changelog.parents(rev)
574 if p2 != nullid and p2 in [x.node for x in self.applied]:
574 if p2 != nullid and p2 in [x.node for x in self.applied]:
575 return p2
575 return p2
576 return p1
576 return p1
577
577
578 def mergepatch(self, repo, mergeq, series, diffopts):
578 def mergepatch(self, repo, mergeq, series, diffopts):
579 if not self.applied:
579 if not self.applied:
580 # each of the patches merged in will have two parents. This
580 # each of the patches merged in will have two parents. This
581 # can confuse the qrefresh, qdiff, and strip code because it
581 # can confuse the qrefresh, qdiff, and strip code because it
582 # needs to know which parent is actually in the patch queue.
582 # needs to know which parent is actually in the patch queue.
583 # so, we insert a merge marker with only one parent. This way
583 # so, we insert a merge marker with only one parent. This way
584 # the first patch in the queue is never a merge patch
584 # the first patch in the queue is never a merge patch
585 #
585 #
586 pname = ".hg.patches.merge.marker"
586 pname = ".hg.patches.merge.marker"
587 n = repo.commit('[mq]: merge marker', force=True)
587 n = repo.commit('[mq]: merge marker', force=True)
588 self.removeundo(repo)
588 self.removeundo(repo)
589 self.applied.append(statusentry(n, pname))
589 self.applied.append(statusentry(n, pname))
590 self.applied_dirty = 1
590 self.applied_dirty = 1
591
591
592 head = self.qparents(repo)
592 head = self.qparents(repo)
593
593
594 for patch in series:
594 for patch in series:
595 patch = mergeq.lookup(patch, strict=True)
595 patch = mergeq.lookup(patch, strict=True)
596 if not patch:
596 if not patch:
597 self.ui.warn(_("patch %s does not exist\n") % patch)
597 self.ui.warn(_("patch %s does not exist\n") % patch)
598 return (1, None)
598 return (1, None)
599 pushable, reason = self.pushable(patch)
599 pushable, reason = self.pushable(patch)
600 if not pushable:
600 if not pushable:
601 self.explain_pushable(patch, all_patches=True)
601 self.explain_pushable(patch, all_patches=True)
602 continue
602 continue
603 info = mergeq.isapplied(patch)
603 info = mergeq.isapplied(patch)
604 if not info:
604 if not info:
605 self.ui.warn(_("patch %s is not applied\n") % patch)
605 self.ui.warn(_("patch %s is not applied\n") % patch)
606 return (1, None)
606 return (1, None)
607 rev = info[1]
607 rev = info[1]
608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
609 if head:
609 if head:
610 self.applied.append(statusentry(head, patch))
610 self.applied.append(statusentry(head, patch))
611 self.applied_dirty = 1
611 self.applied_dirty = 1
612 if err:
612 if err:
613 return (err, head)
613 return (err, head)
614 self.save_dirty()
614 self.save_dirty()
615 return (0, head)
615 return (0, head)
616
616
617 def patch(self, repo, patchfile):
617 def patch(self, repo, patchfile):
618 '''Apply patchfile to the working directory.
618 '''Apply patchfile to the working directory.
619 patchfile: name of patch file'''
619 patchfile: name of patch file'''
620 files = {}
620 files = {}
621 try:
621 try:
622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
623 cwd=repo.root, files=files, eolmode=None)
623 cwd=repo.root, files=files, eolmode=None)
624 return (True, list(files), fuzz)
624 return (True, list(files), fuzz)
625 except Exception, inst:
625 except Exception, inst:
626 self.ui.note(str(inst) + '\n')
626 self.ui.note(str(inst) + '\n')
627 if not self.ui.verbose:
627 if not self.ui.verbose:
628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
629 return (False, list(files), False)
629 return (False, list(files), False)
630
630
631 def apply(self, repo, series, list=False, update_status=True,
631 def apply(self, repo, series, list=False, update_status=True,
632 strict=False, patchdir=None, merge=None, all_files=None):
632 strict=False, patchdir=None, merge=None, all_files=None):
633 wlock = lock = tr = None
633 wlock = lock = tr = None
634 try:
634 try:
635 wlock = repo.wlock()
635 wlock = repo.wlock()
636 lock = repo.lock()
636 lock = repo.lock()
637 tr = repo.transaction("qpush")
637 tr = repo.transaction("qpush")
638 try:
638 try:
639 ret = self._apply(repo, series, list, update_status,
639 ret = self._apply(repo, series, list, update_status,
640 strict, patchdir, merge, all_files=all_files)
640 strict, patchdir, merge, all_files=all_files)
641 tr.close()
641 tr.close()
642 self.save_dirty()
642 self.save_dirty()
643 return ret
643 return ret
644 except:
644 except:
645 try:
645 try:
646 tr.abort()
646 tr.abort()
647 finally:
647 finally:
648 repo.invalidate()
648 repo.invalidate()
649 repo.dirstate.invalidate()
649 repo.dirstate.invalidate()
650 raise
650 raise
651 finally:
651 finally:
652 release(tr, lock, wlock)
652 release(tr, lock, wlock)
653 self.removeundo(repo)
653 self.removeundo(repo)
654
654
655 def _apply(self, repo, series, list=False, update_status=True,
655 def _apply(self, repo, series, list=False, update_status=True,
656 strict=False, patchdir=None, merge=None, all_files=None):
656 strict=False, patchdir=None, merge=None, all_files=None):
657 '''returns (error, hash)
657 '''returns (error, hash)
658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
659 # TODO unify with commands.py
659 # TODO unify with commands.py
660 if not patchdir:
660 if not patchdir:
661 patchdir = self.path
661 patchdir = self.path
662 err = 0
662 err = 0
663 n = None
663 n = None
664 for patchname in series:
664 for patchname in series:
665 pushable, reason = self.pushable(patchname)
665 pushable, reason = self.pushable(patchname)
666 if not pushable:
666 if not pushable:
667 self.explain_pushable(patchname, all_patches=True)
667 self.explain_pushable(patchname, all_patches=True)
668 continue
668 continue
669 self.ui.status(_("applying %s\n") % patchname)
669 self.ui.status(_("applying %s\n") % patchname)
670 pf = os.path.join(patchdir, patchname)
670 pf = os.path.join(patchdir, patchname)
671
671
672 try:
672 try:
673 ph = patchheader(self.join(patchname), self.plainmode)
673 ph = patchheader(self.join(patchname), self.plainmode)
674 except IOError:
674 except IOError:
675 self.ui.warn(_("unable to read %s\n") % patchname)
675 self.ui.warn(_("unable to read %s\n") % patchname)
676 err = 1
676 err = 1
677 break
677 break
678
678
679 message = ph.message
679 message = ph.message
680 if not message:
680 if not message:
681 # The commit message should not be translated
681 # The commit message should not be translated
682 message = "imported patch %s\n" % patchname
682 message = "imported patch %s\n" % patchname
683 else:
683 else:
684 if list:
684 if list:
685 # The commit message should not be translated
685 # The commit message should not be translated
686 message.append("\nimported patch %s" % patchname)
686 message.append("\nimported patch %s" % patchname)
687 message = '\n'.join(message)
687 message = '\n'.join(message)
688
688
689 if ph.haspatch:
689 if ph.haspatch:
690 (patcherr, files, fuzz) = self.patch(repo, pf)
690 (patcherr, files, fuzz) = self.patch(repo, pf)
691 if all_files is not None:
691 if all_files is not None:
692 all_files.update(files)
692 all_files.update(files)
693 patcherr = not patcherr
693 patcherr = not patcherr
694 else:
694 else:
695 self.ui.warn(_("patch %s is empty\n") % patchname)
695 self.ui.warn(_("patch %s is empty\n") % patchname)
696 patcherr, files, fuzz = 0, [], 0
696 patcherr, files, fuzz = 0, [], 0
697
697
698 if merge and files:
698 if merge and files:
699 # Mark as removed/merged and update dirstate parent info
699 # Mark as removed/merged and update dirstate parent info
700 removed = []
700 removed = []
701 merged = []
701 merged = []
702 for f in files:
702 for f in files:
703 if os.path.lexists(repo.wjoin(f)):
703 if os.path.lexists(repo.wjoin(f)):
704 merged.append(f)
704 merged.append(f)
705 else:
705 else:
706 removed.append(f)
706 removed.append(f)
707 for f in removed:
707 for f in removed:
708 repo.dirstate.remove(f)
708 repo.dirstate.remove(f)
709 for f in merged:
709 for f in merged:
710 repo.dirstate.merge(f)
710 repo.dirstate.merge(f)
711 p1, p2 = repo.dirstate.parents()
711 p1, p2 = repo.dirstate.parents()
712 repo.dirstate.setparents(p1, merge)
712 repo.dirstate.setparents(p1, merge)
713
713
714 match = scmutil.matchfiles(repo, files or [])
714 match = scmutil.matchfiles(repo, files or [])
715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
716
716
717 if n is None:
717 if n is None:
718 raise util.Abort(_("repository commit failed"))
718 raise util.Abort(_("repository commit failed"))
719
719
720 if update_status:
720 if update_status:
721 self.applied.append(statusentry(n, patchname))
721 self.applied.append(statusentry(n, patchname))
722
722
723 if patcherr:
723 if patcherr:
724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
725 err = 2
725 err = 2
726 break
726 break
727
727
728 if fuzz and strict:
728 if fuzz and strict:
729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
730 err = 3
730 err = 3
731 break
731 break
732 return (err, n)
732 return (err, n)
733
733
734 def _cleanup(self, patches, numrevs, keep=False):
734 def _cleanup(self, patches, numrevs, keep=False):
735 if not keep:
735 if not keep:
736 r = self.qrepo()
736 r = self.qrepo()
737 if r:
737 if r:
738 r[None].remove(patches, True)
738 r[None].remove(patches, True)
739 else:
739 else:
740 for p in patches:
740 for p in patches:
741 os.unlink(self.join(p))
741 os.unlink(self.join(p))
742
742
743 if numrevs:
743 if numrevs:
744 qfinished = self.applied[:numrevs]
744 qfinished = self.applied[:numrevs]
745 del self.applied[:numrevs]
745 del self.applied[:numrevs]
746 self.applied_dirty = 1
746 self.applied_dirty = 1
747
747
748 unknown = []
748 unknown = []
749
749
750 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
750 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
751 reverse=True):
751 reverse=True):
752 if i is not None:
752 if i is not None:
753 del self.full_series[i]
753 del self.full_series[i]
754 else:
754 else:
755 unknown.append(p)
755 unknown.append(p)
756
756
757 if unknown:
757 if unknown:
758 if numrevs:
758 if numrevs:
759 rev = dict((entry.name, entry.node) for entry in qfinished)
759 rev = dict((entry.name, entry.node) for entry in qfinished)
760 for p in unknown:
760 for p in unknown:
761 msg = _('revision %s refers to unknown patches: %s\n')
761 msg = _('revision %s refers to unknown patches: %s\n')
762 self.ui.warn(msg % (short(rev[p]), p))
762 self.ui.warn(msg % (short(rev[p]), p))
763 else:
763 else:
764 msg = _('unknown patches: %s\n')
764 msg = _('unknown patches: %s\n')
765 raise util.Abort(''.join(msg % p for p in unknown))
765 raise util.Abort(''.join(msg % p for p in unknown))
766
766
767 self.parse_series()
767 self.parse_series()
768 self.series_dirty = 1
768 self.series_dirty = 1
769
769
770 def _revpatches(self, repo, revs):
770 def _revpatches(self, repo, revs):
771 firstrev = repo[self.applied[0].node].rev()
771 firstrev = repo[self.applied[0].node].rev()
772 patches = []
772 patches = []
773 for i, rev in enumerate(revs):
773 for i, rev in enumerate(revs):
774
774
775 if rev < firstrev:
775 if rev < firstrev:
776 raise util.Abort(_('revision %d is not managed') % rev)
776 raise util.Abort(_('revision %d is not managed') % rev)
777
777
778 ctx = repo[rev]
778 ctx = repo[rev]
779 base = self.applied[i].node
779 base = self.applied[i].node
780 if ctx.node() != base:
780 if ctx.node() != base:
781 msg = _('cannot delete revision %d above applied patches')
781 msg = _('cannot delete revision %d above applied patches')
782 raise util.Abort(msg % rev)
782 raise util.Abort(msg % rev)
783
783
784 patch = self.applied[i].name
784 patch = self.applied[i].name
785 for fmt in ('[mq]: %s', 'imported patch %s'):
785 for fmt in ('[mq]: %s', 'imported patch %s'):
786 if ctx.description() == fmt % patch:
786 if ctx.description() == fmt % patch:
787 msg = _('patch %s finalized without changeset message\n')
787 msg = _('patch %s finalized without changeset message\n')
788 repo.ui.status(msg % patch)
788 repo.ui.status(msg % patch)
789 break
789 break
790
790
791 patches.append(patch)
791 patches.append(patch)
792 return patches
792 return patches
793
793
794 def finish(self, repo, revs):
794 def finish(self, repo, revs):
795 patches = self._revpatches(repo, sorted(revs))
795 patches = self._revpatches(repo, sorted(revs))
796 self._cleanup(patches, len(patches))
796 self._cleanup(patches, len(patches))
797
797
798 def delete(self, repo, patches, opts):
798 def delete(self, repo, patches, opts):
799 if not patches and not opts.get('rev'):
799 if not patches and not opts.get('rev'):
800 raise util.Abort(_('qdelete requires at least one revision or '
800 raise util.Abort(_('qdelete requires at least one revision or '
801 'patch name'))
801 'patch name'))
802
802
803 realpatches = []
803 realpatches = []
804 for patch in patches:
804 for patch in patches:
805 patch = self.lookup(patch, strict=True)
805 patch = self.lookup(patch, strict=True)
806 info = self.isapplied(patch)
806 info = self.isapplied(patch)
807 if info:
807 if info:
808 raise util.Abort(_("cannot delete applied patch %s") % patch)
808 raise util.Abort(_("cannot delete applied patch %s") % patch)
809 if patch not in self.series:
809 if patch not in self.series:
810 raise util.Abort(_("patch %s not in series file") % patch)
810 raise util.Abort(_("patch %s not in series file") % patch)
811 if patch not in realpatches:
811 if patch not in realpatches:
812 realpatches.append(patch)
812 realpatches.append(patch)
813
813
814 numrevs = 0
814 numrevs = 0
815 if opts.get('rev'):
815 if opts.get('rev'):
816 if not self.applied:
816 if not self.applied:
817 raise util.Abort(_('no patches applied'))
817 raise util.Abort(_('no patches applied'))
818 revs = scmutil.revrange(repo, opts.get('rev'))
818 revs = scmutil.revrange(repo, opts.get('rev'))
819 if len(revs) > 1 and revs[0] > revs[1]:
819 if len(revs) > 1 and revs[0] > revs[1]:
820 revs.reverse()
820 revs.reverse()
821 revpatches = self._revpatches(repo, revs)
821 revpatches = self._revpatches(repo, revs)
822 realpatches += revpatches
822 realpatches += revpatches
823 numrevs = len(revpatches)
823 numrevs = len(revpatches)
824
824
825 self._cleanup(realpatches, numrevs, opts.get('keep'))
825 self._cleanup(realpatches, numrevs, opts.get('keep'))
826
826
827 def check_toppatch(self, repo):
827 def check_toppatch(self, repo):
828 if self.applied:
828 if self.applied:
829 top = self.applied[-1].node
829 top = self.applied[-1].node
830 patch = self.applied[-1].name
830 patch = self.applied[-1].name
831 pp = repo.dirstate.parents()
831 pp = repo.dirstate.parents()
832 if top not in pp:
832 if top not in pp:
833 raise util.Abort(_("working directory revision is not qtip"))
833 raise util.Abort(_("working directory revision is not qtip"))
834 return top, patch
834 return top, patch
835 return None, None
835 return None, None
836
836
837 def check_substate(self, repo):
837 def check_substate(self, repo):
838 '''return list of subrepos at a different revision than substate.
838 '''return list of subrepos at a different revision than substate.
839 Abort if any subrepos have uncommitted changes.'''
839 Abort if any subrepos have uncommitted changes.'''
840 inclsubs = []
840 inclsubs = []
841 wctx = repo[None]
841 wctx = repo[None]
842 for s in wctx.substate:
842 for s in wctx.substate:
843 if wctx.sub(s).dirty(True):
843 if wctx.sub(s).dirty(True):
844 raise util.Abort(
844 raise util.Abort(
845 _("uncommitted changes in subrepository %s") % s)
845 _("uncommitted changes in subrepository %s") % s)
846 elif wctx.sub(s).dirty():
846 elif wctx.sub(s).dirty():
847 inclsubs.append(s)
847 inclsubs.append(s)
848 return inclsubs
848 return inclsubs
849
849
850 def localchangesfound(self, refresh=True):
850 def localchangesfound(self, refresh=True):
851 if refresh:
851 if refresh:
852 raise util.Abort(_("local changes found, refresh first"))
852 raise util.Abort(_("local changes found, refresh first"))
853 else:
853 else:
854 raise util.Abort(_("local changes found"))
854 raise util.Abort(_("local changes found"))
855
855
856 def check_localchanges(self, repo, force=False, refresh=True):
856 def check_localchanges(self, repo, force=False, refresh=True):
857 m, a, r, d = repo.status()[:4]
857 m, a, r, d = repo.status()[:4]
858 if (m or a or r or d) and not force:
858 if (m or a or r or d) and not force:
859 self.localchangesfound(refresh)
859 self.localchangesfound(refresh)
860 return m, a, r, d
860 return m, a, r, d
861
861
862 _reserved = ('series', 'status', 'guards', '.', '..')
862 _reserved = ('series', 'status', 'guards', '.', '..')
863 def check_reserved_name(self, name):
863 def check_reserved_name(self, name):
864 if name in self._reserved:
864 if name in self._reserved:
865 raise util.Abort(_('"%s" cannot be used as the name of a patch')
865 raise util.Abort(_('"%s" cannot be used as the name of a patch')
866 % name)
866 % name)
867 for prefix in ('.hg', '.mq'):
867 for prefix in ('.hg', '.mq'):
868 if name.startswith(prefix):
868 if name.startswith(prefix):
869 raise util.Abort(_('patch name cannot begin with "%s"')
869 raise util.Abort(_('patch name cannot begin with "%s"')
870 % prefix)
870 % prefix)
871 for c in ('#', ':'):
871 for c in ('#', ':'):
872 if c in name:
872 if c in name:
873 raise util.Abort(_('"%s" cannot be used in the name of a patch')
873 raise util.Abort(_('"%s" cannot be used in the name of a patch')
874 % c)
874 % c)
875
875
876
876
877 def new(self, repo, patchfn, *pats, **opts):
877 def new(self, repo, patchfn, *pats, **opts):
878 """options:
878 """options:
879 msg: a string or a no-argument function returning a string
879 msg: a string or a no-argument function returning a string
880 """
880 """
881 msg = opts.get('msg')
881 msg = opts.get('msg')
882 user = opts.get('user')
882 user = opts.get('user')
883 date = opts.get('date')
883 date = opts.get('date')
884 if date:
884 if date:
885 date = util.parsedate(date)
885 date = util.parsedate(date)
886 diffopts = self.diffopts({'git': opts.get('git')})
886 diffopts = self.diffopts({'git': opts.get('git')})
887 self.check_reserved_name(patchfn)
887 self.check_reserved_name(patchfn)
888 if os.path.exists(self.join(patchfn)):
888 if os.path.exists(self.join(patchfn)):
889 if os.path.isdir(self.join(patchfn)):
889 if os.path.isdir(self.join(patchfn)):
890 raise util.Abort(_('"%s" already exists as a directory')
890 raise util.Abort(_('"%s" already exists as a directory')
891 % patchfn)
891 % patchfn)
892 else:
892 else:
893 raise util.Abort(_('patch "%s" already exists') % patchfn)
893 raise util.Abort(_('patch "%s" already exists') % patchfn)
894
894
895 inclsubs = self.check_substate(repo)
895 inclsubs = self.check_substate(repo)
896 if inclsubs:
896 if inclsubs:
897 inclsubs.append('.hgsubstate')
897 inclsubs.append('.hgsubstate')
898 if opts.get('include') or opts.get('exclude') or pats:
898 if opts.get('include') or opts.get('exclude') or pats:
899 if inclsubs:
899 if inclsubs:
900 pats = list(pats or []) + inclsubs
900 pats = list(pats or []) + inclsubs
901 match = scmutil.match(repo, pats, opts)
901 match = scmutil.match(repo, pats, opts)
902 # detect missing files in pats
902 # detect missing files in pats
903 def badfn(f, msg):
903 def badfn(f, msg):
904 if f != '.hgsubstate': # .hgsubstate is auto-created
904 if f != '.hgsubstate': # .hgsubstate is auto-created
905 raise util.Abort('%s: %s' % (f, msg))
905 raise util.Abort('%s: %s' % (f, msg))
906 match.bad = badfn
906 match.bad = badfn
907 m, a, r, d = repo.status(match=match)[:4]
907 m, a, r, d = repo.status(match=match)[:4]
908 else:
908 else:
909 m, a, r, d = self.check_localchanges(repo, force=True)
909 m, a, r, d = self.check_localchanges(repo, force=True)
910 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
910 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
911 if len(repo[None].parents()) > 1:
911 if len(repo[None].parents()) > 1:
912 raise util.Abort(_('cannot manage merge changesets'))
912 raise util.Abort(_('cannot manage merge changesets'))
913 commitfiles = m + a + r
913 commitfiles = m + a + r
914 self.check_toppatch(repo)
914 self.check_toppatch(repo)
915 insert = self.full_series_end()
915 insert = self.full_series_end()
916 wlock = repo.wlock()
916 wlock = repo.wlock()
917 try:
917 try:
918 try:
918 try:
919 # if patch file write fails, abort early
919 # if patch file write fails, abort early
920 p = self.opener(patchfn, "w")
920 p = self.opener(patchfn, "w")
921 except IOError, e:
921 except IOError, e:
922 raise util.Abort(_('cannot write patch "%s": %s')
922 raise util.Abort(_('cannot write patch "%s": %s')
923 % (patchfn, e.strerror))
923 % (patchfn, e.strerror))
924 try:
924 try:
925 if self.plainmode:
925 if self.plainmode:
926 if user:
926 if user:
927 p.write("From: " + user + "\n")
927 p.write("From: " + user + "\n")
928 if not date:
928 if not date:
929 p.write("\n")
929 p.write("\n")
930 if date:
930 if date:
931 p.write("Date: %d %d\n\n" % date)
931 p.write("Date: %d %d\n\n" % date)
932 else:
932 else:
933 p.write("# HG changeset patch\n")
933 p.write("# HG changeset patch\n")
934 p.write("# Parent "
934 p.write("# Parent "
935 + hex(repo[None].p1().node()) + "\n")
935 + hex(repo[None].p1().node()) + "\n")
936 if user:
936 if user:
937 p.write("# User " + user + "\n")
937 p.write("# User " + user + "\n")
938 if date:
938 if date:
939 p.write("# Date %s %s\n\n" % date)
939 p.write("# Date %s %s\n\n" % date)
940 if hasattr(msg, '__call__'):
940 if hasattr(msg, '__call__'):
941 msg = msg()
941 msg = msg()
942 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
942 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
943 n = repo.commit(commitmsg, user, date, match=match, force=True)
943 n = repo.commit(commitmsg, user, date, match=match, force=True)
944 if n is None:
944 if n is None:
945 raise util.Abort(_("repo commit failed"))
945 raise util.Abort(_("repo commit failed"))
946 try:
946 try:
947 self.full_series[insert:insert] = [patchfn]
947 self.full_series[insert:insert] = [patchfn]
948 self.applied.append(statusentry(n, patchfn))
948 self.applied.append(statusentry(n, patchfn))
949 self.parse_series()
949 self.parse_series()
950 self.series_dirty = 1
950 self.series_dirty = 1
951 self.applied_dirty = 1
951 self.applied_dirty = 1
952 if msg:
952 if msg:
953 msg = msg + "\n\n"
953 msg = msg + "\n\n"
954 p.write(msg)
954 p.write(msg)
955 if commitfiles:
955 if commitfiles:
956 parent = self.qparents(repo, n)
956 parent = self.qparents(repo, n)
957 chunks = patchmod.diff(repo, node1=parent, node2=n,
957 chunks = patchmod.diff(repo, node1=parent, node2=n,
958 match=match, opts=diffopts)
958 match=match, opts=diffopts)
959 for chunk in chunks:
959 for chunk in chunks:
960 p.write(chunk)
960 p.write(chunk)
961 p.close()
961 p.close()
962 wlock.release()
962 wlock.release()
963 wlock = None
963 wlock = None
964 r = self.qrepo()
964 r = self.qrepo()
965 if r:
965 if r:
966 r[None].add([patchfn])
966 r[None].add([patchfn])
967 except:
967 except:
968 repo.rollback()
968 repo.rollback()
969 raise
969 raise
970 except Exception:
970 except Exception:
971 patchpath = self.join(patchfn)
971 patchpath = self.join(patchfn)
972 try:
972 try:
973 os.unlink(patchpath)
973 os.unlink(patchpath)
974 except:
974 except:
975 self.ui.warn(_('error unlinking %s\n') % patchpath)
975 self.ui.warn(_('error unlinking %s\n') % patchpath)
976 raise
976 raise
977 self.removeundo(repo)
977 self.removeundo(repo)
978 finally:
978 finally:
979 release(wlock)
979 release(wlock)
980
980
981 def strip(self, repo, revs, update=True, backup="all", force=None):
981 def strip(self, repo, revs, update=True, backup="all", force=None):
982 wlock = lock = None
982 wlock = lock = None
983 try:
983 try:
984 wlock = repo.wlock()
984 wlock = repo.wlock()
985 lock = repo.lock()
985 lock = repo.lock()
986
986
987 if update:
987 if update:
988 self.check_localchanges(repo, force=force, refresh=False)
988 self.check_localchanges(repo, force=force, refresh=False)
989 urev = self.qparents(repo, revs[0])
989 urev = self.qparents(repo, revs[0])
990 hg.clean(repo, urev)
990 hg.clean(repo, urev)
991 repo.dirstate.write()
991 repo.dirstate.write()
992
992
993 self.removeundo(repo)
993 self.removeundo(repo)
994 for rev in revs:
994 for rev in revs:
995 repair.strip(self.ui, repo, rev, backup)
995 repair.strip(self.ui, repo, rev, backup)
996 # strip may have unbundled a set of backed up revisions after
996 # strip may have unbundled a set of backed up revisions after
997 # the actual strip
997 # the actual strip
998 self.removeundo(repo)
998 self.removeundo(repo)
999 finally:
999 finally:
1000 release(lock, wlock)
1000 release(lock, wlock)
1001
1001
1002 def isapplied(self, patch):
1002 def isapplied(self, patch):
1003 """returns (index, rev, patch)"""
1003 """returns (index, rev, patch)"""
1004 for i, a in enumerate(self.applied):
1004 for i, a in enumerate(self.applied):
1005 if a.name == patch:
1005 if a.name == patch:
1006 return (i, a.node, a.name)
1006 return (i, a.node, a.name)
1007 return None
1007 return None
1008
1008
1009 # if the exact patch name does not exist, we try a few
1009 # if the exact patch name does not exist, we try a few
1010 # variations. If strict is passed, we try only #1
1010 # variations. If strict is passed, we try only #1
1011 #
1011 #
1012 # 1) a number to indicate an offset in the series file
1012 # 1) a number to indicate an offset in the series file
1013 # 2) a unique substring of the patch name was given
1013 # 2) a unique substring of the patch name was given
1014 # 3) patchname[-+]num to indicate an offset in the series file
1014 # 3) patchname[-+]num to indicate an offset in the series file
1015 def lookup(self, patch, strict=False):
1015 def lookup(self, patch, strict=False):
1016 patch = patch and str(patch)
1016 patch = patch and str(patch)
1017
1017
1018 def partial_name(s):
1018 def partial_name(s):
1019 if s in self.series:
1019 if s in self.series:
1020 return s
1020 return s
1021 matches = [x for x in self.series if s in x]
1021 matches = [x for x in self.series if s in x]
1022 if len(matches) > 1:
1022 if len(matches) > 1:
1023 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1023 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1024 for m in matches:
1024 for m in matches:
1025 self.ui.warn(' %s\n' % m)
1025 self.ui.warn(' %s\n' % m)
1026 return None
1026 return None
1027 if matches:
1027 if matches:
1028 return matches[0]
1028 return matches[0]
1029 if self.series and self.applied:
1029 if self.series and self.applied:
1030 if s == 'qtip':
1030 if s == 'qtip':
1031 return self.series[self.series_end(True)-1]
1031 return self.series[self.series_end(True)-1]
1032 if s == 'qbase':
1032 if s == 'qbase':
1033 return self.series[0]
1033 return self.series[0]
1034 return None
1034 return None
1035
1035
1036 if patch is None:
1036 if patch is None:
1037 return None
1037 return None
1038 if patch in self.series:
1038 if patch in self.series:
1039 return patch
1039 return patch
1040
1040
1041 if not os.path.isfile(self.join(patch)):
1041 if not os.path.isfile(self.join(patch)):
1042 try:
1042 try:
1043 sno = int(patch)
1043 sno = int(patch)
1044 except (ValueError, OverflowError):
1044 except (ValueError, OverflowError):
1045 pass
1045 pass
1046 else:
1046 else:
1047 if -len(self.series) <= sno < len(self.series):
1047 if -len(self.series) <= sno < len(self.series):
1048 return self.series[sno]
1048 return self.series[sno]
1049
1049
1050 if not strict:
1050 if not strict:
1051 res = partial_name(patch)
1051 res = partial_name(patch)
1052 if res:
1052 if res:
1053 return res
1053 return res
1054 minus = patch.rfind('-')
1054 minus = patch.rfind('-')
1055 if minus >= 0:
1055 if minus >= 0:
1056 res = partial_name(patch[:minus])
1056 res = partial_name(patch[:minus])
1057 if res:
1057 if res:
1058 i = self.series.index(res)
1058 i = self.series.index(res)
1059 try:
1059 try:
1060 off = int(patch[minus + 1:] or 1)
1060 off = int(patch[minus + 1:] or 1)
1061 except (ValueError, OverflowError):
1061 except (ValueError, OverflowError):
1062 pass
1062 pass
1063 else:
1063 else:
1064 if i - off >= 0:
1064 if i - off >= 0:
1065 return self.series[i - off]
1065 return self.series[i - off]
1066 plus = patch.rfind('+')
1066 plus = patch.rfind('+')
1067 if plus >= 0:
1067 if plus >= 0:
1068 res = partial_name(patch[:plus])
1068 res = partial_name(patch[:plus])
1069 if res:
1069 if res:
1070 i = self.series.index(res)
1070 i = self.series.index(res)
1071 try:
1071 try:
1072 off = int(patch[plus + 1:] or 1)
1072 off = int(patch[plus + 1:] or 1)
1073 except (ValueError, OverflowError):
1073 except (ValueError, OverflowError):
1074 pass
1074 pass
1075 else:
1075 else:
1076 if i + off < len(self.series):
1076 if i + off < len(self.series):
1077 return self.series[i + off]
1077 return self.series[i + off]
1078 raise util.Abort(_("patch %s not in series") % patch)
1078 raise util.Abort(_("patch %s not in series") % patch)
1079
1079
1080 def push(self, repo, patch=None, force=False, list=False,
1080 def push(self, repo, patch=None, force=False, list=False,
1081 mergeq=None, all=False, move=False, exact=False):
1081 mergeq=None, all=False, move=False, exact=False):
1082 diffopts = self.diffopts()
1082 diffopts = self.diffopts()
1083 wlock = repo.wlock()
1083 wlock = repo.wlock()
1084 try:
1084 try:
1085 heads = []
1085 heads = []
1086 for b, ls in repo.branchmap().iteritems():
1086 for b, ls in repo.branchmap().iteritems():
1087 heads += ls
1087 heads += ls
1088 if not heads:
1088 if not heads:
1089 heads = [nullid]
1089 heads = [nullid]
1090 if repo.dirstate.p1() not in heads and not exact:
1090 if repo.dirstate.p1() not in heads and not exact:
1091 self.ui.status(_("(working directory not at a head)\n"))
1091 self.ui.status(_("(working directory not at a head)\n"))
1092
1092
1093 if not self.series:
1093 if not self.series:
1094 self.ui.warn(_('no patches in series\n'))
1094 self.ui.warn(_('no patches in series\n'))
1095 return 0
1095 return 0
1096
1096
1097 patch = self.lookup(patch)
1097 patch = self.lookup(patch)
1098 # Suppose our series file is: A B C and the current 'top'
1098 # Suppose our series file is: A B C and the current 'top'
1099 # patch is B. qpush C should be performed (moving forward)
1099 # patch is B. qpush C should be performed (moving forward)
1100 # qpush B is a NOP (no change) qpush A is an error (can't
1100 # qpush B is a NOP (no change) qpush A is an error (can't
1101 # go backwards with qpush)
1101 # go backwards with qpush)
1102 if patch:
1102 if patch:
1103 info = self.isapplied(patch)
1103 info = self.isapplied(patch)
1104 if info and info[0] >= len(self.applied) - 1:
1104 if info and info[0] >= len(self.applied) - 1:
1105 self.ui.warn(
1105 self.ui.warn(
1106 _('qpush: %s is already at the top\n') % patch)
1106 _('qpush: %s is already at the top\n') % patch)
1107 return 0
1107 return 0
1108
1108
1109 pushable, reason = self.pushable(patch)
1109 pushable, reason = self.pushable(patch)
1110 if pushable:
1110 if pushable:
1111 if self.series.index(patch) < self.series_end():
1111 if self.series.index(patch) < self.series_end():
1112 raise util.Abort(
1112 raise util.Abort(
1113 _("cannot push to a previous patch: %s") % patch)
1113 _("cannot push to a previous patch: %s") % patch)
1114 else:
1114 else:
1115 if reason:
1115 if reason:
1116 reason = _('guarded by %r') % reason
1116 reason = _('guarded by %r') % reason
1117 else:
1117 else:
1118 reason = _('no matching guards')
1118 reason = _('no matching guards')
1119 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1119 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1120 return 1
1120 return 1
1121 elif all:
1121 elif all:
1122 patch = self.series[-1]
1122 patch = self.series[-1]
1123 if self.isapplied(patch):
1123 if self.isapplied(patch):
1124 self.ui.warn(_('all patches are currently applied\n'))
1124 self.ui.warn(_('all patches are currently applied\n'))
1125 return 0
1125 return 0
1126
1126
1127 # Following the above example, starting at 'top' of B:
1127 # Following the above example, starting at 'top' of B:
1128 # qpush should be performed (pushes C), but a subsequent
1128 # qpush should be performed (pushes C), but a subsequent
1129 # qpush without an argument is an error (nothing to
1129 # qpush without an argument is an error (nothing to
1130 # apply). This allows a loop of "...while hg qpush..." to
1130 # apply). This allows a loop of "...while hg qpush..." to
1131 # work as it detects an error when done
1131 # work as it detects an error when done
1132 start = self.series_end()
1132 start = self.series_end()
1133 if start == len(self.series):
1133 if start == len(self.series):
1134 self.ui.warn(_('patch series already fully applied\n'))
1134 self.ui.warn(_('patch series already fully applied\n'))
1135 return 1
1135 return 1
1136
1136
1137 if exact:
1137 if exact:
1138 if move:
1138 if move:
1139 raise util.Abort(_("cannot use --exact and --move together"))
1139 raise util.Abort(_("cannot use --exact and --move together"))
1140 if self.applied:
1140 if self.applied:
1141 raise util.Abort(_("cannot push --exact with applied patches"))
1141 raise util.Abort(_("cannot push --exact with applied patches"))
1142 root = self.series[start]
1142 root = self.series[start]
1143 target = patchheader(self.join(root), self.plainmode).parent
1143 target = patchheader(self.join(root), self.plainmode).parent
1144 if not target:
1144 if not target:
1145 raise util.Abort(_("%s does not have a parent recorded" % root))
1145 raise util.Abort(_("%s does not have a parent recorded" % root))
1146 if not repo[target] == repo['.']:
1146 if not repo[target] == repo['.']:
1147 hg.update(repo, target)
1147 hg.update(repo, target)
1148
1148
1149 if move:
1149 if move:
1150 if not patch:
1150 if not patch:
1151 raise util.Abort(_("please specify the patch to move"))
1151 raise util.Abort(_("please specify the patch to move"))
1152 for i, rpn in enumerate(self.full_series[start:]):
1152 for i, rpn in enumerate(self.full_series[start:]):
1153 # strip markers for patch guards
1153 # strip markers for patch guards
1154 if self.guard_re.split(rpn, 1)[0] == patch:
1154 if self.guard_re.split(rpn, 1)[0] == patch:
1155 break
1155 break
1156 index = start + i
1156 index = start + i
1157 assert index < len(self.full_series)
1157 assert index < len(self.full_series)
1158 fullpatch = self.full_series[index]
1158 fullpatch = self.full_series[index]
1159 del self.full_series[index]
1159 del self.full_series[index]
1160 self.full_series.insert(start, fullpatch)
1160 self.full_series.insert(start, fullpatch)
1161 self.parse_series()
1161 self.parse_series()
1162 self.series_dirty = 1
1162 self.series_dirty = 1
1163
1163
1164 self.applied_dirty = 1
1164 self.applied_dirty = 1
1165 if start > 0:
1165 if start > 0:
1166 self.check_toppatch(repo)
1166 self.check_toppatch(repo)
1167 if not patch:
1167 if not patch:
1168 patch = self.series[start]
1168 patch = self.series[start]
1169 end = start + 1
1169 end = start + 1
1170 else:
1170 else:
1171 end = self.series.index(patch, start) + 1
1171 end = self.series.index(patch, start) + 1
1172
1172
1173 s = self.series[start:end]
1173 s = self.series[start:end]
1174
1174
1175 if not force:
1175 if not force:
1176 mm, aa, rr, dd = repo.status()[:4]
1176 mm, aa, rr, dd = repo.status()[:4]
1177 wcfiles = set(mm + aa + rr + dd)
1177 wcfiles = set(mm + aa + rr + dd)
1178 if wcfiles:
1178 if wcfiles:
1179 for patchname in s:
1179 for patchname in s:
1180 pf = os.path.join(self.path, patchname)
1180 pf = os.path.join(self.path, patchname)
1181 patchfiles = patchmod.changedfiles(pf, strip=1)
1181 patchfiles = patchmod.changedfiles(self.ui, repo, pf)
1182 if wcfiles.intersection(patchfiles):
1182 if wcfiles.intersection(patchfiles):
1183 self.localchangesfound(self.applied)
1183 self.localchangesfound(self.applied)
1184 elif mergeq:
1184 elif mergeq:
1185 self.check_localchanges(refresh=self.applied)
1185 self.check_localchanges(refresh=self.applied)
1186
1186
1187 all_files = set()
1187 all_files = set()
1188 try:
1188 try:
1189 if mergeq:
1189 if mergeq:
1190 ret = self.mergepatch(repo, mergeq, s, diffopts)
1190 ret = self.mergepatch(repo, mergeq, s, diffopts)
1191 else:
1191 else:
1192 ret = self.apply(repo, s, list, all_files=all_files)
1192 ret = self.apply(repo, s, list, all_files=all_files)
1193 except:
1193 except:
1194 self.ui.warn(_('cleaning up working directory...'))
1194 self.ui.warn(_('cleaning up working directory...'))
1195 node = repo.dirstate.p1()
1195 node = repo.dirstate.p1()
1196 hg.revert(repo, node, None)
1196 hg.revert(repo, node, None)
1197 # only remove unknown files that we know we touched or
1197 # only remove unknown files that we know we touched or
1198 # created while patching
1198 # created while patching
1199 for f in all_files:
1199 for f in all_files:
1200 if f not in repo.dirstate:
1200 if f not in repo.dirstate:
1201 try:
1201 try:
1202 util.unlinkpath(repo.wjoin(f))
1202 util.unlinkpath(repo.wjoin(f))
1203 except OSError, inst:
1203 except OSError, inst:
1204 if inst.errno != errno.ENOENT:
1204 if inst.errno != errno.ENOENT:
1205 raise
1205 raise
1206 self.ui.warn(_('done\n'))
1206 self.ui.warn(_('done\n'))
1207 raise
1207 raise
1208
1208
1209 if not self.applied:
1209 if not self.applied:
1210 return ret[0]
1210 return ret[0]
1211 top = self.applied[-1].name
1211 top = self.applied[-1].name
1212 if ret[0] and ret[0] > 1:
1212 if ret[0] and ret[0] > 1:
1213 msg = _("errors during apply, please fix and refresh %s\n")
1213 msg = _("errors during apply, please fix and refresh %s\n")
1214 self.ui.write(msg % top)
1214 self.ui.write(msg % top)
1215 else:
1215 else:
1216 self.ui.write(_("now at: %s\n") % top)
1216 self.ui.write(_("now at: %s\n") % top)
1217 return ret[0]
1217 return ret[0]
1218
1218
1219 finally:
1219 finally:
1220 wlock.release()
1220 wlock.release()
1221
1221
1222 def pop(self, repo, patch=None, force=False, update=True, all=False):
1222 def pop(self, repo, patch=None, force=False, update=True, all=False):
1223 wlock = repo.wlock()
1223 wlock = repo.wlock()
1224 try:
1224 try:
1225 if patch:
1225 if patch:
1226 # index, rev, patch
1226 # index, rev, patch
1227 info = self.isapplied(patch)
1227 info = self.isapplied(patch)
1228 if not info:
1228 if not info:
1229 patch = self.lookup(patch)
1229 patch = self.lookup(patch)
1230 info = self.isapplied(patch)
1230 info = self.isapplied(patch)
1231 if not info:
1231 if not info:
1232 raise util.Abort(_("patch %s is not applied") % patch)
1232 raise util.Abort(_("patch %s is not applied") % patch)
1233
1233
1234 if not self.applied:
1234 if not self.applied:
1235 # Allow qpop -a to work repeatedly,
1235 # Allow qpop -a to work repeatedly,
1236 # but not qpop without an argument
1236 # but not qpop without an argument
1237 self.ui.warn(_("no patches applied\n"))
1237 self.ui.warn(_("no patches applied\n"))
1238 return not all
1238 return not all
1239
1239
1240 if all:
1240 if all:
1241 start = 0
1241 start = 0
1242 elif patch:
1242 elif patch:
1243 start = info[0] + 1
1243 start = info[0] + 1
1244 else:
1244 else:
1245 start = len(self.applied) - 1
1245 start = len(self.applied) - 1
1246
1246
1247 if start >= len(self.applied):
1247 if start >= len(self.applied):
1248 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1248 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1249 return
1249 return
1250
1250
1251 if not update:
1251 if not update:
1252 parents = repo.dirstate.parents()
1252 parents = repo.dirstate.parents()
1253 rr = [x.node for x in self.applied]
1253 rr = [x.node for x in self.applied]
1254 for p in parents:
1254 for p in parents:
1255 if p in rr:
1255 if p in rr:
1256 self.ui.warn(_("qpop: forcing dirstate update\n"))
1256 self.ui.warn(_("qpop: forcing dirstate update\n"))
1257 update = True
1257 update = True
1258 else:
1258 else:
1259 parents = [p.node() for p in repo[None].parents()]
1259 parents = [p.node() for p in repo[None].parents()]
1260 needupdate = False
1260 needupdate = False
1261 for entry in self.applied[start:]:
1261 for entry in self.applied[start:]:
1262 if entry.node in parents:
1262 if entry.node in parents:
1263 needupdate = True
1263 needupdate = True
1264 break
1264 break
1265 update = needupdate
1265 update = needupdate
1266
1266
1267 self.applied_dirty = 1
1267 self.applied_dirty = 1
1268 end = len(self.applied)
1268 end = len(self.applied)
1269 rev = self.applied[start].node
1269 rev = self.applied[start].node
1270 if update:
1270 if update:
1271 top = self.check_toppatch(repo)[0]
1271 top = self.check_toppatch(repo)[0]
1272
1272
1273 try:
1273 try:
1274 heads = repo.changelog.heads(rev)
1274 heads = repo.changelog.heads(rev)
1275 except error.LookupError:
1275 except error.LookupError:
1276 node = short(rev)
1276 node = short(rev)
1277 raise util.Abort(_('trying to pop unknown node %s') % node)
1277 raise util.Abort(_('trying to pop unknown node %s') % node)
1278
1278
1279 if heads != [self.applied[-1].node]:
1279 if heads != [self.applied[-1].node]:
1280 raise util.Abort(_("popping would remove a revision not "
1280 raise util.Abort(_("popping would remove a revision not "
1281 "managed by this patch queue"))
1281 "managed by this patch queue"))
1282
1282
1283 # we know there are no local changes, so we can make a simplified
1283 # we know there are no local changes, so we can make a simplified
1284 # form of hg.update.
1284 # form of hg.update.
1285 if update:
1285 if update:
1286 qp = self.qparents(repo, rev)
1286 qp = self.qparents(repo, rev)
1287 ctx = repo[qp]
1287 ctx = repo[qp]
1288 m, a, r, d = repo.status(qp, top)[:4]
1288 m, a, r, d = repo.status(qp, top)[:4]
1289 parentfiles = set(m + a + r + d)
1289 parentfiles = set(m + a + r + d)
1290 if not force and parentfiles:
1290 if not force and parentfiles:
1291 mm, aa, rr, dd = repo.status()[:4]
1291 mm, aa, rr, dd = repo.status()[:4]
1292 wcfiles = set(mm + aa + rr + dd)
1292 wcfiles = set(mm + aa + rr + dd)
1293 if wcfiles.intersection(parentfiles):
1293 if wcfiles.intersection(parentfiles):
1294 self.localchangesfound()
1294 self.localchangesfound()
1295 if d:
1295 if d:
1296 raise util.Abort(_("deletions found between repo revs"))
1296 raise util.Abort(_("deletions found between repo revs"))
1297 for f in a:
1297 for f in a:
1298 try:
1298 try:
1299 util.unlinkpath(repo.wjoin(f))
1299 util.unlinkpath(repo.wjoin(f))
1300 except OSError, e:
1300 except OSError, e:
1301 if e.errno != errno.ENOENT:
1301 if e.errno != errno.ENOENT:
1302 raise
1302 raise
1303 repo.dirstate.forget(f)
1303 repo.dirstate.forget(f)
1304 for f in m + r:
1304 for f in m + r:
1305 fctx = ctx[f]
1305 fctx = ctx[f]
1306 repo.wwrite(f, fctx.data(), fctx.flags())
1306 repo.wwrite(f, fctx.data(), fctx.flags())
1307 repo.dirstate.normal(f)
1307 repo.dirstate.normal(f)
1308 repo.dirstate.setparents(qp, nullid)
1308 repo.dirstate.setparents(qp, nullid)
1309 for patch in reversed(self.applied[start:end]):
1309 for patch in reversed(self.applied[start:end]):
1310 self.ui.status(_("popping %s\n") % patch.name)
1310 self.ui.status(_("popping %s\n") % patch.name)
1311 del self.applied[start:end]
1311 del self.applied[start:end]
1312 self.strip(repo, [rev], update=False, backup='strip')
1312 self.strip(repo, [rev], update=False, backup='strip')
1313 if self.applied:
1313 if self.applied:
1314 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1314 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1315 else:
1315 else:
1316 self.ui.write(_("patch queue now empty\n"))
1316 self.ui.write(_("patch queue now empty\n"))
1317 finally:
1317 finally:
1318 wlock.release()
1318 wlock.release()
1319
1319
1320 def diff(self, repo, pats, opts):
1320 def diff(self, repo, pats, opts):
1321 top, patch = self.check_toppatch(repo)
1321 top, patch = self.check_toppatch(repo)
1322 if not top:
1322 if not top:
1323 self.ui.write(_("no patches applied\n"))
1323 self.ui.write(_("no patches applied\n"))
1324 return
1324 return
1325 qp = self.qparents(repo, top)
1325 qp = self.qparents(repo, top)
1326 if opts.get('reverse'):
1326 if opts.get('reverse'):
1327 node1, node2 = None, qp
1327 node1, node2 = None, qp
1328 else:
1328 else:
1329 node1, node2 = qp, None
1329 node1, node2 = qp, None
1330 diffopts = self.diffopts(opts, patch)
1330 diffopts = self.diffopts(opts, patch)
1331 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1331 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1332
1332
1333 def refresh(self, repo, pats=None, **opts):
1333 def refresh(self, repo, pats=None, **opts):
1334 if not self.applied:
1334 if not self.applied:
1335 self.ui.write(_("no patches applied\n"))
1335 self.ui.write(_("no patches applied\n"))
1336 return 1
1336 return 1
1337 msg = opts.get('msg', '').rstrip()
1337 msg = opts.get('msg', '').rstrip()
1338 newuser = opts.get('user')
1338 newuser = opts.get('user')
1339 newdate = opts.get('date')
1339 newdate = opts.get('date')
1340 if newdate:
1340 if newdate:
1341 newdate = '%d %d' % util.parsedate(newdate)
1341 newdate = '%d %d' % util.parsedate(newdate)
1342 wlock = repo.wlock()
1342 wlock = repo.wlock()
1343
1343
1344 try:
1344 try:
1345 self.check_toppatch(repo)
1345 self.check_toppatch(repo)
1346 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1346 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1347 if repo.changelog.heads(top) != [top]:
1347 if repo.changelog.heads(top) != [top]:
1348 raise util.Abort(_("cannot refresh a revision with children"))
1348 raise util.Abort(_("cannot refresh a revision with children"))
1349
1349
1350 inclsubs = self.check_substate(repo)
1350 inclsubs = self.check_substate(repo)
1351
1351
1352 cparents = repo.changelog.parents(top)
1352 cparents = repo.changelog.parents(top)
1353 patchparent = self.qparents(repo, top)
1353 patchparent = self.qparents(repo, top)
1354 ph = patchheader(self.join(patchfn), self.plainmode)
1354 ph = patchheader(self.join(patchfn), self.plainmode)
1355 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1355 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1356 if msg:
1356 if msg:
1357 ph.setmessage(msg)
1357 ph.setmessage(msg)
1358 if newuser:
1358 if newuser:
1359 ph.setuser(newuser)
1359 ph.setuser(newuser)
1360 if newdate:
1360 if newdate:
1361 ph.setdate(newdate)
1361 ph.setdate(newdate)
1362 ph.setparent(hex(patchparent))
1362 ph.setparent(hex(patchparent))
1363
1363
1364 # only commit new patch when write is complete
1364 # only commit new patch when write is complete
1365 patchf = self.opener(patchfn, 'w', atomictemp=True)
1365 patchf = self.opener(patchfn, 'w', atomictemp=True)
1366
1366
1367 comments = str(ph)
1367 comments = str(ph)
1368 if comments:
1368 if comments:
1369 patchf.write(comments)
1369 patchf.write(comments)
1370
1370
1371 # update the dirstate in place, strip off the qtip commit
1371 # update the dirstate in place, strip off the qtip commit
1372 # and then commit.
1372 # and then commit.
1373 #
1373 #
1374 # this should really read:
1374 # this should really read:
1375 # mm, dd, aa = repo.status(top, patchparent)[:3]
1375 # mm, dd, aa = repo.status(top, patchparent)[:3]
1376 # but we do it backwards to take advantage of manifest/chlog
1376 # but we do it backwards to take advantage of manifest/chlog
1377 # caching against the next repo.status call
1377 # caching against the next repo.status call
1378 mm, aa, dd = repo.status(patchparent, top)[:3]
1378 mm, aa, dd = repo.status(patchparent, top)[:3]
1379 changes = repo.changelog.read(top)
1379 changes = repo.changelog.read(top)
1380 man = repo.manifest.read(changes[0])
1380 man = repo.manifest.read(changes[0])
1381 aaa = aa[:]
1381 aaa = aa[:]
1382 matchfn = scmutil.match(repo, pats, opts)
1382 matchfn = scmutil.match(repo, pats, opts)
1383 # in short mode, we only diff the files included in the
1383 # in short mode, we only diff the files included in the
1384 # patch already plus specified files
1384 # patch already plus specified files
1385 if opts.get('short'):
1385 if opts.get('short'):
1386 # if amending a patch, we start with existing
1386 # if amending a patch, we start with existing
1387 # files plus specified files - unfiltered
1387 # files plus specified files - unfiltered
1388 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1388 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1389 # filter with inc/exl options
1389 # filter with inc/exl options
1390 matchfn = scmutil.match(repo, opts=opts)
1390 matchfn = scmutil.match(repo, opts=opts)
1391 else:
1391 else:
1392 match = scmutil.matchall(repo)
1392 match = scmutil.matchall(repo)
1393 m, a, r, d = repo.status(match=match)[:4]
1393 m, a, r, d = repo.status(match=match)[:4]
1394 mm = set(mm)
1394 mm = set(mm)
1395 aa = set(aa)
1395 aa = set(aa)
1396 dd = set(dd)
1396 dd = set(dd)
1397
1397
1398 # we might end up with files that were added between
1398 # we might end up with files that were added between
1399 # qtip and the dirstate parent, but then changed in the
1399 # qtip and the dirstate parent, but then changed in the
1400 # local dirstate. in this case, we want them to only
1400 # local dirstate. in this case, we want them to only
1401 # show up in the added section
1401 # show up in the added section
1402 for x in m:
1402 for x in m:
1403 if x not in aa:
1403 if x not in aa:
1404 mm.add(x)
1404 mm.add(x)
1405 # we might end up with files added by the local dirstate that
1405 # we might end up with files added by the local dirstate that
1406 # were deleted by the patch. In this case, they should only
1406 # were deleted by the patch. In this case, they should only
1407 # show up in the changed section.
1407 # show up in the changed section.
1408 for x in a:
1408 for x in a:
1409 if x in dd:
1409 if x in dd:
1410 dd.remove(x)
1410 dd.remove(x)
1411 mm.add(x)
1411 mm.add(x)
1412 else:
1412 else:
1413 aa.add(x)
1413 aa.add(x)
1414 # make sure any files deleted in the local dirstate
1414 # make sure any files deleted in the local dirstate
1415 # are not in the add or change column of the patch
1415 # are not in the add or change column of the patch
1416 forget = []
1416 forget = []
1417 for x in d + r:
1417 for x in d + r:
1418 if x in aa:
1418 if x in aa:
1419 aa.remove(x)
1419 aa.remove(x)
1420 forget.append(x)
1420 forget.append(x)
1421 continue
1421 continue
1422 else:
1422 else:
1423 mm.discard(x)
1423 mm.discard(x)
1424 dd.add(x)
1424 dd.add(x)
1425
1425
1426 m = list(mm)
1426 m = list(mm)
1427 r = list(dd)
1427 r = list(dd)
1428 a = list(aa)
1428 a = list(aa)
1429 c = [filter(matchfn, l) for l in (m, a, r)]
1429 c = [filter(matchfn, l) for l in (m, a, r)]
1430 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1430 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1431 chunks = patchmod.diff(repo, patchparent, match=match,
1431 chunks = patchmod.diff(repo, patchparent, match=match,
1432 changes=c, opts=diffopts)
1432 changes=c, opts=diffopts)
1433 for chunk in chunks:
1433 for chunk in chunks:
1434 patchf.write(chunk)
1434 patchf.write(chunk)
1435
1435
1436 try:
1436 try:
1437 if diffopts.git or diffopts.upgrade:
1437 if diffopts.git or diffopts.upgrade:
1438 copies = {}
1438 copies = {}
1439 for dst in a:
1439 for dst in a:
1440 src = repo.dirstate.copied(dst)
1440 src = repo.dirstate.copied(dst)
1441 # during qfold, the source file for copies may
1441 # during qfold, the source file for copies may
1442 # be removed. Treat this as a simple add.
1442 # be removed. Treat this as a simple add.
1443 if src is not None and src in repo.dirstate:
1443 if src is not None and src in repo.dirstate:
1444 copies.setdefault(src, []).append(dst)
1444 copies.setdefault(src, []).append(dst)
1445 repo.dirstate.add(dst)
1445 repo.dirstate.add(dst)
1446 # remember the copies between patchparent and qtip
1446 # remember the copies between patchparent and qtip
1447 for dst in aaa:
1447 for dst in aaa:
1448 f = repo.file(dst)
1448 f = repo.file(dst)
1449 src = f.renamed(man[dst])
1449 src = f.renamed(man[dst])
1450 if src:
1450 if src:
1451 copies.setdefault(src[0], []).extend(
1451 copies.setdefault(src[0], []).extend(
1452 copies.get(dst, []))
1452 copies.get(dst, []))
1453 if dst in a:
1453 if dst in a:
1454 copies[src[0]].append(dst)
1454 copies[src[0]].append(dst)
1455 # we can't copy a file created by the patch itself
1455 # we can't copy a file created by the patch itself
1456 if dst in copies:
1456 if dst in copies:
1457 del copies[dst]
1457 del copies[dst]
1458 for src, dsts in copies.iteritems():
1458 for src, dsts in copies.iteritems():
1459 for dst in dsts:
1459 for dst in dsts:
1460 repo.dirstate.copy(src, dst)
1460 repo.dirstate.copy(src, dst)
1461 else:
1461 else:
1462 for dst in a:
1462 for dst in a:
1463 repo.dirstate.add(dst)
1463 repo.dirstate.add(dst)
1464 # Drop useless copy information
1464 # Drop useless copy information
1465 for f in list(repo.dirstate.copies()):
1465 for f in list(repo.dirstate.copies()):
1466 repo.dirstate.copy(None, f)
1466 repo.dirstate.copy(None, f)
1467 for f in r:
1467 for f in r:
1468 repo.dirstate.remove(f)
1468 repo.dirstate.remove(f)
1469 # if the patch excludes a modified file, mark that
1469 # if the patch excludes a modified file, mark that
1470 # file with mtime=0 so status can see it.
1470 # file with mtime=0 so status can see it.
1471 mm = []
1471 mm = []
1472 for i in xrange(len(m)-1, -1, -1):
1472 for i in xrange(len(m)-1, -1, -1):
1473 if not matchfn(m[i]):
1473 if not matchfn(m[i]):
1474 mm.append(m[i])
1474 mm.append(m[i])
1475 del m[i]
1475 del m[i]
1476 for f in m:
1476 for f in m:
1477 repo.dirstate.normal(f)
1477 repo.dirstate.normal(f)
1478 for f in mm:
1478 for f in mm:
1479 repo.dirstate.normallookup(f)
1479 repo.dirstate.normallookup(f)
1480 for f in forget:
1480 for f in forget:
1481 repo.dirstate.forget(f)
1481 repo.dirstate.forget(f)
1482
1482
1483 if not msg:
1483 if not msg:
1484 if not ph.message:
1484 if not ph.message:
1485 message = "[mq]: %s\n" % patchfn
1485 message = "[mq]: %s\n" % patchfn
1486 else:
1486 else:
1487 message = "\n".join(ph.message)
1487 message = "\n".join(ph.message)
1488 else:
1488 else:
1489 message = msg
1489 message = msg
1490
1490
1491 user = ph.user or changes[1]
1491 user = ph.user or changes[1]
1492
1492
1493 # assumes strip can roll itself back if interrupted
1493 # assumes strip can roll itself back if interrupted
1494 repo.dirstate.setparents(*cparents)
1494 repo.dirstate.setparents(*cparents)
1495 self.applied.pop()
1495 self.applied.pop()
1496 self.applied_dirty = 1
1496 self.applied_dirty = 1
1497 self.strip(repo, [top], update=False,
1497 self.strip(repo, [top], update=False,
1498 backup='strip')
1498 backup='strip')
1499 except:
1499 except:
1500 repo.dirstate.invalidate()
1500 repo.dirstate.invalidate()
1501 raise
1501 raise
1502
1502
1503 try:
1503 try:
1504 # might be nice to attempt to roll back strip after this
1504 # might be nice to attempt to roll back strip after this
1505 n = repo.commit(message, user, ph.date, match=match,
1505 n = repo.commit(message, user, ph.date, match=match,
1506 force=True)
1506 force=True)
1507 # only write patch after a successful commit
1507 # only write patch after a successful commit
1508 patchf.rename()
1508 patchf.rename()
1509 self.applied.append(statusentry(n, patchfn))
1509 self.applied.append(statusentry(n, patchfn))
1510 except:
1510 except:
1511 ctx = repo[cparents[0]]
1511 ctx = repo[cparents[0]]
1512 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1512 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1513 self.save_dirty()
1513 self.save_dirty()
1514 self.ui.warn(_('refresh interrupted while patch was popped! '
1514 self.ui.warn(_('refresh interrupted while patch was popped! '
1515 '(revert --all, qpush to recover)\n'))
1515 '(revert --all, qpush to recover)\n'))
1516 raise
1516 raise
1517 finally:
1517 finally:
1518 wlock.release()
1518 wlock.release()
1519 self.removeundo(repo)
1519 self.removeundo(repo)
1520
1520
1521 def init(self, repo, create=False):
1521 def init(self, repo, create=False):
1522 if not create and os.path.isdir(self.path):
1522 if not create and os.path.isdir(self.path):
1523 raise util.Abort(_("patch queue directory already exists"))
1523 raise util.Abort(_("patch queue directory already exists"))
1524 try:
1524 try:
1525 os.mkdir(self.path)
1525 os.mkdir(self.path)
1526 except OSError, inst:
1526 except OSError, inst:
1527 if inst.errno != errno.EEXIST or not create:
1527 if inst.errno != errno.EEXIST or not create:
1528 raise
1528 raise
1529 if create:
1529 if create:
1530 return self.qrepo(create=True)
1530 return self.qrepo(create=True)
1531
1531
1532 def unapplied(self, repo, patch=None):
1532 def unapplied(self, repo, patch=None):
1533 if patch and patch not in self.series:
1533 if patch and patch not in self.series:
1534 raise util.Abort(_("patch %s is not in series file") % patch)
1534 raise util.Abort(_("patch %s is not in series file") % patch)
1535 if not patch:
1535 if not patch:
1536 start = self.series_end()
1536 start = self.series_end()
1537 else:
1537 else:
1538 start = self.series.index(patch) + 1
1538 start = self.series.index(patch) + 1
1539 unapplied = []
1539 unapplied = []
1540 for i in xrange(start, len(self.series)):
1540 for i in xrange(start, len(self.series)):
1541 pushable, reason = self.pushable(i)
1541 pushable, reason = self.pushable(i)
1542 if pushable:
1542 if pushable:
1543 unapplied.append((i, self.series[i]))
1543 unapplied.append((i, self.series[i]))
1544 self.explain_pushable(i)
1544 self.explain_pushable(i)
1545 return unapplied
1545 return unapplied
1546
1546
1547 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1547 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1548 summary=False):
1548 summary=False):
1549 def displayname(pfx, patchname, state):
1549 def displayname(pfx, patchname, state):
1550 if pfx:
1550 if pfx:
1551 self.ui.write(pfx)
1551 self.ui.write(pfx)
1552 if summary:
1552 if summary:
1553 ph = patchheader(self.join(patchname), self.plainmode)
1553 ph = patchheader(self.join(patchname), self.plainmode)
1554 msg = ph.message and ph.message[0] or ''
1554 msg = ph.message and ph.message[0] or ''
1555 if self.ui.formatted():
1555 if self.ui.formatted():
1556 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1556 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1557 if width > 0:
1557 if width > 0:
1558 msg = util.ellipsis(msg, width)
1558 msg = util.ellipsis(msg, width)
1559 else:
1559 else:
1560 msg = ''
1560 msg = ''
1561 self.ui.write(patchname, label='qseries.' + state)
1561 self.ui.write(patchname, label='qseries.' + state)
1562 self.ui.write(': ')
1562 self.ui.write(': ')
1563 self.ui.write(msg, label='qseries.message.' + state)
1563 self.ui.write(msg, label='qseries.message.' + state)
1564 else:
1564 else:
1565 self.ui.write(patchname, label='qseries.' + state)
1565 self.ui.write(patchname, label='qseries.' + state)
1566 self.ui.write('\n')
1566 self.ui.write('\n')
1567
1567
1568 applied = set([p.name for p in self.applied])
1568 applied = set([p.name for p in self.applied])
1569 if length is None:
1569 if length is None:
1570 length = len(self.series) - start
1570 length = len(self.series) - start
1571 if not missing:
1571 if not missing:
1572 if self.ui.verbose:
1572 if self.ui.verbose:
1573 idxwidth = len(str(start + length - 1))
1573 idxwidth = len(str(start + length - 1))
1574 for i in xrange(start, start + length):
1574 for i in xrange(start, start + length):
1575 patch = self.series[i]
1575 patch = self.series[i]
1576 if patch in applied:
1576 if patch in applied:
1577 char, state = 'A', 'applied'
1577 char, state = 'A', 'applied'
1578 elif self.pushable(i)[0]:
1578 elif self.pushable(i)[0]:
1579 char, state = 'U', 'unapplied'
1579 char, state = 'U', 'unapplied'
1580 else:
1580 else:
1581 char, state = 'G', 'guarded'
1581 char, state = 'G', 'guarded'
1582 pfx = ''
1582 pfx = ''
1583 if self.ui.verbose:
1583 if self.ui.verbose:
1584 pfx = '%*d %s ' % (idxwidth, i, char)
1584 pfx = '%*d %s ' % (idxwidth, i, char)
1585 elif status and status != char:
1585 elif status and status != char:
1586 continue
1586 continue
1587 displayname(pfx, patch, state)
1587 displayname(pfx, patch, state)
1588 else:
1588 else:
1589 msng_list = []
1589 msng_list = []
1590 for root, dirs, files in os.walk(self.path):
1590 for root, dirs, files in os.walk(self.path):
1591 d = root[len(self.path) + 1:]
1591 d = root[len(self.path) + 1:]
1592 for f in files:
1592 for f in files:
1593 fl = os.path.join(d, f)
1593 fl = os.path.join(d, f)
1594 if (fl not in self.series and
1594 if (fl not in self.series and
1595 fl not in (self.status_path, self.series_path,
1595 fl not in (self.status_path, self.series_path,
1596 self.guards_path)
1596 self.guards_path)
1597 and not fl.startswith('.')):
1597 and not fl.startswith('.')):
1598 msng_list.append(fl)
1598 msng_list.append(fl)
1599 for x in sorted(msng_list):
1599 for x in sorted(msng_list):
1600 pfx = self.ui.verbose and ('D ') or ''
1600 pfx = self.ui.verbose and ('D ') or ''
1601 displayname(pfx, x, 'missing')
1601 displayname(pfx, x, 'missing')
1602
1602
1603 def issaveline(self, l):
1603 def issaveline(self, l):
1604 if l.name == '.hg.patches.save.line':
1604 if l.name == '.hg.patches.save.line':
1605 return True
1605 return True
1606
1606
1607 def qrepo(self, create=False):
1607 def qrepo(self, create=False):
1608 ui = self.ui.copy()
1608 ui = self.ui.copy()
1609 ui.setconfig('paths', 'default', '', overlay=False)
1609 ui.setconfig('paths', 'default', '', overlay=False)
1610 ui.setconfig('paths', 'default-push', '', overlay=False)
1610 ui.setconfig('paths', 'default-push', '', overlay=False)
1611 if create or os.path.isdir(self.join(".hg")):
1611 if create or os.path.isdir(self.join(".hg")):
1612 return hg.repository(ui, path=self.path, create=create)
1612 return hg.repository(ui, path=self.path, create=create)
1613
1613
1614 def restore(self, repo, rev, delete=None, qupdate=None):
1614 def restore(self, repo, rev, delete=None, qupdate=None):
1615 desc = repo[rev].description().strip()
1615 desc = repo[rev].description().strip()
1616 lines = desc.splitlines()
1616 lines = desc.splitlines()
1617 i = 0
1617 i = 0
1618 datastart = None
1618 datastart = None
1619 series = []
1619 series = []
1620 applied = []
1620 applied = []
1621 qpp = None
1621 qpp = None
1622 for i, line in enumerate(lines):
1622 for i, line in enumerate(lines):
1623 if line == 'Patch Data:':
1623 if line == 'Patch Data:':
1624 datastart = i + 1
1624 datastart = i + 1
1625 elif line.startswith('Dirstate:'):
1625 elif line.startswith('Dirstate:'):
1626 l = line.rstrip()
1626 l = line.rstrip()
1627 l = l[10:].split(' ')
1627 l = l[10:].split(' ')
1628 qpp = [bin(x) for x in l]
1628 qpp = [bin(x) for x in l]
1629 elif datastart is not None:
1629 elif datastart is not None:
1630 l = line.rstrip()
1630 l = line.rstrip()
1631 n, name = l.split(':', 1)
1631 n, name = l.split(':', 1)
1632 if n:
1632 if n:
1633 applied.append(statusentry(bin(n), name))
1633 applied.append(statusentry(bin(n), name))
1634 else:
1634 else:
1635 series.append(l)
1635 series.append(l)
1636 if datastart is None:
1636 if datastart is None:
1637 self.ui.warn(_("No saved patch data found\n"))
1637 self.ui.warn(_("No saved patch data found\n"))
1638 return 1
1638 return 1
1639 self.ui.warn(_("restoring status: %s\n") % lines[0])
1639 self.ui.warn(_("restoring status: %s\n") % lines[0])
1640 self.full_series = series
1640 self.full_series = series
1641 self.applied = applied
1641 self.applied = applied
1642 self.parse_series()
1642 self.parse_series()
1643 self.series_dirty = 1
1643 self.series_dirty = 1
1644 self.applied_dirty = 1
1644 self.applied_dirty = 1
1645 heads = repo.changelog.heads()
1645 heads = repo.changelog.heads()
1646 if delete:
1646 if delete:
1647 if rev not in heads:
1647 if rev not in heads:
1648 self.ui.warn(_("save entry has children, leaving it alone\n"))
1648 self.ui.warn(_("save entry has children, leaving it alone\n"))
1649 else:
1649 else:
1650 self.ui.warn(_("removing save entry %s\n") % short(rev))
1650 self.ui.warn(_("removing save entry %s\n") % short(rev))
1651 pp = repo.dirstate.parents()
1651 pp = repo.dirstate.parents()
1652 if rev in pp:
1652 if rev in pp:
1653 update = True
1653 update = True
1654 else:
1654 else:
1655 update = False
1655 update = False
1656 self.strip(repo, [rev], update=update, backup='strip')
1656 self.strip(repo, [rev], update=update, backup='strip')
1657 if qpp:
1657 if qpp:
1658 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1658 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1659 (short(qpp[0]), short(qpp[1])))
1659 (short(qpp[0]), short(qpp[1])))
1660 if qupdate:
1660 if qupdate:
1661 self.ui.status(_("updating queue directory\n"))
1661 self.ui.status(_("updating queue directory\n"))
1662 r = self.qrepo()
1662 r = self.qrepo()
1663 if not r:
1663 if not r:
1664 self.ui.warn(_("Unable to load queue repository\n"))
1664 self.ui.warn(_("Unable to load queue repository\n"))
1665 return 1
1665 return 1
1666 hg.clean(r, qpp[0])
1666 hg.clean(r, qpp[0])
1667
1667
1668 def save(self, repo, msg=None):
1668 def save(self, repo, msg=None):
1669 if not self.applied:
1669 if not self.applied:
1670 self.ui.warn(_("save: no patches applied, exiting\n"))
1670 self.ui.warn(_("save: no patches applied, exiting\n"))
1671 return 1
1671 return 1
1672 if self.issaveline(self.applied[-1]):
1672 if self.issaveline(self.applied[-1]):
1673 self.ui.warn(_("status is already saved\n"))
1673 self.ui.warn(_("status is already saved\n"))
1674 return 1
1674 return 1
1675
1675
1676 if not msg:
1676 if not msg:
1677 msg = _("hg patches saved state")
1677 msg = _("hg patches saved state")
1678 else:
1678 else:
1679 msg = "hg patches: " + msg.rstrip('\r\n')
1679 msg = "hg patches: " + msg.rstrip('\r\n')
1680 r = self.qrepo()
1680 r = self.qrepo()
1681 if r:
1681 if r:
1682 pp = r.dirstate.parents()
1682 pp = r.dirstate.parents()
1683 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1683 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1684 msg += "\n\nPatch Data:\n"
1684 msg += "\n\nPatch Data:\n"
1685 msg += ''.join('%s\n' % x for x in self.applied)
1685 msg += ''.join('%s\n' % x for x in self.applied)
1686 msg += ''.join(':%s\n' % x for x in self.full_series)
1686 msg += ''.join(':%s\n' % x for x in self.full_series)
1687 n = repo.commit(msg, force=True)
1687 n = repo.commit(msg, force=True)
1688 if not n:
1688 if not n:
1689 self.ui.warn(_("repo commit failed\n"))
1689 self.ui.warn(_("repo commit failed\n"))
1690 return 1
1690 return 1
1691 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1691 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1692 self.applied_dirty = 1
1692 self.applied_dirty = 1
1693 self.removeundo(repo)
1693 self.removeundo(repo)
1694
1694
1695 def full_series_end(self):
1695 def full_series_end(self):
1696 if self.applied:
1696 if self.applied:
1697 p = self.applied[-1].name
1697 p = self.applied[-1].name
1698 end = self.find_series(p)
1698 end = self.find_series(p)
1699 if end is None:
1699 if end is None:
1700 return len(self.full_series)
1700 return len(self.full_series)
1701 return end + 1
1701 return end + 1
1702 return 0
1702 return 0
1703
1703
1704 def series_end(self, all_patches=False):
1704 def series_end(self, all_patches=False):
1705 """If all_patches is False, return the index of the next pushable patch
1705 """If all_patches is False, return the index of the next pushable patch
1706 in the series, or the series length. If all_patches is True, return the
1706 in the series, or the series length. If all_patches is True, return the
1707 index of the first patch past the last applied one.
1707 index of the first patch past the last applied one.
1708 """
1708 """
1709 end = 0
1709 end = 0
1710 def next(start):
1710 def next(start):
1711 if all_patches or start >= len(self.series):
1711 if all_patches or start >= len(self.series):
1712 return start
1712 return start
1713 for i in xrange(start, len(self.series)):
1713 for i in xrange(start, len(self.series)):
1714 p, reason = self.pushable(i)
1714 p, reason = self.pushable(i)
1715 if p:
1715 if p:
1716 break
1716 break
1717 self.explain_pushable(i)
1717 self.explain_pushable(i)
1718 return i
1718 return i
1719 if self.applied:
1719 if self.applied:
1720 p = self.applied[-1].name
1720 p = self.applied[-1].name
1721 try:
1721 try:
1722 end = self.series.index(p)
1722 end = self.series.index(p)
1723 except ValueError:
1723 except ValueError:
1724 return 0
1724 return 0
1725 return next(end + 1)
1725 return next(end + 1)
1726 return next(end)
1726 return next(end)
1727
1727
1728 def appliedname(self, index):
1728 def appliedname(self, index):
1729 pname = self.applied[index].name
1729 pname = self.applied[index].name
1730 if not self.ui.verbose:
1730 if not self.ui.verbose:
1731 p = pname
1731 p = pname
1732 else:
1732 else:
1733 p = str(self.series.index(pname)) + " " + pname
1733 p = str(self.series.index(pname)) + " " + pname
1734 return p
1734 return p
1735
1735
1736 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1736 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1737 force=None, git=False):
1737 force=None, git=False):
1738 def checkseries(patchname):
1738 def checkseries(patchname):
1739 if patchname in self.series:
1739 if patchname in self.series:
1740 raise util.Abort(_('patch %s is already in the series file')
1740 raise util.Abort(_('patch %s is already in the series file')
1741 % patchname)
1741 % patchname)
1742 def checkfile(patchname):
1742 def checkfile(patchname):
1743 if not force and os.path.exists(self.join(patchname)):
1743 if not force and os.path.exists(self.join(patchname)):
1744 raise util.Abort(_('patch "%s" already exists')
1744 raise util.Abort(_('patch "%s" already exists')
1745 % patchname)
1745 % patchname)
1746
1746
1747 if rev:
1747 if rev:
1748 if files:
1748 if files:
1749 raise util.Abort(_('option "-r" not valid when importing '
1749 raise util.Abort(_('option "-r" not valid when importing '
1750 'files'))
1750 'files'))
1751 rev = scmutil.revrange(repo, rev)
1751 rev = scmutil.revrange(repo, rev)
1752 rev.sort(reverse=True)
1752 rev.sort(reverse=True)
1753 if (len(files) > 1 or len(rev) > 1) and patchname:
1753 if (len(files) > 1 or len(rev) > 1) and patchname:
1754 raise util.Abort(_('option "-n" not valid when importing multiple '
1754 raise util.Abort(_('option "-n" not valid when importing multiple '
1755 'patches'))
1755 'patches'))
1756 if rev:
1756 if rev:
1757 # If mq patches are applied, we can only import revisions
1757 # If mq patches are applied, we can only import revisions
1758 # that form a linear path to qbase.
1758 # that form a linear path to qbase.
1759 # Otherwise, they should form a linear path to a head.
1759 # Otherwise, they should form a linear path to a head.
1760 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1760 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1761 if len(heads) > 1:
1761 if len(heads) > 1:
1762 raise util.Abort(_('revision %d is the root of more than one '
1762 raise util.Abort(_('revision %d is the root of more than one '
1763 'branch') % rev[-1])
1763 'branch') % rev[-1])
1764 if self.applied:
1764 if self.applied:
1765 base = repo.changelog.node(rev[0])
1765 base = repo.changelog.node(rev[0])
1766 if base in [n.node for n in self.applied]:
1766 if base in [n.node for n in self.applied]:
1767 raise util.Abort(_('revision %d is already managed')
1767 raise util.Abort(_('revision %d is already managed')
1768 % rev[0])
1768 % rev[0])
1769 if heads != [self.applied[-1].node]:
1769 if heads != [self.applied[-1].node]:
1770 raise util.Abort(_('revision %d is not the parent of '
1770 raise util.Abort(_('revision %d is not the parent of '
1771 'the queue') % rev[0])
1771 'the queue') % rev[0])
1772 base = repo.changelog.rev(self.applied[0].node)
1772 base = repo.changelog.rev(self.applied[0].node)
1773 lastparent = repo.changelog.parentrevs(base)[0]
1773 lastparent = repo.changelog.parentrevs(base)[0]
1774 else:
1774 else:
1775 if heads != [repo.changelog.node(rev[0])]:
1775 if heads != [repo.changelog.node(rev[0])]:
1776 raise util.Abort(_('revision %d has unmanaged children')
1776 raise util.Abort(_('revision %d has unmanaged children')
1777 % rev[0])
1777 % rev[0])
1778 lastparent = None
1778 lastparent = None
1779
1779
1780 diffopts = self.diffopts({'git': git})
1780 diffopts = self.diffopts({'git': git})
1781 for r in rev:
1781 for r in rev:
1782 p1, p2 = repo.changelog.parentrevs(r)
1782 p1, p2 = repo.changelog.parentrevs(r)
1783 n = repo.changelog.node(r)
1783 n = repo.changelog.node(r)
1784 if p2 != nullrev:
1784 if p2 != nullrev:
1785 raise util.Abort(_('cannot import merge revision %d') % r)
1785 raise util.Abort(_('cannot import merge revision %d') % r)
1786 if lastparent and lastparent != r:
1786 if lastparent and lastparent != r:
1787 raise util.Abort(_('revision %d is not the parent of %d')
1787 raise util.Abort(_('revision %d is not the parent of %d')
1788 % (r, lastparent))
1788 % (r, lastparent))
1789 lastparent = p1
1789 lastparent = p1
1790
1790
1791 if not patchname:
1791 if not patchname:
1792 patchname = normname('%d.diff' % r)
1792 patchname = normname('%d.diff' % r)
1793 self.check_reserved_name(patchname)
1793 self.check_reserved_name(patchname)
1794 checkseries(patchname)
1794 checkseries(patchname)
1795 checkfile(patchname)
1795 checkfile(patchname)
1796 self.full_series.insert(0, patchname)
1796 self.full_series.insert(0, patchname)
1797
1797
1798 patchf = self.opener(patchname, "w")
1798 patchf = self.opener(patchname, "w")
1799 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1799 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1800 patchf.close()
1800 patchf.close()
1801
1801
1802 se = statusentry(n, patchname)
1802 se = statusentry(n, patchname)
1803 self.applied.insert(0, se)
1803 self.applied.insert(0, se)
1804
1804
1805 self.added.append(patchname)
1805 self.added.append(patchname)
1806 patchname = None
1806 patchname = None
1807 self.parse_series()
1807 self.parse_series()
1808 self.applied_dirty = 1
1808 self.applied_dirty = 1
1809 self.series_dirty = True
1809 self.series_dirty = True
1810
1810
1811 for i, filename in enumerate(files):
1811 for i, filename in enumerate(files):
1812 if existing:
1812 if existing:
1813 if filename == '-':
1813 if filename == '-':
1814 raise util.Abort(_('-e is incompatible with import from -'))
1814 raise util.Abort(_('-e is incompatible with import from -'))
1815 filename = normname(filename)
1815 filename = normname(filename)
1816 self.check_reserved_name(filename)
1816 self.check_reserved_name(filename)
1817 originpath = self.join(filename)
1817 originpath = self.join(filename)
1818 if not os.path.isfile(originpath):
1818 if not os.path.isfile(originpath):
1819 raise util.Abort(_("patch %s does not exist") % filename)
1819 raise util.Abort(_("patch %s does not exist") % filename)
1820
1820
1821 if patchname:
1821 if patchname:
1822 self.check_reserved_name(patchname)
1822 self.check_reserved_name(patchname)
1823 checkfile(patchname)
1823 checkfile(patchname)
1824
1824
1825 self.ui.write(_('renaming %s to %s\n')
1825 self.ui.write(_('renaming %s to %s\n')
1826 % (filename, patchname))
1826 % (filename, patchname))
1827 util.rename(originpath, self.join(patchname))
1827 util.rename(originpath, self.join(patchname))
1828 else:
1828 else:
1829 patchname = filename
1829 patchname = filename
1830
1830
1831 else:
1831 else:
1832 try:
1832 try:
1833 if filename == '-':
1833 if filename == '-':
1834 if not patchname:
1834 if not patchname:
1835 raise util.Abort(
1835 raise util.Abort(
1836 _('need --name to import a patch from -'))
1836 _('need --name to import a patch from -'))
1837 text = sys.stdin.read()
1837 text = sys.stdin.read()
1838 else:
1838 else:
1839 fp = url.open(self.ui, filename)
1839 fp = url.open(self.ui, filename)
1840 text = fp.read()
1840 text = fp.read()
1841 fp.close()
1841 fp.close()
1842 except (OSError, IOError):
1842 except (OSError, IOError):
1843 raise util.Abort(_("unable to read file %s") % filename)
1843 raise util.Abort(_("unable to read file %s") % filename)
1844 if not patchname:
1844 if not patchname:
1845 patchname = normname(os.path.basename(filename))
1845 patchname = normname(os.path.basename(filename))
1846 self.check_reserved_name(patchname)
1846 self.check_reserved_name(patchname)
1847 checkfile(patchname)
1847 checkfile(patchname)
1848 patchf = self.opener(patchname, "w")
1848 patchf = self.opener(patchname, "w")
1849 patchf.write(text)
1849 patchf.write(text)
1850 patchf.close()
1850 patchf.close()
1851 if not force:
1851 if not force:
1852 checkseries(patchname)
1852 checkseries(patchname)
1853 if patchname not in self.series:
1853 if patchname not in self.series:
1854 index = self.full_series_end() + i
1854 index = self.full_series_end() + i
1855 self.full_series[index:index] = [patchname]
1855 self.full_series[index:index] = [patchname]
1856 self.parse_series()
1856 self.parse_series()
1857 self.series_dirty = True
1857 self.series_dirty = True
1858 self.ui.warn(_("adding %s to series file\n") % patchname)
1858 self.ui.warn(_("adding %s to series file\n") % patchname)
1859 self.added.append(patchname)
1859 self.added.append(patchname)
1860 patchname = None
1860 patchname = None
1861
1861
1862 self.removeundo(repo)
1862 self.removeundo(repo)
1863
1863
1864 @command("qdelete|qremove|qrm",
1864 @command("qdelete|qremove|qrm",
1865 [('k', 'keep', None, _('keep patch file')),
1865 [('k', 'keep', None, _('keep patch file')),
1866 ('r', 'rev', [],
1866 ('r', 'rev', [],
1867 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1867 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1868 _('hg qdelete [-k] [PATCH]...'))
1868 _('hg qdelete [-k] [PATCH]...'))
1869 def delete(ui, repo, *patches, **opts):
1869 def delete(ui, repo, *patches, **opts):
1870 """remove patches from queue
1870 """remove patches from queue
1871
1871
1872 The patches must not be applied, and at least one patch is required. With
1872 The patches must not be applied, and at least one patch is required. With
1873 -k/--keep, the patch files are preserved in the patch directory.
1873 -k/--keep, the patch files are preserved in the patch directory.
1874
1874
1875 To stop managing a patch and move it into permanent history,
1875 To stop managing a patch and move it into permanent history,
1876 use the :hg:`qfinish` command."""
1876 use the :hg:`qfinish` command."""
1877 q = repo.mq
1877 q = repo.mq
1878 q.delete(repo, patches, opts)
1878 q.delete(repo, patches, opts)
1879 q.save_dirty()
1879 q.save_dirty()
1880 return 0
1880 return 0
1881
1881
1882 @command("qapplied",
1882 @command("qapplied",
1883 [('1', 'last', None, _('show only the last patch'))
1883 [('1', 'last', None, _('show only the last patch'))
1884 ] + seriesopts,
1884 ] + seriesopts,
1885 _('hg qapplied [-1] [-s] [PATCH]'))
1885 _('hg qapplied [-1] [-s] [PATCH]'))
1886 def applied(ui, repo, patch=None, **opts):
1886 def applied(ui, repo, patch=None, **opts):
1887 """print the patches already applied
1887 """print the patches already applied
1888
1888
1889 Returns 0 on success."""
1889 Returns 0 on success."""
1890
1890
1891 q = repo.mq
1891 q = repo.mq
1892
1892
1893 if patch:
1893 if patch:
1894 if patch not in q.series:
1894 if patch not in q.series:
1895 raise util.Abort(_("patch %s is not in series file") % patch)
1895 raise util.Abort(_("patch %s is not in series file") % patch)
1896 end = q.series.index(patch) + 1
1896 end = q.series.index(patch) + 1
1897 else:
1897 else:
1898 end = q.series_end(True)
1898 end = q.series_end(True)
1899
1899
1900 if opts.get('last') and not end:
1900 if opts.get('last') and not end:
1901 ui.write(_("no patches applied\n"))
1901 ui.write(_("no patches applied\n"))
1902 return 1
1902 return 1
1903 elif opts.get('last') and end == 1:
1903 elif opts.get('last') and end == 1:
1904 ui.write(_("only one patch applied\n"))
1904 ui.write(_("only one patch applied\n"))
1905 return 1
1905 return 1
1906 elif opts.get('last'):
1906 elif opts.get('last'):
1907 start = end - 2
1907 start = end - 2
1908 end = 1
1908 end = 1
1909 else:
1909 else:
1910 start = 0
1910 start = 0
1911
1911
1912 q.qseries(repo, length=end, start=start, status='A',
1912 q.qseries(repo, length=end, start=start, status='A',
1913 summary=opts.get('summary'))
1913 summary=opts.get('summary'))
1914
1914
1915
1915
1916 @command("qunapplied",
1916 @command("qunapplied",
1917 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1917 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1918 _('hg qunapplied [-1] [-s] [PATCH]'))
1918 _('hg qunapplied [-1] [-s] [PATCH]'))
1919 def unapplied(ui, repo, patch=None, **opts):
1919 def unapplied(ui, repo, patch=None, **opts):
1920 """print the patches not yet applied
1920 """print the patches not yet applied
1921
1921
1922 Returns 0 on success."""
1922 Returns 0 on success."""
1923
1923
1924 q = repo.mq
1924 q = repo.mq
1925 if patch:
1925 if patch:
1926 if patch not in q.series:
1926 if patch not in q.series:
1927 raise util.Abort(_("patch %s is not in series file") % patch)
1927 raise util.Abort(_("patch %s is not in series file") % patch)
1928 start = q.series.index(patch) + 1
1928 start = q.series.index(patch) + 1
1929 else:
1929 else:
1930 start = q.series_end(True)
1930 start = q.series_end(True)
1931
1931
1932 if start == len(q.series) and opts.get('first'):
1932 if start == len(q.series) and opts.get('first'):
1933 ui.write(_("all patches applied\n"))
1933 ui.write(_("all patches applied\n"))
1934 return 1
1934 return 1
1935
1935
1936 length = opts.get('first') and 1 or None
1936 length = opts.get('first') and 1 or None
1937 q.qseries(repo, start=start, length=length, status='U',
1937 q.qseries(repo, start=start, length=length, status='U',
1938 summary=opts.get('summary'))
1938 summary=opts.get('summary'))
1939
1939
1940 @command("qimport",
1940 @command("qimport",
1941 [('e', 'existing', None, _('import file in patch directory')),
1941 [('e', 'existing', None, _('import file in patch directory')),
1942 ('n', 'name', '',
1942 ('n', 'name', '',
1943 _('name of patch file'), _('NAME')),
1943 _('name of patch file'), _('NAME')),
1944 ('f', 'force', None, _('overwrite existing files')),
1944 ('f', 'force', None, _('overwrite existing files')),
1945 ('r', 'rev', [],
1945 ('r', 'rev', [],
1946 _('place existing revisions under mq control'), _('REV')),
1946 _('place existing revisions under mq control'), _('REV')),
1947 ('g', 'git', None, _('use git extended diff format')),
1947 ('g', 'git', None, _('use git extended diff format')),
1948 ('P', 'push', None, _('qpush after importing'))],
1948 ('P', 'push', None, _('qpush after importing'))],
1949 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1949 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1950 def qimport(ui, repo, *filename, **opts):
1950 def qimport(ui, repo, *filename, **opts):
1951 """import a patch
1951 """import a patch
1952
1952
1953 The patch is inserted into the series after the last applied
1953 The patch is inserted into the series after the last applied
1954 patch. If no patches have been applied, qimport prepends the patch
1954 patch. If no patches have been applied, qimport prepends the patch
1955 to the series.
1955 to the series.
1956
1956
1957 The patch will have the same name as its source file unless you
1957 The patch will have the same name as its source file unless you
1958 give it a new one with -n/--name.
1958 give it a new one with -n/--name.
1959
1959
1960 You can register an existing patch inside the patch directory with
1960 You can register an existing patch inside the patch directory with
1961 the -e/--existing flag.
1961 the -e/--existing flag.
1962
1962
1963 With -f/--force, an existing patch of the same name will be
1963 With -f/--force, an existing patch of the same name will be
1964 overwritten.
1964 overwritten.
1965
1965
1966 An existing changeset may be placed under mq control with -r/--rev
1966 An existing changeset may be placed under mq control with -r/--rev
1967 (e.g. qimport --rev tip -n patch will place tip under mq control).
1967 (e.g. qimport --rev tip -n patch will place tip under mq control).
1968 With -g/--git, patches imported with --rev will use the git diff
1968 With -g/--git, patches imported with --rev will use the git diff
1969 format. See the diffs help topic for information on why this is
1969 format. See the diffs help topic for information on why this is
1970 important for preserving rename/copy information and permission
1970 important for preserving rename/copy information and permission
1971 changes. Use :hg:`qfinish` to remove changesets from mq control.
1971 changes. Use :hg:`qfinish` to remove changesets from mq control.
1972
1972
1973 To import a patch from standard input, pass - as the patch file.
1973 To import a patch from standard input, pass - as the patch file.
1974 When importing from standard input, a patch name must be specified
1974 When importing from standard input, a patch name must be specified
1975 using the --name flag.
1975 using the --name flag.
1976
1976
1977 To import an existing patch while renaming it::
1977 To import an existing patch while renaming it::
1978
1978
1979 hg qimport -e existing-patch -n new-name
1979 hg qimport -e existing-patch -n new-name
1980
1980
1981 Returns 0 if import succeeded.
1981 Returns 0 if import succeeded.
1982 """
1982 """
1983 q = repo.mq
1983 q = repo.mq
1984 try:
1984 try:
1985 q.qimport(repo, filename, patchname=opts.get('name'),
1985 q.qimport(repo, filename, patchname=opts.get('name'),
1986 existing=opts.get('existing'), force=opts.get('force'),
1986 existing=opts.get('existing'), force=opts.get('force'),
1987 rev=opts.get('rev'), git=opts.get('git'))
1987 rev=opts.get('rev'), git=opts.get('git'))
1988 finally:
1988 finally:
1989 q.save_dirty()
1989 q.save_dirty()
1990
1990
1991 if opts.get('push') and not opts.get('rev'):
1991 if opts.get('push') and not opts.get('rev'):
1992 return q.push(repo, None)
1992 return q.push(repo, None)
1993 return 0
1993 return 0
1994
1994
1995 def qinit(ui, repo, create):
1995 def qinit(ui, repo, create):
1996 """initialize a new queue repository
1996 """initialize a new queue repository
1997
1997
1998 This command also creates a series file for ordering patches, and
1998 This command also creates a series file for ordering patches, and
1999 an mq-specific .hgignore file in the queue repository, to exclude
1999 an mq-specific .hgignore file in the queue repository, to exclude
2000 the status and guards files (these contain mostly transient state).
2000 the status and guards files (these contain mostly transient state).
2001
2001
2002 Returns 0 if initialization succeeded."""
2002 Returns 0 if initialization succeeded."""
2003 q = repo.mq
2003 q = repo.mq
2004 r = q.init(repo, create)
2004 r = q.init(repo, create)
2005 q.save_dirty()
2005 q.save_dirty()
2006 if r:
2006 if r:
2007 if not os.path.exists(r.wjoin('.hgignore')):
2007 if not os.path.exists(r.wjoin('.hgignore')):
2008 fp = r.wopener('.hgignore', 'w')
2008 fp = r.wopener('.hgignore', 'w')
2009 fp.write('^\\.hg\n')
2009 fp.write('^\\.hg\n')
2010 fp.write('^\\.mq\n')
2010 fp.write('^\\.mq\n')
2011 fp.write('syntax: glob\n')
2011 fp.write('syntax: glob\n')
2012 fp.write('status\n')
2012 fp.write('status\n')
2013 fp.write('guards\n')
2013 fp.write('guards\n')
2014 fp.close()
2014 fp.close()
2015 if not os.path.exists(r.wjoin('series')):
2015 if not os.path.exists(r.wjoin('series')):
2016 r.wopener('series', 'w').close()
2016 r.wopener('series', 'w').close()
2017 r[None].add(['.hgignore', 'series'])
2017 r[None].add(['.hgignore', 'series'])
2018 commands.add(ui, r)
2018 commands.add(ui, r)
2019 return 0
2019 return 0
2020
2020
2021 @command("^qinit",
2021 @command("^qinit",
2022 [('c', 'create-repo', None, _('create queue repository'))],
2022 [('c', 'create-repo', None, _('create queue repository'))],
2023 _('hg qinit [-c]'))
2023 _('hg qinit [-c]'))
2024 def init(ui, repo, **opts):
2024 def init(ui, repo, **opts):
2025 """init a new queue repository (DEPRECATED)
2025 """init a new queue repository (DEPRECATED)
2026
2026
2027 The queue repository is unversioned by default. If
2027 The queue repository is unversioned by default. If
2028 -c/--create-repo is specified, qinit will create a separate nested
2028 -c/--create-repo is specified, qinit will create a separate nested
2029 repository for patches (qinit -c may also be run later to convert
2029 repository for patches (qinit -c may also be run later to convert
2030 an unversioned patch repository into a versioned one). You can use
2030 an unversioned patch repository into a versioned one). You can use
2031 qcommit to commit changes to this queue repository.
2031 qcommit to commit changes to this queue repository.
2032
2032
2033 This command is deprecated. Without -c, it's implied by other relevant
2033 This command is deprecated. Without -c, it's implied by other relevant
2034 commands. With -c, use :hg:`init --mq` instead."""
2034 commands. With -c, use :hg:`init --mq` instead."""
2035 return qinit(ui, repo, create=opts.get('create_repo'))
2035 return qinit(ui, repo, create=opts.get('create_repo'))
2036
2036
2037 @command("qclone",
2037 @command("qclone",
2038 [('', 'pull', None, _('use pull protocol to copy metadata')),
2038 [('', 'pull', None, _('use pull protocol to copy metadata')),
2039 ('U', 'noupdate', None, _('do not update the new working directories')),
2039 ('U', 'noupdate', None, _('do not update the new working directories')),
2040 ('', 'uncompressed', None,
2040 ('', 'uncompressed', None,
2041 _('use uncompressed transfer (fast over LAN)')),
2041 _('use uncompressed transfer (fast over LAN)')),
2042 ('p', 'patches', '',
2042 ('p', 'patches', '',
2043 _('location of source patch repository'), _('REPO')),
2043 _('location of source patch repository'), _('REPO')),
2044 ] + commands.remoteopts,
2044 ] + commands.remoteopts,
2045 _('hg qclone [OPTION]... SOURCE [DEST]'))
2045 _('hg qclone [OPTION]... SOURCE [DEST]'))
2046 def clone(ui, source, dest=None, **opts):
2046 def clone(ui, source, dest=None, **opts):
2047 '''clone main and patch repository at same time
2047 '''clone main and patch repository at same time
2048
2048
2049 If source is local, destination will have no patches applied. If
2049 If source is local, destination will have no patches applied. If
2050 source is remote, this command can not check if patches are
2050 source is remote, this command can not check if patches are
2051 applied in source, so cannot guarantee that patches are not
2051 applied in source, so cannot guarantee that patches are not
2052 applied in destination. If you clone remote repository, be sure
2052 applied in destination. If you clone remote repository, be sure
2053 before that it has no patches applied.
2053 before that it has no patches applied.
2054
2054
2055 Source patch repository is looked for in <src>/.hg/patches by
2055 Source patch repository is looked for in <src>/.hg/patches by
2056 default. Use -p <url> to change.
2056 default. Use -p <url> to change.
2057
2057
2058 The patch directory must be a nested Mercurial repository, as
2058 The patch directory must be a nested Mercurial repository, as
2059 would be created by :hg:`init --mq`.
2059 would be created by :hg:`init --mq`.
2060
2060
2061 Return 0 on success.
2061 Return 0 on success.
2062 '''
2062 '''
2063 def patchdir(repo):
2063 def patchdir(repo):
2064 url = repo.url()
2064 url = repo.url()
2065 if url.endswith('/'):
2065 if url.endswith('/'):
2066 url = url[:-1]
2066 url = url[:-1]
2067 return url + '/.hg/patches'
2067 return url + '/.hg/patches'
2068 if dest is None:
2068 if dest is None:
2069 dest = hg.defaultdest(source)
2069 dest = hg.defaultdest(source)
2070 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2070 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2071 if opts.get('patches'):
2071 if opts.get('patches'):
2072 patchespath = ui.expandpath(opts.get('patches'))
2072 patchespath = ui.expandpath(opts.get('patches'))
2073 else:
2073 else:
2074 patchespath = patchdir(sr)
2074 patchespath = patchdir(sr)
2075 try:
2075 try:
2076 hg.repository(ui, patchespath)
2076 hg.repository(ui, patchespath)
2077 except error.RepoError:
2077 except error.RepoError:
2078 raise util.Abort(_('versioned patch repository not found'
2078 raise util.Abort(_('versioned patch repository not found'
2079 ' (see init --mq)'))
2079 ' (see init --mq)'))
2080 qbase, destrev = None, None
2080 qbase, destrev = None, None
2081 if sr.local():
2081 if sr.local():
2082 if sr.mq.applied:
2082 if sr.mq.applied:
2083 qbase = sr.mq.applied[0].node
2083 qbase = sr.mq.applied[0].node
2084 if not hg.islocal(dest):
2084 if not hg.islocal(dest):
2085 heads = set(sr.heads())
2085 heads = set(sr.heads())
2086 destrev = list(heads.difference(sr.heads(qbase)))
2086 destrev = list(heads.difference(sr.heads(qbase)))
2087 destrev.append(sr.changelog.parents(qbase)[0])
2087 destrev.append(sr.changelog.parents(qbase)[0])
2088 elif sr.capable('lookup'):
2088 elif sr.capable('lookup'):
2089 try:
2089 try:
2090 qbase = sr.lookup('qbase')
2090 qbase = sr.lookup('qbase')
2091 except error.RepoError:
2091 except error.RepoError:
2092 pass
2092 pass
2093 ui.note(_('cloning main repository\n'))
2093 ui.note(_('cloning main repository\n'))
2094 sr, dr = hg.clone(ui, sr.url(), dest,
2094 sr, dr = hg.clone(ui, sr.url(), dest,
2095 pull=opts.get('pull'),
2095 pull=opts.get('pull'),
2096 rev=destrev,
2096 rev=destrev,
2097 update=False,
2097 update=False,
2098 stream=opts.get('uncompressed'))
2098 stream=opts.get('uncompressed'))
2099 ui.note(_('cloning patch repository\n'))
2099 ui.note(_('cloning patch repository\n'))
2100 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2100 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2101 pull=opts.get('pull'), update=not opts.get('noupdate'),
2101 pull=opts.get('pull'), update=not opts.get('noupdate'),
2102 stream=opts.get('uncompressed'))
2102 stream=opts.get('uncompressed'))
2103 if dr.local():
2103 if dr.local():
2104 if qbase:
2104 if qbase:
2105 ui.note(_('stripping applied patches from destination '
2105 ui.note(_('stripping applied patches from destination '
2106 'repository\n'))
2106 'repository\n'))
2107 dr.mq.strip(dr, [qbase], update=False, backup=None)
2107 dr.mq.strip(dr, [qbase], update=False, backup=None)
2108 if not opts.get('noupdate'):
2108 if not opts.get('noupdate'):
2109 ui.note(_('updating destination repository\n'))
2109 ui.note(_('updating destination repository\n'))
2110 hg.update(dr, dr.changelog.tip())
2110 hg.update(dr, dr.changelog.tip())
2111
2111
2112 @command("qcommit|qci",
2112 @command("qcommit|qci",
2113 commands.table["^commit|ci"][1],
2113 commands.table["^commit|ci"][1],
2114 _('hg qcommit [OPTION]... [FILE]...'))
2114 _('hg qcommit [OPTION]... [FILE]...'))
2115 def commit(ui, repo, *pats, **opts):
2115 def commit(ui, repo, *pats, **opts):
2116 """commit changes in the queue repository (DEPRECATED)
2116 """commit changes in the queue repository (DEPRECATED)
2117
2117
2118 This command is deprecated; use :hg:`commit --mq` instead."""
2118 This command is deprecated; use :hg:`commit --mq` instead."""
2119 q = repo.mq
2119 q = repo.mq
2120 r = q.qrepo()
2120 r = q.qrepo()
2121 if not r:
2121 if not r:
2122 raise util.Abort('no queue repository')
2122 raise util.Abort('no queue repository')
2123 commands.commit(r.ui, r, *pats, **opts)
2123 commands.commit(r.ui, r, *pats, **opts)
2124
2124
2125 @command("qseries",
2125 @command("qseries",
2126 [('m', 'missing', None, _('print patches not in series')),
2126 [('m', 'missing', None, _('print patches not in series')),
2127 ] + seriesopts,
2127 ] + seriesopts,
2128 _('hg qseries [-ms]'))
2128 _('hg qseries [-ms]'))
2129 def series(ui, repo, **opts):
2129 def series(ui, repo, **opts):
2130 """print the entire series file
2130 """print the entire series file
2131
2131
2132 Returns 0 on success."""
2132 Returns 0 on success."""
2133 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2133 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2134 return 0
2134 return 0
2135
2135
2136 @command("qtop", [] + seriesopts, _('hg qtop [-s]'))
2136 @command("qtop", [] + seriesopts, _('hg qtop [-s]'))
2137 def top(ui, repo, **opts):
2137 def top(ui, repo, **opts):
2138 """print the name of the current patch
2138 """print the name of the current patch
2139
2139
2140 Returns 0 on success."""
2140 Returns 0 on success."""
2141 q = repo.mq
2141 q = repo.mq
2142 t = q.applied and q.series_end(True) or 0
2142 t = q.applied and q.series_end(True) or 0
2143 if t:
2143 if t:
2144 q.qseries(repo, start=t - 1, length=1, status='A',
2144 q.qseries(repo, start=t - 1, length=1, status='A',
2145 summary=opts.get('summary'))
2145 summary=opts.get('summary'))
2146 else:
2146 else:
2147 ui.write(_("no patches applied\n"))
2147 ui.write(_("no patches applied\n"))
2148 return 1
2148 return 1
2149
2149
2150 @command("qnext", [] + seriesopts, _('hg qnext [-s]'))
2150 @command("qnext", [] + seriesopts, _('hg qnext [-s]'))
2151 def next(ui, repo, **opts):
2151 def next(ui, repo, **opts):
2152 """print the name of the next patch
2152 """print the name of the next patch
2153
2153
2154 Returns 0 on success."""
2154 Returns 0 on success."""
2155 q = repo.mq
2155 q = repo.mq
2156 end = q.series_end()
2156 end = q.series_end()
2157 if end == len(q.series):
2157 if end == len(q.series):
2158 ui.write(_("all patches applied\n"))
2158 ui.write(_("all patches applied\n"))
2159 return 1
2159 return 1
2160 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2160 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2161
2161
2162 @command("qprev", [] + seriesopts, _('hg qprev [-s]'))
2162 @command("qprev", [] + seriesopts, _('hg qprev [-s]'))
2163 def prev(ui, repo, **opts):
2163 def prev(ui, repo, **opts):
2164 """print the name of the previous patch
2164 """print the name of the previous patch
2165
2165
2166 Returns 0 on success."""
2166 Returns 0 on success."""
2167 q = repo.mq
2167 q = repo.mq
2168 l = len(q.applied)
2168 l = len(q.applied)
2169 if l == 1:
2169 if l == 1:
2170 ui.write(_("only one patch applied\n"))
2170 ui.write(_("only one patch applied\n"))
2171 return 1
2171 return 1
2172 if not l:
2172 if not l:
2173 ui.write(_("no patches applied\n"))
2173 ui.write(_("no patches applied\n"))
2174 return 1
2174 return 1
2175 q.qseries(repo, start=l - 2, length=1, status='A',
2175 q.qseries(repo, start=l - 2, length=1, status='A',
2176 summary=opts.get('summary'))
2176 summary=opts.get('summary'))
2177
2177
2178 def setupheaderopts(ui, opts):
2178 def setupheaderopts(ui, opts):
2179 if not opts.get('user') and opts.get('currentuser'):
2179 if not opts.get('user') and opts.get('currentuser'):
2180 opts['user'] = ui.username()
2180 opts['user'] = ui.username()
2181 if not opts.get('date') and opts.get('currentdate'):
2181 if not opts.get('date') and opts.get('currentdate'):
2182 opts['date'] = "%d %d" % util.makedate()
2182 opts['date'] = "%d %d" % util.makedate()
2183
2183
2184 @command("^qnew",
2184 @command("^qnew",
2185 [('e', 'edit', None, _('edit commit message')),
2185 [('e', 'edit', None, _('edit commit message')),
2186 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2186 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2187 ('g', 'git', None, _('use git extended diff format')),
2187 ('g', 'git', None, _('use git extended diff format')),
2188 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2188 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2189 ('u', 'user', '',
2189 ('u', 'user', '',
2190 _('add "From: <USER>" to patch'), _('USER')),
2190 _('add "From: <USER>" to patch'), _('USER')),
2191 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2191 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2192 ('d', 'date', '',
2192 ('d', 'date', '',
2193 _('add "Date: <DATE>" to patch'), _('DATE'))
2193 _('add "Date: <DATE>" to patch'), _('DATE'))
2194 ] + commands.walkopts + commands.commitopts,
2194 ] + commands.walkopts + commands.commitopts,
2195 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2195 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2196 def new(ui, repo, patch, *args, **opts):
2196 def new(ui, repo, patch, *args, **opts):
2197 """create a new patch
2197 """create a new patch
2198
2198
2199 qnew creates a new patch on top of the currently-applied patch (if
2199 qnew creates a new patch on top of the currently-applied patch (if
2200 any). The patch will be initialized with any outstanding changes
2200 any). The patch will be initialized with any outstanding changes
2201 in the working directory. You may also use -I/--include,
2201 in the working directory. You may also use -I/--include,
2202 -X/--exclude, and/or a list of files after the patch name to add
2202 -X/--exclude, and/or a list of files after the patch name to add
2203 only changes to matching files to the new patch, leaving the rest
2203 only changes to matching files to the new patch, leaving the rest
2204 as uncommitted modifications.
2204 as uncommitted modifications.
2205
2205
2206 -u/--user and -d/--date can be used to set the (given) user and
2206 -u/--user and -d/--date can be used to set the (given) user and
2207 date, respectively. -U/--currentuser and -D/--currentdate set user
2207 date, respectively. -U/--currentuser and -D/--currentdate set user
2208 to current user and date to current date.
2208 to current user and date to current date.
2209
2209
2210 -e/--edit, -m/--message or -l/--logfile set the patch header as
2210 -e/--edit, -m/--message or -l/--logfile set the patch header as
2211 well as the commit message. If none is specified, the header is
2211 well as the commit message. If none is specified, the header is
2212 empty and the commit message is '[mq]: PATCH'.
2212 empty and the commit message is '[mq]: PATCH'.
2213
2213
2214 Use the -g/--git option to keep the patch in the git extended diff
2214 Use the -g/--git option to keep the patch in the git extended diff
2215 format. Read the diffs help topic for more information on why this
2215 format. Read the diffs help topic for more information on why this
2216 is important for preserving permission changes and copy/rename
2216 is important for preserving permission changes and copy/rename
2217 information.
2217 information.
2218
2218
2219 Returns 0 on successful creation of a new patch.
2219 Returns 0 on successful creation of a new patch.
2220 """
2220 """
2221 msg = cmdutil.logmessage(opts)
2221 msg = cmdutil.logmessage(opts)
2222 def getmsg():
2222 def getmsg():
2223 return ui.edit(msg, opts.get('user') or ui.username())
2223 return ui.edit(msg, opts.get('user') or ui.username())
2224 q = repo.mq
2224 q = repo.mq
2225 opts['msg'] = msg
2225 opts['msg'] = msg
2226 if opts.get('edit'):
2226 if opts.get('edit'):
2227 opts['msg'] = getmsg
2227 opts['msg'] = getmsg
2228 else:
2228 else:
2229 opts['msg'] = msg
2229 opts['msg'] = msg
2230 setupheaderopts(ui, opts)
2230 setupheaderopts(ui, opts)
2231 q.new(repo, patch, *args, **opts)
2231 q.new(repo, patch, *args, **opts)
2232 q.save_dirty()
2232 q.save_dirty()
2233 return 0
2233 return 0
2234
2234
2235 @command("^qrefresh",
2235 @command("^qrefresh",
2236 [('e', 'edit', None, _('edit commit message')),
2236 [('e', 'edit', None, _('edit commit message')),
2237 ('g', 'git', None, _('use git extended diff format')),
2237 ('g', 'git', None, _('use git extended diff format')),
2238 ('s', 'short', None,
2238 ('s', 'short', None,
2239 _('refresh only files already in the patch and specified files')),
2239 _('refresh only files already in the patch and specified files')),
2240 ('U', 'currentuser', None,
2240 ('U', 'currentuser', None,
2241 _('add/update author field in patch with current user')),
2241 _('add/update author field in patch with current user')),
2242 ('u', 'user', '',
2242 ('u', 'user', '',
2243 _('add/update author field in patch with given user'), _('USER')),
2243 _('add/update author field in patch with given user'), _('USER')),
2244 ('D', 'currentdate', None,
2244 ('D', 'currentdate', None,
2245 _('add/update date field in patch with current date')),
2245 _('add/update date field in patch with current date')),
2246 ('d', 'date', '',
2246 ('d', 'date', '',
2247 _('add/update date field in patch with given date'), _('DATE'))
2247 _('add/update date field in patch with given date'), _('DATE'))
2248 ] + commands.walkopts + commands.commitopts,
2248 ] + commands.walkopts + commands.commitopts,
2249 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2249 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2250 def refresh(ui, repo, *pats, **opts):
2250 def refresh(ui, repo, *pats, **opts):
2251 """update the current patch
2251 """update the current patch
2252
2252
2253 If any file patterns are provided, the refreshed patch will
2253 If any file patterns are provided, the refreshed patch will
2254 contain only the modifications that match those patterns; the
2254 contain only the modifications that match those patterns; the
2255 remaining modifications will remain in the working directory.
2255 remaining modifications will remain in the working directory.
2256
2256
2257 If -s/--short is specified, files currently included in the patch
2257 If -s/--short is specified, files currently included in the patch
2258 will be refreshed just like matched files and remain in the patch.
2258 will be refreshed just like matched files and remain in the patch.
2259
2259
2260 If -e/--edit is specified, Mercurial will start your configured editor for
2260 If -e/--edit is specified, Mercurial will start your configured editor for
2261 you to enter a message. In case qrefresh fails, you will find a backup of
2261 you to enter a message. In case qrefresh fails, you will find a backup of
2262 your message in ``.hg/last-message.txt``.
2262 your message in ``.hg/last-message.txt``.
2263
2263
2264 hg add/remove/copy/rename work as usual, though you might want to
2264 hg add/remove/copy/rename work as usual, though you might want to
2265 use git-style patches (-g/--git or [diff] git=1) to track copies
2265 use git-style patches (-g/--git or [diff] git=1) to track copies
2266 and renames. See the diffs help topic for more information on the
2266 and renames. See the diffs help topic for more information on the
2267 git diff format.
2267 git diff format.
2268
2268
2269 Returns 0 on success.
2269 Returns 0 on success.
2270 """
2270 """
2271 q = repo.mq
2271 q = repo.mq
2272 message = cmdutil.logmessage(opts)
2272 message = cmdutil.logmessage(opts)
2273 if opts.get('edit'):
2273 if opts.get('edit'):
2274 if not q.applied:
2274 if not q.applied:
2275 ui.write(_("no patches applied\n"))
2275 ui.write(_("no patches applied\n"))
2276 return 1
2276 return 1
2277 if message:
2277 if message:
2278 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2278 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2279 patch = q.applied[-1].name
2279 patch = q.applied[-1].name
2280 ph = patchheader(q.join(patch), q.plainmode)
2280 ph = patchheader(q.join(patch), q.plainmode)
2281 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2281 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2282 # We don't want to lose the patch message if qrefresh fails (issue2062)
2282 # We don't want to lose the patch message if qrefresh fails (issue2062)
2283 msgfile = repo.opener('last-message.txt', 'wb')
2283 msgfile = repo.opener('last-message.txt', 'wb')
2284 msgfile.write(message)
2284 msgfile.write(message)
2285 msgfile.close()
2285 msgfile.close()
2286 setupheaderopts(ui, opts)
2286 setupheaderopts(ui, opts)
2287 ret = q.refresh(repo, pats, msg=message, **opts)
2287 ret = q.refresh(repo, pats, msg=message, **opts)
2288 q.save_dirty()
2288 q.save_dirty()
2289 return ret
2289 return ret
2290
2290
2291 @command("^qdiff",
2291 @command("^qdiff",
2292 commands.diffopts + commands.diffopts2 + commands.walkopts,
2292 commands.diffopts + commands.diffopts2 + commands.walkopts,
2293 _('hg qdiff [OPTION]... [FILE]...'))
2293 _('hg qdiff [OPTION]... [FILE]...'))
2294 def diff(ui, repo, *pats, **opts):
2294 def diff(ui, repo, *pats, **opts):
2295 """diff of the current patch and subsequent modifications
2295 """diff of the current patch and subsequent modifications
2296
2296
2297 Shows a diff which includes the current patch as well as any
2297 Shows a diff which includes the current patch as well as any
2298 changes which have been made in the working directory since the
2298 changes which have been made in the working directory since the
2299 last refresh (thus showing what the current patch would become
2299 last refresh (thus showing what the current patch would become
2300 after a qrefresh).
2300 after a qrefresh).
2301
2301
2302 Use :hg:`diff` if you only want to see the changes made since the
2302 Use :hg:`diff` if you only want to see the changes made since the
2303 last qrefresh, or :hg:`export qtip` if you want to see changes
2303 last qrefresh, or :hg:`export qtip` if you want to see changes
2304 made by the current patch without including changes made since the
2304 made by the current patch without including changes made since the
2305 qrefresh.
2305 qrefresh.
2306
2306
2307 Returns 0 on success.
2307 Returns 0 on success.
2308 """
2308 """
2309 repo.mq.diff(repo, pats, opts)
2309 repo.mq.diff(repo, pats, opts)
2310 return 0
2310 return 0
2311
2311
2312 @command('qfold',
2312 @command('qfold',
2313 [('e', 'edit', None, _('edit patch header')),
2313 [('e', 'edit', None, _('edit patch header')),
2314 ('k', 'keep', None, _('keep folded patch files')),
2314 ('k', 'keep', None, _('keep folded patch files')),
2315 ] + commands.commitopts,
2315 ] + commands.commitopts,
2316 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2316 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2317 def fold(ui, repo, *files, **opts):
2317 def fold(ui, repo, *files, **opts):
2318 """fold the named patches into the current patch
2318 """fold the named patches into the current patch
2319
2319
2320 Patches must not yet be applied. Each patch will be successively
2320 Patches must not yet be applied. Each patch will be successively
2321 applied to the current patch in the order given. If all the
2321 applied to the current patch in the order given. If all the
2322 patches apply successfully, the current patch will be refreshed
2322 patches apply successfully, the current patch will be refreshed
2323 with the new cumulative patch, and the folded patches will be
2323 with the new cumulative patch, and the folded patches will be
2324 deleted. With -k/--keep, the folded patch files will not be
2324 deleted. With -k/--keep, the folded patch files will not be
2325 removed afterwards.
2325 removed afterwards.
2326
2326
2327 The header for each folded patch will be concatenated with the
2327 The header for each folded patch will be concatenated with the
2328 current patch header, separated by a line of ``* * *``.
2328 current patch header, separated by a line of ``* * *``.
2329
2329
2330 Returns 0 on success."""
2330 Returns 0 on success."""
2331
2331
2332 q = repo.mq
2332 q = repo.mq
2333
2333
2334 if not files:
2334 if not files:
2335 raise util.Abort(_('qfold requires at least one patch name'))
2335 raise util.Abort(_('qfold requires at least one patch name'))
2336 if not q.check_toppatch(repo)[0]:
2336 if not q.check_toppatch(repo)[0]:
2337 raise util.Abort(_('no patches applied'))
2337 raise util.Abort(_('no patches applied'))
2338 q.check_localchanges(repo)
2338 q.check_localchanges(repo)
2339
2339
2340 message = cmdutil.logmessage(opts)
2340 message = cmdutil.logmessage(opts)
2341 if opts.get('edit'):
2341 if opts.get('edit'):
2342 if message:
2342 if message:
2343 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2343 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2344
2344
2345 parent = q.lookup('qtip')
2345 parent = q.lookup('qtip')
2346 patches = []
2346 patches = []
2347 messages = []
2347 messages = []
2348 for f in files:
2348 for f in files:
2349 p = q.lookup(f)
2349 p = q.lookup(f)
2350 if p in patches or p == parent:
2350 if p in patches or p == parent:
2351 ui.warn(_('Skipping already folded patch %s\n') % p)
2351 ui.warn(_('Skipping already folded patch %s\n') % p)
2352 if q.isapplied(p):
2352 if q.isapplied(p):
2353 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2353 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2354 patches.append(p)
2354 patches.append(p)
2355
2355
2356 for p in patches:
2356 for p in patches:
2357 if not message:
2357 if not message:
2358 ph = patchheader(q.join(p), q.plainmode)
2358 ph = patchheader(q.join(p), q.plainmode)
2359 if ph.message:
2359 if ph.message:
2360 messages.append(ph.message)
2360 messages.append(ph.message)
2361 pf = q.join(p)
2361 pf = q.join(p)
2362 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2362 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2363 if not patchsuccess:
2363 if not patchsuccess:
2364 raise util.Abort(_('error folding patch %s') % p)
2364 raise util.Abort(_('error folding patch %s') % p)
2365
2365
2366 if not message:
2366 if not message:
2367 ph = patchheader(q.join(parent), q.plainmode)
2367 ph = patchheader(q.join(parent), q.plainmode)
2368 message, user = ph.message, ph.user
2368 message, user = ph.message, ph.user
2369 for msg in messages:
2369 for msg in messages:
2370 message.append('* * *')
2370 message.append('* * *')
2371 message.extend(msg)
2371 message.extend(msg)
2372 message = '\n'.join(message)
2372 message = '\n'.join(message)
2373
2373
2374 if opts.get('edit'):
2374 if opts.get('edit'):
2375 message = ui.edit(message, user or ui.username())
2375 message = ui.edit(message, user or ui.username())
2376
2376
2377 diffopts = q.patchopts(q.diffopts(), *patches)
2377 diffopts = q.patchopts(q.diffopts(), *patches)
2378 q.refresh(repo, msg=message, git=diffopts.git)
2378 q.refresh(repo, msg=message, git=diffopts.git)
2379 q.delete(repo, patches, opts)
2379 q.delete(repo, patches, opts)
2380 q.save_dirty()
2380 q.save_dirty()
2381
2381
2382 @command("qgoto",
2382 @command("qgoto",
2383 [('f', 'force', None, _('overwrite any local changes'))],
2383 [('f', 'force', None, _('overwrite any local changes'))],
2384 _('hg qgoto [OPTION]... PATCH'))
2384 _('hg qgoto [OPTION]... PATCH'))
2385 def goto(ui, repo, patch, **opts):
2385 def goto(ui, repo, patch, **opts):
2386 '''push or pop patches until named patch is at top of stack
2386 '''push or pop patches until named patch is at top of stack
2387
2387
2388 Returns 0 on success.'''
2388 Returns 0 on success.'''
2389 q = repo.mq
2389 q = repo.mq
2390 patch = q.lookup(patch)
2390 patch = q.lookup(patch)
2391 if q.isapplied(patch):
2391 if q.isapplied(patch):
2392 ret = q.pop(repo, patch, force=opts.get('force'))
2392 ret = q.pop(repo, patch, force=opts.get('force'))
2393 else:
2393 else:
2394 ret = q.push(repo, patch, force=opts.get('force'))
2394 ret = q.push(repo, patch, force=opts.get('force'))
2395 q.save_dirty()
2395 q.save_dirty()
2396 return ret
2396 return ret
2397
2397
2398 @command("qguard",
2398 @command("qguard",
2399 [('l', 'list', None, _('list all patches and guards')),
2399 [('l', 'list', None, _('list all patches and guards')),
2400 ('n', 'none', None, _('drop all guards'))],
2400 ('n', 'none', None, _('drop all guards'))],
2401 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2401 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2402 def guard(ui, repo, *args, **opts):
2402 def guard(ui, repo, *args, **opts):
2403 '''set or print guards for a patch
2403 '''set or print guards for a patch
2404
2404
2405 Guards control whether a patch can be pushed. A patch with no
2405 Guards control whether a patch can be pushed. A patch with no
2406 guards is always pushed. A patch with a positive guard ("+foo") is
2406 guards is always pushed. A patch with a positive guard ("+foo") is
2407 pushed only if the :hg:`qselect` command has activated it. A patch with
2407 pushed only if the :hg:`qselect` command has activated it. A patch with
2408 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2408 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2409 has activated it.
2409 has activated it.
2410
2410
2411 With no arguments, print the currently active guards.
2411 With no arguments, print the currently active guards.
2412 With arguments, set guards for the named patch.
2412 With arguments, set guards for the named patch.
2413
2413
2414 .. note::
2414 .. note::
2415 Specifying negative guards now requires '--'.
2415 Specifying negative guards now requires '--'.
2416
2416
2417 To set guards on another patch::
2417 To set guards on another patch::
2418
2418
2419 hg qguard other.patch -- +2.6.17 -stable
2419 hg qguard other.patch -- +2.6.17 -stable
2420
2420
2421 Returns 0 on success.
2421 Returns 0 on success.
2422 '''
2422 '''
2423 def status(idx):
2423 def status(idx):
2424 guards = q.series_guards[idx] or ['unguarded']
2424 guards = q.series_guards[idx] or ['unguarded']
2425 if q.series[idx] in applied:
2425 if q.series[idx] in applied:
2426 state = 'applied'
2426 state = 'applied'
2427 elif q.pushable(idx)[0]:
2427 elif q.pushable(idx)[0]:
2428 state = 'unapplied'
2428 state = 'unapplied'
2429 else:
2429 else:
2430 state = 'guarded'
2430 state = 'guarded'
2431 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2431 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2432 ui.write('%s: ' % ui.label(q.series[idx], label))
2432 ui.write('%s: ' % ui.label(q.series[idx], label))
2433
2433
2434 for i, guard in enumerate(guards):
2434 for i, guard in enumerate(guards):
2435 if guard.startswith('+'):
2435 if guard.startswith('+'):
2436 ui.write(guard, label='qguard.positive')
2436 ui.write(guard, label='qguard.positive')
2437 elif guard.startswith('-'):
2437 elif guard.startswith('-'):
2438 ui.write(guard, label='qguard.negative')
2438 ui.write(guard, label='qguard.negative')
2439 else:
2439 else:
2440 ui.write(guard, label='qguard.unguarded')
2440 ui.write(guard, label='qguard.unguarded')
2441 if i != len(guards) - 1:
2441 if i != len(guards) - 1:
2442 ui.write(' ')
2442 ui.write(' ')
2443 ui.write('\n')
2443 ui.write('\n')
2444 q = repo.mq
2444 q = repo.mq
2445 applied = set(p.name for p in q.applied)
2445 applied = set(p.name for p in q.applied)
2446 patch = None
2446 patch = None
2447 args = list(args)
2447 args = list(args)
2448 if opts.get('list'):
2448 if opts.get('list'):
2449 if args or opts.get('none'):
2449 if args or opts.get('none'):
2450 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2450 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2451 for i in xrange(len(q.series)):
2451 for i in xrange(len(q.series)):
2452 status(i)
2452 status(i)
2453 return
2453 return
2454 if not args or args[0][0:1] in '-+':
2454 if not args or args[0][0:1] in '-+':
2455 if not q.applied:
2455 if not q.applied:
2456 raise util.Abort(_('no patches applied'))
2456 raise util.Abort(_('no patches applied'))
2457 patch = q.applied[-1].name
2457 patch = q.applied[-1].name
2458 if patch is None and args[0][0:1] not in '-+':
2458 if patch is None and args[0][0:1] not in '-+':
2459 patch = args.pop(0)
2459 patch = args.pop(0)
2460 if patch is None:
2460 if patch is None:
2461 raise util.Abort(_('no patch to work with'))
2461 raise util.Abort(_('no patch to work with'))
2462 if args or opts.get('none'):
2462 if args or opts.get('none'):
2463 idx = q.find_series(patch)
2463 idx = q.find_series(patch)
2464 if idx is None:
2464 if idx is None:
2465 raise util.Abort(_('no patch named %s') % patch)
2465 raise util.Abort(_('no patch named %s') % patch)
2466 q.set_guards(idx, args)
2466 q.set_guards(idx, args)
2467 q.save_dirty()
2467 q.save_dirty()
2468 else:
2468 else:
2469 status(q.series.index(q.lookup(patch)))
2469 status(q.series.index(q.lookup(patch)))
2470
2470
2471 @command("qheader", [], _('hg qheader [PATCH]'))
2471 @command("qheader", [], _('hg qheader [PATCH]'))
2472 def header(ui, repo, patch=None):
2472 def header(ui, repo, patch=None):
2473 """print the header of the topmost or specified patch
2473 """print the header of the topmost or specified patch
2474
2474
2475 Returns 0 on success."""
2475 Returns 0 on success."""
2476 q = repo.mq
2476 q = repo.mq
2477
2477
2478 if patch:
2478 if patch:
2479 patch = q.lookup(patch)
2479 patch = q.lookup(patch)
2480 else:
2480 else:
2481 if not q.applied:
2481 if not q.applied:
2482 ui.write(_('no patches applied\n'))
2482 ui.write(_('no patches applied\n'))
2483 return 1
2483 return 1
2484 patch = q.lookup('qtip')
2484 patch = q.lookup('qtip')
2485 ph = patchheader(q.join(patch), q.plainmode)
2485 ph = patchheader(q.join(patch), q.plainmode)
2486
2486
2487 ui.write('\n'.join(ph.message) + '\n')
2487 ui.write('\n'.join(ph.message) + '\n')
2488
2488
2489 def lastsavename(path):
2489 def lastsavename(path):
2490 (directory, base) = os.path.split(path)
2490 (directory, base) = os.path.split(path)
2491 names = os.listdir(directory)
2491 names = os.listdir(directory)
2492 namere = re.compile("%s.([0-9]+)" % base)
2492 namere = re.compile("%s.([0-9]+)" % base)
2493 maxindex = None
2493 maxindex = None
2494 maxname = None
2494 maxname = None
2495 for f in names:
2495 for f in names:
2496 m = namere.match(f)
2496 m = namere.match(f)
2497 if m:
2497 if m:
2498 index = int(m.group(1))
2498 index = int(m.group(1))
2499 if maxindex is None or index > maxindex:
2499 if maxindex is None or index > maxindex:
2500 maxindex = index
2500 maxindex = index
2501 maxname = f
2501 maxname = f
2502 if maxname:
2502 if maxname:
2503 return (os.path.join(directory, maxname), maxindex)
2503 return (os.path.join(directory, maxname), maxindex)
2504 return (None, None)
2504 return (None, None)
2505
2505
2506 def savename(path):
2506 def savename(path):
2507 (last, index) = lastsavename(path)
2507 (last, index) = lastsavename(path)
2508 if last is None:
2508 if last is None:
2509 index = 0
2509 index = 0
2510 newpath = path + ".%d" % (index + 1)
2510 newpath = path + ".%d" % (index + 1)
2511 return newpath
2511 return newpath
2512
2512
2513 @command("^qpush",
2513 @command("^qpush",
2514 [('f', 'force', None, _('apply on top of local changes')),
2514 [('f', 'force', None, _('apply on top of local changes')),
2515 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2515 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2516 ('l', 'list', None, _('list patch name in commit text')),
2516 ('l', 'list', None, _('list patch name in commit text')),
2517 ('a', 'all', None, _('apply all patches')),
2517 ('a', 'all', None, _('apply all patches')),
2518 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2518 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2519 ('n', 'name', '',
2519 ('n', 'name', '',
2520 _('merge queue name (DEPRECATED)'), _('NAME')),
2520 _('merge queue name (DEPRECATED)'), _('NAME')),
2521 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2521 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2522 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2522 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2523 def push(ui, repo, patch=None, **opts):
2523 def push(ui, repo, patch=None, **opts):
2524 """push the next patch onto the stack
2524 """push the next patch onto the stack
2525
2525
2526 When -f/--force is applied, all local changes in patched files
2526 When -f/--force is applied, all local changes in patched files
2527 will be lost.
2527 will be lost.
2528
2528
2529 Return 0 on success.
2529 Return 0 on success.
2530 """
2530 """
2531 q = repo.mq
2531 q = repo.mq
2532 mergeq = None
2532 mergeq = None
2533
2533
2534 if opts.get('merge'):
2534 if opts.get('merge'):
2535 if opts.get('name'):
2535 if opts.get('name'):
2536 newpath = repo.join(opts.get('name'))
2536 newpath = repo.join(opts.get('name'))
2537 else:
2537 else:
2538 newpath, i = lastsavename(q.path)
2538 newpath, i = lastsavename(q.path)
2539 if not newpath:
2539 if not newpath:
2540 ui.warn(_("no saved queues found, please use -n\n"))
2540 ui.warn(_("no saved queues found, please use -n\n"))
2541 return 1
2541 return 1
2542 mergeq = queue(ui, repo.join(""), newpath)
2542 mergeq = queue(ui, repo.join(""), newpath)
2543 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2543 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2544 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2544 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2545 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2545 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2546 exact=opts.get('exact'))
2546 exact=opts.get('exact'))
2547 return ret
2547 return ret
2548
2548
2549 @command("^qpop",
2549 @command("^qpop",
2550 [('a', 'all', None, _('pop all patches')),
2550 [('a', 'all', None, _('pop all patches')),
2551 ('n', 'name', '',
2551 ('n', 'name', '',
2552 _('queue name to pop (DEPRECATED)'), _('NAME')),
2552 _('queue name to pop (DEPRECATED)'), _('NAME')),
2553 ('f', 'force', None, _('forget any local changes to patched files'))],
2553 ('f', 'force', None, _('forget any local changes to patched files'))],
2554 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2554 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2555 def pop(ui, repo, patch=None, **opts):
2555 def pop(ui, repo, patch=None, **opts):
2556 """pop the current patch off the stack
2556 """pop the current patch off the stack
2557
2557
2558 By default, pops off the top of the patch stack. If given a patch
2558 By default, pops off the top of the patch stack. If given a patch
2559 name, keeps popping off patches until the named patch is at the
2559 name, keeps popping off patches until the named patch is at the
2560 top of the stack.
2560 top of the stack.
2561
2561
2562 Return 0 on success.
2562 Return 0 on success.
2563 """
2563 """
2564 localupdate = True
2564 localupdate = True
2565 if opts.get('name'):
2565 if opts.get('name'):
2566 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2566 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2567 ui.warn(_('using patch queue: %s\n') % q.path)
2567 ui.warn(_('using patch queue: %s\n') % q.path)
2568 localupdate = False
2568 localupdate = False
2569 else:
2569 else:
2570 q = repo.mq
2570 q = repo.mq
2571 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2571 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2572 all=opts.get('all'))
2572 all=opts.get('all'))
2573 q.save_dirty()
2573 q.save_dirty()
2574 return ret
2574 return ret
2575
2575
2576 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2576 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2577 def rename(ui, repo, patch, name=None, **opts):
2577 def rename(ui, repo, patch, name=None, **opts):
2578 """rename a patch
2578 """rename a patch
2579
2579
2580 With one argument, renames the current patch to PATCH1.
2580 With one argument, renames the current patch to PATCH1.
2581 With two arguments, renames PATCH1 to PATCH2.
2581 With two arguments, renames PATCH1 to PATCH2.
2582
2582
2583 Returns 0 on success."""
2583 Returns 0 on success."""
2584
2584
2585 q = repo.mq
2585 q = repo.mq
2586
2586
2587 if not name:
2587 if not name:
2588 name = patch
2588 name = patch
2589 patch = None
2589 patch = None
2590
2590
2591 if patch:
2591 if patch:
2592 patch = q.lookup(patch)
2592 patch = q.lookup(patch)
2593 else:
2593 else:
2594 if not q.applied:
2594 if not q.applied:
2595 ui.write(_('no patches applied\n'))
2595 ui.write(_('no patches applied\n'))
2596 return
2596 return
2597 patch = q.lookup('qtip')
2597 patch = q.lookup('qtip')
2598 absdest = q.join(name)
2598 absdest = q.join(name)
2599 if os.path.isdir(absdest):
2599 if os.path.isdir(absdest):
2600 name = normname(os.path.join(name, os.path.basename(patch)))
2600 name = normname(os.path.join(name, os.path.basename(patch)))
2601 absdest = q.join(name)
2601 absdest = q.join(name)
2602 if os.path.exists(absdest):
2602 if os.path.exists(absdest):
2603 raise util.Abort(_('%s already exists') % absdest)
2603 raise util.Abort(_('%s already exists') % absdest)
2604
2604
2605 if name in q.series:
2605 if name in q.series:
2606 raise util.Abort(
2606 raise util.Abort(
2607 _('A patch named %s already exists in the series file') % name)
2607 _('A patch named %s already exists in the series file') % name)
2608
2608
2609 ui.note(_('renaming %s to %s\n') % (patch, name))
2609 ui.note(_('renaming %s to %s\n') % (patch, name))
2610 i = q.find_series(patch)
2610 i = q.find_series(patch)
2611 guards = q.guard_re.findall(q.full_series[i])
2611 guards = q.guard_re.findall(q.full_series[i])
2612 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2612 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2613 q.parse_series()
2613 q.parse_series()
2614 q.series_dirty = 1
2614 q.series_dirty = 1
2615
2615
2616 info = q.isapplied(patch)
2616 info = q.isapplied(patch)
2617 if info:
2617 if info:
2618 q.applied[info[0]] = statusentry(info[1], name)
2618 q.applied[info[0]] = statusentry(info[1], name)
2619 q.applied_dirty = 1
2619 q.applied_dirty = 1
2620
2620
2621 destdir = os.path.dirname(absdest)
2621 destdir = os.path.dirname(absdest)
2622 if not os.path.isdir(destdir):
2622 if not os.path.isdir(destdir):
2623 os.makedirs(destdir)
2623 os.makedirs(destdir)
2624 util.rename(q.join(patch), absdest)
2624 util.rename(q.join(patch), absdest)
2625 r = q.qrepo()
2625 r = q.qrepo()
2626 if r and patch in r.dirstate:
2626 if r and patch in r.dirstate:
2627 wctx = r[None]
2627 wctx = r[None]
2628 wlock = r.wlock()
2628 wlock = r.wlock()
2629 try:
2629 try:
2630 if r.dirstate[patch] == 'a':
2630 if r.dirstate[patch] == 'a':
2631 r.dirstate.forget(patch)
2631 r.dirstate.forget(patch)
2632 r.dirstate.add(name)
2632 r.dirstate.add(name)
2633 else:
2633 else:
2634 if r.dirstate[name] == 'r':
2634 if r.dirstate[name] == 'r':
2635 wctx.undelete([name])
2635 wctx.undelete([name])
2636 wctx.copy(patch, name)
2636 wctx.copy(patch, name)
2637 wctx.remove([patch], False)
2637 wctx.remove([patch], False)
2638 finally:
2638 finally:
2639 wlock.release()
2639 wlock.release()
2640
2640
2641 q.save_dirty()
2641 q.save_dirty()
2642
2642
2643 @command("qrestore",
2643 @command("qrestore",
2644 [('d', 'delete', None, _('delete save entry')),
2644 [('d', 'delete', None, _('delete save entry')),
2645 ('u', 'update', None, _('update queue working directory'))],
2645 ('u', 'update', None, _('update queue working directory'))],
2646 _('hg qrestore [-d] [-u] REV'))
2646 _('hg qrestore [-d] [-u] REV'))
2647 def restore(ui, repo, rev, **opts):
2647 def restore(ui, repo, rev, **opts):
2648 """restore the queue state saved by a revision (DEPRECATED)
2648 """restore the queue state saved by a revision (DEPRECATED)
2649
2649
2650 This command is deprecated, use :hg:`rebase` instead."""
2650 This command is deprecated, use :hg:`rebase` instead."""
2651 rev = repo.lookup(rev)
2651 rev = repo.lookup(rev)
2652 q = repo.mq
2652 q = repo.mq
2653 q.restore(repo, rev, delete=opts.get('delete'),
2653 q.restore(repo, rev, delete=opts.get('delete'),
2654 qupdate=opts.get('update'))
2654 qupdate=opts.get('update'))
2655 q.save_dirty()
2655 q.save_dirty()
2656 return 0
2656 return 0
2657
2657
2658 @command("qsave",
2658 @command("qsave",
2659 [('c', 'copy', None, _('copy patch directory')),
2659 [('c', 'copy', None, _('copy patch directory')),
2660 ('n', 'name', '',
2660 ('n', 'name', '',
2661 _('copy directory name'), _('NAME')),
2661 _('copy directory name'), _('NAME')),
2662 ('e', 'empty', None, _('clear queue status file')),
2662 ('e', 'empty', None, _('clear queue status file')),
2663 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2663 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2664 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2664 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2665 def save(ui, repo, **opts):
2665 def save(ui, repo, **opts):
2666 """save current queue state (DEPRECATED)
2666 """save current queue state (DEPRECATED)
2667
2667
2668 This command is deprecated, use :hg:`rebase` instead."""
2668 This command is deprecated, use :hg:`rebase` instead."""
2669 q = repo.mq
2669 q = repo.mq
2670 message = cmdutil.logmessage(opts)
2670 message = cmdutil.logmessage(opts)
2671 ret = q.save(repo, msg=message)
2671 ret = q.save(repo, msg=message)
2672 if ret:
2672 if ret:
2673 return ret
2673 return ret
2674 q.save_dirty()
2674 q.save_dirty()
2675 if opts.get('copy'):
2675 if opts.get('copy'):
2676 path = q.path
2676 path = q.path
2677 if opts.get('name'):
2677 if opts.get('name'):
2678 newpath = os.path.join(q.basepath, opts.get('name'))
2678 newpath = os.path.join(q.basepath, opts.get('name'))
2679 if os.path.exists(newpath):
2679 if os.path.exists(newpath):
2680 if not os.path.isdir(newpath):
2680 if not os.path.isdir(newpath):
2681 raise util.Abort(_('destination %s exists and is not '
2681 raise util.Abort(_('destination %s exists and is not '
2682 'a directory') % newpath)
2682 'a directory') % newpath)
2683 if not opts.get('force'):
2683 if not opts.get('force'):
2684 raise util.Abort(_('destination %s exists, '
2684 raise util.Abort(_('destination %s exists, '
2685 'use -f to force') % newpath)
2685 'use -f to force') % newpath)
2686 else:
2686 else:
2687 newpath = savename(path)
2687 newpath = savename(path)
2688 ui.warn(_("copy %s to %s\n") % (path, newpath))
2688 ui.warn(_("copy %s to %s\n") % (path, newpath))
2689 util.copyfiles(path, newpath)
2689 util.copyfiles(path, newpath)
2690 if opts.get('empty'):
2690 if opts.get('empty'):
2691 try:
2691 try:
2692 os.unlink(q.join(q.status_path))
2692 os.unlink(q.join(q.status_path))
2693 except:
2693 except:
2694 pass
2694 pass
2695 return 0
2695 return 0
2696
2696
2697 @command("strip",
2697 @command("strip",
2698 [('f', 'force', None, _('force removal of changesets, discard '
2698 [('f', 'force', None, _('force removal of changesets, discard '
2699 'uncommitted changes (no backup)')),
2699 'uncommitted changes (no backup)')),
2700 ('b', 'backup', None, _('bundle only changesets with local revision'
2700 ('b', 'backup', None, _('bundle only changesets with local revision'
2701 ' number greater than REV which are not'
2701 ' number greater than REV which are not'
2702 ' descendants of REV (DEPRECATED)')),
2702 ' descendants of REV (DEPRECATED)')),
2703 ('n', 'no-backup', None, _('no backups')),
2703 ('n', 'no-backup', None, _('no backups')),
2704 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2704 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2705 ('k', 'keep', None, _("do not modify working copy during strip"))],
2705 ('k', 'keep', None, _("do not modify working copy during strip"))],
2706 _('hg strip [-k] [-f] [-n] REV...'))
2706 _('hg strip [-k] [-f] [-n] REV...'))
2707 def strip(ui, repo, *revs, **opts):
2707 def strip(ui, repo, *revs, **opts):
2708 """strip changesets and all their descendants from the repository
2708 """strip changesets and all their descendants from the repository
2709
2709
2710 The strip command removes the specified changesets and all their
2710 The strip command removes the specified changesets and all their
2711 descendants. If the working directory has uncommitted changes, the
2711 descendants. If the working directory has uncommitted changes, the
2712 operation is aborted unless the --force flag is supplied, in which
2712 operation is aborted unless the --force flag is supplied, in which
2713 case changes will be discarded.
2713 case changes will be discarded.
2714
2714
2715 If a parent of the working directory is stripped, then the working
2715 If a parent of the working directory is stripped, then the working
2716 directory will automatically be updated to the most recent
2716 directory will automatically be updated to the most recent
2717 available ancestor of the stripped parent after the operation
2717 available ancestor of the stripped parent after the operation
2718 completes.
2718 completes.
2719
2719
2720 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2720 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2721 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2721 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2722 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2722 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2723 where BUNDLE is the bundle file created by the strip. Note that
2723 where BUNDLE is the bundle file created by the strip. Note that
2724 the local revision numbers will in general be different after the
2724 the local revision numbers will in general be different after the
2725 restore.
2725 restore.
2726
2726
2727 Use the --no-backup option to discard the backup bundle once the
2727 Use the --no-backup option to discard the backup bundle once the
2728 operation completes.
2728 operation completes.
2729
2729
2730 Return 0 on success.
2730 Return 0 on success.
2731 """
2731 """
2732 backup = 'all'
2732 backup = 'all'
2733 if opts.get('backup'):
2733 if opts.get('backup'):
2734 backup = 'strip'
2734 backup = 'strip'
2735 elif opts.get('no_backup') or opts.get('nobackup'):
2735 elif opts.get('no_backup') or opts.get('nobackup'):
2736 backup = 'none'
2736 backup = 'none'
2737
2737
2738 cl = repo.changelog
2738 cl = repo.changelog
2739 revs = set(scmutil.revrange(repo, revs))
2739 revs = set(scmutil.revrange(repo, revs))
2740 if not revs:
2740 if not revs:
2741 raise util.Abort(_('empty revision set'))
2741 raise util.Abort(_('empty revision set'))
2742
2742
2743 descendants = set(cl.descendants(*revs))
2743 descendants = set(cl.descendants(*revs))
2744 strippedrevs = revs.union(descendants)
2744 strippedrevs = revs.union(descendants)
2745 roots = revs.difference(descendants)
2745 roots = revs.difference(descendants)
2746
2746
2747 update = False
2747 update = False
2748 # if one of the wdir parent is stripped we'll need
2748 # if one of the wdir parent is stripped we'll need
2749 # to update away to an earlier revision
2749 # to update away to an earlier revision
2750 for p in repo.dirstate.parents():
2750 for p in repo.dirstate.parents():
2751 if p != nullid and cl.rev(p) in strippedrevs:
2751 if p != nullid and cl.rev(p) in strippedrevs:
2752 update = True
2752 update = True
2753 break
2753 break
2754
2754
2755 rootnodes = set(cl.node(r) for r in roots)
2755 rootnodes = set(cl.node(r) for r in roots)
2756
2756
2757 q = repo.mq
2757 q = repo.mq
2758 if q.applied:
2758 if q.applied:
2759 # refresh queue state if we're about to strip
2759 # refresh queue state if we're about to strip
2760 # applied patches
2760 # applied patches
2761 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2761 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2762 q.applied_dirty = True
2762 q.applied_dirty = True
2763 start = 0
2763 start = 0
2764 end = len(q.applied)
2764 end = len(q.applied)
2765 for i, statusentry in enumerate(q.applied):
2765 for i, statusentry in enumerate(q.applied):
2766 if statusentry.node in rootnodes:
2766 if statusentry.node in rootnodes:
2767 # if one of the stripped roots is an applied
2767 # if one of the stripped roots is an applied
2768 # patch, only part of the queue is stripped
2768 # patch, only part of the queue is stripped
2769 start = i
2769 start = i
2770 break
2770 break
2771 del q.applied[start:end]
2771 del q.applied[start:end]
2772 q.save_dirty()
2772 q.save_dirty()
2773
2773
2774 revs = list(rootnodes)
2774 revs = list(rootnodes)
2775 if update and opts.get('keep'):
2775 if update and opts.get('keep'):
2776 wlock = repo.wlock()
2776 wlock = repo.wlock()
2777 try:
2777 try:
2778 urev = repo.mq.qparents(repo, revs[0])
2778 urev = repo.mq.qparents(repo, revs[0])
2779 repo.dirstate.rebuild(urev, repo[urev].manifest())
2779 repo.dirstate.rebuild(urev, repo[urev].manifest())
2780 repo.dirstate.write()
2780 repo.dirstate.write()
2781 update = False
2781 update = False
2782 finally:
2782 finally:
2783 wlock.release()
2783 wlock.release()
2784
2784
2785 repo.mq.strip(repo, revs, backup=backup, update=update,
2785 repo.mq.strip(repo, revs, backup=backup, update=update,
2786 force=opts.get('force'))
2786 force=opts.get('force'))
2787 return 0
2787 return 0
2788
2788
2789 @command("qselect",
2789 @command("qselect",
2790 [('n', 'none', None, _('disable all guards')),
2790 [('n', 'none', None, _('disable all guards')),
2791 ('s', 'series', None, _('list all guards in series file')),
2791 ('s', 'series', None, _('list all guards in series file')),
2792 ('', 'pop', None, _('pop to before first guarded applied patch')),
2792 ('', 'pop', None, _('pop to before first guarded applied patch')),
2793 ('', 'reapply', None, _('pop, then reapply patches'))],
2793 ('', 'reapply', None, _('pop, then reapply patches'))],
2794 _('hg qselect [OPTION]... [GUARD]...'))
2794 _('hg qselect [OPTION]... [GUARD]...'))
2795 def select(ui, repo, *args, **opts):
2795 def select(ui, repo, *args, **opts):
2796 '''set or print guarded patches to push
2796 '''set or print guarded patches to push
2797
2797
2798 Use the :hg:`qguard` command to set or print guards on patch, then use
2798 Use the :hg:`qguard` command to set or print guards on patch, then use
2799 qselect to tell mq which guards to use. A patch will be pushed if
2799 qselect to tell mq which guards to use. A patch will be pushed if
2800 it has no guards or any positive guards match the currently
2800 it has no guards or any positive guards match the currently
2801 selected guard, but will not be pushed if any negative guards
2801 selected guard, but will not be pushed if any negative guards
2802 match the current guard. For example::
2802 match the current guard. For example::
2803
2803
2804 qguard foo.patch -- -stable (negative guard)
2804 qguard foo.patch -- -stable (negative guard)
2805 qguard bar.patch +stable (positive guard)
2805 qguard bar.patch +stable (positive guard)
2806 qselect stable
2806 qselect stable
2807
2807
2808 This activates the "stable" guard. mq will skip foo.patch (because
2808 This activates the "stable" guard. mq will skip foo.patch (because
2809 it has a negative match) but push bar.patch (because it has a
2809 it has a negative match) but push bar.patch (because it has a
2810 positive match).
2810 positive match).
2811
2811
2812 With no arguments, prints the currently active guards.
2812 With no arguments, prints the currently active guards.
2813 With one argument, sets the active guard.
2813 With one argument, sets the active guard.
2814
2814
2815 Use -n/--none to deactivate guards (no other arguments needed).
2815 Use -n/--none to deactivate guards (no other arguments needed).
2816 When no guards are active, patches with positive guards are
2816 When no guards are active, patches with positive guards are
2817 skipped and patches with negative guards are pushed.
2817 skipped and patches with negative guards are pushed.
2818
2818
2819 qselect can change the guards on applied patches. It does not pop
2819 qselect can change the guards on applied patches. It does not pop
2820 guarded patches by default. Use --pop to pop back to the last
2820 guarded patches by default. Use --pop to pop back to the last
2821 applied patch that is not guarded. Use --reapply (which implies
2821 applied patch that is not guarded. Use --reapply (which implies
2822 --pop) to push back to the current patch afterwards, but skip
2822 --pop) to push back to the current patch afterwards, but skip
2823 guarded patches.
2823 guarded patches.
2824
2824
2825 Use -s/--series to print a list of all guards in the series file
2825 Use -s/--series to print a list of all guards in the series file
2826 (no other arguments needed). Use -v for more information.
2826 (no other arguments needed). Use -v for more information.
2827
2827
2828 Returns 0 on success.'''
2828 Returns 0 on success.'''
2829
2829
2830 q = repo.mq
2830 q = repo.mq
2831 guards = q.active()
2831 guards = q.active()
2832 if args or opts.get('none'):
2832 if args or opts.get('none'):
2833 old_unapplied = q.unapplied(repo)
2833 old_unapplied = q.unapplied(repo)
2834 old_guarded = [i for i in xrange(len(q.applied)) if
2834 old_guarded = [i for i in xrange(len(q.applied)) if
2835 not q.pushable(i)[0]]
2835 not q.pushable(i)[0]]
2836 q.set_active(args)
2836 q.set_active(args)
2837 q.save_dirty()
2837 q.save_dirty()
2838 if not args:
2838 if not args:
2839 ui.status(_('guards deactivated\n'))
2839 ui.status(_('guards deactivated\n'))
2840 if not opts.get('pop') and not opts.get('reapply'):
2840 if not opts.get('pop') and not opts.get('reapply'):
2841 unapplied = q.unapplied(repo)
2841 unapplied = q.unapplied(repo)
2842 guarded = [i for i in xrange(len(q.applied))
2842 guarded = [i for i in xrange(len(q.applied))
2843 if not q.pushable(i)[0]]
2843 if not q.pushable(i)[0]]
2844 if len(unapplied) != len(old_unapplied):
2844 if len(unapplied) != len(old_unapplied):
2845 ui.status(_('number of unguarded, unapplied patches has '
2845 ui.status(_('number of unguarded, unapplied patches has '
2846 'changed from %d to %d\n') %
2846 'changed from %d to %d\n') %
2847 (len(old_unapplied), len(unapplied)))
2847 (len(old_unapplied), len(unapplied)))
2848 if len(guarded) != len(old_guarded):
2848 if len(guarded) != len(old_guarded):
2849 ui.status(_('number of guarded, applied patches has changed '
2849 ui.status(_('number of guarded, applied patches has changed '
2850 'from %d to %d\n') %
2850 'from %d to %d\n') %
2851 (len(old_guarded), len(guarded)))
2851 (len(old_guarded), len(guarded)))
2852 elif opts.get('series'):
2852 elif opts.get('series'):
2853 guards = {}
2853 guards = {}
2854 noguards = 0
2854 noguards = 0
2855 for gs in q.series_guards:
2855 for gs in q.series_guards:
2856 if not gs:
2856 if not gs:
2857 noguards += 1
2857 noguards += 1
2858 for g in gs:
2858 for g in gs:
2859 guards.setdefault(g, 0)
2859 guards.setdefault(g, 0)
2860 guards[g] += 1
2860 guards[g] += 1
2861 if ui.verbose:
2861 if ui.verbose:
2862 guards['NONE'] = noguards
2862 guards['NONE'] = noguards
2863 guards = guards.items()
2863 guards = guards.items()
2864 guards.sort(key=lambda x: x[0][1:])
2864 guards.sort(key=lambda x: x[0][1:])
2865 if guards:
2865 if guards:
2866 ui.note(_('guards in series file:\n'))
2866 ui.note(_('guards in series file:\n'))
2867 for guard, count in guards:
2867 for guard, count in guards:
2868 ui.note('%2d ' % count)
2868 ui.note('%2d ' % count)
2869 ui.write(guard, '\n')
2869 ui.write(guard, '\n')
2870 else:
2870 else:
2871 ui.note(_('no guards in series file\n'))
2871 ui.note(_('no guards in series file\n'))
2872 else:
2872 else:
2873 if guards:
2873 if guards:
2874 ui.note(_('active guards:\n'))
2874 ui.note(_('active guards:\n'))
2875 for g in guards:
2875 for g in guards:
2876 ui.write(g, '\n')
2876 ui.write(g, '\n')
2877 else:
2877 else:
2878 ui.write(_('no active guards\n'))
2878 ui.write(_('no active guards\n'))
2879 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2879 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2880 popped = False
2880 popped = False
2881 if opts.get('pop') or opts.get('reapply'):
2881 if opts.get('pop') or opts.get('reapply'):
2882 for i in xrange(len(q.applied)):
2882 for i in xrange(len(q.applied)):
2883 pushable, reason = q.pushable(i)
2883 pushable, reason = q.pushable(i)
2884 if not pushable:
2884 if not pushable:
2885 ui.status(_('popping guarded patches\n'))
2885 ui.status(_('popping guarded patches\n'))
2886 popped = True
2886 popped = True
2887 if i == 0:
2887 if i == 0:
2888 q.pop(repo, all=True)
2888 q.pop(repo, all=True)
2889 else:
2889 else:
2890 q.pop(repo, i - 1)
2890 q.pop(repo, i - 1)
2891 break
2891 break
2892 if popped:
2892 if popped:
2893 try:
2893 try:
2894 if reapply:
2894 if reapply:
2895 ui.status(_('reapplying unguarded patches\n'))
2895 ui.status(_('reapplying unguarded patches\n'))
2896 q.push(repo, reapply)
2896 q.push(repo, reapply)
2897 finally:
2897 finally:
2898 q.save_dirty()
2898 q.save_dirty()
2899
2899
2900 @command("qfinish",
2900 @command("qfinish",
2901 [('a', 'applied', None, _('finish all applied changesets'))],
2901 [('a', 'applied', None, _('finish all applied changesets'))],
2902 _('hg qfinish [-a] [REV]...'))
2902 _('hg qfinish [-a] [REV]...'))
2903 def finish(ui, repo, *revrange, **opts):
2903 def finish(ui, repo, *revrange, **opts):
2904 """move applied patches into repository history
2904 """move applied patches into repository history
2905
2905
2906 Finishes the specified revisions (corresponding to applied
2906 Finishes the specified revisions (corresponding to applied
2907 patches) by moving them out of mq control into regular repository
2907 patches) by moving them out of mq control into regular repository
2908 history.
2908 history.
2909
2909
2910 Accepts a revision range or the -a/--applied option. If --applied
2910 Accepts a revision range or the -a/--applied option. If --applied
2911 is specified, all applied mq revisions are removed from mq
2911 is specified, all applied mq revisions are removed from mq
2912 control. Otherwise, the given revisions must be at the base of the
2912 control. Otherwise, the given revisions must be at the base of the
2913 stack of applied patches.
2913 stack of applied patches.
2914
2914
2915 This can be especially useful if your changes have been applied to
2915 This can be especially useful if your changes have been applied to
2916 an upstream repository, or if you are about to push your changes
2916 an upstream repository, or if you are about to push your changes
2917 to upstream.
2917 to upstream.
2918
2918
2919 Returns 0 on success.
2919 Returns 0 on success.
2920 """
2920 """
2921 if not opts.get('applied') and not revrange:
2921 if not opts.get('applied') and not revrange:
2922 raise util.Abort(_('no revisions specified'))
2922 raise util.Abort(_('no revisions specified'))
2923 elif opts.get('applied'):
2923 elif opts.get('applied'):
2924 revrange = ('qbase::qtip',) + revrange
2924 revrange = ('qbase::qtip',) + revrange
2925
2925
2926 q = repo.mq
2926 q = repo.mq
2927 if not q.applied:
2927 if not q.applied:
2928 ui.status(_('no patches applied\n'))
2928 ui.status(_('no patches applied\n'))
2929 return 0
2929 return 0
2930
2930
2931 revs = scmutil.revrange(repo, revrange)
2931 revs = scmutil.revrange(repo, revrange)
2932 q.finish(repo, revs)
2932 q.finish(repo, revs)
2933 q.save_dirty()
2933 q.save_dirty()
2934 return 0
2934 return 0
2935
2935
2936 @command("qqueue",
2936 @command("qqueue",
2937 [('l', 'list', False, _('list all available queues')),
2937 [('l', 'list', False, _('list all available queues')),
2938 ('c', 'create', False, _('create new queue')),
2938 ('c', 'create', False, _('create new queue')),
2939 ('', 'rename', False, _('rename active queue')),
2939 ('', 'rename', False, _('rename active queue')),
2940 ('', 'delete', False, _('delete reference to queue')),
2940 ('', 'delete', False, _('delete reference to queue')),
2941 ('', 'purge', False, _('delete queue, and remove patch dir')),
2941 ('', 'purge', False, _('delete queue, and remove patch dir')),
2942 ],
2942 ],
2943 _('[OPTION] [QUEUE]'))
2943 _('[OPTION] [QUEUE]'))
2944 def qqueue(ui, repo, name=None, **opts):
2944 def qqueue(ui, repo, name=None, **opts):
2945 '''manage multiple patch queues
2945 '''manage multiple patch queues
2946
2946
2947 Supports switching between different patch queues, as well as creating
2947 Supports switching between different patch queues, as well as creating
2948 new patch queues and deleting existing ones.
2948 new patch queues and deleting existing ones.
2949
2949
2950 Omitting a queue name or specifying -l/--list will show you the registered
2950 Omitting a queue name or specifying -l/--list will show you the registered
2951 queues - by default the "normal" patches queue is registered. The currently
2951 queues - by default the "normal" patches queue is registered. The currently
2952 active queue will be marked with "(active)".
2952 active queue will be marked with "(active)".
2953
2953
2954 To create a new queue, use -c/--create. The queue is automatically made
2954 To create a new queue, use -c/--create. The queue is automatically made
2955 active, except in the case where there are applied patches from the
2955 active, except in the case where there are applied patches from the
2956 currently active queue in the repository. Then the queue will only be
2956 currently active queue in the repository. Then the queue will only be
2957 created and switching will fail.
2957 created and switching will fail.
2958
2958
2959 To delete an existing queue, use --delete. You cannot delete the currently
2959 To delete an existing queue, use --delete. You cannot delete the currently
2960 active queue.
2960 active queue.
2961
2961
2962 Returns 0 on success.
2962 Returns 0 on success.
2963 '''
2963 '''
2964
2964
2965 q = repo.mq
2965 q = repo.mq
2966
2966
2967 _defaultqueue = 'patches'
2967 _defaultqueue = 'patches'
2968 _allqueues = 'patches.queues'
2968 _allqueues = 'patches.queues'
2969 _activequeue = 'patches.queue'
2969 _activequeue = 'patches.queue'
2970
2970
2971 def _getcurrent():
2971 def _getcurrent():
2972 cur = os.path.basename(q.path)
2972 cur = os.path.basename(q.path)
2973 if cur.startswith('patches-'):
2973 if cur.startswith('patches-'):
2974 cur = cur[8:]
2974 cur = cur[8:]
2975 return cur
2975 return cur
2976
2976
2977 def _noqueues():
2977 def _noqueues():
2978 try:
2978 try:
2979 fh = repo.opener(_allqueues, 'r')
2979 fh = repo.opener(_allqueues, 'r')
2980 fh.close()
2980 fh.close()
2981 except IOError:
2981 except IOError:
2982 return True
2982 return True
2983
2983
2984 return False
2984 return False
2985
2985
2986 def _getqueues():
2986 def _getqueues():
2987 current = _getcurrent()
2987 current = _getcurrent()
2988
2988
2989 try:
2989 try:
2990 fh = repo.opener(_allqueues, 'r')
2990 fh = repo.opener(_allqueues, 'r')
2991 queues = [queue.strip() for queue in fh if queue.strip()]
2991 queues = [queue.strip() for queue in fh if queue.strip()]
2992 fh.close()
2992 fh.close()
2993 if current not in queues:
2993 if current not in queues:
2994 queues.append(current)
2994 queues.append(current)
2995 except IOError:
2995 except IOError:
2996 queues = [_defaultqueue]
2996 queues = [_defaultqueue]
2997
2997
2998 return sorted(queues)
2998 return sorted(queues)
2999
2999
3000 def _setactive(name):
3000 def _setactive(name):
3001 if q.applied:
3001 if q.applied:
3002 raise util.Abort(_('patches applied - cannot set new queue active'))
3002 raise util.Abort(_('patches applied - cannot set new queue active'))
3003 _setactivenocheck(name)
3003 _setactivenocheck(name)
3004
3004
3005 def _setactivenocheck(name):
3005 def _setactivenocheck(name):
3006 fh = repo.opener(_activequeue, 'w')
3006 fh = repo.opener(_activequeue, 'w')
3007 if name != 'patches':
3007 if name != 'patches':
3008 fh.write(name)
3008 fh.write(name)
3009 fh.close()
3009 fh.close()
3010
3010
3011 def _addqueue(name):
3011 def _addqueue(name):
3012 fh = repo.opener(_allqueues, 'a')
3012 fh = repo.opener(_allqueues, 'a')
3013 fh.write('%s\n' % (name,))
3013 fh.write('%s\n' % (name,))
3014 fh.close()
3014 fh.close()
3015
3015
3016 def _queuedir(name):
3016 def _queuedir(name):
3017 if name == 'patches':
3017 if name == 'patches':
3018 return repo.join('patches')
3018 return repo.join('patches')
3019 else:
3019 else:
3020 return repo.join('patches-' + name)
3020 return repo.join('patches-' + name)
3021
3021
3022 def _validname(name):
3022 def _validname(name):
3023 for n in name:
3023 for n in name:
3024 if n in ':\\/.':
3024 if n in ':\\/.':
3025 return False
3025 return False
3026 return True
3026 return True
3027
3027
3028 def _delete(name):
3028 def _delete(name):
3029 if name not in existing:
3029 if name not in existing:
3030 raise util.Abort(_('cannot delete queue that does not exist'))
3030 raise util.Abort(_('cannot delete queue that does not exist'))
3031
3031
3032 current = _getcurrent()
3032 current = _getcurrent()
3033
3033
3034 if name == current:
3034 if name == current:
3035 raise util.Abort(_('cannot delete currently active queue'))
3035 raise util.Abort(_('cannot delete currently active queue'))
3036
3036
3037 fh = repo.opener('patches.queues.new', 'w')
3037 fh = repo.opener('patches.queues.new', 'w')
3038 for queue in existing:
3038 for queue in existing:
3039 if queue == name:
3039 if queue == name:
3040 continue
3040 continue
3041 fh.write('%s\n' % (queue,))
3041 fh.write('%s\n' % (queue,))
3042 fh.close()
3042 fh.close()
3043 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3043 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3044
3044
3045 if not name or opts.get('list'):
3045 if not name or opts.get('list'):
3046 current = _getcurrent()
3046 current = _getcurrent()
3047 for queue in _getqueues():
3047 for queue in _getqueues():
3048 ui.write('%s' % (queue,))
3048 ui.write('%s' % (queue,))
3049 if queue == current and not ui.quiet:
3049 if queue == current and not ui.quiet:
3050 ui.write(_(' (active)\n'))
3050 ui.write(_(' (active)\n'))
3051 else:
3051 else:
3052 ui.write('\n')
3052 ui.write('\n')
3053 return
3053 return
3054
3054
3055 if not _validname(name):
3055 if not _validname(name):
3056 raise util.Abort(
3056 raise util.Abort(
3057 _('invalid queue name, may not contain the characters ":\\/."'))
3057 _('invalid queue name, may not contain the characters ":\\/."'))
3058
3058
3059 existing = _getqueues()
3059 existing = _getqueues()
3060
3060
3061 if opts.get('create'):
3061 if opts.get('create'):
3062 if name in existing:
3062 if name in existing:
3063 raise util.Abort(_('queue "%s" already exists') % name)
3063 raise util.Abort(_('queue "%s" already exists') % name)
3064 if _noqueues():
3064 if _noqueues():
3065 _addqueue(_defaultqueue)
3065 _addqueue(_defaultqueue)
3066 _addqueue(name)
3066 _addqueue(name)
3067 _setactive(name)
3067 _setactive(name)
3068 elif opts.get('rename'):
3068 elif opts.get('rename'):
3069 current = _getcurrent()
3069 current = _getcurrent()
3070 if name == current:
3070 if name == current:
3071 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3071 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3072 if name in existing:
3072 if name in existing:
3073 raise util.Abort(_('queue "%s" already exists') % name)
3073 raise util.Abort(_('queue "%s" already exists') % name)
3074
3074
3075 olddir = _queuedir(current)
3075 olddir = _queuedir(current)
3076 newdir = _queuedir(name)
3076 newdir = _queuedir(name)
3077
3077
3078 if os.path.exists(newdir):
3078 if os.path.exists(newdir):
3079 raise util.Abort(_('non-queue directory "%s" already exists') %
3079 raise util.Abort(_('non-queue directory "%s" already exists') %
3080 newdir)
3080 newdir)
3081
3081
3082 fh = repo.opener('patches.queues.new', 'w')
3082 fh = repo.opener('patches.queues.new', 'w')
3083 for queue in existing:
3083 for queue in existing:
3084 if queue == current:
3084 if queue == current:
3085 fh.write('%s\n' % (name,))
3085 fh.write('%s\n' % (name,))
3086 if os.path.exists(olddir):
3086 if os.path.exists(olddir):
3087 util.rename(olddir, newdir)
3087 util.rename(olddir, newdir)
3088 else:
3088 else:
3089 fh.write('%s\n' % (queue,))
3089 fh.write('%s\n' % (queue,))
3090 fh.close()
3090 fh.close()
3091 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3091 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3092 _setactivenocheck(name)
3092 _setactivenocheck(name)
3093 elif opts.get('delete'):
3093 elif opts.get('delete'):
3094 _delete(name)
3094 _delete(name)
3095 elif opts.get('purge'):
3095 elif opts.get('purge'):
3096 if name in existing:
3096 if name in existing:
3097 _delete(name)
3097 _delete(name)
3098 qdir = _queuedir(name)
3098 qdir = _queuedir(name)
3099 if os.path.exists(qdir):
3099 if os.path.exists(qdir):
3100 shutil.rmtree(qdir)
3100 shutil.rmtree(qdir)
3101 else:
3101 else:
3102 if name not in existing:
3102 if name not in existing:
3103 raise util.Abort(_('use --create to create a new queue'))
3103 raise util.Abort(_('use --create to create a new queue'))
3104 _setactive(name)
3104 _setactive(name)
3105
3105
3106 def reposetup(ui, repo):
3106 def reposetup(ui, repo):
3107 class mqrepo(repo.__class__):
3107 class mqrepo(repo.__class__):
3108 @util.propertycache
3108 @util.propertycache
3109 def mq(self):
3109 def mq(self):
3110 return queue(self.ui, self.join(""))
3110 return queue(self.ui, self.join(""))
3111
3111
3112 def abort_if_wdir_patched(self, errmsg, force=False):
3112 def abort_if_wdir_patched(self, errmsg, force=False):
3113 if self.mq.applied and not force:
3113 if self.mq.applied and not force:
3114 parents = self.dirstate.parents()
3114 parents = self.dirstate.parents()
3115 patches = [s.node for s in self.mq.applied]
3115 patches = [s.node for s in self.mq.applied]
3116 if parents[0] in patches or parents[1] in patches:
3116 if parents[0] in patches or parents[1] in patches:
3117 raise util.Abort(errmsg)
3117 raise util.Abort(errmsg)
3118
3118
3119 def commit(self, text="", user=None, date=None, match=None,
3119 def commit(self, text="", user=None, date=None, match=None,
3120 force=False, editor=False, extra={}):
3120 force=False, editor=False, extra={}):
3121 self.abort_if_wdir_patched(
3121 self.abort_if_wdir_patched(
3122 _('cannot commit over an applied mq patch'),
3122 _('cannot commit over an applied mq patch'),
3123 force)
3123 force)
3124
3124
3125 return super(mqrepo, self).commit(text, user, date, match, force,
3125 return super(mqrepo, self).commit(text, user, date, match, force,
3126 editor, extra)
3126 editor, extra)
3127
3127
3128 def checkpush(self, force, revs):
3128 def checkpush(self, force, revs):
3129 if self.mq.applied and not force:
3129 if self.mq.applied and not force:
3130 haspatches = True
3130 haspatches = True
3131 if revs:
3131 if revs:
3132 # Assume applied patches have no non-patch descendants
3132 # Assume applied patches have no non-patch descendants
3133 # and are not on remote already. If they appear in the
3133 # and are not on remote already. If they appear in the
3134 # set of resolved 'revs', bail out.
3134 # set of resolved 'revs', bail out.
3135 applied = set(e.node for e in self.mq.applied)
3135 applied = set(e.node for e in self.mq.applied)
3136 haspatches = bool([n for n in revs if n in applied])
3136 haspatches = bool([n for n in revs if n in applied])
3137 if haspatches:
3137 if haspatches:
3138 raise util.Abort(_('source has mq patches applied'))
3138 raise util.Abort(_('source has mq patches applied'))
3139 super(mqrepo, self).checkpush(force, revs)
3139 super(mqrepo, self).checkpush(force, revs)
3140
3140
3141 def _findtags(self):
3141 def _findtags(self):
3142 '''augment tags from base class with patch tags'''
3142 '''augment tags from base class with patch tags'''
3143 result = super(mqrepo, self)._findtags()
3143 result = super(mqrepo, self)._findtags()
3144
3144
3145 q = self.mq
3145 q = self.mq
3146 if not q.applied:
3146 if not q.applied:
3147 return result
3147 return result
3148
3148
3149 mqtags = [(patch.node, patch.name) for patch in q.applied]
3149 mqtags = [(patch.node, patch.name) for patch in q.applied]
3150
3150
3151 try:
3151 try:
3152 self.changelog.rev(mqtags[-1][0])
3152 self.changelog.rev(mqtags[-1][0])
3153 except error.RepoLookupError:
3153 except error.RepoLookupError:
3154 self.ui.warn(_('mq status file refers to unknown node %s\n')
3154 self.ui.warn(_('mq status file refers to unknown node %s\n')
3155 % short(mqtags[-1][0]))
3155 % short(mqtags[-1][0]))
3156 return result
3156 return result
3157
3157
3158 mqtags.append((mqtags[-1][0], 'qtip'))
3158 mqtags.append((mqtags[-1][0], 'qtip'))
3159 mqtags.append((mqtags[0][0], 'qbase'))
3159 mqtags.append((mqtags[0][0], 'qbase'))
3160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3161 tags = result[0]
3161 tags = result[0]
3162 for patch in mqtags:
3162 for patch in mqtags:
3163 if patch[1] in tags:
3163 if patch[1] in tags:
3164 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3164 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3165 % patch[1])
3165 % patch[1])
3166 else:
3166 else:
3167 tags[patch[1]] = patch[0]
3167 tags[patch[1]] = patch[0]
3168
3168
3169 return result
3169 return result
3170
3170
3171 def _branchtags(self, partial, lrev):
3171 def _branchtags(self, partial, lrev):
3172 q = self.mq
3172 q = self.mq
3173 if not q.applied:
3173 if not q.applied:
3174 return super(mqrepo, self)._branchtags(partial, lrev)
3174 return super(mqrepo, self)._branchtags(partial, lrev)
3175
3175
3176 cl = self.changelog
3176 cl = self.changelog
3177 qbasenode = q.applied[0].node
3177 qbasenode = q.applied[0].node
3178 try:
3178 try:
3179 qbase = cl.rev(qbasenode)
3179 qbase = cl.rev(qbasenode)
3180 except error.LookupError:
3180 except error.LookupError:
3181 self.ui.warn(_('mq status file refers to unknown node %s\n')
3181 self.ui.warn(_('mq status file refers to unknown node %s\n')
3182 % short(qbasenode))
3182 % short(qbasenode))
3183 return super(mqrepo, self)._branchtags(partial, lrev)
3183 return super(mqrepo, self)._branchtags(partial, lrev)
3184
3184
3185 start = lrev + 1
3185 start = lrev + 1
3186 if start < qbase:
3186 if start < qbase:
3187 # update the cache (excluding the patches) and save it
3187 # update the cache (excluding the patches) and save it
3188 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3188 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3189 self._updatebranchcache(partial, ctxgen)
3189 self._updatebranchcache(partial, ctxgen)
3190 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3190 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3191 start = qbase
3191 start = qbase
3192 # if start = qbase, the cache is as updated as it should be.
3192 # if start = qbase, the cache is as updated as it should be.
3193 # if start > qbase, the cache includes (part of) the patches.
3193 # if start > qbase, the cache includes (part of) the patches.
3194 # we might as well use it, but we won't save it.
3194 # we might as well use it, but we won't save it.
3195
3195
3196 # update the cache up to the tip
3196 # update the cache up to the tip
3197 ctxgen = (self[r] for r in xrange(start, len(cl)))
3197 ctxgen = (self[r] for r in xrange(start, len(cl)))
3198 self._updatebranchcache(partial, ctxgen)
3198 self._updatebranchcache(partial, ctxgen)
3199
3199
3200 return partial
3200 return partial
3201
3201
3202 if repo.local():
3202 if repo.local():
3203 repo.__class__ = mqrepo
3203 repo.__class__ = mqrepo
3204
3204
3205 def mqimport(orig, ui, repo, *args, **kwargs):
3205 def mqimport(orig, ui, repo, *args, **kwargs):
3206 if (hasattr(repo, 'abort_if_wdir_patched')
3206 if (hasattr(repo, 'abort_if_wdir_patched')
3207 and not kwargs.get('no_commit', False)):
3207 and not kwargs.get('no_commit', False)):
3208 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3208 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3209 kwargs.get('force'))
3209 kwargs.get('force'))
3210 return orig(ui, repo, *args, **kwargs)
3210 return orig(ui, repo, *args, **kwargs)
3211
3211
3212 def mqinit(orig, ui, *args, **kwargs):
3212 def mqinit(orig, ui, *args, **kwargs):
3213 mq = kwargs.pop('mq', None)
3213 mq = kwargs.pop('mq', None)
3214
3214
3215 if not mq:
3215 if not mq:
3216 return orig(ui, *args, **kwargs)
3216 return orig(ui, *args, **kwargs)
3217
3217
3218 if args:
3218 if args:
3219 repopath = args[0]
3219 repopath = args[0]
3220 if not hg.islocal(repopath):
3220 if not hg.islocal(repopath):
3221 raise util.Abort(_('only a local queue repository '
3221 raise util.Abort(_('only a local queue repository '
3222 'may be initialized'))
3222 'may be initialized'))
3223 else:
3223 else:
3224 repopath = cmdutil.findrepo(os.getcwd())
3224 repopath = cmdutil.findrepo(os.getcwd())
3225 if not repopath:
3225 if not repopath:
3226 raise util.Abort(_('there is no Mercurial repository here '
3226 raise util.Abort(_('there is no Mercurial repository here '
3227 '(.hg not found)'))
3227 '(.hg not found)'))
3228 repo = hg.repository(ui, repopath)
3228 repo = hg.repository(ui, repopath)
3229 return qinit(ui, repo, True)
3229 return qinit(ui, repo, True)
3230
3230
3231 def mqcommand(orig, ui, repo, *args, **kwargs):
3231 def mqcommand(orig, ui, repo, *args, **kwargs):
3232 """Add --mq option to operate on patch repository instead of main"""
3232 """Add --mq option to operate on patch repository instead of main"""
3233
3233
3234 # some commands do not like getting unknown options
3234 # some commands do not like getting unknown options
3235 mq = kwargs.pop('mq', None)
3235 mq = kwargs.pop('mq', None)
3236
3236
3237 if not mq:
3237 if not mq:
3238 return orig(ui, repo, *args, **kwargs)
3238 return orig(ui, repo, *args, **kwargs)
3239
3239
3240 q = repo.mq
3240 q = repo.mq
3241 r = q.qrepo()
3241 r = q.qrepo()
3242 if not r:
3242 if not r:
3243 raise util.Abort(_('no queue repository'))
3243 raise util.Abort(_('no queue repository'))
3244 return orig(r.ui, r, *args, **kwargs)
3244 return orig(r.ui, r, *args, **kwargs)
3245
3245
3246 def summary(orig, ui, repo, *args, **kwargs):
3246 def summary(orig, ui, repo, *args, **kwargs):
3247 r = orig(ui, repo, *args, **kwargs)
3247 r = orig(ui, repo, *args, **kwargs)
3248 q = repo.mq
3248 q = repo.mq
3249 m = []
3249 m = []
3250 a, u = len(q.applied), len(q.unapplied(repo))
3250 a, u = len(q.applied), len(q.unapplied(repo))
3251 if a:
3251 if a:
3252 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3252 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3253 if u:
3253 if u:
3254 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3254 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3255 if m:
3255 if m:
3256 ui.write("mq: %s\n" % ', '.join(m))
3256 ui.write("mq: %s\n" % ', '.join(m))
3257 else:
3257 else:
3258 ui.note(_("mq: (empty queue)\n"))
3258 ui.note(_("mq: (empty queue)\n"))
3259 return r
3259 return r
3260
3260
3261 def revsetmq(repo, subset, x):
3261 def revsetmq(repo, subset, x):
3262 """``mq()``
3262 """``mq()``
3263 Changesets managed by MQ.
3263 Changesets managed by MQ.
3264 """
3264 """
3265 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3265 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3266 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3266 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3267 return [r for r in subset if r in applied]
3267 return [r for r in subset if r in applied]
3268
3268
3269 def extsetup(ui):
3269 def extsetup(ui):
3270 revset.symbols['mq'] = revsetmq
3270 revset.symbols['mq'] = revsetmq
3271
3271
3272 # tell hggettext to extract docstrings from these functions:
3272 # tell hggettext to extract docstrings from these functions:
3273 i18nfunctions = [revsetmq]
3273 i18nfunctions = [revsetmq]
3274
3274
3275 def uisetup(ui):
3275 def uisetup(ui):
3276 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3276 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3277
3277
3278 extensions.wrapcommand(commands.table, 'import', mqimport)
3278 extensions.wrapcommand(commands.table, 'import', mqimport)
3279 extensions.wrapcommand(commands.table, 'summary', summary)
3279 extensions.wrapcommand(commands.table, 'summary', summary)
3280
3280
3281 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3281 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3282 entry[1].extend(mqopt)
3282 entry[1].extend(mqopt)
3283
3283
3284 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3284 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3285
3285
3286 def dotable(cmdtable):
3286 def dotable(cmdtable):
3287 for cmd in cmdtable.keys():
3287 for cmd in cmdtable.keys():
3288 cmd = cmdutil.parsealiases(cmd)[0]
3288 cmd = cmdutil.parsealiases(cmd)[0]
3289 if cmd in nowrap:
3289 if cmd in nowrap:
3290 continue
3290 continue
3291 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3291 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3292 entry[1].extend(mqopt)
3292 entry[1].extend(mqopt)
3293
3293
3294 dotable(commands.table)
3294 dotable(commands.table)
3295
3295
3296 for extname, extmodule in extensions.extensions():
3296 for extname, extmodule in extensions.extensions():
3297 if extmodule.__file__ != __file__:
3297 if extmodule.__file__ != __file__:
3298 dotable(getattr(extmodule, 'cmdtable', {}))
3298 dotable(getattr(extmodule, 'cmdtable', {}))
3299
3299
3300
3300
3301 colortable = {'qguard.negative': 'red',
3301 colortable = {'qguard.negative': 'red',
3302 'qguard.positive': 'yellow',
3302 'qguard.positive': 'yellow',
3303 'qguard.unguarded': 'green',
3303 'qguard.unguarded': 'green',
3304 'qseries.applied': 'blue bold underline',
3304 'qseries.applied': 'blue bold underline',
3305 'qseries.guarded': 'black bold',
3305 'qseries.guarded': 'black bold',
3306 'qseries.missing': 'red bold',
3306 'qseries.missing': 'red bold',
3307 'qseries.unapplied': 'black bold'}
3307 'qseries.unapplied': 'black bold'}
@@ -1,1745 +1,1752 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib
10 import tempfile, zlib
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
15
15
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
17
17
18 class PatchError(Exception):
18 class PatchError(Exception):
19 pass
19 pass
20
20
21
21
22 # public functions
22 # public functions
23
23
24 def split(stream):
24 def split(stream):
25 '''return an iterator of individual patches from a stream'''
25 '''return an iterator of individual patches from a stream'''
26 def isheader(line, inheader):
26 def isheader(line, inheader):
27 if inheader and line[0] in (' ', '\t'):
27 if inheader and line[0] in (' ', '\t'):
28 # continuation
28 # continuation
29 return True
29 return True
30 if line[0] in (' ', '-', '+'):
30 if line[0] in (' ', '-', '+'):
31 # diff line - don't check for header pattern in there
31 # diff line - don't check for header pattern in there
32 return False
32 return False
33 l = line.split(': ', 1)
33 l = line.split(': ', 1)
34 return len(l) == 2 and ' ' not in l[0]
34 return len(l) == 2 and ' ' not in l[0]
35
35
36 def chunk(lines):
36 def chunk(lines):
37 return cStringIO.StringIO(''.join(lines))
37 return cStringIO.StringIO(''.join(lines))
38
38
39 def hgsplit(stream, cur):
39 def hgsplit(stream, cur):
40 inheader = True
40 inheader = True
41
41
42 for line in stream:
42 for line in stream:
43 if not line.strip():
43 if not line.strip():
44 inheader = False
44 inheader = False
45 if not inheader and line.startswith('# HG changeset patch'):
45 if not inheader and line.startswith('# HG changeset patch'):
46 yield chunk(cur)
46 yield chunk(cur)
47 cur = []
47 cur = []
48 inheader = True
48 inheader = True
49
49
50 cur.append(line)
50 cur.append(line)
51
51
52 if cur:
52 if cur:
53 yield chunk(cur)
53 yield chunk(cur)
54
54
55 def mboxsplit(stream, cur):
55 def mboxsplit(stream, cur):
56 for line in stream:
56 for line in stream:
57 if line.startswith('From '):
57 if line.startswith('From '):
58 for c in split(chunk(cur[1:])):
58 for c in split(chunk(cur[1:])):
59 yield c
59 yield c
60 cur = []
60 cur = []
61
61
62 cur.append(line)
62 cur.append(line)
63
63
64 if cur:
64 if cur:
65 for c in split(chunk(cur[1:])):
65 for c in split(chunk(cur[1:])):
66 yield c
66 yield c
67
67
68 def mimesplit(stream, cur):
68 def mimesplit(stream, cur):
69 def msgfp(m):
69 def msgfp(m):
70 fp = cStringIO.StringIO()
70 fp = cStringIO.StringIO()
71 g = email.Generator.Generator(fp, mangle_from_=False)
71 g = email.Generator.Generator(fp, mangle_from_=False)
72 g.flatten(m)
72 g.flatten(m)
73 fp.seek(0)
73 fp.seek(0)
74 return fp
74 return fp
75
75
76 for line in stream:
76 for line in stream:
77 cur.append(line)
77 cur.append(line)
78 c = chunk(cur)
78 c = chunk(cur)
79
79
80 m = email.Parser.Parser().parse(c)
80 m = email.Parser.Parser().parse(c)
81 if not m.is_multipart():
81 if not m.is_multipart():
82 yield msgfp(m)
82 yield msgfp(m)
83 else:
83 else:
84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 for part in m.walk():
85 for part in m.walk():
86 ct = part.get_content_type()
86 ct = part.get_content_type()
87 if ct not in ok_types:
87 if ct not in ok_types:
88 continue
88 continue
89 yield msgfp(part)
89 yield msgfp(part)
90
90
91 def headersplit(stream, cur):
91 def headersplit(stream, cur):
92 inheader = False
92 inheader = False
93
93
94 for line in stream:
94 for line in stream:
95 if not inheader and isheader(line, inheader):
95 if not inheader and isheader(line, inheader):
96 yield chunk(cur)
96 yield chunk(cur)
97 cur = []
97 cur = []
98 inheader = True
98 inheader = True
99 if inheader and not isheader(line, inheader):
99 if inheader and not isheader(line, inheader):
100 inheader = False
100 inheader = False
101
101
102 cur.append(line)
102 cur.append(line)
103
103
104 if cur:
104 if cur:
105 yield chunk(cur)
105 yield chunk(cur)
106
106
107 def remainder(cur):
107 def remainder(cur):
108 yield chunk(cur)
108 yield chunk(cur)
109
109
110 class fiter(object):
110 class fiter(object):
111 def __init__(self, fp):
111 def __init__(self, fp):
112 self.fp = fp
112 self.fp = fp
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return self
115 return self
116
116
117 def next(self):
117 def next(self):
118 l = self.fp.readline()
118 l = self.fp.readline()
119 if not l:
119 if not l:
120 raise StopIteration
120 raise StopIteration
121 return l
121 return l
122
122
123 inheader = False
123 inheader = False
124 cur = []
124 cur = []
125
125
126 mimeheaders = ['content-type']
126 mimeheaders = ['content-type']
127
127
128 if not hasattr(stream, 'next'):
128 if not hasattr(stream, 'next'):
129 # http responses, for example, have readline but not next
129 # http responses, for example, have readline but not next
130 stream = fiter(stream)
130 stream = fiter(stream)
131
131
132 for line in stream:
132 for line in stream:
133 cur.append(line)
133 cur.append(line)
134 if line.startswith('# HG changeset patch'):
134 if line.startswith('# HG changeset patch'):
135 return hgsplit(stream, cur)
135 return hgsplit(stream, cur)
136 elif line.startswith('From '):
136 elif line.startswith('From '):
137 return mboxsplit(stream, cur)
137 return mboxsplit(stream, cur)
138 elif isheader(line, inheader):
138 elif isheader(line, inheader):
139 inheader = True
139 inheader = True
140 if line.split(':', 1)[0].lower() in mimeheaders:
140 if line.split(':', 1)[0].lower() in mimeheaders:
141 # let email parser handle this
141 # let email parser handle this
142 return mimesplit(stream, cur)
142 return mimesplit(stream, cur)
143 elif line.startswith('--- ') and inheader:
143 elif line.startswith('--- ') and inheader:
144 # No evil headers seen by diff start, split by hand
144 # No evil headers seen by diff start, split by hand
145 return headersplit(stream, cur)
145 return headersplit(stream, cur)
146 # Not enough info, keep reading
146 # Not enough info, keep reading
147
147
148 # if we are here, we have a very plain patch
148 # if we are here, we have a very plain patch
149 return remainder(cur)
149 return remainder(cur)
150
150
151 def extract(ui, fileobj):
151 def extract(ui, fileobj):
152 '''extract patch from data read from fileobj.
152 '''extract patch from data read from fileobj.
153
153
154 patch can be a normal patch or contained in an email message.
154 patch can be a normal patch or contained in an email message.
155
155
156 return tuple (filename, message, user, date, branch, node, p1, p2).
156 return tuple (filename, message, user, date, branch, node, p1, p2).
157 Any item in the returned tuple can be None. If filename is None,
157 Any item in the returned tuple can be None. If filename is None,
158 fileobj did not contain a patch. Caller must unlink filename when done.'''
158 fileobj did not contain a patch. Caller must unlink filename when done.'''
159
159
160 # attempt to detect the start of a patch
160 # attempt to detect the start of a patch
161 # (this heuristic is borrowed from quilt)
161 # (this heuristic is borrowed from quilt)
162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'---[ \t].*?^\+\+\+[ \t]|'
164 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166
166
167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 tmpfp = os.fdopen(fd, 'w')
168 tmpfp = os.fdopen(fd, 'w')
169 try:
169 try:
170 msg = email.Parser.Parser().parse(fileobj)
170 msg = email.Parser.Parser().parse(fileobj)
171
171
172 subject = msg['Subject']
172 subject = msg['Subject']
173 user = msg['From']
173 user = msg['From']
174 if not subject and not user:
174 if not subject and not user:
175 # Not an email, restore parsed headers if any
175 # Not an email, restore parsed headers if any
176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177
177
178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 # should try to parse msg['Date']
179 # should try to parse msg['Date']
180 date = None
180 date = None
181 nodeid = None
181 nodeid = None
182 branch = None
182 branch = None
183 parents = []
183 parents = []
184
184
185 if subject:
185 if subject:
186 if subject.startswith('[PATCH'):
186 if subject.startswith('[PATCH'):
187 pend = subject.find(']')
187 pend = subject.find(']')
188 if pend >= 0:
188 if pend >= 0:
189 subject = subject[pend + 1:].lstrip()
189 subject = subject[pend + 1:].lstrip()
190 subject = subject.replace('\n\t', ' ')
190 subject = subject.replace('\n\t', ' ')
191 ui.debug('Subject: %s\n' % subject)
191 ui.debug('Subject: %s\n' % subject)
192 if user:
192 if user:
193 ui.debug('From: %s\n' % user)
193 ui.debug('From: %s\n' % user)
194 diffs_seen = 0
194 diffs_seen = 0
195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 message = ''
196 message = ''
197 for part in msg.walk():
197 for part in msg.walk():
198 content_type = part.get_content_type()
198 content_type = part.get_content_type()
199 ui.debug('Content-Type: %s\n' % content_type)
199 ui.debug('Content-Type: %s\n' % content_type)
200 if content_type not in ok_types:
200 if content_type not in ok_types:
201 continue
201 continue
202 payload = part.get_payload(decode=True)
202 payload = part.get_payload(decode=True)
203 m = diffre.search(payload)
203 m = diffre.search(payload)
204 if m:
204 if m:
205 hgpatch = False
205 hgpatch = False
206 hgpatchheader = False
206 hgpatchheader = False
207 ignoretext = False
207 ignoretext = False
208
208
209 ui.debug('found patch at byte %d\n' % m.start(0))
209 ui.debug('found patch at byte %d\n' % m.start(0))
210 diffs_seen += 1
210 diffs_seen += 1
211 cfp = cStringIO.StringIO()
211 cfp = cStringIO.StringIO()
212 for line in payload[:m.start(0)].splitlines():
212 for line in payload[:m.start(0)].splitlines():
213 if line.startswith('# HG changeset patch') and not hgpatch:
213 if line.startswith('# HG changeset patch') and not hgpatch:
214 ui.debug('patch generated by hg export\n')
214 ui.debug('patch generated by hg export\n')
215 hgpatch = True
215 hgpatch = True
216 hgpatchheader = True
216 hgpatchheader = True
217 # drop earlier commit message content
217 # drop earlier commit message content
218 cfp.seek(0)
218 cfp.seek(0)
219 cfp.truncate()
219 cfp.truncate()
220 subject = None
220 subject = None
221 elif hgpatchheader:
221 elif hgpatchheader:
222 if line.startswith('# User '):
222 if line.startswith('# User '):
223 user = line[7:]
223 user = line[7:]
224 ui.debug('From: %s\n' % user)
224 ui.debug('From: %s\n' % user)
225 elif line.startswith("# Date "):
225 elif line.startswith("# Date "):
226 date = line[7:]
226 date = line[7:]
227 elif line.startswith("# Branch "):
227 elif line.startswith("# Branch "):
228 branch = line[9:]
228 branch = line[9:]
229 elif line.startswith("# Node ID "):
229 elif line.startswith("# Node ID "):
230 nodeid = line[10:]
230 nodeid = line[10:]
231 elif line.startswith("# Parent "):
231 elif line.startswith("# Parent "):
232 parents.append(line[10:])
232 parents.append(line[10:])
233 elif not line.startswith("# "):
233 elif not line.startswith("# "):
234 hgpatchheader = False
234 hgpatchheader = False
235 elif line == '---' and gitsendmail:
235 elif line == '---' and gitsendmail:
236 ignoretext = True
236 ignoretext = True
237 if not hgpatchheader and not ignoretext:
237 if not hgpatchheader and not ignoretext:
238 cfp.write(line)
238 cfp.write(line)
239 cfp.write('\n')
239 cfp.write('\n')
240 message = cfp.getvalue()
240 message = cfp.getvalue()
241 if tmpfp:
241 if tmpfp:
242 tmpfp.write(payload)
242 tmpfp.write(payload)
243 if not payload.endswith('\n'):
243 if not payload.endswith('\n'):
244 tmpfp.write('\n')
244 tmpfp.write('\n')
245 elif not diffs_seen and message and content_type == 'text/plain':
245 elif not diffs_seen and message and content_type == 'text/plain':
246 message += '\n' + payload
246 message += '\n' + payload
247 except:
247 except:
248 tmpfp.close()
248 tmpfp.close()
249 os.unlink(tmpname)
249 os.unlink(tmpname)
250 raise
250 raise
251
251
252 if subject and not message.startswith(subject):
252 if subject and not message.startswith(subject):
253 message = '%s\n%s' % (subject, message)
253 message = '%s\n%s' % (subject, message)
254 tmpfp.close()
254 tmpfp.close()
255 if not diffs_seen:
255 if not diffs_seen:
256 os.unlink(tmpname)
256 os.unlink(tmpname)
257 return None, message, user, date, branch, None, None, None
257 return None, message, user, date, branch, None, None, None
258 p1 = parents and parents.pop(0) or None
258 p1 = parents and parents.pop(0) or None
259 p2 = parents and parents.pop(0) or None
259 p2 = parents and parents.pop(0) or None
260 return tmpname, message, user, date, branch, nodeid, p1, p2
260 return tmpname, message, user, date, branch, nodeid, p1, p2
261
261
262 class patchmeta(object):
262 class patchmeta(object):
263 """Patched file metadata
263 """Patched file metadata
264
264
265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 or COPY. 'path' is patched file path. 'oldpath' is set to the
266 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 origin file when 'op' is either COPY or RENAME, None otherwise. If
267 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 file mode is changed, 'mode' is a tuple (islink, isexec) where
268 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 'islink' is True if the file is a symlink and 'isexec' is True if
269 'islink' is True if the file is a symlink and 'isexec' is True if
270 the file is executable. Otherwise, 'mode' is None.
270 the file is executable. Otherwise, 'mode' is None.
271 """
271 """
272 def __init__(self, path):
272 def __init__(self, path):
273 self.path = path
273 self.path = path
274 self.oldpath = None
274 self.oldpath = None
275 self.mode = None
275 self.mode = None
276 self.op = 'MODIFY'
276 self.op = 'MODIFY'
277 self.binary = False
277 self.binary = False
278
278
279 def setmode(self, mode):
279 def setmode(self, mode):
280 islink = mode & 020000
280 islink = mode & 020000
281 isexec = mode & 0100
281 isexec = mode & 0100
282 self.mode = (islink, isexec)
282 self.mode = (islink, isexec)
283
283
284 def __repr__(self):
284 def __repr__(self):
285 return "<patchmeta %s %r>" % (self.op, self.path)
285 return "<patchmeta %s %r>" % (self.op, self.path)
286
286
287 def readgitpatch(lr):
287 def readgitpatch(lr):
288 """extract git-style metadata about patches from <patchname>"""
288 """extract git-style metadata about patches from <patchname>"""
289
289
290 # Filter patch for git information
290 # Filter patch for git information
291 gp = None
291 gp = None
292 gitpatches = []
292 gitpatches = []
293 for line in lr:
293 for line in lr:
294 line = line.rstrip(' \r\n')
294 line = line.rstrip(' \r\n')
295 if line.startswith('diff --git'):
295 if line.startswith('diff --git'):
296 m = gitre.match(line)
296 m = gitre.match(line)
297 if m:
297 if m:
298 if gp:
298 if gp:
299 gitpatches.append(gp)
299 gitpatches.append(gp)
300 dst = m.group(2)
300 dst = m.group(2)
301 gp = patchmeta(dst)
301 gp = patchmeta(dst)
302 elif gp:
302 elif gp:
303 if line.startswith('--- '):
303 if line.startswith('--- '):
304 gitpatches.append(gp)
304 gitpatches.append(gp)
305 gp = None
305 gp = None
306 continue
306 continue
307 if line.startswith('rename from '):
307 if line.startswith('rename from '):
308 gp.op = 'RENAME'
308 gp.op = 'RENAME'
309 gp.oldpath = line[12:]
309 gp.oldpath = line[12:]
310 elif line.startswith('rename to '):
310 elif line.startswith('rename to '):
311 gp.path = line[10:]
311 gp.path = line[10:]
312 elif line.startswith('copy from '):
312 elif line.startswith('copy from '):
313 gp.op = 'COPY'
313 gp.op = 'COPY'
314 gp.oldpath = line[10:]
314 gp.oldpath = line[10:]
315 elif line.startswith('copy to '):
315 elif line.startswith('copy to '):
316 gp.path = line[8:]
316 gp.path = line[8:]
317 elif line.startswith('deleted file'):
317 elif line.startswith('deleted file'):
318 gp.op = 'DELETE'
318 gp.op = 'DELETE'
319 elif line.startswith('new file mode '):
319 elif line.startswith('new file mode '):
320 gp.op = 'ADD'
320 gp.op = 'ADD'
321 gp.setmode(int(line[-6:], 8))
321 gp.setmode(int(line[-6:], 8))
322 elif line.startswith('new mode '):
322 elif line.startswith('new mode '):
323 gp.setmode(int(line[-6:], 8))
323 gp.setmode(int(line[-6:], 8))
324 elif line.startswith('GIT binary patch'):
324 elif line.startswith('GIT binary patch'):
325 gp.binary = True
325 gp.binary = True
326 if gp:
326 if gp:
327 gitpatches.append(gp)
327 gitpatches.append(gp)
328
328
329 return gitpatches
329 return gitpatches
330
330
331 class linereader(object):
331 class linereader(object):
332 # simple class to allow pushing lines back into the input stream
332 # simple class to allow pushing lines back into the input stream
333 def __init__(self, fp, textmode=False):
333 def __init__(self, fp, textmode=False):
334 self.fp = fp
334 self.fp = fp
335 self.buf = []
335 self.buf = []
336 self.textmode = textmode
336 self.textmode = textmode
337 self.eol = None
337 self.eol = None
338
338
339 def push(self, line):
339 def push(self, line):
340 if line is not None:
340 if line is not None:
341 self.buf.append(line)
341 self.buf.append(line)
342
342
343 def readline(self):
343 def readline(self):
344 if self.buf:
344 if self.buf:
345 l = self.buf[0]
345 l = self.buf[0]
346 del self.buf[0]
346 del self.buf[0]
347 return l
347 return l
348 l = self.fp.readline()
348 l = self.fp.readline()
349 if not self.eol:
349 if not self.eol:
350 if l.endswith('\r\n'):
350 if l.endswith('\r\n'):
351 self.eol = '\r\n'
351 self.eol = '\r\n'
352 elif l.endswith('\n'):
352 elif l.endswith('\n'):
353 self.eol = '\n'
353 self.eol = '\n'
354 if self.textmode and l.endswith('\r\n'):
354 if self.textmode and l.endswith('\r\n'):
355 l = l[:-2] + '\n'
355 l = l[:-2] + '\n'
356 return l
356 return l
357
357
358 def __iter__(self):
358 def __iter__(self):
359 while 1:
359 while 1:
360 l = self.readline()
360 l = self.readline()
361 if not l:
361 if not l:
362 break
362 break
363 yield l
363 yield l
364
364
365 class abstractbackend(object):
365 class abstractbackend(object):
366 def __init__(self, ui):
366 def __init__(self, ui):
367 self.ui = ui
367 self.ui = ui
368
368
369 def readlines(self, fname):
369 def readlines(self, fname):
370 """Return target file lines, or its content as a single line
370 """Return target file lines, or its content as a single line
371 for symlinks.
371 for symlinks.
372 """
372 """
373 raise NotImplementedError
373 raise NotImplementedError
374
374
375 def writelines(self, fname, lines):
375 def writelines(self, fname, lines):
376 """Write lines to target file."""
376 """Write lines to target file."""
377 raise NotImplementedError
377 raise NotImplementedError
378
378
379 def unlink(self, fname):
379 def unlink(self, fname):
380 """Unlink target file."""
380 """Unlink target file."""
381 raise NotImplementedError
381 raise NotImplementedError
382
382
383 def writerej(self, fname, failed, total, lines):
383 def writerej(self, fname, failed, total, lines):
384 """Write rejected lines for fname. total is the number of hunks
384 """Write rejected lines for fname. total is the number of hunks
385 which failed to apply and total the total number of hunks for this
385 which failed to apply and total the total number of hunks for this
386 files.
386 files.
387 """
387 """
388 pass
388 pass
389
389
390 def copy(self, src, dst):
390 def copy(self, src, dst):
391 """Copy src file into dst file. Create intermediate directories if
391 """Copy src file into dst file. Create intermediate directories if
392 necessary. Files are specified relatively to the patching base
392 necessary. Files are specified relatively to the patching base
393 directory.
393 directory.
394 """
394 """
395 raise NotImplementedError
395 raise NotImplementedError
396
396
397 def exists(self, fname):
398 raise NotImplementedError
399
397 class fsbackend(abstractbackend):
400 class fsbackend(abstractbackend):
398 def __init__(self, ui, basedir):
401 def __init__(self, ui, basedir):
399 super(fsbackend, self).__init__(ui)
402 super(fsbackend, self).__init__(ui)
400 self.opener = scmutil.opener(basedir)
403 self.opener = scmutil.opener(basedir)
401
404
402 def readlines(self, fname):
405 def readlines(self, fname):
403 if os.path.islink(fname):
406 if os.path.islink(fname):
404 return [os.readlink(fname)]
407 return [os.readlink(fname)]
405 fp = self.opener(fname, 'r')
408 fp = self.opener(fname, 'r')
406 try:
409 try:
407 return list(fp)
410 return list(fp)
408 finally:
411 finally:
409 fp.close()
412 fp.close()
410
413
411 def writelines(self, fname, lines):
414 def writelines(self, fname, lines):
412 # Ensure supplied data ends in fname, being a regular file or
415 # Ensure supplied data ends in fname, being a regular file or
413 # a symlink. _updatedir will -too magically- take care
416 # a symlink. _updatedir will -too magically- take care
414 # of setting it to the proper type afterwards.
417 # of setting it to the proper type afterwards.
415 st_mode = None
418 st_mode = None
416 islink = os.path.islink(fname)
419 islink = os.path.islink(fname)
417 if islink:
420 if islink:
418 fp = cStringIO.StringIO()
421 fp = cStringIO.StringIO()
419 else:
422 else:
420 try:
423 try:
421 st_mode = os.lstat(fname).st_mode & 0777
424 st_mode = os.lstat(fname).st_mode & 0777
422 except OSError, e:
425 except OSError, e:
423 if e.errno != errno.ENOENT:
426 if e.errno != errno.ENOENT:
424 raise
427 raise
425 fp = self.opener(fname, 'w')
428 fp = self.opener(fname, 'w')
426 try:
429 try:
427 fp.writelines(lines)
430 fp.writelines(lines)
428 if islink:
431 if islink:
429 self.opener.symlink(fp.getvalue(), fname)
432 self.opener.symlink(fp.getvalue(), fname)
430 if st_mode is not None:
433 if st_mode is not None:
431 os.chmod(fname, st_mode)
434 os.chmod(fname, st_mode)
432 finally:
435 finally:
433 fp.close()
436 fp.close()
434
437
435 def unlink(self, fname):
438 def unlink(self, fname):
436 os.unlink(fname)
439 os.unlink(fname)
437
440
438 def writerej(self, fname, failed, total, lines):
441 def writerej(self, fname, failed, total, lines):
439 fname = fname + ".rej"
442 fname = fname + ".rej"
440 self.ui.warn(
443 self.ui.warn(
441 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
444 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
442 (failed, total, fname))
445 (failed, total, fname))
443 fp = self.opener(fname, 'w')
446 fp = self.opener(fname, 'w')
444 fp.writelines(lines)
447 fp.writelines(lines)
445 fp.close()
448 fp.close()
446
449
447 def copy(self, src, dst):
450 def copy(self, src, dst):
448 basedir = self.opener.base
451 basedir = self.opener.base
449 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
452 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
450 for x in [src, dst]]
453 for x in [src, dst]]
451 if os.path.lexists(absdst):
454 if os.path.lexists(absdst):
452 raise util.Abort(_("cannot create %s: destination already exists")
455 raise util.Abort(_("cannot create %s: destination already exists")
453 % dst)
456 % dst)
454 dstdir = os.path.dirname(absdst)
457 dstdir = os.path.dirname(absdst)
455 if dstdir and not os.path.isdir(dstdir):
458 if dstdir and not os.path.isdir(dstdir):
456 try:
459 try:
457 os.makedirs(dstdir)
460 os.makedirs(dstdir)
458 except IOError:
461 except IOError:
459 raise util.Abort(
462 raise util.Abort(
460 _("cannot create %s: unable to create destination directory")
463 _("cannot create %s: unable to create destination directory")
461 % dst)
464 % dst)
462 util.copyfile(abssrc, absdst)
465 util.copyfile(abssrc, absdst)
463
466
467 def exists(self, fname):
468 return os.path.lexists(fname)
469
464 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
470 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
465 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
471 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
466 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
472 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
467 eolmodes = ['strict', 'crlf', 'lf', 'auto']
473 eolmodes = ['strict', 'crlf', 'lf', 'auto']
468
474
469 class patchfile(object):
475 class patchfile(object):
470 def __init__(self, ui, fname, backend, missing=False, eolmode='strict'):
476 def __init__(self, ui, fname, backend, missing=False, eolmode='strict'):
471 self.fname = fname
477 self.fname = fname
472 self.eolmode = eolmode
478 self.eolmode = eolmode
473 self.eol = None
479 self.eol = None
474 self.backend = backend
480 self.backend = backend
475 self.ui = ui
481 self.ui = ui
476 self.lines = []
482 self.lines = []
477 self.exists = False
483 self.exists = False
478 self.missing = missing
484 self.missing = missing
479 if not missing:
485 if not missing:
480 try:
486 try:
481 self.lines = self.backend.readlines(fname)
487 self.lines = self.backend.readlines(fname)
482 if self.lines:
488 if self.lines:
483 # Normalize line endings
489 # Normalize line endings
484 if self.lines[0].endswith('\r\n'):
490 if self.lines[0].endswith('\r\n'):
485 self.eol = '\r\n'
491 self.eol = '\r\n'
486 elif self.lines[0].endswith('\n'):
492 elif self.lines[0].endswith('\n'):
487 self.eol = '\n'
493 self.eol = '\n'
488 if eolmode != 'strict':
494 if eolmode != 'strict':
489 nlines = []
495 nlines = []
490 for l in self.lines:
496 for l in self.lines:
491 if l.endswith('\r\n'):
497 if l.endswith('\r\n'):
492 l = l[:-2] + '\n'
498 l = l[:-2] + '\n'
493 nlines.append(l)
499 nlines.append(l)
494 self.lines = nlines
500 self.lines = nlines
495 self.exists = True
501 self.exists = True
496 except IOError:
502 except IOError:
497 pass
503 pass
498 else:
504 else:
499 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
505 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
500
506
501 self.hash = {}
507 self.hash = {}
502 self.dirty = 0
508 self.dirty = 0
503 self.offset = 0
509 self.offset = 0
504 self.skew = 0
510 self.skew = 0
505 self.rej = []
511 self.rej = []
506 self.fileprinted = False
512 self.fileprinted = False
507 self.printfile(False)
513 self.printfile(False)
508 self.hunks = 0
514 self.hunks = 0
509
515
510 def writelines(self, fname, lines):
516 def writelines(self, fname, lines):
511 if self.eolmode == 'auto':
517 if self.eolmode == 'auto':
512 eol = self.eol
518 eol = self.eol
513 elif self.eolmode == 'crlf':
519 elif self.eolmode == 'crlf':
514 eol = '\r\n'
520 eol = '\r\n'
515 else:
521 else:
516 eol = '\n'
522 eol = '\n'
517
523
518 if self.eolmode != 'strict' and eol and eol != '\n':
524 if self.eolmode != 'strict' and eol and eol != '\n':
519 rawlines = []
525 rawlines = []
520 for l in lines:
526 for l in lines:
521 if l and l[-1] == '\n':
527 if l and l[-1] == '\n':
522 l = l[:-1] + eol
528 l = l[:-1] + eol
523 rawlines.append(l)
529 rawlines.append(l)
524 lines = rawlines
530 lines = rawlines
525
531
526 self.backend.writelines(fname, lines)
532 self.backend.writelines(fname, lines)
527
533
528 def printfile(self, warn):
534 def printfile(self, warn):
529 if self.fileprinted:
535 if self.fileprinted:
530 return
536 return
531 if warn or self.ui.verbose:
537 if warn or self.ui.verbose:
532 self.fileprinted = True
538 self.fileprinted = True
533 s = _("patching file %s\n") % self.fname
539 s = _("patching file %s\n") % self.fname
534 if warn:
540 if warn:
535 self.ui.warn(s)
541 self.ui.warn(s)
536 else:
542 else:
537 self.ui.note(s)
543 self.ui.note(s)
538
544
539
545
540 def findlines(self, l, linenum):
546 def findlines(self, l, linenum):
541 # looks through the hash and finds candidate lines. The
547 # looks through the hash and finds candidate lines. The
542 # result is a list of line numbers sorted based on distance
548 # result is a list of line numbers sorted based on distance
543 # from linenum
549 # from linenum
544
550
545 cand = self.hash.get(l, [])
551 cand = self.hash.get(l, [])
546 if len(cand) > 1:
552 if len(cand) > 1:
547 # resort our list of potentials forward then back.
553 # resort our list of potentials forward then back.
548 cand.sort(key=lambda x: abs(x - linenum))
554 cand.sort(key=lambda x: abs(x - linenum))
549 return cand
555 return cand
550
556
551 def write_rej(self):
557 def write_rej(self):
552 # our rejects are a little different from patch(1). This always
558 # our rejects are a little different from patch(1). This always
553 # creates rejects in the same form as the original patch. A file
559 # creates rejects in the same form as the original patch. A file
554 # header is inserted so that you can run the reject through patch again
560 # header is inserted so that you can run the reject through patch again
555 # without having to type the filename.
561 # without having to type the filename.
556 if not self.rej:
562 if not self.rej:
557 return
563 return
558 base = os.path.basename(self.fname)
564 base = os.path.basename(self.fname)
559 lines = ["--- %s\n+++ %s\n" % (base, base)]
565 lines = ["--- %s\n+++ %s\n" % (base, base)]
560 for x in self.rej:
566 for x in self.rej:
561 for l in x.hunk:
567 for l in x.hunk:
562 lines.append(l)
568 lines.append(l)
563 if l[-1] != '\n':
569 if l[-1] != '\n':
564 lines.append("\n\ No newline at end of file\n")
570 lines.append("\n\ No newline at end of file\n")
565 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
571 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
566
572
567 def apply(self, h):
573 def apply(self, h):
568 if not h.complete():
574 if not h.complete():
569 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
575 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
570 (h.number, h.desc, len(h.a), h.lena, len(h.b),
576 (h.number, h.desc, len(h.a), h.lena, len(h.b),
571 h.lenb))
577 h.lenb))
572
578
573 self.hunks += 1
579 self.hunks += 1
574
580
575 if self.missing:
581 if self.missing:
576 self.rej.append(h)
582 self.rej.append(h)
577 return -1
583 return -1
578
584
579 if self.exists and h.createfile():
585 if self.exists and h.createfile():
580 self.ui.warn(_("file %s already exists\n") % self.fname)
586 self.ui.warn(_("file %s already exists\n") % self.fname)
581 self.rej.append(h)
587 self.rej.append(h)
582 return -1
588 return -1
583
589
584 if isinstance(h, binhunk):
590 if isinstance(h, binhunk):
585 if h.rmfile():
591 if h.rmfile():
586 self.backend.unlink(self.fname)
592 self.backend.unlink(self.fname)
587 else:
593 else:
588 self.lines[:] = h.new()
594 self.lines[:] = h.new()
589 self.offset += len(h.new())
595 self.offset += len(h.new())
590 self.dirty = True
596 self.dirty = True
591 return 0
597 return 0
592
598
593 horig = h
599 horig = h
594 if (self.eolmode in ('crlf', 'lf')
600 if (self.eolmode in ('crlf', 'lf')
595 or self.eolmode == 'auto' and self.eol):
601 or self.eolmode == 'auto' and self.eol):
596 # If new eols are going to be normalized, then normalize
602 # If new eols are going to be normalized, then normalize
597 # hunk data before patching. Otherwise, preserve input
603 # hunk data before patching. Otherwise, preserve input
598 # line-endings.
604 # line-endings.
599 h = h.getnormalized()
605 h = h.getnormalized()
600
606
601 # fast case first, no offsets, no fuzz
607 # fast case first, no offsets, no fuzz
602 old = h.old()
608 old = h.old()
603 # patch starts counting at 1 unless we are adding the file
609 # patch starts counting at 1 unless we are adding the file
604 if h.starta == 0:
610 if h.starta == 0:
605 start = 0
611 start = 0
606 else:
612 else:
607 start = h.starta + self.offset - 1
613 start = h.starta + self.offset - 1
608 orig_start = start
614 orig_start = start
609 # if there's skew we want to emit the "(offset %d lines)" even
615 # if there's skew we want to emit the "(offset %d lines)" even
610 # when the hunk cleanly applies at start + skew, so skip the
616 # when the hunk cleanly applies at start + skew, so skip the
611 # fast case code
617 # fast case code
612 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
618 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
613 if h.rmfile():
619 if h.rmfile():
614 self.backend.unlink(self.fname)
620 self.backend.unlink(self.fname)
615 else:
621 else:
616 self.lines[start : start + h.lena] = h.new()
622 self.lines[start : start + h.lena] = h.new()
617 self.offset += h.lenb - h.lena
623 self.offset += h.lenb - h.lena
618 self.dirty = True
624 self.dirty = True
619 return 0
625 return 0
620
626
621 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
627 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
622 self.hash = {}
628 self.hash = {}
623 for x, s in enumerate(self.lines):
629 for x, s in enumerate(self.lines):
624 self.hash.setdefault(s, []).append(x)
630 self.hash.setdefault(s, []).append(x)
625 if h.hunk[-1][0] != ' ':
631 if h.hunk[-1][0] != ' ':
626 # if the hunk tried to put something at the bottom of the file
632 # if the hunk tried to put something at the bottom of the file
627 # override the start line and use eof here
633 # override the start line and use eof here
628 search_start = len(self.lines)
634 search_start = len(self.lines)
629 else:
635 else:
630 search_start = orig_start + self.skew
636 search_start = orig_start + self.skew
631
637
632 for fuzzlen in xrange(3):
638 for fuzzlen in xrange(3):
633 for toponly in [True, False]:
639 for toponly in [True, False]:
634 old = h.old(fuzzlen, toponly)
640 old = h.old(fuzzlen, toponly)
635
641
636 cand = self.findlines(old[0][1:], search_start)
642 cand = self.findlines(old[0][1:], search_start)
637 for l in cand:
643 for l in cand:
638 if diffhelpers.testhunk(old, self.lines, l) == 0:
644 if diffhelpers.testhunk(old, self.lines, l) == 0:
639 newlines = h.new(fuzzlen, toponly)
645 newlines = h.new(fuzzlen, toponly)
640 self.lines[l : l + len(old)] = newlines
646 self.lines[l : l + len(old)] = newlines
641 self.offset += len(newlines) - len(old)
647 self.offset += len(newlines) - len(old)
642 self.skew = l - orig_start
648 self.skew = l - orig_start
643 self.dirty = True
649 self.dirty = True
644 offset = l - orig_start - fuzzlen
650 offset = l - orig_start - fuzzlen
645 if fuzzlen:
651 if fuzzlen:
646 msg = _("Hunk #%d succeeded at %d "
652 msg = _("Hunk #%d succeeded at %d "
647 "with fuzz %d "
653 "with fuzz %d "
648 "(offset %d lines).\n")
654 "(offset %d lines).\n")
649 self.printfile(True)
655 self.printfile(True)
650 self.ui.warn(msg %
656 self.ui.warn(msg %
651 (h.number, l + 1, fuzzlen, offset))
657 (h.number, l + 1, fuzzlen, offset))
652 else:
658 else:
653 msg = _("Hunk #%d succeeded at %d "
659 msg = _("Hunk #%d succeeded at %d "
654 "(offset %d lines).\n")
660 "(offset %d lines).\n")
655 self.ui.note(msg % (h.number, l + 1, offset))
661 self.ui.note(msg % (h.number, l + 1, offset))
656 return fuzzlen
662 return fuzzlen
657 self.printfile(True)
663 self.printfile(True)
658 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
664 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
659 self.rej.append(horig)
665 self.rej.append(horig)
660 return -1
666 return -1
661
667
662 def close(self):
668 def close(self):
663 if self.dirty:
669 if self.dirty:
664 self.writelines(self.fname, self.lines)
670 self.writelines(self.fname, self.lines)
665 self.write_rej()
671 self.write_rej()
666 return len(self.rej)
672 return len(self.rej)
667
673
668 class hunk(object):
674 class hunk(object):
669 def __init__(self, desc, num, lr, context, create=False, remove=False):
675 def __init__(self, desc, num, lr, context, create=False, remove=False):
670 self.number = num
676 self.number = num
671 self.desc = desc
677 self.desc = desc
672 self.hunk = [desc]
678 self.hunk = [desc]
673 self.a = []
679 self.a = []
674 self.b = []
680 self.b = []
675 self.starta = self.lena = None
681 self.starta = self.lena = None
676 self.startb = self.lenb = None
682 self.startb = self.lenb = None
677 if lr is not None:
683 if lr is not None:
678 if context:
684 if context:
679 self.read_context_hunk(lr)
685 self.read_context_hunk(lr)
680 else:
686 else:
681 self.read_unified_hunk(lr)
687 self.read_unified_hunk(lr)
682 self.create = create
688 self.create = create
683 self.remove = remove and not create
689 self.remove = remove and not create
684
690
685 def getnormalized(self):
691 def getnormalized(self):
686 """Return a copy with line endings normalized to LF."""
692 """Return a copy with line endings normalized to LF."""
687
693
688 def normalize(lines):
694 def normalize(lines):
689 nlines = []
695 nlines = []
690 for line in lines:
696 for line in lines:
691 if line.endswith('\r\n'):
697 if line.endswith('\r\n'):
692 line = line[:-2] + '\n'
698 line = line[:-2] + '\n'
693 nlines.append(line)
699 nlines.append(line)
694 return nlines
700 return nlines
695
701
696 # Dummy object, it is rebuilt manually
702 # Dummy object, it is rebuilt manually
697 nh = hunk(self.desc, self.number, None, None, False, False)
703 nh = hunk(self.desc, self.number, None, None, False, False)
698 nh.number = self.number
704 nh.number = self.number
699 nh.desc = self.desc
705 nh.desc = self.desc
700 nh.hunk = self.hunk
706 nh.hunk = self.hunk
701 nh.a = normalize(self.a)
707 nh.a = normalize(self.a)
702 nh.b = normalize(self.b)
708 nh.b = normalize(self.b)
703 nh.starta = self.starta
709 nh.starta = self.starta
704 nh.startb = self.startb
710 nh.startb = self.startb
705 nh.lena = self.lena
711 nh.lena = self.lena
706 nh.lenb = self.lenb
712 nh.lenb = self.lenb
707 nh.create = self.create
713 nh.create = self.create
708 nh.remove = self.remove
714 nh.remove = self.remove
709 return nh
715 return nh
710
716
711 def read_unified_hunk(self, lr):
717 def read_unified_hunk(self, lr):
712 m = unidesc.match(self.desc)
718 m = unidesc.match(self.desc)
713 if not m:
719 if not m:
714 raise PatchError(_("bad hunk #%d") % self.number)
720 raise PatchError(_("bad hunk #%d") % self.number)
715 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
721 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
716 if self.lena is None:
722 if self.lena is None:
717 self.lena = 1
723 self.lena = 1
718 else:
724 else:
719 self.lena = int(self.lena)
725 self.lena = int(self.lena)
720 if self.lenb is None:
726 if self.lenb is None:
721 self.lenb = 1
727 self.lenb = 1
722 else:
728 else:
723 self.lenb = int(self.lenb)
729 self.lenb = int(self.lenb)
724 self.starta = int(self.starta)
730 self.starta = int(self.starta)
725 self.startb = int(self.startb)
731 self.startb = int(self.startb)
726 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
732 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
727 # if we hit eof before finishing out the hunk, the last line will
733 # if we hit eof before finishing out the hunk, the last line will
728 # be zero length. Lets try to fix it up.
734 # be zero length. Lets try to fix it up.
729 while len(self.hunk[-1]) == 0:
735 while len(self.hunk[-1]) == 0:
730 del self.hunk[-1]
736 del self.hunk[-1]
731 del self.a[-1]
737 del self.a[-1]
732 del self.b[-1]
738 del self.b[-1]
733 self.lena -= 1
739 self.lena -= 1
734 self.lenb -= 1
740 self.lenb -= 1
735 self._fixnewline(lr)
741 self._fixnewline(lr)
736
742
737 def read_context_hunk(self, lr):
743 def read_context_hunk(self, lr):
738 self.desc = lr.readline()
744 self.desc = lr.readline()
739 m = contextdesc.match(self.desc)
745 m = contextdesc.match(self.desc)
740 if not m:
746 if not m:
741 raise PatchError(_("bad hunk #%d") % self.number)
747 raise PatchError(_("bad hunk #%d") % self.number)
742 foo, self.starta, foo2, aend, foo3 = m.groups()
748 foo, self.starta, foo2, aend, foo3 = m.groups()
743 self.starta = int(self.starta)
749 self.starta = int(self.starta)
744 if aend is None:
750 if aend is None:
745 aend = self.starta
751 aend = self.starta
746 self.lena = int(aend) - self.starta
752 self.lena = int(aend) - self.starta
747 if self.starta:
753 if self.starta:
748 self.lena += 1
754 self.lena += 1
749 for x in xrange(self.lena):
755 for x in xrange(self.lena):
750 l = lr.readline()
756 l = lr.readline()
751 if l.startswith('---'):
757 if l.startswith('---'):
752 # lines addition, old block is empty
758 # lines addition, old block is empty
753 lr.push(l)
759 lr.push(l)
754 break
760 break
755 s = l[2:]
761 s = l[2:]
756 if l.startswith('- ') or l.startswith('! '):
762 if l.startswith('- ') or l.startswith('! '):
757 u = '-' + s
763 u = '-' + s
758 elif l.startswith(' '):
764 elif l.startswith(' '):
759 u = ' ' + s
765 u = ' ' + s
760 else:
766 else:
761 raise PatchError(_("bad hunk #%d old text line %d") %
767 raise PatchError(_("bad hunk #%d old text line %d") %
762 (self.number, x))
768 (self.number, x))
763 self.a.append(u)
769 self.a.append(u)
764 self.hunk.append(u)
770 self.hunk.append(u)
765
771
766 l = lr.readline()
772 l = lr.readline()
767 if l.startswith('\ '):
773 if l.startswith('\ '):
768 s = self.a[-1][:-1]
774 s = self.a[-1][:-1]
769 self.a[-1] = s
775 self.a[-1] = s
770 self.hunk[-1] = s
776 self.hunk[-1] = s
771 l = lr.readline()
777 l = lr.readline()
772 m = contextdesc.match(l)
778 m = contextdesc.match(l)
773 if not m:
779 if not m:
774 raise PatchError(_("bad hunk #%d") % self.number)
780 raise PatchError(_("bad hunk #%d") % self.number)
775 foo, self.startb, foo2, bend, foo3 = m.groups()
781 foo, self.startb, foo2, bend, foo3 = m.groups()
776 self.startb = int(self.startb)
782 self.startb = int(self.startb)
777 if bend is None:
783 if bend is None:
778 bend = self.startb
784 bend = self.startb
779 self.lenb = int(bend) - self.startb
785 self.lenb = int(bend) - self.startb
780 if self.startb:
786 if self.startb:
781 self.lenb += 1
787 self.lenb += 1
782 hunki = 1
788 hunki = 1
783 for x in xrange(self.lenb):
789 for x in xrange(self.lenb):
784 l = lr.readline()
790 l = lr.readline()
785 if l.startswith('\ '):
791 if l.startswith('\ '):
786 # XXX: the only way to hit this is with an invalid line range.
792 # XXX: the only way to hit this is with an invalid line range.
787 # The no-eol marker is not counted in the line range, but I
793 # The no-eol marker is not counted in the line range, but I
788 # guess there are diff(1) out there which behave differently.
794 # guess there are diff(1) out there which behave differently.
789 s = self.b[-1][:-1]
795 s = self.b[-1][:-1]
790 self.b[-1] = s
796 self.b[-1] = s
791 self.hunk[hunki - 1] = s
797 self.hunk[hunki - 1] = s
792 continue
798 continue
793 if not l:
799 if not l:
794 # line deletions, new block is empty and we hit EOF
800 # line deletions, new block is empty and we hit EOF
795 lr.push(l)
801 lr.push(l)
796 break
802 break
797 s = l[2:]
803 s = l[2:]
798 if l.startswith('+ ') or l.startswith('! '):
804 if l.startswith('+ ') or l.startswith('! '):
799 u = '+' + s
805 u = '+' + s
800 elif l.startswith(' '):
806 elif l.startswith(' '):
801 u = ' ' + s
807 u = ' ' + s
802 elif len(self.b) == 0:
808 elif len(self.b) == 0:
803 # line deletions, new block is empty
809 # line deletions, new block is empty
804 lr.push(l)
810 lr.push(l)
805 break
811 break
806 else:
812 else:
807 raise PatchError(_("bad hunk #%d old text line %d") %
813 raise PatchError(_("bad hunk #%d old text line %d") %
808 (self.number, x))
814 (self.number, x))
809 self.b.append(s)
815 self.b.append(s)
810 while True:
816 while True:
811 if hunki >= len(self.hunk):
817 if hunki >= len(self.hunk):
812 h = ""
818 h = ""
813 else:
819 else:
814 h = self.hunk[hunki]
820 h = self.hunk[hunki]
815 hunki += 1
821 hunki += 1
816 if h == u:
822 if h == u:
817 break
823 break
818 elif h.startswith('-'):
824 elif h.startswith('-'):
819 continue
825 continue
820 else:
826 else:
821 self.hunk.insert(hunki - 1, u)
827 self.hunk.insert(hunki - 1, u)
822 break
828 break
823
829
824 if not self.a:
830 if not self.a:
825 # this happens when lines were only added to the hunk
831 # this happens when lines were only added to the hunk
826 for x in self.hunk:
832 for x in self.hunk:
827 if x.startswith('-') or x.startswith(' '):
833 if x.startswith('-') or x.startswith(' '):
828 self.a.append(x)
834 self.a.append(x)
829 if not self.b:
835 if not self.b:
830 # this happens when lines were only deleted from the hunk
836 # this happens when lines were only deleted from the hunk
831 for x in self.hunk:
837 for x in self.hunk:
832 if x.startswith('+') or x.startswith(' '):
838 if x.startswith('+') or x.startswith(' '):
833 self.b.append(x[1:])
839 self.b.append(x[1:])
834 # @@ -start,len +start,len @@
840 # @@ -start,len +start,len @@
835 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
841 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
836 self.startb, self.lenb)
842 self.startb, self.lenb)
837 self.hunk[0] = self.desc
843 self.hunk[0] = self.desc
838 self._fixnewline(lr)
844 self._fixnewline(lr)
839
845
840 def _fixnewline(self, lr):
846 def _fixnewline(self, lr):
841 l = lr.readline()
847 l = lr.readline()
842 if l.startswith('\ '):
848 if l.startswith('\ '):
843 diffhelpers.fix_newline(self.hunk, self.a, self.b)
849 diffhelpers.fix_newline(self.hunk, self.a, self.b)
844 else:
850 else:
845 lr.push(l)
851 lr.push(l)
846
852
847 def complete(self):
853 def complete(self):
848 return len(self.a) == self.lena and len(self.b) == self.lenb
854 return len(self.a) == self.lena and len(self.b) == self.lenb
849
855
850 def createfile(self):
856 def createfile(self):
851 return self.starta == 0 and self.lena == 0 and self.create
857 return self.starta == 0 and self.lena == 0 and self.create
852
858
853 def rmfile(self):
859 def rmfile(self):
854 return self.startb == 0 and self.lenb == 0 and self.remove
860 return self.startb == 0 and self.lenb == 0 and self.remove
855
861
856 def fuzzit(self, l, fuzz, toponly):
862 def fuzzit(self, l, fuzz, toponly):
857 # this removes context lines from the top and bottom of list 'l'. It
863 # this removes context lines from the top and bottom of list 'l'. It
858 # checks the hunk to make sure only context lines are removed, and then
864 # checks the hunk to make sure only context lines are removed, and then
859 # returns a new shortened list of lines.
865 # returns a new shortened list of lines.
860 fuzz = min(fuzz, len(l)-1)
866 fuzz = min(fuzz, len(l)-1)
861 if fuzz:
867 if fuzz:
862 top = 0
868 top = 0
863 bot = 0
869 bot = 0
864 hlen = len(self.hunk)
870 hlen = len(self.hunk)
865 for x in xrange(hlen - 1):
871 for x in xrange(hlen - 1):
866 # the hunk starts with the @@ line, so use x+1
872 # the hunk starts with the @@ line, so use x+1
867 if self.hunk[x + 1][0] == ' ':
873 if self.hunk[x + 1][0] == ' ':
868 top += 1
874 top += 1
869 else:
875 else:
870 break
876 break
871 if not toponly:
877 if not toponly:
872 for x in xrange(hlen - 1):
878 for x in xrange(hlen - 1):
873 if self.hunk[hlen - bot - 1][0] == ' ':
879 if self.hunk[hlen - bot - 1][0] == ' ':
874 bot += 1
880 bot += 1
875 else:
881 else:
876 break
882 break
877
883
878 # top and bot now count context in the hunk
884 # top and bot now count context in the hunk
879 # adjust them if either one is short
885 # adjust them if either one is short
880 context = max(top, bot, 3)
886 context = max(top, bot, 3)
881 if bot < context:
887 if bot < context:
882 bot = max(0, fuzz - (context - bot))
888 bot = max(0, fuzz - (context - bot))
883 else:
889 else:
884 bot = min(fuzz, bot)
890 bot = min(fuzz, bot)
885 if top < context:
891 if top < context:
886 top = max(0, fuzz - (context - top))
892 top = max(0, fuzz - (context - top))
887 else:
893 else:
888 top = min(fuzz, top)
894 top = min(fuzz, top)
889
895
890 return l[top:len(l)-bot]
896 return l[top:len(l)-bot]
891 return l
897 return l
892
898
893 def old(self, fuzz=0, toponly=False):
899 def old(self, fuzz=0, toponly=False):
894 return self.fuzzit(self.a, fuzz, toponly)
900 return self.fuzzit(self.a, fuzz, toponly)
895
901
896 def new(self, fuzz=0, toponly=False):
902 def new(self, fuzz=0, toponly=False):
897 return self.fuzzit(self.b, fuzz, toponly)
903 return self.fuzzit(self.b, fuzz, toponly)
898
904
899 class binhunk:
905 class binhunk:
900 'A binary patch file. Only understands literals so far.'
906 'A binary patch file. Only understands literals so far.'
901 def __init__(self, gitpatch):
907 def __init__(self, gitpatch):
902 self.gitpatch = gitpatch
908 self.gitpatch = gitpatch
903 self.text = None
909 self.text = None
904 self.hunk = ['GIT binary patch\n']
910 self.hunk = ['GIT binary patch\n']
905
911
906 def createfile(self):
912 def createfile(self):
907 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
913 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
908
914
909 def rmfile(self):
915 def rmfile(self):
910 return self.gitpatch.op == 'DELETE'
916 return self.gitpatch.op == 'DELETE'
911
917
912 def complete(self):
918 def complete(self):
913 return self.text is not None
919 return self.text is not None
914
920
915 def new(self):
921 def new(self):
916 return [self.text]
922 return [self.text]
917
923
918 def extract(self, lr):
924 def extract(self, lr):
919 line = lr.readline()
925 line = lr.readline()
920 self.hunk.append(line)
926 self.hunk.append(line)
921 while line and not line.startswith('literal '):
927 while line and not line.startswith('literal '):
922 line = lr.readline()
928 line = lr.readline()
923 self.hunk.append(line)
929 self.hunk.append(line)
924 if not line:
930 if not line:
925 raise PatchError(_('could not extract binary patch'))
931 raise PatchError(_('could not extract binary patch'))
926 size = int(line[8:].rstrip())
932 size = int(line[8:].rstrip())
927 dec = []
933 dec = []
928 line = lr.readline()
934 line = lr.readline()
929 self.hunk.append(line)
935 self.hunk.append(line)
930 while len(line) > 1:
936 while len(line) > 1:
931 l = line[0]
937 l = line[0]
932 if l <= 'Z' and l >= 'A':
938 if l <= 'Z' and l >= 'A':
933 l = ord(l) - ord('A') + 1
939 l = ord(l) - ord('A') + 1
934 else:
940 else:
935 l = ord(l) - ord('a') + 27
941 l = ord(l) - ord('a') + 27
936 dec.append(base85.b85decode(line[1:-1])[:l])
942 dec.append(base85.b85decode(line[1:-1])[:l])
937 line = lr.readline()
943 line = lr.readline()
938 self.hunk.append(line)
944 self.hunk.append(line)
939 text = zlib.decompress(''.join(dec))
945 text = zlib.decompress(''.join(dec))
940 if len(text) != size:
946 if len(text) != size:
941 raise PatchError(_('binary patch is %d bytes, not %d') %
947 raise PatchError(_('binary patch is %d bytes, not %d') %
942 len(text), size)
948 len(text), size)
943 self.text = text
949 self.text = text
944
950
945 def parsefilename(str):
951 def parsefilename(str):
946 # --- filename \t|space stuff
952 # --- filename \t|space stuff
947 s = str[4:].rstrip('\r\n')
953 s = str[4:].rstrip('\r\n')
948 i = s.find('\t')
954 i = s.find('\t')
949 if i < 0:
955 if i < 0:
950 i = s.find(' ')
956 i = s.find(' ')
951 if i < 0:
957 if i < 0:
952 return s
958 return s
953 return s[:i]
959 return s[:i]
954
960
955 def pathstrip(path, strip):
961 def pathstrip(path, strip):
956 pathlen = len(path)
962 pathlen = len(path)
957 i = 0
963 i = 0
958 if strip == 0:
964 if strip == 0:
959 return '', path.rstrip()
965 return '', path.rstrip()
960 count = strip
966 count = strip
961 while count > 0:
967 while count > 0:
962 i = path.find('/', i)
968 i = path.find('/', i)
963 if i == -1:
969 if i == -1:
964 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
970 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
965 (count, strip, path))
971 (count, strip, path))
966 i += 1
972 i += 1
967 # consume '//' in the path
973 # consume '//' in the path
968 while i < pathlen - 1 and path[i] == '/':
974 while i < pathlen - 1 and path[i] == '/':
969 i += 1
975 i += 1
970 count -= 1
976 count -= 1
971 return path[:i].lstrip(), path[i:].rstrip()
977 return path[:i].lstrip(), path[i:].rstrip()
972
978
973 def selectfile(afile_orig, bfile_orig, hunk, strip):
979 def selectfile(backend, afile_orig, bfile_orig, hunk, strip):
974 nulla = afile_orig == "/dev/null"
980 nulla = afile_orig == "/dev/null"
975 nullb = bfile_orig == "/dev/null"
981 nullb = bfile_orig == "/dev/null"
976 abase, afile = pathstrip(afile_orig, strip)
982 abase, afile = pathstrip(afile_orig, strip)
977 gooda = not nulla and os.path.lexists(afile)
983 gooda = not nulla and backend.exists(afile)
978 bbase, bfile = pathstrip(bfile_orig, strip)
984 bbase, bfile = pathstrip(bfile_orig, strip)
979 if afile == bfile:
985 if afile == bfile:
980 goodb = gooda
986 goodb = gooda
981 else:
987 else:
982 goodb = not nullb and os.path.lexists(bfile)
988 goodb = not nullb and backend.exists(bfile)
983 createfunc = hunk.createfile
989 createfunc = hunk.createfile
984 missing = not goodb and not gooda and not createfunc()
990 missing = not goodb and not gooda and not createfunc()
985
991
986 # some diff programs apparently produce patches where the afile is
992 # some diff programs apparently produce patches where the afile is
987 # not /dev/null, but afile starts with bfile
993 # not /dev/null, but afile starts with bfile
988 abasedir = afile[:afile.rfind('/') + 1]
994 abasedir = afile[:afile.rfind('/') + 1]
989 bbasedir = bfile[:bfile.rfind('/') + 1]
995 bbasedir = bfile[:bfile.rfind('/') + 1]
990 if missing and abasedir == bbasedir and afile.startswith(bfile):
996 if missing and abasedir == bbasedir and afile.startswith(bfile):
991 # this isn't very pretty
997 # this isn't very pretty
992 hunk.create = True
998 hunk.create = True
993 if createfunc():
999 if createfunc():
994 missing = False
1000 missing = False
995 else:
1001 else:
996 hunk.create = False
1002 hunk.create = False
997
1003
998 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1004 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
999 # diff is between a file and its backup. In this case, the original
1005 # diff is between a file and its backup. In this case, the original
1000 # file should be patched (see original mpatch code).
1006 # file should be patched (see original mpatch code).
1001 isbackup = (abase == bbase and bfile.startswith(afile))
1007 isbackup = (abase == bbase and bfile.startswith(afile))
1002 fname = None
1008 fname = None
1003 if not missing:
1009 if not missing:
1004 if gooda and goodb:
1010 if gooda and goodb:
1005 fname = isbackup and afile or bfile
1011 fname = isbackup and afile or bfile
1006 elif gooda:
1012 elif gooda:
1007 fname = afile
1013 fname = afile
1008
1014
1009 if not fname:
1015 if not fname:
1010 if not nullb:
1016 if not nullb:
1011 fname = isbackup and afile or bfile
1017 fname = isbackup and afile or bfile
1012 elif not nulla:
1018 elif not nulla:
1013 fname = afile
1019 fname = afile
1014 else:
1020 else:
1015 raise PatchError(_("undefined source and destination files"))
1021 raise PatchError(_("undefined source and destination files"))
1016
1022
1017 return fname, missing
1023 return fname, missing
1018
1024
1019 def scangitpatch(lr, firstline):
1025 def scangitpatch(lr, firstline):
1020 """
1026 """
1021 Git patches can emit:
1027 Git patches can emit:
1022 - rename a to b
1028 - rename a to b
1023 - change b
1029 - change b
1024 - copy a to c
1030 - copy a to c
1025 - change c
1031 - change c
1026
1032
1027 We cannot apply this sequence as-is, the renamed 'a' could not be
1033 We cannot apply this sequence as-is, the renamed 'a' could not be
1028 found for it would have been renamed already. And we cannot copy
1034 found for it would have been renamed already. And we cannot copy
1029 from 'b' instead because 'b' would have been changed already. So
1035 from 'b' instead because 'b' would have been changed already. So
1030 we scan the git patch for copy and rename commands so we can
1036 we scan the git patch for copy and rename commands so we can
1031 perform the copies ahead of time.
1037 perform the copies ahead of time.
1032 """
1038 """
1033 pos = 0
1039 pos = 0
1034 try:
1040 try:
1035 pos = lr.fp.tell()
1041 pos = lr.fp.tell()
1036 fp = lr.fp
1042 fp = lr.fp
1037 except IOError:
1043 except IOError:
1038 fp = cStringIO.StringIO(lr.fp.read())
1044 fp = cStringIO.StringIO(lr.fp.read())
1039 gitlr = linereader(fp, lr.textmode)
1045 gitlr = linereader(fp, lr.textmode)
1040 gitlr.push(firstline)
1046 gitlr.push(firstline)
1041 gitpatches = readgitpatch(gitlr)
1047 gitpatches = readgitpatch(gitlr)
1042 fp.seek(pos)
1048 fp.seek(pos)
1043 return gitpatches
1049 return gitpatches
1044
1050
1045 def iterhunks(fp):
1051 def iterhunks(fp):
1046 """Read a patch and yield the following events:
1052 """Read a patch and yield the following events:
1047 - ("file", afile, bfile, firsthunk): select a new target file.
1053 - ("file", afile, bfile, firsthunk): select a new target file.
1048 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1054 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1049 "file" event.
1055 "file" event.
1050 - ("git", gitchanges): current diff is in git format, gitchanges
1056 - ("git", gitchanges): current diff is in git format, gitchanges
1051 maps filenames to gitpatch records. Unique event.
1057 maps filenames to gitpatch records. Unique event.
1052 """
1058 """
1053 changed = {}
1059 changed = {}
1054 afile = ""
1060 afile = ""
1055 bfile = ""
1061 bfile = ""
1056 state = None
1062 state = None
1057 hunknum = 0
1063 hunknum = 0
1058 emitfile = newfile = False
1064 emitfile = newfile = False
1059 git = False
1065 git = False
1060
1066
1061 # our states
1067 # our states
1062 BFILE = 1
1068 BFILE = 1
1063 context = None
1069 context = None
1064 lr = linereader(fp)
1070 lr = linereader(fp)
1065
1071
1066 while True:
1072 while True:
1067 x = lr.readline()
1073 x = lr.readline()
1068 if not x:
1074 if not x:
1069 break
1075 break
1070 if (state == BFILE and ((not context and x[0] == '@') or
1076 if (state == BFILE and ((not context and x[0] == '@') or
1071 ((context is not False) and x.startswith('***************')))):
1077 ((context is not False) and x.startswith('***************')))):
1072 if context is None and x.startswith('***************'):
1078 if context is None and x.startswith('***************'):
1073 context = True
1079 context = True
1074 gpatch = changed.get(bfile)
1080 gpatch = changed.get(bfile)
1075 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1081 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1076 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1082 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1077 h = hunk(x, hunknum + 1, lr, context, create, remove)
1083 h = hunk(x, hunknum + 1, lr, context, create, remove)
1078 hunknum += 1
1084 hunknum += 1
1079 if emitfile:
1085 if emitfile:
1080 emitfile = False
1086 emitfile = False
1081 yield 'file', (afile, bfile, h)
1087 yield 'file', (afile, bfile, h)
1082 yield 'hunk', h
1088 yield 'hunk', h
1083 elif state == BFILE and x.startswith('GIT binary patch'):
1089 elif state == BFILE and x.startswith('GIT binary patch'):
1084 h = binhunk(changed[bfile])
1090 h = binhunk(changed[bfile])
1085 hunknum += 1
1091 hunknum += 1
1086 if emitfile:
1092 if emitfile:
1087 emitfile = False
1093 emitfile = False
1088 yield 'file', ('a/' + afile, 'b/' + bfile, h)
1094 yield 'file', ('a/' + afile, 'b/' + bfile, h)
1089 h.extract(lr)
1095 h.extract(lr)
1090 yield 'hunk', h
1096 yield 'hunk', h
1091 elif x.startswith('diff --git'):
1097 elif x.startswith('diff --git'):
1092 # check for git diff, scanning the whole patch file if needed
1098 # check for git diff, scanning the whole patch file if needed
1093 m = gitre.match(x)
1099 m = gitre.match(x)
1094 if m:
1100 if m:
1095 afile, bfile = m.group(1, 2)
1101 afile, bfile = m.group(1, 2)
1096 if not git:
1102 if not git:
1097 git = True
1103 git = True
1098 gitpatches = scangitpatch(lr, x)
1104 gitpatches = scangitpatch(lr, x)
1099 yield 'git', gitpatches
1105 yield 'git', gitpatches
1100 for gp in gitpatches:
1106 for gp in gitpatches:
1101 changed[gp.path] = gp
1107 changed[gp.path] = gp
1102 # else error?
1108 # else error?
1103 # copy/rename + modify should modify target, not source
1109 # copy/rename + modify should modify target, not source
1104 gp = changed.get(bfile)
1110 gp = changed.get(bfile)
1105 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1111 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1106 or gp.mode):
1112 or gp.mode):
1107 afile = bfile
1113 afile = bfile
1108 newfile = True
1114 newfile = True
1109 elif x.startswith('---'):
1115 elif x.startswith('---'):
1110 # check for a unified diff
1116 # check for a unified diff
1111 l2 = lr.readline()
1117 l2 = lr.readline()
1112 if not l2.startswith('+++'):
1118 if not l2.startswith('+++'):
1113 lr.push(l2)
1119 lr.push(l2)
1114 continue
1120 continue
1115 newfile = True
1121 newfile = True
1116 context = False
1122 context = False
1117 afile = parsefilename(x)
1123 afile = parsefilename(x)
1118 bfile = parsefilename(l2)
1124 bfile = parsefilename(l2)
1119 elif x.startswith('***'):
1125 elif x.startswith('***'):
1120 # check for a context diff
1126 # check for a context diff
1121 l2 = lr.readline()
1127 l2 = lr.readline()
1122 if not l2.startswith('---'):
1128 if not l2.startswith('---'):
1123 lr.push(l2)
1129 lr.push(l2)
1124 continue
1130 continue
1125 l3 = lr.readline()
1131 l3 = lr.readline()
1126 lr.push(l3)
1132 lr.push(l3)
1127 if not l3.startswith("***************"):
1133 if not l3.startswith("***************"):
1128 lr.push(l2)
1134 lr.push(l2)
1129 continue
1135 continue
1130 newfile = True
1136 newfile = True
1131 context = True
1137 context = True
1132 afile = parsefilename(x)
1138 afile = parsefilename(x)
1133 bfile = parsefilename(l2)
1139 bfile = parsefilename(l2)
1134
1140
1135 if newfile:
1141 if newfile:
1136 newfile = False
1142 newfile = False
1137 emitfile = True
1143 emitfile = True
1138 state = BFILE
1144 state = BFILE
1139 hunknum = 0
1145 hunknum = 0
1140
1146
1141 def applydiff(ui, fp, changed, strip=1, eolmode='strict'):
1147 def applydiff(ui, fp, changed, strip=1, eolmode='strict'):
1142 """Reads a patch from fp and tries to apply it.
1148 """Reads a patch from fp and tries to apply it.
1143
1149
1144 The dict 'changed' is filled in with all of the filenames changed
1150 The dict 'changed' is filled in with all of the filenames changed
1145 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1151 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1146 found and 1 if there was any fuzz.
1152 found and 1 if there was any fuzz.
1147
1153
1148 If 'eolmode' is 'strict', the patch content and patched file are
1154 If 'eolmode' is 'strict', the patch content and patched file are
1149 read in binary mode. Otherwise, line endings are ignored when
1155 read in binary mode. Otherwise, line endings are ignored when
1150 patching then normalized according to 'eolmode'.
1156 patching then normalized according to 'eolmode'.
1151
1157
1152 Callers probably want to call '_updatedir' after this to
1158 Callers probably want to call '_updatedir' after this to
1153 apply certain categories of changes not done by this function.
1159 apply certain categories of changes not done by this function.
1154 """
1160 """
1155 return _applydiff(ui, fp, patchfile, changed, strip=strip,
1161 return _applydiff(ui, fp, patchfile, changed, strip=strip,
1156 eolmode=eolmode)
1162 eolmode=eolmode)
1157
1163
1158 def _applydiff(ui, fp, patcher, changed, strip=1, eolmode='strict'):
1164 def _applydiff(ui, fp, patcher, changed, strip=1, eolmode='strict'):
1159 rejects = 0
1165 rejects = 0
1160 err = 0
1166 err = 0
1161 current_file = None
1167 current_file = None
1162 cwd = os.getcwd()
1168 cwd = os.getcwd()
1163 backend = fsbackend(ui, os.getcwd())
1169 backend = fsbackend(ui, os.getcwd())
1164
1170
1165 for state, values in iterhunks(fp):
1171 for state, values in iterhunks(fp):
1166 if state == 'hunk':
1172 if state == 'hunk':
1167 if not current_file:
1173 if not current_file:
1168 continue
1174 continue
1169 ret = current_file.apply(values)
1175 ret = current_file.apply(values)
1170 if ret >= 0:
1176 if ret >= 0:
1171 changed.setdefault(current_file.fname, None)
1177 changed.setdefault(current_file.fname, None)
1172 if ret > 0:
1178 if ret > 0:
1173 err = 1
1179 err = 1
1174 elif state == 'file':
1180 elif state == 'file':
1175 if current_file:
1181 if current_file:
1176 rejects += current_file.close()
1182 rejects += current_file.close()
1177 afile, bfile, first_hunk = values
1183 afile, bfile, first_hunk = values
1178 try:
1184 try:
1179 current_file, missing = selectfile(afile, bfile,
1185 current_file, missing = selectfile(backend, afile, bfile,
1180 first_hunk, strip)
1186 first_hunk, strip)
1181 current_file = patcher(ui, current_file, backend,
1187 current_file = patcher(ui, current_file, backend,
1182 missing=missing, eolmode=eolmode)
1188 missing=missing, eolmode=eolmode)
1183 except PatchError, inst:
1189 except PatchError, inst:
1184 ui.warn(str(inst) + '\n')
1190 ui.warn(str(inst) + '\n')
1185 current_file = None
1191 current_file = None
1186 rejects += 1
1192 rejects += 1
1187 continue
1193 continue
1188 elif state == 'git':
1194 elif state == 'git':
1189 for gp in values:
1195 for gp in values:
1190 gp.path = pathstrip(gp.path, strip - 1)[1]
1196 gp.path = pathstrip(gp.path, strip - 1)[1]
1191 if gp.oldpath:
1197 if gp.oldpath:
1192 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1198 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1193 # Binary patches really overwrite target files, copying them
1199 # Binary patches really overwrite target files, copying them
1194 # will just make it fails with "target file exists"
1200 # will just make it fails with "target file exists"
1195 if gp.op in ('COPY', 'RENAME') and not gp.binary:
1201 if gp.op in ('COPY', 'RENAME') and not gp.binary:
1196 backend.copy(gp.oldpath, gp.path)
1202 backend.copy(gp.oldpath, gp.path)
1197 changed[gp.path] = gp
1203 changed[gp.path] = gp
1198 else:
1204 else:
1199 raise util.Abort(_('unsupported parser state: %s') % state)
1205 raise util.Abort(_('unsupported parser state: %s') % state)
1200
1206
1201 if current_file:
1207 if current_file:
1202 rejects += current_file.close()
1208 rejects += current_file.close()
1203
1209
1204 if rejects:
1210 if rejects:
1205 return -1
1211 return -1
1206 return err
1212 return err
1207
1213
1208 def _updatedir(ui, repo, patches, similarity=0):
1214 def _updatedir(ui, repo, patches, similarity=0):
1209 '''Update dirstate after patch application according to metadata'''
1215 '''Update dirstate after patch application according to metadata'''
1210 if not patches:
1216 if not patches:
1211 return []
1217 return []
1212 copies = []
1218 copies = []
1213 removes = set()
1219 removes = set()
1214 cfiles = patches.keys()
1220 cfiles = patches.keys()
1215 cwd = repo.getcwd()
1221 cwd = repo.getcwd()
1216 if cwd:
1222 if cwd:
1217 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1223 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1218 for f in patches:
1224 for f in patches:
1219 gp = patches[f]
1225 gp = patches[f]
1220 if not gp:
1226 if not gp:
1221 continue
1227 continue
1222 if gp.op == 'RENAME':
1228 if gp.op == 'RENAME':
1223 copies.append((gp.oldpath, gp.path))
1229 copies.append((gp.oldpath, gp.path))
1224 removes.add(gp.oldpath)
1230 removes.add(gp.oldpath)
1225 elif gp.op == 'COPY':
1231 elif gp.op == 'COPY':
1226 copies.append((gp.oldpath, gp.path))
1232 copies.append((gp.oldpath, gp.path))
1227 elif gp.op == 'DELETE':
1233 elif gp.op == 'DELETE':
1228 removes.add(gp.path)
1234 removes.add(gp.path)
1229
1235
1230 wctx = repo[None]
1236 wctx = repo[None]
1231 for src, dst in copies:
1237 for src, dst in copies:
1232 scmutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
1238 scmutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
1233 if (not similarity) and removes:
1239 if (not similarity) and removes:
1234 wctx.remove(sorted(removes), True)
1240 wctx.remove(sorted(removes), True)
1235
1241
1236 for f in patches:
1242 for f in patches:
1237 gp = patches[f]
1243 gp = patches[f]
1238 if gp and gp.mode:
1244 if gp and gp.mode:
1239 islink, isexec = gp.mode
1245 islink, isexec = gp.mode
1240 dst = repo.wjoin(gp.path)
1246 dst = repo.wjoin(gp.path)
1241 # patch won't create empty files
1247 # patch won't create empty files
1242 if gp.op == 'ADD' and not os.path.lexists(dst):
1248 if gp.op == 'ADD' and not os.path.lexists(dst):
1243 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1249 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1244 repo.wwrite(gp.path, '', flags)
1250 repo.wwrite(gp.path, '', flags)
1245 util.setflags(dst, islink, isexec)
1251 util.setflags(dst, islink, isexec)
1246 scmutil.addremove(repo, cfiles, similarity=similarity)
1252 scmutil.addremove(repo, cfiles, similarity=similarity)
1247 files = patches.keys()
1253 files = patches.keys()
1248 files.extend([r for r in removes if r not in files])
1254 files.extend([r for r in removes if r not in files])
1249 return sorted(files)
1255 return sorted(files)
1250
1256
1251 def _externalpatch(patcher, patchname, ui, strip, cwd, files):
1257 def _externalpatch(patcher, patchname, ui, strip, cwd, files):
1252 """use <patcher> to apply <patchname> to the working directory.
1258 """use <patcher> to apply <patchname> to the working directory.
1253 returns whether patch was applied with fuzz factor."""
1259 returns whether patch was applied with fuzz factor."""
1254
1260
1255 fuzz = False
1261 fuzz = False
1256 args = []
1262 args = []
1257 if cwd:
1263 if cwd:
1258 args.append('-d %s' % util.shellquote(cwd))
1264 args.append('-d %s' % util.shellquote(cwd))
1259 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1265 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1260 util.shellquote(patchname)))
1266 util.shellquote(patchname)))
1261
1267
1262 for line in fp:
1268 for line in fp:
1263 line = line.rstrip()
1269 line = line.rstrip()
1264 ui.note(line + '\n')
1270 ui.note(line + '\n')
1265 if line.startswith('patching file '):
1271 if line.startswith('patching file '):
1266 pf = util.parsepatchoutput(line)
1272 pf = util.parsepatchoutput(line)
1267 printed_file = False
1273 printed_file = False
1268 files.setdefault(pf, None)
1274 files.setdefault(pf, None)
1269 elif line.find('with fuzz') >= 0:
1275 elif line.find('with fuzz') >= 0:
1270 fuzz = True
1276 fuzz = True
1271 if not printed_file:
1277 if not printed_file:
1272 ui.warn(pf + '\n')
1278 ui.warn(pf + '\n')
1273 printed_file = True
1279 printed_file = True
1274 ui.warn(line + '\n')
1280 ui.warn(line + '\n')
1275 elif line.find('saving rejects to file') >= 0:
1281 elif line.find('saving rejects to file') >= 0:
1276 ui.warn(line + '\n')
1282 ui.warn(line + '\n')
1277 elif line.find('FAILED') >= 0:
1283 elif line.find('FAILED') >= 0:
1278 if not printed_file:
1284 if not printed_file:
1279 ui.warn(pf + '\n')
1285 ui.warn(pf + '\n')
1280 printed_file = True
1286 printed_file = True
1281 ui.warn(line + '\n')
1287 ui.warn(line + '\n')
1282 code = fp.close()
1288 code = fp.close()
1283 if code:
1289 if code:
1284 raise PatchError(_("patch command failed: %s") %
1290 raise PatchError(_("patch command failed: %s") %
1285 util.explainexit(code)[0])
1291 util.explainexit(code)[0])
1286 return fuzz
1292 return fuzz
1287
1293
1288 def internalpatch(ui, repo, patchobj, strip, cwd, files=None, eolmode='strict',
1294 def internalpatch(ui, repo, patchobj, strip, cwd, files=None, eolmode='strict',
1289 similarity=0):
1295 similarity=0):
1290 """use builtin patch to apply <patchobj> to the working directory.
1296 """use builtin patch to apply <patchobj> to the working directory.
1291 returns whether patch was applied with fuzz factor."""
1297 returns whether patch was applied with fuzz factor."""
1292
1298
1293 if files is None:
1299 if files is None:
1294 files = {}
1300 files = {}
1295 if eolmode is None:
1301 if eolmode is None:
1296 eolmode = ui.config('patch', 'eol', 'strict')
1302 eolmode = ui.config('patch', 'eol', 'strict')
1297 if eolmode.lower() not in eolmodes:
1303 if eolmode.lower() not in eolmodes:
1298 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1304 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1299 eolmode = eolmode.lower()
1305 eolmode = eolmode.lower()
1300
1306
1301 try:
1307 try:
1302 fp = open(patchobj, 'rb')
1308 fp = open(patchobj, 'rb')
1303 except TypeError:
1309 except TypeError:
1304 fp = patchobj
1310 fp = patchobj
1305 if cwd:
1311 if cwd:
1306 curdir = os.getcwd()
1312 curdir = os.getcwd()
1307 os.chdir(cwd)
1313 os.chdir(cwd)
1308 try:
1314 try:
1309 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1315 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1310 finally:
1316 finally:
1311 if cwd:
1317 if cwd:
1312 os.chdir(curdir)
1318 os.chdir(curdir)
1313 if fp != patchobj:
1319 if fp != patchobj:
1314 fp.close()
1320 fp.close()
1315 touched = _updatedir(ui, repo, files, similarity)
1321 touched = _updatedir(ui, repo, files, similarity)
1316 files.update(dict.fromkeys(touched))
1322 files.update(dict.fromkeys(touched))
1317 if ret < 0:
1323 if ret < 0:
1318 raise PatchError(_('patch failed to apply'))
1324 raise PatchError(_('patch failed to apply'))
1319 return ret > 0
1325 return ret > 0
1320
1326
1321 def patch(ui, repo, patchname, strip=1, cwd=None, files=None, eolmode='strict',
1327 def patch(ui, repo, patchname, strip=1, cwd=None, files=None, eolmode='strict',
1322 similarity=0):
1328 similarity=0):
1323 """Apply <patchname> to the working directory.
1329 """Apply <patchname> to the working directory.
1324
1330
1325 'eolmode' specifies how end of lines should be handled. It can be:
1331 'eolmode' specifies how end of lines should be handled. It can be:
1326 - 'strict': inputs are read in binary mode, EOLs are preserved
1332 - 'strict': inputs are read in binary mode, EOLs are preserved
1327 - 'crlf': EOLs are ignored when patching and reset to CRLF
1333 - 'crlf': EOLs are ignored when patching and reset to CRLF
1328 - 'lf': EOLs are ignored when patching and reset to LF
1334 - 'lf': EOLs are ignored when patching and reset to LF
1329 - None: get it from user settings, default to 'strict'
1335 - None: get it from user settings, default to 'strict'
1330 'eolmode' is ignored when using an external patcher program.
1336 'eolmode' is ignored when using an external patcher program.
1331
1337
1332 Returns whether patch was applied with fuzz factor.
1338 Returns whether patch was applied with fuzz factor.
1333 """
1339 """
1334 patcher = ui.config('ui', 'patch')
1340 patcher = ui.config('ui', 'patch')
1335 if files is None:
1341 if files is None:
1336 files = {}
1342 files = {}
1337 try:
1343 try:
1338 if patcher:
1344 if patcher:
1339 try:
1345 try:
1340 return _externalpatch(patcher, patchname, ui, strip, cwd,
1346 return _externalpatch(patcher, patchname, ui, strip, cwd,
1341 files)
1347 files)
1342 finally:
1348 finally:
1343 touched = _updatedir(ui, repo, files, similarity)
1349 touched = _updatedir(ui, repo, files, similarity)
1344 files.update(dict.fromkeys(touched))
1350 files.update(dict.fromkeys(touched))
1345 return internalpatch(ui, repo, patchname, strip, cwd, files, eolmode,
1351 return internalpatch(ui, repo, patchname, strip, cwd, files, eolmode,
1346 similarity)
1352 similarity)
1347 except PatchError, err:
1353 except PatchError, err:
1348 raise util.Abort(str(err))
1354 raise util.Abort(str(err))
1349
1355
1350 def changedfiles(patchpath, strip=1):
1356 def changedfiles(ui, repo, patchpath, strip=1):
1357 backend = fsbackend(ui, repo.root)
1351 fp = open(patchpath, 'rb')
1358 fp = open(patchpath, 'rb')
1352 try:
1359 try:
1353 changed = set()
1360 changed = set()
1354 for state, values in iterhunks(fp):
1361 for state, values in iterhunks(fp):
1355 if state == 'hunk':
1362 if state == 'hunk':
1356 continue
1363 continue
1357 elif state == 'file':
1364 elif state == 'file':
1358 afile, bfile, first_hunk = values
1365 afile, bfile, first_hunk = values
1359 current_file, missing = selectfile(afile, bfile,
1366 current_file, missing = selectfile(backend, afile, bfile,
1360 first_hunk, strip)
1367 first_hunk, strip)
1361 changed.add(current_file)
1368 changed.add(current_file)
1362 elif state == 'git':
1369 elif state == 'git':
1363 for gp in values:
1370 for gp in values:
1364 gp.path = pathstrip(gp.path, strip - 1)[1]
1371 gp.path = pathstrip(gp.path, strip - 1)[1]
1365 changed.add(gp.path)
1372 changed.add(gp.path)
1366 if gp.oldpath:
1373 if gp.oldpath:
1367 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1374 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1368 if gp.op == 'RENAME':
1375 if gp.op == 'RENAME':
1369 changed.add(gp.oldpath)
1376 changed.add(gp.oldpath)
1370 else:
1377 else:
1371 raise util.Abort(_('unsupported parser state: %s') % state)
1378 raise util.Abort(_('unsupported parser state: %s') % state)
1372 return changed
1379 return changed
1373 finally:
1380 finally:
1374 fp.close()
1381 fp.close()
1375
1382
1376 def b85diff(to, tn):
1383 def b85diff(to, tn):
1377 '''print base85-encoded binary diff'''
1384 '''print base85-encoded binary diff'''
1378 def gitindex(text):
1385 def gitindex(text):
1379 if not text:
1386 if not text:
1380 return hex(nullid)
1387 return hex(nullid)
1381 l = len(text)
1388 l = len(text)
1382 s = util.sha1('blob %d\0' % l)
1389 s = util.sha1('blob %d\0' % l)
1383 s.update(text)
1390 s.update(text)
1384 return s.hexdigest()
1391 return s.hexdigest()
1385
1392
1386 def fmtline(line):
1393 def fmtline(line):
1387 l = len(line)
1394 l = len(line)
1388 if l <= 26:
1395 if l <= 26:
1389 l = chr(ord('A') + l - 1)
1396 l = chr(ord('A') + l - 1)
1390 else:
1397 else:
1391 l = chr(l - 26 + ord('a') - 1)
1398 l = chr(l - 26 + ord('a') - 1)
1392 return '%c%s\n' % (l, base85.b85encode(line, True))
1399 return '%c%s\n' % (l, base85.b85encode(line, True))
1393
1400
1394 def chunk(text, csize=52):
1401 def chunk(text, csize=52):
1395 l = len(text)
1402 l = len(text)
1396 i = 0
1403 i = 0
1397 while i < l:
1404 while i < l:
1398 yield text[i:i + csize]
1405 yield text[i:i + csize]
1399 i += csize
1406 i += csize
1400
1407
1401 tohash = gitindex(to)
1408 tohash = gitindex(to)
1402 tnhash = gitindex(tn)
1409 tnhash = gitindex(tn)
1403 if tohash == tnhash:
1410 if tohash == tnhash:
1404 return ""
1411 return ""
1405
1412
1406 # TODO: deltas
1413 # TODO: deltas
1407 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1414 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1408 (tohash, tnhash, len(tn))]
1415 (tohash, tnhash, len(tn))]
1409 for l in chunk(zlib.compress(tn)):
1416 for l in chunk(zlib.compress(tn)):
1410 ret.append(fmtline(l))
1417 ret.append(fmtline(l))
1411 ret.append('\n')
1418 ret.append('\n')
1412 return ''.join(ret)
1419 return ''.join(ret)
1413
1420
1414 class GitDiffRequired(Exception):
1421 class GitDiffRequired(Exception):
1415 pass
1422 pass
1416
1423
1417 def diffopts(ui, opts=None, untrusted=False):
1424 def diffopts(ui, opts=None, untrusted=False):
1418 def get(key, name=None, getter=ui.configbool):
1425 def get(key, name=None, getter=ui.configbool):
1419 return ((opts and opts.get(key)) or
1426 return ((opts and opts.get(key)) or
1420 getter('diff', name or key, None, untrusted=untrusted))
1427 getter('diff', name or key, None, untrusted=untrusted))
1421 return mdiff.diffopts(
1428 return mdiff.diffopts(
1422 text=opts and opts.get('text'),
1429 text=opts and opts.get('text'),
1423 git=get('git'),
1430 git=get('git'),
1424 nodates=get('nodates'),
1431 nodates=get('nodates'),
1425 showfunc=get('show_function', 'showfunc'),
1432 showfunc=get('show_function', 'showfunc'),
1426 ignorews=get('ignore_all_space', 'ignorews'),
1433 ignorews=get('ignore_all_space', 'ignorews'),
1427 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1434 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1428 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1435 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1429 context=get('unified', getter=ui.config))
1436 context=get('unified', getter=ui.config))
1430
1437
1431 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1438 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1432 losedatafn=None, prefix=''):
1439 losedatafn=None, prefix=''):
1433 '''yields diff of changes to files between two nodes, or node and
1440 '''yields diff of changes to files between two nodes, or node and
1434 working directory.
1441 working directory.
1435
1442
1436 if node1 is None, use first dirstate parent instead.
1443 if node1 is None, use first dirstate parent instead.
1437 if node2 is None, compare node1 with working directory.
1444 if node2 is None, compare node1 with working directory.
1438
1445
1439 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1446 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1440 every time some change cannot be represented with the current
1447 every time some change cannot be represented with the current
1441 patch format. Return False to upgrade to git patch format, True to
1448 patch format. Return False to upgrade to git patch format, True to
1442 accept the loss or raise an exception to abort the diff. It is
1449 accept the loss or raise an exception to abort the diff. It is
1443 called with the name of current file being diffed as 'fn'. If set
1450 called with the name of current file being diffed as 'fn'. If set
1444 to None, patches will always be upgraded to git format when
1451 to None, patches will always be upgraded to git format when
1445 necessary.
1452 necessary.
1446
1453
1447 prefix is a filename prefix that is prepended to all filenames on
1454 prefix is a filename prefix that is prepended to all filenames on
1448 display (used for subrepos).
1455 display (used for subrepos).
1449 '''
1456 '''
1450
1457
1451 if opts is None:
1458 if opts is None:
1452 opts = mdiff.defaultopts
1459 opts = mdiff.defaultopts
1453
1460
1454 if not node1 and not node2:
1461 if not node1 and not node2:
1455 node1 = repo.dirstate.p1()
1462 node1 = repo.dirstate.p1()
1456
1463
1457 def lrugetfilectx():
1464 def lrugetfilectx():
1458 cache = {}
1465 cache = {}
1459 order = []
1466 order = []
1460 def getfilectx(f, ctx):
1467 def getfilectx(f, ctx):
1461 fctx = ctx.filectx(f, filelog=cache.get(f))
1468 fctx = ctx.filectx(f, filelog=cache.get(f))
1462 if f not in cache:
1469 if f not in cache:
1463 if len(cache) > 20:
1470 if len(cache) > 20:
1464 del cache[order.pop(0)]
1471 del cache[order.pop(0)]
1465 cache[f] = fctx.filelog()
1472 cache[f] = fctx.filelog()
1466 else:
1473 else:
1467 order.remove(f)
1474 order.remove(f)
1468 order.append(f)
1475 order.append(f)
1469 return fctx
1476 return fctx
1470 return getfilectx
1477 return getfilectx
1471 getfilectx = lrugetfilectx()
1478 getfilectx = lrugetfilectx()
1472
1479
1473 ctx1 = repo[node1]
1480 ctx1 = repo[node1]
1474 ctx2 = repo[node2]
1481 ctx2 = repo[node2]
1475
1482
1476 if not changes:
1483 if not changes:
1477 changes = repo.status(ctx1, ctx2, match=match)
1484 changes = repo.status(ctx1, ctx2, match=match)
1478 modified, added, removed = changes[:3]
1485 modified, added, removed = changes[:3]
1479
1486
1480 if not modified and not added and not removed:
1487 if not modified and not added and not removed:
1481 return []
1488 return []
1482
1489
1483 revs = None
1490 revs = None
1484 if not repo.ui.quiet:
1491 if not repo.ui.quiet:
1485 hexfunc = repo.ui.debugflag and hex or short
1492 hexfunc = repo.ui.debugflag and hex or short
1486 revs = [hexfunc(node) for node in [node1, node2] if node]
1493 revs = [hexfunc(node) for node in [node1, node2] if node]
1487
1494
1488 copy = {}
1495 copy = {}
1489 if opts.git or opts.upgrade:
1496 if opts.git or opts.upgrade:
1490 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1497 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1491
1498
1492 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1499 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1493 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1500 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1494 if opts.upgrade and not opts.git:
1501 if opts.upgrade and not opts.git:
1495 try:
1502 try:
1496 def losedata(fn):
1503 def losedata(fn):
1497 if not losedatafn or not losedatafn(fn=fn):
1504 if not losedatafn or not losedatafn(fn=fn):
1498 raise GitDiffRequired()
1505 raise GitDiffRequired()
1499 # Buffer the whole output until we are sure it can be generated
1506 # Buffer the whole output until we are sure it can be generated
1500 return list(difffn(opts.copy(git=False), losedata))
1507 return list(difffn(opts.copy(git=False), losedata))
1501 except GitDiffRequired:
1508 except GitDiffRequired:
1502 return difffn(opts.copy(git=True), None)
1509 return difffn(opts.copy(git=True), None)
1503 else:
1510 else:
1504 return difffn(opts, None)
1511 return difffn(opts, None)
1505
1512
1506 def difflabel(func, *args, **kw):
1513 def difflabel(func, *args, **kw):
1507 '''yields 2-tuples of (output, label) based on the output of func()'''
1514 '''yields 2-tuples of (output, label) based on the output of func()'''
1508 prefixes = [('diff', 'diff.diffline'),
1515 prefixes = [('diff', 'diff.diffline'),
1509 ('copy', 'diff.extended'),
1516 ('copy', 'diff.extended'),
1510 ('rename', 'diff.extended'),
1517 ('rename', 'diff.extended'),
1511 ('old', 'diff.extended'),
1518 ('old', 'diff.extended'),
1512 ('new', 'diff.extended'),
1519 ('new', 'diff.extended'),
1513 ('deleted', 'diff.extended'),
1520 ('deleted', 'diff.extended'),
1514 ('---', 'diff.file_a'),
1521 ('---', 'diff.file_a'),
1515 ('+++', 'diff.file_b'),
1522 ('+++', 'diff.file_b'),
1516 ('@@', 'diff.hunk'),
1523 ('@@', 'diff.hunk'),
1517 ('-', 'diff.deleted'),
1524 ('-', 'diff.deleted'),
1518 ('+', 'diff.inserted')]
1525 ('+', 'diff.inserted')]
1519
1526
1520 for chunk in func(*args, **kw):
1527 for chunk in func(*args, **kw):
1521 lines = chunk.split('\n')
1528 lines = chunk.split('\n')
1522 for i, line in enumerate(lines):
1529 for i, line in enumerate(lines):
1523 if i != 0:
1530 if i != 0:
1524 yield ('\n', '')
1531 yield ('\n', '')
1525 stripline = line
1532 stripline = line
1526 if line and line[0] in '+-':
1533 if line and line[0] in '+-':
1527 # highlight trailing whitespace, but only in changed lines
1534 # highlight trailing whitespace, but only in changed lines
1528 stripline = line.rstrip()
1535 stripline = line.rstrip()
1529 for prefix, label in prefixes:
1536 for prefix, label in prefixes:
1530 if stripline.startswith(prefix):
1537 if stripline.startswith(prefix):
1531 yield (stripline, label)
1538 yield (stripline, label)
1532 break
1539 break
1533 else:
1540 else:
1534 yield (line, '')
1541 yield (line, '')
1535 if line != stripline:
1542 if line != stripline:
1536 yield (line[len(stripline):], 'diff.trailingwhitespace')
1543 yield (line[len(stripline):], 'diff.trailingwhitespace')
1537
1544
1538 def diffui(*args, **kw):
1545 def diffui(*args, **kw):
1539 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1546 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1540 return difflabel(diff, *args, **kw)
1547 return difflabel(diff, *args, **kw)
1541
1548
1542
1549
1543 def _addmodehdr(header, omode, nmode):
1550 def _addmodehdr(header, omode, nmode):
1544 if omode != nmode:
1551 if omode != nmode:
1545 header.append('old mode %s\n' % omode)
1552 header.append('old mode %s\n' % omode)
1546 header.append('new mode %s\n' % nmode)
1553 header.append('new mode %s\n' % nmode)
1547
1554
1548 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1555 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1549 copy, getfilectx, opts, losedatafn, prefix):
1556 copy, getfilectx, opts, losedatafn, prefix):
1550
1557
1551 def join(f):
1558 def join(f):
1552 return os.path.join(prefix, f)
1559 return os.path.join(prefix, f)
1553
1560
1554 date1 = util.datestr(ctx1.date())
1561 date1 = util.datestr(ctx1.date())
1555 man1 = ctx1.manifest()
1562 man1 = ctx1.manifest()
1556
1563
1557 gone = set()
1564 gone = set()
1558 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1565 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1559
1566
1560 copyto = dict([(v, k) for k, v in copy.items()])
1567 copyto = dict([(v, k) for k, v in copy.items()])
1561
1568
1562 if opts.git:
1569 if opts.git:
1563 revs = None
1570 revs = None
1564
1571
1565 for f in sorted(modified + added + removed):
1572 for f in sorted(modified + added + removed):
1566 to = None
1573 to = None
1567 tn = None
1574 tn = None
1568 dodiff = True
1575 dodiff = True
1569 header = []
1576 header = []
1570 if f in man1:
1577 if f in man1:
1571 to = getfilectx(f, ctx1).data()
1578 to = getfilectx(f, ctx1).data()
1572 if f not in removed:
1579 if f not in removed:
1573 tn = getfilectx(f, ctx2).data()
1580 tn = getfilectx(f, ctx2).data()
1574 a, b = f, f
1581 a, b = f, f
1575 if opts.git or losedatafn:
1582 if opts.git or losedatafn:
1576 if f in added:
1583 if f in added:
1577 mode = gitmode[ctx2.flags(f)]
1584 mode = gitmode[ctx2.flags(f)]
1578 if f in copy or f in copyto:
1585 if f in copy or f in copyto:
1579 if opts.git:
1586 if opts.git:
1580 if f in copy:
1587 if f in copy:
1581 a = copy[f]
1588 a = copy[f]
1582 else:
1589 else:
1583 a = copyto[f]
1590 a = copyto[f]
1584 omode = gitmode[man1.flags(a)]
1591 omode = gitmode[man1.flags(a)]
1585 _addmodehdr(header, omode, mode)
1592 _addmodehdr(header, omode, mode)
1586 if a in removed and a not in gone:
1593 if a in removed and a not in gone:
1587 op = 'rename'
1594 op = 'rename'
1588 gone.add(a)
1595 gone.add(a)
1589 else:
1596 else:
1590 op = 'copy'
1597 op = 'copy'
1591 header.append('%s from %s\n' % (op, join(a)))
1598 header.append('%s from %s\n' % (op, join(a)))
1592 header.append('%s to %s\n' % (op, join(f)))
1599 header.append('%s to %s\n' % (op, join(f)))
1593 to = getfilectx(a, ctx1).data()
1600 to = getfilectx(a, ctx1).data()
1594 else:
1601 else:
1595 losedatafn(f)
1602 losedatafn(f)
1596 else:
1603 else:
1597 if opts.git:
1604 if opts.git:
1598 header.append('new file mode %s\n' % mode)
1605 header.append('new file mode %s\n' % mode)
1599 elif ctx2.flags(f):
1606 elif ctx2.flags(f):
1600 losedatafn(f)
1607 losedatafn(f)
1601 # In theory, if tn was copied or renamed we should check
1608 # In theory, if tn was copied or renamed we should check
1602 # if the source is binary too but the copy record already
1609 # if the source is binary too but the copy record already
1603 # forces git mode.
1610 # forces git mode.
1604 if util.binary(tn):
1611 if util.binary(tn):
1605 if opts.git:
1612 if opts.git:
1606 dodiff = 'binary'
1613 dodiff = 'binary'
1607 else:
1614 else:
1608 losedatafn(f)
1615 losedatafn(f)
1609 if not opts.git and not tn:
1616 if not opts.git and not tn:
1610 # regular diffs cannot represent new empty file
1617 # regular diffs cannot represent new empty file
1611 losedatafn(f)
1618 losedatafn(f)
1612 elif f in removed:
1619 elif f in removed:
1613 if opts.git:
1620 if opts.git:
1614 # have we already reported a copy above?
1621 # have we already reported a copy above?
1615 if ((f in copy and copy[f] in added
1622 if ((f in copy and copy[f] in added
1616 and copyto[copy[f]] == f) or
1623 and copyto[copy[f]] == f) or
1617 (f in copyto and copyto[f] in added
1624 (f in copyto and copyto[f] in added
1618 and copy[copyto[f]] == f)):
1625 and copy[copyto[f]] == f)):
1619 dodiff = False
1626 dodiff = False
1620 else:
1627 else:
1621 header.append('deleted file mode %s\n' %
1628 header.append('deleted file mode %s\n' %
1622 gitmode[man1.flags(f)])
1629 gitmode[man1.flags(f)])
1623 elif not to or util.binary(to):
1630 elif not to or util.binary(to):
1624 # regular diffs cannot represent empty file deletion
1631 # regular diffs cannot represent empty file deletion
1625 losedatafn(f)
1632 losedatafn(f)
1626 else:
1633 else:
1627 oflag = man1.flags(f)
1634 oflag = man1.flags(f)
1628 nflag = ctx2.flags(f)
1635 nflag = ctx2.flags(f)
1629 binary = util.binary(to) or util.binary(tn)
1636 binary = util.binary(to) or util.binary(tn)
1630 if opts.git:
1637 if opts.git:
1631 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1638 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1632 if binary:
1639 if binary:
1633 dodiff = 'binary'
1640 dodiff = 'binary'
1634 elif binary or nflag != oflag:
1641 elif binary or nflag != oflag:
1635 losedatafn(f)
1642 losedatafn(f)
1636 if opts.git:
1643 if opts.git:
1637 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1644 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1638
1645
1639 if dodiff:
1646 if dodiff:
1640 if dodiff == 'binary':
1647 if dodiff == 'binary':
1641 text = b85diff(to, tn)
1648 text = b85diff(to, tn)
1642 else:
1649 else:
1643 text = mdiff.unidiff(to, date1,
1650 text = mdiff.unidiff(to, date1,
1644 # ctx2 date may be dynamic
1651 # ctx2 date may be dynamic
1645 tn, util.datestr(ctx2.date()),
1652 tn, util.datestr(ctx2.date()),
1646 join(a), join(b), revs, opts=opts)
1653 join(a), join(b), revs, opts=opts)
1647 if header and (text or len(header) > 1):
1654 if header and (text or len(header) > 1):
1648 yield ''.join(header)
1655 yield ''.join(header)
1649 if text:
1656 if text:
1650 yield text
1657 yield text
1651
1658
1652 def diffstatdata(lines):
1659 def diffstatdata(lines):
1653 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1660 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1654
1661
1655 filename, adds, removes = None, 0, 0
1662 filename, adds, removes = None, 0, 0
1656 for line in lines:
1663 for line in lines:
1657 if line.startswith('diff'):
1664 if line.startswith('diff'):
1658 if filename:
1665 if filename:
1659 isbinary = adds == 0 and removes == 0
1666 isbinary = adds == 0 and removes == 0
1660 yield (filename, adds, removes, isbinary)
1667 yield (filename, adds, removes, isbinary)
1661 # set numbers to 0 anyway when starting new file
1668 # set numbers to 0 anyway when starting new file
1662 adds, removes = 0, 0
1669 adds, removes = 0, 0
1663 if line.startswith('diff --git'):
1670 if line.startswith('diff --git'):
1664 filename = gitre.search(line).group(1)
1671 filename = gitre.search(line).group(1)
1665 elif line.startswith('diff -r'):
1672 elif line.startswith('diff -r'):
1666 # format: "diff -r ... -r ... filename"
1673 # format: "diff -r ... -r ... filename"
1667 filename = diffre.search(line).group(1)
1674 filename = diffre.search(line).group(1)
1668 elif line.startswith('+') and not line.startswith('+++'):
1675 elif line.startswith('+') and not line.startswith('+++'):
1669 adds += 1
1676 adds += 1
1670 elif line.startswith('-') and not line.startswith('---'):
1677 elif line.startswith('-') and not line.startswith('---'):
1671 removes += 1
1678 removes += 1
1672 if filename:
1679 if filename:
1673 isbinary = adds == 0 and removes == 0
1680 isbinary = adds == 0 and removes == 0
1674 yield (filename, adds, removes, isbinary)
1681 yield (filename, adds, removes, isbinary)
1675
1682
1676 def diffstat(lines, width=80, git=False):
1683 def diffstat(lines, width=80, git=False):
1677 output = []
1684 output = []
1678 stats = list(diffstatdata(lines))
1685 stats = list(diffstatdata(lines))
1679
1686
1680 maxtotal, maxname = 0, 0
1687 maxtotal, maxname = 0, 0
1681 totaladds, totalremoves = 0, 0
1688 totaladds, totalremoves = 0, 0
1682 hasbinary = False
1689 hasbinary = False
1683
1690
1684 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1691 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1685 for filename, adds, removes, isbinary in stats]
1692 for filename, adds, removes, isbinary in stats]
1686
1693
1687 for filename, adds, removes, isbinary, namewidth in sized:
1694 for filename, adds, removes, isbinary, namewidth in sized:
1688 totaladds += adds
1695 totaladds += adds
1689 totalremoves += removes
1696 totalremoves += removes
1690 maxname = max(maxname, namewidth)
1697 maxname = max(maxname, namewidth)
1691 maxtotal = max(maxtotal, adds + removes)
1698 maxtotal = max(maxtotal, adds + removes)
1692 if isbinary:
1699 if isbinary:
1693 hasbinary = True
1700 hasbinary = True
1694
1701
1695 countwidth = len(str(maxtotal))
1702 countwidth = len(str(maxtotal))
1696 if hasbinary and countwidth < 3:
1703 if hasbinary and countwidth < 3:
1697 countwidth = 3
1704 countwidth = 3
1698 graphwidth = width - countwidth - maxname - 6
1705 graphwidth = width - countwidth - maxname - 6
1699 if graphwidth < 10:
1706 if graphwidth < 10:
1700 graphwidth = 10
1707 graphwidth = 10
1701
1708
1702 def scale(i):
1709 def scale(i):
1703 if maxtotal <= graphwidth:
1710 if maxtotal <= graphwidth:
1704 return i
1711 return i
1705 # If diffstat runs out of room it doesn't print anything,
1712 # If diffstat runs out of room it doesn't print anything,
1706 # which isn't very useful, so always print at least one + or -
1713 # which isn't very useful, so always print at least one + or -
1707 # if there were at least some changes.
1714 # if there were at least some changes.
1708 return max(i * graphwidth // maxtotal, int(bool(i)))
1715 return max(i * graphwidth // maxtotal, int(bool(i)))
1709
1716
1710 for filename, adds, removes, isbinary, namewidth in sized:
1717 for filename, adds, removes, isbinary, namewidth in sized:
1711 if git and isbinary:
1718 if git and isbinary:
1712 count = 'Bin'
1719 count = 'Bin'
1713 else:
1720 else:
1714 count = adds + removes
1721 count = adds + removes
1715 pluses = '+' * scale(adds)
1722 pluses = '+' * scale(adds)
1716 minuses = '-' * scale(removes)
1723 minuses = '-' * scale(removes)
1717 output.append(' %s%s | %*s %s%s\n' %
1724 output.append(' %s%s | %*s %s%s\n' %
1718 (filename, ' ' * (maxname - namewidth),
1725 (filename, ' ' * (maxname - namewidth),
1719 countwidth, count,
1726 countwidth, count,
1720 pluses, minuses))
1727 pluses, minuses))
1721
1728
1722 if stats:
1729 if stats:
1723 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1730 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1724 % (len(stats), totaladds, totalremoves))
1731 % (len(stats), totaladds, totalremoves))
1725
1732
1726 return ''.join(output)
1733 return ''.join(output)
1727
1734
1728 def diffstatui(*args, **kw):
1735 def diffstatui(*args, **kw):
1729 '''like diffstat(), but yields 2-tuples of (output, label) for
1736 '''like diffstat(), but yields 2-tuples of (output, label) for
1730 ui.write()
1737 ui.write()
1731 '''
1738 '''
1732
1739
1733 for line in diffstat(*args, **kw).splitlines():
1740 for line in diffstat(*args, **kw).splitlines():
1734 if line and line[-1] in '+-':
1741 if line and line[-1] in '+-':
1735 name, graph = line.rsplit(' ', 1)
1742 name, graph = line.rsplit(' ', 1)
1736 yield (name + ' ', '')
1743 yield (name + ' ', '')
1737 m = re.search(r'\++', graph)
1744 m = re.search(r'\++', graph)
1738 if m:
1745 if m:
1739 yield (m.group(0), 'diffstat.inserted')
1746 yield (m.group(0), 'diffstat.inserted')
1740 m = re.search(r'-+', graph)
1747 m = re.search(r'-+', graph)
1741 if m:
1748 if m:
1742 yield (m.group(0), 'diffstat.deleted')
1749 yield (m.group(0), 'diffstat.deleted')
1743 else:
1750 else:
1744 yield (line, '')
1751 yield (line, '')
1745 yield ('\n', '')
1752 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now