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