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