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