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