##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r10763:68a7b9ed merge default
parent child Browse files
Show More
@@ -1,2793 +1,2793 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details)::
17 Common tasks (use "hg help command" for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40 '''
40 '''
41
41
42 from mercurial.i18n import _
42 from mercurial.i18n import _
43 from mercurial.node import bin, hex, short, nullid, nullrev
43 from mercurial.node import bin, hex, short, nullid, nullrev
44 from mercurial.lock import release
44 from mercurial.lock import release
45 from mercurial import commands, cmdutil, hg, patch, util
45 from mercurial import commands, cmdutil, hg, patch, util
46 from mercurial import repair, extensions, url, error
46 from mercurial import repair, extensions, url, error
47 import os, sys, re, errno
47 import os, sys, re, errno
48
48
49 commands.norepo += " qclone"
49 commands.norepo += " qclone"
50
50
51 # Patch names looks like unix-file names.
51 # Patch names looks like unix-file names.
52 # They must be joinable with queue directory and result in the patch path.
52 # They must be joinable with queue directory and result in the patch path.
53 normname = util.normpath
53 normname = util.normpath
54
54
55 class statusentry(object):
55 class statusentry(object):
56 def __init__(self, node, name):
56 def __init__(self, node, name):
57 self.node, self.name = node, name
57 self.node, self.name = node, name
58
58
59 def __str__(self):
59 def __str__(self):
60 return hex(self.node) + ':' + self.name
60 return hex(self.node) + ':' + self.name
61
61
62 class patchheader(object):
62 class patchheader(object):
63 def __init__(self, pf, plainmode=False):
63 def __init__(self, pf, plainmode=False):
64 def eatdiff(lines):
64 def eatdiff(lines):
65 while lines:
65 while lines:
66 l = lines[-1]
66 l = lines[-1]
67 if (l.startswith("diff -") or
67 if (l.startswith("diff -") or
68 l.startswith("Index:") or
68 l.startswith("Index:") or
69 l.startswith("===========")):
69 l.startswith("===========")):
70 del lines[-1]
70 del lines[-1]
71 else:
71 else:
72 break
72 break
73 def eatempty(lines):
73 def eatempty(lines):
74 while lines:
74 while lines:
75 if not lines[-1].strip():
75 if not lines[-1].strip():
76 del lines[-1]
76 del lines[-1]
77 else:
77 else:
78 break
78 break
79
79
80 message = []
80 message = []
81 comments = []
81 comments = []
82 user = None
82 user = None
83 date = None
83 date = None
84 parent = None
84 parent = None
85 format = None
85 format = None
86 subject = None
86 subject = None
87 diffstart = 0
87 diffstart = 0
88
88
89 for line in file(pf):
89 for line in file(pf):
90 line = line.rstrip()
90 line = line.rstrip()
91 if (line.startswith('diff --git')
91 if (line.startswith('diff --git')
92 or (diffstart and line.startswith('+++ '))):
92 or (diffstart and line.startswith('+++ '))):
93 diffstart = 2
93 diffstart = 2
94 break
94 break
95 diffstart = 0 # reset
95 diffstart = 0 # reset
96 if line.startswith("--- "):
96 if line.startswith("--- "):
97 diffstart = 1
97 diffstart = 1
98 continue
98 continue
99 elif format == "hgpatch":
99 elif format == "hgpatch":
100 # parse values when importing the result of an hg export
100 # parse values when importing the result of an hg export
101 if line.startswith("# User "):
101 if line.startswith("# User "):
102 user = line[7:]
102 user = line[7:]
103 elif line.startswith("# Date "):
103 elif line.startswith("# Date "):
104 date = line[7:]
104 date = line[7:]
105 elif line.startswith("# Parent "):
105 elif line.startswith("# Parent "):
106 parent = line[9:]
106 parent = line[9:]
107 elif not line.startswith("# ") and line:
107 elif not line.startswith("# ") and line:
108 message.append(line)
108 message.append(line)
109 format = None
109 format = None
110 elif line == '# HG changeset patch':
110 elif line == '# HG changeset patch':
111 message = []
111 message = []
112 format = "hgpatch"
112 format = "hgpatch"
113 elif (format != "tagdone" and (line.startswith("Subject: ") or
113 elif (format != "tagdone" and (line.startswith("Subject: ") or
114 line.startswith("subject: "))):
114 line.startswith("subject: "))):
115 subject = line[9:]
115 subject = line[9:]
116 format = "tag"
116 format = "tag"
117 elif (format != "tagdone" and (line.startswith("From: ") or
117 elif (format != "tagdone" and (line.startswith("From: ") or
118 line.startswith("from: "))):
118 line.startswith("from: "))):
119 user = line[6:]
119 user = line[6:]
120 format = "tag"
120 format = "tag"
121 elif (format != "tagdone" and (line.startswith("Date: ") or
121 elif (format != "tagdone" and (line.startswith("Date: ") or
122 line.startswith("date: "))):
122 line.startswith("date: "))):
123 date = line[6:]
123 date = line[6:]
124 format = "tag"
124 format = "tag"
125 elif format == "tag" and line == "":
125 elif format == "tag" and line == "":
126 # when looking for tags (subject: from: etc) they
126 # when looking for tags (subject: from: etc) they
127 # end once you find a blank line in the source
127 # end once you find a blank line in the source
128 format = "tagdone"
128 format = "tagdone"
129 elif message or line:
129 elif message or line:
130 message.append(line)
130 message.append(line)
131 comments.append(line)
131 comments.append(line)
132
132
133 eatdiff(message)
133 eatdiff(message)
134 eatdiff(comments)
134 eatdiff(comments)
135 eatempty(message)
135 eatempty(message)
136 eatempty(comments)
136 eatempty(comments)
137
137
138 # make sure message isn't empty
138 # make sure message isn't empty
139 if format and format.startswith("tag") and subject:
139 if format and format.startswith("tag") and subject:
140 message.insert(0, "")
140 message.insert(0, "")
141 message.insert(0, subject)
141 message.insert(0, subject)
142
142
143 self.message = message
143 self.message = message
144 self.comments = comments
144 self.comments = comments
145 self.user = user
145 self.user = user
146 self.date = date
146 self.date = date
147 self.parent = parent
147 self.parent = parent
148 self.haspatch = diffstart > 1
148 self.haspatch = diffstart > 1
149 self.plainmode = plainmode
149 self.plainmode = plainmode
150
150
151 def setuser(self, user):
151 def setuser(self, user):
152 if not self.updateheader(['From: ', '# User '], user):
152 if not self.updateheader(['From: ', '# User '], user):
153 try:
153 try:
154 patchheaderat = self.comments.index('# HG changeset patch')
154 patchheaderat = self.comments.index('# HG changeset patch')
155 self.comments.insert(patchheaderat + 1, '# User ' + user)
155 self.comments.insert(patchheaderat + 1, '# User ' + user)
156 except ValueError:
156 except ValueError:
157 if self.plainmode or self._hasheader(['Date: ']):
157 if self.plainmode or self._hasheader(['Date: ']):
158 self.comments = ['From: ' + user] + self.comments
158 self.comments = ['From: ' + user] + self.comments
159 else:
159 else:
160 tmp = ['# HG changeset patch', '# User ' + user, '']
160 tmp = ['# HG changeset patch', '# User ' + user, '']
161 self.comments = tmp + self.comments
161 self.comments = tmp + self.comments
162 self.user = user
162 self.user = user
163
163
164 def setdate(self, date):
164 def setdate(self, date):
165 if not self.updateheader(['Date: ', '# Date '], date):
165 if not self.updateheader(['Date: ', '# Date '], date):
166 try:
166 try:
167 patchheaderat = self.comments.index('# HG changeset patch')
167 patchheaderat = self.comments.index('# HG changeset patch')
168 self.comments.insert(patchheaderat + 1, '# Date ' + date)
168 self.comments.insert(patchheaderat + 1, '# Date ' + date)
169 except ValueError:
169 except ValueError:
170 if self.plainmode or self._hasheader(['From: ']):
170 if self.plainmode or self._hasheader(['From: ']):
171 self.comments = ['Date: ' + date] + self.comments
171 self.comments = ['Date: ' + date] + self.comments
172 else:
172 else:
173 tmp = ['# HG changeset patch', '# Date ' + date, '']
173 tmp = ['# HG changeset patch', '# Date ' + date, '']
174 self.comments = tmp + self.comments
174 self.comments = tmp + self.comments
175 self.date = date
175 self.date = date
176
176
177 def setparent(self, parent):
177 def setparent(self, parent):
178 if not self.updateheader(['# Parent '], parent):
178 if not self.updateheader(['# Parent '], parent):
179 try:
179 try:
180 patchheaderat = self.comments.index('# HG changeset patch')
180 patchheaderat = self.comments.index('# HG changeset patch')
181 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
181 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
182 except ValueError:
182 except ValueError:
183 pass
183 pass
184 self.parent = parent
184 self.parent = parent
185
185
186 def setmessage(self, message):
186 def setmessage(self, message):
187 if self.comments:
187 if self.comments:
188 self._delmsg()
188 self._delmsg()
189 self.message = [message]
189 self.message = [message]
190 self.comments += self.message
190 self.comments += self.message
191
191
192 def updateheader(self, prefixes, new):
192 def updateheader(self, prefixes, new):
193 '''Update all references to a field in the patch header.
193 '''Update all references to a field in the patch header.
194 Return whether the field is present.'''
194 Return whether the field is present.'''
195 res = False
195 res = False
196 for prefix in prefixes:
196 for prefix in prefixes:
197 for i in xrange(len(self.comments)):
197 for i in xrange(len(self.comments)):
198 if self.comments[i].startswith(prefix):
198 if self.comments[i].startswith(prefix):
199 self.comments[i] = prefix + new
199 self.comments[i] = prefix + new
200 res = True
200 res = True
201 break
201 break
202 return res
202 return res
203
203
204 def _hasheader(self, prefixes):
204 def _hasheader(self, prefixes):
205 '''Check if a header starts with any of the given prefixes.'''
205 '''Check if a header starts with any of the given prefixes.'''
206 for prefix in prefixes:
206 for prefix in prefixes:
207 for comment in self.comments:
207 for comment in self.comments:
208 if comment.startswith(prefix):
208 if comment.startswith(prefix):
209 return True
209 return True
210 return False
210 return False
211
211
212 def __str__(self):
212 def __str__(self):
213 if not self.comments:
213 if not self.comments:
214 return ''
214 return ''
215 return '\n'.join(self.comments) + '\n\n'
215 return '\n'.join(self.comments) + '\n\n'
216
216
217 def _delmsg(self):
217 def _delmsg(self):
218 '''Remove existing message, keeping the rest of the comments fields.
218 '''Remove existing message, keeping the rest of the comments fields.
219 If comments contains 'subject: ', message will prepend
219 If comments contains 'subject: ', message will prepend
220 the field and a blank line.'''
220 the field and a blank line.'''
221 if self.message:
221 if self.message:
222 subj = 'subject: ' + self.message[0].lower()
222 subj = 'subject: ' + self.message[0].lower()
223 for i in xrange(len(self.comments)):
223 for i in xrange(len(self.comments)):
224 if subj == self.comments[i].lower():
224 if subj == self.comments[i].lower():
225 del self.comments[i]
225 del self.comments[i]
226 self.message = self.message[2:]
226 self.message = self.message[2:]
227 break
227 break
228 ci = 0
228 ci = 0
229 for mi in self.message:
229 for mi in self.message:
230 while mi != self.comments[ci]:
230 while mi != self.comments[ci]:
231 ci += 1
231 ci += 1
232 del self.comments[ci]
232 del self.comments[ci]
233
233
234 class queue(object):
234 class queue(object):
235 def __init__(self, ui, path, patchdir=None):
235 def __init__(self, ui, path, patchdir=None):
236 self.basepath = path
236 self.basepath = path
237 self.path = patchdir or os.path.join(path, "patches")
237 self.path = patchdir or os.path.join(path, "patches")
238 self.opener = util.opener(self.path)
238 self.opener = util.opener(self.path)
239 self.ui = ui
239 self.ui = ui
240 self.applied_dirty = 0
240 self.applied_dirty = 0
241 self.series_dirty = 0
241 self.series_dirty = 0
242 self.series_path = "series"
242 self.series_path = "series"
243 self.status_path = "status"
243 self.status_path = "status"
244 self.guards_path = "guards"
244 self.guards_path = "guards"
245 self.active_guards = None
245 self.active_guards = None
246 self.guards_dirty = False
246 self.guards_dirty = False
247 # Handle mq.git as a bool with extended values
247 # Handle mq.git as a bool with extended values
248 try:
248 try:
249 gitmode = ui.configbool('mq', 'git', None)
249 gitmode = ui.configbool('mq', 'git', None)
250 if gitmode is None:
250 if gitmode is None:
251 raise error.ConfigError()
251 raise error.ConfigError()
252 self.gitmode = gitmode and 'yes' or 'no'
252 self.gitmode = gitmode and 'yes' or 'no'
253 except error.ConfigError:
253 except error.ConfigError:
254 self.gitmode = ui.config('mq', 'git', 'auto').lower()
254 self.gitmode = ui.config('mq', 'git', 'auto').lower()
255 self.plainmode = ui.configbool('mq', 'plain', False)
255 self.plainmode = ui.configbool('mq', 'plain', False)
256
256
257 @util.propertycache
257 @util.propertycache
258 def applied(self):
258 def applied(self):
259 if os.path.exists(self.join(self.status_path)):
259 if os.path.exists(self.join(self.status_path)):
260 def parse(l):
260 def parse(l):
261 n, name = l.split(':', 1)
261 n, name = l.split(':', 1)
262 return statusentry(bin(n), name)
262 return statusentry(bin(n), name)
263 lines = self.opener(self.status_path).read().splitlines()
263 lines = self.opener(self.status_path).read().splitlines()
264 return [parse(l) for l in lines]
264 return [parse(l) for l in lines]
265 return []
265 return []
266
266
267 @util.propertycache
267 @util.propertycache
268 def full_series(self):
268 def full_series(self):
269 if os.path.exists(self.join(self.series_path)):
269 if os.path.exists(self.join(self.series_path)):
270 return self.opener(self.series_path).read().splitlines()
270 return self.opener(self.series_path).read().splitlines()
271 return []
271 return []
272
272
273 @util.propertycache
273 @util.propertycache
274 def series(self):
274 def series(self):
275 self.parse_series()
275 self.parse_series()
276 return self.series
276 return self.series
277
277
278 @util.propertycache
278 @util.propertycache
279 def series_guards(self):
279 def series_guards(self):
280 self.parse_series()
280 self.parse_series()
281 return self.series_guards
281 return self.series_guards
282
282
283 def invalidate(self):
283 def invalidate(self):
284 for a in 'applied full_series series series_guards'.split():
284 for a in 'applied full_series series series_guards'.split():
285 if a in self.__dict__:
285 if a in self.__dict__:
286 delattr(self, a)
286 delattr(self, a)
287 self.applied_dirty = 0
287 self.applied_dirty = 0
288 self.series_dirty = 0
288 self.series_dirty = 0
289 self.guards_dirty = False
289 self.guards_dirty = False
290 self.active_guards = None
290 self.active_guards = None
291
291
292 def diffopts(self, opts={}, patchfn=None):
292 def diffopts(self, opts={}, patchfn=None):
293 diffopts = patch.diffopts(self.ui, opts)
293 diffopts = patch.diffopts(self.ui, opts)
294 if self.gitmode == 'auto':
294 if self.gitmode == 'auto':
295 diffopts.upgrade = True
295 diffopts.upgrade = True
296 elif self.gitmode == 'keep':
296 elif self.gitmode == 'keep':
297 pass
297 pass
298 elif self.gitmode in ('yes', 'no'):
298 elif self.gitmode in ('yes', 'no'):
299 diffopts.git = self.gitmode == 'yes'
299 diffopts.git = self.gitmode == 'yes'
300 else:
300 else:
301 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
301 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
302 ' got %s') % self.gitmode)
302 ' got %s') % self.gitmode)
303 if patchfn:
303 if patchfn:
304 diffopts = self.patchopts(diffopts, patchfn)
304 diffopts = self.patchopts(diffopts, patchfn)
305 return diffopts
305 return diffopts
306
306
307 def patchopts(self, diffopts, *patches):
307 def patchopts(self, diffopts, *patches):
308 """Return a copy of input diff options with git set to true if
308 """Return a copy of input diff options with git set to true if
309 referenced patch is a git patch and should be preserved as such.
309 referenced patch is a git patch and should be preserved as such.
310 """
310 """
311 diffopts = diffopts.copy()
311 diffopts = diffopts.copy()
312 if not diffopts.git and self.gitmode == 'keep':
312 if not diffopts.git and self.gitmode == 'keep':
313 for patchfn in patches:
313 for patchfn in patches:
314 patchf = self.opener(patchfn, 'r')
314 patchf = self.opener(patchfn, 'r')
315 # if the patch was a git patch, refresh it as a git patch
315 # if the patch was a git patch, refresh it as a git patch
316 for line in patchf:
316 for line in patchf:
317 if line.startswith('diff --git'):
317 if line.startswith('diff --git'):
318 diffopts.git = True
318 diffopts.git = True
319 break
319 break
320 patchf.close()
320 patchf.close()
321 return diffopts
321 return diffopts
322
322
323 def join(self, *p):
323 def join(self, *p):
324 return os.path.join(self.path, *p)
324 return os.path.join(self.path, *p)
325
325
326 def find_series(self, patch):
326 def find_series(self, patch):
327 def matchpatch(l):
327 def matchpatch(l):
328 l = l.split('#', 1)[0]
328 l = l.split('#', 1)[0]
329 return l.strip() == patch
329 return l.strip() == patch
330 for index, l in enumerate(self.full_series):
330 for index, l in enumerate(self.full_series):
331 if matchpatch(l):
331 if matchpatch(l):
332 return index
332 return index
333 return None
333 return None
334
334
335 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
335 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
336
336
337 def parse_series(self):
337 def parse_series(self):
338 self.series = []
338 self.series = []
339 self.series_guards = []
339 self.series_guards = []
340 for l in self.full_series:
340 for l in self.full_series:
341 h = l.find('#')
341 h = l.find('#')
342 if h == -1:
342 if h == -1:
343 patch = l
343 patch = l
344 comment = ''
344 comment = ''
345 elif h == 0:
345 elif h == 0:
346 continue
346 continue
347 else:
347 else:
348 patch = l[:h]
348 patch = l[:h]
349 comment = l[h:]
349 comment = l[h:]
350 patch = patch.strip()
350 patch = patch.strip()
351 if patch:
351 if patch:
352 if patch in self.series:
352 if patch in self.series:
353 raise util.Abort(_('%s appears more than once in %s') %
353 raise util.Abort(_('%s appears more than once in %s') %
354 (patch, self.join(self.series_path)))
354 (patch, self.join(self.series_path)))
355 self.series.append(patch)
355 self.series.append(patch)
356 self.series_guards.append(self.guard_re.findall(comment))
356 self.series_guards.append(self.guard_re.findall(comment))
357
357
358 def check_guard(self, guard):
358 def check_guard(self, guard):
359 if not guard:
359 if not guard:
360 return _('guard cannot be an empty string')
360 return _('guard cannot be an empty string')
361 bad_chars = '# \t\r\n\f'
361 bad_chars = '# \t\r\n\f'
362 first = guard[0]
362 first = guard[0]
363 if first in '-+':
363 if first in '-+':
364 return (_('guard %r starts with invalid character: %r') %
364 return (_('guard %r starts with invalid character: %r') %
365 (guard, first))
365 (guard, first))
366 for c in bad_chars:
366 for c in bad_chars:
367 if c in guard:
367 if c in guard:
368 return _('invalid character in guard %r: %r') % (guard, c)
368 return _('invalid character in guard %r: %r') % (guard, c)
369
369
370 def set_active(self, guards):
370 def set_active(self, guards):
371 for guard in guards:
371 for guard in guards:
372 bad = self.check_guard(guard)
372 bad = self.check_guard(guard)
373 if bad:
373 if bad:
374 raise util.Abort(bad)
374 raise util.Abort(bad)
375 guards = sorted(set(guards))
375 guards = sorted(set(guards))
376 self.ui.debug('active guards: %s\n' % ' '.join(guards))
376 self.ui.debug('active guards: %s\n' % ' '.join(guards))
377 self.active_guards = guards
377 self.active_guards = guards
378 self.guards_dirty = True
378 self.guards_dirty = True
379
379
380 def active(self):
380 def active(self):
381 if self.active_guards is None:
381 if self.active_guards is None:
382 self.active_guards = []
382 self.active_guards = []
383 try:
383 try:
384 guards = self.opener(self.guards_path).read().split()
384 guards = self.opener(self.guards_path).read().split()
385 except IOError, err:
385 except IOError, err:
386 if err.errno != errno.ENOENT:
386 if err.errno != errno.ENOENT:
387 raise
387 raise
388 guards = []
388 guards = []
389 for i, guard in enumerate(guards):
389 for i, guard in enumerate(guards):
390 bad = self.check_guard(guard)
390 bad = self.check_guard(guard)
391 if bad:
391 if bad:
392 self.ui.warn('%s:%d: %s\n' %
392 self.ui.warn('%s:%d: %s\n' %
393 (self.join(self.guards_path), i + 1, bad))
393 (self.join(self.guards_path), i + 1, bad))
394 else:
394 else:
395 self.active_guards.append(guard)
395 self.active_guards.append(guard)
396 return self.active_guards
396 return self.active_guards
397
397
398 def set_guards(self, idx, guards):
398 def set_guards(self, idx, guards):
399 for g in guards:
399 for g in guards:
400 if len(g) < 2:
400 if len(g) < 2:
401 raise util.Abort(_('guard %r too short') % g)
401 raise util.Abort(_('guard %r too short') % g)
402 if g[0] not in '-+':
402 if g[0] not in '-+':
403 raise util.Abort(_('guard %r starts with invalid char') % g)
403 raise util.Abort(_('guard %r starts with invalid char') % g)
404 bad = self.check_guard(g[1:])
404 bad = self.check_guard(g[1:])
405 if bad:
405 if bad:
406 raise util.Abort(bad)
406 raise util.Abort(bad)
407 drop = self.guard_re.sub('', self.full_series[idx])
407 drop = self.guard_re.sub('', self.full_series[idx])
408 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
408 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
409 self.parse_series()
409 self.parse_series()
410 self.series_dirty = True
410 self.series_dirty = True
411
411
412 def pushable(self, idx):
412 def pushable(self, idx):
413 if isinstance(idx, str):
413 if isinstance(idx, str):
414 idx = self.series.index(idx)
414 idx = self.series.index(idx)
415 patchguards = self.series_guards[idx]
415 patchguards = self.series_guards[idx]
416 if not patchguards:
416 if not patchguards:
417 return True, None
417 return True, None
418 guards = self.active()
418 guards = self.active()
419 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
419 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
420 if exactneg:
420 if exactneg:
421 return False, exactneg[0]
421 return False, exactneg[0]
422 pos = [g for g in patchguards if g[0] == '+']
422 pos = [g for g in patchguards if g[0] == '+']
423 exactpos = [g for g in pos if g[1:] in guards]
423 exactpos = [g for g in pos if g[1:] in guards]
424 if pos:
424 if pos:
425 if exactpos:
425 if exactpos:
426 return True, exactpos[0]
426 return True, exactpos[0]
427 return False, pos
427 return False, pos
428 return True, ''
428 return True, ''
429
429
430 def explain_pushable(self, idx, all_patches=False):
430 def explain_pushable(self, idx, all_patches=False):
431 write = all_patches and self.ui.write or self.ui.warn
431 write = all_patches and self.ui.write or self.ui.warn
432 if all_patches or self.ui.verbose:
432 if all_patches or self.ui.verbose:
433 if isinstance(idx, str):
433 if isinstance(idx, str):
434 idx = self.series.index(idx)
434 idx = self.series.index(idx)
435 pushable, why = self.pushable(idx)
435 pushable, why = self.pushable(idx)
436 if all_patches and pushable:
436 if all_patches and pushable:
437 if why is None:
437 if why is None:
438 write(_('allowing %s - no guards in effect\n') %
438 write(_('allowing %s - no guards in effect\n') %
439 self.series[idx])
439 self.series[idx])
440 else:
440 else:
441 if not why:
441 if not why:
442 write(_('allowing %s - no matching negative guards\n') %
442 write(_('allowing %s - no matching negative guards\n') %
443 self.series[idx])
443 self.series[idx])
444 else:
444 else:
445 write(_('allowing %s - guarded by %r\n') %
445 write(_('allowing %s - guarded by %r\n') %
446 (self.series[idx], why))
446 (self.series[idx], why))
447 if not pushable:
447 if not pushable:
448 if why:
448 if why:
449 write(_('skipping %s - guarded by %r\n') %
449 write(_('skipping %s - guarded by %r\n') %
450 (self.series[idx], why))
450 (self.series[idx], why))
451 else:
451 else:
452 write(_('skipping %s - no matching guards\n') %
452 write(_('skipping %s - no matching guards\n') %
453 self.series[idx])
453 self.series[idx])
454
454
455 def save_dirty(self):
455 def save_dirty(self):
456 def write_list(items, path):
456 def write_list(items, path):
457 fp = self.opener(path, 'w')
457 fp = self.opener(path, 'w')
458 for i in items:
458 for i in items:
459 fp.write("%s\n" % i)
459 fp.write("%s\n" % i)
460 fp.close()
460 fp.close()
461 if self.applied_dirty:
461 if self.applied_dirty:
462 write_list(map(str, self.applied), self.status_path)
462 write_list(map(str, self.applied), self.status_path)
463 if self.series_dirty:
463 if self.series_dirty:
464 write_list(self.full_series, self.series_path)
464 write_list(self.full_series, self.series_path)
465 if self.guards_dirty:
465 if self.guards_dirty:
466 write_list(self.active_guards, self.guards_path)
466 write_list(self.active_guards, self.guards_path)
467
467
468 def removeundo(self, repo):
468 def removeundo(self, repo):
469 undo = repo.sjoin('undo')
469 undo = repo.sjoin('undo')
470 if not os.path.exists(undo):
470 if not os.path.exists(undo):
471 return
471 return
472 try:
472 try:
473 os.unlink(undo)
473 os.unlink(undo)
474 except OSError, inst:
474 except OSError, inst:
475 self.ui.warn(_('error removing undo: %s\n') % str(inst))
475 self.ui.warn(_('error removing undo: %s\n') % str(inst))
476
476
477 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
477 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
478 fp=None, changes=None, opts={}):
478 fp=None, changes=None, opts={}):
479 stat = opts.get('stat')
479 stat = opts.get('stat')
480 if stat:
480 if stat:
481 opts['unified'] = '0'
481 opts['unified'] = '0'
482
482
483 m = cmdutil.match(repo, files, opts)
483 m = cmdutil.match(repo, files, opts)
484 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
484 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
485 write = fp is None and repo.ui.write or fp.write
485 write = fp is None and repo.ui.write or fp.write
486 if stat:
486 if stat:
487 width = self.ui.interactive() and util.termwidth() or 80
487 width = self.ui.interactive() and util.termwidth() or 80
488 write(patch.diffstat(util.iterlines(chunks), width=width,
488 write(patch.diffstat(util.iterlines(chunks), width=width,
489 git=diffopts.git))
489 git=diffopts.git))
490 else:
490 else:
491 for chunk in chunks:
491 for chunk in chunks:
492 write(chunk)
492 write(chunk)
493
493
494 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
494 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
495 # first try just applying the patch
495 # first try just applying the patch
496 (err, n) = self.apply(repo, [patch], update_status=False,
496 (err, n) = self.apply(repo, [patch], update_status=False,
497 strict=True, merge=rev)
497 strict=True, merge=rev)
498
498
499 if err == 0:
499 if err == 0:
500 return (err, n)
500 return (err, n)
501
501
502 if n is None:
502 if n is None:
503 raise util.Abort(_("apply failed for patch %s") % patch)
503 raise util.Abort(_("apply failed for patch %s") % patch)
504
504
505 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
505 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
506
506
507 # apply failed, strip away that rev and merge.
507 # apply failed, strip away that rev and merge.
508 hg.clean(repo, head)
508 hg.clean(repo, head)
509 self.strip(repo, n, update=False, backup='strip')
509 self.strip(repo, n, update=False, backup='strip')
510
510
511 ctx = repo[rev]
511 ctx = repo[rev]
512 ret = hg.merge(repo, rev)
512 ret = hg.merge(repo, rev)
513 if ret:
513 if ret:
514 raise util.Abort(_("update returned %d") % ret)
514 raise util.Abort(_("update returned %d") % ret)
515 n = repo.commit(ctx.description(), ctx.user(), force=True)
515 n = repo.commit(ctx.description(), ctx.user(), force=True)
516 if n is None:
516 if n is None:
517 raise util.Abort(_("repo commit failed"))
517 raise util.Abort(_("repo commit failed"))
518 try:
518 try:
519 ph = patchheader(mergeq.join(patch), self.plainmode)
519 ph = patchheader(mergeq.join(patch), self.plainmode)
520 except:
520 except:
521 raise util.Abort(_("unable to read %s") % patch)
521 raise util.Abort(_("unable to read %s") % patch)
522
522
523 diffopts = self.patchopts(diffopts, patch)
523 diffopts = self.patchopts(diffopts, patch)
524 patchf = self.opener(patch, "w")
524 patchf = self.opener(patch, "w")
525 comments = str(ph)
525 comments = str(ph)
526 if comments:
526 if comments:
527 patchf.write(comments)
527 patchf.write(comments)
528 self.printdiff(repo, diffopts, head, n, fp=patchf)
528 self.printdiff(repo, diffopts, head, n, fp=patchf)
529 patchf.close()
529 patchf.close()
530 self.removeundo(repo)
530 self.removeundo(repo)
531 return (0, n)
531 return (0, n)
532
532
533 def qparents(self, repo, rev=None):
533 def qparents(self, repo, rev=None):
534 if rev is None:
534 if rev is None:
535 (p1, p2) = repo.dirstate.parents()
535 (p1, p2) = repo.dirstate.parents()
536 if p2 == nullid:
536 if p2 == nullid:
537 return p1
537 return p1
538 if not self.applied:
538 if not self.applied:
539 return None
539 return None
540 return self.applied[-1].node
540 return self.applied[-1].node
541 p1, p2 = repo.changelog.parents(rev)
541 p1, p2 = repo.changelog.parents(rev)
542 if p2 != nullid and p2 in [x.node for x in self.applied]:
542 if p2 != nullid and p2 in [x.node for x in self.applied]:
543 return p2
543 return p2
544 return p1
544 return p1
545
545
546 def mergepatch(self, repo, mergeq, series, diffopts):
546 def mergepatch(self, repo, mergeq, series, diffopts):
547 if not self.applied:
547 if not self.applied:
548 # each of the patches merged in will have two parents. This
548 # each of the patches merged in will have two parents. This
549 # can confuse the qrefresh, qdiff, and strip code because it
549 # can confuse the qrefresh, qdiff, and strip code because it
550 # needs to know which parent is actually in the patch queue.
550 # needs to know which parent is actually in the patch queue.
551 # so, we insert a merge marker with only one parent. This way
551 # so, we insert a merge marker with only one parent. This way
552 # the first patch in the queue is never a merge patch
552 # the first patch in the queue is never a merge patch
553 #
553 #
554 pname = ".hg.patches.merge.marker"
554 pname = ".hg.patches.merge.marker"
555 n = repo.commit('[mq]: merge marker', force=True)
555 n = repo.commit('[mq]: merge marker', force=True)
556 self.removeundo(repo)
556 self.removeundo(repo)
557 self.applied.append(statusentry(n, pname))
557 self.applied.append(statusentry(n, pname))
558 self.applied_dirty = 1
558 self.applied_dirty = 1
559
559
560 head = self.qparents(repo)
560 head = self.qparents(repo)
561
561
562 for patch in series:
562 for patch in series:
563 patch = mergeq.lookup(patch, strict=True)
563 patch = mergeq.lookup(patch, strict=True)
564 if not patch:
564 if not patch:
565 self.ui.warn(_("patch %s does not exist\n") % patch)
565 self.ui.warn(_("patch %s does not exist\n") % patch)
566 return (1, None)
566 return (1, None)
567 pushable, reason = self.pushable(patch)
567 pushable, reason = self.pushable(patch)
568 if not pushable:
568 if not pushable:
569 self.explain_pushable(patch, all_patches=True)
569 self.explain_pushable(patch, all_patches=True)
570 continue
570 continue
571 info = mergeq.isapplied(patch)
571 info = mergeq.isapplied(patch)
572 if not info:
572 if not info:
573 self.ui.warn(_("patch %s is not applied\n") % patch)
573 self.ui.warn(_("patch %s is not applied\n") % patch)
574 return (1, None)
574 return (1, None)
575 rev = info[1]
575 rev = info[1]
576 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
576 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
577 if head:
577 if head:
578 self.applied.append(statusentry(head, patch))
578 self.applied.append(statusentry(head, patch))
579 self.applied_dirty = 1
579 self.applied_dirty = 1
580 if err:
580 if err:
581 return (err, head)
581 return (err, head)
582 self.save_dirty()
582 self.save_dirty()
583 return (0, head)
583 return (0, head)
584
584
585 def patch(self, repo, patchfile):
585 def patch(self, repo, patchfile):
586 '''Apply patchfile to the working directory.
586 '''Apply patchfile to the working directory.
587 patchfile: name of patch file'''
587 patchfile: name of patch file'''
588 files = {}
588 files = {}
589 try:
589 try:
590 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
590 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
591 files=files, eolmode=None)
591 files=files, eolmode=None)
592 except Exception, inst:
592 except Exception, inst:
593 self.ui.note(str(inst) + '\n')
593 self.ui.note(str(inst) + '\n')
594 if not self.ui.verbose:
594 if not self.ui.verbose:
595 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
595 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
596 return (False, files, False)
596 return (False, files, False)
597
597
598 return (True, files, fuzz)
598 return (True, files, fuzz)
599
599
600 def apply(self, repo, series, list=False, update_status=True,
600 def apply(self, repo, series, list=False, update_status=True,
601 strict=False, patchdir=None, merge=None, all_files=None):
601 strict=False, patchdir=None, merge=None, all_files=None):
602 wlock = lock = tr = None
602 wlock = lock = tr = None
603 try:
603 try:
604 wlock = repo.wlock()
604 wlock = repo.wlock()
605 lock = repo.lock()
605 lock = repo.lock()
606 tr = repo.transaction()
606 tr = repo.transaction()
607 try:
607 try:
608 ret = self._apply(repo, series, list, update_status,
608 ret = self._apply(repo, series, list, update_status,
609 strict, patchdir, merge, all_files=all_files)
609 strict, patchdir, merge, all_files=all_files)
610 tr.close()
610 tr.close()
611 self.save_dirty()
611 self.save_dirty()
612 return ret
612 return ret
613 except:
613 except:
614 try:
614 try:
615 tr.abort()
615 tr.abort()
616 finally:
616 finally:
617 repo.invalidate()
617 repo.invalidate()
618 repo.dirstate.invalidate()
618 repo.dirstate.invalidate()
619 raise
619 raise
620 finally:
620 finally:
621 del tr
621 del tr
622 release(lock, wlock)
622 release(lock, wlock)
623 self.removeundo(repo)
623 self.removeundo(repo)
624
624
625 def _apply(self, repo, series, list=False, update_status=True,
625 def _apply(self, repo, series, list=False, update_status=True,
626 strict=False, patchdir=None, merge=None, all_files=None):
626 strict=False, patchdir=None, merge=None, all_files=None):
627 '''returns (error, hash)
627 '''returns (error, hash)
628 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
628 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
629 # TODO unify with commands.py
629 # TODO unify with commands.py
630 if not patchdir:
630 if not patchdir:
631 patchdir = self.path
631 patchdir = self.path
632 err = 0
632 err = 0
633 n = None
633 n = None
634 for patchname in series:
634 for patchname in series:
635 pushable, reason = self.pushable(patchname)
635 pushable, reason = self.pushable(patchname)
636 if not pushable:
636 if not pushable:
637 self.explain_pushable(patchname, all_patches=True)
637 self.explain_pushable(patchname, all_patches=True)
638 continue
638 continue
639 self.ui.status(_("applying %s\n") % patchname)
639 self.ui.status(_("applying %s\n") % patchname)
640 pf = os.path.join(patchdir, patchname)
640 pf = os.path.join(patchdir, patchname)
641
641
642 try:
642 try:
643 ph = patchheader(self.join(patchname), self.plainmode)
643 ph = patchheader(self.join(patchname), self.plainmode)
644 except:
644 except:
645 self.ui.warn(_("unable to read %s\n") % patchname)
645 self.ui.warn(_("unable to read %s\n") % patchname)
646 err = 1
646 err = 1
647 break
647 break
648
648
649 message = ph.message
649 message = ph.message
650 if not message:
650 if not message:
651 message = "imported patch %s\n" % patchname
651 message = "imported patch %s\n" % patchname
652 else:
652 else:
653 if list:
653 if list:
654 message.append("\nimported patch %s" % patchname)
654 message.append("\nimported patch %s" % patchname)
655 message = '\n'.join(message)
655 message = '\n'.join(message)
656
656
657 if ph.haspatch:
657 if ph.haspatch:
658 (patcherr, files, fuzz) = self.patch(repo, pf)
658 (patcherr, files, fuzz) = self.patch(repo, pf)
659 if all_files is not None:
659 if all_files is not None:
660 all_files.update(files)
660 all_files.update(files)
661 patcherr = not patcherr
661 patcherr = not patcherr
662 else:
662 else:
663 self.ui.warn(_("patch %s is empty\n") % patchname)
663 self.ui.warn(_("patch %s is empty\n") % patchname)
664 patcherr, files, fuzz = 0, [], 0
664 patcherr, files, fuzz = 0, [], 0
665
665
666 if merge and files:
666 if merge and files:
667 # Mark as removed/merged and update dirstate parent info
667 # Mark as removed/merged and update dirstate parent info
668 removed = []
668 removed = []
669 merged = []
669 merged = []
670 for f in files:
670 for f in files:
671 if os.path.exists(repo.wjoin(f)):
671 if os.path.exists(repo.wjoin(f)):
672 merged.append(f)
672 merged.append(f)
673 else:
673 else:
674 removed.append(f)
674 removed.append(f)
675 for f in removed:
675 for f in removed:
676 repo.dirstate.remove(f)
676 repo.dirstate.remove(f)
677 for f in merged:
677 for f in merged:
678 repo.dirstate.merge(f)
678 repo.dirstate.merge(f)
679 p1, p2 = repo.dirstate.parents()
679 p1, p2 = repo.dirstate.parents()
680 repo.dirstate.setparents(p1, merge)
680 repo.dirstate.setparents(p1, merge)
681
681
682 files = patch.updatedir(self.ui, repo, files)
682 files = patch.updatedir(self.ui, repo, files)
683 match = cmdutil.matchfiles(repo, files or [])
683 match = cmdutil.matchfiles(repo, files or [])
684 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
684 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
685
685
686 if n is None:
686 if n is None:
687 raise util.Abort(_("repo commit failed"))
687 raise util.Abort(_("repo commit failed"))
688
688
689 if update_status:
689 if update_status:
690 self.applied.append(statusentry(n, patchname))
690 self.applied.append(statusentry(n, patchname))
691
691
692 if patcherr:
692 if patcherr:
693 self.ui.warn(_("patch failed, rejects left in working dir\n"))
693 self.ui.warn(_("patch failed, rejects left in working dir\n"))
694 err = 2
694 err = 2
695 break
695 break
696
696
697 if fuzz and strict:
697 if fuzz and strict:
698 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
698 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
699 err = 3
699 err = 3
700 break
700 break
701 return (err, n)
701 return (err, n)
702
702
703 def _cleanup(self, patches, numrevs, keep=False):
703 def _cleanup(self, patches, numrevs, keep=False):
704 if not keep:
704 if not keep:
705 r = self.qrepo()
705 r = self.qrepo()
706 if r:
706 if r:
707 r.remove(patches, True)
707 r.remove(patches, True)
708 else:
708 else:
709 for p in patches:
709 for p in patches:
710 os.unlink(self.join(p))
710 os.unlink(self.join(p))
711
711
712 if numrevs:
712 if numrevs:
713 del self.applied[:numrevs]
713 del self.applied[:numrevs]
714 self.applied_dirty = 1
714 self.applied_dirty = 1
715
715
716 for i in sorted([self.find_series(p) for p in patches], reverse=True):
716 for i in sorted([self.find_series(p) for p in patches], reverse=True):
717 del self.full_series[i]
717 del self.full_series[i]
718 self.parse_series()
718 self.parse_series()
719 self.series_dirty = 1
719 self.series_dirty = 1
720
720
721 def _revpatches(self, repo, revs):
721 def _revpatches(self, repo, revs):
722 firstrev = repo[self.applied[0].node].rev()
722 firstrev = repo[self.applied[0].node].rev()
723 patches = []
723 patches = []
724 for i, rev in enumerate(revs):
724 for i, rev in enumerate(revs):
725
725
726 if rev < firstrev:
726 if rev < firstrev:
727 raise util.Abort(_('revision %d is not managed') % rev)
727 raise util.Abort(_('revision %d is not managed') % rev)
728
728
729 ctx = repo[rev]
729 ctx = repo[rev]
730 base = self.applied[i].node
730 base = self.applied[i].node
731 if ctx.node() != base:
731 if ctx.node() != base:
732 msg = _('cannot delete revision %d above applied patches')
732 msg = _('cannot delete revision %d above applied patches')
733 raise util.Abort(msg % rev)
733 raise util.Abort(msg % rev)
734
734
735 patch = self.applied[i].name
735 patch = self.applied[i].name
736 for fmt in ('[mq]: %s', 'imported patch %s'):
736 for fmt in ('[mq]: %s', 'imported patch %s'):
737 if ctx.description() == fmt % patch:
737 if ctx.description() == fmt % patch:
738 msg = _('patch %s finalized without changeset message\n')
738 msg = _('patch %s finalized without changeset message\n')
739 repo.ui.status(msg % patch)
739 repo.ui.status(msg % patch)
740 break
740 break
741
741
742 patches.append(patch)
742 patches.append(patch)
743 return patches
743 return patches
744
744
745 def finish(self, repo, revs):
745 def finish(self, repo, revs):
746 patches = self._revpatches(repo, sorted(revs))
746 patches = self._revpatches(repo, sorted(revs))
747 self._cleanup(patches, len(patches))
747 self._cleanup(patches, len(patches))
748
748
749 def delete(self, repo, patches, opts):
749 def delete(self, repo, patches, opts):
750 if not patches and not opts.get('rev'):
750 if not patches and not opts.get('rev'):
751 raise util.Abort(_('qdelete requires at least one revision or '
751 raise util.Abort(_('qdelete requires at least one revision or '
752 'patch name'))
752 'patch name'))
753
753
754 realpatches = []
754 realpatches = []
755 for patch in patches:
755 for patch in patches:
756 patch = self.lookup(patch, strict=True)
756 patch = self.lookup(patch, strict=True)
757 info = self.isapplied(patch)
757 info = self.isapplied(patch)
758 if info:
758 if info:
759 raise util.Abort(_("cannot delete applied patch %s") % patch)
759 raise util.Abort(_("cannot delete applied patch %s") % patch)
760 if patch not in self.series:
760 if patch not in self.series:
761 raise util.Abort(_("patch %s not in series file") % patch)
761 raise util.Abort(_("patch %s not in series file") % patch)
762 realpatches.append(patch)
762 realpatches.append(patch)
763
763
764 numrevs = 0
764 numrevs = 0
765 if opts.get('rev'):
765 if opts.get('rev'):
766 if not self.applied:
766 if not self.applied:
767 raise util.Abort(_('no patches applied'))
767 raise util.Abort(_('no patches applied'))
768 revs = cmdutil.revrange(repo, opts['rev'])
768 revs = cmdutil.revrange(repo, opts['rev'])
769 if len(revs) > 1 and revs[0] > revs[1]:
769 if len(revs) > 1 and revs[0] > revs[1]:
770 revs.reverse()
770 revs.reverse()
771 revpatches = self._revpatches(repo, revs)
771 revpatches = self._revpatches(repo, revs)
772 realpatches += revpatches
772 realpatches += revpatches
773 numrevs = len(revpatches)
773 numrevs = len(revpatches)
774
774
775 self._cleanup(realpatches, numrevs, opts.get('keep'))
775 self._cleanup(realpatches, numrevs, opts.get('keep'))
776
776
777 def check_toppatch(self, repo):
777 def check_toppatch(self, repo):
778 if self.applied:
778 if self.applied:
779 top = self.applied[-1].node
779 top = self.applied[-1].node
780 patch = self.applied[-1].name
780 patch = self.applied[-1].name
781 pp = repo.dirstate.parents()
781 pp = repo.dirstate.parents()
782 if top not in pp:
782 if top not in pp:
783 raise util.Abort(_("working directory revision is not qtip"))
783 raise util.Abort(_("working directory revision is not qtip"))
784 return top, patch
784 return top, patch
785 return None, None
785 return None, None
786
786
787 def check_localchanges(self, repo, force=False, refresh=True):
787 def check_localchanges(self, repo, force=False, refresh=True):
788 m, a, r, d = repo.status()[:4]
788 m, a, r, d = repo.status()[:4]
789 if (m or a or r or d) and not force:
789 if (m or a or r or d) and not force:
790 if refresh:
790 if refresh:
791 raise util.Abort(_("local changes found, refresh first"))
791 raise util.Abort(_("local changes found, refresh first"))
792 else:
792 else:
793 raise util.Abort(_("local changes found"))
793 raise util.Abort(_("local changes found"))
794 return m, a, r, d
794 return m, a, r, d
795
795
796 _reserved = ('series', 'status', 'guards')
796 _reserved = ('series', 'status', 'guards')
797 def check_reserved_name(self, name):
797 def check_reserved_name(self, name):
798 if (name in self._reserved or name.startswith('.hg')
798 if (name in self._reserved or name.startswith('.hg')
799 or name.startswith('.mq') or '#' in name or ':' in name):
799 or name.startswith('.mq') or '#' in name or ':' in name):
800 raise util.Abort(_('"%s" cannot be used as the name of a patch')
800 raise util.Abort(_('"%s" cannot be used as the name of a patch')
801 % name)
801 % name)
802
802
803 def new(self, repo, patchfn, *pats, **opts):
803 def new(self, repo, patchfn, *pats, **opts):
804 """options:
804 """options:
805 msg: a string or a no-argument function returning a string
805 msg: a string or a no-argument function returning a string
806 """
806 """
807 msg = opts.get('msg')
807 msg = opts.get('msg')
808 user = opts.get('user')
808 user = opts.get('user')
809 date = opts.get('date')
809 date = opts.get('date')
810 if date:
810 if date:
811 date = util.parsedate(date)
811 date = util.parsedate(date)
812 diffopts = self.diffopts({'git': opts.get('git')})
812 diffopts = self.diffopts({'git': opts.get('git')})
813 self.check_reserved_name(patchfn)
813 self.check_reserved_name(patchfn)
814 if os.path.exists(self.join(patchfn)):
814 if os.path.exists(self.join(patchfn)):
815 raise util.Abort(_('patch "%s" already exists') % patchfn)
815 raise util.Abort(_('patch "%s" already exists') % patchfn)
816 if opts.get('include') or opts.get('exclude') or pats:
816 if opts.get('include') or opts.get('exclude') or pats:
817 match = cmdutil.match(repo, pats, opts)
817 match = cmdutil.match(repo, pats, opts)
818 # detect missing files in pats
818 # detect missing files in pats
819 def badfn(f, msg):
819 def badfn(f, msg):
820 raise util.Abort('%s: %s' % (f, msg))
820 raise util.Abort('%s: %s' % (f, msg))
821 match.bad = badfn
821 match.bad = badfn
822 m, a, r, d = repo.status(match=match)[:4]
822 m, a, r, d = repo.status(match=match)[:4]
823 else:
823 else:
824 m, a, r, d = self.check_localchanges(repo, force=True)
824 m, a, r, d = self.check_localchanges(repo, force=True)
825 match = cmdutil.matchfiles(repo, m + a + r)
825 match = cmdutil.matchfiles(repo, m + a + r)
826 if len(repo[None].parents()) > 1:
826 if len(repo[None].parents()) > 1:
827 raise util.Abort(_('cannot manage merge changesets'))
827 raise util.Abort(_('cannot manage merge changesets'))
828 commitfiles = m + a + r
828 commitfiles = m + a + r
829 self.check_toppatch(repo)
829 self.check_toppatch(repo)
830 insert = self.full_series_end()
830 insert = self.full_series_end()
831 wlock = repo.wlock()
831 wlock = repo.wlock()
832 try:
832 try:
833 # if patch file write fails, abort early
833 # if patch file write fails, abort early
834 p = self.opener(patchfn, "w")
834 p = self.opener(patchfn, "w")
835 try:
835 try:
836 if self.plainmode:
836 if self.plainmode:
837 if user:
837 if user:
838 p.write("From: " + user + "\n")
838 p.write("From: " + user + "\n")
839 if not date:
839 if not date:
840 p.write("\n")
840 p.write("\n")
841 if date:
841 if date:
842 p.write("Date: %d %d\n\n" % date)
842 p.write("Date: %d %d\n\n" % date)
843 else:
843 else:
844 p.write("# HG changeset patch\n")
844 p.write("# HG changeset patch\n")
845 p.write("# Parent "
845 p.write("# Parent "
846 + hex(repo[None].parents()[0].node()) + "\n")
846 + hex(repo[None].parents()[0].node()) + "\n")
847 if user:
847 if user:
848 p.write("# User " + user + "\n")
848 p.write("# User " + user + "\n")
849 if date:
849 if date:
850 p.write("# Date %s %s\n\n" % date)
850 p.write("# Date %s %s\n\n" % date)
851 if hasattr(msg, '__call__'):
851 if hasattr(msg, '__call__'):
852 msg = msg()
852 msg = msg()
853 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
853 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
854 n = repo.commit(commitmsg, user, date, match=match, force=True)
854 n = repo.commit(commitmsg, user, date, match=match, force=True)
855 if n is None:
855 if n is None:
856 raise util.Abort(_("repo commit failed"))
856 raise util.Abort(_("repo commit failed"))
857 try:
857 try:
858 self.full_series[insert:insert] = [patchfn]
858 self.full_series[insert:insert] = [patchfn]
859 self.applied.append(statusentry(n, patchfn))
859 self.applied.append(statusentry(n, patchfn))
860 self.parse_series()
860 self.parse_series()
861 self.series_dirty = 1
861 self.series_dirty = 1
862 self.applied_dirty = 1
862 self.applied_dirty = 1
863 if msg:
863 if msg:
864 msg = msg + "\n\n"
864 msg = msg + "\n\n"
865 p.write(msg)
865 p.write(msg)
866 if commitfiles:
866 if commitfiles:
867 parent = self.qparents(repo, n)
867 parent = self.qparents(repo, n)
868 chunks = patch.diff(repo, node1=parent, node2=n,
868 chunks = patch.diff(repo, node1=parent, node2=n,
869 match=match, opts=diffopts)
869 match=match, opts=diffopts)
870 for chunk in chunks:
870 for chunk in chunks:
871 p.write(chunk)
871 p.write(chunk)
872 p.close()
872 p.close()
873 wlock.release()
873 wlock.release()
874 wlock = None
874 wlock = None
875 r = self.qrepo()
875 r = self.qrepo()
876 if r:
876 if r:
877 r.add([patchfn])
877 r.add([patchfn])
878 except:
878 except:
879 repo.rollback()
879 repo.rollback()
880 raise
880 raise
881 except Exception:
881 except Exception:
882 patchpath = self.join(patchfn)
882 patchpath = self.join(patchfn)
883 try:
883 try:
884 os.unlink(patchpath)
884 os.unlink(patchpath)
885 except:
885 except:
886 self.ui.warn(_('error unlinking %s\n') % patchpath)
886 self.ui.warn(_('error unlinking %s\n') % patchpath)
887 raise
887 raise
888 self.removeundo(repo)
888 self.removeundo(repo)
889 finally:
889 finally:
890 release(wlock)
890 release(wlock)
891
891
892 def strip(self, repo, rev, update=True, backup="all", force=None):
892 def strip(self, repo, rev, update=True, backup="all", force=None):
893 wlock = lock = None
893 wlock = lock = None
894 try:
894 try:
895 wlock = repo.wlock()
895 wlock = repo.wlock()
896 lock = repo.lock()
896 lock = repo.lock()
897
897
898 if update:
898 if update:
899 self.check_localchanges(repo, force=force, refresh=False)
899 self.check_localchanges(repo, force=force, refresh=False)
900 urev = self.qparents(repo, rev)
900 urev = self.qparents(repo, rev)
901 hg.clean(repo, urev)
901 hg.clean(repo, urev)
902 repo.dirstate.write()
902 repo.dirstate.write()
903
903
904 self.removeundo(repo)
904 self.removeundo(repo)
905 repair.strip(self.ui, repo, rev, backup)
905 repair.strip(self.ui, repo, rev, backup)
906 # strip may have unbundled a set of backed up revisions after
906 # strip may have unbundled a set of backed up revisions after
907 # the actual strip
907 # the actual strip
908 self.removeundo(repo)
908 self.removeundo(repo)
909 finally:
909 finally:
910 release(lock, wlock)
910 release(lock, wlock)
911
911
912 def isapplied(self, patch):
912 def isapplied(self, patch):
913 """returns (index, rev, patch)"""
913 """returns (index, rev, patch)"""
914 for i, a in enumerate(self.applied):
914 for i, a in enumerate(self.applied):
915 if a.name == patch:
915 if a.name == patch:
916 return (i, a.node, a.name)
916 return (i, a.node, a.name)
917 return None
917 return None
918
918
919 # if the exact patch name does not exist, we try a few
919 # if the exact patch name does not exist, we try a few
920 # variations. If strict is passed, we try only #1
920 # variations. If strict is passed, we try only #1
921 #
921 #
922 # 1) a number to indicate an offset in the series file
922 # 1) a number to indicate an offset in the series file
923 # 2) a unique substring of the patch name was given
923 # 2) a unique substring of the patch name was given
924 # 3) patchname[-+]num to indicate an offset in the series file
924 # 3) patchname[-+]num to indicate an offset in the series file
925 def lookup(self, patch, strict=False):
925 def lookup(self, patch, strict=False):
926 patch = patch and str(patch)
926 patch = patch and str(patch)
927
927
928 def partial_name(s):
928 def partial_name(s):
929 if s in self.series:
929 if s in self.series:
930 return s
930 return s
931 matches = [x for x in self.series if s in x]
931 matches = [x for x in self.series if s in x]
932 if len(matches) > 1:
932 if len(matches) > 1:
933 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
933 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
934 for m in matches:
934 for m in matches:
935 self.ui.warn(' %s\n' % m)
935 self.ui.warn(' %s\n' % m)
936 return None
936 return None
937 if matches:
937 if matches:
938 return matches[0]
938 return matches[0]
939 if self.series and self.applied:
939 if self.series and self.applied:
940 if s == 'qtip':
940 if s == 'qtip':
941 return self.series[self.series_end(True)-1]
941 return self.series[self.series_end(True)-1]
942 if s == 'qbase':
942 if s == 'qbase':
943 return self.series[0]
943 return self.series[0]
944 return None
944 return None
945
945
946 if patch is None:
946 if patch is None:
947 return None
947 return None
948 if patch in self.series:
948 if patch in self.series:
949 return patch
949 return patch
950
950
951 if not os.path.isfile(self.join(patch)):
951 if not os.path.isfile(self.join(patch)):
952 try:
952 try:
953 sno = int(patch)
953 sno = int(patch)
954 except (ValueError, OverflowError):
954 except (ValueError, OverflowError):
955 pass
955 pass
956 else:
956 else:
957 if -len(self.series) <= sno < len(self.series):
957 if -len(self.series) <= sno < len(self.series):
958 return self.series[sno]
958 return self.series[sno]
959
959
960 if not strict:
960 if not strict:
961 res = partial_name(patch)
961 res = partial_name(patch)
962 if res:
962 if res:
963 return res
963 return res
964 minus = patch.rfind('-')
964 minus = patch.rfind('-')
965 if minus >= 0:
965 if minus >= 0:
966 res = partial_name(patch[:minus])
966 res = partial_name(patch[:minus])
967 if res:
967 if res:
968 i = self.series.index(res)
968 i = self.series.index(res)
969 try:
969 try:
970 off = int(patch[minus + 1:] or 1)
970 off = int(patch[minus + 1:] or 1)
971 except (ValueError, OverflowError):
971 except (ValueError, OverflowError):
972 pass
972 pass
973 else:
973 else:
974 if i - off >= 0:
974 if i - off >= 0:
975 return self.series[i - off]
975 return self.series[i - off]
976 plus = patch.rfind('+')
976 plus = patch.rfind('+')
977 if plus >= 0:
977 if plus >= 0:
978 res = partial_name(patch[:plus])
978 res = partial_name(patch[:plus])
979 if res:
979 if res:
980 i = self.series.index(res)
980 i = self.series.index(res)
981 try:
981 try:
982 off = int(patch[plus + 1:] or 1)
982 off = int(patch[plus + 1:] or 1)
983 except (ValueError, OverflowError):
983 except (ValueError, OverflowError):
984 pass
984 pass
985 else:
985 else:
986 if i + off < len(self.series):
986 if i + off < len(self.series):
987 return self.series[i + off]
987 return self.series[i + off]
988 raise util.Abort(_("patch %s not in series") % patch)
988 raise util.Abort(_("patch %s not in series") % patch)
989
989
990 def push(self, repo, patch=None, force=False, list=False,
990 def push(self, repo, patch=None, force=False, list=False,
991 mergeq=None, all=False):
991 mergeq=None, all=False):
992 diffopts = self.diffopts()
992 diffopts = self.diffopts()
993 wlock = repo.wlock()
993 wlock = repo.wlock()
994 try:
994 try:
995 heads = []
995 heads = []
996 for b, ls in repo.branchmap().iteritems():
996 for b, ls in repo.branchmap().iteritems():
997 heads += ls
997 heads += ls
998 if not heads:
998 if not heads:
999 heads = [nullid]
999 heads = [nullid]
1000 if repo.dirstate.parents()[0] not in heads:
1000 if repo.dirstate.parents()[0] not in heads:
1001 self.ui.status(_("(working directory not at a head)\n"))
1001 self.ui.status(_("(working directory not at a head)\n"))
1002
1002
1003 if not self.series:
1003 if not self.series:
1004 self.ui.warn(_('no patches in series\n'))
1004 self.ui.warn(_('no patches in series\n'))
1005 return 0
1005 return 0
1006
1006
1007 patch = self.lookup(patch)
1007 patch = self.lookup(patch)
1008 # Suppose our series file is: A B C and the current 'top'
1008 # Suppose our series file is: A B C and the current 'top'
1009 # patch is B. qpush C should be performed (moving forward)
1009 # patch is B. qpush C should be performed (moving forward)
1010 # qpush B is a NOP (no change) qpush A is an error (can't
1010 # qpush B is a NOP (no change) qpush A is an error (can't
1011 # go backwards with qpush)
1011 # go backwards with qpush)
1012 if patch:
1012 if patch:
1013 info = self.isapplied(patch)
1013 info = self.isapplied(patch)
1014 if info:
1014 if info:
1015 if info[0] < len(self.applied) - 1:
1015 if info[0] < len(self.applied) - 1:
1016 raise util.Abort(
1016 raise util.Abort(
1017 _("cannot push to a previous patch: %s") % patch)
1017 _("cannot push to a previous patch: %s") % patch)
1018 self.ui.warn(
1018 self.ui.warn(
1019 _('qpush: %s is already at the top\n') % patch)
1019 _('qpush: %s is already at the top\n') % patch)
1020 return
1020 return
1021 pushable, reason = self.pushable(patch)
1021 pushable, reason = self.pushable(patch)
1022 if not pushable:
1022 if not pushable:
1023 if reason:
1023 if reason:
1024 reason = _('guarded by %r') % reason
1024 reason = _('guarded by %r') % reason
1025 else:
1025 else:
1026 reason = _('no matching guards')
1026 reason = _('no matching guards')
1027 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1027 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1028 return 1
1028 return 1
1029 elif all:
1029 elif all:
1030 patch = self.series[-1]
1030 patch = self.series[-1]
1031 if self.isapplied(patch):
1031 if self.isapplied(patch):
1032 self.ui.warn(_('all patches are currently applied\n'))
1032 self.ui.warn(_('all patches are currently applied\n'))
1033 return 0
1033 return 0
1034
1034
1035 # Following the above example, starting at 'top' of B:
1035 # Following the above example, starting at 'top' of B:
1036 # qpush should be performed (pushes C), but a subsequent
1036 # qpush should be performed (pushes C), but a subsequent
1037 # qpush without an argument is an error (nothing to
1037 # qpush without an argument is an error (nothing to
1038 # apply). This allows a loop of "...while hg qpush..." to
1038 # apply). This allows a loop of "...while hg qpush..." to
1039 # work as it detects an error when done
1039 # work as it detects an error when done
1040 start = self.series_end()
1040 start = self.series_end()
1041 if start == len(self.series):
1041 if start == len(self.series):
1042 self.ui.warn(_('patch series already fully applied\n'))
1042 self.ui.warn(_('patch series already fully applied\n'))
1043 return 1
1043 return 1
1044 if not force:
1044 if not force:
1045 self.check_localchanges(repo)
1045 self.check_localchanges(repo)
1046
1046
1047 self.applied_dirty = 1
1047 self.applied_dirty = 1
1048 if start > 0:
1048 if start > 0:
1049 self.check_toppatch(repo)
1049 self.check_toppatch(repo)
1050 if not patch:
1050 if not patch:
1051 patch = self.series[start]
1051 patch = self.series[start]
1052 end = start + 1
1052 end = start + 1
1053 else:
1053 else:
1054 end = self.series.index(patch, start) + 1
1054 end = self.series.index(patch, start) + 1
1055
1055
1056 s = self.series[start:end]
1056 s = self.series[start:end]
1057 all_files = set()
1057 all_files = set()
1058 try:
1058 try:
1059 if mergeq:
1059 if mergeq:
1060 ret = self.mergepatch(repo, mergeq, s, diffopts)
1060 ret = self.mergepatch(repo, mergeq, s, diffopts)
1061 else:
1061 else:
1062 ret = self.apply(repo, s, list, all_files=all_files)
1062 ret = self.apply(repo, s, list, all_files=all_files)
1063 except:
1063 except:
1064 self.ui.warn(_('cleaning up working directory...'))
1064 self.ui.warn(_('cleaning up working directory...'))
1065 node = repo.dirstate.parents()[0]
1065 node = repo.dirstate.parents()[0]
1066 hg.revert(repo, node, None)
1066 hg.revert(repo, node, None)
1067 # only remove unknown files that we know we touched or
1067 # only remove unknown files that we know we touched or
1068 # created while patching
1068 # created while patching
1069 for f in all_files:
1069 for f in all_files:
1070 if f not in repo.dirstate:
1070 if f not in repo.dirstate:
1071 try:
1071 try:
1072 util.unlink(repo.wjoin(f))
1072 util.unlink(repo.wjoin(f))
1073 except OSError, inst:
1073 except OSError, inst:
1074 if inst.errno != errno.ENOENT:
1074 if inst.errno != errno.ENOENT:
1075 raise
1075 raise
1076 self.ui.warn(_('done\n'))
1076 self.ui.warn(_('done\n'))
1077 raise
1077 raise
1078
1078
1079 if not self.applied:
1079 if not self.applied:
1080 return ret[0]
1080 return ret[0]
1081 top = self.applied[-1].name
1081 top = self.applied[-1].name
1082 if ret[0] and ret[0] > 1:
1082 if ret[0] and ret[0] > 1:
1083 msg = _("errors during apply, please fix and refresh %s\n")
1083 msg = _("errors during apply, please fix and refresh %s\n")
1084 self.ui.write(msg % top)
1084 self.ui.write(msg % top)
1085 else:
1085 else:
1086 self.ui.write(_("now at: %s\n") % top)
1086 self.ui.write(_("now at: %s\n") % top)
1087 return ret[0]
1087 return ret[0]
1088
1088
1089 finally:
1089 finally:
1090 wlock.release()
1090 wlock.release()
1091
1091
1092 def pop(self, repo, patch=None, force=False, update=True, all=False):
1092 def pop(self, repo, patch=None, force=False, update=True, all=False):
1093 wlock = repo.wlock()
1093 wlock = repo.wlock()
1094 try:
1094 try:
1095 if patch:
1095 if patch:
1096 # index, rev, patch
1096 # index, rev, patch
1097 info = self.isapplied(patch)
1097 info = self.isapplied(patch)
1098 if not info:
1098 if not info:
1099 patch = self.lookup(patch)
1099 patch = self.lookup(patch)
1100 info = self.isapplied(patch)
1100 info = self.isapplied(patch)
1101 if not info:
1101 if not info:
1102 raise util.Abort(_("patch %s is not applied") % patch)
1102 raise util.Abort(_("patch %s is not applied") % patch)
1103
1103
1104 if not self.applied:
1104 if not self.applied:
1105 # Allow qpop -a to work repeatedly,
1105 # Allow qpop -a to work repeatedly,
1106 # but not qpop without an argument
1106 # but not qpop without an argument
1107 self.ui.warn(_("no patches applied\n"))
1107 self.ui.warn(_("no patches applied\n"))
1108 return not all
1108 return not all
1109
1109
1110 if all:
1110 if all:
1111 start = 0
1111 start = 0
1112 elif patch:
1112 elif patch:
1113 start = info[0] + 1
1113 start = info[0] + 1
1114 else:
1114 else:
1115 start = len(self.applied) - 1
1115 start = len(self.applied) - 1
1116
1116
1117 if start >= len(self.applied):
1117 if start >= len(self.applied):
1118 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1118 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1119 return
1119 return
1120
1120
1121 if not update:
1121 if not update:
1122 parents = repo.dirstate.parents()
1122 parents = repo.dirstate.parents()
1123 rr = [x.node for x in self.applied]
1123 rr = [x.node for x in self.applied]
1124 for p in parents:
1124 for p in parents:
1125 if p in rr:
1125 if p in rr:
1126 self.ui.warn(_("qpop: forcing dirstate update\n"))
1126 self.ui.warn(_("qpop: forcing dirstate update\n"))
1127 update = True
1127 update = True
1128 else:
1128 else:
1129 parents = [p.node() for p in repo[None].parents()]
1129 parents = [p.node() for p in repo[None].parents()]
1130 needupdate = False
1130 needupdate = False
1131 for entry in self.applied[start:]:
1131 for entry in self.applied[start:]:
1132 if entry.node in parents:
1132 if entry.node in parents:
1133 needupdate = True
1133 needupdate = True
1134 break
1134 break
1135 update = needupdate
1135 update = needupdate
1136
1136
1137 if not force and update:
1137 if not force and update:
1138 self.check_localchanges(repo)
1138 self.check_localchanges(repo)
1139
1139
1140 self.applied_dirty = 1
1140 self.applied_dirty = 1
1141 end = len(self.applied)
1141 end = len(self.applied)
1142 rev = self.applied[start].node
1142 rev = self.applied[start].node
1143 if update:
1143 if update:
1144 top = self.check_toppatch(repo)[0]
1144 top = self.check_toppatch(repo)[0]
1145
1145
1146 try:
1146 try:
1147 heads = repo.changelog.heads(rev)
1147 heads = repo.changelog.heads(rev)
1148 except error.LookupError:
1148 except error.LookupError:
1149 node = short(rev)
1149 node = short(rev)
1150 raise util.Abort(_('trying to pop unknown node %s') % node)
1150 raise util.Abort(_('trying to pop unknown node %s') % node)
1151
1151
1152 if heads != [self.applied[-1].node]:
1152 if heads != [self.applied[-1].node]:
1153 raise util.Abort(_("popping would remove a revision not "
1153 raise util.Abort(_("popping would remove a revision not "
1154 "managed by this patch queue"))
1154 "managed by this patch queue"))
1155
1155
1156 # we know there are no local changes, so we can make a simplified
1156 # we know there are no local changes, so we can make a simplified
1157 # form of hg.update.
1157 # form of hg.update.
1158 if update:
1158 if update:
1159 qp = self.qparents(repo, rev)
1159 qp = self.qparents(repo, rev)
1160 ctx = repo[qp]
1160 ctx = repo[qp]
1161 m, a, r, d = repo.status(qp, top)[:4]
1161 m, a, r, d = repo.status(qp, top)[:4]
1162 if d:
1162 if d:
1163 raise util.Abort(_("deletions found between repo revs"))
1163 raise util.Abort(_("deletions found between repo revs"))
1164 for f in a:
1164 for f in a:
1165 try:
1165 try:
1166 os.unlink(repo.wjoin(f))
1166 os.unlink(repo.wjoin(f))
1167 except OSError, e:
1167 except OSError, e:
1168 if e.errno != errno.ENOENT:
1168 if e.errno != errno.ENOENT:
1169 raise
1169 raise
1170 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1170 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1171 except: pass
1171 except: pass
1172 repo.dirstate.forget(f)
1172 repo.dirstate.forget(f)
1173 for f in m + r:
1173 for f in m + r:
1174 fctx = ctx[f]
1174 fctx = ctx[f]
1175 repo.wwrite(f, fctx.data(), fctx.flags())
1175 repo.wwrite(f, fctx.data(), fctx.flags())
1176 repo.dirstate.normal(f)
1176 repo.dirstate.normal(f)
1177 repo.dirstate.setparents(qp, nullid)
1177 repo.dirstate.setparents(qp, nullid)
1178 for patch in reversed(self.applied[start:end]):
1178 for patch in reversed(self.applied[start:end]):
1179 self.ui.status(_("popping %s\n") % patch.name)
1179 self.ui.status(_("popping %s\n") % patch.name)
1180 del self.applied[start:end]
1180 del self.applied[start:end]
1181 self.strip(repo, rev, update=False, backup='strip')
1181 self.strip(repo, rev, update=False, backup='strip')
1182 if self.applied:
1182 if self.applied:
1183 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1183 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1184 else:
1184 else:
1185 self.ui.write(_("patch queue now empty\n"))
1185 self.ui.write(_("patch queue now empty\n"))
1186 finally:
1186 finally:
1187 wlock.release()
1187 wlock.release()
1188
1188
1189 def diff(self, repo, pats, opts):
1189 def diff(self, repo, pats, opts):
1190 top, patch = self.check_toppatch(repo)
1190 top, patch = self.check_toppatch(repo)
1191 if not top:
1191 if not top:
1192 self.ui.write(_("no patches applied\n"))
1192 self.ui.write(_("no patches applied\n"))
1193 return
1193 return
1194 qp = self.qparents(repo, top)
1194 qp = self.qparents(repo, top)
1195 if opts.get('reverse'):
1195 if opts.get('reverse'):
1196 node1, node2 = None, qp
1196 node1, node2 = None, qp
1197 else:
1197 else:
1198 node1, node2 = qp, None
1198 node1, node2 = qp, None
1199 diffopts = self.diffopts(opts, patch)
1199 diffopts = self.diffopts(opts, patch)
1200 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1200 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1201
1201
1202 def refresh(self, repo, pats=None, **opts):
1202 def refresh(self, repo, pats=None, **opts):
1203 if not self.applied:
1203 if not self.applied:
1204 self.ui.write(_("no patches applied\n"))
1204 self.ui.write(_("no patches applied\n"))
1205 return 1
1205 return 1
1206 msg = opts.get('msg', '').rstrip()
1206 msg = opts.get('msg', '').rstrip()
1207 newuser = opts.get('user')
1207 newuser = opts.get('user')
1208 newdate = opts.get('date')
1208 newdate = opts.get('date')
1209 if newdate:
1209 if newdate:
1210 newdate = '%d %d' % util.parsedate(newdate)
1210 newdate = '%d %d' % util.parsedate(newdate)
1211 wlock = repo.wlock()
1211 wlock = repo.wlock()
1212
1212
1213 try:
1213 try:
1214 self.check_toppatch(repo)
1214 self.check_toppatch(repo)
1215 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1215 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1216 if repo.changelog.heads(top) != [top]:
1216 if repo.changelog.heads(top) != [top]:
1217 raise util.Abort(_("cannot refresh a revision with children"))
1217 raise util.Abort(_("cannot refresh a revision with children"))
1218
1218
1219 cparents = repo.changelog.parents(top)
1219 cparents = repo.changelog.parents(top)
1220 patchparent = self.qparents(repo, top)
1220 patchparent = self.qparents(repo, top)
1221 ph = patchheader(self.join(patchfn), self.plainmode)
1221 ph = patchheader(self.join(patchfn), self.plainmode)
1222 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1222 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1223 if msg:
1223 if msg:
1224 ph.setmessage(msg)
1224 ph.setmessage(msg)
1225 if newuser:
1225 if newuser:
1226 ph.setuser(newuser)
1226 ph.setuser(newuser)
1227 if newdate:
1227 if newdate:
1228 ph.setdate(newdate)
1228 ph.setdate(newdate)
1229 ph.setparent(hex(patchparent))
1229 ph.setparent(hex(patchparent))
1230
1230
1231 # only commit new patch when write is complete
1231 # only commit new patch when write is complete
1232 patchf = self.opener(patchfn, 'w', atomictemp=True)
1232 patchf = self.opener(patchfn, 'w', atomictemp=True)
1233
1233
1234 comments = str(ph)
1234 comments = str(ph)
1235 if comments:
1235 if comments:
1236 patchf.write(comments)
1236 patchf.write(comments)
1237
1237
1238 # update the dirstate in place, strip off the qtip commit
1238 # update the dirstate in place, strip off the qtip commit
1239 # and then commit.
1239 # and then commit.
1240 #
1240 #
1241 # this should really read:
1241 # this should really read:
1242 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1242 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1243 # but we do it backwards to take advantage of manifest/chlog
1243 # but we do it backwards to take advantage of manifest/chlog
1244 # caching against the next repo.status call
1244 # caching against the next repo.status call
1245 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1245 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1246 changes = repo.changelog.read(top)
1246 changes = repo.changelog.read(top)
1247 man = repo.manifest.read(changes[0])
1247 man = repo.manifest.read(changes[0])
1248 aaa = aa[:]
1248 aaa = aa[:]
1249 matchfn = cmdutil.match(repo, pats, opts)
1249 matchfn = cmdutil.match(repo, pats, opts)
1250 # in short mode, we only diff the files included in the
1250 # in short mode, we only diff the files included in the
1251 # patch already plus specified files
1251 # patch already plus specified files
1252 if opts.get('short'):
1252 if opts.get('short'):
1253 # if amending a patch, we start with existing
1253 # if amending a patch, we start with existing
1254 # files plus specified files - unfiltered
1254 # files plus specified files - unfiltered
1255 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1255 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1256 # filter with inc/exl options
1256 # filter with inc/exl options
1257 matchfn = cmdutil.match(repo, opts=opts)
1257 matchfn = cmdutil.match(repo, opts=opts)
1258 else:
1258 else:
1259 match = cmdutil.matchall(repo)
1259 match = cmdutil.matchall(repo)
1260 m, a, r, d = repo.status(match=match)[:4]
1260 m, a, r, d = repo.status(match=match)[:4]
1261
1261
1262 # we might end up with files that were added between
1262 # we might end up with files that were added between
1263 # qtip and the dirstate parent, but then changed in the
1263 # qtip and the dirstate parent, but then changed in the
1264 # local dirstate. in this case, we want them to only
1264 # local dirstate. in this case, we want them to only
1265 # show up in the added section
1265 # show up in the added section
1266 for x in m:
1266 for x in m:
1267 if x not in aa:
1267 if x not in aa:
1268 mm.append(x)
1268 mm.append(x)
1269 # we might end up with files added by the local dirstate that
1269 # we might end up with files added by the local dirstate that
1270 # were deleted by the patch. In this case, they should only
1270 # were deleted by the patch. In this case, they should only
1271 # show up in the changed section.
1271 # show up in the changed section.
1272 for x in a:
1272 for x in a:
1273 if x in dd:
1273 if x in dd:
1274 del dd[dd.index(x)]
1274 del dd[dd.index(x)]
1275 mm.append(x)
1275 mm.append(x)
1276 else:
1276 else:
1277 aa.append(x)
1277 aa.append(x)
1278 # make sure any files deleted in the local dirstate
1278 # make sure any files deleted in the local dirstate
1279 # are not in the add or change column of the patch
1279 # are not in the add or change column of the patch
1280 forget = []
1280 forget = []
1281 for x in d + r:
1281 for x in d + r:
1282 if x in aa:
1282 if x in aa:
1283 del aa[aa.index(x)]
1283 del aa[aa.index(x)]
1284 forget.append(x)
1284 forget.append(x)
1285 continue
1285 continue
1286 elif x in mm:
1286 elif x in mm:
1287 del mm[mm.index(x)]
1287 del mm[mm.index(x)]
1288 dd.append(x)
1288 dd.append(x)
1289
1289
1290 m = list(set(mm))
1290 m = list(set(mm))
1291 r = list(set(dd))
1291 r = list(set(dd))
1292 a = list(set(aa))
1292 a = list(set(aa))
1293 c = [filter(matchfn, l) for l in (m, a, r)]
1293 c = [filter(matchfn, l) for l in (m, a, r)]
1294 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1294 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1295 chunks = patch.diff(repo, patchparent, match=match,
1295 chunks = patch.diff(repo, patchparent, match=match,
1296 changes=c, opts=diffopts)
1296 changes=c, opts=diffopts)
1297 for chunk in chunks:
1297 for chunk in chunks:
1298 patchf.write(chunk)
1298 patchf.write(chunk)
1299
1299
1300 try:
1300 try:
1301 if diffopts.git or diffopts.upgrade:
1301 if diffopts.git or diffopts.upgrade:
1302 copies = {}
1302 copies = {}
1303 for dst in a:
1303 for dst in a:
1304 src = repo.dirstate.copied(dst)
1304 src = repo.dirstate.copied(dst)
1305 # during qfold, the source file for copies may
1305 # during qfold, the source file for copies may
1306 # be removed. Treat this as a simple add.
1306 # be removed. Treat this as a simple add.
1307 if src is not None and src in repo.dirstate:
1307 if src is not None and src in repo.dirstate:
1308 copies.setdefault(src, []).append(dst)
1308 copies.setdefault(src, []).append(dst)
1309 repo.dirstate.add(dst)
1309 repo.dirstate.add(dst)
1310 # remember the copies between patchparent and qtip
1310 # remember the copies between patchparent and qtip
1311 for dst in aaa:
1311 for dst in aaa:
1312 f = repo.file(dst)
1312 f = repo.file(dst)
1313 src = f.renamed(man[dst])
1313 src = f.renamed(man[dst])
1314 if src:
1314 if src:
1315 copies.setdefault(src[0], []).extend(
1315 copies.setdefault(src[0], []).extend(
1316 copies.get(dst, []))
1316 copies.get(dst, []))
1317 if dst in a:
1317 if dst in a:
1318 copies[src[0]].append(dst)
1318 copies[src[0]].append(dst)
1319 # we can't copy a file created by the patch itself
1319 # we can't copy a file created by the patch itself
1320 if dst in copies:
1320 if dst in copies:
1321 del copies[dst]
1321 del copies[dst]
1322 for src, dsts in copies.iteritems():
1322 for src, dsts in copies.iteritems():
1323 for dst in dsts:
1323 for dst in dsts:
1324 repo.dirstate.copy(src, dst)
1324 repo.dirstate.copy(src, dst)
1325 else:
1325 else:
1326 for dst in a:
1326 for dst in a:
1327 repo.dirstate.add(dst)
1327 repo.dirstate.add(dst)
1328 # Drop useless copy information
1328 # Drop useless copy information
1329 for f in list(repo.dirstate.copies()):
1329 for f in list(repo.dirstate.copies()):
1330 repo.dirstate.copy(None, f)
1330 repo.dirstate.copy(None, f)
1331 for f in r:
1331 for f in r:
1332 repo.dirstate.remove(f)
1332 repo.dirstate.remove(f)
1333 # if the patch excludes a modified file, mark that
1333 # if the patch excludes a modified file, mark that
1334 # file with mtime=0 so status can see it.
1334 # file with mtime=0 so status can see it.
1335 mm = []
1335 mm = []
1336 for i in xrange(len(m)-1, -1, -1):
1336 for i in xrange(len(m)-1, -1, -1):
1337 if not matchfn(m[i]):
1337 if not matchfn(m[i]):
1338 mm.append(m[i])
1338 mm.append(m[i])
1339 del m[i]
1339 del m[i]
1340 for f in m:
1340 for f in m:
1341 repo.dirstate.normal(f)
1341 repo.dirstate.normal(f)
1342 for f in mm:
1342 for f in mm:
1343 repo.dirstate.normallookup(f)
1343 repo.dirstate.normallookup(f)
1344 for f in forget:
1344 for f in forget:
1345 repo.dirstate.forget(f)
1345 repo.dirstate.forget(f)
1346
1346
1347 if not msg:
1347 if not msg:
1348 if not ph.message:
1348 if not ph.message:
1349 message = "[mq]: %s\n" % patchfn
1349 message = "[mq]: %s\n" % patchfn
1350 else:
1350 else:
1351 message = "\n".join(ph.message)
1351 message = "\n".join(ph.message)
1352 else:
1352 else:
1353 message = msg
1353 message = msg
1354
1354
1355 user = ph.user or changes[1]
1355 user = ph.user or changes[1]
1356
1356
1357 # assumes strip can roll itself back if interrupted
1357 # assumes strip can roll itself back if interrupted
1358 repo.dirstate.setparents(*cparents)
1358 repo.dirstate.setparents(*cparents)
1359 self.applied.pop()
1359 self.applied.pop()
1360 self.applied_dirty = 1
1360 self.applied_dirty = 1
1361 self.strip(repo, top, update=False,
1361 self.strip(repo, top, update=False,
1362 backup='strip')
1362 backup='strip')
1363 except:
1363 except:
1364 repo.dirstate.invalidate()
1364 repo.dirstate.invalidate()
1365 raise
1365 raise
1366
1366
1367 try:
1367 try:
1368 # might be nice to attempt to roll back strip after this
1368 # might be nice to attempt to roll back strip after this
1369 patchf.rename()
1369 patchf.rename()
1370 n = repo.commit(message, user, ph.date, match=match,
1370 n = repo.commit(message, user, ph.date, match=match,
1371 force=True)
1371 force=True)
1372 self.applied.append(statusentry(n, patchfn))
1372 self.applied.append(statusentry(n, patchfn))
1373 except:
1373 except:
1374 ctx = repo[cparents[0]]
1374 ctx = repo[cparents[0]]
1375 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1375 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1376 self.save_dirty()
1376 self.save_dirty()
1377 self.ui.warn(_('refresh interrupted while patch was popped! '
1377 self.ui.warn(_('refresh interrupted while patch was popped! '
1378 '(revert --all, qpush to recover)\n'))
1378 '(revert --all, qpush to recover)\n'))
1379 raise
1379 raise
1380 finally:
1380 finally:
1381 wlock.release()
1381 wlock.release()
1382 self.removeundo(repo)
1382 self.removeundo(repo)
1383
1383
1384 def init(self, repo, create=False):
1384 def init(self, repo, create=False):
1385 if not create and os.path.isdir(self.path):
1385 if not create and os.path.isdir(self.path):
1386 raise util.Abort(_("patch queue directory already exists"))
1386 raise util.Abort(_("patch queue directory already exists"))
1387 try:
1387 try:
1388 os.mkdir(self.path)
1388 os.mkdir(self.path)
1389 except OSError, inst:
1389 except OSError, inst:
1390 if inst.errno != errno.EEXIST or not create:
1390 if inst.errno != errno.EEXIST or not create:
1391 raise
1391 raise
1392 if create:
1392 if create:
1393 return self.qrepo(create=True)
1393 return self.qrepo(create=True)
1394
1394
1395 def unapplied(self, repo, patch=None):
1395 def unapplied(self, repo, patch=None):
1396 if patch and patch not in self.series:
1396 if patch and patch not in self.series:
1397 raise util.Abort(_("patch %s is not in series file") % patch)
1397 raise util.Abort(_("patch %s is not in series file") % patch)
1398 if not patch:
1398 if not patch:
1399 start = self.series_end()
1399 start = self.series_end()
1400 else:
1400 else:
1401 start = self.series.index(patch) + 1
1401 start = self.series.index(patch) + 1
1402 unapplied = []
1402 unapplied = []
1403 for i in xrange(start, len(self.series)):
1403 for i in xrange(start, len(self.series)):
1404 pushable, reason = self.pushable(i)
1404 pushable, reason = self.pushable(i)
1405 if pushable:
1405 if pushable:
1406 unapplied.append((i, self.series[i]))
1406 unapplied.append((i, self.series[i]))
1407 self.explain_pushable(i)
1407 self.explain_pushable(i)
1408 return unapplied
1408 return unapplied
1409
1409
1410 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1410 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1411 summary=False):
1411 summary=False):
1412 def displayname(pfx, patchname):
1412 def displayname(pfx, patchname):
1413 if summary:
1413 if summary:
1414 ph = patchheader(self.join(patchname), self.plainmode)
1414 ph = patchheader(self.join(patchname), self.plainmode)
1415 msg = ph.message and ph.message[0] or ''
1415 msg = ph.message and ph.message[0] or ''
1416 if self.ui.interactive():
1416 if self.ui.interactive():
1417 width = util.termwidth() - len(pfx) - len(patchname) - 2
1417 width = util.termwidth() - len(pfx) - len(patchname) - 2
1418 if width > 0:
1418 if width > 0:
1419 msg = util.ellipsis(msg, width)
1419 msg = util.ellipsis(msg, width)
1420 else:
1420 else:
1421 msg = ''
1421 msg = ''
1422 msg = "%s%s: %s" % (pfx, patchname, msg)
1422 msg = "%s%s: %s" % (pfx, patchname, msg)
1423 else:
1423 else:
1424 msg = pfx + patchname
1424 msg = pfx + patchname
1425 self.ui.write(msg + '\n')
1425 self.ui.write(msg + '\n')
1426
1426
1427 applied = set([p.name for p in self.applied])
1427 applied = set([p.name for p in self.applied])
1428 if length is None:
1428 if length is None:
1429 length = len(self.series) - start
1429 length = len(self.series) - start
1430 if not missing:
1430 if not missing:
1431 if self.ui.verbose:
1431 if self.ui.verbose:
1432 idxwidth = len(str(start + length - 1))
1432 idxwidth = len(str(start + length - 1))
1433 for i in xrange(start, start + length):
1433 for i in xrange(start, start + length):
1434 patch = self.series[i]
1434 patch = self.series[i]
1435 if patch in applied:
1435 if patch in applied:
1436 stat = 'A'
1436 stat = 'A'
1437 elif self.pushable(i)[0]:
1437 elif self.pushable(i)[0]:
1438 stat = 'U'
1438 stat = 'U'
1439 else:
1439 else:
1440 stat = 'G'
1440 stat = 'G'
1441 pfx = ''
1441 pfx = ''
1442 if self.ui.verbose:
1442 if self.ui.verbose:
1443 pfx = '%*d %s ' % (idxwidth, i, stat)
1443 pfx = '%*d %s ' % (idxwidth, i, stat)
1444 elif status and status != stat:
1444 elif status and status != stat:
1445 continue
1445 continue
1446 displayname(pfx, patch)
1446 displayname(pfx, patch)
1447 else:
1447 else:
1448 msng_list = []
1448 msng_list = []
1449 for root, dirs, files in os.walk(self.path):
1449 for root, dirs, files in os.walk(self.path):
1450 d = root[len(self.path) + 1:]
1450 d = root[len(self.path) + 1:]
1451 for f in files:
1451 for f in files:
1452 fl = os.path.join(d, f)
1452 fl = os.path.join(d, f)
1453 if (fl not in self.series and
1453 if (fl not in self.series and
1454 fl not in (self.status_path, self.series_path,
1454 fl not in (self.status_path, self.series_path,
1455 self.guards_path)
1455 self.guards_path)
1456 and not fl.startswith('.')):
1456 and not fl.startswith('.')):
1457 msng_list.append(fl)
1457 msng_list.append(fl)
1458 for x in sorted(msng_list):
1458 for x in sorted(msng_list):
1459 pfx = self.ui.verbose and ('D ') or ''
1459 pfx = self.ui.verbose and ('D ') or ''
1460 displayname(pfx, x)
1460 displayname(pfx, x)
1461
1461
1462 def issaveline(self, l):
1462 def issaveline(self, l):
1463 if l.name == '.hg.patches.save.line':
1463 if l.name == '.hg.patches.save.line':
1464 return True
1464 return True
1465
1465
1466 def qrepo(self, create=False):
1466 def qrepo(self, create=False):
1467 if create or os.path.isdir(self.join(".hg")):
1467 if create or os.path.isdir(self.join(".hg")):
1468 return hg.repository(self.ui, path=self.path, create=create)
1468 return hg.repository(self.ui, path=self.path, create=create)
1469
1469
1470 def restore(self, repo, rev, delete=None, qupdate=None):
1470 def restore(self, repo, rev, delete=None, qupdate=None):
1471 desc = repo[rev].description().strip()
1471 desc = repo[rev].description().strip()
1472 lines = desc.splitlines()
1472 lines = desc.splitlines()
1473 i = 0
1473 i = 0
1474 datastart = None
1474 datastart = None
1475 series = []
1475 series = []
1476 applied = []
1476 applied = []
1477 qpp = None
1477 qpp = None
1478 for i, line in enumerate(lines):
1478 for i, line in enumerate(lines):
1479 if line == 'Patch Data:':
1479 if line == 'Patch Data:':
1480 datastart = i + 1
1480 datastart = i + 1
1481 elif line.startswith('Dirstate:'):
1481 elif line.startswith('Dirstate:'):
1482 l = line.rstrip()
1482 l = line.rstrip()
1483 l = l[10:].split(' ')
1483 l = l[10:].split(' ')
1484 qpp = [bin(x) for x in l]
1484 qpp = [bin(x) for x in l]
1485 elif datastart != None:
1485 elif datastart != None:
1486 l = line.rstrip()
1486 l = line.rstrip()
1487 n, name = l.split(':', 1)
1487 n, name = l.split(':', 1)
1488 if n:
1488 if n:
1489 applied.append(statusentry(bin(n), name))
1489 applied.append(statusentry(bin(n), name))
1490 else:
1490 else:
1491 series.append(l)
1491 series.append(l)
1492 if datastart is None:
1492 if datastart is None:
1493 self.ui.warn(_("No saved patch data found\n"))
1493 self.ui.warn(_("No saved patch data found\n"))
1494 return 1
1494 return 1
1495 self.ui.warn(_("restoring status: %s\n") % lines[0])
1495 self.ui.warn(_("restoring status: %s\n") % lines[0])
1496 self.full_series = series
1496 self.full_series = series
1497 self.applied = applied
1497 self.applied = applied
1498 self.parse_series()
1498 self.parse_series()
1499 self.series_dirty = 1
1499 self.series_dirty = 1
1500 self.applied_dirty = 1
1500 self.applied_dirty = 1
1501 heads = repo.changelog.heads()
1501 heads = repo.changelog.heads()
1502 if delete:
1502 if delete:
1503 if rev not in heads:
1503 if rev not in heads:
1504 self.ui.warn(_("save entry has children, leaving it alone\n"))
1504 self.ui.warn(_("save entry has children, leaving it alone\n"))
1505 else:
1505 else:
1506 self.ui.warn(_("removing save entry %s\n") % short(rev))
1506 self.ui.warn(_("removing save entry %s\n") % short(rev))
1507 pp = repo.dirstate.parents()
1507 pp = repo.dirstate.parents()
1508 if rev in pp:
1508 if rev in pp:
1509 update = True
1509 update = True
1510 else:
1510 else:
1511 update = False
1511 update = False
1512 self.strip(repo, rev, update=update, backup='strip')
1512 self.strip(repo, rev, update=update, backup='strip')
1513 if qpp:
1513 if qpp:
1514 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1514 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1515 (short(qpp[0]), short(qpp[1])))
1515 (short(qpp[0]), short(qpp[1])))
1516 if qupdate:
1516 if qupdate:
1517 self.ui.status(_("queue directory updating\n"))
1517 self.ui.status(_("queue directory updating\n"))
1518 r = self.qrepo()
1518 r = self.qrepo()
1519 if not r:
1519 if not r:
1520 self.ui.warn(_("Unable to load queue repository\n"))
1520 self.ui.warn(_("Unable to load queue repository\n"))
1521 return 1
1521 return 1
1522 hg.clean(r, qpp[0])
1522 hg.clean(r, qpp[0])
1523
1523
1524 def save(self, repo, msg=None):
1524 def save(self, repo, msg=None):
1525 if not self.applied:
1525 if not self.applied:
1526 self.ui.warn(_("save: no patches applied, exiting\n"))
1526 self.ui.warn(_("save: no patches applied, exiting\n"))
1527 return 1
1527 return 1
1528 if self.issaveline(self.applied[-1]):
1528 if self.issaveline(self.applied[-1]):
1529 self.ui.warn(_("status is already saved\n"))
1529 self.ui.warn(_("status is already saved\n"))
1530 return 1
1530 return 1
1531
1531
1532 if not msg:
1532 if not msg:
1533 msg = _("hg patches saved state")
1533 msg = _("hg patches saved state")
1534 else:
1534 else:
1535 msg = "hg patches: " + msg.rstrip('\r\n')
1535 msg = "hg patches: " + msg.rstrip('\r\n')
1536 r = self.qrepo()
1536 r = self.qrepo()
1537 if r:
1537 if r:
1538 pp = r.dirstate.parents()
1538 pp = r.dirstate.parents()
1539 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1539 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1540 msg += "\n\nPatch Data:\n"
1540 msg += "\n\nPatch Data:\n"
1541 msg += ''.join('%s\n' % x for x in self.applied)
1541 msg += ''.join('%s\n' % x for x in self.applied)
1542 msg += ''.join(':%s\n' % x for x in self.full_series)
1542 msg += ''.join(':%s\n' % x for x in self.full_series)
1543 n = repo.commit(msg, force=True)
1543 n = repo.commit(msg, force=True)
1544 if not n:
1544 if not n:
1545 self.ui.warn(_("repo commit failed\n"))
1545 self.ui.warn(_("repo commit failed\n"))
1546 return 1
1546 return 1
1547 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1547 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1548 self.applied_dirty = 1
1548 self.applied_dirty = 1
1549 self.removeundo(repo)
1549 self.removeundo(repo)
1550
1550
1551 def full_series_end(self):
1551 def full_series_end(self):
1552 if self.applied:
1552 if self.applied:
1553 p = self.applied[-1].name
1553 p = self.applied[-1].name
1554 end = self.find_series(p)
1554 end = self.find_series(p)
1555 if end is None:
1555 if end is None:
1556 return len(self.full_series)
1556 return len(self.full_series)
1557 return end + 1
1557 return end + 1
1558 return 0
1558 return 0
1559
1559
1560 def series_end(self, all_patches=False):
1560 def series_end(self, all_patches=False):
1561 """If all_patches is False, return the index of the next pushable patch
1561 """If all_patches is False, return the index of the next pushable patch
1562 in the series, or the series length. If all_patches is True, return the
1562 in the series, or the series length. If all_patches is True, return the
1563 index of the first patch past the last applied one.
1563 index of the first patch past the last applied one.
1564 """
1564 """
1565 end = 0
1565 end = 0
1566 def next(start):
1566 def next(start):
1567 if all_patches or start >= len(self.series):
1567 if all_patches or start >= len(self.series):
1568 return start
1568 return start
1569 for i in xrange(start, len(self.series)):
1569 for i in xrange(start, len(self.series)):
1570 p, reason = self.pushable(i)
1570 p, reason = self.pushable(i)
1571 if p:
1571 if p:
1572 break
1572 break
1573 self.explain_pushable(i)
1573 self.explain_pushable(i)
1574 return i
1574 return i
1575 if self.applied:
1575 if self.applied:
1576 p = self.applied[-1].name
1576 p = self.applied[-1].name
1577 try:
1577 try:
1578 end = self.series.index(p)
1578 end = self.series.index(p)
1579 except ValueError:
1579 except ValueError:
1580 return 0
1580 return 0
1581 return next(end + 1)
1581 return next(end + 1)
1582 return next(end)
1582 return next(end)
1583
1583
1584 def appliedname(self, index):
1584 def appliedname(self, index):
1585 pname = self.applied[index].name
1585 pname = self.applied[index].name
1586 if not self.ui.verbose:
1586 if not self.ui.verbose:
1587 p = pname
1587 p = pname
1588 else:
1588 else:
1589 p = str(self.series.index(pname)) + " " + pname
1589 p = str(self.series.index(pname)) + " " + pname
1590 return p
1590 return p
1591
1591
1592 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1592 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1593 force=None, git=False):
1593 force=None, git=False):
1594 def checkseries(patchname):
1594 def checkseries(patchname):
1595 if patchname in self.series:
1595 if patchname in self.series:
1596 raise util.Abort(_('patch %s is already in the series file')
1596 raise util.Abort(_('patch %s is already in the series file')
1597 % patchname)
1597 % patchname)
1598 def checkfile(patchname):
1598 def checkfile(patchname):
1599 if not force and os.path.exists(self.join(patchname)):
1599 if not force and os.path.exists(self.join(patchname)):
1600 raise util.Abort(_('patch "%s" already exists')
1600 raise util.Abort(_('patch "%s" already exists')
1601 % patchname)
1601 % patchname)
1602
1602
1603 if rev:
1603 if rev:
1604 if files:
1604 if files:
1605 raise util.Abort(_('option "-r" not valid when importing '
1605 raise util.Abort(_('option "-r" not valid when importing '
1606 'files'))
1606 'files'))
1607 rev = cmdutil.revrange(repo, rev)
1607 rev = cmdutil.revrange(repo, rev)
1608 rev.sort(reverse=True)
1608 rev.sort(reverse=True)
1609 if (len(files) > 1 or len(rev) > 1) and patchname:
1609 if (len(files) > 1 or len(rev) > 1) and patchname:
1610 raise util.Abort(_('option "-n" not valid when importing multiple '
1610 raise util.Abort(_('option "-n" not valid when importing multiple '
1611 'patches'))
1611 'patches'))
1612 added = []
1612 added = []
1613 if rev:
1613 if rev:
1614 # If mq patches are applied, we can only import revisions
1614 # If mq patches are applied, we can only import revisions
1615 # that form a linear path to qbase.
1615 # that form a linear path to qbase.
1616 # Otherwise, they should form a linear path to a head.
1616 # Otherwise, they should form a linear path to a head.
1617 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1617 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1618 if len(heads) > 1:
1618 if len(heads) > 1:
1619 raise util.Abort(_('revision %d is the root of more than one '
1619 raise util.Abort(_('revision %d is the root of more than one '
1620 'branch') % rev[-1])
1620 'branch') % rev[-1])
1621 if self.applied:
1621 if self.applied:
1622 base = repo.changelog.node(rev[0])
1622 base = repo.changelog.node(rev[0])
1623 if base in [n.node for n in self.applied]:
1623 if base in [n.node for n in self.applied]:
1624 raise util.Abort(_('revision %d is already managed')
1624 raise util.Abort(_('revision %d is already managed')
1625 % rev[0])
1625 % rev[0])
1626 if heads != [self.applied[-1].node]:
1626 if heads != [self.applied[-1].node]:
1627 raise util.Abort(_('revision %d is not the parent of '
1627 raise util.Abort(_('revision %d is not the parent of '
1628 'the queue') % rev[0])
1628 'the queue') % rev[0])
1629 base = repo.changelog.rev(self.applied[0].node)
1629 base = repo.changelog.rev(self.applied[0].node)
1630 lastparent = repo.changelog.parentrevs(base)[0]
1630 lastparent = repo.changelog.parentrevs(base)[0]
1631 else:
1631 else:
1632 if heads != [repo.changelog.node(rev[0])]:
1632 if heads != [repo.changelog.node(rev[0])]:
1633 raise util.Abort(_('revision %d has unmanaged children')
1633 raise util.Abort(_('revision %d has unmanaged children')
1634 % rev[0])
1634 % rev[0])
1635 lastparent = None
1635 lastparent = None
1636
1636
1637 diffopts = self.diffopts({'git': git})
1637 diffopts = self.diffopts({'git': git})
1638 for r in rev:
1638 for r in rev:
1639 p1, p2 = repo.changelog.parentrevs(r)
1639 p1, p2 = repo.changelog.parentrevs(r)
1640 n = repo.changelog.node(r)
1640 n = repo.changelog.node(r)
1641 if p2 != nullrev:
1641 if p2 != nullrev:
1642 raise util.Abort(_('cannot import merge revision %d') % r)
1642 raise util.Abort(_('cannot import merge revision %d') % r)
1643 if lastparent and lastparent != r:
1643 if lastparent and lastparent != r:
1644 raise util.Abort(_('revision %d is not the parent of %d')
1644 raise util.Abort(_('revision %d is not the parent of %d')
1645 % (r, lastparent))
1645 % (r, lastparent))
1646 lastparent = p1
1646 lastparent = p1
1647
1647
1648 if not patchname:
1648 if not patchname:
1649 patchname = normname('%d.diff' % r)
1649 patchname = normname('%d.diff' % r)
1650 self.check_reserved_name(patchname)
1650 self.check_reserved_name(patchname)
1651 checkseries(patchname)
1651 checkseries(patchname)
1652 checkfile(patchname)
1652 checkfile(patchname)
1653 self.full_series.insert(0, patchname)
1653 self.full_series.insert(0, patchname)
1654
1654
1655 patchf = self.opener(patchname, "w")
1655 patchf = self.opener(patchname, "w")
1656 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1656 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1657 patchf.close()
1657 patchf.close()
1658
1658
1659 se = statusentry(n, patchname)
1659 se = statusentry(n, patchname)
1660 self.applied.insert(0, se)
1660 self.applied.insert(0, se)
1661
1661
1662 added.append(patchname)
1662 added.append(patchname)
1663 patchname = None
1663 patchname = None
1664 self.parse_series()
1664 self.parse_series()
1665 self.applied_dirty = 1
1665 self.applied_dirty = 1
1666
1666
1667 for i, filename in enumerate(files):
1667 for i, filename in enumerate(files):
1668 if existing:
1668 if existing:
1669 if filename == '-':
1669 if filename == '-':
1670 raise util.Abort(_('-e is incompatible with import from -'))
1670 raise util.Abort(_('-e is incompatible with import from -'))
1671 if not patchname:
1671 if not patchname:
1672 patchname = normname(filename)
1672 patchname = normname(filename)
1673 self.check_reserved_name(patchname)
1673 self.check_reserved_name(patchname)
1674 if not os.path.isfile(self.join(patchname)):
1674 if not os.path.isfile(self.join(patchname)):
1675 raise util.Abort(_("patch %s does not exist") % patchname)
1675 raise util.Abort(_("patch %s does not exist") % patchname)
1676 else:
1676 else:
1677 try:
1677 try:
1678 if filename == '-':
1678 if filename == '-':
1679 if not patchname:
1679 if not patchname:
1680 raise util.Abort(
1680 raise util.Abort(
1681 _('need --name to import a patch from -'))
1681 _('need --name to import a patch from -'))
1682 text = sys.stdin.read()
1682 text = sys.stdin.read()
1683 else:
1683 else:
1684 text = url.open(self.ui, filename).read()
1684 text = url.open(self.ui, filename).read()
1685 except (OSError, IOError):
1685 except (OSError, IOError):
1686 raise util.Abort(_("unable to read %s") % filename)
1686 raise util.Abort(_("unable to read %s") % filename)
1687 if not patchname:
1687 if not patchname:
1688 patchname = normname(os.path.basename(filename))
1688 patchname = normname(os.path.basename(filename))
1689 self.check_reserved_name(patchname)
1689 self.check_reserved_name(patchname)
1690 checkfile(patchname)
1690 checkfile(patchname)
1691 patchf = self.opener(patchname, "w")
1691 patchf = self.opener(patchname, "w")
1692 patchf.write(text)
1692 patchf.write(text)
1693 if not force:
1693 if not force:
1694 checkseries(patchname)
1694 checkseries(patchname)
1695 if patchname not in self.series:
1695 if patchname not in self.series:
1696 index = self.full_series_end() + i
1696 index = self.full_series_end() + i
1697 self.full_series[index:index] = [patchname]
1697 self.full_series[index:index] = [patchname]
1698 self.parse_series()
1698 self.parse_series()
1699 self.ui.warn(_("adding %s to series file\n") % patchname)
1699 self.ui.warn(_("adding %s to series file\n") % patchname)
1700 added.append(patchname)
1700 added.append(patchname)
1701 patchname = None
1701 patchname = None
1702 self.series_dirty = 1
1702 self.series_dirty = 1
1703 qrepo = self.qrepo()
1703 qrepo = self.qrepo()
1704 if qrepo:
1704 if qrepo:
1705 qrepo.add(added)
1705 qrepo.add(added)
1706
1706
1707 def delete(ui, repo, *patches, **opts):
1707 def delete(ui, repo, *patches, **opts):
1708 """remove patches from queue
1708 """remove patches from queue
1709
1709
1710 The patches must not be applied, and at least one patch is required. With
1710 The patches must not be applied, and at least one patch is required. With
1711 -k/--keep, the patch files are preserved in the patch directory.
1711 -k/--keep, the patch files are preserved in the patch directory.
1712
1712
1713 To stop managing a patch and move it into permanent history,
1713 To stop managing a patch and move it into permanent history,
1714 use the qfinish command."""
1714 use the qfinish command."""
1715 q = repo.mq
1715 q = repo.mq
1716 q.delete(repo, patches, opts)
1716 q.delete(repo, patches, opts)
1717 q.save_dirty()
1717 q.save_dirty()
1718 return 0
1718 return 0
1719
1719
1720 def applied(ui, repo, patch=None, **opts):
1720 def applied(ui, repo, patch=None, **opts):
1721 """print the patches already applied"""
1721 """print the patches already applied"""
1722
1722
1723 q = repo.mq
1723 q = repo.mq
1724 l = len(q.applied)
1724 l = len(q.applied)
1725
1725
1726 if patch:
1726 if patch:
1727 if patch not in q.series:
1727 if patch not in q.series:
1728 raise util.Abort(_("patch %s is not in series file") % patch)
1728 raise util.Abort(_("patch %s is not in series file") % patch)
1729 end = q.series.index(patch) + 1
1729 end = q.series.index(patch) + 1
1730 else:
1730 else:
1731 end = q.series_end(True)
1731 end = q.series_end(True)
1732
1732
1733 if opts.get('last') and not end:
1733 if opts.get('last') and not end:
1734 ui.write(_("no patches applied\n"))
1734 ui.write(_("no patches applied\n"))
1735 return 1
1735 return 1
1736 elif opts.get('last') and end == 1:
1736 elif opts.get('last') and end == 1:
1737 ui.write(_("only one patch applied\n"))
1737 ui.write(_("only one patch applied\n"))
1738 return 1
1738 return 1
1739 elif opts.get('last'):
1739 elif opts.get('last'):
1740 start = end - 2
1740 start = end - 2
1741 end = 1
1741 end = 1
1742 else:
1742 else:
1743 start = 0
1743 start = 0
1744
1744
1745 return q.qseries(repo, length=end, start=start, status='A',
1745 return q.qseries(repo, length=end, start=start, status='A',
1746 summary=opts.get('summary'))
1746 summary=opts.get('summary'))
1747
1747
1748 def unapplied(ui, repo, patch=None, **opts):
1748 def unapplied(ui, repo, patch=None, **opts):
1749 """print the patches not yet applied"""
1749 """print the patches not yet applied"""
1750
1750
1751 q = repo.mq
1751 q = repo.mq
1752 if patch:
1752 if patch:
1753 if patch not in q.series:
1753 if patch not in q.series:
1754 raise util.Abort(_("patch %s is not in series file") % patch)
1754 raise util.Abort(_("patch %s is not in series file") % patch)
1755 start = q.series.index(patch) + 1
1755 start = q.series.index(patch) + 1
1756 else:
1756 else:
1757 start = q.series_end(True)
1757 start = q.series_end(True)
1758
1758
1759 if start == len(q.series) and opts.get('first'):
1759 if start == len(q.series) and opts.get('first'):
1760 ui.write(_("all patches applied\n"))
1760 ui.write(_("all patches applied\n"))
1761 return 1
1761 return 1
1762
1762
1763 length = opts.get('first') and 1 or None
1763 length = opts.get('first') and 1 or None
1764 return q.qseries(repo, start=start, length=length, status='U',
1764 return q.qseries(repo, start=start, length=length, status='U',
1765 summary=opts.get('summary'))
1765 summary=opts.get('summary'))
1766
1766
1767 def qimport(ui, repo, *filename, **opts):
1767 def qimport(ui, repo, *filename, **opts):
1768 """import a patch
1768 """import a patch
1769
1769
1770 The patch is inserted into the series after the last applied
1770 The patch is inserted into the series after the last applied
1771 patch. If no patches have been applied, qimport prepends the patch
1771 patch. If no patches have been applied, qimport prepends the patch
1772 to the series.
1772 to the series.
1773
1773
1774 The patch will have the same name as its source file unless you
1774 The patch will have the same name as its source file unless you
1775 give it a new one with -n/--name.
1775 give it a new one with -n/--name.
1776
1776
1777 You can register an existing patch inside the patch directory with
1777 You can register an existing patch inside the patch directory with
1778 the -e/--existing flag.
1778 the -e/--existing flag.
1779
1779
1780 With -f/--force, an existing patch of the same name will be
1780 With -f/--force, an existing patch of the same name will be
1781 overwritten.
1781 overwritten.
1782
1782
1783 An existing changeset may be placed under mq control with -r/--rev
1783 An existing changeset may be placed under mq control with -r/--rev
1784 (e.g. qimport --rev tip -n patch will place tip under mq control).
1784 (e.g. qimport --rev tip -n patch will place tip under mq control).
1785 With -g/--git, patches imported with --rev will use the git diff
1785 With -g/--git, patches imported with --rev will use the git diff
1786 format. See the diffs help topic for information on why this is
1786 format. See the diffs help topic for information on why this is
1787 important for preserving rename/copy information and permission
1787 important for preserving rename/copy information and permission
1788 changes.
1788 changes.
1789
1789
1790 To import a patch from standard input, pass - as the patch file.
1790 To import a patch from standard input, pass - as the patch file.
1791 When importing from standard input, a patch name must be specified
1791 When importing from standard input, a patch name must be specified
1792 using the --name flag.
1792 using the --name flag.
1793 """
1793 """
1794 q = repo.mq
1794 q = repo.mq
1795 q.qimport(repo, filename, patchname=opts['name'],
1795 q.qimport(repo, filename, patchname=opts['name'],
1796 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1796 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1797 git=opts['git'])
1797 git=opts['git'])
1798 q.save_dirty()
1798 q.save_dirty()
1799
1799
1800 if opts.get('push') and not opts.get('rev'):
1800 if opts.get('push') and not opts.get('rev'):
1801 return q.push(repo, None)
1801 return q.push(repo, None)
1802 return 0
1802 return 0
1803
1803
1804 def qinit(ui, repo, create):
1804 def qinit(ui, repo, create):
1805 """initialize a new queue repository
1805 """initialize a new queue repository
1806
1806
1807 This command also creates a series file for ordering patches, and
1807 This command also creates a series file for ordering patches, and
1808 an mq-specific .hgignore file in the queue repository, to exclude
1808 an mq-specific .hgignore file in the queue repository, to exclude
1809 the status and guards files (these contain mostly transient state)."""
1809 the status and guards files (these contain mostly transient state)."""
1810 q = repo.mq
1810 q = repo.mq
1811 r = q.init(repo, create)
1811 r = q.init(repo, create)
1812 q.save_dirty()
1812 q.save_dirty()
1813 if r:
1813 if r:
1814 if not os.path.exists(r.wjoin('.hgignore')):
1814 if not os.path.exists(r.wjoin('.hgignore')):
1815 fp = r.wopener('.hgignore', 'w')
1815 fp = r.wopener('.hgignore', 'w')
1816 fp.write('^\\.hg\n')
1816 fp.write('^\\.hg\n')
1817 fp.write('^\\.mq\n')
1817 fp.write('^\\.mq\n')
1818 fp.write('syntax: glob\n')
1818 fp.write('syntax: glob\n')
1819 fp.write('status\n')
1819 fp.write('status\n')
1820 fp.write('guards\n')
1820 fp.write('guards\n')
1821 fp.close()
1821 fp.close()
1822 if not os.path.exists(r.wjoin('series')):
1822 if not os.path.exists(r.wjoin('series')):
1823 r.wopener('series', 'w').close()
1823 r.wopener('series', 'w').close()
1824 r.add(['.hgignore', 'series'])
1824 r.add(['.hgignore', 'series'])
1825 commands.add(ui, r)
1825 commands.add(ui, r)
1826 return 0
1826 return 0
1827
1827
1828 def init(ui, repo, **opts):
1828 def init(ui, repo, **opts):
1829 """init a new queue repository (DEPRECATED)
1829 """init a new queue repository (DEPRECATED)
1830
1830
1831 The queue repository is unversioned by default. If
1831 The queue repository is unversioned by default. If
1832 -c/--create-repo is specified, qinit will create a separate nested
1832 -c/--create-repo is specified, qinit will create a separate nested
1833 repository for patches (qinit -c may also be run later to convert
1833 repository for patches (qinit -c may also be run later to convert
1834 an unversioned patch repository into a versioned one). You can use
1834 an unversioned patch repository into a versioned one). You can use
1835 qcommit to commit changes to this queue repository.
1835 qcommit to commit changes to this queue repository.
1836
1836
1837 This command is deprecated. Without -c, it's implied by other relevant
1837 This command is deprecated. Without -c, it's implied by other relevant
1838 commands. With -c, use hg init --mq instead."""
1838 commands. With -c, use hg init --mq instead."""
1839 return qinit(ui, repo, create=opts['create_repo'])
1839 return qinit(ui, repo, create=opts['create_repo'])
1840
1840
1841 def clone(ui, source, dest=None, **opts):
1841 def clone(ui, source, dest=None, **opts):
1842 '''clone main and patch repository at same time
1842 '''clone main and patch repository at same time
1843
1843
1844 If source is local, destination will have no patches applied. If
1844 If source is local, destination will have no patches applied. If
1845 source is remote, this command can not check if patches are
1845 source is remote, this command can not check if patches are
1846 applied in source, so cannot guarantee that patches are not
1846 applied in source, so cannot guarantee that patches are not
1847 applied in destination. If you clone remote repository, be sure
1847 applied in destination. If you clone remote repository, be sure
1848 before that it has no patches applied.
1848 before that it has no patches applied.
1849
1849
1850 Source patch repository is looked for in <src>/.hg/patches by
1850 Source patch repository is looked for in <src>/.hg/patches by
1851 default. Use -p <url> to change.
1851 default. Use -p <url> to change.
1852
1852
1853 The patch directory must be a nested Mercurial repository, as
1853 The patch directory must be a nested Mercurial repository, as
1854 would be created by init --mq.
1854 would be created by init --mq.
1855 '''
1855 '''
1856 def patchdir(repo):
1856 def patchdir(repo):
1857 url = repo.url()
1857 url = repo.url()
1858 if url.endswith('/'):
1858 if url.endswith('/'):
1859 url = url[:-1]
1859 url = url[:-1]
1860 return url + '/.hg/patches'
1860 return url + '/.hg/patches'
1861 if dest is None:
1861 if dest is None:
1862 dest = hg.defaultdest(source)
1862 dest = hg.defaultdest(source)
1863 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1863 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1864 if opts['patches']:
1864 if opts['patches']:
1865 patchespath = ui.expandpath(opts['patches'])
1865 patchespath = ui.expandpath(opts['patches'])
1866 else:
1866 else:
1867 patchespath = patchdir(sr)
1867 patchespath = patchdir(sr)
1868 try:
1868 try:
1869 hg.repository(ui, patchespath)
1869 hg.repository(ui, patchespath)
1870 except error.RepoError:
1870 except error.RepoError:
1871 raise util.Abort(_('versioned patch repository not found'
1871 raise util.Abort(_('versioned patch repository not found'
1872 ' (see init --mq)'))
1872 ' (see init --mq)'))
1873 qbase, destrev = None, None
1873 qbase, destrev = None, None
1874 if sr.local():
1874 if sr.local():
1875 if sr.mq.applied:
1875 if sr.mq.applied:
1876 qbase = sr.mq.applied[0].node
1876 qbase = sr.mq.applied[0].node
1877 if not hg.islocal(dest):
1877 if not hg.islocal(dest):
1878 heads = set(sr.heads())
1878 heads = set(sr.heads())
1879 destrev = list(heads.difference(sr.heads(qbase)))
1879 destrev = list(heads.difference(sr.heads(qbase)))
1880 destrev.append(sr.changelog.parents(qbase)[0])
1880 destrev.append(sr.changelog.parents(qbase)[0])
1881 elif sr.capable('lookup'):
1881 elif sr.capable('lookup'):
1882 try:
1882 try:
1883 qbase = sr.lookup('qbase')
1883 qbase = sr.lookup('qbase')
1884 except error.RepoError:
1884 except error.RepoError:
1885 pass
1885 pass
1886 ui.note(_('cloning main repository\n'))
1886 ui.note(_('cloning main repository\n'))
1887 sr, dr = hg.clone(ui, sr.url(), dest,
1887 sr, dr = hg.clone(ui, sr.url(), dest,
1888 pull=opts['pull'],
1888 pull=opts['pull'],
1889 rev=destrev,
1889 rev=destrev,
1890 update=False,
1890 update=False,
1891 stream=opts['uncompressed'])
1891 stream=opts['uncompressed'])
1892 ui.note(_('cloning patch repository\n'))
1892 ui.note(_('cloning patch repository\n'))
1893 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1893 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1894 pull=opts['pull'], update=not opts['noupdate'],
1894 pull=opts['pull'], update=not opts['noupdate'],
1895 stream=opts['uncompressed'])
1895 stream=opts['uncompressed'])
1896 if dr.local():
1896 if dr.local():
1897 if qbase:
1897 if qbase:
1898 ui.note(_('stripping applied patches from destination '
1898 ui.note(_('stripping applied patches from destination '
1899 'repository\n'))
1899 'repository\n'))
1900 dr.mq.strip(dr, qbase, update=False, backup=None)
1900 dr.mq.strip(dr, qbase, update=False, backup=None)
1901 if not opts['noupdate']:
1901 if not opts['noupdate']:
1902 ui.note(_('updating destination repository\n'))
1902 ui.note(_('updating destination repository\n'))
1903 hg.update(dr, dr.changelog.tip())
1903 hg.update(dr, dr.changelog.tip())
1904
1904
1905 def commit(ui, repo, *pats, **opts):
1905 def commit(ui, repo, *pats, **opts):
1906 """commit changes in the queue repository (DEPRECATED)
1906 """commit changes in the queue repository (DEPRECATED)
1907
1907
1908 This command is deprecated; use hg --mq commit instead."""
1908 This command is deprecated; use hg commit --mq instead."""
1909 q = repo.mq
1909 q = repo.mq
1910 r = q.qrepo()
1910 r = q.qrepo()
1911 if not r:
1911 if not r:
1912 raise util.Abort('no queue repository')
1912 raise util.Abort('no queue repository')
1913 commands.commit(r.ui, r, *pats, **opts)
1913 commands.commit(r.ui, r, *pats, **opts)
1914
1914
1915 def series(ui, repo, **opts):
1915 def series(ui, repo, **opts):
1916 """print the entire series file"""
1916 """print the entire series file"""
1917 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1917 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1918 return 0
1918 return 0
1919
1919
1920 def top(ui, repo, **opts):
1920 def top(ui, repo, **opts):
1921 """print the name of the current patch"""
1921 """print the name of the current patch"""
1922 q = repo.mq
1922 q = repo.mq
1923 t = q.applied and q.series_end(True) or 0
1923 t = q.applied and q.series_end(True) or 0
1924 if t:
1924 if t:
1925 return q.qseries(repo, start=t - 1, length=1, status='A',
1925 return q.qseries(repo, start=t - 1, length=1, status='A',
1926 summary=opts.get('summary'))
1926 summary=opts.get('summary'))
1927 else:
1927 else:
1928 ui.write(_("no patches applied\n"))
1928 ui.write(_("no patches applied\n"))
1929 return 1
1929 return 1
1930
1930
1931 def next(ui, repo, **opts):
1931 def next(ui, repo, **opts):
1932 """print the name of the next patch"""
1932 """print the name of the next patch"""
1933 q = repo.mq
1933 q = repo.mq
1934 end = q.series_end()
1934 end = q.series_end()
1935 if end == len(q.series):
1935 if end == len(q.series):
1936 ui.write(_("all patches applied\n"))
1936 ui.write(_("all patches applied\n"))
1937 return 1
1937 return 1
1938 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1938 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1939
1939
1940 def prev(ui, repo, **opts):
1940 def prev(ui, repo, **opts):
1941 """print the name of the previous patch"""
1941 """print the name of the previous patch"""
1942 q = repo.mq
1942 q = repo.mq
1943 l = len(q.applied)
1943 l = len(q.applied)
1944 if l == 1:
1944 if l == 1:
1945 ui.write(_("only one patch applied\n"))
1945 ui.write(_("only one patch applied\n"))
1946 return 1
1946 return 1
1947 if not l:
1947 if not l:
1948 ui.write(_("no patches applied\n"))
1948 ui.write(_("no patches applied\n"))
1949 return 1
1949 return 1
1950 return q.qseries(repo, start=l - 2, length=1, status='A',
1950 return q.qseries(repo, start=l - 2, length=1, status='A',
1951 summary=opts.get('summary'))
1951 summary=opts.get('summary'))
1952
1952
1953 def setupheaderopts(ui, opts):
1953 def setupheaderopts(ui, opts):
1954 if not opts.get('user') and opts.get('currentuser'):
1954 if not opts.get('user') and opts.get('currentuser'):
1955 opts['user'] = ui.username()
1955 opts['user'] = ui.username()
1956 if not opts.get('date') and opts.get('currentdate'):
1956 if not opts.get('date') and opts.get('currentdate'):
1957 opts['date'] = "%d %d" % util.makedate()
1957 opts['date'] = "%d %d" % util.makedate()
1958
1958
1959 def new(ui, repo, patch, *args, **opts):
1959 def new(ui, repo, patch, *args, **opts):
1960 """create a new patch
1960 """create a new patch
1961
1961
1962 qnew creates a new patch on top of the currently-applied patch (if
1962 qnew creates a new patch on top of the currently-applied patch (if
1963 any). It will refuse to run if there are any outstanding changes
1963 any). It will refuse to run if there are any outstanding changes
1964 unless -f/--force is specified, in which case the patch will be
1964 unless -f/--force is specified, in which case the patch will be
1965 initialized with them. You may also use -I/--include,
1965 initialized with them. You may also use -I/--include,
1966 -X/--exclude, and/or a list of files after the patch name to add
1966 -X/--exclude, and/or a list of files after the patch name to add
1967 only changes to matching files to the new patch, leaving the rest
1967 only changes to matching files to the new patch, leaving the rest
1968 as uncommitted modifications.
1968 as uncommitted modifications.
1969
1969
1970 -u/--user and -d/--date can be used to set the (given) user and
1970 -u/--user and -d/--date can be used to set the (given) user and
1971 date, respectively. -U/--currentuser and -D/--currentdate set user
1971 date, respectively. -U/--currentuser and -D/--currentdate set user
1972 to current user and date to current date.
1972 to current user and date to current date.
1973
1973
1974 -e/--edit, -m/--message or -l/--logfile set the patch header as
1974 -e/--edit, -m/--message or -l/--logfile set the patch header as
1975 well as the commit message. If none is specified, the header is
1975 well as the commit message. If none is specified, the header is
1976 empty and the commit message is '[mq]: PATCH'.
1976 empty and the commit message is '[mq]: PATCH'.
1977
1977
1978 Use the -g/--git option to keep the patch in the git extended diff
1978 Use the -g/--git option to keep the patch in the git extended diff
1979 format. Read the diffs help topic for more information on why this
1979 format. Read the diffs help topic for more information on why this
1980 is important for preserving permission changes and copy/rename
1980 is important for preserving permission changes and copy/rename
1981 information.
1981 information.
1982 """
1982 """
1983 msg = cmdutil.logmessage(opts)
1983 msg = cmdutil.logmessage(opts)
1984 def getmsg():
1984 def getmsg():
1985 return ui.edit(msg, ui.username())
1985 return ui.edit(msg, ui.username())
1986 q = repo.mq
1986 q = repo.mq
1987 opts['msg'] = msg
1987 opts['msg'] = msg
1988 if opts.get('edit'):
1988 if opts.get('edit'):
1989 opts['msg'] = getmsg
1989 opts['msg'] = getmsg
1990 else:
1990 else:
1991 opts['msg'] = msg
1991 opts['msg'] = msg
1992 setupheaderopts(ui, opts)
1992 setupheaderopts(ui, opts)
1993 q.new(repo, patch, *args, **opts)
1993 q.new(repo, patch, *args, **opts)
1994 q.save_dirty()
1994 q.save_dirty()
1995 return 0
1995 return 0
1996
1996
1997 def refresh(ui, repo, *pats, **opts):
1997 def refresh(ui, repo, *pats, **opts):
1998 """update the current patch
1998 """update the current patch
1999
1999
2000 If any file patterns are provided, the refreshed patch will
2000 If any file patterns are provided, the refreshed patch will
2001 contain only the modifications that match those patterns; the
2001 contain only the modifications that match those patterns; the
2002 remaining modifications will remain in the working directory.
2002 remaining modifications will remain in the working directory.
2003
2003
2004 If -s/--short is specified, files currently included in the patch
2004 If -s/--short is specified, files currently included in the patch
2005 will be refreshed just like matched files and remain in the patch.
2005 will be refreshed just like matched files and remain in the patch.
2006
2006
2007 hg add/remove/copy/rename work as usual, though you might want to
2007 hg add/remove/copy/rename work as usual, though you might want to
2008 use git-style patches (-g/--git or [diff] git=1) to track copies
2008 use git-style patches (-g/--git or [diff] git=1) to track copies
2009 and renames. See the diffs help topic for more information on the
2009 and renames. See the diffs help topic for more information on the
2010 git diff format.
2010 git diff format.
2011 """
2011 """
2012 q = repo.mq
2012 q = repo.mq
2013 message = cmdutil.logmessage(opts)
2013 message = cmdutil.logmessage(opts)
2014 if opts['edit']:
2014 if opts['edit']:
2015 if not q.applied:
2015 if not q.applied:
2016 ui.write(_("no patches applied\n"))
2016 ui.write(_("no patches applied\n"))
2017 return 1
2017 return 1
2018 if message:
2018 if message:
2019 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2019 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2020 patch = q.applied[-1].name
2020 patch = q.applied[-1].name
2021 ph = patchheader(q.join(patch), q.plainmode)
2021 ph = patchheader(q.join(patch), q.plainmode)
2022 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2022 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2023 setupheaderopts(ui, opts)
2023 setupheaderopts(ui, opts)
2024 ret = q.refresh(repo, pats, msg=message, **opts)
2024 ret = q.refresh(repo, pats, msg=message, **opts)
2025 q.save_dirty()
2025 q.save_dirty()
2026 return ret
2026 return ret
2027
2027
2028 def diff(ui, repo, *pats, **opts):
2028 def diff(ui, repo, *pats, **opts):
2029 """diff of the current patch and subsequent modifications
2029 """diff of the current patch and subsequent modifications
2030
2030
2031 Shows a diff which includes the current patch as well as any
2031 Shows a diff which includes the current patch as well as any
2032 changes which have been made in the working directory since the
2032 changes which have been made in the working directory since the
2033 last refresh (thus showing what the current patch would become
2033 last refresh (thus showing what the current patch would become
2034 after a qrefresh).
2034 after a qrefresh).
2035
2035
2036 Use 'hg diff' if you only want to see the changes made since the
2036 Use 'hg diff' if you only want to see the changes made since the
2037 last qrefresh, or 'hg export qtip' if you want to see changes made
2037 last qrefresh, or 'hg export qtip' if you want to see changes made
2038 by the current patch without including changes made since the
2038 by the current patch without including changes made since the
2039 qrefresh.
2039 qrefresh.
2040 """
2040 """
2041 repo.mq.diff(repo, pats, opts)
2041 repo.mq.diff(repo, pats, opts)
2042 return 0
2042 return 0
2043
2043
2044 def fold(ui, repo, *files, **opts):
2044 def fold(ui, repo, *files, **opts):
2045 """fold the named patches into the current patch
2045 """fold the named patches into the current patch
2046
2046
2047 Patches must not yet be applied. Each patch will be successively
2047 Patches must not yet be applied. Each patch will be successively
2048 applied to the current patch in the order given. If all the
2048 applied to the current patch in the order given. If all the
2049 patches apply successfully, the current patch will be refreshed
2049 patches apply successfully, the current patch will be refreshed
2050 with the new cumulative patch, and the folded patches will be
2050 with the new cumulative patch, and the folded patches will be
2051 deleted. With -k/--keep, the folded patch files will not be
2051 deleted. With -k/--keep, the folded patch files will not be
2052 removed afterwards.
2052 removed afterwards.
2053
2053
2054 The header for each folded patch will be concatenated with the
2054 The header for each folded patch will be concatenated with the
2055 current patch header, separated by a line of '* * *'."""
2055 current patch header, separated by a line of '* * *'."""
2056
2056
2057 q = repo.mq
2057 q = repo.mq
2058
2058
2059 if not files:
2059 if not files:
2060 raise util.Abort(_('qfold requires at least one patch name'))
2060 raise util.Abort(_('qfold requires at least one patch name'))
2061 if not q.check_toppatch(repo)[0]:
2061 if not q.check_toppatch(repo)[0]:
2062 raise util.Abort(_('No patches applied'))
2062 raise util.Abort(_('No patches applied'))
2063 q.check_localchanges(repo)
2063 q.check_localchanges(repo)
2064
2064
2065 message = cmdutil.logmessage(opts)
2065 message = cmdutil.logmessage(opts)
2066 if opts['edit']:
2066 if opts['edit']:
2067 if message:
2067 if message:
2068 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2068 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2069
2069
2070 parent = q.lookup('qtip')
2070 parent = q.lookup('qtip')
2071 patches = []
2071 patches = []
2072 messages = []
2072 messages = []
2073 for f in files:
2073 for f in files:
2074 p = q.lookup(f)
2074 p = q.lookup(f)
2075 if p in patches or p == parent:
2075 if p in patches or p == parent:
2076 ui.warn(_('Skipping already folded patch %s') % p)
2076 ui.warn(_('Skipping already folded patch %s') % p)
2077 if q.isapplied(p):
2077 if q.isapplied(p):
2078 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2078 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2079 patches.append(p)
2079 patches.append(p)
2080
2080
2081 for p in patches:
2081 for p in patches:
2082 if not message:
2082 if not message:
2083 ph = patchheader(q.join(p), q.plainmode)
2083 ph = patchheader(q.join(p), q.plainmode)
2084 if ph.message:
2084 if ph.message:
2085 messages.append(ph.message)
2085 messages.append(ph.message)
2086 pf = q.join(p)
2086 pf = q.join(p)
2087 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2087 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2088 if not patchsuccess:
2088 if not patchsuccess:
2089 raise util.Abort(_('Error folding patch %s') % p)
2089 raise util.Abort(_('Error folding patch %s') % p)
2090 patch.updatedir(ui, repo, files)
2090 patch.updatedir(ui, repo, files)
2091
2091
2092 if not message:
2092 if not message:
2093 ph = patchheader(q.join(parent), q.plainmode)
2093 ph = patchheader(q.join(parent), q.plainmode)
2094 message, user = ph.message, ph.user
2094 message, user = ph.message, ph.user
2095 for msg in messages:
2095 for msg in messages:
2096 message.append('* * *')
2096 message.append('* * *')
2097 message.extend(msg)
2097 message.extend(msg)
2098 message = '\n'.join(message)
2098 message = '\n'.join(message)
2099
2099
2100 if opts['edit']:
2100 if opts['edit']:
2101 message = ui.edit(message, user or ui.username())
2101 message = ui.edit(message, user or ui.username())
2102
2102
2103 diffopts = q.patchopts(q.diffopts(), *patches)
2103 diffopts = q.patchopts(q.diffopts(), *patches)
2104 q.refresh(repo, msg=message, git=diffopts.git)
2104 q.refresh(repo, msg=message, git=diffopts.git)
2105 q.delete(repo, patches, opts)
2105 q.delete(repo, patches, opts)
2106 q.save_dirty()
2106 q.save_dirty()
2107
2107
2108 def goto(ui, repo, patch, **opts):
2108 def goto(ui, repo, patch, **opts):
2109 '''push or pop patches until named patch is at top of stack'''
2109 '''push or pop patches until named patch is at top of stack'''
2110 q = repo.mq
2110 q = repo.mq
2111 patch = q.lookup(patch)
2111 patch = q.lookup(patch)
2112 if q.isapplied(patch):
2112 if q.isapplied(patch):
2113 ret = q.pop(repo, patch, force=opts['force'])
2113 ret = q.pop(repo, patch, force=opts['force'])
2114 else:
2114 else:
2115 ret = q.push(repo, patch, force=opts['force'])
2115 ret = q.push(repo, patch, force=opts['force'])
2116 q.save_dirty()
2116 q.save_dirty()
2117 return ret
2117 return ret
2118
2118
2119 def guard(ui, repo, *args, **opts):
2119 def guard(ui, repo, *args, **opts):
2120 '''set or print guards for a patch
2120 '''set or print guards for a patch
2121
2121
2122 Guards control whether a patch can be pushed. A patch with no
2122 Guards control whether a patch can be pushed. A patch with no
2123 guards is always pushed. A patch with a positive guard ("+foo") is
2123 guards is always pushed. A patch with a positive guard ("+foo") is
2124 pushed only if the qselect command has activated it. A patch with
2124 pushed only if the qselect command has activated it. A patch with
2125 a negative guard ("-foo") is never pushed if the qselect command
2125 a negative guard ("-foo") is never pushed if the qselect command
2126 has activated it.
2126 has activated it.
2127
2127
2128 With no arguments, print the currently active guards.
2128 With no arguments, print the currently active guards.
2129 With arguments, set guards for the named patch.
2129 With arguments, set guards for the named patch.
2130 NOTE: Specifying negative guards now requires '--'.
2130 NOTE: Specifying negative guards now requires '--'.
2131
2131
2132 To set guards on another patch::
2132 To set guards on another patch::
2133
2133
2134 hg qguard other.patch -- +2.6.17 -stable
2134 hg qguard other.patch -- +2.6.17 -stable
2135 '''
2135 '''
2136 def status(idx):
2136 def status(idx):
2137 guards = q.series_guards[idx] or ['unguarded']
2137 guards = q.series_guards[idx] or ['unguarded']
2138 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2138 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2139 q = repo.mq
2139 q = repo.mq
2140 patch = None
2140 patch = None
2141 args = list(args)
2141 args = list(args)
2142 if opts['list']:
2142 if opts['list']:
2143 if args or opts['none']:
2143 if args or opts['none']:
2144 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2144 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2145 for i in xrange(len(q.series)):
2145 for i in xrange(len(q.series)):
2146 status(i)
2146 status(i)
2147 return
2147 return
2148 if not args or args[0][0:1] in '-+':
2148 if not args or args[0][0:1] in '-+':
2149 if not q.applied:
2149 if not q.applied:
2150 raise util.Abort(_('no patches applied'))
2150 raise util.Abort(_('no patches applied'))
2151 patch = q.applied[-1].name
2151 patch = q.applied[-1].name
2152 if patch is None and args[0][0:1] not in '-+':
2152 if patch is None and args[0][0:1] not in '-+':
2153 patch = args.pop(0)
2153 patch = args.pop(0)
2154 if patch is None:
2154 if patch is None:
2155 raise util.Abort(_('no patch to work with'))
2155 raise util.Abort(_('no patch to work with'))
2156 if args or opts['none']:
2156 if args or opts['none']:
2157 idx = q.find_series(patch)
2157 idx = q.find_series(patch)
2158 if idx is None:
2158 if idx is None:
2159 raise util.Abort(_('no patch named %s') % patch)
2159 raise util.Abort(_('no patch named %s') % patch)
2160 q.set_guards(idx, args)
2160 q.set_guards(idx, args)
2161 q.save_dirty()
2161 q.save_dirty()
2162 else:
2162 else:
2163 status(q.series.index(q.lookup(patch)))
2163 status(q.series.index(q.lookup(patch)))
2164
2164
2165 def header(ui, repo, patch=None):
2165 def header(ui, repo, patch=None):
2166 """print the header of the topmost or specified patch"""
2166 """print the header of the topmost or specified patch"""
2167 q = repo.mq
2167 q = repo.mq
2168
2168
2169 if patch:
2169 if patch:
2170 patch = q.lookup(patch)
2170 patch = q.lookup(patch)
2171 else:
2171 else:
2172 if not q.applied:
2172 if not q.applied:
2173 ui.write(_('no patches applied\n'))
2173 ui.write(_('no patches applied\n'))
2174 return 1
2174 return 1
2175 patch = q.lookup('qtip')
2175 patch = q.lookup('qtip')
2176 ph = patchheader(q.join(patch), q.plainmode)
2176 ph = patchheader(q.join(patch), q.plainmode)
2177
2177
2178 ui.write('\n'.join(ph.message) + '\n')
2178 ui.write('\n'.join(ph.message) + '\n')
2179
2179
2180 def lastsavename(path):
2180 def lastsavename(path):
2181 (directory, base) = os.path.split(path)
2181 (directory, base) = os.path.split(path)
2182 names = os.listdir(directory)
2182 names = os.listdir(directory)
2183 namere = re.compile("%s.([0-9]+)" % base)
2183 namere = re.compile("%s.([0-9]+)" % base)
2184 maxindex = None
2184 maxindex = None
2185 maxname = None
2185 maxname = None
2186 for f in names:
2186 for f in names:
2187 m = namere.match(f)
2187 m = namere.match(f)
2188 if m:
2188 if m:
2189 index = int(m.group(1))
2189 index = int(m.group(1))
2190 if maxindex is None or index > maxindex:
2190 if maxindex is None or index > maxindex:
2191 maxindex = index
2191 maxindex = index
2192 maxname = f
2192 maxname = f
2193 if maxname:
2193 if maxname:
2194 return (os.path.join(directory, maxname), maxindex)
2194 return (os.path.join(directory, maxname), maxindex)
2195 return (None, None)
2195 return (None, None)
2196
2196
2197 def savename(path):
2197 def savename(path):
2198 (last, index) = lastsavename(path)
2198 (last, index) = lastsavename(path)
2199 if last is None:
2199 if last is None:
2200 index = 0
2200 index = 0
2201 newpath = path + ".%d" % (index + 1)
2201 newpath = path + ".%d" % (index + 1)
2202 return newpath
2202 return newpath
2203
2203
2204 def push(ui, repo, patch=None, **opts):
2204 def push(ui, repo, patch=None, **opts):
2205 """push the next patch onto the stack
2205 """push the next patch onto the stack
2206
2206
2207 When -f/--force is applied, all local changes in patched files
2207 When -f/--force is applied, all local changes in patched files
2208 will be lost.
2208 will be lost.
2209 """
2209 """
2210 q = repo.mq
2210 q = repo.mq
2211 mergeq = None
2211 mergeq = None
2212
2212
2213 if opts['merge']:
2213 if opts['merge']:
2214 if opts['name']:
2214 if opts['name']:
2215 newpath = repo.join(opts['name'])
2215 newpath = repo.join(opts['name'])
2216 else:
2216 else:
2217 newpath, i = lastsavename(q.path)
2217 newpath, i = lastsavename(q.path)
2218 if not newpath:
2218 if not newpath:
2219 ui.warn(_("no saved queues found, please use -n\n"))
2219 ui.warn(_("no saved queues found, please use -n\n"))
2220 return 1
2220 return 1
2221 mergeq = queue(ui, repo.join(""), newpath)
2221 mergeq = queue(ui, repo.join(""), newpath)
2222 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2222 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2223 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2223 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2224 mergeq=mergeq, all=opts.get('all'))
2224 mergeq=mergeq, all=opts.get('all'))
2225 return ret
2225 return ret
2226
2226
2227 def pop(ui, repo, patch=None, **opts):
2227 def pop(ui, repo, patch=None, **opts):
2228 """pop the current patch off the stack
2228 """pop the current patch off the stack
2229
2229
2230 By default, pops off the top of the patch stack. If given a patch
2230 By default, pops off the top of the patch stack. If given a patch
2231 name, keeps popping off patches until the named patch is at the
2231 name, keeps popping off patches until the named patch is at the
2232 top of the stack.
2232 top of the stack.
2233 """
2233 """
2234 localupdate = True
2234 localupdate = True
2235 if opts['name']:
2235 if opts['name']:
2236 q = queue(ui, repo.join(""), repo.join(opts['name']))
2236 q = queue(ui, repo.join(""), repo.join(opts['name']))
2237 ui.warn(_('using patch queue: %s\n') % q.path)
2237 ui.warn(_('using patch queue: %s\n') % q.path)
2238 localupdate = False
2238 localupdate = False
2239 else:
2239 else:
2240 q = repo.mq
2240 q = repo.mq
2241 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2241 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2242 all=opts['all'])
2242 all=opts['all'])
2243 q.save_dirty()
2243 q.save_dirty()
2244 return ret
2244 return ret
2245
2245
2246 def rename(ui, repo, patch, name=None, **opts):
2246 def rename(ui, repo, patch, name=None, **opts):
2247 """rename a patch
2247 """rename a patch
2248
2248
2249 With one argument, renames the current patch to PATCH1.
2249 With one argument, renames the current patch to PATCH1.
2250 With two arguments, renames PATCH1 to PATCH2."""
2250 With two arguments, renames PATCH1 to PATCH2."""
2251
2251
2252 q = repo.mq
2252 q = repo.mq
2253
2253
2254 if not name:
2254 if not name:
2255 name = patch
2255 name = patch
2256 patch = None
2256 patch = None
2257
2257
2258 if patch:
2258 if patch:
2259 patch = q.lookup(patch)
2259 patch = q.lookup(patch)
2260 else:
2260 else:
2261 if not q.applied:
2261 if not q.applied:
2262 ui.write(_('no patches applied\n'))
2262 ui.write(_('no patches applied\n'))
2263 return
2263 return
2264 patch = q.lookup('qtip')
2264 patch = q.lookup('qtip')
2265 absdest = q.join(name)
2265 absdest = q.join(name)
2266 if os.path.isdir(absdest):
2266 if os.path.isdir(absdest):
2267 name = normname(os.path.join(name, os.path.basename(patch)))
2267 name = normname(os.path.join(name, os.path.basename(patch)))
2268 absdest = q.join(name)
2268 absdest = q.join(name)
2269 if os.path.exists(absdest):
2269 if os.path.exists(absdest):
2270 raise util.Abort(_('%s already exists') % absdest)
2270 raise util.Abort(_('%s already exists') % absdest)
2271
2271
2272 if name in q.series:
2272 if name in q.series:
2273 raise util.Abort(
2273 raise util.Abort(
2274 _('A patch named %s already exists in the series file') % name)
2274 _('A patch named %s already exists in the series file') % name)
2275
2275
2276 ui.note(_('renaming %s to %s\n') % (patch, name))
2276 ui.note(_('renaming %s to %s\n') % (patch, name))
2277 i = q.find_series(patch)
2277 i = q.find_series(patch)
2278 guards = q.guard_re.findall(q.full_series[i])
2278 guards = q.guard_re.findall(q.full_series[i])
2279 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2279 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2280 q.parse_series()
2280 q.parse_series()
2281 q.series_dirty = 1
2281 q.series_dirty = 1
2282
2282
2283 info = q.isapplied(patch)
2283 info = q.isapplied(patch)
2284 if info:
2284 if info:
2285 q.applied[info[0]] = statusentry(info[1], name)
2285 q.applied[info[0]] = statusentry(info[1], name)
2286 q.applied_dirty = 1
2286 q.applied_dirty = 1
2287
2287
2288 util.rename(q.join(patch), absdest)
2288 util.rename(q.join(patch), absdest)
2289 r = q.qrepo()
2289 r = q.qrepo()
2290 if r:
2290 if r:
2291 wlock = r.wlock()
2291 wlock = r.wlock()
2292 try:
2292 try:
2293 if r.dirstate[patch] == 'a':
2293 if r.dirstate[patch] == 'a':
2294 r.dirstate.forget(patch)
2294 r.dirstate.forget(patch)
2295 r.dirstate.add(name)
2295 r.dirstate.add(name)
2296 else:
2296 else:
2297 if r.dirstate[name] == 'r':
2297 if r.dirstate[name] == 'r':
2298 r.undelete([name])
2298 r.undelete([name])
2299 r.copy(patch, name)
2299 r.copy(patch, name)
2300 r.remove([patch], False)
2300 r.remove([patch], False)
2301 finally:
2301 finally:
2302 wlock.release()
2302 wlock.release()
2303
2303
2304 q.save_dirty()
2304 q.save_dirty()
2305
2305
2306 def restore(ui, repo, rev, **opts):
2306 def restore(ui, repo, rev, **opts):
2307 """restore the queue state saved by a revision (DEPRECATED)
2307 """restore the queue state saved by a revision (DEPRECATED)
2308
2308
2309 This command is deprecated, use rebase --mq instead."""
2309 This command is deprecated, use rebase --mq instead."""
2310 rev = repo.lookup(rev)
2310 rev = repo.lookup(rev)
2311 q = repo.mq
2311 q = repo.mq
2312 q.restore(repo, rev, delete=opts['delete'],
2312 q.restore(repo, rev, delete=opts['delete'],
2313 qupdate=opts['update'])
2313 qupdate=opts['update'])
2314 q.save_dirty()
2314 q.save_dirty()
2315 return 0
2315 return 0
2316
2316
2317 def save(ui, repo, **opts):
2317 def save(ui, repo, **opts):
2318 """save current queue state (DEPRECATED)
2318 """save current queue state (DEPRECATED)
2319
2319
2320 This command is deprecated, use rebase --mq instead."""
2320 This command is deprecated, use rebase --mq instead."""
2321 q = repo.mq
2321 q = repo.mq
2322 message = cmdutil.logmessage(opts)
2322 message = cmdutil.logmessage(opts)
2323 ret = q.save(repo, msg=message)
2323 ret = q.save(repo, msg=message)
2324 if ret:
2324 if ret:
2325 return ret
2325 return ret
2326 q.save_dirty()
2326 q.save_dirty()
2327 if opts['copy']:
2327 if opts['copy']:
2328 path = q.path
2328 path = q.path
2329 if opts['name']:
2329 if opts['name']:
2330 newpath = os.path.join(q.basepath, opts['name'])
2330 newpath = os.path.join(q.basepath, opts['name'])
2331 if os.path.exists(newpath):
2331 if os.path.exists(newpath):
2332 if not os.path.isdir(newpath):
2332 if not os.path.isdir(newpath):
2333 raise util.Abort(_('destination %s exists and is not '
2333 raise util.Abort(_('destination %s exists and is not '
2334 'a directory') % newpath)
2334 'a directory') % newpath)
2335 if not opts['force']:
2335 if not opts['force']:
2336 raise util.Abort(_('destination %s exists, '
2336 raise util.Abort(_('destination %s exists, '
2337 'use -f to force') % newpath)
2337 'use -f to force') % newpath)
2338 else:
2338 else:
2339 newpath = savename(path)
2339 newpath = savename(path)
2340 ui.warn(_("copy %s to %s\n") % (path, newpath))
2340 ui.warn(_("copy %s to %s\n") % (path, newpath))
2341 util.copyfiles(path, newpath)
2341 util.copyfiles(path, newpath)
2342 if opts['empty']:
2342 if opts['empty']:
2343 try:
2343 try:
2344 os.unlink(q.join(q.status_path))
2344 os.unlink(q.join(q.status_path))
2345 except:
2345 except:
2346 pass
2346 pass
2347 return 0
2347 return 0
2348
2348
2349 def strip(ui, repo, rev, **opts):
2349 def strip(ui, repo, rev, **opts):
2350 """strip a revision and all its descendants from the repository
2350 """strip a revision and all its descendants from the repository
2351
2351
2352 If one of the working directory's parent revisions is stripped, the
2352 If one of the working directory's parent revisions is stripped, the
2353 working directory will be updated to the parent of the stripped
2353 working directory will be updated to the parent of the stripped
2354 revision.
2354 revision.
2355 """
2355 """
2356 backup = 'all'
2356 backup = 'all'
2357 if opts['backup']:
2357 if opts['backup']:
2358 backup = 'strip'
2358 backup = 'strip'
2359 elif opts['nobackup']:
2359 elif opts['nobackup']:
2360 backup = 'none'
2360 backup = 'none'
2361
2361
2362 rev = repo.lookup(rev)
2362 rev = repo.lookup(rev)
2363 p = repo.dirstate.parents()
2363 p = repo.dirstate.parents()
2364 cl = repo.changelog
2364 cl = repo.changelog
2365 update = True
2365 update = True
2366 if p[0] == nullid:
2366 if p[0] == nullid:
2367 update = False
2367 update = False
2368 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2368 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2369 update = False
2369 update = False
2370 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2370 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2371 update = False
2371 update = False
2372
2372
2373 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2373 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2374 return 0
2374 return 0
2375
2375
2376 def select(ui, repo, *args, **opts):
2376 def select(ui, repo, *args, **opts):
2377 '''set or print guarded patches to push
2377 '''set or print guarded patches to push
2378
2378
2379 Use the qguard command to set or print guards on patch, then use
2379 Use the qguard command to set or print guards on patch, then use
2380 qselect to tell mq which guards to use. A patch will be pushed if
2380 qselect to tell mq which guards to use. A patch will be pushed if
2381 it has no guards or any positive guards match the currently
2381 it has no guards or any positive guards match the currently
2382 selected guard, but will not be pushed if any negative guards
2382 selected guard, but will not be pushed if any negative guards
2383 match the current guard. For example::
2383 match the current guard. For example::
2384
2384
2385 qguard foo.patch -stable (negative guard)
2385 qguard foo.patch -stable (negative guard)
2386 qguard bar.patch +stable (positive guard)
2386 qguard bar.patch +stable (positive guard)
2387 qselect stable
2387 qselect stable
2388
2388
2389 This activates the "stable" guard. mq will skip foo.patch (because
2389 This activates the "stable" guard. mq will skip foo.patch (because
2390 it has a negative match) but push bar.patch (because it has a
2390 it has a negative match) but push bar.patch (because it has a
2391 positive match).
2391 positive match).
2392
2392
2393 With no arguments, prints the currently active guards.
2393 With no arguments, prints the currently active guards.
2394 With one argument, sets the active guard.
2394 With one argument, sets the active guard.
2395
2395
2396 Use -n/--none to deactivate guards (no other arguments needed).
2396 Use -n/--none to deactivate guards (no other arguments needed).
2397 When no guards are active, patches with positive guards are
2397 When no guards are active, patches with positive guards are
2398 skipped and patches with negative guards are pushed.
2398 skipped and patches with negative guards are pushed.
2399
2399
2400 qselect can change the guards on applied patches. It does not pop
2400 qselect can change the guards on applied patches. It does not pop
2401 guarded patches by default. Use --pop to pop back to the last
2401 guarded patches by default. Use --pop to pop back to the last
2402 applied patch that is not guarded. Use --reapply (which implies
2402 applied patch that is not guarded. Use --reapply (which implies
2403 --pop) to push back to the current patch afterwards, but skip
2403 --pop) to push back to the current patch afterwards, but skip
2404 guarded patches.
2404 guarded patches.
2405
2405
2406 Use -s/--series to print a list of all guards in the series file
2406 Use -s/--series to print a list of all guards in the series file
2407 (no other arguments needed). Use -v for more information.'''
2407 (no other arguments needed). Use -v for more information.'''
2408
2408
2409 q = repo.mq
2409 q = repo.mq
2410 guards = q.active()
2410 guards = q.active()
2411 if args or opts['none']:
2411 if args or opts['none']:
2412 old_unapplied = q.unapplied(repo)
2412 old_unapplied = q.unapplied(repo)
2413 old_guarded = [i for i in xrange(len(q.applied)) if
2413 old_guarded = [i for i in xrange(len(q.applied)) if
2414 not q.pushable(i)[0]]
2414 not q.pushable(i)[0]]
2415 q.set_active(args)
2415 q.set_active(args)
2416 q.save_dirty()
2416 q.save_dirty()
2417 if not args:
2417 if not args:
2418 ui.status(_('guards deactivated\n'))
2418 ui.status(_('guards deactivated\n'))
2419 if not opts['pop'] and not opts['reapply']:
2419 if not opts['pop'] and not opts['reapply']:
2420 unapplied = q.unapplied(repo)
2420 unapplied = q.unapplied(repo)
2421 guarded = [i for i in xrange(len(q.applied))
2421 guarded = [i for i in xrange(len(q.applied))
2422 if not q.pushable(i)[0]]
2422 if not q.pushable(i)[0]]
2423 if len(unapplied) != len(old_unapplied):
2423 if len(unapplied) != len(old_unapplied):
2424 ui.status(_('number of unguarded, unapplied patches has '
2424 ui.status(_('number of unguarded, unapplied patches has '
2425 'changed from %d to %d\n') %
2425 'changed from %d to %d\n') %
2426 (len(old_unapplied), len(unapplied)))
2426 (len(old_unapplied), len(unapplied)))
2427 if len(guarded) != len(old_guarded):
2427 if len(guarded) != len(old_guarded):
2428 ui.status(_('number of guarded, applied patches has changed '
2428 ui.status(_('number of guarded, applied patches has changed '
2429 'from %d to %d\n') %
2429 'from %d to %d\n') %
2430 (len(old_guarded), len(guarded)))
2430 (len(old_guarded), len(guarded)))
2431 elif opts['series']:
2431 elif opts['series']:
2432 guards = {}
2432 guards = {}
2433 noguards = 0
2433 noguards = 0
2434 for gs in q.series_guards:
2434 for gs in q.series_guards:
2435 if not gs:
2435 if not gs:
2436 noguards += 1
2436 noguards += 1
2437 for g in gs:
2437 for g in gs:
2438 guards.setdefault(g, 0)
2438 guards.setdefault(g, 0)
2439 guards[g] += 1
2439 guards[g] += 1
2440 if ui.verbose:
2440 if ui.verbose:
2441 guards['NONE'] = noguards
2441 guards['NONE'] = noguards
2442 guards = guards.items()
2442 guards = guards.items()
2443 guards.sort(key=lambda x: x[0][1:])
2443 guards.sort(key=lambda x: x[0][1:])
2444 if guards:
2444 if guards:
2445 ui.note(_('guards in series file:\n'))
2445 ui.note(_('guards in series file:\n'))
2446 for guard, count in guards:
2446 for guard, count in guards:
2447 ui.note('%2d ' % count)
2447 ui.note('%2d ' % count)
2448 ui.write(guard, '\n')
2448 ui.write(guard, '\n')
2449 else:
2449 else:
2450 ui.note(_('no guards in series file\n'))
2450 ui.note(_('no guards in series file\n'))
2451 else:
2451 else:
2452 if guards:
2452 if guards:
2453 ui.note(_('active guards:\n'))
2453 ui.note(_('active guards:\n'))
2454 for g in guards:
2454 for g in guards:
2455 ui.write(g, '\n')
2455 ui.write(g, '\n')
2456 else:
2456 else:
2457 ui.write(_('no active guards\n'))
2457 ui.write(_('no active guards\n'))
2458 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2458 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2459 popped = False
2459 popped = False
2460 if opts['pop'] or opts['reapply']:
2460 if opts['pop'] or opts['reapply']:
2461 for i in xrange(len(q.applied)):
2461 for i in xrange(len(q.applied)):
2462 pushable, reason = q.pushable(i)
2462 pushable, reason = q.pushable(i)
2463 if not pushable:
2463 if not pushable:
2464 ui.status(_('popping guarded patches\n'))
2464 ui.status(_('popping guarded patches\n'))
2465 popped = True
2465 popped = True
2466 if i == 0:
2466 if i == 0:
2467 q.pop(repo, all=True)
2467 q.pop(repo, all=True)
2468 else:
2468 else:
2469 q.pop(repo, i - 1)
2469 q.pop(repo, i - 1)
2470 break
2470 break
2471 if popped:
2471 if popped:
2472 try:
2472 try:
2473 if reapply:
2473 if reapply:
2474 ui.status(_('reapplying unguarded patches\n'))
2474 ui.status(_('reapplying unguarded patches\n'))
2475 q.push(repo, reapply)
2475 q.push(repo, reapply)
2476 finally:
2476 finally:
2477 q.save_dirty()
2477 q.save_dirty()
2478
2478
2479 def finish(ui, repo, *revrange, **opts):
2479 def finish(ui, repo, *revrange, **opts):
2480 """move applied patches into repository history
2480 """move applied patches into repository history
2481
2481
2482 Finishes the specified revisions (corresponding to applied
2482 Finishes the specified revisions (corresponding to applied
2483 patches) by moving them out of mq control into regular repository
2483 patches) by moving them out of mq control into regular repository
2484 history.
2484 history.
2485
2485
2486 Accepts a revision range or the -a/--applied option. If --applied
2486 Accepts a revision range or the -a/--applied option. If --applied
2487 is specified, all applied mq revisions are removed from mq
2487 is specified, all applied mq revisions are removed from mq
2488 control. Otherwise, the given revisions must be at the base of the
2488 control. Otherwise, the given revisions must be at the base of the
2489 stack of applied patches.
2489 stack of applied patches.
2490
2490
2491 This can be especially useful if your changes have been applied to
2491 This can be especially useful if your changes have been applied to
2492 an upstream repository, or if you are about to push your changes
2492 an upstream repository, or if you are about to push your changes
2493 to upstream.
2493 to upstream.
2494 """
2494 """
2495 if not opts['applied'] and not revrange:
2495 if not opts['applied'] and not revrange:
2496 raise util.Abort(_('no revisions specified'))
2496 raise util.Abort(_('no revisions specified'))
2497 elif opts['applied']:
2497 elif opts['applied']:
2498 revrange = ('qbase:qtip',) + revrange
2498 revrange = ('qbase:qtip',) + revrange
2499
2499
2500 q = repo.mq
2500 q = repo.mq
2501 if not q.applied:
2501 if not q.applied:
2502 ui.status(_('no patches applied\n'))
2502 ui.status(_('no patches applied\n'))
2503 return 0
2503 return 0
2504
2504
2505 revs = cmdutil.revrange(repo, revrange)
2505 revs = cmdutil.revrange(repo, revrange)
2506 q.finish(repo, revs)
2506 q.finish(repo, revs)
2507 q.save_dirty()
2507 q.save_dirty()
2508 return 0
2508 return 0
2509
2509
2510 def reposetup(ui, repo):
2510 def reposetup(ui, repo):
2511 class mqrepo(repo.__class__):
2511 class mqrepo(repo.__class__):
2512 @util.propertycache
2512 @util.propertycache
2513 def mq(self):
2513 def mq(self):
2514 return queue(self.ui, self.join(""))
2514 return queue(self.ui, self.join(""))
2515
2515
2516 def abort_if_wdir_patched(self, errmsg, force=False):
2516 def abort_if_wdir_patched(self, errmsg, force=False):
2517 if self.mq.applied and not force:
2517 if self.mq.applied and not force:
2518 parent = self.dirstate.parents()[0]
2518 parent = self.dirstate.parents()[0]
2519 if parent in [s.node for s in self.mq.applied]:
2519 if parent in [s.node for s in self.mq.applied]:
2520 raise util.Abort(errmsg)
2520 raise util.Abort(errmsg)
2521
2521
2522 def commit(self, text="", user=None, date=None, match=None,
2522 def commit(self, text="", user=None, date=None, match=None,
2523 force=False, editor=False, extra={}):
2523 force=False, editor=False, extra={}):
2524 self.abort_if_wdir_patched(
2524 self.abort_if_wdir_patched(
2525 _('cannot commit over an applied mq patch'),
2525 _('cannot commit over an applied mq patch'),
2526 force)
2526 force)
2527
2527
2528 return super(mqrepo, self).commit(text, user, date, match, force,
2528 return super(mqrepo, self).commit(text, user, date, match, force,
2529 editor, extra)
2529 editor, extra)
2530
2530
2531 def push(self, remote, force=False, revs=None):
2531 def push(self, remote, force=False, revs=None):
2532 if self.mq.applied and not force and not revs:
2532 if self.mq.applied and not force and not revs:
2533 raise util.Abort(_('source has mq patches applied'))
2533 raise util.Abort(_('source has mq patches applied'))
2534 return super(mqrepo, self).push(remote, force, revs)
2534 return super(mqrepo, self).push(remote, force, revs)
2535
2535
2536 def _findtags(self):
2536 def _findtags(self):
2537 '''augment tags from base class with patch tags'''
2537 '''augment tags from base class with patch tags'''
2538 result = super(mqrepo, self)._findtags()
2538 result = super(mqrepo, self)._findtags()
2539
2539
2540 q = self.mq
2540 q = self.mq
2541 if not q.applied:
2541 if not q.applied:
2542 return result
2542 return result
2543
2543
2544 mqtags = [(patch.node, patch.name) for patch in q.applied]
2544 mqtags = [(patch.node, patch.name) for patch in q.applied]
2545
2545
2546 if mqtags[-1][0] not in self.changelog.nodemap:
2546 if mqtags[-1][0] not in self.changelog.nodemap:
2547 self.ui.warn(_('mq status file refers to unknown node %s\n')
2547 self.ui.warn(_('mq status file refers to unknown node %s\n')
2548 % short(mqtags[-1][0]))
2548 % short(mqtags[-1][0]))
2549 return result
2549 return result
2550
2550
2551 mqtags.append((mqtags[-1][0], 'qtip'))
2551 mqtags.append((mqtags[-1][0], 'qtip'))
2552 mqtags.append((mqtags[0][0], 'qbase'))
2552 mqtags.append((mqtags[0][0], 'qbase'))
2553 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2553 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2554 tags = result[0]
2554 tags = result[0]
2555 for patch in mqtags:
2555 for patch in mqtags:
2556 if patch[1] in tags:
2556 if patch[1] in tags:
2557 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2557 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2558 % patch[1])
2558 % patch[1])
2559 else:
2559 else:
2560 tags[patch[1]] = patch[0]
2560 tags[patch[1]] = patch[0]
2561
2561
2562 return result
2562 return result
2563
2563
2564 def _branchtags(self, partial, lrev):
2564 def _branchtags(self, partial, lrev):
2565 q = self.mq
2565 q = self.mq
2566 if not q.applied:
2566 if not q.applied:
2567 return super(mqrepo, self)._branchtags(partial, lrev)
2567 return super(mqrepo, self)._branchtags(partial, lrev)
2568
2568
2569 cl = self.changelog
2569 cl = self.changelog
2570 qbasenode = q.applied[0].node
2570 qbasenode = q.applied[0].node
2571 if qbasenode not in cl.nodemap:
2571 if qbasenode not in cl.nodemap:
2572 self.ui.warn(_('mq status file refers to unknown node %s\n')
2572 self.ui.warn(_('mq status file refers to unknown node %s\n')
2573 % short(qbasenode))
2573 % short(qbasenode))
2574 return super(mqrepo, self)._branchtags(partial, lrev)
2574 return super(mqrepo, self)._branchtags(partial, lrev)
2575
2575
2576 qbase = cl.rev(qbasenode)
2576 qbase = cl.rev(qbasenode)
2577 start = lrev + 1
2577 start = lrev + 1
2578 if start < qbase:
2578 if start < qbase:
2579 # update the cache (excluding the patches) and save it
2579 # update the cache (excluding the patches) and save it
2580 self._updatebranchcache(partial, lrev + 1, qbase)
2580 self._updatebranchcache(partial, lrev + 1, qbase)
2581 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2581 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2582 start = qbase
2582 start = qbase
2583 # if start = qbase, the cache is as updated as it should be.
2583 # if start = qbase, the cache is as updated as it should be.
2584 # if start > qbase, the cache includes (part of) the patches.
2584 # if start > qbase, the cache includes (part of) the patches.
2585 # we might as well use it, but we won't save it.
2585 # we might as well use it, but we won't save it.
2586
2586
2587 # update the cache up to the tip
2587 # update the cache up to the tip
2588 self._updatebranchcache(partial, start, len(cl))
2588 self._updatebranchcache(partial, start, len(cl))
2589
2589
2590 return partial
2590 return partial
2591
2591
2592 if repo.local():
2592 if repo.local():
2593 repo.__class__ = mqrepo
2593 repo.__class__ = mqrepo
2594
2594
2595 def mqimport(orig, ui, repo, *args, **kwargs):
2595 def mqimport(orig, ui, repo, *args, **kwargs):
2596 if (hasattr(repo, 'abort_if_wdir_patched')
2596 if (hasattr(repo, 'abort_if_wdir_patched')
2597 and not kwargs.get('no_commit', False)):
2597 and not kwargs.get('no_commit', False)):
2598 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2598 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2599 kwargs.get('force'))
2599 kwargs.get('force'))
2600 return orig(ui, repo, *args, **kwargs)
2600 return orig(ui, repo, *args, **kwargs)
2601
2601
2602 def mqinit(orig, ui, *args, **kwargs):
2602 def mqinit(orig, ui, *args, **kwargs):
2603 mq = kwargs.pop('mq', None)
2603 mq = kwargs.pop('mq', None)
2604
2604
2605 if not mq:
2605 if not mq:
2606 return orig(ui, *args, **kwargs)
2606 return orig(ui, *args, **kwargs)
2607
2607
2608 if args:
2608 if args:
2609 repopath = args[0]
2609 repopath = args[0]
2610 if not hg.islocal(repopath):
2610 if not hg.islocal(repopath):
2611 raise util.Abort(_('only a local queue repository '
2611 raise util.Abort(_('only a local queue repository '
2612 'may be initialized'))
2612 'may be initialized'))
2613 else:
2613 else:
2614 repopath = cmdutil.findrepo(os.getcwd())
2614 repopath = cmdutil.findrepo(os.getcwd())
2615 if not repopath:
2615 if not repopath:
2616 raise util.Abort(_('There is no Mercurial repository here '
2616 raise util.Abort(_('There is no Mercurial repository here '
2617 '(.hg not found)'))
2617 '(.hg not found)'))
2618 repo = hg.repository(ui, repopath)
2618 repo = hg.repository(ui, repopath)
2619 return qinit(ui, repo, True)
2619 return qinit(ui, repo, True)
2620
2620
2621 def mqcommand(orig, ui, repo, *args, **kwargs):
2621 def mqcommand(orig, ui, repo, *args, **kwargs):
2622 """Add --mq option to operate on patch repository instead of main"""
2622 """Add --mq option to operate on patch repository instead of main"""
2623
2623
2624 # some commands do not like getting unknown options
2624 # some commands do not like getting unknown options
2625 mq = kwargs.pop('mq', None)
2625 mq = kwargs.pop('mq', None)
2626
2626
2627 if not mq:
2627 if not mq:
2628 return orig(ui, repo, *args, **kwargs)
2628 return orig(ui, repo, *args, **kwargs)
2629
2629
2630 q = repo.mq
2630 q = repo.mq
2631 r = q.qrepo()
2631 r = q.qrepo()
2632 if not r:
2632 if not r:
2633 raise util.Abort('no queue repository')
2633 raise util.Abort('no queue repository')
2634 return orig(r.ui, r, *args, **kwargs)
2634 return orig(r.ui, r, *args, **kwargs)
2635
2635
2636 def uisetup(ui):
2636 def uisetup(ui):
2637 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2637 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2638
2638
2639 extensions.wrapcommand(commands.table, 'import', mqimport)
2639 extensions.wrapcommand(commands.table, 'import', mqimport)
2640
2640
2641 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2641 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2642 entry[1].extend(mqopt)
2642 entry[1].extend(mqopt)
2643
2643
2644 for cmd in commands.table.keys():
2644 for cmd in commands.table.keys():
2645 cmd = cmdutil.parsealiases(cmd)[0]
2645 cmd = cmdutil.parsealiases(cmd)[0]
2646 if cmd in commands.norepo:
2646 if cmd in commands.norepo:
2647 continue
2647 continue
2648 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2648 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2649 entry[1].extend(mqopt)
2649 entry[1].extend(mqopt)
2650
2650
2651 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2651 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2652
2652
2653 cmdtable = {
2653 cmdtable = {
2654 "qapplied":
2654 "qapplied":
2655 (applied,
2655 (applied,
2656 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2656 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2657 _('hg qapplied [-1] [-s] [PATCH]')),
2657 _('hg qapplied [-1] [-s] [PATCH]')),
2658 "qclone":
2658 "qclone":
2659 (clone,
2659 (clone,
2660 [('', 'pull', None, _('use pull protocol to copy metadata')),
2660 [('', 'pull', None, _('use pull protocol to copy metadata')),
2661 ('U', 'noupdate', None, _('do not update the new working directories')),
2661 ('U', 'noupdate', None, _('do not update the new working directories')),
2662 ('', 'uncompressed', None,
2662 ('', 'uncompressed', None,
2663 _('use uncompressed transfer (fast over LAN)')),
2663 _('use uncompressed transfer (fast over LAN)')),
2664 ('p', 'patches', '', _('location of source patch repository')),
2664 ('p', 'patches', '', _('location of source patch repository')),
2665 ] + commands.remoteopts,
2665 ] + commands.remoteopts,
2666 _('hg qclone [OPTION]... SOURCE [DEST]')),
2666 _('hg qclone [OPTION]... SOURCE [DEST]')),
2667 "qcommit|qci":
2667 "qcommit|qci":
2668 (commit,
2668 (commit,
2669 commands.table["^commit|ci"][1],
2669 commands.table["^commit|ci"][1],
2670 _('hg qcommit [OPTION]... [FILE]...')),
2670 _('hg qcommit [OPTION]... [FILE]...')),
2671 "^qdiff":
2671 "^qdiff":
2672 (diff,
2672 (diff,
2673 commands.diffopts + commands.diffopts2 + commands.walkopts,
2673 commands.diffopts + commands.diffopts2 + commands.walkopts,
2674 _('hg qdiff [OPTION]... [FILE]...')),
2674 _('hg qdiff [OPTION]... [FILE]...')),
2675 "qdelete|qremove|qrm":
2675 "qdelete|qremove|qrm":
2676 (delete,
2676 (delete,
2677 [('k', 'keep', None, _('keep patch file')),
2677 [('k', 'keep', None, _('keep patch file')),
2678 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2678 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2679 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2679 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2680 'qfold':
2680 'qfold':
2681 (fold,
2681 (fold,
2682 [('e', 'edit', None, _('edit patch header')),
2682 [('e', 'edit', None, _('edit patch header')),
2683 ('k', 'keep', None, _('keep folded patch files')),
2683 ('k', 'keep', None, _('keep folded patch files')),
2684 ] + commands.commitopts,
2684 ] + commands.commitopts,
2685 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2685 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2686 'qgoto':
2686 'qgoto':
2687 (goto,
2687 (goto,
2688 [('f', 'force', None, _('overwrite any local changes'))],
2688 [('f', 'force', None, _('overwrite any local changes'))],
2689 _('hg qgoto [OPTION]... PATCH')),
2689 _('hg qgoto [OPTION]... PATCH')),
2690 'qguard':
2690 'qguard':
2691 (guard,
2691 (guard,
2692 [('l', 'list', None, _('list all patches and guards')),
2692 [('l', 'list', None, _('list all patches and guards')),
2693 ('n', 'none', None, _('drop all guards'))],
2693 ('n', 'none', None, _('drop all guards'))],
2694 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2694 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2695 'qheader': (header, [], _('hg qheader [PATCH]')),
2695 'qheader': (header, [], _('hg qheader [PATCH]')),
2696 "^qimport":
2696 "^qimport":
2697 (qimport,
2697 (qimport,
2698 [('e', 'existing', None, _('import file in patch directory')),
2698 [('e', 'existing', None, _('import file in patch directory')),
2699 ('n', 'name', '', _('name of patch file')),
2699 ('n', 'name', '', _('name of patch file')),
2700 ('f', 'force', None, _('overwrite existing files')),
2700 ('f', 'force', None, _('overwrite existing files')),
2701 ('r', 'rev', [], _('place existing revisions under mq control')),
2701 ('r', 'rev', [], _('place existing revisions under mq control')),
2702 ('g', 'git', None, _('use git extended diff format')),
2702 ('g', 'git', None, _('use git extended diff format')),
2703 ('P', 'push', None, _('qpush after importing'))],
2703 ('P', 'push', None, _('qpush after importing'))],
2704 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2704 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2705 "^qinit":
2705 "^qinit":
2706 (init,
2706 (init,
2707 [('c', 'create-repo', None, _('create queue repository'))],
2707 [('c', 'create-repo', None, _('create queue repository'))],
2708 _('hg qinit [-c]')),
2708 _('hg qinit [-c]')),
2709 "qnew":
2709 "qnew":
2710 (new,
2710 (new,
2711 [('e', 'edit', None, _('edit commit message')),
2711 [('e', 'edit', None, _('edit commit message')),
2712 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2712 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2713 ('g', 'git', None, _('use git extended diff format')),
2713 ('g', 'git', None, _('use git extended diff format')),
2714 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2714 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2715 ('u', 'user', '', _('add "From: <given user>" to patch')),
2715 ('u', 'user', '', _('add "From: <given user>" to patch')),
2716 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2716 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2717 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2717 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2718 ] + commands.walkopts + commands.commitopts,
2718 ] + commands.walkopts + commands.commitopts,
2719 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2719 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2720 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2720 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2721 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2721 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2722 "^qpop":
2722 "^qpop":
2723 (pop,
2723 (pop,
2724 [('a', 'all', None, _('pop all patches')),
2724 [('a', 'all', None, _('pop all patches')),
2725 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2725 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2726 ('f', 'force', None, _('forget any local changes to patched files'))],
2726 ('f', 'force', None, _('forget any local changes to patched files'))],
2727 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2727 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2728 "^qpush":
2728 "^qpush":
2729 (push,
2729 (push,
2730 [('f', 'force', None, _('apply if the patch has rejects')),
2730 [('f', 'force', None, _('apply if the patch has rejects')),
2731 ('l', 'list', None, _('list patch name in commit text')),
2731 ('l', 'list', None, _('list patch name in commit text')),
2732 ('a', 'all', None, _('apply all patches')),
2732 ('a', 'all', None, _('apply all patches')),
2733 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2733 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2734 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2734 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2735 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2735 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2736 "^qrefresh":
2736 "^qrefresh":
2737 (refresh,
2737 (refresh,
2738 [('e', 'edit', None, _('edit commit message')),
2738 [('e', 'edit', None, _('edit commit message')),
2739 ('g', 'git', None, _('use git extended diff format')),
2739 ('g', 'git', None, _('use git extended diff format')),
2740 ('s', 'short', None,
2740 ('s', 'short', None,
2741 _('refresh only files already in the patch and specified files')),
2741 _('refresh only files already in the patch and specified files')),
2742 ('U', 'currentuser', None,
2742 ('U', 'currentuser', None,
2743 _('add/update author field in patch with current user')),
2743 _('add/update author field in patch with current user')),
2744 ('u', 'user', '',
2744 ('u', 'user', '',
2745 _('add/update author field in patch with given user')),
2745 _('add/update author field in patch with given user')),
2746 ('D', 'currentdate', None,
2746 ('D', 'currentdate', None,
2747 _('add/update date field in patch with current date')),
2747 _('add/update date field in patch with current date')),
2748 ('d', 'date', '',
2748 ('d', 'date', '',
2749 _('add/update date field in patch with given date'))
2749 _('add/update date field in patch with given date'))
2750 ] + commands.walkopts + commands.commitopts,
2750 ] + commands.walkopts + commands.commitopts,
2751 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2751 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2752 'qrename|qmv':
2752 'qrename|qmv':
2753 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2753 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2754 "qrestore":
2754 "qrestore":
2755 (restore,
2755 (restore,
2756 [('d', 'delete', None, _('delete save entry')),
2756 [('d', 'delete', None, _('delete save entry')),
2757 ('u', 'update', None, _('update queue working directory'))],
2757 ('u', 'update', None, _('update queue working directory'))],
2758 _('hg qrestore [-d] [-u] REV')),
2758 _('hg qrestore [-d] [-u] REV')),
2759 "qsave":
2759 "qsave":
2760 (save,
2760 (save,
2761 [('c', 'copy', None, _('copy patch directory')),
2761 [('c', 'copy', None, _('copy patch directory')),
2762 ('n', 'name', '', _('copy directory name')),
2762 ('n', 'name', '', _('copy directory name')),
2763 ('e', 'empty', None, _('clear queue status file')),
2763 ('e', 'empty', None, _('clear queue status file')),
2764 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2764 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2765 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2765 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2766 "qselect":
2766 "qselect":
2767 (select,
2767 (select,
2768 [('n', 'none', None, _('disable all guards')),
2768 [('n', 'none', None, _('disable all guards')),
2769 ('s', 'series', None, _('list all guards in series file')),
2769 ('s', 'series', None, _('list all guards in series file')),
2770 ('', 'pop', None, _('pop to before first guarded applied patch')),
2770 ('', 'pop', None, _('pop to before first guarded applied patch')),
2771 ('', 'reapply', None, _('pop, then reapply patches'))],
2771 ('', 'reapply', None, _('pop, then reapply patches'))],
2772 _('hg qselect [OPTION]... [GUARD]...')),
2772 _('hg qselect [OPTION]... [GUARD]...')),
2773 "qseries":
2773 "qseries":
2774 (series,
2774 (series,
2775 [('m', 'missing', None, _('print patches not in series')),
2775 [('m', 'missing', None, _('print patches not in series')),
2776 ] + seriesopts,
2776 ] + seriesopts,
2777 _('hg qseries [-ms]')),
2777 _('hg qseries [-ms]')),
2778 "^strip":
2778 "^strip":
2779 (strip,
2779 (strip,
2780 [('f', 'force', None, _('force removal with local changes')),
2780 [('f', 'force', None, _('force removal with local changes')),
2781 ('b', 'backup', None, _('bundle unrelated changesets')),
2781 ('b', 'backup', None, _('bundle unrelated changesets')),
2782 ('n', 'nobackup', None, _('no backups'))],
2782 ('n', 'nobackup', None, _('no backups'))],
2783 _('hg strip [-f] [-b] [-n] REV')),
2783 _('hg strip [-f] [-b] [-n] REV')),
2784 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2784 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2785 "qunapplied":
2785 "qunapplied":
2786 (unapplied,
2786 (unapplied,
2787 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2787 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2788 _('hg qunapplied [-1] [-s] [PATCH]')),
2788 _('hg qunapplied [-1] [-s] [PATCH]')),
2789 "qfinish":
2789 "qfinish":
2790 (finish,
2790 (finish,
2791 [('a', 'applied', None, _('finish all applied changesets'))],
2791 [('a', 'applied', None, _('finish all applied changesets'))],
2792 _('hg qfinish [-a] [REV]...')),
2792 _('hg qfinish [-a] [REV]...')),
2793 }
2793 }
@@ -1,552 +1,551 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, error
17 from mercurial import hg, util, repair, merge, cmdutil, commands, error
18 from mercurial import extensions, ancestor, copies, patch
18 from mercurial import extensions, ancestor, copies, patch
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26
26
27 def rebase(ui, repo, **opts):
27 def rebase(ui, repo, **opts):
28 """move changeset (and descendants) to a different branch
28 """move changeset (and descendants) to a different branch
29
29
30 Rebase uses repeated merging to graft changesets from one part of
30 Rebase uses repeated merging to graft changesets from one part of
31 history (the source) onto another (the destination). This can be
31 history (the source) onto another (the destination). This can be
32 useful for linearizing local changes relative to a master
32 useful for linearizing local changes relative to a master
33 development tree.
33 development tree.
34
34
35 If you don't specify a destination changeset (``-d/--dest``),
35 If you don't specify a destination changeset (``-d/--dest``),
36 rebase uses the tipmost head of the current named branch as the
36 rebase uses the tipmost head of the current named branch as the
37 destination. (The destination changeset is not modified by
37 destination. (The destination changeset is not modified by
38 rebasing, but new changesets are added as its descendants.)
38 rebasing, but new changesets are added as its descendants.)
39
39
40 You can specify which changesets to rebase in two ways: as a
40 You can specify which changesets to rebase in two ways: as a
41 "source" changeset or as a "base" changeset. Both are shorthand
41 "source" changeset or as a "base" changeset. Both are shorthand
42 for a topologically related set of changesets (the "source
42 for a topologically related set of changesets (the "source
43 branch"). If you specify source (``-s/--source``), rebase will
43 branch"). If you specify source (``-s/--source``), rebase will
44 rebase that changeset and all of its descendants onto dest. If you
44 rebase that changeset and all of its descendants onto dest. If you
45 specify base (``-b/--base``), rebase will select ancestors of base
45 specify base (``-b/--base``), rebase will select ancestors of base
46 back to but not including the common ancestor with dest. Thus,
46 back to but not including the common ancestor with dest. Thus,
47 ``-b`` is less precise but more convenient than ``-s``: you can
47 ``-b`` is less precise but more convenient than ``-s``: you can
48 specify any changeset in the source branch, and rebase will select
48 specify any changeset in the source branch, and rebase will select
49 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
49 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
50 uses the parent of the working directory as the base.
50 uses the parent of the working directory as the base.
51
51
52 By default, rebase recreates the changesets in the source branch
52 By default, rebase recreates the changesets in the source branch
53 as descendants of dest and then destroys the originals. Use
53 as descendants of dest and then destroys the originals. Use
54 ``--keep`` to preserve the original source changesets. Some
54 ``--keep`` to preserve the original source changesets. Some
55 changesets in the source branch (e.g. merges from the destination
55 changesets in the source branch (e.g. merges from the destination
56 branch) may be dropped if they no longer contribute any change.
56 branch) may be dropped if they no longer contribute any change.
57
57
58 One result of the rules for selecting the destination changeset
58 One result of the rules for selecting the destination changeset
59 and source branch is that, unlike ``merge``, rebase will do
59 and source branch is that, unlike ``merge``, rebase will do
60 nothing if you are at the latest (tipmost) head of a named branch
60 nothing if you are at the latest (tipmost) head of a named branch
61 with two heads. You need to explicitly specify source and/or
61 with two heads. You need to explicitly specify source and/or
62 destination (or ``update`` to the other head, if it's the head of
62 destination (or ``update`` to the other head, if it's the head of
63 the intended source branch).
63 the intended source branch).
64
64
65 If a rebase is interrupted to manually resolve a merge, it can be
65 If a rebase is interrupted to manually resolve a merge, it can be
66 continued with --continue/-c or aborted with --abort/-a.
66 continued with --continue/-c or aborted with --abort/-a.
67 """
67 """
68 originalwd = target = None
68 originalwd = target = None
69 external = nullrev
69 external = nullrev
70 state = {}
70 state = {}
71 skipped = set()
71 skipped = set()
72 targetancestors = set()
72 targetancestors = set()
73
73
74 lock = wlock = None
74 lock = wlock = None
75 try:
75 try:
76 lock = repo.lock()
76 lock = repo.lock()
77 wlock = repo.wlock()
77 wlock = repo.wlock()
78
78
79 # Validate input and define rebasing points
79 # Validate input and define rebasing points
80 destf = opts.get('dest', None)
80 destf = opts.get('dest', None)
81 srcf = opts.get('source', None)
81 srcf = opts.get('source', None)
82 basef = opts.get('base', None)
82 basef = opts.get('base', None)
83 contf = opts.get('continue')
83 contf = opts.get('continue')
84 abortf = opts.get('abort')
84 abortf = opts.get('abort')
85 collapsef = opts.get('collapse', False)
85 collapsef = opts.get('collapse', False)
86 extrafn = opts.get('extrafn')
86 extrafn = opts.get('extrafn')
87 keepf = opts.get('keep', False)
87 keepf = opts.get('keep', False)
88 keepbranchesf = opts.get('keepbranches', False)
88 keepbranchesf = opts.get('keepbranches', False)
89 detachf = opts.get('detach', False)
89 detachf = opts.get('detach', False)
90 # keepopen is not meant for use on the command line, but by
90 # keepopen is not meant for use on the command line, but by
91 # other extensions
91 # other extensions
92 keepopen = opts.get('keepopen', False)
92 keepopen = opts.get('keepopen', False)
93
93
94 if contf or abortf:
94 if contf or abortf:
95 if contf and abortf:
95 if contf and abortf:
96 raise error.ParseError('rebase',
96 raise error.ParseError('rebase',
97 _('cannot use both abort and continue'))
97 _('cannot use both abort and continue'))
98 if collapsef:
98 if collapsef:
99 raise error.ParseError(
99 raise error.ParseError(
100 'rebase', _('cannot use collapse with continue or abort'))
100 'rebase', _('cannot use collapse with continue or abort'))
101
101
102 if detachf:
102 if detachf:
103 raise error.ParseError(
103 raise error.ParseError(
104 'rebase', _('cannot use detach with continue or abort'))
104 'rebase', _('cannot use detach with continue or abort'))
105
105
106 if srcf or basef or destf:
106 if srcf or basef or destf:
107 raise error.ParseError('rebase',
107 raise error.ParseError('rebase',
108 _('abort and continue do not allow specifying revisions'))
108 _('abort and continue do not allow specifying revisions'))
109
109
110 (originalwd, target, state, collapsef, keepf,
110 (originalwd, target, state, collapsef, keepf,
111 keepbranchesf, external) = restorestatus(repo)
111 keepbranchesf, external) = restorestatus(repo)
112 if abortf:
112 if abortf:
113 abort(repo, originalwd, target, state)
113 abort(repo, originalwd, target, state)
114 return
114 return
115 else:
115 else:
116 if srcf and basef:
116 if srcf and basef:
117 raise error.ParseError('rebase', _('cannot specify both a '
117 raise error.ParseError('rebase', _('cannot specify both a '
118 'revision and a base'))
118 'revision and a base'))
119 if detachf:
119 if detachf:
120 if not srcf:
120 if not srcf:
121 raise error.ParseError(
121 raise error.ParseError(
122 'rebase', _('detach requires a revision to be specified'))
122 'rebase', _('detach requires a revision to be specified'))
123 if basef:
123 if basef:
124 raise error.ParseError(
124 raise error.ParseError(
125 'rebase', _('cannot specify a base with detach'))
125 'rebase', _('cannot specify a base with detach'))
126
126
127 cmdutil.bail_if_changed(repo)
127 cmdutil.bail_if_changed(repo)
128 result = buildstate(repo, destf, srcf, basef, detachf)
128 result = buildstate(repo, destf, srcf, basef, detachf)
129 if not result:
129 if not result:
130 # Empty state built, nothing to rebase
130 # Empty state built, nothing to rebase
131 ui.status(_('nothing to rebase\n'))
131 ui.status(_('nothing to rebase\n'))
132 return
132 return
133 else:
133 else:
134 originalwd, target, state = result
134 originalwd, target, state = result
135 if collapsef:
135 if collapsef:
136 targetancestors = set(repo.changelog.ancestors(target))
136 targetancestors = set(repo.changelog.ancestors(target))
137 external = checkexternal(repo, state, targetancestors)
137 external = checkexternal(repo, state, targetancestors)
138
138
139 if keepbranchesf:
139 if keepbranchesf:
140 if extrafn:
140 if extrafn:
141 raise error.ParseError(
141 raise error.ParseError(
142 'rebase', _('cannot use both keepbranches and extrafn'))
142 'rebase', _('cannot use both keepbranches and extrafn'))
143 def extrafn(ctx, extra):
143 def extrafn(ctx, extra):
144 extra['branch'] = ctx.branch()
144 extra['branch'] = ctx.branch()
145
145
146 # Rebase
146 # Rebase
147 if not targetancestors:
147 if not targetancestors:
148 targetancestors = set(repo.changelog.ancestors(target))
148 targetancestors = set(repo.changelog.ancestors(target))
149 targetancestors.add(target)
149 targetancestors.add(target)
150
150
151 for rev in sorted(state):
151 for rev in sorted(state):
152 if state[rev] == -1:
152 if state[rev] == -1:
153 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
153 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
154 storestatus(repo, originalwd, target, state, collapsef, keepf,
154 storestatus(repo, originalwd, target, state, collapsef, keepf,
155 keepbranchesf, external)
155 keepbranchesf, external)
156 p1, p2 = defineparents(repo, rev, target, state,
156 p1, p2 = defineparents(repo, rev, target, state,
157 targetancestors)
157 targetancestors)
158 if len(repo.parents()) == 2:
158 if len(repo.parents()) == 2:
159 repo.ui.debug('resuming interrupted rebase\n')
159 repo.ui.debug('resuming interrupted rebase\n')
160 else:
160 else:
161 stats = rebasenode(repo, rev, p1, p2, state)
161 stats = rebasenode(repo, rev, p1, p2, state)
162 if stats and stats[3] > 0:
162 if stats and stats[3] > 0:
163 raise util.Abort(_('fix unresolved conflicts with hg '
163 raise util.Abort(_('fix unresolved conflicts with hg '
164 'resolve then run hg rebase --continue'))
164 'resolve then run hg rebase --continue'))
165 updatedirstate(repo, rev, target, p2)
165 updatedirstate(repo, rev, target, p2)
166 if not collapsef:
166 if not collapsef:
167 extra = {'rebase_source': repo[rev].hex()}
167 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
168 if extrafn:
169 extrafn(repo[rev], extra)
170 newrev = concludenode(repo, rev, p1, p2, extra=extra)
171 else:
168 else:
172 # Skip commit if we are collapsing
169 # Skip commit if we are collapsing
173 repo.dirstate.setparents(repo[p1].node())
170 repo.dirstate.setparents(repo[p1].node())
174 newrev = None
171 newrev = None
175 # Update the state
172 # Update the state
176 if newrev is not None:
173 if newrev is not None:
177 state[rev] = repo[newrev].rev()
174 state[rev] = repo[newrev].rev()
178 else:
175 else:
179 if not collapsef:
176 if not collapsef:
180 ui.note(_('no changes, revision %d skipped\n') % rev)
177 ui.note(_('no changes, revision %d skipped\n') % rev)
181 ui.debug('next revision set to %s\n' % p1)
178 ui.debug('next revision set to %s\n' % p1)
182 skipped.add(rev)
179 skipped.add(rev)
183 state[rev] = p1
180 state[rev] = p1
184
181
185 ui.note(_('rebase merging completed\n'))
182 ui.note(_('rebase merging completed\n'))
186
183
187 if collapsef and not keepopen:
184 if collapsef and not keepopen:
188 p1, p2 = defineparents(repo, min(state), target,
185 p1, p2 = defineparents(repo, min(state), target,
189 state, targetancestors)
186 state, targetancestors)
190 commitmsg = 'Collapsed revision'
187 commitmsg = 'Collapsed revision'
191 for rebased in state:
188 for rebased in state:
192 if rebased not in skipped and state[rebased] != nullmerge:
189 if rebased not in skipped and state[rebased] != nullmerge:
193 commitmsg += '\n* %s' % repo[rebased].description()
190 commitmsg += '\n* %s' % repo[rebased].description()
194 commitmsg = ui.edit(commitmsg, repo.ui.username())
191 commitmsg = ui.edit(commitmsg, repo.ui.username())
195 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
192 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
196 extra=extrafn)
193 extrafn=extrafn)
197
194
198 if 'qtip' in repo.tags():
195 if 'qtip' in repo.tags():
199 updatemq(repo, state, skipped, **opts)
196 updatemq(repo, state, skipped, **opts)
200
197
201 if not keepf:
198 if not keepf:
202 # Remove no more useful revisions
199 # Remove no more useful revisions
203 rebased = [rev for rev in state if state[rev] != nullmerge]
200 rebased = [rev for rev in state if state[rev] != nullmerge]
204 if rebased:
201 if rebased:
205 if set(repo.changelog.descendants(min(rebased))) - set(state):
202 if set(repo.changelog.descendants(min(rebased))) - set(state):
206 ui.warn(_("warning: new changesets detected "
203 ui.warn(_("warning: new changesets detected "
207 "on source branch, not stripping\n"))
204 "on source branch, not stripping\n"))
208 else:
205 else:
209 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
206 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
210
207
211 clearstatus(repo)
208 clearstatus(repo)
212 ui.status(_("rebase completed\n"))
209 ui.status(_("rebase completed\n"))
213 if os.path.exists(repo.sjoin('undo')):
210 if os.path.exists(repo.sjoin('undo')):
214 util.unlink(repo.sjoin('undo'))
211 util.unlink(repo.sjoin('undo'))
215 if skipped:
212 if skipped:
216 ui.note(_("%d revisions have been skipped\n") % len(skipped))
213 ui.note(_("%d revisions have been skipped\n") % len(skipped))
217 finally:
214 finally:
218 release(lock, wlock)
215 release(lock, wlock)
219
216
220 def rebasemerge(repo, rev, first=False):
217 def rebasemerge(repo, rev, first=False):
221 'return the correct ancestor'
218 'return the correct ancestor'
222 oldancestor = ancestor.ancestor
219 oldancestor = ancestor.ancestor
223
220
224 def newancestor(a, b, pfunc):
221 def newancestor(a, b, pfunc):
225 if b == rev:
222 if b == rev:
226 return repo[rev].parents()[0].rev()
223 return repo[rev].parents()[0].rev()
227 return oldancestor(a, b, pfunc)
224 return oldancestor(a, b, pfunc)
228
225
229 if not first:
226 if not first:
230 ancestor.ancestor = newancestor
227 ancestor.ancestor = newancestor
231 else:
228 else:
232 repo.ui.debug("first revision, do not change ancestor\n")
229 repo.ui.debug("first revision, do not change ancestor\n")
233 try:
230 try:
234 stats = merge.update(repo, rev, True, True, False)
231 stats = merge.update(repo, rev, True, True, False)
235 return stats
232 return stats
236 finally:
233 finally:
237 ancestor.ancestor = oldancestor
234 ancestor.ancestor = oldancestor
238
235
239 def checkexternal(repo, state, targetancestors):
236 def checkexternal(repo, state, targetancestors):
240 """Check whether one or more external revisions need to be taken in
237 """Check whether one or more external revisions need to be taken in
241 consideration. In the latter case, abort.
238 consideration. In the latter case, abort.
242 """
239 """
243 external = nullrev
240 external = nullrev
244 source = min(state)
241 source = min(state)
245 for rev in state:
242 for rev in state:
246 if rev == source:
243 if rev == source:
247 continue
244 continue
248 # Check externals and fail if there are more than one
245 # Check externals and fail if there are more than one
249 for p in repo[rev].parents():
246 for p in repo[rev].parents():
250 if (p.rev() not in state
247 if (p.rev() not in state
251 and p.rev() not in targetancestors):
248 and p.rev() not in targetancestors):
252 if external != nullrev:
249 if external != nullrev:
253 raise util.Abort(_('unable to collapse, there is more '
250 raise util.Abort(_('unable to collapse, there is more '
254 'than one external parent'))
251 'than one external parent'))
255 external = p.rev()
252 external = p.rev()
256 return external
253 return external
257
254
258 def updatedirstate(repo, rev, p1, p2):
255 def updatedirstate(repo, rev, p1, p2):
259 """Keep track of renamed files in the revision that is going to be rebased
256 """Keep track of renamed files in the revision that is going to be rebased
260 """
257 """
261 # Here we simulate the copies and renames in the source changeset
258 # Here we simulate the copies and renames in the source changeset
262 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
259 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
263 m1 = repo[rev].manifest()
260 m1 = repo[rev].manifest()
264 m2 = repo[p1].manifest()
261 m2 = repo[p1].manifest()
265 for k, v in cop.iteritems():
262 for k, v in cop.iteritems():
266 if k in m1:
263 if k in m1:
267 if v in m1 or v in m2:
264 if v in m1 or v in m2:
268 repo.dirstate.copy(v, k)
265 repo.dirstate.copy(v, k)
269 if v in m2 and v not in m1:
266 if v in m2 and v not in m1:
270 repo.dirstate.remove(v)
267 repo.dirstate.remove(v)
271
268
272 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
269 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
273 'Commit the changes and store useful information in extra'
270 'Commit the changes and store useful information in extra'
274 try:
271 try:
275 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
272 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
276 if commitmsg is None:
273 if commitmsg is None:
277 commitmsg = repo[rev].description()
274 commitmsg = repo[rev].description()
278 if extra is None:
275 ctx = repo[rev]
279 extra = {}
276 extra = {'rebase_source': ctx.hex()}
277 if extrafn:
278 extrafn(ctx, extra)
280 # Commit might fail if unresolved files exist
279 # Commit might fail if unresolved files exist
281 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
280 newrev = repo.commit(text=commitmsg, user=ctx.user(),
282 date=repo[rev].date(), extra=extra)
281 date=ctx.date(), extra=extra)
283 repo.dirstate.setbranch(repo[newrev].branch())
282 repo.dirstate.setbranch(repo[newrev].branch())
284 return newrev
283 return newrev
285 except util.Abort:
284 except util.Abort:
286 # Invalidate the previous setparents
285 # Invalidate the previous setparents
287 repo.dirstate.invalidate()
286 repo.dirstate.invalidate()
288 raise
287 raise
289
288
290 def rebasenode(repo, rev, p1, p2, state):
289 def rebasenode(repo, rev, p1, p2, state):
291 'Rebase a single revision'
290 'Rebase a single revision'
292 # Merge phase
291 # Merge phase
293 # Update to target and merge it with local
292 # Update to target and merge it with local
294 if repo['.'].rev() != repo[p1].rev():
293 if repo['.'].rev() != repo[p1].rev():
295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
294 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
296 merge.update(repo, p1, False, True, False)
295 merge.update(repo, p1, False, True, False)
297 else:
296 else:
298 repo.ui.debug(" already in target\n")
297 repo.ui.debug(" already in target\n")
299 repo.dirstate.write()
298 repo.dirstate.write()
300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
299 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
301 first = repo[rev].rev() == repo[min(state)].rev()
300 first = repo[rev].rev() == repo[min(state)].rev()
302 stats = rebasemerge(repo, rev, first)
301 stats = rebasemerge(repo, rev, first)
303 return stats
302 return stats
304
303
305 def defineparents(repo, rev, target, state, targetancestors):
304 def defineparents(repo, rev, target, state, targetancestors):
306 'Return the new parent relationship of the revision that will be rebased'
305 'Return the new parent relationship of the revision that will be rebased'
307 parents = repo[rev].parents()
306 parents = repo[rev].parents()
308 p1 = p2 = nullrev
307 p1 = p2 = nullrev
309
308
310 P1n = parents[0].rev()
309 P1n = parents[0].rev()
311 if P1n in targetancestors:
310 if P1n in targetancestors:
312 p1 = target
311 p1 = target
313 elif P1n in state:
312 elif P1n in state:
314 if state[P1n] == nullmerge:
313 if state[P1n] == nullmerge:
315 p1 = target
314 p1 = target
316 else:
315 else:
317 p1 = state[P1n]
316 p1 = state[P1n]
318 else: # P1n external
317 else: # P1n external
319 p1 = target
318 p1 = target
320 p2 = P1n
319 p2 = P1n
321
320
322 if len(parents) == 2 and parents[1].rev() not in targetancestors:
321 if len(parents) == 2 and parents[1].rev() not in targetancestors:
323 P2n = parents[1].rev()
322 P2n = parents[1].rev()
324 # interesting second parent
323 # interesting second parent
325 if P2n in state:
324 if P2n in state:
326 if p1 == target: # P1n in targetancestors or external
325 if p1 == target: # P1n in targetancestors or external
327 p1 = state[P2n]
326 p1 = state[P2n]
328 else:
327 else:
329 p2 = state[P2n]
328 p2 = state[P2n]
330 else: # P2n external
329 else: # P2n external
331 if p2 != nullrev: # P1n external too => rev is a merged revision
330 if p2 != nullrev: # P1n external too => rev is a merged revision
332 raise util.Abort(_('cannot use revision %d as base, result '
331 raise util.Abort(_('cannot use revision %d as base, result '
333 'would have 3 parents') % rev)
332 'would have 3 parents') % rev)
334 p2 = P2n
333 p2 = P2n
335 repo.ui.debug(" future parents are %d and %d\n" %
334 repo.ui.debug(" future parents are %d and %d\n" %
336 (repo[p1].rev(), repo[p2].rev()))
335 (repo[p1].rev(), repo[p2].rev()))
337 return p1, p2
336 return p1, p2
338
337
339 def isagitpatch(repo, patchname):
338 def isagitpatch(repo, patchname):
340 'Return true if the given patch is in git format'
339 'Return true if the given patch is in git format'
341 mqpatch = os.path.join(repo.mq.path, patchname)
340 mqpatch = os.path.join(repo.mq.path, patchname)
342 for line in patch.linereader(file(mqpatch, 'rb')):
341 for line in patch.linereader(file(mqpatch, 'rb')):
343 if line.startswith('diff --git'):
342 if line.startswith('diff --git'):
344 return True
343 return True
345 return False
344 return False
346
345
347 def updatemq(repo, state, skipped, **opts):
346 def updatemq(repo, state, skipped, **opts):
348 'Update rebased mq patches - finalize and then import them'
347 'Update rebased mq patches - finalize and then import them'
349 mqrebase = {}
348 mqrebase = {}
350 for p in repo.mq.applied:
349 for p in repo.mq.applied:
351 if repo[p.node].rev() in state:
350 if repo[p.node].rev() in state:
352 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
351 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
353 (repo[p.node].rev(), p.name))
352 (repo[p.node].rev(), p.name))
354 mqrebase[repo[p.node].rev()] = (p.name, isagitpatch(repo, p.name))
353 mqrebase[repo[p.node].rev()] = (p.name, isagitpatch(repo, p.name))
355
354
356 if mqrebase:
355 if mqrebase:
357 repo.mq.finish(repo, mqrebase.keys())
356 repo.mq.finish(repo, mqrebase.keys())
358
357
359 # We must start import from the newest revision
358 # We must start import from the newest revision
360 for rev in sorted(mqrebase, reverse=True):
359 for rev in sorted(mqrebase, reverse=True):
361 if rev not in skipped:
360 if rev not in skipped:
362 repo.ui.debug('import mq patch %d (%s)\n'
361 repo.ui.debug('import mq patch %d (%s)\n'
363 % (state[rev], mqrebase[rev][0]))
362 % (state[rev], mqrebase[rev][0]))
364 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
363 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
365 git=mqrebase[rev][1],rev=[str(state[rev])])
364 git=mqrebase[rev][1],rev=[str(state[rev])])
366 repo.mq.save_dirty()
365 repo.mq.save_dirty()
367
366
368 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
367 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
369 external):
368 external):
370 'Store the current status to allow recovery'
369 'Store the current status to allow recovery'
371 f = repo.opener("rebasestate", "w")
370 f = repo.opener("rebasestate", "w")
372 f.write(repo[originalwd].hex() + '\n')
371 f.write(repo[originalwd].hex() + '\n')
373 f.write(repo[target].hex() + '\n')
372 f.write(repo[target].hex() + '\n')
374 f.write(repo[external].hex() + '\n')
373 f.write(repo[external].hex() + '\n')
375 f.write('%d\n' % int(collapse))
374 f.write('%d\n' % int(collapse))
376 f.write('%d\n' % int(keep))
375 f.write('%d\n' % int(keep))
377 f.write('%d\n' % int(keepbranches))
376 f.write('%d\n' % int(keepbranches))
378 for d, v in state.iteritems():
377 for d, v in state.iteritems():
379 oldrev = repo[d].hex()
378 oldrev = repo[d].hex()
380 newrev = repo[v].hex()
379 newrev = repo[v].hex()
381 f.write("%s:%s\n" % (oldrev, newrev))
380 f.write("%s:%s\n" % (oldrev, newrev))
382 f.close()
381 f.close()
383 repo.ui.debug('rebase status stored\n')
382 repo.ui.debug('rebase status stored\n')
384
383
385 def clearstatus(repo):
384 def clearstatus(repo):
386 'Remove the status files'
385 'Remove the status files'
387 if os.path.exists(repo.join("rebasestate")):
386 if os.path.exists(repo.join("rebasestate")):
388 util.unlink(repo.join("rebasestate"))
387 util.unlink(repo.join("rebasestate"))
389
388
390 def restorestatus(repo):
389 def restorestatus(repo):
391 'Restore a previously stored status'
390 'Restore a previously stored status'
392 try:
391 try:
393 target = None
392 target = None
394 collapse = False
393 collapse = False
395 external = nullrev
394 external = nullrev
396 state = {}
395 state = {}
397 f = repo.opener("rebasestate")
396 f = repo.opener("rebasestate")
398 for i, l in enumerate(f.read().splitlines()):
397 for i, l in enumerate(f.read().splitlines()):
399 if i == 0:
398 if i == 0:
400 originalwd = repo[l].rev()
399 originalwd = repo[l].rev()
401 elif i == 1:
400 elif i == 1:
402 target = repo[l].rev()
401 target = repo[l].rev()
403 elif i == 2:
402 elif i == 2:
404 external = repo[l].rev()
403 external = repo[l].rev()
405 elif i == 3:
404 elif i == 3:
406 collapse = bool(int(l))
405 collapse = bool(int(l))
407 elif i == 4:
406 elif i == 4:
408 keep = bool(int(l))
407 keep = bool(int(l))
409 elif i == 5:
408 elif i == 5:
410 keepbranches = bool(int(l))
409 keepbranches = bool(int(l))
411 else:
410 else:
412 oldrev, newrev = l.split(':')
411 oldrev, newrev = l.split(':')
413 state[repo[oldrev].rev()] = repo[newrev].rev()
412 state[repo[oldrev].rev()] = repo[newrev].rev()
414 repo.ui.debug('rebase status resumed\n')
413 repo.ui.debug('rebase status resumed\n')
415 return originalwd, target, state, collapse, keep, keepbranches, external
414 return originalwd, target, state, collapse, keep, keepbranches, external
416 except IOError, err:
415 except IOError, err:
417 if err.errno != errno.ENOENT:
416 if err.errno != errno.ENOENT:
418 raise
417 raise
419 raise util.Abort(_('no rebase in progress'))
418 raise util.Abort(_('no rebase in progress'))
420
419
421 def abort(repo, originalwd, target, state):
420 def abort(repo, originalwd, target, state):
422 'Restore the repository to its original state'
421 'Restore the repository to its original state'
423 if set(repo.changelog.descendants(target)) - set(state.values()):
422 if set(repo.changelog.descendants(target)) - set(state.values()):
424 repo.ui.warn(_("warning: new changesets detected on target branch, "
423 repo.ui.warn(_("warning: new changesets detected on target branch, "
425 "not stripping\n"))
424 "not stripping\n"))
426 else:
425 else:
427 # Strip from the first rebased revision
426 # Strip from the first rebased revision
428 merge.update(repo, repo[originalwd].rev(), False, True, False)
427 merge.update(repo, repo[originalwd].rev(), False, True, False)
429 rebased = filter(lambda x: x > -1, state.values())
428 rebased = filter(lambda x: x > -1, state.values())
430 if rebased:
429 if rebased:
431 strippoint = min(rebased)
430 strippoint = min(rebased)
432 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
431 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
433 clearstatus(repo)
432 clearstatus(repo)
434 repo.ui.status(_('rebase aborted\n'))
433 repo.ui.status(_('rebase aborted\n'))
435
434
436 def buildstate(repo, dest, src, base, detach):
435 def buildstate(repo, dest, src, base, detach):
437 'Define which revisions are going to be rebased and where'
436 'Define which revisions are going to be rebased and where'
438 targetancestors = set()
437 targetancestors = set()
439 detachset = set()
438 detachset = set()
440
439
441 if not dest:
440 if not dest:
442 # Destination defaults to the latest revision in the current branch
441 # Destination defaults to the latest revision in the current branch
443 branch = repo[None].branch()
442 branch = repo[None].branch()
444 dest = repo[branch].rev()
443 dest = repo[branch].rev()
445 else:
444 else:
446 dest = repo[dest].rev()
445 dest = repo[dest].rev()
447
446
448 # This check isn't strictly necessary, since mq detects commits over an
447 # This check isn't strictly necessary, since mq detects commits over an
449 # applied patch. But it prevents messing up the working directory when
448 # applied patch. But it prevents messing up the working directory when
450 # a partially completed rebase is blocked by mq.
449 # a partially completed rebase is blocked by mq.
451 if 'qtip' in repo.tags() and (repo[dest].node() in
450 if 'qtip' in repo.tags() and (repo[dest].node() in
452 [s.node for s in repo.mq.applied]):
451 [s.node for s in repo.mq.applied]):
453 raise util.Abort(_('cannot rebase onto an applied mq patch'))
452 raise util.Abort(_('cannot rebase onto an applied mq patch'))
454
453
455 if src:
454 if src:
456 commonbase = repo[src].ancestor(repo[dest])
455 commonbase = repo[src].ancestor(repo[dest])
457 if commonbase == repo[src]:
456 if commonbase == repo[src]:
458 raise util.Abort(_('source is ancestor of destination'))
457 raise util.Abort(_('source is ancestor of destination'))
459 if commonbase == repo[dest]:
458 if commonbase == repo[dest]:
460 raise util.Abort(_('source is descendant of destination'))
459 raise util.Abort(_('source is descendant of destination'))
461 source = repo[src].rev()
460 source = repo[src].rev()
462 if detach:
461 if detach:
463 # We need to keep track of source's ancestors up to the common base
462 # We need to keep track of source's ancestors up to the common base
464 srcancestors = set(repo.changelog.ancestors(source))
463 srcancestors = set(repo.changelog.ancestors(source))
465 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
464 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
466 detachset = srcancestors - baseancestors
465 detachset = srcancestors - baseancestors
467 detachset.remove(commonbase.rev())
466 detachset.remove(commonbase.rev())
468 else:
467 else:
469 if base:
468 if base:
470 cwd = repo[base].rev()
469 cwd = repo[base].rev()
471 else:
470 else:
472 cwd = repo['.'].rev()
471 cwd = repo['.'].rev()
473
472
474 if cwd == dest:
473 if cwd == dest:
475 repo.ui.debug('source and destination are the same\n')
474 repo.ui.debug('source and destination are the same\n')
476 return None
475 return None
477
476
478 targetancestors = set(repo.changelog.ancestors(dest))
477 targetancestors = set(repo.changelog.ancestors(dest))
479 if cwd in targetancestors:
478 if cwd in targetancestors:
480 repo.ui.debug('source is ancestor of destination\n')
479 repo.ui.debug('source is ancestor of destination\n')
481 return None
480 return None
482
481
483 cwdancestors = set(repo.changelog.ancestors(cwd))
482 cwdancestors = set(repo.changelog.ancestors(cwd))
484 if dest in cwdancestors:
483 if dest in cwdancestors:
485 repo.ui.debug('source is descendant of destination\n')
484 repo.ui.debug('source is descendant of destination\n')
486 return None
485 return None
487
486
488 cwdancestors.add(cwd)
487 cwdancestors.add(cwd)
489 rebasingbranch = cwdancestors - targetancestors
488 rebasingbranch = cwdancestors - targetancestors
490 source = min(rebasingbranch)
489 source = min(rebasingbranch)
491
490
492 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
491 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
493 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
492 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
494 state.update(dict.fromkeys(detachset, nullmerge))
493 state.update(dict.fromkeys(detachset, nullmerge))
495 state[source] = nullrev
494 state[source] = nullrev
496 return repo['.'].rev(), repo[dest].rev(), state
495 return repo['.'].rev(), repo[dest].rev(), state
497
496
498 def pullrebase(orig, ui, repo, *args, **opts):
497 def pullrebase(orig, ui, repo, *args, **opts):
499 'Call rebase after pull if the latter has been invoked with --rebase'
498 'Call rebase after pull if the latter has been invoked with --rebase'
500 if opts.get('rebase'):
499 if opts.get('rebase'):
501 if opts.get('update'):
500 if opts.get('update'):
502 del opts['update']
501 del opts['update']
503 ui.debug('--update and --rebase are not compatible, ignoring '
502 ui.debug('--update and --rebase are not compatible, ignoring '
504 'the update flag\n')
503 'the update flag\n')
505
504
506 cmdutil.bail_if_changed(repo)
505 cmdutil.bail_if_changed(repo)
507 revsprepull = len(repo)
506 revsprepull = len(repo)
508 origpostincoming = commands.postincoming
507 origpostincoming = commands.postincoming
509 def _dummy(*args, **kwargs):
508 def _dummy(*args, **kwargs):
510 pass
509 pass
511 commands.postincoming = _dummy
510 commands.postincoming = _dummy
512 try:
511 try:
513 orig(ui, repo, *args, **opts)
512 orig(ui, repo, *args, **opts)
514 finally:
513 finally:
515 commands.postincoming = origpostincoming
514 commands.postincoming = origpostincoming
516 revspostpull = len(repo)
515 revspostpull = len(repo)
517 if revspostpull > revsprepull:
516 if revspostpull > revsprepull:
518 rebase(ui, repo, **opts)
517 rebase(ui, repo, **opts)
519 branch = repo[None].branch()
518 branch = repo[None].branch()
520 dest = repo[branch].rev()
519 dest = repo[branch].rev()
521 if dest != repo['.'].rev():
520 if dest != repo['.'].rev():
522 # there was nothing to rebase we force an update
521 # there was nothing to rebase we force an update
523 hg.update(repo, dest)
522 hg.update(repo, dest)
524 else:
523 else:
525 orig(ui, repo, *args, **opts)
524 orig(ui, repo, *args, **opts)
526
525
527 def uisetup(ui):
526 def uisetup(ui):
528 'Replace pull with a decorator to provide --rebase option'
527 'Replace pull with a decorator to provide --rebase option'
529 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
528 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
530 entry[1].append(('', 'rebase', None,
529 entry[1].append(('', 'rebase', None,
531 _("rebase working directory to branch head"))
530 _("rebase working directory to branch head"))
532 )
531 )
533
532
534 cmdtable = {
533 cmdtable = {
535 "rebase":
534 "rebase":
536 (rebase,
535 (rebase,
537 [
536 [
538 ('s', 'source', '', _('rebase from the specified changeset')),
537 ('s', 'source', '', _('rebase from the specified changeset')),
539 ('b', 'base', '', _('rebase from the base of the specified changeset '
538 ('b', 'base', '', _('rebase from the base of the specified changeset '
540 '(up to greatest common ancestor of base and dest)')),
539 '(up to greatest common ancestor of base and dest)')),
541 ('d', 'dest', '', _('rebase onto the specified changeset')),
540 ('d', 'dest', '', _('rebase onto the specified changeset')),
542 ('', 'collapse', False, _('collapse the rebased changesets')),
541 ('', 'collapse', False, _('collapse the rebased changesets')),
543 ('', 'keep', False, _('keep original changesets')),
542 ('', 'keep', False, _('keep original changesets')),
544 ('', 'keepbranches', False, _('keep original branch names')),
543 ('', 'keepbranches', False, _('keep original branch names')),
545 ('', 'detach', False, _('force detaching of source from its original '
544 ('', 'detach', False, _('force detaching of source from its original '
546 'branch')),
545 'branch')),
547 ('c', 'continue', False, _('continue an interrupted rebase')),
546 ('c', 'continue', False, _('continue an interrupted rebase')),
548 ('a', 'abort', False, _('abort an interrupted rebase'))] +
547 ('a', 'abort', False, _('abort an interrupted rebase'))] +
549 templateopts,
548 templateopts,
550 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
549 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
551 'hg rebase {-a|-c}'))
550 'hg rebase {-a|-c}'))
552 }
551 }
@@ -1,149 +1,150 b''
1 Mercurial allows you to customize output of commands through
1 Mercurial allows you to customize output of commands through
2 templates. You can either pass in a template from the command
2 templates. You can either pass in a template from the command
3 line, via the --template option, or select an existing
3 line, via the --template option, or select an existing
4 template-style (--style).
4 template-style (--style).
5
5
6 You can customize output for any "log-like" command: log,
6 You can customize output for any "log-like" command: log,
7 outgoing, incoming, tip, parents, heads and glog.
7 outgoing, incoming, tip, parents, heads and glog.
8
8
9 Three styles are packaged with Mercurial: default (the style used
9 Three styles are packaged with Mercurial: default (the style used
10 when no explicit preference is passed), compact and changelog.
10 when no explicit preference is passed), compact and changelog.
11 Usage::
11 Usage::
12
12
13 $ hg log -r1 --style changelog
13 $ hg log -r1 --style changelog
14
14
15 A template is a piece of text, with markup to invoke variable
15 A template is a piece of text, with markup to invoke variable
16 expansion::
16 expansion::
17
17
18 $ hg log -r1 --template "{node}\n"
18 $ hg log -r1 --template "{node}\n"
19 b56ce7b07c52de7d5fd79fb89701ea538af65746
19 b56ce7b07c52de7d5fd79fb89701ea538af65746
20
20
21 Strings in curly braces are called keywords. The availability of
21 Strings in curly braces are called keywords. The availability of
22 keywords depends on the exact context of the templater. These
22 keywords depends on the exact context of the templater. These
23 keywords are usually available for templating a log-like command:
23 keywords are usually available for templating a log-like command:
24
24
25 :author: String. The unmodified author of the changeset.
25 :author: String. The unmodified author of the changeset.
26
26
27 :branches: String. The name of the branch on which the changeset was
27 :branches: String. The name of the branch on which the changeset was
28 committed. Will be empty if the branch name was default.
28 committed. Will be empty if the branch name was default.
29
29
30 :date: Date information. The date when the changeset was committed.
30 :date: Date information. The date when the changeset was committed.
31
31
32 :desc: String. The text of the changeset description.
32 :desc: String. The text of the changeset description.
33
33
34 :diffstat: String. Statistics of changes with the following format:
34 :diffstat: String. Statistics of changes with the following format:
35 "modified files: +added/-removed lines"
35 "modified files: +added/-removed lines"
36
36
37 :files: List of strings. All files modified, added, or removed by this
37 :files: List of strings. All files modified, added, or removed by this
38 changeset.
38 changeset.
39
39
40 :file_adds: List of strings. Files added by this changeset.
40 :file_adds: List of strings. Files added by this changeset.
41
41
42 :file_copies: List of strings. Files copied in this changeset with
42 :file_copies: List of strings. Files copied in this changeset with
43 their sources.
43 their sources.
44
44
45 :file_copies_switch: List of strings. Like "file_copies" but displayed
45 :file_copies_switch: List of strings. Like "file_copies" but displayed
46 only if the --copied switch is set.
46 only if the --copied switch is set.
47
47
48 :file_mods: List of strings. Files modified by this changeset.
48 :file_mods: List of strings. Files modified by this changeset.
49
49
50 :file_dels: List of strings. Files removed by this changeset.
50 :file_dels: List of strings. Files removed by this changeset.
51
51
52 :node: String. The changeset identification hash, as a 40-character
52 :node: String. The changeset identification hash, as a 40-character
53 hexadecimal string.
53 hexadecimal string.
54
54
55 :parents: List of strings. The parents of the changeset.
55 :parents: List of strings. The parents of the changeset.
56
56
57 :rev: Integer. The repository-local changeset revision number.
57 :rev: Integer. The repository-local changeset revision number.
58
58
59 :tags: List of strings. Any tags associated with the changeset.
59 :tags: List of strings. Any tags associated with the changeset.
60
60
61 :latesttag: String. Most recent global tag in the ancestors of this
61 :latesttag: String. Most recent global tag in the ancestors of this
62 changeset.
62 changeset.
63
63
64 :latesttagdistance: Integer. Longest path to the latest tag.
64 :latesttagdistance: Integer. Longest path to the latest tag.
65
65
66 The "date" keyword does not produce human-readable output. If you
66 The "date" keyword does not produce human-readable output. If you
67 want to use a date in your output, you can use a filter to process
67 want to use a date in your output, you can use a filter to process
68 it. Filters are functions which return a string based on the input
68 it. Filters are functions which return a string based on the input
69 variable. You can also use a chain of filters to get the desired
69 variable. Be sure to use the stringify filter first when you're
70 output::
70 applying a string-input filter to a list-like input variable.
71 You can also use a chain of filters to get the desired output::
71
72
72 $ hg tip --template "{date|isodate}\n"
73 $ hg tip --template "{date|isodate}\n"
73 2008-08-21 18:22 +0000
74 2008-08-21 18:22 +0000
74
75
75 List of filters:
76 List of filters:
76
77
77 :addbreaks: Any text. Add an XHTML "<br />" tag before the end of
78 :addbreaks: Any text. Add an XHTML "<br />" tag before the end of
78 every line except the last.
79 every line except the last.
79
80
80 :age: Date. Returns a human-readable date/time difference between the
81 :age: Date. Returns a human-readable date/time difference between the
81 given date/time and the current date/time.
82 given date/time and the current date/time.
82
83
83 :basename: Any text. Treats the text as a path, and returns the last
84 :basename: Any text. Treats the text as a path, and returns the last
84 component of the path after splitting by the path separator
85 component of the path after splitting by the path separator
85 (ignoring trailing separators). For example, "foo/bar/baz" becomes
86 (ignoring trailing separators). For example, "foo/bar/baz" becomes
86 "baz" and "foo/bar//" becomes "bar".
87 "baz" and "foo/bar//" becomes "bar".
87
88
88 :stripdir: Treat the text as path and strip a directory level, if
89 :stripdir: Treat the text as path and strip a directory level, if
89 possible. For example, "foo" and "foo/bar" becomes "foo".
90 possible. For example, "foo" and "foo/bar" becomes "foo".
90
91
91 :date: Date. Returns a date in a Unix date format, including the
92 :date: Date. Returns a date in a Unix date format, including the
92 timezone: "Mon Sep 04 15:13:13 2006 0700".
93 timezone: "Mon Sep 04 15:13:13 2006 0700".
93
94
94 :domain: Any text. Finds the first string that looks like an email
95 :domain: Any text. Finds the first string that looks like an email
95 address, and extracts just the domain component. Example: ``User
96 address, and extracts just the domain component. Example: ``User
96 <user@example.com>`` becomes ``example.com``.
97 <user@example.com>`` becomes ``example.com``.
97
98
98 :email: Any text. Extracts the first string that looks like an email
99 :email: Any text. Extracts the first string that looks like an email
99 address. Example: ``User <user@example.com>`` becomes
100 address. Example: ``User <user@example.com>`` becomes
100 ``user@example.com``.
101 ``user@example.com``.
101
102
102 :escape: Any text. Replaces the special XML/XHTML characters "&", "<"
103 :escape: Any text. Replaces the special XML/XHTML characters "&", "<"
103 and ">" with XML entities.
104 and ">" with XML entities.
104
105
105 :fill68: Any text. Wraps the text to fit in 68 columns.
106 :fill68: Any text. Wraps the text to fit in 68 columns.
106
107
107 :fill76: Any text. Wraps the text to fit in 76 columns.
108 :fill76: Any text. Wraps the text to fit in 76 columns.
108
109
109 :firstline: Any text. Returns the first line of text.
110 :firstline: Any text. Returns the first line of text.
110
111
111 :nonempty: Any text. Returns '(none)' if the string is empty.
112 :nonempty: Any text. Returns '(none)' if the string is empty.
112
113
113 :hgdate: Date. Returns the date as a pair of numbers: "1157407993
114 :hgdate: Date. Returns the date as a pair of numbers: "1157407993
114 25200" (Unix timestamp, timezone offset).
115 25200" (Unix timestamp, timezone offset).
115
116
116 :isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
117 :isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
117 +0200".
118 +0200".
118
119
119 :isodatesec: Date. Returns the date in ISO 8601 format, including
120 :isodatesec: Date. Returns the date in ISO 8601 format, including
120 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
121 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
121 filter.
122 filter.
122
123
123 :localdate: Date. Converts a date to local date.
124 :localdate: Date. Converts a date to local date.
124
125
125 :obfuscate: Any text. Returns the input text rendered as a sequence of
126 :obfuscate: Any text. Returns the input text rendered as a sequence of
126 XML entities.
127 XML entities.
127
128
128 :person: Any text. Returns the text before an email address.
129 :person: Any text. Returns the text before an email address.
129
130
130 :rfc822date: Date. Returns a date using the same format used in email
131 :rfc822date: Date. Returns a date using the same format used in email
131 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
132 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
132
133
133 :rfc3339date: Date. Returns a date using the Internet date format
134 :rfc3339date: Date. Returns a date using the Internet date format
134 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
135 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
135
136
136 :short: Changeset hash. Returns the short form of a changeset hash,
137 :short: Changeset hash. Returns the short form of a changeset hash,
137 i.e. a 12-byte hexadecimal string.
138 i.e. a 12-byte hexadecimal string.
138
139
139 :shortdate: Date. Returns a date like "2006-09-18".
140 :shortdate: Date. Returns a date like "2006-09-18".
140
141
141 :strip: Any text. Strips all leading and trailing whitespace.
142 :strip: Any text. Strips all leading and trailing whitespace.
142
143
143 :tabindent: Any text. Returns the text, with every line except the
144 :tabindent: Any text. Returns the text, with every line except the
144 first starting with a tab character.
145 first starting with a tab character.
145
146
146 :urlescape: Any text. Escapes all "special" characters. For example,
147 :urlescape: Any text. Escapes all "special" characters. For example,
147 "foo bar" becomes "foo%20bar".
148 "foo bar" becomes "foo%20bar".
148
149
149 :user: Any text. Returns the user portion of an email address.
150 :user: Any text. Returns the user portion of an email address.
@@ -1,73 +1,77 b''
1 # hgweb/wsgicgi.py - CGI->WSGI translator
1 # hgweb/wsgicgi.py - CGI->WSGI translator
2 #
2 #
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
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 # This was originally copied from the public domain code at
8 # This was originally copied from the public domain code at
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
10
10
11 import os, sys
11 import os, sys
12 from mercurial import util
12 from mercurial import util
13
13
14 def launch(application):
14 def launch(application):
15 util.set_binary(sys.stdin)
15 util.set_binary(sys.stdin)
16 util.set_binary(sys.stdout)
16 util.set_binary(sys.stdout)
17
17
18 environ = dict(os.environ.iteritems())
18 environ = dict(os.environ.iteritems())
19 environ.setdefault('PATH_INFO', '')
19 environ.setdefault('PATH_INFO', '')
20 if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
20 if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
21 # IIS includes script_name in path_info
21 # IIS includes script_name in path_info
22 scriptname = environ['SCRIPT_NAME']
22 scriptname = environ['SCRIPT_NAME']
23 if environ['PATH_INFO'].startswith(scriptname):
23 if environ['PATH_INFO'].startswith(scriptname):
24 environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
24 environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
25
25
26 environ['wsgi.input'] = sys.stdin
26 environ['wsgi.input'] = sys.stdin
27 environ['wsgi.errors'] = sys.stderr
27 environ['wsgi.errors'] = sys.stderr
28 environ['wsgi.version'] = (1, 0)
28 environ['wsgi.version'] = (1, 0)
29 environ['wsgi.multithread'] = False
29 environ['wsgi.multithread'] = False
30 environ['wsgi.multiprocess'] = True
30 environ['wsgi.multiprocess'] = True
31 environ['wsgi.run_once'] = True
31 environ['wsgi.run_once'] = True
32
32
33 if environ.get('HTTPS', 'off').lower() in ('on', '1', 'yes'):
33 if environ.get('HTTPS', 'off').lower() in ('on', '1', 'yes'):
34 environ['wsgi.url_scheme'] = 'https'
34 environ['wsgi.url_scheme'] = 'https'
35 else:
35 else:
36 environ['wsgi.url_scheme'] = 'http'
36 environ['wsgi.url_scheme'] = 'http'
37
37
38 headers_set = []
38 headers_set = []
39 headers_sent = []
39 headers_sent = []
40 out = sys.stdout
40 out = sys.stdout
41
41
42 def write(data):
42 def write(data):
43 if not headers_set:
43 if not headers_set:
44 raise AssertionError("write() before start_response()")
44 raise AssertionError("write() before start_response()")
45
45
46 elif not headers_sent:
46 elif not headers_sent:
47 # Before the first output, send the stored headers
47 # Before the first output, send the stored headers
48 status, response_headers = headers_sent[:] = headers_set
48 status, response_headers = headers_sent[:] = headers_set
49 out.write('Status: %s\r\n' % status)
49 out.write('Status: %s\r\n' % status)
50 for header in response_headers:
50 for header in response_headers:
51 out.write('%s: %s\r\n' % header)
51 out.write('%s: %s\r\n' % header)
52 out.write('\r\n')
52 out.write('\r\n')
53
53
54 out.write(data)
54 out.write(data)
55 out.flush()
55 out.flush()
56
56
57 def start_response(status, response_headers, exc_info=None):
57 def start_response(status, response_headers, exc_info=None):
58 if exc_info:
58 if exc_info:
59 try:
59 try:
60 if headers_sent:
60 if headers_sent:
61 # Re-raise original exception if headers sent
61 # Re-raise original exception if headers sent
62 raise exc_info[0](exc_info[1], exc_info[2])
62 raise exc_info[0](exc_info[1], exc_info[2])
63 finally:
63 finally:
64 exc_info = None # avoid dangling circular ref
64 exc_info = None # avoid dangling circular ref
65 elif headers_set:
65 elif headers_set:
66 raise AssertionError("Headers already set!")
66 raise AssertionError("Headers already set!")
67
67
68 headers_set[:] = [status, response_headers]
68 headers_set[:] = [status, response_headers]
69 return write
69 return write
70
70
71 content = application(environ, start_response)
71 content = application(environ, start_response)
72 try:
72 for chunk in content:
73 for chunk in content:
73 write(chunk)
74 write(chunk)
75 finally:
76 if hasattr(content, 'close'):
77 content.close()
@@ -1,308 +1,314 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # This is the mercurial setup script.
3 # This is the mercurial setup script.
4 #
4 #
5 # 'python setup.py install', or
5 # 'python setup.py install', or
6 # 'python setup.py --help' for more options
6 # 'python setup.py --help' for more options
7
7
8 import sys
8 import sys
9 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
9 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
10 raise SystemExit("Mercurial requires Python 2.4 or later.")
10 raise SystemExit("Mercurial requires Python 2.4 or later.")
11
11
12 # Solaris Python packaging brain damage
12 # Solaris Python packaging brain damage
13 try:
13 try:
14 import hashlib
14 import hashlib
15 sha = hashlib.sha1()
15 sha = hashlib.sha1()
16 except:
16 except:
17 try:
17 try:
18 import sha
18 import sha
19 except:
19 except:
20 raise SystemExit(
20 raise SystemExit(
21 "Couldn't import standard hashlib (incomplete Python install).")
21 "Couldn't import standard hashlib (incomplete Python install).")
22
22
23 try:
23 try:
24 import zlib
24 import zlib
25 except:
25 except:
26 raise SystemExit(
26 raise SystemExit(
27 "Couldn't import standard zlib (incomplete Python install).")
27 "Couldn't import standard zlib (incomplete Python install).")
28
28
29 try:
30 import bz2
31 except:
32 raise SystemExit(
33 "Couldn't import standard bz2 (incomplete Python install).")
34
29 import os, subprocess, time
35 import os, subprocess, time
30 import shutil
36 import shutil
31 import tempfile
37 import tempfile
32 from distutils.core import setup, Extension
38 from distutils.core import setup, Extension
33 from distutils.dist import Distribution
39 from distutils.dist import Distribution
34 from distutils.command.install_data import install_data
40 from distutils.command.install_data import install_data
35 from distutils.command.build import build
41 from distutils.command.build import build
36 from distutils.command.build_py import build_py
42 from distutils.command.build_py import build_py
37 from distutils.spawn import spawn, find_executable
43 from distutils.spawn import spawn, find_executable
38 from distutils.ccompiler import new_compiler
44 from distutils.ccompiler import new_compiler
39
45
40 scripts = ['hg']
46 scripts = ['hg']
41 if os.name == 'nt':
47 if os.name == 'nt':
42 scripts.append('contrib/win32/hg.bat')
48 scripts.append('contrib/win32/hg.bat')
43
49
44 # simplified version of distutils.ccompiler.CCompiler.has_function
50 # simplified version of distutils.ccompiler.CCompiler.has_function
45 # that actually removes its temporary files.
51 # that actually removes its temporary files.
46 def hasfunction(cc, funcname):
52 def hasfunction(cc, funcname):
47 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
53 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
48 devnull = oldstderr = None
54 devnull = oldstderr = None
49 try:
55 try:
50 try:
56 try:
51 fname = os.path.join(tmpdir, 'funcname.c')
57 fname = os.path.join(tmpdir, 'funcname.c')
52 f = open(fname, 'w')
58 f = open(fname, 'w')
53 f.write('int main(void) {\n')
59 f.write('int main(void) {\n')
54 f.write(' %s();\n' % funcname)
60 f.write(' %s();\n' % funcname)
55 f.write('}\n')
61 f.write('}\n')
56 f.close()
62 f.close()
57 # Redirect stderr to /dev/null to hide any error messages
63 # Redirect stderr to /dev/null to hide any error messages
58 # from the compiler.
64 # from the compiler.
59 # This will have to be changed if we ever have to check
65 # This will have to be changed if we ever have to check
60 # for a function on Windows.
66 # for a function on Windows.
61 devnull = open('/dev/null', 'w')
67 devnull = open('/dev/null', 'w')
62 oldstderr = os.dup(sys.stderr.fileno())
68 oldstderr = os.dup(sys.stderr.fileno())
63 os.dup2(devnull.fileno(), sys.stderr.fileno())
69 os.dup2(devnull.fileno(), sys.stderr.fileno())
64 objects = cc.compile([fname], output_dir=tmpdir)
70 objects = cc.compile([fname], output_dir=tmpdir)
65 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
71 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
66 except:
72 except:
67 return False
73 return False
68 return True
74 return True
69 finally:
75 finally:
70 if oldstderr is not None:
76 if oldstderr is not None:
71 os.dup2(oldstderr, sys.stderr.fileno())
77 os.dup2(oldstderr, sys.stderr.fileno())
72 if devnull is not None:
78 if devnull is not None:
73 devnull.close()
79 devnull.close()
74 shutil.rmtree(tmpdir)
80 shutil.rmtree(tmpdir)
75
81
76 # py2exe needs to be installed to work
82 # py2exe needs to be installed to work
77 try:
83 try:
78 import py2exe
84 import py2exe
79 py2exeloaded = True
85 py2exeloaded = True
80
86
81 # Help py2exe to find win32com.shell
87 # Help py2exe to find win32com.shell
82 try:
88 try:
83 import modulefinder
89 import modulefinder
84 import win32com
90 import win32com
85 for p in win32com.__path__[1:]: # Take the path to win32comext
91 for p in win32com.__path__[1:]: # Take the path to win32comext
86 modulefinder.AddPackagePath("win32com", p)
92 modulefinder.AddPackagePath("win32com", p)
87 pn = "win32com.shell"
93 pn = "win32com.shell"
88 __import__(pn)
94 __import__(pn)
89 m = sys.modules[pn]
95 m = sys.modules[pn]
90 for p in m.__path__[1:]:
96 for p in m.__path__[1:]:
91 modulefinder.AddPackagePath(pn, p)
97 modulefinder.AddPackagePath(pn, p)
92 except ImportError:
98 except ImportError:
93 pass
99 pass
94
100
95 except ImportError:
101 except ImportError:
96 py2exeloaded = False
102 py2exeloaded = False
97 pass
103 pass
98
104
99 def runcmd(cmd, env):
105 def runcmd(cmd, env):
100 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
106 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
101 stderr=subprocess.PIPE, env=env)
107 stderr=subprocess.PIPE, env=env)
102 out, err = p.communicate()
108 out, err = p.communicate()
103 # If root is executing setup.py, but the repository is owned by
109 # If root is executing setup.py, but the repository is owned by
104 # another user (as in "sudo python setup.py install") we will get
110 # another user (as in "sudo python setup.py install") we will get
105 # trust warnings since the .hg/hgrc file is untrusted. That is
111 # trust warnings since the .hg/hgrc file is untrusted. That is
106 # fine, we don't want to load it anyway. Python may warn about
112 # fine, we don't want to load it anyway. Python may warn about
107 # a missing __init__.py in mercurial/locale, we also ignore that.
113 # a missing __init__.py in mercurial/locale, we also ignore that.
108 err = [e for e in err.splitlines()
114 err = [e for e in err.splitlines()
109 if not e.startswith('Not trusting file') \
115 if not e.startswith('Not trusting file') \
110 and not e.startswith('warning: Not importing')]
116 and not e.startswith('warning: Not importing')]
111 if err:
117 if err:
112 return ''
118 return ''
113 return out
119 return out
114
120
115 version = ''
121 version = ''
116
122
117 if os.path.isdir('.hg'):
123 if os.path.isdir('.hg'):
118 # Execute hg out of this directory with a custom environment which
124 # Execute hg out of this directory with a custom environment which
119 # includes the pure Python modules in mercurial/pure. We also take
125 # includes the pure Python modules in mercurial/pure. We also take
120 # care to not use any hgrc files and do no localization.
126 # care to not use any hgrc files and do no localization.
121 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
127 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
122 env = {'PYTHONPATH': os.pathsep.join(pypath),
128 env = {'PYTHONPATH': os.pathsep.join(pypath),
123 'HGRCPATH': '',
129 'HGRCPATH': '',
124 'LANGUAGE': 'C'}
130 'LANGUAGE': 'C'}
125 if 'LD_LIBRARY_PATH' in os.environ:
131 if 'LD_LIBRARY_PATH' in os.environ:
126 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
132 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
127 if 'SystemRoot' in os.environ:
133 if 'SystemRoot' in os.environ:
128 # Copy SystemRoot into the custom environment for Python 2.6
134 # Copy SystemRoot into the custom environment for Python 2.6
129 # under Windows. Otherwise, the subprocess will fail with
135 # under Windows. Otherwise, the subprocess will fail with
130 # error 0xc0150004. See: http://bugs.python.org/issue3440
136 # error 0xc0150004. See: http://bugs.python.org/issue3440
131 env['SystemRoot'] = os.environ['SystemRoot']
137 env['SystemRoot'] = os.environ['SystemRoot']
132 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
138 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
133 l = runcmd(cmd, env).split()
139 l = runcmd(cmd, env).split()
134 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
140 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
135 l.pop()
141 l.pop()
136 if len(l) > 1: # tag found
142 if len(l) > 1: # tag found
137 version = l[-1]
143 version = l[-1]
138 if l[0].endswith('+'): # propagate the dirty status to the tag
144 if l[0].endswith('+'): # propagate the dirty status to the tag
139 version += '+'
145 version += '+'
140 elif len(l) == 1: # no tag found
146 elif len(l) == 1: # no tag found
141 cmd = [sys.executable, 'hg', 'parents', '--template',
147 cmd = [sys.executable, 'hg', 'parents', '--template',
142 '{latesttag}+{latesttagdistance}-']
148 '{latesttag}+{latesttagdistance}-']
143 version = runcmd(cmd, env) + l[0]
149 version = runcmd(cmd, env) + l[0]
144 if version.endswith('+'):
150 if version.endswith('+'):
145 version += time.strftime('%Y%m%d')
151 version += time.strftime('%Y%m%d')
146 elif os.path.exists('.hg_archival.txt'):
152 elif os.path.exists('.hg_archival.txt'):
147 kw = dict([[t.strip() for t in l.split(':', 1)]
153 kw = dict([[t.strip() for t in l.split(':', 1)]
148 for l in open('.hg_archival.txt')])
154 for l in open('.hg_archival.txt')])
149 if 'tag' in kw:
155 if 'tag' in kw:
150 version = kw['tag']
156 version = kw['tag']
151 elif 'latesttag' in kw:
157 elif 'latesttag' in kw:
152 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
158 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
153 else:
159 else:
154 version = kw.get('node', '')[:12]
160 version = kw.get('node', '')[:12]
155
161
156 if version:
162 if version:
157 f = open("mercurial/__version__.py", "w")
163 f = open("mercurial/__version__.py", "w")
158 f.write('# this file is autogenerated by setup.py\n')
164 f.write('# this file is autogenerated by setup.py\n')
159 f.write('version = "%s"\n' % version)
165 f.write('version = "%s"\n' % version)
160 f.close()
166 f.close()
161
167
162
168
163 try:
169 try:
164 from mercurial import __version__
170 from mercurial import __version__
165 version = __version__.version
171 version = __version__.version
166 except ImportError:
172 except ImportError:
167 version = 'unknown'
173 version = 'unknown'
168
174
169 class hgbuildmo(build):
175 class hgbuildmo(build):
170
176
171 description = "build translations (.mo files)"
177 description = "build translations (.mo files)"
172
178
173 def run(self):
179 def run(self):
174 if not find_executable('msgfmt'):
180 if not find_executable('msgfmt'):
175 self.warn("could not find msgfmt executable, no translations "
181 self.warn("could not find msgfmt executable, no translations "
176 "will be built")
182 "will be built")
177 return
183 return
178
184
179 podir = 'i18n'
185 podir = 'i18n'
180 if not os.path.isdir(podir):
186 if not os.path.isdir(podir):
181 self.warn("could not find %s/ directory" % podir)
187 self.warn("could not find %s/ directory" % podir)
182 return
188 return
183
189
184 join = os.path.join
190 join = os.path.join
185 for po in os.listdir(podir):
191 for po in os.listdir(podir):
186 if not po.endswith('.po'):
192 if not po.endswith('.po'):
187 continue
193 continue
188 pofile = join(podir, po)
194 pofile = join(podir, po)
189 modir = join('locale', po[:-3], 'LC_MESSAGES')
195 modir = join('locale', po[:-3], 'LC_MESSAGES')
190 mofile = join(modir, 'hg.mo')
196 mofile = join(modir, 'hg.mo')
191 mobuildfile = join('mercurial', mofile)
197 mobuildfile = join('mercurial', mofile)
192 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
198 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
193 if sys.platform != 'sunos5':
199 if sys.platform != 'sunos5':
194 # msgfmt on Solaris does not know about -c
200 # msgfmt on Solaris does not know about -c
195 cmd.append('-c')
201 cmd.append('-c')
196 self.mkpath(join('mercurial', modir))
202 self.mkpath(join('mercurial', modir))
197 self.make_file([pofile], mobuildfile, spawn, (cmd,))
203 self.make_file([pofile], mobuildfile, spawn, (cmd,))
198
204
199 # Insert hgbuildmo first so that files in mercurial/locale/ are found
205 # Insert hgbuildmo first so that files in mercurial/locale/ are found
200 # when build_py is run next.
206 # when build_py is run next.
201 build.sub_commands.insert(0, ('build_mo', None))
207 build.sub_commands.insert(0, ('build_mo', None))
202
208
203 Distribution.pure = 0
209 Distribution.pure = 0
204 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
210 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
205 "code instead of C extensions"))
211 "code instead of C extensions"))
206
212
207 class hgbuildpy(build_py):
213 class hgbuildpy(build_py):
208
214
209 def finalize_options(self):
215 def finalize_options(self):
210 build_py.finalize_options(self)
216 build_py.finalize_options(self)
211
217
212 if self.distribution.pure:
218 if self.distribution.pure:
213 if self.py_modules is None:
219 if self.py_modules is None:
214 self.py_modules = []
220 self.py_modules = []
215 for ext in self.distribution.ext_modules:
221 for ext in self.distribution.ext_modules:
216 if ext.name.startswith("mercurial."):
222 if ext.name.startswith("mercurial."):
217 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
223 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
218 self.distribution.ext_modules = []
224 self.distribution.ext_modules = []
219
225
220 def find_modules(self):
226 def find_modules(self):
221 modules = build_py.find_modules(self)
227 modules = build_py.find_modules(self)
222 for module in modules:
228 for module in modules:
223 if module[0] == "mercurial.pure":
229 if module[0] == "mercurial.pure":
224 if module[1] != "__init__":
230 if module[1] != "__init__":
225 yield ("mercurial", module[1], module[2])
231 yield ("mercurial", module[1], module[2])
226 else:
232 else:
227 yield module
233 yield module
228
234
229 cmdclass = {'build_mo': hgbuildmo,
235 cmdclass = {'build_mo': hgbuildmo,
230 'build_py': hgbuildpy}
236 'build_py': hgbuildpy}
231
237
232 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
238 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
233 'hgext.highlight', 'hgext.zeroconf']
239 'hgext.highlight', 'hgext.zeroconf']
234
240
235 pymodules = []
241 pymodules = []
236
242
237 extmodules = [
243 extmodules = [
238 Extension('mercurial.base85', ['mercurial/base85.c']),
244 Extension('mercurial.base85', ['mercurial/base85.c']),
239 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
245 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
240 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
246 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
241 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
247 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
242 Extension('mercurial.parsers', ['mercurial/parsers.c']),
248 Extension('mercurial.parsers', ['mercurial/parsers.c']),
243 ]
249 ]
244
250
245 # disable osutil.c under windows + python 2.4 (issue1364)
251 # disable osutil.c under windows + python 2.4 (issue1364)
246 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
252 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
247 pymodules.append('mercurial.pure.osutil')
253 pymodules.append('mercurial.pure.osutil')
248 else:
254 else:
249 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
255 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
250
256
251 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
257 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
252 # The inotify extension is only usable with Linux 2.6 kernels.
258 # The inotify extension is only usable with Linux 2.6 kernels.
253 # You also need a reasonably recent C library.
259 # You also need a reasonably recent C library.
254 cc = new_compiler()
260 cc = new_compiler()
255 if hasfunction(cc, 'inotify_add_watch'):
261 if hasfunction(cc, 'inotify_add_watch'):
256 extmodules.append(Extension('hgext.inotify.linux._inotify',
262 extmodules.append(Extension('hgext.inotify.linux._inotify',
257 ['hgext/inotify/linux/_inotify.c']))
263 ['hgext/inotify/linux/_inotify.c']))
258 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
264 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
259
265
260 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
266 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
261 'help/*.txt']}
267 'help/*.txt']}
262
268
263 def ordinarypath(p):
269 def ordinarypath(p):
264 return p and p[0] != '.' and p[-1] != '~'
270 return p and p[0] != '.' and p[-1] != '~'
265
271
266 for root in ('templates',):
272 for root in ('templates',):
267 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
273 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
268 curdir = curdir.split(os.sep, 1)[1]
274 curdir = curdir.split(os.sep, 1)[1]
269 dirs[:] = filter(ordinarypath, dirs)
275 dirs[:] = filter(ordinarypath, dirs)
270 for f in filter(ordinarypath, files):
276 for f in filter(ordinarypath, files):
271 f = os.path.join(curdir, f)
277 f = os.path.join(curdir, f)
272 packagedata['mercurial'].append(f)
278 packagedata['mercurial'].append(f)
273
279
274 datafiles = []
280 datafiles = []
275 setupversion = version
281 setupversion = version
276 extra = {}
282 extra = {}
277
283
278 if py2exeloaded:
284 if py2exeloaded:
279 extra['console'] = [
285 extra['console'] = [
280 {'script':'hg',
286 {'script':'hg',
281 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
287 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
282 'product_version':version}]
288 'product_version':version}]
283
289
284 if os.name == 'nt':
290 if os.name == 'nt':
285 # Windows binary file versions for exe/dll files must have the
291 # Windows binary file versions for exe/dll files must have the
286 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
292 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
287 setupversion = version.split('+', 1)[0]
293 setupversion = version.split('+', 1)[0]
288
294
289 setup(name='mercurial',
295 setup(name='mercurial',
290 version=setupversion,
296 version=setupversion,
291 author='Matt Mackall',
297 author='Matt Mackall',
292 author_email='mpm@selenic.com',
298 author_email='mpm@selenic.com',
293 url='http://mercurial.selenic.com/',
299 url='http://mercurial.selenic.com/',
294 description='Scalable distributed SCM',
300 description='Scalable distributed SCM',
295 license='GNU GPLv2+',
301 license='GNU GPLv2+',
296 scripts=scripts,
302 scripts=scripts,
297 packages=packages,
303 packages=packages,
298 py_modules=pymodules,
304 py_modules=pymodules,
299 ext_modules=extmodules,
305 ext_modules=extmodules,
300 data_files=datafiles,
306 data_files=datafiles,
301 package_data=packagedata,
307 package_data=packagedata,
302 cmdclass=cmdclass,
308 cmdclass=cmdclass,
303 options=dict(py2exe=dict(packages=['hgext', 'email']),
309 options=dict(py2exe=dict(packages=['hgext', 'email']),
304 bdist_mpkg=dict(zipdist=True,
310 bdist_mpkg=dict(zipdist=True,
305 license='COPYING',
311 license='COPYING',
306 readme='contrib/macosx/Readme.html',
312 readme='contrib/macosx/Readme.html',
307 welcome='contrib/macosx/Welcome.html')),
313 welcome='contrib/macosx/Welcome.html')),
308 **extra)
314 **extra)
@@ -1,142 +1,143 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" cvs || exit 80
3 "$TESTDIR/hghave" cvs || exit 80
4
4
5 cvscall()
5 cvscall()
6 {
6 {
7 cvs -f "$@"
7 cvs -f "$@"
8 }
8 }
9
9
10 hgcat()
10 hgcat()
11 {
11 {
12 hg --cwd src-hg cat -r tip "$1"
12 hg --cwd src-hg cat -r tip "$1"
13 }
13 }
14
14
15 echo "[extensions]" >> $HGRCPATH
15 echo "[extensions]" >> $HGRCPATH
16 echo "convert = " >> $HGRCPATH
16 echo "convert = " >> $HGRCPATH
17 echo "graphlog = " >> $HGRCPATH
17 echo "graphlog = " >> $HGRCPATH
18
18
19 cat > cvshooks.py <<EOF
19 cat > cvshooks.py <<EOF
20 def cvslog(ui,repo,hooktype,log):
20 def cvslog(ui,repo,hooktype,log):
21 print "%s hook: %d entries"%(hooktype,len(log))
21 print "%s hook: %d entries"%(hooktype,len(log))
22
22
23 def cvschangesets(ui,repo,hooktype,changesets):
23 def cvschangesets(ui,repo,hooktype,changesets):
24 print "%s hook: %d changesets"%(hooktype,len(changesets))
24 print "%s hook: %d changesets"%(hooktype,len(changesets))
25 EOF
25 EOF
26 hookpath=`pwd`
26 hookpath=`pwd`
27
27
28 echo "[hooks]" >> $HGRCPATH
28 echo "[hooks]" >> $HGRCPATH
29 echo "cvslog=python:$hookpath/cvshooks.py:cvslog" >> $HGRCPATH
29 echo "cvslog=python:$hookpath/cvshooks.py:cvslog" >> $HGRCPATH
30 echo "cvschangesets=python:$hookpath/cvshooks.py:cvschangesets" >> $HGRCPATH
30 echo "cvschangesets=python:$hookpath/cvshooks.py:cvschangesets" >> $HGRCPATH
31
31
32 echo % create cvs repository
32 echo % create cvs repository
33 mkdir cvsrepo
33 mkdir cvsrepo
34 cd cvsrepo
34 cd cvsrepo
35 CVSROOT=`pwd`
35 CVSROOT=`pwd`
36 export CVSROOT
36 export CVSROOT
37 CVS_OPTIONS=-f
37 CVS_OPTIONS=-f
38 export CVS_OPTIONS
38 export CVS_OPTIONS
39 cd ..
39 cd ..
40
40
41 cvscall -q -d "$CVSROOT" init
41 cvscall -q -d "$CVSROOT" init
42
42
43 echo % create source directory
43 echo % create source directory
44 mkdir src-temp
44 mkdir src-temp
45 cd src-temp
45 cd src-temp
46 echo a > a
46 echo a > a
47 mkdir b
47 mkdir b
48 cd b
48 cd b
49 echo c > c
49 echo c > c
50 cd ..
50 cd ..
51
51
52 echo % import source directory
52 echo % import source directory
53 cvscall -q import -m import src INITIAL start
53 cvscall -q import -m import src INITIAL start
54 cd ..
54 cd ..
55
55
56 echo % checkout source directory
56 echo % checkout source directory
57 cvscall -q checkout src
57 cvscall -q checkout src
58
58
59 echo % commit a new revision changing b/c
59 echo % commit a new revision changing b/c
60 cd src
60 cd src
61 sleep 1
61 sleep 1
62 echo c >> b/c
62 echo c >> b/c
63 cvscall -q commit -mci0 . | grep '<--' |\
63 cvscall -q commit -mci0 . | grep '<--' |\
64 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
64 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
65 cd ..
65 cd ..
66
66
67 echo % convert fresh repo
67 echo % convert fresh repo
68 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
68 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
69 hgcat a
69 hgcat a
70 hgcat b/c
70 hgcat b/c
71
71
72 echo % convert fresh repo with --filemap
72 echo % convert fresh repo with --filemap
73 echo include b/c > filemap
73 echo include b/c > filemap
74 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
74 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
75 hgcat b/c
75 hgcat b/c
76 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
76 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
77
77
78 echo % 'convert full repository (issue1649)'
78 echo % 'convert full repository (issue1649)'
79 cvscall -q -d "$CVSROOT" checkout -d srcfull "." | grep -v CVSROOT
79 cvscall -q -d "$CVSROOT" checkout -d srcfull "." | grep -v CVSROOT
80 ls srcfull
80 ls srcfull
81 hg convert srcfull srcfull-hg \
81 hg convert srcfull srcfull-hg \
82 | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' \
82 | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' \
83 | grep -v 'log entries' | grep -v 'hook:'
83 | grep -v 'log entries' | grep -v 'hook:' \
84 | grep -v '^[0-3] .*' # filter instable changeset order
84 hg cat -r tip srcfull-hg/src/a
85 hg cat -r tip srcfull-hg/src/a
85 hg cat -r tip srcfull-hg/src/b/c
86 hg cat -r tip srcfull-hg/src/b/c
86
87
87 echo % commit new file revisions
88 echo % commit new file revisions
88 cd src
89 cd src
89 echo a >> a
90 echo a >> a
90 echo c >> b/c
91 echo c >> b/c
91 cvscall -q commit -mci1 . | grep '<--' |\
92 cvscall -q commit -mci1 . | grep '<--' |\
92 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
93 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
93 cd ..
94 cd ..
94
95
95 echo % convert again
96 echo % convert again
96 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
97 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
97 hgcat a
98 hgcat a
98 hgcat b/c
99 hgcat b/c
99
100
100 echo % convert again with --filemap
101 echo % convert again with --filemap
101 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
102 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
102 hgcat b/c
103 hgcat b/c
103 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
104 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
104
105
105 echo % commit branch
106 echo % commit branch
106 cd src
107 cd src
107 cvs -q update -r1.1 b/c
108 cvs -q update -r1.1 b/c
108 cvs -q tag -b branch
109 cvs -q tag -b branch
109 cvs -q update -r branch > /dev/null
110 cvs -q update -r branch > /dev/null
110 echo d >> b/c
111 echo d >> b/c
111 cvs -q commit -mci2 . | grep '<--' |\
112 cvs -q commit -mci2 . | grep '<--' |\
112 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
113 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
113 cd ..
114 cd ..
114
115
115 echo % convert again
116 echo % convert again
116 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
117 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
117 hgcat b/c
118 hgcat b/c
118
119
119 echo % convert again with --filemap
120 echo % convert again with --filemap
120 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
121 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
121 hgcat b/c
122 hgcat b/c
122 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
123 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
123
124
124 echo % commit a new revision with funny log message
125 echo % commit a new revision with funny log message
125 cd src
126 cd src
126 sleep 1
127 sleep 1
127 echo e >> a
128 echo e >> a
128 cvscall -q commit -m'funny
129 cvscall -q commit -m'funny
129 ----------------------------
130 ----------------------------
130 log message' . | grep '<--' |\
131 log message' . | grep '<--' |\
131 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
132 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
132 cd ..
133 cd ..
133
134
134 echo % convert again
135 echo % convert again
135 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
136 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
136
137
137 echo "graphlog = " >> $HGRCPATH
138 echo "graphlog = " >> $HGRCPATH
138 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
139 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
139
140
140 echo % testing debugcvsps
141 echo % testing debugcvsps
141 cd src
142 cd src
142 hg debugcvsps | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
143 hg debugcvsps | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
@@ -1,292 +1,288 b''
1 % create cvs repository
1 % create cvs repository
2 % create source directory
2 % create source directory
3 % import source directory
3 % import source directory
4 N src/a
4 N src/a
5 N src/b/c
5 N src/b/c
6
6
7 No conflicts created by this import
7 No conflicts created by this import
8
8
9 % checkout source directory
9 % checkout source directory
10 U src/a
10 U src/a
11 U src/b/c
11 U src/b/c
12 % commit a new revision changing b/c
12 % commit a new revision changing b/c
13 checking in src/b/c,v
13 checking in src/b/c,v
14 % convert fresh repo
14 % convert fresh repo
15 initializing destination src-hg repository
15 initializing destination src-hg repository
16 connecting to cvsrepo
16 connecting to cvsrepo
17 scanning source...
17 scanning source...
18 collecting CVS rlog
18 collecting CVS rlog
19 5 log entries
19 5 log entries
20 cvslog hook: 5 entries
20 cvslog hook: 5 entries
21 creating changesets
21 creating changesets
22 3 changeset entries
22 3 changeset entries
23 cvschangesets hook: 3 changesets
23 cvschangesets hook: 3 changesets
24 sorting...
24 sorting...
25 converting...
25 converting...
26 2 Initial revision
26 2 Initial revision
27 1 import
27 1 import
28 0 ci0
28 0 ci0
29 updating tags
29 updating tags
30 a
30 a
31 c
31 c
32 c
32 c
33 % convert fresh repo with --filemap
33 % convert fresh repo with --filemap
34 initializing destination src-filemap repository
34 initializing destination src-filemap repository
35 connecting to cvsrepo
35 connecting to cvsrepo
36 scanning source...
36 scanning source...
37 collecting CVS rlog
37 collecting CVS rlog
38 5 log entries
38 5 log entries
39 cvslog hook: 5 entries
39 cvslog hook: 5 entries
40 creating changesets
40 creating changesets
41 3 changeset entries
41 3 changeset entries
42 cvschangesets hook: 3 changesets
42 cvschangesets hook: 3 changesets
43 sorting...
43 sorting...
44 converting...
44 converting...
45 2 Initial revision
45 2 Initial revision
46 1 import
46 1 import
47 filtering out empty revision
47 filtering out empty revision
48 rolling back last transaction
48 rolling back last transaction
49 0 ci0
49 0 ci0
50 updating tags
50 updating tags
51 c
51 c
52 c
52 c
53 2 update tags files: .hgtags
53 2 update tags files: .hgtags
54 1 ci0 files: b/c
54 1 ci0 files: b/c
55 0 Initial revision files: b/c
55 0 Initial revision files: b/c
56 % convert full repository (issue1649)
56 % convert full repository (issue1649)
57 U srcfull/src/a
57 U srcfull/src/a
58 U srcfull/src/b/c
58 U srcfull/src/b/c
59 CVS
59 CVS
60 CVSROOT
60 CVSROOT
61 src
61 src
62 initializing destination srcfull-hg repository
62 initializing destination srcfull-hg repository
63 connecting to cvsrepo
63 connecting to cvsrepo
64 scanning source...
64 scanning source...
65 collecting CVS rlog
65 collecting CVS rlog
66 creating changesets
66 creating changesets
67 4 changeset entries
67 4 changeset entries
68 sorting...
68 sorting...
69 converting...
69 converting...
70 3 Initial revision
71 2 import
72 1 initial checkin
73 0 ci0
74 updating tags
70 updating tags
75 a
71 a
76 c
72 c
77 c
73 c
78 % commit new file revisions
74 % commit new file revisions
79 checking in src/a,v
75 checking in src/a,v
80 checking in src/b/c,v
76 checking in src/b/c,v
81 % convert again
77 % convert again
82 connecting to cvsrepo
78 connecting to cvsrepo
83 scanning source...
79 scanning source...
84 collecting CVS rlog
80 collecting CVS rlog
85 7 log entries
81 7 log entries
86 cvslog hook: 7 entries
82 cvslog hook: 7 entries
87 creating changesets
83 creating changesets
88 4 changeset entries
84 4 changeset entries
89 cvschangesets hook: 4 changesets
85 cvschangesets hook: 4 changesets
90 sorting...
86 sorting...
91 converting...
87 converting...
92 0 ci1
88 0 ci1
93 a
89 a
94 a
90 a
95 c
91 c
96 c
92 c
97 c
93 c
98 % convert again with --filemap
94 % convert again with --filemap
99 connecting to cvsrepo
95 connecting to cvsrepo
100 scanning source...
96 scanning source...
101 collecting CVS rlog
97 collecting CVS rlog
102 7 log entries
98 7 log entries
103 cvslog hook: 7 entries
99 cvslog hook: 7 entries
104 creating changesets
100 creating changesets
105 4 changeset entries
101 4 changeset entries
106 cvschangesets hook: 4 changesets
102 cvschangesets hook: 4 changesets
107 sorting...
103 sorting...
108 converting...
104 converting...
109 0 ci1
105 0 ci1
110 c
106 c
111 c
107 c
112 c
108 c
113 3 ci1 files: b/c
109 3 ci1 files: b/c
114 2 update tags files: .hgtags
110 2 update tags files: .hgtags
115 1 ci0 files: b/c
111 1 ci0 files: b/c
116 0 Initial revision files: b/c
112 0 Initial revision files: b/c
117 % commit branch
113 % commit branch
118 U b/c
114 U b/c
119 T a
115 T a
120 T b/c
116 T b/c
121 checking in src/b/c,v
117 checking in src/b/c,v
122 % convert again
118 % convert again
123 connecting to cvsrepo
119 connecting to cvsrepo
124 scanning source...
120 scanning source...
125 collecting CVS rlog
121 collecting CVS rlog
126 8 log entries
122 8 log entries
127 cvslog hook: 8 entries
123 cvslog hook: 8 entries
128 creating changesets
124 creating changesets
129 5 changeset entries
125 5 changeset entries
130 cvschangesets hook: 5 changesets
126 cvschangesets hook: 5 changesets
131 sorting...
127 sorting...
132 converting...
128 converting...
133 0 ci2
129 0 ci2
134 c
130 c
135 d
131 d
136 % convert again with --filemap
132 % convert again with --filemap
137 connecting to cvsrepo
133 connecting to cvsrepo
138 scanning source...
134 scanning source...
139 collecting CVS rlog
135 collecting CVS rlog
140 8 log entries
136 8 log entries
141 cvslog hook: 8 entries
137 cvslog hook: 8 entries
142 creating changesets
138 creating changesets
143 5 changeset entries
139 5 changeset entries
144 cvschangesets hook: 5 changesets
140 cvschangesets hook: 5 changesets
145 sorting...
141 sorting...
146 converting...
142 converting...
147 0 ci2
143 0 ci2
148 c
144 c
149 d
145 d
150 4 ci2 files: b/c
146 4 ci2 files: b/c
151 3 ci1 files: b/c
147 3 ci1 files: b/c
152 2 update tags files: .hgtags
148 2 update tags files: .hgtags
153 1 ci0 files: b/c
149 1 ci0 files: b/c
154 0 Initial revision files: b/c
150 0 Initial revision files: b/c
155 % commit a new revision with funny log message
151 % commit a new revision with funny log message
156 checking in src/a,v
152 checking in src/a,v
157 % convert again
153 % convert again
158 connecting to cvsrepo
154 connecting to cvsrepo
159 scanning source...
155 scanning source...
160 collecting CVS rlog
156 collecting CVS rlog
161 9 log entries
157 9 log entries
162 cvslog hook: 9 entries
158 cvslog hook: 9 entries
163 creating changesets
159 creating changesets
164 6 changeset entries
160 6 changeset entries
165 cvschangesets hook: 6 changesets
161 cvschangesets hook: 6 changesets
166 sorting...
162 sorting...
167 converting...
163 converting...
168 0 funny
164 0 funny
169 o 6 (branch) funny
165 o 6 (branch) funny
170 | ----------------------------
166 | ----------------------------
171 | log message files: a
167 | log message files: a
172 o 5 (branch) ci2 files: b/c
168 o 5 (branch) ci2 files: b/c
173
169
174 o 4 () ci1 files: a b/c
170 o 4 () ci1 files: a b/c
175 |
171 |
176 o 3 () update tags files: .hgtags
172 o 3 () update tags files: .hgtags
177 |
173 |
178 o 2 () ci0 files: b/c
174 o 2 () ci0 files: b/c
179 |
175 |
180 | o 1 (INITIAL) import files:
176 | o 1 (INITIAL) import files:
181 |/
177 |/
182 o 0 () Initial revision files: a b/c
178 o 0 () Initial revision files: a b/c
183
179
184 % testing debugcvsps
180 % testing debugcvsps
185 collecting CVS rlog
181 collecting CVS rlog
186 9 log entries
182 9 log entries
187 cvslog hook: 9 entries
183 cvslog hook: 9 entries
188 creating changesets
184 creating changesets
189 8 changeset entries
185 8 changeset entries
190 cvschangesets hook: 8 changesets
186 cvschangesets hook: 8 changesets
191 ---------------------
187 ---------------------
192 PatchSet 1
188 PatchSet 1
193 Date:
189 Date:
194 Author:
190 Author:
195 Branch: HEAD
191 Branch: HEAD
196 Tag: (none)
192 Tag: (none)
197 Branchpoints: INITIAL
193 Branchpoints: INITIAL
198 Log:
194 Log:
199 Initial revision
195 Initial revision
200
196
201 Members:
197 Members:
202 a:INITIAL->1.1
198 a:INITIAL->1.1
203
199
204 ---------------------
200 ---------------------
205 PatchSet 2
201 PatchSet 2
206 Date:
202 Date:
207 Author:
203 Author:
208 Branch: HEAD
204 Branch: HEAD
209 Tag: (none)
205 Tag: (none)
210 Branchpoints: INITIAL, branch
206 Branchpoints: INITIAL, branch
211 Log:
207 Log:
212 Initial revision
208 Initial revision
213
209
214 Members:
210 Members:
215 b/c:INITIAL->1.1
211 b/c:INITIAL->1.1
216
212
217 ---------------------
213 ---------------------
218 PatchSet 3
214 PatchSet 3
219 Date:
215 Date:
220 Author:
216 Author:
221 Branch: INITIAL
217 Branch: INITIAL
222 Tag: start
218 Tag: start
223 Log:
219 Log:
224 import
220 import
225
221
226 Members:
222 Members:
227 a:1.1->1.1.1.1
223 a:1.1->1.1.1.1
228 b/c:1.1->1.1.1.1
224 b/c:1.1->1.1.1.1
229
225
230 ---------------------
226 ---------------------
231 PatchSet 4
227 PatchSet 4
232 Date:
228 Date:
233 Author:
229 Author:
234 Branch: HEAD
230 Branch: HEAD
235 Tag: (none)
231 Tag: (none)
236 Log:
232 Log:
237 ci0
233 ci0
238
234
239 Members:
235 Members:
240 b/c:1.1->1.2
236 b/c:1.1->1.2
241
237
242 ---------------------
238 ---------------------
243 PatchSet 5
239 PatchSet 5
244 Date:
240 Date:
245 Author:
241 Author:
246 Branch: HEAD
242 Branch: HEAD
247 Tag: (none)
243 Tag: (none)
248 Branchpoints: branch
244 Branchpoints: branch
249 Log:
245 Log:
250 ci1
246 ci1
251
247
252 Members:
248 Members:
253 a:1.1->1.2
249 a:1.1->1.2
254
250
255 ---------------------
251 ---------------------
256 PatchSet 6
252 PatchSet 6
257 Date:
253 Date:
258 Author:
254 Author:
259 Branch: HEAD
255 Branch: HEAD
260 Tag: (none)
256 Tag: (none)
261 Log:
257 Log:
262 ci1
258 ci1
263
259
264 Members:
260 Members:
265 b/c:1.2->1.3
261 b/c:1.2->1.3
266
262
267 ---------------------
263 ---------------------
268 PatchSet 7
264 PatchSet 7
269 Date:
265 Date:
270 Author:
266 Author:
271 Branch: branch
267 Branch: branch
272 Tag: (none)
268 Tag: (none)
273 Log:
269 Log:
274 ci2
270 ci2
275
271
276 Members:
272 Members:
277 b/c:1.1->1.1.2.1
273 b/c:1.1->1.1.2.1
278
274
279 ---------------------
275 ---------------------
280 PatchSet 8
276 PatchSet 8
281 Date:
277 Date:
282 Author:
278 Author:
283 Branch: branch
279 Branch: branch
284 Tag: (none)
280 Tag: (none)
285 Log:
281 Log:
286 funny
282 funny
287 ----------------------------
283 ----------------------------
288 log message
284 log message
289
285
290 Members:
286 Members:
291 a:1.2->1.2.2.1
287 a:1.2->1.2.2.1
292
288
@@ -1,175 +1,175 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "graphlog=" >> $HGRCPATH
4 echo "graphlog=" >> $HGRCPATH
5 echo "rebase=" >> $HGRCPATH
5 echo "rebase=" >> $HGRCPATH
6
6
7 BASE=`pwd`
7 BASE=`pwd`
8
8
9 addcommit () {
9 addcommit () {
10 echo $1 > $1
10 echo $1 > $1
11 hg add $1
11 hg add $1
12 hg commit -d "${2} 0" -m $1
12 hg commit -d "${2} 0" -m $1
13 }
13 }
14
14
15 commit () {
15 commit () {
16 hg commit -d "${2} 0" -m $1
16 hg commit -d "${2} 0" -m $1
17 }
17 }
18
18
19 createrepo () {
19 createrepo () {
20 cd $BASE
20 cd $BASE
21 rm -rf a
21 rm -rf a
22 hg init a
22 hg init a
23 cd a
23 cd a
24 addcommit "A" 0
24 addcommit "A" 0
25 addcommit "B" 1
25 addcommit "B" 1
26 addcommit "C" 2
26 addcommit "C" 2
27 addcommit "D" 3
27 addcommit "D" 3
28
28
29 hg update -C 0
29 hg update -C 0
30 addcommit "E" 4
30 addcommit "E" 4
31
31
32 hg update -C 0
32 hg update -C 0
33 addcommit "F" 5
33 addcommit "F" 5
34
34
35 hg merge -r 4
35 hg merge -r 4
36 commit "G" 6
36 commit "G" 6
37
37
38 hg update -C 5
38 hg update -C 5
39 addcommit "H" 7
39 addcommit "H" 7
40 }
40 }
41
41
42 createrepo > /dev/null 2>&1
42 createrepo > /dev/null 2>&1
43 hg glog --template '{rev}: {desc}\n'
43 hg glog --template '{rev}: {desc}\n'
44 echo '% Rebasing B onto H'
44 echo '% Rebasing B onto H'
45 hg up -C 3
45 hg up -C 3
46 hg rebase --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
46 hg rebase --collapse --keepbranches 2>&1 | sed 's/\(saving bundle to \).*/\1/'
47 hg glog --template '{rev}: {desc}\n'
47 hg glog --template '{rev}: {desc}\n'
48 echo "Expected A, B, C, D, F, H"
48 echo "Expected A, B, C, D, F, H"
49 hg manifest
49 hg manifest
50
50
51 createrepo > /dev/null 2>&1
51 createrepo > /dev/null 2>&1
52 echo
52 echo
53 echo '% Rebasing G onto H'
53 echo '% Rebasing G onto H'
54 hg rebase --base 6 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
54 hg rebase --base 6 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
55 hg glog --template '{rev}: {desc}\n'
55 hg glog --template '{rev}: {desc}\n'
56 echo "Expected A, E, F, H"
56 echo "Expected A, E, F, H"
57 hg manifest
57 hg manifest
58
58
59 createrepocomplex () {
59 createrepocomplex () {
60 cd $BASE
60 cd $BASE
61 rm -rf a
61 rm -rf a
62 hg init a
62 hg init a
63 cd a
63 cd a
64 addcommit "A" 0
64 addcommit "A" 0
65 addcommit "B" 1
65 addcommit "B" 1
66
66
67 hg up 0
67 hg up 0
68 addcommit "C" 2
68 addcommit "C" 2
69 hg merge
69 hg merge
70 commit "D" 3
70 commit "D" 3
71
71
72 hg up 1
72 hg up 1
73 addcommit "E" 4
73 addcommit "E" 4
74
74
75 addcommit "F" 5
75 addcommit "F" 5
76
76
77 hg merge
77 hg merge
78 commit "G" 6
78 commit "G" 6
79
79
80 hg up 0
80 hg up 0
81 addcommit "H" 7
81 addcommit "H" 7
82 }
82 }
83
83
84 echo
84 echo
85 createrepocomplex > /dev/null 2>&1
85 createrepocomplex > /dev/null 2>&1
86 hg glog --template '{rev}: {desc}\n'
86 hg glog --template '{rev}: {desc}\n'
87
87
88 echo
88 echo
89 echo '% Rebase and collapse - more than one external (fail)'
89 echo '% Rebase and collapse - more than one external (fail)'
90 hg rebase -s 2 --collapse
90 hg rebase -s 2 --collapse
91
91
92 echo
92 echo
93 echo '% Rebase and collapse - E onto H'
93 echo '% Rebase and collapse - E onto H'
94 hg rebase -s 4 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
94 hg rebase -s 4 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
95 hg glog --template '{rev}: {desc}\n'
95 hg glog --template '{rev}: {desc}\n'
96 echo "Expected A, B, C, E, F, H"
96 echo "Expected A, B, C, E, F, H"
97 hg manifest
97 hg manifest
98
98
99 createrepocomplex () {
99 createrepocomplex () {
100 cd $BASE
100 cd $BASE
101 rm -rf a
101 rm -rf a
102 hg init a
102 hg init a
103 cd a
103 cd a
104 addcommit "A" 0
104 addcommit "A" 0
105 addcommit "B" 1
105 addcommit "B" 1
106
106
107 hg up 0
107 hg up 0
108 addcommit "C" 2
108 addcommit "C" 2
109 hg merge
109 hg merge
110 commit "D" 3
110 commit "D" 3
111
111
112 hg up 1
112 hg up 1
113 addcommit "E" 4
113 addcommit "E" 4
114
114
115 echo "F" > E
115 echo "F" > E
116 commit "F" 5
116 commit "F" 5
117
117
118 addcommit "G" 6
118 addcommit "G" 6
119
119
120 hg merge
120 hg merge
121 commit "H" 7
121 commit "H" 7
122
122
123 hg up 0
123 hg up 0
124 addcommit "I" 8
124 addcommit "I" 8
125 }
125 }
126
126
127 echo
127 echo
128 createrepocomplex > /dev/null 2>&1
128 createrepocomplex > /dev/null 2>&1
129 hg glog --template '{rev}: {desc}\n'
129 hg glog --template '{rev}: {desc}\n'
130
130
131 echo
131 echo
132 echo '% Rebase and collapse - E onto I'
132 echo '% Rebase and collapse - E onto I'
133 hg rebase -s 4 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
133 hg rebase -s 4 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
134
134
135 hg glog --template '{rev}: {desc}\n'
135 hg glog --template '{rev}: {desc}\n'
136
136
137 echo "Expected A, B, C, E, G, I"
137 echo "Expected A, B, C, E, G, I"
138 hg manifest
138 hg manifest
139
139
140 echo 'Cat E:'
140 echo 'Cat E:'
141 cat E
141 cat E
142
142
143 createrepocomplex () {
143 createrepocomplex () {
144 cd $BASE
144 cd $BASE
145 rm -rf a
145 rm -rf a
146 hg init a
146 hg init a
147 cd a
147 cd a
148 addcommit "A" 0
148 addcommit "A" 0
149 addcommit "B" 1
149 addcommit "B" 1
150
150
151 addcommit "C" 2
151 addcommit "C" 2
152 hg up 1
152 hg up 1
153
153
154 addcommit "D" 3
154 addcommit "D" 3
155
155
156 hg merge
156 hg merge
157 commit "E" 4
157 commit "E" 4
158
158
159 hg up 0
159 hg up 0
160 addcommit "F" 5
160 addcommit "F" 5
161 }
161 }
162
162
163 echo
163 echo
164 createrepocomplex > /dev/null 2>&1
164 createrepocomplex > /dev/null 2>&1
165 hg glog --template '{rev}: {desc}\n'
165 hg glog --template '{rev}: {desc}\n'
166
166
167 echo
167 echo
168 echo '% Rebase and collapse - B onto F'
168 echo '% Rebase and collapse - B onto F'
169 hg rebase -s 1 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
169 hg rebase -s 1 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
170
170
171 hg glog --template '{rev}: {desc}\n'
171 hg glog --template '{rev}: {desc}\n'
172
172
173 echo "Expected A, B, C, D, F"
173 echo "Expected A, B, C, D, F"
174 hg manifest
174 hg manifest
175 exit 0
175 exit 0
General Comments 0
You need to be logged in to leave comments. Login now