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