##// END OF EJS Templates
mq: more qdelete help text tweaks
Brendan Cully -
r4737:2ececafa default
parent child Browse files
Show More
@@ -1,2208 +1,2211
1 1 # queue.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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
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 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 33 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 34 from mercurial import repair
35 35 import os, sys, re, errno
36 36
37 37 commands.norepo += " qclone qversion"
38 38
39 39 # Patch names looks like unix-file names.
40 40 # They must be joinable with queue directory and result in the patch path.
41 41 normname = util.normpath
42 42
43 43 class statusentry:
44 44 def __init__(self, rev, name=None):
45 45 if not name:
46 46 fields = rev.split(':', 1)
47 47 if len(fields) == 2:
48 48 self.rev, self.name = fields
49 49 else:
50 50 self.rev, self.name = None, None
51 51 else:
52 52 self.rev, self.name = rev, name
53 53
54 54 def __str__(self):
55 55 return self.rev + ':' + self.name
56 56
57 57 class queue:
58 58 def __init__(self, ui, path, patchdir=None):
59 59 self.basepath = path
60 60 self.path = patchdir or os.path.join(path, "patches")
61 61 self.opener = util.opener(self.path)
62 62 self.ui = ui
63 63 self.applied = []
64 64 self.full_series = []
65 65 self.applied_dirty = 0
66 66 self.series_dirty = 0
67 67 self.series_path = "series"
68 68 self.status_path = "status"
69 69 self.guards_path = "guards"
70 70 self.active_guards = None
71 71 self.guards_dirty = False
72 72 self._diffopts = None
73 73
74 74 if os.path.exists(self.join(self.series_path)):
75 75 self.full_series = self.opener(self.series_path).read().splitlines()
76 76 self.parse_series()
77 77
78 78 if os.path.exists(self.join(self.status_path)):
79 79 lines = self.opener(self.status_path).read().splitlines()
80 80 self.applied = [statusentry(l) for l in lines]
81 81
82 82 def diffopts(self):
83 83 if self._diffopts is None:
84 84 self._diffopts = patch.diffopts(self.ui)
85 85 return self._diffopts
86 86
87 87 def join(self, *p):
88 88 return os.path.join(self.path, *p)
89 89
90 90 def find_series(self, patch):
91 91 pre = re.compile("(\s*)([^#]+)")
92 92 index = 0
93 93 for l in self.full_series:
94 94 m = pre.match(l)
95 95 if m:
96 96 s = m.group(2)
97 97 s = s.rstrip()
98 98 if s == patch:
99 99 return index
100 100 index += 1
101 101 return None
102 102
103 103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
104 104
105 105 def parse_series(self):
106 106 self.series = []
107 107 self.series_guards = []
108 108 for l in self.full_series:
109 109 h = l.find('#')
110 110 if h == -1:
111 111 patch = l
112 112 comment = ''
113 113 elif h == 0:
114 114 continue
115 115 else:
116 116 patch = l[:h]
117 117 comment = l[h:]
118 118 patch = patch.strip()
119 119 if patch:
120 120 if patch in self.series:
121 121 raise util.Abort(_('%s appears more than once in %s') %
122 122 (patch, self.join(self.series_path)))
123 123 self.series.append(patch)
124 124 self.series_guards.append(self.guard_re.findall(comment))
125 125
126 126 def check_guard(self, guard):
127 127 bad_chars = '# \t\r\n\f'
128 128 first = guard[0]
129 129 for c in '-+':
130 130 if first == c:
131 131 return (_('guard %r starts with invalid character: %r') %
132 132 (guard, c))
133 133 for c in bad_chars:
134 134 if c in guard:
135 135 return _('invalid character in guard %r: %r') % (guard, c)
136 136
137 137 def set_active(self, guards):
138 138 for guard in guards:
139 139 bad = self.check_guard(guard)
140 140 if bad:
141 141 raise util.Abort(bad)
142 142 guards = dict.fromkeys(guards).keys()
143 143 guards.sort()
144 144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
145 145 self.active_guards = guards
146 146 self.guards_dirty = True
147 147
148 148 def active(self):
149 149 if self.active_guards is None:
150 150 self.active_guards = []
151 151 try:
152 152 guards = self.opener(self.guards_path).read().split()
153 153 except IOError, err:
154 154 if err.errno != errno.ENOENT: raise
155 155 guards = []
156 156 for i, guard in enumerate(guards):
157 157 bad = self.check_guard(guard)
158 158 if bad:
159 159 self.ui.warn('%s:%d: %s\n' %
160 160 (self.join(self.guards_path), i + 1, bad))
161 161 else:
162 162 self.active_guards.append(guard)
163 163 return self.active_guards
164 164
165 165 def set_guards(self, idx, guards):
166 166 for g in guards:
167 167 if len(g) < 2:
168 168 raise util.Abort(_('guard %r too short') % g)
169 169 if g[0] not in '-+':
170 170 raise util.Abort(_('guard %r starts with invalid char') % g)
171 171 bad = self.check_guard(g[1:])
172 172 if bad:
173 173 raise util.Abort(bad)
174 174 drop = self.guard_re.sub('', self.full_series[idx])
175 175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
176 176 self.parse_series()
177 177 self.series_dirty = True
178 178
179 179 def pushable(self, idx):
180 180 if isinstance(idx, str):
181 181 idx = self.series.index(idx)
182 182 patchguards = self.series_guards[idx]
183 183 if not patchguards:
184 184 return True, None
185 185 default = False
186 186 guards = self.active()
187 187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
188 188 if exactneg:
189 189 return False, exactneg[0]
190 190 pos = [g for g in patchguards if g[0] == '+']
191 191 exactpos = [g for g in pos if g[1:] in guards]
192 192 if pos:
193 193 if exactpos:
194 194 return True, exactpos[0]
195 195 return False, pos
196 196 return True, ''
197 197
198 198 def explain_pushable(self, idx, all_patches=False):
199 199 write = all_patches and self.ui.write or self.ui.warn
200 200 if all_patches or self.ui.verbose:
201 201 if isinstance(idx, str):
202 202 idx = self.series.index(idx)
203 203 pushable, why = self.pushable(idx)
204 204 if all_patches and pushable:
205 205 if why is None:
206 206 write(_('allowing %s - no guards in effect\n') %
207 207 self.series[idx])
208 208 else:
209 209 if not why:
210 210 write(_('allowing %s - no matching negative guards\n') %
211 211 self.series[idx])
212 212 else:
213 213 write(_('allowing %s - guarded by %r\n') %
214 214 (self.series[idx], why))
215 215 if not pushable:
216 216 if why:
217 217 write(_('skipping %s - guarded by %r\n') %
218 218 (self.series[idx], why))
219 219 else:
220 220 write(_('skipping %s - no matching guards\n') %
221 221 self.series[idx])
222 222
223 223 def save_dirty(self):
224 224 def write_list(items, path):
225 225 fp = self.opener(path, 'w')
226 226 for i in items:
227 227 print >> fp, i
228 228 fp.close()
229 229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
230 230 if self.series_dirty: write_list(self.full_series, self.series_path)
231 231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
232 232
233 233 def readheaders(self, patch):
234 234 def eatdiff(lines):
235 235 while lines:
236 236 l = lines[-1]
237 237 if (l.startswith("diff -") or
238 238 l.startswith("Index:") or
239 239 l.startswith("===========")):
240 240 del lines[-1]
241 241 else:
242 242 break
243 243 def eatempty(lines):
244 244 while lines:
245 245 l = lines[-1]
246 246 if re.match('\s*$', l):
247 247 del lines[-1]
248 248 else:
249 249 break
250 250
251 251 pf = self.join(patch)
252 252 message = []
253 253 comments = []
254 254 user = None
255 255 date = None
256 256 format = None
257 257 subject = None
258 258 diffstart = 0
259 259
260 260 for line in file(pf):
261 261 line = line.rstrip()
262 262 if line.startswith('diff --git'):
263 263 diffstart = 2
264 264 break
265 265 if diffstart:
266 266 if line.startswith('+++ '):
267 267 diffstart = 2
268 268 break
269 269 if line.startswith("--- "):
270 270 diffstart = 1
271 271 continue
272 272 elif format == "hgpatch":
273 273 # parse values when importing the result of an hg export
274 274 if line.startswith("# User "):
275 275 user = line[7:]
276 276 elif line.startswith("# Date "):
277 277 date = line[7:]
278 278 elif not line.startswith("# ") and line:
279 279 message.append(line)
280 280 format = None
281 281 elif line == '# HG changeset patch':
282 282 format = "hgpatch"
283 283 elif (format != "tagdone" and (line.startswith("Subject: ") or
284 284 line.startswith("subject: "))):
285 285 subject = line[9:]
286 286 format = "tag"
287 287 elif (format != "tagdone" and (line.startswith("From: ") or
288 288 line.startswith("from: "))):
289 289 user = line[6:]
290 290 format = "tag"
291 291 elif format == "tag" and line == "":
292 292 # when looking for tags (subject: from: etc) they
293 293 # end once you find a blank line in the source
294 294 format = "tagdone"
295 295 elif message or line:
296 296 message.append(line)
297 297 comments.append(line)
298 298
299 299 eatdiff(message)
300 300 eatdiff(comments)
301 301 eatempty(message)
302 302 eatempty(comments)
303 303
304 304 # make sure message isn't empty
305 305 if format and format.startswith("tag") and subject:
306 306 message.insert(0, "")
307 307 message.insert(0, subject)
308 308 return (message, comments, user, date, diffstart > 1)
309 309
310 310 def removeundo(self, repo):
311 311 undo = repo.sjoin('undo')
312 312 if not os.path.exists(undo):
313 313 return
314 314 try:
315 315 os.unlink(undo)
316 316 except OSError, inst:
317 317 self.ui.warn('error removing undo: %s\n' % str(inst))
318 318
319 319 def printdiff(self, repo, node1, node2=None, files=None,
320 320 fp=None, changes=None, opts={}):
321 321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
322 322
323 323 patch.diff(repo, node1, node2, fns, match=matchfn,
324 324 fp=fp, changes=changes, opts=self.diffopts())
325 325
326 326 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
327 327 # first try just applying the patch
328 328 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 329 strict=True, merge=rev, wlock=wlock)
330 330
331 331 if err == 0:
332 332 return (err, n)
333 333
334 334 if n is None:
335 335 raise util.Abort(_("apply failed for patch %s") % patch)
336 336
337 337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338 338
339 339 # apply failed, strip away that rev and merge.
340 340 hg.clean(repo, head, wlock=wlock)
341 341 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
342 342
343 343 ctx = repo.changectx(rev)
344 344 ret = hg.merge(repo, rev, wlock=wlock)
345 345 if ret:
346 346 raise util.Abort(_("update returned %d") % ret)
347 347 n = repo.commit(None, ctx.description(), ctx.user(),
348 348 force=1, wlock=wlock)
349 349 if n == None:
350 350 raise util.Abort(_("repo commit failed"))
351 351 try:
352 352 message, comments, user, date, patchfound = mergeq.readheaders(patch)
353 353 except:
354 354 raise util.Abort(_("unable to read %s") % patch)
355 355
356 356 patchf = self.opener(patch, "w")
357 357 if comments:
358 358 comments = "\n".join(comments) + '\n\n'
359 359 patchf.write(comments)
360 360 self.printdiff(repo, head, n, fp=patchf)
361 361 patchf.close()
362 362 self.removeundo(repo)
363 363 return (0, n)
364 364
365 365 def qparents(self, repo, rev=None):
366 366 if rev is None:
367 367 (p1, p2) = repo.dirstate.parents()
368 368 if p2 == revlog.nullid:
369 369 return p1
370 370 if len(self.applied) == 0:
371 371 return None
372 372 return revlog.bin(self.applied[-1].rev)
373 373 pp = repo.changelog.parents(rev)
374 374 if pp[1] != revlog.nullid:
375 375 arevs = [ x.rev for x in self.applied ]
376 376 p0 = revlog.hex(pp[0])
377 377 p1 = revlog.hex(pp[1])
378 378 if p0 in arevs:
379 379 return pp[0]
380 380 if p1 in arevs:
381 381 return pp[1]
382 382 return pp[0]
383 383
384 384 def mergepatch(self, repo, mergeq, series, wlock):
385 385 if len(self.applied) == 0:
386 386 # each of the patches merged in will have two parents. This
387 387 # can confuse the qrefresh, qdiff, and strip code because it
388 388 # needs to know which parent is actually in the patch queue.
389 389 # so, we insert a merge marker with only one parent. This way
390 390 # the first patch in the queue is never a merge patch
391 391 #
392 392 pname = ".hg.patches.merge.marker"
393 393 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
394 394 wlock=wlock)
395 395 self.removeundo(repo)
396 396 self.applied.append(statusentry(revlog.hex(n), pname))
397 397 self.applied_dirty = 1
398 398
399 399 head = self.qparents(repo)
400 400
401 401 for patch in series:
402 402 patch = mergeq.lookup(patch, strict=True)
403 403 if not patch:
404 404 self.ui.warn("patch %s does not exist\n" % patch)
405 405 return (1, None)
406 406 pushable, reason = self.pushable(patch)
407 407 if not pushable:
408 408 self.explain_pushable(patch, all_patches=True)
409 409 continue
410 410 info = mergeq.isapplied(patch)
411 411 if not info:
412 412 self.ui.warn("patch %s is not applied\n" % patch)
413 413 return (1, None)
414 414 rev = revlog.bin(info[1])
415 415 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
416 416 if head:
417 417 self.applied.append(statusentry(revlog.hex(head), patch))
418 418 self.applied_dirty = 1
419 419 if err:
420 420 return (err, head)
421 421 self.save_dirty()
422 422 return (0, head)
423 423
424 424 def patch(self, repo, patchfile):
425 425 '''Apply patchfile to the working directory.
426 426 patchfile: file name of patch'''
427 427 files = {}
428 428 try:
429 429 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
430 430 files=files)
431 431 except Exception, inst:
432 432 self.ui.note(str(inst) + '\n')
433 433 if not self.ui.verbose:
434 434 self.ui.warn("patch failed, unable to continue (try -v)\n")
435 435 return (False, files, False)
436 436
437 437 return (True, files, fuzz)
438 438
439 439 def apply(self, repo, series, list=False, update_status=True,
440 440 strict=False, patchdir=None, merge=None, wlock=None,
441 441 all_files={}):
442 442 if not wlock:
443 443 wlock = repo.wlock()
444 444 lock = repo.lock()
445 445 tr = repo.transaction()
446 446 try:
447 447 ret = self._apply(tr, repo, series, list, update_status,
448 448 strict, patchdir, merge, wlock,
449 449 lock=lock, all_files=all_files)
450 450 tr.close()
451 451 self.save_dirty()
452 452 return ret
453 453 except:
454 454 try:
455 455 tr.abort()
456 456 finally:
457 457 repo.invalidate()
458 458 repo.dirstate.invalidate()
459 459 raise
460 460
461 461 def _apply(self, tr, repo, series, list=False, update_status=True,
462 462 strict=False, patchdir=None, merge=None, wlock=None,
463 463 lock=None, all_files={}):
464 464 # TODO unify with commands.py
465 465 if not patchdir:
466 466 patchdir = self.path
467 467 err = 0
468 468 n = None
469 469 for patchname in series:
470 470 pushable, reason = self.pushable(patchname)
471 471 if not pushable:
472 472 self.explain_pushable(patchname, all_patches=True)
473 473 continue
474 474 self.ui.warn("applying %s\n" % patchname)
475 475 pf = os.path.join(patchdir, patchname)
476 476
477 477 try:
478 478 message, comments, user, date, patchfound = self.readheaders(patchname)
479 479 except:
480 480 self.ui.warn("Unable to read %s\n" % patchname)
481 481 err = 1
482 482 break
483 483
484 484 if not message:
485 485 message = "imported patch %s\n" % patchname
486 486 else:
487 487 if list:
488 488 message.append("\nimported patch %s" % patchname)
489 489 message = '\n'.join(message)
490 490
491 491 (patcherr, files, fuzz) = self.patch(repo, pf)
492 492 all_files.update(files)
493 493 patcherr = not patcherr
494 494
495 495 if merge and files:
496 496 # Mark as removed/merged and update dirstate parent info
497 497 removed = []
498 498 merged = []
499 499 for f in files:
500 500 if os.path.exists(repo.dirstate.wjoin(f)):
501 501 merged.append(f)
502 502 else:
503 503 removed.append(f)
504 504 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
505 505 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
506 506 p1, p2 = repo.dirstate.parents()
507 507 repo.dirstate.setparents(p1, merge)
508 508 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
509 509 n = repo.commit(files, message, user, date, force=1, lock=lock,
510 510 wlock=wlock)
511 511
512 512 if n == None:
513 513 raise util.Abort(_("repo commit failed"))
514 514
515 515 if update_status:
516 516 self.applied.append(statusentry(revlog.hex(n), patchname))
517 517
518 518 if patcherr:
519 519 if not patchfound:
520 520 self.ui.warn("patch %s is empty\n" % patchname)
521 521 err = 0
522 522 else:
523 523 self.ui.warn("patch failed, rejects left in working dir\n")
524 524 err = 1
525 525 break
526 526
527 527 if fuzz and strict:
528 528 self.ui.warn("fuzz found when applying patch, stopping\n")
529 529 err = 1
530 530 break
531 531 self.removeundo(repo)
532 532 return (err, n)
533 533
534 534 def delete(self, repo, patches, opts):
535 535 if not patches and not opts.get('rev'):
536 raise util.Abort(_('missing patch name'))
536 raise util.Abort(_('qdelete requires at least one revision or '
537 'patch name'))
537 538
538 539 realpatches = []
539 540 for patch in patches:
540 541 patch = self.lookup(patch, strict=True)
541 542 info = self.isapplied(patch)
542 543 if info:
543 544 raise util.Abort(_("cannot delete applied patch %s") % patch)
544 545 if patch not in self.series:
545 546 raise util.Abort(_("patch %s not in series file") % patch)
546 547 realpatches.append(patch)
547 548
548 549 appliedbase = 0
549 550 if opts.get('rev'):
550 551 if not self.applied:
551 552 raise util.Abort(_('no patches applied'))
552 553 revs = cmdutil.revrange(repo, opts['rev'])
553 554 if len(revs) > 1 and revs[0] > revs[1]:
554 555 revs.reverse()
555 556 for rev in revs:
556 557 if appliedbase >= len(self.applied):
557 558 raise util.Abort(_("revision %d is not managed") % rev)
558 559
559 560 base = revlog.bin(self.applied[appliedbase].rev)
560 561 node = repo.changelog.node(rev)
561 562 if node != base:
562 563 raise util.Abort(_("cannot delete revision %d above "
563 564 "applied patches") % rev)
564 565 realpatches.append(self.applied[appliedbase].name)
565 566 appliedbase += 1
566 567
567 568 if not opts.get('keep'):
568 569 r = self.qrepo()
569 570 if r:
570 571 r.remove(realpatches, True)
571 572 else:
572 573 for p in realpatches:
573 574 os.unlink(self.join(p))
574 575
575 576 if appliedbase:
576 577 del self.applied[:appliedbase]
577 578 self.applied_dirty = 1
578 579 indices = [self.find_series(p) for p in realpatches]
579 580 indices.sort()
580 581 for i in indices[-1::-1]:
581 582 del self.full_series[i]
582 583 self.parse_series()
583 584 self.series_dirty = 1
584 585
585 586 def check_toppatch(self, repo):
586 587 if len(self.applied) > 0:
587 588 top = revlog.bin(self.applied[-1].rev)
588 589 pp = repo.dirstate.parents()
589 590 if top not in pp:
590 591 raise util.Abort(_("queue top not at same revision as working directory"))
591 592 return top
592 593 return None
593 594 def check_localchanges(self, repo, force=False, refresh=True):
594 595 m, a, r, d = repo.status()[:4]
595 596 if m or a or r or d:
596 597 if not force:
597 598 if refresh:
598 599 raise util.Abort(_("local changes found, refresh first"))
599 600 else:
600 601 raise util.Abort(_("local changes found"))
601 602 return m, a, r, d
602 603
603 604 def new(self, repo, patch, *pats, **opts):
604 605 msg = opts.get('msg')
605 606 force = opts.get('force')
606 607 if os.path.exists(self.join(patch)):
607 608 raise util.Abort(_('patch "%s" already exists') % patch)
608 609 if opts.get('include') or opts.get('exclude') or pats:
609 610 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
610 611 m, a, r, d = repo.status(files=fns, match=match)[:4]
611 612 else:
612 613 m, a, r, d = self.check_localchanges(repo, force)
613 614 commitfiles = m + a + r
614 615 self.check_toppatch(repo)
615 616 wlock = repo.wlock()
616 617 insert = self.full_series_end()
617 618 if msg:
618 619 n = repo.commit(commitfiles, msg, force=True, wlock=wlock)
619 620 else:
620 621 n = repo.commit(commitfiles,
621 622 "[mq]: %s" % patch, force=True, wlock=wlock)
622 623 if n == None:
623 624 raise util.Abort(_("repo commit failed"))
624 625 self.full_series[insert:insert] = [patch]
625 626 self.applied.append(statusentry(revlog.hex(n), patch))
626 627 self.parse_series()
627 628 self.series_dirty = 1
628 629 self.applied_dirty = 1
629 630 p = self.opener(patch, "w")
630 631 if msg:
631 632 msg = msg + "\n"
632 633 p.write(msg)
633 634 p.close()
634 635 wlock = None
635 636 r = self.qrepo()
636 637 if r: r.add([patch])
637 638 if commitfiles:
638 639 self.refresh(repo, short=True)
639 640 self.removeundo(repo)
640 641
641 642 def strip(self, repo, rev, update=True, backup="all", wlock=None):
642 643 if not wlock:
643 644 wlock = repo.wlock()
644 645 lock = repo.lock()
645 646
646 647 if update:
647 648 self.check_localchanges(repo, refresh=False)
648 649 urev = self.qparents(repo, rev)
649 650 hg.clean(repo, urev, wlock=wlock)
650 651 repo.dirstate.write()
651 652
652 653 self.removeundo(repo)
653 654 repair.strip(self.ui, repo, rev, backup)
654 655
655 656 def isapplied(self, patch):
656 657 """returns (index, rev, patch)"""
657 658 for i in xrange(len(self.applied)):
658 659 a = self.applied[i]
659 660 if a.name == patch:
660 661 return (i, a.rev, a.name)
661 662 return None
662 663
663 664 # if the exact patch name does not exist, we try a few
664 665 # variations. If strict is passed, we try only #1
665 666 #
666 667 # 1) a number to indicate an offset in the series file
667 668 # 2) a unique substring of the patch name was given
668 669 # 3) patchname[-+]num to indicate an offset in the series file
669 670 def lookup(self, patch, strict=False):
670 671 patch = patch and str(patch)
671 672
672 673 def partial_name(s):
673 674 if s in self.series:
674 675 return s
675 676 matches = [x for x in self.series if s in x]
676 677 if len(matches) > 1:
677 678 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
678 679 for m in matches:
679 680 self.ui.warn(' %s\n' % m)
680 681 return None
681 682 if matches:
682 683 return matches[0]
683 684 if len(self.series) > 0 and len(self.applied) > 0:
684 685 if s == 'qtip':
685 686 return self.series[self.series_end(True)-1]
686 687 if s == 'qbase':
687 688 return self.series[0]
688 689 return None
689 690 if patch == None:
690 691 return None
691 692
692 693 # we don't want to return a partial match until we make
693 694 # sure the file name passed in does not exist (checked below)
694 695 res = partial_name(patch)
695 696 if res and res == patch:
696 697 return res
697 698
698 699 if not os.path.isfile(self.join(patch)):
699 700 try:
700 701 sno = int(patch)
701 702 except(ValueError, OverflowError):
702 703 pass
703 704 else:
704 705 if sno < len(self.series):
705 706 return self.series[sno]
706 707 if not strict:
707 708 # return any partial match made above
708 709 if res:
709 710 return res
710 711 minus = patch.rfind('-')
711 712 if minus >= 0:
712 713 res = partial_name(patch[:minus])
713 714 if res:
714 715 i = self.series.index(res)
715 716 try:
716 717 off = int(patch[minus+1:] or 1)
717 718 except(ValueError, OverflowError):
718 719 pass
719 720 else:
720 721 if i - off >= 0:
721 722 return self.series[i - off]
722 723 plus = patch.rfind('+')
723 724 if plus >= 0:
724 725 res = partial_name(patch[:plus])
725 726 if res:
726 727 i = self.series.index(res)
727 728 try:
728 729 off = int(patch[plus+1:] or 1)
729 730 except(ValueError, OverflowError):
730 731 pass
731 732 else:
732 733 if i + off < len(self.series):
733 734 return self.series[i + off]
734 735 raise util.Abort(_("patch %s not in series") % patch)
735 736
736 737 def push(self, repo, patch=None, force=False, list=False,
737 738 mergeq=None, wlock=None):
738 739 if not wlock:
739 740 wlock = repo.wlock()
740 741 patch = self.lookup(patch)
741 742 # Suppose our series file is: A B C and the current 'top' patch is B.
742 743 # qpush C should be performed (moving forward)
743 744 # qpush B is a NOP (no change)
744 745 # qpush A is an error (can't go backwards with qpush)
745 746 if patch:
746 747 info = self.isapplied(patch)
747 748 if info:
748 749 if info[0] < len(self.applied) - 1:
749 750 raise util.Abort(_("cannot push to a previous patch: %s") %
750 751 patch)
751 752 if info[0] < len(self.series) - 1:
752 753 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
753 754 else:
754 755 self.ui.warn(_('all patches are currently applied\n'))
755 756 return
756 757
757 758 # Following the above example, starting at 'top' of B:
758 759 # qpush should be performed (pushes C), but a subsequent qpush without
759 760 # an argument is an error (nothing to apply). This allows a loop
760 761 # of "...while hg qpush..." to work as it detects an error when done
761 762 if self.series_end() == len(self.series):
762 763 self.ui.warn(_('patch series already fully applied\n'))
763 764 return 1
764 765 if not force:
765 766 self.check_localchanges(repo)
766 767
767 768 self.applied_dirty = 1;
768 769 start = self.series_end()
769 770 if start > 0:
770 771 self.check_toppatch(repo)
771 772 if not patch:
772 773 patch = self.series[start]
773 774 end = start + 1
774 775 else:
775 776 end = self.series.index(patch, start) + 1
776 777 s = self.series[start:end]
777 778 all_files = {}
778 779 try:
779 780 if mergeq:
780 781 ret = self.mergepatch(repo, mergeq, s, wlock)
781 782 else:
782 783 ret = self.apply(repo, s, list, wlock=wlock,
783 784 all_files=all_files)
784 785 except:
785 786 self.ui.warn(_('cleaning up working directory...'))
786 787 node = repo.dirstate.parents()[0]
787 788 hg.revert(repo, node, None, wlock)
788 789 unknown = repo.status(wlock=wlock)[4]
789 790 # only remove unknown files that we know we touched or
790 791 # created while patching
791 792 for f in unknown:
792 793 if f in all_files:
793 794 util.unlink(repo.wjoin(f))
794 795 self.ui.warn(_('done\n'))
795 796 raise
796 797 top = self.applied[-1].name
797 798 if ret[0]:
798 799 self.ui.write("Errors during apply, please fix and refresh %s\n" %
799 800 top)
800 801 else:
801 802 self.ui.write("Now at: %s\n" % top)
802 803 return ret[0]
803 804
804 805 def pop(self, repo, patch=None, force=False, update=True, all=False,
805 806 wlock=None):
806 807 def getfile(f, rev):
807 808 t = repo.file(f).read(rev)
808 809 repo.wfile(f, "w").write(t)
809 810
810 811 if not wlock:
811 812 wlock = repo.wlock()
812 813 if patch:
813 814 # index, rev, patch
814 815 info = self.isapplied(patch)
815 816 if not info:
816 817 patch = self.lookup(patch)
817 818 info = self.isapplied(patch)
818 819 if not info:
819 820 raise util.Abort(_("patch %s is not applied") % patch)
820 821
821 822 if len(self.applied) == 0:
822 823 # Allow qpop -a to work repeatedly,
823 824 # but not qpop without an argument
824 825 self.ui.warn(_("no patches applied\n"))
825 826 return not all
826 827
827 828 if not update:
828 829 parents = repo.dirstate.parents()
829 830 rr = [ revlog.bin(x.rev) for x in self.applied ]
830 831 for p in parents:
831 832 if p in rr:
832 833 self.ui.warn("qpop: forcing dirstate update\n")
833 834 update = True
834 835
835 836 if not force and update:
836 837 self.check_localchanges(repo)
837 838
838 839 self.applied_dirty = 1;
839 840 end = len(self.applied)
840 841 if not patch:
841 842 if all:
842 843 popi = 0
843 844 else:
844 845 popi = len(self.applied) - 1
845 846 else:
846 847 popi = info[0] + 1
847 848 if popi >= end:
848 849 self.ui.warn("qpop: %s is already at the top\n" % patch)
849 850 return
850 851 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
851 852
852 853 start = info[0]
853 854 rev = revlog.bin(info[1])
854 855
855 856 # we know there are no local changes, so we can make a simplified
856 857 # form of hg.update.
857 858 if update:
858 859 top = self.check_toppatch(repo)
859 860 qp = self.qparents(repo, rev)
860 861 changes = repo.changelog.read(qp)
861 862 mmap = repo.manifest.read(changes[0])
862 863 m, a, r, d, u = repo.status(qp, top)[:5]
863 864 if d:
864 865 raise util.Abort("deletions found between repo revs")
865 866 for f in m:
866 867 getfile(f, mmap[f])
867 868 for f in r:
868 869 getfile(f, mmap[f])
869 870 util.set_exec(repo.wjoin(f), mmap.execf(f))
870 871 repo.dirstate.update(m + r, 'n')
871 872 for f in a:
872 873 try:
873 874 os.unlink(repo.wjoin(f))
874 875 except OSError, e:
875 876 if e.errno != errno.ENOENT:
876 877 raise
877 878 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
878 879 except: pass
879 880 if a:
880 881 repo.dirstate.forget(a)
881 882 repo.dirstate.setparents(qp, revlog.nullid)
882 883 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
883 884 del self.applied[start:end]
884 885 if len(self.applied):
885 886 self.ui.write("Now at: %s\n" % self.applied[-1].name)
886 887 else:
887 888 self.ui.write("Patch queue now empty\n")
888 889
889 890 def diff(self, repo, pats, opts):
890 891 top = self.check_toppatch(repo)
891 892 if not top:
892 893 self.ui.write("No patches applied\n")
893 894 return
894 895 qp = self.qparents(repo, top)
895 896 if opts.get('git'):
896 897 self.diffopts().git = True
897 898 self.printdiff(repo, qp, files=pats, opts=opts)
898 899
899 900 def refresh(self, repo, pats=None, **opts):
900 901 if len(self.applied) == 0:
901 902 self.ui.write("No patches applied\n")
902 903 return 1
903 904 wlock = repo.wlock()
904 905 self.check_toppatch(repo)
905 906 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
906 907 top = revlog.bin(top)
907 908 cparents = repo.changelog.parents(top)
908 909 patchparent = self.qparents(repo, top)
909 910 message, comments, user, date, patchfound = self.readheaders(patchfn)
910 911
911 912 patchf = self.opener(patchfn, "w")
912 913 msg = opts.get('msg', '').rstrip()
913 914 if msg:
914 915 if comments:
915 916 # Remove existing message.
916 917 ci = 0
917 918 subj = None
918 919 for mi in xrange(len(message)):
919 920 if comments[ci].lower().startswith('subject: '):
920 921 subj = comments[ci][9:]
921 922 while message[mi] != comments[ci] and message[mi] != subj:
922 923 ci += 1
923 924 del comments[ci]
924 925 comments.append(msg)
925 926 if comments:
926 927 comments = "\n".join(comments) + '\n\n'
927 928 patchf.write(comments)
928 929
929 930 if opts.get('git'):
930 931 self.diffopts().git = True
931 932 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
932 933 tip = repo.changelog.tip()
933 934 if top == tip:
934 935 # if the top of our patch queue is also the tip, there is an
935 936 # optimization here. We update the dirstate in place and strip
936 937 # off the tip commit. Then just commit the current directory
937 938 # tree. We can also send repo.commit the list of files
938 939 # changed to speed up the diff
939 940 #
940 941 # in short mode, we only diff the files included in the
941 942 # patch already
942 943 #
943 944 # this should really read:
944 945 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
945 946 # but we do it backwards to take advantage of manifest/chlog
946 947 # caching against the next repo.status call
947 948 #
948 949 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
949 950 changes = repo.changelog.read(tip)
950 951 man = repo.manifest.read(changes[0])
951 952 aaa = aa[:]
952 953 if opts.get('short'):
953 954 filelist = mm + aa + dd
954 955 match = dict.fromkeys(filelist).__contains__
955 956 else:
956 957 filelist = None
957 958 match = util.always
958 959 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
959 960
960 961 # we might end up with files that were added between tip and
961 962 # the dirstate parent, but then changed in the local dirstate.
962 963 # in this case, we want them to only show up in the added section
963 964 for x in m:
964 965 if x not in aa:
965 966 mm.append(x)
966 967 # we might end up with files added by the local dirstate that
967 968 # were deleted by the patch. In this case, they should only
968 969 # show up in the changed section.
969 970 for x in a:
970 971 if x in dd:
971 972 del dd[dd.index(x)]
972 973 mm.append(x)
973 974 else:
974 975 aa.append(x)
975 976 # make sure any files deleted in the local dirstate
976 977 # are not in the add or change column of the patch
977 978 forget = []
978 979 for x in d + r:
979 980 if x in aa:
980 981 del aa[aa.index(x)]
981 982 forget.append(x)
982 983 continue
983 984 elif x in mm:
984 985 del mm[mm.index(x)]
985 986 dd.append(x)
986 987
987 988 m = util.unique(mm)
988 989 r = util.unique(dd)
989 990 a = util.unique(aa)
990 991 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
991 992 filelist = util.unique(c[0] + c[1] + c[2])
992 993 patch.diff(repo, patchparent, files=filelist, match=matchfn,
993 994 fp=patchf, changes=c, opts=self.diffopts())
994 995 patchf.close()
995 996
996 997 repo.dirstate.setparents(*cparents)
997 998 copies = {}
998 999 for dst in a:
999 1000 src = repo.dirstate.copied(dst)
1000 1001 if src is None:
1001 1002 continue
1002 1003 copies.setdefault(src, []).append(dst)
1003 1004 repo.dirstate.update(a, 'a')
1004 1005 # remember the copies between patchparent and tip
1005 1006 # this may be slow, so don't do it if we're not tracking copies
1006 1007 if self.diffopts().git:
1007 1008 for dst in aaa:
1008 1009 f = repo.file(dst)
1009 1010 src = f.renamed(man[dst])
1010 1011 if src:
1011 1012 copies[src[0]] = copies.get(dst, [])
1012 1013 if dst in a:
1013 1014 copies[src[0]].append(dst)
1014 1015 # we can't copy a file created by the patch itself
1015 1016 if dst in copies:
1016 1017 del copies[dst]
1017 1018 for src, dsts in copies.iteritems():
1018 1019 for dst in dsts:
1019 1020 repo.dirstate.copy(src, dst)
1020 1021 repo.dirstate.update(r, 'r')
1021 1022 # if the patch excludes a modified file, mark that file with mtime=0
1022 1023 # so status can see it.
1023 1024 mm = []
1024 1025 for i in xrange(len(m)-1, -1, -1):
1025 1026 if not matchfn(m[i]):
1026 1027 mm.append(m[i])
1027 1028 del m[i]
1028 1029 repo.dirstate.update(m, 'n')
1029 1030 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1030 1031 repo.dirstate.forget(forget)
1031 1032
1032 1033 if not msg:
1033 1034 if not message:
1034 1035 message = "[mq]: %s\n" % patchfn
1035 1036 else:
1036 1037 message = "\n".join(message)
1037 1038 else:
1038 1039 message = msg
1039 1040
1040 1041 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1041 1042 n = repo.commit(filelist, message, changes[1], match=matchfn,
1042 1043 force=1, wlock=wlock)
1043 1044 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1044 1045 self.applied_dirty = 1
1045 1046 self.removeundo(repo)
1046 1047 else:
1047 1048 self.printdiff(repo, patchparent, fp=patchf)
1048 1049 patchf.close()
1049 1050 added = repo.status()[1]
1050 1051 for a in added:
1051 1052 f = repo.wjoin(a)
1052 1053 try:
1053 1054 os.unlink(f)
1054 1055 except OSError, e:
1055 1056 if e.errno != errno.ENOENT:
1056 1057 raise
1057 1058 try: os.removedirs(os.path.dirname(f))
1058 1059 except: pass
1059 1060 # forget the file copies in the dirstate
1060 1061 # push should readd the files later on
1061 1062 repo.dirstate.forget(added)
1062 1063 self.pop(repo, force=True, wlock=wlock)
1063 1064 self.push(repo, force=True, wlock=wlock)
1064 1065
1065 1066 def init(self, repo, create=False):
1066 1067 if not create and os.path.isdir(self.path):
1067 1068 raise util.Abort(_("patch queue directory already exists"))
1068 1069 try:
1069 1070 os.mkdir(self.path)
1070 1071 except OSError, inst:
1071 1072 if inst.errno != errno.EEXIST or not create:
1072 1073 raise
1073 1074 if create:
1074 1075 return self.qrepo(create=True)
1075 1076
1076 1077 def unapplied(self, repo, patch=None):
1077 1078 if patch and patch not in self.series:
1078 1079 raise util.Abort(_("patch %s is not in series file") % patch)
1079 1080 if not patch:
1080 1081 start = self.series_end()
1081 1082 else:
1082 1083 start = self.series.index(patch) + 1
1083 1084 unapplied = []
1084 1085 for i in xrange(start, len(self.series)):
1085 1086 pushable, reason = self.pushable(i)
1086 1087 if pushable:
1087 1088 unapplied.append((i, self.series[i]))
1088 1089 self.explain_pushable(i)
1089 1090 return unapplied
1090 1091
1091 1092 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1092 1093 summary=False):
1093 1094 def displayname(patchname):
1094 1095 if summary:
1095 1096 msg = self.readheaders(patchname)[0]
1096 1097 msg = msg and ': ' + msg[0] or ': '
1097 1098 else:
1098 1099 msg = ''
1099 1100 return '%s%s' % (patchname, msg)
1100 1101
1101 1102 applied = dict.fromkeys([p.name for p in self.applied])
1102 1103 if length is None:
1103 1104 length = len(self.series) - start
1104 1105 if not missing:
1105 1106 for i in xrange(start, start+length):
1106 1107 patch = self.series[i]
1107 1108 if patch in applied:
1108 1109 stat = 'A'
1109 1110 elif self.pushable(i)[0]:
1110 1111 stat = 'U'
1111 1112 else:
1112 1113 stat = 'G'
1113 1114 pfx = ''
1114 1115 if self.ui.verbose:
1115 1116 pfx = '%d %s ' % (i, stat)
1116 1117 elif status and status != stat:
1117 1118 continue
1118 1119 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1119 1120 else:
1120 1121 msng_list = []
1121 1122 for root, dirs, files in os.walk(self.path):
1122 1123 d = root[len(self.path) + 1:]
1123 1124 for f in files:
1124 1125 fl = os.path.join(d, f)
1125 1126 if (fl not in self.series and
1126 1127 fl not in (self.status_path, self.series_path,
1127 1128 self.guards_path)
1128 1129 and not fl.startswith('.')):
1129 1130 msng_list.append(fl)
1130 1131 msng_list.sort()
1131 1132 for x in msng_list:
1132 1133 pfx = self.ui.verbose and ('D ') or ''
1133 1134 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1134 1135
1135 1136 def issaveline(self, l):
1136 1137 if l.name == '.hg.patches.save.line':
1137 1138 return True
1138 1139
1139 1140 def qrepo(self, create=False):
1140 1141 if create or os.path.isdir(self.join(".hg")):
1141 1142 return hg.repository(self.ui, path=self.path, create=create)
1142 1143
1143 1144 def restore(self, repo, rev, delete=None, qupdate=None):
1144 1145 c = repo.changelog.read(rev)
1145 1146 desc = c[4].strip()
1146 1147 lines = desc.splitlines()
1147 1148 i = 0
1148 1149 datastart = None
1149 1150 series = []
1150 1151 applied = []
1151 1152 qpp = None
1152 1153 for i in xrange(0, len(lines)):
1153 1154 if lines[i] == 'Patch Data:':
1154 1155 datastart = i + 1
1155 1156 elif lines[i].startswith('Dirstate:'):
1156 1157 l = lines[i].rstrip()
1157 1158 l = l[10:].split(' ')
1158 1159 qpp = [ hg.bin(x) for x in l ]
1159 1160 elif datastart != None:
1160 1161 l = lines[i].rstrip()
1161 1162 se = statusentry(l)
1162 1163 file_ = se.name
1163 1164 if se.rev:
1164 1165 applied.append(se)
1165 1166 else:
1166 1167 series.append(file_)
1167 1168 if datastart == None:
1168 1169 self.ui.warn("No saved patch data found\n")
1169 1170 return 1
1170 1171 self.ui.warn("restoring status: %s\n" % lines[0])
1171 1172 self.full_series = series
1172 1173 self.applied = applied
1173 1174 self.parse_series()
1174 1175 self.series_dirty = 1
1175 1176 self.applied_dirty = 1
1176 1177 heads = repo.changelog.heads()
1177 1178 if delete:
1178 1179 if rev not in heads:
1179 1180 self.ui.warn("save entry has children, leaving it alone\n")
1180 1181 else:
1181 1182 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1182 1183 pp = repo.dirstate.parents()
1183 1184 if rev in pp:
1184 1185 update = True
1185 1186 else:
1186 1187 update = False
1187 1188 self.strip(repo, rev, update=update, backup='strip')
1188 1189 if qpp:
1189 1190 self.ui.warn("saved queue repository parents: %s %s\n" %
1190 1191 (hg.short(qpp[0]), hg.short(qpp[1])))
1191 1192 if qupdate:
1192 1193 print "queue directory updating"
1193 1194 r = self.qrepo()
1194 1195 if not r:
1195 1196 self.ui.warn("Unable to load queue repository\n")
1196 1197 return 1
1197 1198 hg.clean(r, qpp[0])
1198 1199
1199 1200 def save(self, repo, msg=None):
1200 1201 if len(self.applied) == 0:
1201 1202 self.ui.warn("save: no patches applied, exiting\n")
1202 1203 return 1
1203 1204 if self.issaveline(self.applied[-1]):
1204 1205 self.ui.warn("status is already saved\n")
1205 1206 return 1
1206 1207
1207 1208 ar = [ ':' + x for x in self.full_series ]
1208 1209 if not msg:
1209 1210 msg = "hg patches saved state"
1210 1211 else:
1211 1212 msg = "hg patches: " + msg.rstrip('\r\n')
1212 1213 r = self.qrepo()
1213 1214 if r:
1214 1215 pp = r.dirstate.parents()
1215 1216 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1216 1217 msg += "\n\nPatch Data:\n"
1217 1218 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1218 1219 "\n".join(ar) + '\n' or "")
1219 1220 n = repo.commit(None, text, user=None, force=1)
1220 1221 if not n:
1221 1222 self.ui.warn("repo commit failed\n")
1222 1223 return 1
1223 1224 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1224 1225 self.applied_dirty = 1
1225 1226 self.removeundo(repo)
1226 1227
1227 1228 def full_series_end(self):
1228 1229 if len(self.applied) > 0:
1229 1230 p = self.applied[-1].name
1230 1231 end = self.find_series(p)
1231 1232 if end == None:
1232 1233 return len(self.full_series)
1233 1234 return end + 1
1234 1235 return 0
1235 1236
1236 1237 def series_end(self, all_patches=False):
1237 1238 """If all_patches is False, return the index of the next pushable patch
1238 1239 in the series, or the series length. If all_patches is True, return the
1239 1240 index of the first patch past the last applied one.
1240 1241 """
1241 1242 end = 0
1242 1243 def next(start):
1243 1244 if all_patches:
1244 1245 return start
1245 1246 i = start
1246 1247 while i < len(self.series):
1247 1248 p, reason = self.pushable(i)
1248 1249 if p:
1249 1250 break
1250 1251 self.explain_pushable(i)
1251 1252 i += 1
1252 1253 return i
1253 1254 if len(self.applied) > 0:
1254 1255 p = self.applied[-1].name
1255 1256 try:
1256 1257 end = self.series.index(p)
1257 1258 except ValueError:
1258 1259 return 0
1259 1260 return next(end + 1)
1260 1261 return next(end)
1261 1262
1262 1263 def appliedname(self, index):
1263 1264 pname = self.applied[index].name
1264 1265 if not self.ui.verbose:
1265 1266 p = pname
1266 1267 else:
1267 1268 p = str(self.series.index(pname)) + " " + pname
1268 1269 return p
1269 1270
1270 1271 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1271 1272 force=None, git=False):
1272 1273 def checkseries(patchname):
1273 1274 if patchname in self.series:
1274 1275 raise util.Abort(_('patch %s is already in the series file')
1275 1276 % patchname)
1276 1277 def checkfile(patchname):
1277 1278 if not force and os.path.exists(self.join(patchname)):
1278 1279 raise util.Abort(_('patch "%s" already exists')
1279 1280 % patchname)
1280 1281
1281 1282 if rev:
1282 1283 if files:
1283 1284 raise util.Abort(_('option "-r" not valid when importing '
1284 1285 'files'))
1285 1286 rev = cmdutil.revrange(repo, rev)
1286 1287 rev.sort(lambda x, y: cmp(y, x))
1287 1288 if (len(files) > 1 or len(rev) > 1) and patchname:
1288 1289 raise util.Abort(_('option "-n" not valid when importing multiple '
1289 1290 'patches'))
1290 1291 i = 0
1291 1292 added = []
1292 1293 if rev:
1293 1294 # If mq patches are applied, we can only import revisions
1294 1295 # that form a linear path to qbase.
1295 1296 # Otherwise, they should form a linear path to a head.
1296 1297 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1297 1298 if len(heads) > 1:
1298 1299 raise util.Abort(_('revision %d is the root of more than one '
1299 1300 'branch') % rev[-1])
1300 1301 if self.applied:
1301 1302 base = revlog.hex(repo.changelog.node(rev[0]))
1302 1303 if base in [n.rev for n in self.applied]:
1303 1304 raise util.Abort(_('revision %d is already managed')
1304 1305 % rev[0])
1305 1306 if heads != [revlog.bin(self.applied[-1].rev)]:
1306 1307 raise util.Abort(_('revision %d is not the parent of '
1307 1308 'the queue') % rev[0])
1308 1309 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1309 1310 lastparent = repo.changelog.parentrevs(base)[0]
1310 1311 else:
1311 1312 if heads != [repo.changelog.node(rev[0])]:
1312 1313 raise util.Abort(_('revision %d has unmanaged children')
1313 1314 % rev[0])
1314 1315 lastparent = None
1315 1316
1316 1317 if git:
1317 1318 self.diffopts().git = True
1318 1319
1319 1320 for r in rev:
1320 1321 p1, p2 = repo.changelog.parentrevs(r)
1321 1322 n = repo.changelog.node(r)
1322 1323 if p2 != revlog.nullrev:
1323 1324 raise util.Abort(_('cannot import merge revision %d') % r)
1324 1325 if lastparent and lastparent != r:
1325 1326 raise util.Abort(_('revision %d is not the parent of %d')
1326 1327 % (r, lastparent))
1327 1328 lastparent = p1
1328 1329
1329 1330 if not patchname:
1330 1331 patchname = normname('%d.diff' % r)
1331 1332 checkseries(patchname)
1332 1333 checkfile(patchname)
1333 1334 self.full_series.insert(0, patchname)
1334 1335
1335 1336 patchf = self.opener(patchname, "w")
1336 1337 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1337 1338 patchf.close()
1338 1339
1339 1340 se = statusentry(revlog.hex(n), patchname)
1340 1341 self.applied.insert(0, se)
1341 1342
1342 1343 added.append(patchname)
1343 1344 patchname = None
1344 1345 self.parse_series()
1345 1346 self.applied_dirty = 1
1346 1347
1347 1348 for filename in files:
1348 1349 if existing:
1349 1350 if filename == '-':
1350 1351 raise util.Abort(_('-e is incompatible with import from -'))
1351 1352 if not patchname:
1352 1353 patchname = normname(filename)
1353 1354 if not os.path.isfile(self.join(patchname)):
1354 1355 raise util.Abort(_("patch %s does not exist") % patchname)
1355 1356 else:
1356 1357 try:
1357 1358 if filename == '-':
1358 1359 if not patchname:
1359 1360 raise util.Abort(_('need --name to import a patch from -'))
1360 1361 text = sys.stdin.read()
1361 1362 else:
1362 1363 text = file(filename).read()
1363 1364 except IOError:
1364 1365 raise util.Abort(_("unable to read %s") % patchname)
1365 1366 if not patchname:
1366 1367 patchname = normname(os.path.basename(filename))
1367 1368 checkfile(patchname)
1368 1369 patchf = self.opener(patchname, "w")
1369 1370 patchf.write(text)
1370 1371 checkseries(patchname)
1371 1372 index = self.full_series_end() + i
1372 1373 self.full_series[index:index] = [patchname]
1373 1374 self.parse_series()
1374 1375 self.ui.warn("adding %s to series file\n" % patchname)
1375 1376 i += 1
1376 1377 added.append(patchname)
1377 1378 patchname = None
1378 1379 self.series_dirty = 1
1379 1380 qrepo = self.qrepo()
1380 1381 if qrepo:
1381 1382 qrepo.add(added)
1382 1383
1383 1384 def delete(ui, repo, *patches, **opts):
1384 1385 """remove patches from queue
1385 1386
1386 With --rev, mq will stop managing the named revisions. The
1387 patches must be applied and at the base of the stack. This option
1388 is useful when the patches have been applied upstream.
1387 The patches must not be applied, unless they are arguments to
1388 the --rev parameter. At least one patch or revision is required.
1389 1389
1390 Otherwise, the patches must not be applied.
1390 With --rev, mq will stop managing the named revisions (converting
1391 them to regular mercurial changesets). The patches must be applied
1392 and at the base of the stack. This option is useful when the patches
1393 have been applied upstream.
1391 1394
1392 1395 With --keep, the patch files are preserved in the patch directory."""
1393 1396 q = repo.mq
1394 1397 q.delete(repo, patches, opts)
1395 1398 q.save_dirty()
1396 1399 return 0
1397 1400
1398 1401 def applied(ui, repo, patch=None, **opts):
1399 1402 """print the patches already applied"""
1400 1403 q = repo.mq
1401 1404 if patch:
1402 1405 if patch not in q.series:
1403 1406 raise util.Abort(_("patch %s is not in series file") % patch)
1404 1407 end = q.series.index(patch) + 1
1405 1408 else:
1406 1409 end = q.series_end(True)
1407 1410 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1408 1411
1409 1412 def unapplied(ui, repo, patch=None, **opts):
1410 1413 """print the patches not yet applied"""
1411 1414 q = repo.mq
1412 1415 if patch:
1413 1416 if patch not in q.series:
1414 1417 raise util.Abort(_("patch %s is not in series file") % patch)
1415 1418 start = q.series.index(patch) + 1
1416 1419 else:
1417 1420 start = q.series_end(True)
1418 1421 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1419 1422
1420 1423 def qimport(ui, repo, *filename, **opts):
1421 1424 """import a patch
1422 1425
1423 1426 The patch will have the same name as its source file unless you
1424 1427 give it a new one with --name.
1425 1428
1426 1429 You can register an existing patch inside the patch directory
1427 1430 with the --existing flag.
1428 1431
1429 1432 With --force, an existing patch of the same name will be overwritten.
1430 1433
1431 1434 An existing changeset may be placed under mq control with --rev
1432 1435 (e.g. qimport --rev tip -n patch will place tip under mq control).
1433 1436 With --git, patches imported with --rev will use the git diff
1434 1437 format.
1435 1438 """
1436 1439 q = repo.mq
1437 1440 q.qimport(repo, filename, patchname=opts['name'],
1438 1441 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1439 1442 git=opts['git'])
1440 1443 q.save_dirty()
1441 1444 return 0
1442 1445
1443 1446 def init(ui, repo, **opts):
1444 1447 """init a new queue repository
1445 1448
1446 1449 The queue repository is unversioned by default. If -c is
1447 1450 specified, qinit will create a separate nested repository
1448 1451 for patches (qinit -c may also be run later to convert
1449 1452 an unversioned patch repository into a versioned one).
1450 1453 You can use qcommit to commit changes to this queue repository."""
1451 1454 q = repo.mq
1452 1455 r = q.init(repo, create=opts['create_repo'])
1453 1456 q.save_dirty()
1454 1457 if r:
1455 1458 if not os.path.exists(r.wjoin('.hgignore')):
1456 1459 fp = r.wopener('.hgignore', 'w')
1457 1460 fp.write('syntax: glob\n')
1458 1461 fp.write('status\n')
1459 1462 fp.write('guards\n')
1460 1463 fp.close()
1461 1464 if not os.path.exists(r.wjoin('series')):
1462 1465 r.wopener('series', 'w').close()
1463 1466 r.add(['.hgignore', 'series'])
1464 1467 commands.add(ui, r)
1465 1468 return 0
1466 1469
1467 1470 def clone(ui, source, dest=None, **opts):
1468 1471 '''clone main and patch repository at same time
1469 1472
1470 1473 If source is local, destination will have no patches applied. If
1471 1474 source is remote, this command can not check if patches are
1472 1475 applied in source, so cannot guarantee that patches are not
1473 1476 applied in destination. If you clone remote repository, be sure
1474 1477 before that it has no patches applied.
1475 1478
1476 1479 Source patch repository is looked for in <src>/.hg/patches by
1477 1480 default. Use -p <url> to change.
1478 1481 '''
1479 1482 cmdutil.setremoteconfig(ui, opts)
1480 1483 if dest is None:
1481 1484 dest = hg.defaultdest(source)
1482 1485 sr = hg.repository(ui, ui.expandpath(source))
1483 1486 qbase, destrev = None, None
1484 1487 if sr.local():
1485 1488 if sr.mq.applied:
1486 1489 qbase = revlog.bin(sr.mq.applied[0].rev)
1487 1490 if not hg.islocal(dest):
1488 1491 heads = dict.fromkeys(sr.heads())
1489 1492 for h in sr.heads(qbase):
1490 1493 del heads[h]
1491 1494 destrev = heads.keys()
1492 1495 destrev.append(sr.changelog.parents(qbase)[0])
1493 1496 ui.note(_('cloning main repo\n'))
1494 1497 sr, dr = hg.clone(ui, sr.url(), dest,
1495 1498 pull=opts['pull'],
1496 1499 rev=destrev,
1497 1500 update=False,
1498 1501 stream=opts['uncompressed'])
1499 1502 ui.note(_('cloning patch repo\n'))
1500 1503 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1501 1504 dr.url() + '/.hg/patches',
1502 1505 pull=opts['pull'],
1503 1506 update=not opts['noupdate'],
1504 1507 stream=opts['uncompressed'])
1505 1508 if dr.local():
1506 1509 if qbase:
1507 1510 ui.note(_('stripping applied patches from destination repo\n'))
1508 1511 dr.mq.strip(dr, qbase, update=False, backup=None)
1509 1512 if not opts['noupdate']:
1510 1513 ui.note(_('updating destination repo\n'))
1511 1514 hg.update(dr, dr.changelog.tip())
1512 1515
1513 1516 def commit(ui, repo, *pats, **opts):
1514 1517 """commit changes in the queue repository"""
1515 1518 q = repo.mq
1516 1519 r = q.qrepo()
1517 1520 if not r: raise util.Abort('no queue repository')
1518 1521 commands.commit(r.ui, r, *pats, **opts)
1519 1522
1520 1523 def series(ui, repo, **opts):
1521 1524 """print the entire series file"""
1522 1525 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1523 1526 return 0
1524 1527
1525 1528 def top(ui, repo, **opts):
1526 1529 """print the name of the current patch"""
1527 1530 q = repo.mq
1528 1531 t = q.applied and q.series_end(True) or 0
1529 1532 if t:
1530 1533 return q.qseries(repo, start=t-1, length=1, status='A',
1531 1534 summary=opts.get('summary'))
1532 1535 else:
1533 1536 ui.write("No patches applied\n")
1534 1537 return 1
1535 1538
1536 1539 def next(ui, repo, **opts):
1537 1540 """print the name of the next patch"""
1538 1541 q = repo.mq
1539 1542 end = q.series_end()
1540 1543 if end == len(q.series):
1541 1544 ui.write("All patches applied\n")
1542 1545 return 1
1543 1546 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1544 1547
1545 1548 def prev(ui, repo, **opts):
1546 1549 """print the name of the previous patch"""
1547 1550 q = repo.mq
1548 1551 l = len(q.applied)
1549 1552 if l == 1:
1550 1553 ui.write("Only one patch applied\n")
1551 1554 return 1
1552 1555 if not l:
1553 1556 ui.write("No patches applied\n")
1554 1557 return 1
1555 1558 return q.qseries(repo, start=l-2, length=1, status='A',
1556 1559 summary=opts.get('summary'))
1557 1560
1558 1561 def new(ui, repo, patch, *args, **opts):
1559 1562 """create a new patch
1560 1563
1561 1564 qnew creates a new patch on top of the currently-applied patch
1562 1565 (if any). It will refuse to run if there are any outstanding
1563 1566 changes unless -f is specified, in which case the patch will
1564 1567 be initialised with them. You may also use -I, -X, and/or a list of
1565 1568 files after the patch name to add only changes to matching files
1566 1569 to the new patch, leaving the rest as uncommitted modifications.
1567 1570
1568 1571 -e, -m or -l set the patch header as well as the commit message.
1569 1572 If none is specified, the patch header is empty and the
1570 1573 commit message is '[mq]: PATCH'"""
1571 1574 q = repo.mq
1572 1575 message = cmdutil.logmessage(opts)
1573 1576 if opts['edit']:
1574 1577 message = ui.edit(message, ui.username())
1575 1578 opts['msg'] = message
1576 1579 q.new(repo, patch, *args, **opts)
1577 1580 q.save_dirty()
1578 1581 return 0
1579 1582
1580 1583 def refresh(ui, repo, *pats, **opts):
1581 1584 """update the current patch
1582 1585
1583 1586 If any file patterns are provided, the refreshed patch will contain only
1584 1587 the modifications that match those patterns; the remaining modifications
1585 1588 will remain in the working directory.
1586 1589
1587 1590 hg add/remove/copy/rename work as usual, though you might want to use
1588 1591 git-style patches (--git or [diff] git=1) to track copies and renames.
1589 1592 """
1590 1593 q = repo.mq
1591 1594 message = cmdutil.logmessage(opts)
1592 1595 if opts['edit']:
1593 1596 if message:
1594 1597 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1595 1598 patch = q.applied[-1].name
1596 1599 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1597 1600 message = ui.edit('\n'.join(message), user or ui.username())
1598 1601 ret = q.refresh(repo, pats, msg=message, **opts)
1599 1602 q.save_dirty()
1600 1603 return ret
1601 1604
1602 1605 def diff(ui, repo, *pats, **opts):
1603 1606 """diff of the current patch"""
1604 1607 repo.mq.diff(repo, pats, opts)
1605 1608 return 0
1606 1609
1607 1610 def fold(ui, repo, *files, **opts):
1608 1611 """fold the named patches into the current patch
1609 1612
1610 1613 Patches must not yet be applied. Each patch will be successively
1611 1614 applied to the current patch in the order given. If all the
1612 1615 patches apply successfully, the current patch will be refreshed
1613 1616 with the new cumulative patch, and the folded patches will
1614 1617 be deleted. With -k/--keep, the folded patch files will not
1615 1618 be removed afterwards.
1616 1619
1617 1620 The header for each folded patch will be concatenated with
1618 1621 the current patch header, separated by a line of '* * *'."""
1619 1622
1620 1623 q = repo.mq
1621 1624
1622 1625 if not files:
1623 1626 raise util.Abort(_('qfold requires at least one patch name'))
1624 1627 if not q.check_toppatch(repo):
1625 1628 raise util.Abort(_('No patches applied'))
1626 1629
1627 1630 message = cmdutil.logmessage(opts)
1628 1631 if opts['edit']:
1629 1632 if message:
1630 1633 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1631 1634
1632 1635 parent = q.lookup('qtip')
1633 1636 patches = []
1634 1637 messages = []
1635 1638 for f in files:
1636 1639 p = q.lookup(f)
1637 1640 if p in patches or p == parent:
1638 1641 ui.warn(_('Skipping already folded patch %s') % p)
1639 1642 if q.isapplied(p):
1640 1643 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1641 1644 patches.append(p)
1642 1645
1643 1646 for p in patches:
1644 1647 if not message:
1645 1648 messages.append(q.readheaders(p)[0])
1646 1649 pf = q.join(p)
1647 1650 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1648 1651 if not patchsuccess:
1649 1652 raise util.Abort(_('Error folding patch %s') % p)
1650 1653 patch.updatedir(ui, repo, files)
1651 1654
1652 1655 if not message:
1653 1656 message, comments, user = q.readheaders(parent)[0:3]
1654 1657 for msg in messages:
1655 1658 message.append('* * *')
1656 1659 message.extend(msg)
1657 1660 message = '\n'.join(message)
1658 1661
1659 1662 if opts['edit']:
1660 1663 message = ui.edit(message, user or ui.username())
1661 1664
1662 1665 q.refresh(repo, msg=message)
1663 1666 q.delete(repo, patches, opts)
1664 1667 q.save_dirty()
1665 1668
1666 1669 def goto(ui, repo, patch, **opts):
1667 1670 '''push or pop patches until named patch is at top of stack'''
1668 1671 q = repo.mq
1669 1672 patch = q.lookup(patch)
1670 1673 if q.isapplied(patch):
1671 1674 ret = q.pop(repo, patch, force=opts['force'])
1672 1675 else:
1673 1676 ret = q.push(repo, patch, force=opts['force'])
1674 1677 q.save_dirty()
1675 1678 return ret
1676 1679
1677 1680 def guard(ui, repo, *args, **opts):
1678 1681 '''set or print guards for a patch
1679 1682
1680 1683 Guards control whether a patch can be pushed. A patch with no
1681 1684 guards is always pushed. A patch with a positive guard ("+foo") is
1682 1685 pushed only if the qselect command has activated it. A patch with
1683 1686 a negative guard ("-foo") is never pushed if the qselect command
1684 1687 has activated it.
1685 1688
1686 1689 With no arguments, print the currently active guards.
1687 1690 With arguments, set guards for the named patch.
1688 1691
1689 1692 To set a negative guard "-foo" on topmost patch ("--" is needed so
1690 1693 hg will not interpret "-foo" as an option):
1691 1694 hg qguard -- -foo
1692 1695
1693 1696 To set guards on another patch:
1694 1697 hg qguard other.patch +2.6.17 -stable
1695 1698 '''
1696 1699 def status(idx):
1697 1700 guards = q.series_guards[idx] or ['unguarded']
1698 1701 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1699 1702 q = repo.mq
1700 1703 patch = None
1701 1704 args = list(args)
1702 1705 if opts['list']:
1703 1706 if args or opts['none']:
1704 1707 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1705 1708 for i in xrange(len(q.series)):
1706 1709 status(i)
1707 1710 return
1708 1711 if not args or args[0][0:1] in '-+':
1709 1712 if not q.applied:
1710 1713 raise util.Abort(_('no patches applied'))
1711 1714 patch = q.applied[-1].name
1712 1715 if patch is None and args[0][0:1] not in '-+':
1713 1716 patch = args.pop(0)
1714 1717 if patch is None:
1715 1718 raise util.Abort(_('no patch to work with'))
1716 1719 if args or opts['none']:
1717 1720 idx = q.find_series(patch)
1718 1721 if idx is None:
1719 1722 raise util.Abort(_('no patch named %s') % patch)
1720 1723 q.set_guards(idx, args)
1721 1724 q.save_dirty()
1722 1725 else:
1723 1726 status(q.series.index(q.lookup(patch)))
1724 1727
1725 1728 def header(ui, repo, patch=None):
1726 1729 """Print the header of the topmost or specified patch"""
1727 1730 q = repo.mq
1728 1731
1729 1732 if patch:
1730 1733 patch = q.lookup(patch)
1731 1734 else:
1732 1735 if not q.applied:
1733 1736 ui.write('No patches applied\n')
1734 1737 return 1
1735 1738 patch = q.lookup('qtip')
1736 1739 message = repo.mq.readheaders(patch)[0]
1737 1740
1738 1741 ui.write('\n'.join(message) + '\n')
1739 1742
1740 1743 def lastsavename(path):
1741 1744 (directory, base) = os.path.split(path)
1742 1745 names = os.listdir(directory)
1743 1746 namere = re.compile("%s.([0-9]+)" % base)
1744 1747 maxindex = None
1745 1748 maxname = None
1746 1749 for f in names:
1747 1750 m = namere.match(f)
1748 1751 if m:
1749 1752 index = int(m.group(1))
1750 1753 if maxindex == None or index > maxindex:
1751 1754 maxindex = index
1752 1755 maxname = f
1753 1756 if maxname:
1754 1757 return (os.path.join(directory, maxname), maxindex)
1755 1758 return (None, None)
1756 1759
1757 1760 def savename(path):
1758 1761 (last, index) = lastsavename(path)
1759 1762 if last is None:
1760 1763 index = 0
1761 1764 newpath = path + ".%d" % (index + 1)
1762 1765 return newpath
1763 1766
1764 1767 def push(ui, repo, patch=None, **opts):
1765 1768 """push the next patch onto the stack"""
1766 1769 q = repo.mq
1767 1770 mergeq = None
1768 1771
1769 1772 if opts['all']:
1770 1773 if not q.series:
1771 1774 ui.warn(_('no patches in series\n'))
1772 1775 return 0
1773 1776 patch = q.series[-1]
1774 1777 if opts['merge']:
1775 1778 if opts['name']:
1776 1779 newpath = opts['name']
1777 1780 else:
1778 1781 newpath, i = lastsavename(q.path)
1779 1782 if not newpath:
1780 1783 ui.warn("no saved queues found, please use -n\n")
1781 1784 return 1
1782 1785 mergeq = queue(ui, repo.join(""), newpath)
1783 1786 ui.warn("merging with queue at: %s\n" % mergeq.path)
1784 1787 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1785 1788 mergeq=mergeq)
1786 1789 return ret
1787 1790
1788 1791 def pop(ui, repo, patch=None, **opts):
1789 1792 """pop the current patch off the stack"""
1790 1793 localupdate = True
1791 1794 if opts['name']:
1792 1795 q = queue(ui, repo.join(""), repo.join(opts['name']))
1793 1796 ui.warn('using patch queue: %s\n' % q.path)
1794 1797 localupdate = False
1795 1798 else:
1796 1799 q = repo.mq
1797 1800 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1798 1801 all=opts['all'])
1799 1802 q.save_dirty()
1800 1803 return ret
1801 1804
1802 1805 def rename(ui, repo, patch, name=None, **opts):
1803 1806 """rename a patch
1804 1807
1805 1808 With one argument, renames the current patch to PATCH1.
1806 1809 With two arguments, renames PATCH1 to PATCH2."""
1807 1810
1808 1811 q = repo.mq
1809 1812
1810 1813 if not name:
1811 1814 name = patch
1812 1815 patch = None
1813 1816
1814 1817 if patch:
1815 1818 patch = q.lookup(patch)
1816 1819 else:
1817 1820 if not q.applied:
1818 1821 ui.write(_('No patches applied\n'))
1819 1822 return
1820 1823 patch = q.lookup('qtip')
1821 1824 absdest = q.join(name)
1822 1825 if os.path.isdir(absdest):
1823 1826 name = normname(os.path.join(name, os.path.basename(patch)))
1824 1827 absdest = q.join(name)
1825 1828 if os.path.exists(absdest):
1826 1829 raise util.Abort(_('%s already exists') % absdest)
1827 1830
1828 1831 if name in q.series:
1829 1832 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1830 1833
1831 1834 if ui.verbose:
1832 1835 ui.write('Renaming %s to %s\n' % (patch, name))
1833 1836 i = q.find_series(patch)
1834 1837 guards = q.guard_re.findall(q.full_series[i])
1835 1838 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1836 1839 q.parse_series()
1837 1840 q.series_dirty = 1
1838 1841
1839 1842 info = q.isapplied(patch)
1840 1843 if info:
1841 1844 q.applied[info[0]] = statusentry(info[1], name)
1842 1845 q.applied_dirty = 1
1843 1846
1844 1847 util.rename(q.join(patch), absdest)
1845 1848 r = q.qrepo()
1846 1849 if r:
1847 1850 wlock = r.wlock()
1848 1851 if r.dirstate.state(name) == 'r':
1849 1852 r.undelete([name], wlock)
1850 1853 r.copy(patch, name, wlock)
1851 1854 r.remove([patch], False, wlock)
1852 1855
1853 1856 q.save_dirty()
1854 1857
1855 1858 def restore(ui, repo, rev, **opts):
1856 1859 """restore the queue state saved by a rev"""
1857 1860 rev = repo.lookup(rev)
1858 1861 q = repo.mq
1859 1862 q.restore(repo, rev, delete=opts['delete'],
1860 1863 qupdate=opts['update'])
1861 1864 q.save_dirty()
1862 1865 return 0
1863 1866
1864 1867 def save(ui, repo, **opts):
1865 1868 """save current queue state"""
1866 1869 q = repo.mq
1867 1870 message = cmdutil.logmessage(opts)
1868 1871 ret = q.save(repo, msg=message)
1869 1872 if ret:
1870 1873 return ret
1871 1874 q.save_dirty()
1872 1875 if opts['copy']:
1873 1876 path = q.path
1874 1877 if opts['name']:
1875 1878 newpath = os.path.join(q.basepath, opts['name'])
1876 1879 if os.path.exists(newpath):
1877 1880 if not os.path.isdir(newpath):
1878 1881 raise util.Abort(_('destination %s exists and is not '
1879 1882 'a directory') % newpath)
1880 1883 if not opts['force']:
1881 1884 raise util.Abort(_('destination %s exists, '
1882 1885 'use -f to force') % newpath)
1883 1886 else:
1884 1887 newpath = savename(path)
1885 1888 ui.warn("copy %s to %s\n" % (path, newpath))
1886 1889 util.copyfiles(path, newpath)
1887 1890 if opts['empty']:
1888 1891 try:
1889 1892 os.unlink(q.join(q.status_path))
1890 1893 except:
1891 1894 pass
1892 1895 return 0
1893 1896
1894 1897 def strip(ui, repo, rev, **opts):
1895 1898 """strip a revision and all later revs on the same branch"""
1896 1899 rev = repo.lookup(rev)
1897 1900 backup = 'all'
1898 1901 if opts['backup']:
1899 1902 backup = 'strip'
1900 1903 elif opts['nobackup']:
1901 1904 backup = 'none'
1902 1905 update = repo.dirstate.parents()[0] != revlog.nullid
1903 1906 repo.mq.strip(repo, rev, backup=backup, update=update)
1904 1907 return 0
1905 1908
1906 1909 def select(ui, repo, *args, **opts):
1907 1910 '''set or print guarded patches to push
1908 1911
1909 1912 Use the qguard command to set or print guards on patch, then use
1910 1913 qselect to tell mq which guards to use. A patch will be pushed if it
1911 1914 has no guards or any positive guards match the currently selected guard,
1912 1915 but will not be pushed if any negative guards match the current guard.
1913 1916 For example:
1914 1917
1915 1918 qguard foo.patch -stable (negative guard)
1916 1919 qguard bar.patch +stable (positive guard)
1917 1920 qselect stable
1918 1921
1919 1922 This activates the "stable" guard. mq will skip foo.patch (because
1920 1923 it has a negative match) but push bar.patch (because it
1921 1924 has a positive match).
1922 1925
1923 1926 With no arguments, prints the currently active guards.
1924 1927 With one argument, sets the active guard.
1925 1928
1926 1929 Use -n/--none to deactivate guards (no other arguments needed).
1927 1930 When no guards are active, patches with positive guards are skipped
1928 1931 and patches with negative guards are pushed.
1929 1932
1930 1933 qselect can change the guards on applied patches. It does not pop
1931 1934 guarded patches by default. Use --pop to pop back to the last applied
1932 1935 patch that is not guarded. Use --reapply (which implies --pop) to push
1933 1936 back to the current patch afterwards, but skip guarded patches.
1934 1937
1935 1938 Use -s/--series to print a list of all guards in the series file (no
1936 1939 other arguments needed). Use -v for more information.'''
1937 1940
1938 1941 q = repo.mq
1939 1942 guards = q.active()
1940 1943 if args or opts['none']:
1941 1944 old_unapplied = q.unapplied(repo)
1942 1945 old_guarded = [i for i in xrange(len(q.applied)) if
1943 1946 not q.pushable(i)[0]]
1944 1947 q.set_active(args)
1945 1948 q.save_dirty()
1946 1949 if not args:
1947 1950 ui.status(_('guards deactivated\n'))
1948 1951 if not opts['pop'] and not opts['reapply']:
1949 1952 unapplied = q.unapplied(repo)
1950 1953 guarded = [i for i in xrange(len(q.applied))
1951 1954 if not q.pushable(i)[0]]
1952 1955 if len(unapplied) != len(old_unapplied):
1953 1956 ui.status(_('number of unguarded, unapplied patches has '
1954 1957 'changed from %d to %d\n') %
1955 1958 (len(old_unapplied), len(unapplied)))
1956 1959 if len(guarded) != len(old_guarded):
1957 1960 ui.status(_('number of guarded, applied patches has changed '
1958 1961 'from %d to %d\n') %
1959 1962 (len(old_guarded), len(guarded)))
1960 1963 elif opts['series']:
1961 1964 guards = {}
1962 1965 noguards = 0
1963 1966 for gs in q.series_guards:
1964 1967 if not gs:
1965 1968 noguards += 1
1966 1969 for g in gs:
1967 1970 guards.setdefault(g, 0)
1968 1971 guards[g] += 1
1969 1972 if ui.verbose:
1970 1973 guards['NONE'] = noguards
1971 1974 guards = guards.items()
1972 1975 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1973 1976 if guards:
1974 1977 ui.note(_('guards in series file:\n'))
1975 1978 for guard, count in guards:
1976 1979 ui.note('%2d ' % count)
1977 1980 ui.write(guard, '\n')
1978 1981 else:
1979 1982 ui.note(_('no guards in series file\n'))
1980 1983 else:
1981 1984 if guards:
1982 1985 ui.note(_('active guards:\n'))
1983 1986 for g in guards:
1984 1987 ui.write(g, '\n')
1985 1988 else:
1986 1989 ui.write(_('no active guards\n'))
1987 1990 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1988 1991 popped = False
1989 1992 if opts['pop'] or opts['reapply']:
1990 1993 for i in xrange(len(q.applied)):
1991 1994 pushable, reason = q.pushable(i)
1992 1995 if not pushable:
1993 1996 ui.status(_('popping guarded patches\n'))
1994 1997 popped = True
1995 1998 if i == 0:
1996 1999 q.pop(repo, all=True)
1997 2000 else:
1998 2001 q.pop(repo, i-1)
1999 2002 break
2000 2003 if popped:
2001 2004 try:
2002 2005 if reapply:
2003 2006 ui.status(_('reapplying unguarded patches\n'))
2004 2007 q.push(repo, reapply)
2005 2008 finally:
2006 2009 q.save_dirty()
2007 2010
2008 2011 def reposetup(ui, repo):
2009 2012 class mqrepo(repo.__class__):
2010 2013 def abort_if_wdir_patched(self, errmsg, force=False):
2011 2014 if self.mq.applied and not force:
2012 2015 parent = revlog.hex(self.dirstate.parents()[0])
2013 2016 if parent in [s.rev for s in self.mq.applied]:
2014 2017 raise util.Abort(errmsg)
2015 2018
2016 2019 def commit(self, *args, **opts):
2017 2020 if len(args) >= 6:
2018 2021 force = args[5]
2019 2022 else:
2020 2023 force = opts.get('force')
2021 2024 self.abort_if_wdir_patched(
2022 2025 _('cannot commit over an applied mq patch'),
2023 2026 force)
2024 2027
2025 2028 return super(mqrepo, self).commit(*args, **opts)
2026 2029
2027 2030 def push(self, remote, force=False, revs=None):
2028 2031 if self.mq.applied and not force and not revs:
2029 2032 raise util.Abort(_('source has mq patches applied'))
2030 2033 return super(mqrepo, self).push(remote, force, revs)
2031 2034
2032 2035 def tags(self):
2033 2036 if self.tagscache:
2034 2037 return self.tagscache
2035 2038
2036 2039 tagscache = super(mqrepo, self).tags()
2037 2040
2038 2041 q = self.mq
2039 2042 if not q.applied:
2040 2043 return tagscache
2041 2044
2042 2045 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2043 2046 mqtags.append((mqtags[-1][0], 'qtip'))
2044 2047 mqtags.append((mqtags[0][0], 'qbase'))
2045 2048 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2046 2049 for patch in mqtags:
2047 2050 if patch[1] in tagscache:
2048 2051 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2049 2052 else:
2050 2053 tagscache[patch[1]] = patch[0]
2051 2054
2052 2055 return tagscache
2053 2056
2054 2057 def _branchtags(self):
2055 2058 q = self.mq
2056 2059 if not q.applied:
2057 2060 return super(mqrepo, self)._branchtags()
2058 2061
2059 2062 self.branchcache = {} # avoid recursion in changectx
2060 2063 cl = self.changelog
2061 2064 partial, last, lrev = self._readbranchcache()
2062 2065
2063 2066 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2064 2067 start = lrev + 1
2065 2068 if start < qbase:
2066 2069 # update the cache (excluding the patches) and save it
2067 2070 self._updatebranchcache(partial, lrev+1, qbase)
2068 2071 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2069 2072 start = qbase
2070 2073 # if start = qbase, the cache is as updated as it should be.
2071 2074 # if start > qbase, the cache includes (part of) the patches.
2072 2075 # we might as well use it, but we won't save it.
2073 2076
2074 2077 # update the cache up to the tip
2075 2078 self._updatebranchcache(partial, start, cl.count())
2076 2079
2077 2080 return partial
2078 2081
2079 2082 if repo.local():
2080 2083 repo.__class__ = mqrepo
2081 2084 repo.mq = queue(ui, repo.join(""))
2082 2085
2083 2086 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2084 2087
2085 2088 cmdtable = {
2086 2089 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2087 2090 "qclone":
2088 2091 (clone,
2089 2092 [('', 'pull', None, _('use pull protocol to copy metadata')),
2090 2093 ('U', 'noupdate', None, _('do not update the new working directories')),
2091 2094 ('', 'uncompressed', None,
2092 2095 _('use uncompressed transfer (fast over LAN)')),
2093 2096 ('e', 'ssh', '', _('specify ssh command to use')),
2094 2097 ('p', 'patches', '', _('location of source patch repo')),
2095 2098 ('', 'remotecmd', '',
2096 2099 _('specify hg command to run on the remote side'))],
2097 2100 _('hg qclone [OPTION]... SOURCE [DEST]')),
2098 2101 "qcommit|qci":
2099 2102 (commit,
2100 2103 commands.table["^commit|ci"][1],
2101 2104 _('hg qcommit [OPTION]... [FILE]...')),
2102 2105 "^qdiff":
2103 2106 (diff,
2104 2107 [('g', 'git', None, _('use git extended diff format')),
2105 2108 ('I', 'include', [], _('include names matching the given patterns')),
2106 2109 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2107 2110 _('hg qdiff [-I] [-X] [-g] [FILE]...')),
2108 2111 "qdelete|qremove|qrm":
2109 2112 (delete,
2110 2113 [('k', 'keep', None, _('keep patch file')),
2111 2114 ('r', 'rev', [], _('stop managing a revision'))],
2112 _('hg qdelete [-k] [-r REV]... PATCH...')),
2115 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2113 2116 'qfold':
2114 2117 (fold,
2115 2118 [('e', 'edit', None, _('edit patch header')),
2116 2119 ('k', 'keep', None, _('keep folded patch files')),
2117 2120 ] + commands.commitopts,
2118 2121 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2119 2122 'qgoto':
2120 2123 (goto,
2121 2124 [('f', 'force', None, _('overwrite any local changes'))],
2122 2125 _('hg qgoto [OPTION]... PATCH')),
2123 2126 'qguard':
2124 2127 (guard,
2125 2128 [('l', 'list', None, _('list all patches and guards')),
2126 2129 ('n', 'none', None, _('drop all guards'))],
2127 2130 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2128 2131 'qheader': (header, [], _('hg qheader [PATCH]')),
2129 2132 "^qimport":
2130 2133 (qimport,
2131 2134 [('e', 'existing', None, 'import file in patch dir'),
2132 2135 ('n', 'name', '', 'patch file name'),
2133 2136 ('f', 'force', None, 'overwrite existing files'),
2134 2137 ('r', 'rev', [], 'place existing revisions under mq control'),
2135 2138 ('g', 'git', None, _('use git extended diff format'))],
2136 2139 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2137 2140 "^qinit":
2138 2141 (init,
2139 2142 [('c', 'create-repo', None, 'create queue repository')],
2140 2143 _('hg qinit [-c]')),
2141 2144 "qnew":
2142 2145 (new,
2143 2146 [('e', 'edit', None, _('edit commit message')),
2144 2147 ('f', 'force', None, _('import uncommitted changes into patch')),
2145 2148 ('I', 'include', [], _('include names matching the given patterns')),
2146 2149 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2147 2150 ] + commands.commitopts,
2148 2151 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2149 2152 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2150 2153 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2151 2154 "^qpop":
2152 2155 (pop,
2153 2156 [('a', 'all', None, _('pop all patches')),
2154 2157 ('n', 'name', '', _('queue name to pop')),
2155 2158 ('f', 'force', None, _('forget any local changes'))],
2156 2159 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2157 2160 "^qpush":
2158 2161 (push,
2159 2162 [('f', 'force', None, _('apply if the patch has rejects')),
2160 2163 ('l', 'list', None, _('list patch name in commit text')),
2161 2164 ('a', 'all', None, _('apply all patches')),
2162 2165 ('m', 'merge', None, _('merge from another queue')),
2163 2166 ('n', 'name', '', _('merge queue name'))],
2164 2167 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2165 2168 "^qrefresh":
2166 2169 (refresh,
2167 2170 [('e', 'edit', None, _('edit commit message')),
2168 2171 ('g', 'git', None, _('use git extended diff format')),
2169 2172 ('s', 'short', None, _('refresh only files already in the patch')),
2170 2173 ('I', 'include', [], _('include names matching the given patterns')),
2171 2174 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2172 2175 ] + commands.commitopts,
2173 2176 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2174 2177 'qrename|qmv':
2175 2178 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2176 2179 "qrestore":
2177 2180 (restore,
2178 2181 [('d', 'delete', None, _('delete save entry')),
2179 2182 ('u', 'update', None, _('update queue working dir'))],
2180 2183 _('hg qrestore [-d] [-u] REV')),
2181 2184 "qsave":
2182 2185 (save,
2183 2186 [('c', 'copy', None, _('copy patch directory')),
2184 2187 ('n', 'name', '', _('copy directory name')),
2185 2188 ('e', 'empty', None, _('clear queue status file')),
2186 2189 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2187 2190 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2188 2191 "qselect":
2189 2192 (select,
2190 2193 [('n', 'none', None, _('disable all guards')),
2191 2194 ('s', 'series', None, _('list all guards in series file')),
2192 2195 ('', 'pop', None, _('pop to before first guarded applied patch')),
2193 2196 ('', 'reapply', None, _('pop, then reapply patches'))],
2194 2197 _('hg qselect [OPTION]... [GUARD]...')),
2195 2198 "qseries":
2196 2199 (series,
2197 2200 [('m', 'missing', None, _('print patches not in series')),
2198 2201 ] + seriesopts,
2199 2202 _('hg qseries [-ms]')),
2200 2203 "^strip":
2201 2204 (strip,
2202 2205 [('f', 'force', None, _('force multi-head removal')),
2203 2206 ('b', 'backup', None, _('bundle unrelated changesets')),
2204 2207 ('n', 'nobackup', None, _('no backups'))],
2205 2208 _('hg strip [-f] [-b] [-n] REV')),
2206 2209 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2207 2210 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2208 2211 }
@@ -1,24 +1,24
1 1 adding base
2 abort: missing patch name
2 abort: qdelete requires at least one revision or patch name
3 3 abort: cannot delete applied patch c
4 4 Now at: b
5 5 a
6 6 b
7 7 a
8 8 b
9 9 series
10 10 status
11 11 Now at: a
12 12 a
13 13 b
14 14 series
15 15 status
16 16 1 [mq]: a
17 17 0 base
18 18 abort: cannot delete revision 3 above applied patches
19 19 f
20 20 4 [mq]: f
21 21 3 [mq]: e
22 22 2 [mq]: d
23 23 1 [mq]: a
24 24 0 base
General Comments 0
You need to be logged in to leave comments. Login now