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