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