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