##// END OF EJS Templates
Add command qheader to display the header of a given patch.
Brendan Cully -
r2747:0016fc74 default
parent child Browse files
Show More
@@ -1,1517 +1,1534 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005 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.demandload import *
33 33 demandload(globals(), "os sys re struct traceback errno bz2")
34 34 from mercurial.i18n import gettext as _
35 35 from mercurial import ui, hg, revlog, commands, util
36 36
37 37 versionstr = "0.45"
38 38
39 39 commands.norepo += " qclone qversion"
40 40
41 41 class queue:
42 42 def __init__(self, ui, path, patchdir=None):
43 43 self.basepath = path
44 44 if patchdir:
45 45 self.path = patchdir
46 46 else:
47 47 self.path = os.path.join(path, "patches")
48 48 self.opener = util.opener(self.path)
49 49 self.ui = ui
50 50 self.applied = []
51 51 self.full_series = []
52 52 self.applied_dirty = 0
53 53 self.series_dirty = 0
54 54 self.series_path = "series"
55 55 self.status_path = "status"
56 56
57 57 if os.path.exists(os.path.join(self.path, self.series_path)):
58 58 self.full_series = self.opener(self.series_path).read().splitlines()
59 59 self.read_series(self.full_series)
60 60
61 61 if os.path.exists(os.path.join(self.path, self.status_path)):
62 62 self.applied = self.opener(self.status_path).read().splitlines()
63 63
64 64 def find_series(self, patch):
65 65 pre = re.compile("(\s*)([^#]+)")
66 66 index = 0
67 67 for l in self.full_series:
68 68 m = pre.match(l)
69 69 if m:
70 70 s = m.group(2)
71 71 s = s.rstrip()
72 72 if s == patch:
73 73 return index
74 74 index += 1
75 75 return None
76 76
77 77 def read_series(self, list):
78 78 def matcher(list):
79 79 pre = re.compile("(\s*)([^#]+)")
80 80 for l in list:
81 81 m = pre.match(l)
82 82 if m:
83 83 s = m.group(2)
84 84 s = s.rstrip()
85 85 if len(s) > 0:
86 86 yield s
87 87 self.series = []
88 88 self.series = [ x for x in matcher(list) ]
89 89
90 90 def save_dirty(self):
91 91 if self.applied_dirty:
92 92 if len(self.applied) > 0:
93 93 nl = "\n"
94 94 else:
95 95 nl = ""
96 96 f = self.opener(self.status_path, "w")
97 97 f.write("\n".join(self.applied) + nl)
98 98 if self.series_dirty:
99 99 if len(self.full_series) > 0:
100 100 nl = "\n"
101 101 else:
102 102 nl = ""
103 103 f = self.opener(self.series_path, "w")
104 104 f.write("\n".join(self.full_series) + nl)
105 105
106 106 def readheaders(self, patch):
107 107 def eatdiff(lines):
108 108 while lines:
109 109 l = lines[-1]
110 110 if (l.startswith("diff -") or
111 111 l.startswith("Index:") or
112 112 l.startswith("===========")):
113 113 del lines[-1]
114 114 else:
115 115 break
116 116 def eatempty(lines):
117 117 while lines:
118 118 l = lines[-1]
119 119 if re.match('\s*$', l):
120 120 del lines[-1]
121 121 else:
122 122 break
123 123
124 124 pf = os.path.join(self.path, patch)
125 125 message = []
126 126 comments = []
127 127 user = None
128 128 date = None
129 129 format = None
130 130 subject = None
131 131 diffstart = 0
132 132
133 133 for line in file(pf):
134 134 line = line.rstrip()
135 135 if diffstart:
136 136 if line.startswith('+++ '):
137 137 diffstart = 2
138 138 break
139 139 if line.startswith("--- "):
140 140 diffstart = 1
141 141 continue
142 142 elif format == "hgpatch":
143 143 # parse values when importing the result of an hg export
144 144 if line.startswith("# User "):
145 145 user = line[7:]
146 146 elif line.startswith("# Date "):
147 147 date = line[7:]
148 148 elif not line.startswith("# ") and line:
149 149 message.append(line)
150 150 format = None
151 151 elif line == '# HG changeset patch':
152 152 format = "hgpatch"
153 153 elif (format != "tagdone" and (line.startswith("Subject: ") or
154 154 line.startswith("subject: "))):
155 155 subject = line[9:]
156 156 format = "tag"
157 157 elif (format != "tagdone" and (line.startswith("From: ") or
158 158 line.startswith("from: "))):
159 159 user = line[6:]
160 160 format = "tag"
161 161 elif format == "tag" and line == "":
162 162 # when looking for tags (subject: from: etc) they
163 163 # end once you find a blank line in the source
164 164 format = "tagdone"
165 165 elif message or line:
166 166 message.append(line)
167 167 comments.append(line)
168 168
169 169 eatdiff(message)
170 170 eatdiff(comments)
171 171 eatempty(message)
172 172 eatempty(comments)
173 173
174 174 # make sure message isn't empty
175 175 if format and format.startswith("tag") and subject:
176 176 message.insert(0, "")
177 177 message.insert(0, subject)
178 178 return (message, comments, user, date, diffstart > 1)
179 179
180 180 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
181 181 # first try just applying the patch
182 182 (err, n) = self.apply(repo, [ patch ], update_status=False,
183 183 strict=True, merge=rev, wlock=wlock)
184 184
185 185 if err == 0:
186 186 return (err, n)
187 187
188 188 if n is None:
189 189 raise util.Abort(_("apply failed for patch %s") % patch)
190 190
191 191 self.ui.warn("patch didn't work out, merging %s\n" % patch)
192 192
193 193 # apply failed, strip away that rev and merge.
194 194 repo.update(head, allow=False, force=True, wlock=wlock)
195 195 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
196 196
197 197 c = repo.changelog.read(rev)
198 198 ret = repo.update(rev, allow=True, wlock=wlock)
199 199 if ret:
200 200 raise util.Abort(_("update returned %d") % ret)
201 201 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
202 202 if n == None:
203 203 raise util.Abort(_("repo commit failed"))
204 204 try:
205 205 message, comments, user, date, patchfound = mergeq.readheaders(patch)
206 206 except:
207 207 raise util.Abort(_("unable to read %s") % patch)
208 208
209 209 patchf = self.opener(patch, "w")
210 210 if comments:
211 211 comments = "\n".join(comments) + '\n\n'
212 212 patchf.write(comments)
213 213 commands.dodiff(patchf, self.ui, repo, head, n)
214 214 patchf.close()
215 215 return (0, n)
216 216
217 217 def qparents(self, repo, rev=None):
218 218 if rev is None:
219 219 (p1, p2) = repo.dirstate.parents()
220 220 if p2 == revlog.nullid:
221 221 return p1
222 222 if len(self.applied) == 0:
223 223 return None
224 224 (top, patch) = self.applied[-1].split(':')
225 225 top = revlog.bin(top)
226 226 return top
227 227 pp = repo.changelog.parents(rev)
228 228 if pp[1] != revlog.nullid:
229 229 arevs = [ x.split(':')[0] for x in self.applied ]
230 230 p0 = revlog.hex(pp[0])
231 231 p1 = revlog.hex(pp[1])
232 232 if p0 in arevs:
233 233 return pp[0]
234 234 if p1 in arevs:
235 235 return pp[1]
236 236 return pp[0]
237 237
238 238 def mergepatch(self, repo, mergeq, series, wlock):
239 239 if len(self.applied) == 0:
240 240 # each of the patches merged in will have two parents. This
241 241 # can confuse the qrefresh, qdiff, and strip code because it
242 242 # needs to know which parent is actually in the patch queue.
243 243 # so, we insert a merge marker with only one parent. This way
244 244 # the first patch in the queue is never a merge patch
245 245 #
246 246 pname = ".hg.patches.merge.marker"
247 247 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
248 248 wlock=wlock)
249 249 self.applied.append(revlog.hex(n) + ":" + pname)
250 250 self.applied_dirty = 1
251 251
252 252 head = self.qparents(repo)
253 253
254 254 for patch in series:
255 255 patch = mergeq.lookup(patch, strict=True)
256 256 if not patch:
257 257 self.ui.warn("patch %s does not exist\n" % patch)
258 258 return (1, None)
259 259
260 260 info = mergeq.isapplied(patch)
261 261 if not info:
262 262 self.ui.warn("patch %s is not applied\n" % patch)
263 263 return (1, None)
264 264 rev = revlog.bin(info[1])
265 265 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
266 266 if head:
267 267 self.applied.append(revlog.hex(head) + ":" + patch)
268 268 self.applied_dirty = 1
269 269 if err:
270 270 return (err, head)
271 271 return (0, head)
272 272
273 273 def apply(self, repo, series, list=False, update_status=True,
274 274 strict=False, patchdir=None, merge=None, wlock=None):
275 275 # TODO unify with commands.py
276 276 if not patchdir:
277 277 patchdir = self.path
278 278 err = 0
279 279 if not wlock:
280 280 wlock = repo.wlock()
281 281 lock = repo.lock()
282 282 tr = repo.transaction()
283 283 n = None
284 284 for patch in series:
285 285 self.ui.warn("applying %s\n" % patch)
286 286 pf = os.path.join(patchdir, patch)
287 287
288 288 try:
289 289 message, comments, user, date, patchfound = self.readheaders(patch)
290 290 except:
291 291 self.ui.warn("Unable to read %s\n" % pf)
292 292 err = 1
293 293 break
294 294
295 295 if not message:
296 296 message = "imported patch %s\n" % patch
297 297 else:
298 298 if list:
299 299 message.append("\nimported patch %s" % patch)
300 300 message = '\n'.join(message)
301 301
302 302 try:
303 303 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
304 304 f = os.popen("%s -d '%s' -p1 --no-backup-if-mismatch < '%s'" %
305 305 (pp, repo.root, pf))
306 306 except:
307 307 self.ui.warn("patch failed, unable to continue (try -v)\n")
308 308 err = 1
309 309 break
310 310 files = []
311 311 fuzz = False
312 312 for l in f:
313 313 l = l.rstrip('\r\n');
314 314 if self.ui.verbose:
315 315 self.ui.warn(l + "\n")
316 316 if l[:14] == 'patching file ':
317 317 pf = os.path.normpath(l[14:])
318 318 # when patch finds a space in the file name, it puts
319 319 # single quotes around the filename. strip them off
320 320 if pf[0] == "'" and pf[-1] == "'":
321 321 pf = pf[1:-1]
322 322 if pf not in files:
323 323 files.append(pf)
324 324 printed_file = False
325 325 file_str = l
326 326 elif l.find('with fuzz') >= 0:
327 327 if not printed_file:
328 328 self.ui.warn(file_str + '\n')
329 329 printed_file = True
330 330 self.ui.warn(l + '\n')
331 331 fuzz = True
332 332 elif l.find('saving rejects to file') >= 0:
333 333 self.ui.warn(l + '\n')
334 334 elif l.find('FAILED') >= 0:
335 335 if not printed_file:
336 336 self.ui.warn(file_str + '\n')
337 337 printed_file = True
338 338 self.ui.warn(l + '\n')
339 339 patcherr = f.close()
340 340
341 341 if merge and len(files) > 0:
342 342 # Mark as merged and update dirstate parent info
343 343 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
344 344 p1, p2 = repo.dirstate.parents()
345 345 repo.dirstate.setparents(p1, merge)
346 346 if len(files) > 0:
347 347 cwd = repo.getcwd()
348 348 cfiles = files
349 349 if cwd:
350 350 cfiles = [util.pathto(cwd, f) for f in files]
351 351 commands.addremove_lock(self.ui, repo, cfiles,
352 352 opts={}, wlock=wlock)
353 353 n = repo.commit(files, message, user, date, force=1, lock=lock,
354 354 wlock=wlock)
355 355
356 356 if n == None:
357 357 raise util.Abort(_("repo commit failed"))
358 358
359 359 if update_status:
360 360 self.applied.append(revlog.hex(n) + ":" + patch)
361 361
362 362 if patcherr:
363 363 if not patchfound:
364 364 self.ui.warn("patch %s is empty\n" % patch)
365 365 err = 0
366 366 else:
367 367 self.ui.warn("patch failed, rejects left in working dir\n")
368 368 err = 1
369 369 break
370 370
371 371 if fuzz and strict:
372 372 self.ui.warn("fuzz found when applying patch, stopping\n")
373 373 err = 1
374 374 break
375 375 tr.close()
376 376 return (err, n)
377 377
378 378 def delete(self, repo, patch):
379 379 patch = self.lookup(patch, strict=True)
380 380 info = self.isapplied(patch)
381 381 if info:
382 382 raise util.Abort(_("cannot delete applied patch %s") % patch)
383 383 if patch not in self.series:
384 384 raise util.Abort(_("patch %s not in series file") % patch)
385 385 i = self.find_series(patch)
386 386 del self.full_series[i]
387 387 self.read_series(self.full_series)
388 388 self.series_dirty = 1
389 389
390 390 def check_toppatch(self, repo):
391 391 if len(self.applied) > 0:
392 392 (top, patch) = self.applied[-1].split(':')
393 393 top = revlog.bin(top)
394 394 pp = repo.dirstate.parents()
395 395 if top not in pp:
396 396 raise util.Abort(_("queue top not at same revision as working directory"))
397 397 return top
398 398 return None
399 399 def check_localchanges(self, repo):
400 400 (c, a, r, d, u) = repo.changes(None, None)
401 401 if c or a or d or r:
402 402 raise util.Abort(_("local changes found, refresh first"))
403 403 def new(self, repo, patch, msg=None, force=None):
404 404 if os.path.exists(os.path.join(self.path, patch)):
405 405 raise util.Abort(_('patch "%s" already exists') % patch)
406 406 commitfiles = []
407 407 (c, a, r, d, u) = repo.changes(None, None)
408 408 if c or a or d or r:
409 409 if not force:
410 410 raise util.Abort(_("local changes found, refresh first"))
411 411 commitfiles = c + a + r
412 412 self.check_toppatch(repo)
413 413 wlock = repo.wlock()
414 414 insert = self.full_series_end()
415 415 if msg:
416 416 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
417 417 wlock=wlock)
418 418 else:
419 419 n = repo.commit(commitfiles,
420 420 "New patch: %s" % patch, force=True, wlock=wlock)
421 421 if n == None:
422 422 raise util.Abort(_("repo commit failed"))
423 423 self.full_series[insert:insert] = [patch]
424 424 self.applied.append(revlog.hex(n) + ":" + patch)
425 425 self.read_series(self.full_series)
426 426 self.series_dirty = 1
427 427 self.applied_dirty = 1
428 428 p = self.opener(patch, "w")
429 429 if msg:
430 430 msg = msg + "\n"
431 431 p.write(msg)
432 432 p.close()
433 433 wlock = None
434 434 r = self.qrepo()
435 435 if r: r.add([patch])
436 436 if commitfiles:
437 437 self.refresh(repo, msg=None, short=True)
438 438
439 439 def strip(self, repo, rev, update=True, backup="all", wlock=None):
440 440 def limitheads(chlog, stop):
441 441 """return the list of all nodes that have no children"""
442 442 p = {}
443 443 h = []
444 444 stoprev = 0
445 445 if stop in chlog.nodemap:
446 446 stoprev = chlog.rev(stop)
447 447
448 448 for r in range(chlog.count() - 1, -1, -1):
449 449 n = chlog.node(r)
450 450 if n not in p:
451 451 h.append(n)
452 452 if n == stop:
453 453 break
454 454 if r < stoprev:
455 455 break
456 456 for pn in chlog.parents(n):
457 457 p[pn] = 1
458 458 return h
459 459
460 460 def bundle(cg):
461 461 backupdir = repo.join("strip-backup")
462 462 if not os.path.isdir(backupdir):
463 463 os.mkdir(backupdir)
464 464 name = os.path.join(backupdir, "%s" % revlog.short(rev))
465 465 name = savename(name)
466 466 self.ui.warn("saving bundle to %s\n" % name)
467 467 # TODO, exclusive open
468 468 f = open(name, "wb")
469 469 try:
470 470 f.write("HG10")
471 471 z = bz2.BZ2Compressor(9)
472 472 while 1:
473 473 chunk = cg.read(4096)
474 474 if not chunk:
475 475 break
476 476 f.write(z.compress(chunk))
477 477 f.write(z.flush())
478 478 except:
479 479 os.unlink(name)
480 480 raise
481 481 f.close()
482 482 return name
483 483
484 484 def stripall(rev, revnum):
485 485 cl = repo.changelog
486 486 c = cl.read(rev)
487 487 mm = repo.manifest.read(c[0])
488 488 seen = {}
489 489
490 490 for x in xrange(revnum, cl.count()):
491 491 c = cl.read(cl.node(x))
492 492 for f in c[3]:
493 493 if f in seen:
494 494 continue
495 495 seen[f] = 1
496 496 if f in mm:
497 497 filerev = mm[f]
498 498 else:
499 499 filerev = 0
500 500 seen[f] = filerev
501 501 # we go in two steps here so the strip loop happens in a
502 502 # sensible order. When stripping many files, this helps keep
503 503 # our disk access patterns under control.
504 504 list = seen.keys()
505 505 list.sort()
506 506 for f in list:
507 507 ff = repo.file(f)
508 508 filerev = seen[f]
509 509 if filerev != 0:
510 510 if filerev in ff.nodemap:
511 511 filerev = ff.rev(filerev)
512 512 else:
513 513 filerev = 0
514 514 ff.strip(filerev, revnum)
515 515
516 516 if not wlock:
517 517 wlock = repo.wlock()
518 518 lock = repo.lock()
519 519 chlog = repo.changelog
520 520 # TODO delete the undo files, and handle undo of merge sets
521 521 pp = chlog.parents(rev)
522 522 revnum = chlog.rev(rev)
523 523
524 524 if update:
525 525 (c, a, r, d, u) = repo.changes(None, None)
526 526 if c or a or d or r:
527 527 raise util.Abort(_("local changes found"))
528 528 urev = self.qparents(repo, rev)
529 529 repo.update(urev, allow=False, force=True, wlock=wlock)
530 530 repo.dirstate.write()
531 531
532 532 # save is a list of all the branches we are truncating away
533 533 # that we actually want to keep. changegroup will be used
534 534 # to preserve them and add them back after the truncate
535 535 saveheads = []
536 536 savebases = {}
537 537
538 538 tip = chlog.tip()
539 539 heads = limitheads(chlog, rev)
540 540 seen = {}
541 541
542 542 # search through all the heads, finding those where the revision
543 543 # we want to strip away is an ancestor. Also look for merges
544 544 # that might be turned into new heads by the strip.
545 545 while heads:
546 546 h = heads.pop()
547 547 n = h
548 548 while True:
549 549 seen[n] = 1
550 550 pp = chlog.parents(n)
551 551 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
552 552 if pp[1] not in seen:
553 553 heads.append(pp[1])
554 554 if pp[0] == revlog.nullid:
555 555 break
556 556 if chlog.rev(pp[0]) < revnum:
557 557 break
558 558 n = pp[0]
559 559 if n == rev:
560 560 break
561 561 r = chlog.reachable(h, rev)
562 562 if rev not in r:
563 563 saveheads.append(h)
564 564 for x in r:
565 565 if chlog.rev(x) > revnum:
566 566 savebases[x] = 1
567 567
568 568 # create a changegroup for all the branches we need to keep
569 569 if backup is "all":
570 570 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
571 571 bundle(backupch)
572 572 if saveheads:
573 573 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
574 574 chgrpfile = bundle(backupch)
575 575
576 576 stripall(rev, revnum)
577 577
578 578 change = chlog.read(rev)
579 579 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
580 580 chlog.strip(revnum, revnum)
581 581 if saveheads:
582 582 self.ui.status("adding branch\n")
583 583 commands.unbundle(self.ui, repo, chgrpfile, update=False)
584 584 if backup is not "strip":
585 585 os.unlink(chgrpfile)
586 586
587 587 def isapplied(self, patch):
588 588 """returns (index, rev, patch)"""
589 589 for i in xrange(len(self.applied)):
590 590 p = self.applied[i]
591 591 a = p.split(':')
592 592 if a[1] == patch:
593 593 return (i, a[0], a[1])
594 594 return None
595 595
596 596 # if the exact patch name does not exist, we try a few
597 597 # variations. If strict is passed, we try only #1
598 598 #
599 599 # 1) a number to indicate an offset in the series file
600 600 # 2) a unique substring of the patch name was given
601 601 # 3) patchname[-+]num to indicate an offset in the series file
602 602 def lookup(self, patch, strict=False):
603 603 def partial_name(s):
604 604 count = 0
605 605 if s in self.series:
606 606 return s
607 607 for x in self.series:
608 608 if s in x:
609 609 count += 1
610 610 last = x
611 611 if count > 1:
612 612 return None
613 613 if count:
614 614 return last
615 615 if len(self.series) > 0 and len(self.applied) > 0:
616 616 if s == 'qtip':
617 617 return self.series[self.series_end()-1]
618 618 if s == 'qbase':
619 619 return self.series[0]
620 620 return None
621 621 if patch == None:
622 622 return None
623 623
624 624 # we don't want to return a partial match until we make
625 625 # sure the file name passed in does not exist (checked below)
626 626 res = partial_name(patch)
627 627 if res and res == patch:
628 628 return res
629 629
630 630 if not os.path.isfile(os.path.join(self.path, patch)):
631 631 try:
632 632 sno = int(patch)
633 633 except(ValueError, OverflowError):
634 634 pass
635 635 else:
636 636 if sno < len(self.series):
637 637 patch = self.series[sno]
638 638 return patch
639 639 if not strict:
640 640 # return any partial match made above
641 641 if res:
642 642 return res
643 643 minus = patch.rsplit('-', 1)
644 644 if len(minus) > 1:
645 645 res = partial_name(minus[0])
646 646 if res:
647 647 i = self.series.index(res)
648 648 try:
649 649 off = int(minus[1] or 1)
650 650 except(ValueError, OverflowError):
651 651 pass
652 652 else:
653 653 if i - off >= 0:
654 654 return self.series[i - off]
655 655 plus = patch.rsplit('+', 1)
656 656 if len(plus) > 1:
657 657 res = partial_name(plus[0])
658 658 if res:
659 659 i = self.series.index(res)
660 660 try:
661 661 off = int(plus[1] or 1)
662 662 except(ValueError, OverflowError):
663 663 pass
664 664 else:
665 665 if i + off < len(self.series):
666 666 return self.series[i + off]
667 667 raise util.Abort(_("patch %s not in series") % patch)
668 668
669 669 def push(self, repo, patch=None, force=False, list=False,
670 670 mergeq=None, wlock=None):
671 671 if not wlock:
672 672 wlock = repo.wlock()
673 673 patch = self.lookup(patch)
674 674 if patch and self.isapplied(patch):
675 675 self.ui.warn(_("patch %s is already applied\n") % patch)
676 676 sys.exit(1)
677 677 if self.series_end() == len(self.series):
678 678 self.ui.warn(_("patch series fully applied\n"))
679 679 sys.exit(1)
680 680 if not force:
681 681 self.check_localchanges(repo)
682 682
683 683 self.applied_dirty = 1;
684 684 start = self.series_end()
685 685 if start > 0:
686 686 self.check_toppatch(repo)
687 687 if not patch:
688 688 patch = self.series[start]
689 689 end = start + 1
690 690 else:
691 691 end = self.series.index(patch, start) + 1
692 692 s = self.series[start:end]
693 693 if mergeq:
694 694 ret = self.mergepatch(repo, mergeq, s, wlock)
695 695 else:
696 696 ret = self.apply(repo, s, list, wlock=wlock)
697 697 top = self.applied[-1].split(':')[1]
698 698 if ret[0]:
699 699 self.ui.write("Errors during apply, please fix and refresh %s\n" %
700 700 top)
701 701 else:
702 702 self.ui.write("Now at: %s\n" % top)
703 703 return ret[0]
704 704
705 705 def pop(self, repo, patch=None, force=False, update=True, all=False,
706 706 wlock=None):
707 707 def getfile(f, rev):
708 708 t = repo.file(f).read(rev)
709 709 try:
710 710 repo.wfile(f, "w").write(t)
711 711 except IOError:
712 712 try:
713 713 os.makedirs(os.path.dirname(repo.wjoin(f)))
714 714 except OSError, err:
715 715 if err.errno != errno.EEXIST: raise
716 716 repo.wfile(f, "w").write(t)
717 717
718 718 if not wlock:
719 719 wlock = repo.wlock()
720 720 if patch:
721 721 # index, rev, patch
722 722 info = self.isapplied(patch)
723 723 if not info:
724 724 patch = self.lookup(patch)
725 725 info = self.isapplied(patch)
726 726 if not info:
727 727 raise util.Abort(_("patch %s is not applied") % patch)
728 728 if len(self.applied) == 0:
729 729 self.ui.warn(_("no patches applied\n"))
730 730 sys.exit(1)
731 731
732 732 if not update:
733 733 parents = repo.dirstate.parents()
734 734 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
735 735 for p in parents:
736 736 if p in rr:
737 737 self.ui.warn("qpop: forcing dirstate update\n")
738 738 update = True
739 739
740 740 if not force and update:
741 741 self.check_localchanges(repo)
742 742
743 743 self.applied_dirty = 1;
744 744 end = len(self.applied)
745 745 if not patch:
746 746 if all:
747 747 popi = 0
748 748 else:
749 749 popi = len(self.applied) - 1
750 750 else:
751 751 popi = info[0] + 1
752 752 if popi >= end:
753 753 self.ui.warn("qpop: %s is already at the top\n" % patch)
754 754 return
755 755 info = [ popi ] + self.applied[popi].split(':')
756 756
757 757 start = info[0]
758 758 rev = revlog.bin(info[1])
759 759
760 760 # we know there are no local changes, so we can make a simplified
761 761 # form of hg.update.
762 762 if update:
763 763 top = self.check_toppatch(repo)
764 764 qp = self.qparents(repo, rev)
765 765 changes = repo.changelog.read(qp)
766 766 mf1 = repo.manifest.readflags(changes[0])
767 767 mmap = repo.manifest.read(changes[0])
768 768 (c, a, r, d, u) = repo.changes(qp, top)
769 769 if d:
770 770 raise util.Abort("deletions found between repo revs")
771 771 for f in c:
772 772 getfile(f, mmap[f])
773 773 for f in r:
774 774 getfile(f, mmap[f])
775 775 util.set_exec(repo.wjoin(f), mf1[f])
776 776 repo.dirstate.update(c + r, 'n')
777 777 for f in a:
778 778 try: os.unlink(repo.wjoin(f))
779 779 except: raise
780 780 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
781 781 except: pass
782 782 if a:
783 783 repo.dirstate.forget(a)
784 784 repo.dirstate.setparents(qp, revlog.nullid)
785 785 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
786 786 del self.applied[start:end]
787 787 if len(self.applied):
788 788 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
789 789 else:
790 790 self.ui.write("Patch queue now empty\n")
791 791
792 792 def diff(self, repo, files):
793 793 top = self.check_toppatch(repo)
794 794 if not top:
795 795 self.ui.write("No patches applied\n")
796 796 return
797 797 qp = self.qparents(repo, top)
798 798 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
799 799
800 800 def refresh(self, repo, msg=None, short=False):
801 801 if len(self.applied) == 0:
802 802 self.ui.write("No patches applied\n")
803 803 return
804 804 wlock = repo.wlock()
805 805 self.check_toppatch(repo)
806 806 qp = self.qparents(repo)
807 807 (top, patch) = self.applied[-1].split(':')
808 808 top = revlog.bin(top)
809 809 cparents = repo.changelog.parents(top)
810 810 patchparent = self.qparents(repo, top)
811 811 message, comments, user, date, patchfound = self.readheaders(patch)
812 812
813 813 patchf = self.opener(patch, "w")
814 814 msg = msg.rstrip()
815 815 if msg:
816 816 if comments:
817 817 # Remove existing message.
818 818 ci = 0
819 819 for mi in range(len(message)):
820 820 while message[mi] != comments[ci]:
821 821 ci += 1
822 822 del comments[ci]
823 823 comments.append(msg)
824 824 if comments:
825 825 comments = "\n".join(comments) + '\n\n'
826 826 patchf.write(comments)
827 827
828 828 tip = repo.changelog.tip()
829 829 if top == tip:
830 830 # if the top of our patch queue is also the tip, there is an
831 831 # optimization here. We update the dirstate in place and strip
832 832 # off the tip commit. Then just commit the current directory
833 833 # tree. We can also send repo.commit the list of files
834 834 # changed to speed up the diff
835 835 #
836 836 # in short mode, we only diff the files included in the
837 837 # patch already
838 838 #
839 839 # this should really read:
840 840 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
841 841 # but we do it backwards to take advantage of manifest/chlog
842 842 # caching against the next repo.changes call
843 843 #
844 844 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
845 845 if short:
846 846 filelist = cc + aa + dd
847 847 else:
848 848 filelist = None
849 849 (c, a, r, d, u) = repo.changes(None, None, filelist)
850 850
851 851 # we might end up with files that were added between tip and
852 852 # the dirstate parent, but then changed in the local dirstate.
853 853 # in this case, we want them to only show up in the added section
854 854 for x in c:
855 855 if x not in aa:
856 856 cc.append(x)
857 857 # we might end up with files added by the local dirstate that
858 858 # were deleted by the patch. In this case, they should only
859 859 # show up in the changed section.
860 860 for x in a:
861 861 if x in dd:
862 862 del dd[dd.index(x)]
863 863 cc.append(x)
864 864 else:
865 865 aa.append(x)
866 866 # make sure any files deleted in the local dirstate
867 867 # are not in the add or change column of the patch
868 868 forget = []
869 869 for x in d + r:
870 870 if x in aa:
871 871 del aa[aa.index(x)]
872 872 forget.append(x)
873 873 continue
874 874 elif x in cc:
875 875 del cc[cc.index(x)]
876 876 dd.append(x)
877 877
878 878 c = list(util.unique(cc))
879 879 r = list(util.unique(dd))
880 880 a = list(util.unique(aa))
881 881 filelist = list(util.unique(c + r + a ))
882 882 commands.dodiff(patchf, self.ui, repo, patchparent, None,
883 883 filelist, changes=(c, a, r, [], u))
884 884 patchf.close()
885 885
886 886 changes = repo.changelog.read(tip)
887 887 repo.dirstate.setparents(*cparents)
888 888 repo.dirstate.update(a, 'a')
889 889 repo.dirstate.update(r, 'r')
890 890 repo.dirstate.update(c, 'n')
891 891 repo.dirstate.forget(forget)
892 892
893 893 if not msg:
894 894 if not message:
895 895 message = "patch queue: %s\n" % patch
896 896 else:
897 897 message = "\n".join(message)
898 898 else:
899 899 message = msg
900 900
901 901 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
902 902 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
903 903 self.applied[-1] = revlog.hex(n) + ':' + patch
904 904 self.applied_dirty = 1
905 905 else:
906 906 commands.dodiff(patchf, self.ui, repo, patchparent, None)
907 907 patchf.close()
908 908 self.pop(repo, force=True, wlock=wlock)
909 909 self.push(repo, force=True, wlock=wlock)
910 910
911 911 def init(self, repo, create=False):
912 912 if os.path.isdir(self.path):
913 913 raise util.Abort(_("patch queue directory already exists"))
914 914 os.mkdir(self.path)
915 915 if create:
916 916 return self.qrepo(create=True)
917 917
918 918 def unapplied(self, repo, patch=None):
919 919 if patch and patch not in self.series:
920 920 raise util.Abort(_("patch %s is not in series file") % patch)
921 921 if not patch:
922 922 start = self.series_end()
923 923 else:
924 924 start = self.series.index(patch) + 1
925 925 for p in self.series[start:]:
926 926 if self.ui.verbose:
927 927 self.ui.write("%d " % self.series.index(p))
928 928 self.ui.write("%s\n" % p)
929 929
930 930 def qseries(self, repo, missing=None):
931 931 start = self.series_end()
932 932 if not missing:
933 933 for p in self.series[:start]:
934 934 if self.ui.verbose:
935 935 self.ui.write("%d A " % self.series.index(p))
936 936 self.ui.write("%s\n" % p)
937 937 for p in self.series[start:]:
938 938 if self.ui.verbose:
939 939 self.ui.write("%d U " % self.series.index(p))
940 940 self.ui.write("%s\n" % p)
941 941 else:
942 942 list = []
943 943 for root, dirs, files in os.walk(self.path):
944 944 d = root[len(self.path) + 1:]
945 945 for f in files:
946 946 fl = os.path.join(d, f)
947 947 if (fl not in self.series and
948 948 fl not in (self.status_path, self.series_path)
949 949 and not fl.startswith('.')):
950 950 list.append(fl)
951 951 list.sort()
952 952 if list:
953 953 for x in list:
954 954 if self.ui.verbose:
955 955 self.ui.write("D ")
956 956 self.ui.write("%s\n" % x)
957 957
958 958 def issaveline(self, l):
959 959 name = l.split(':')[1]
960 960 if name == '.hg.patches.save.line':
961 961 return True
962 962
963 963 def qrepo(self, create=False):
964 964 if create or os.path.isdir(os.path.join(self.path, ".hg")):
965 965 return hg.repository(self.ui, path=self.path, create=create)
966 966
967 967 def restore(self, repo, rev, delete=None, qupdate=None):
968 968 c = repo.changelog.read(rev)
969 969 desc = c[4].strip()
970 970 lines = desc.splitlines()
971 971 i = 0
972 972 datastart = None
973 973 series = []
974 974 applied = []
975 975 qpp = None
976 976 for i in xrange(0, len(lines)):
977 977 if lines[i] == 'Patch Data:':
978 978 datastart = i + 1
979 979 elif lines[i].startswith('Dirstate:'):
980 980 l = lines[i].rstrip()
981 981 l = l[10:].split(' ')
982 982 qpp = [ hg.bin(x) for x in l ]
983 983 elif datastart != None:
984 984 l = lines[i].rstrip()
985 985 index = l.index(':')
986 986 id = l[:index]
987 987 file = l[index + 1:]
988 988 if id:
989 989 applied.append(l)
990 990 series.append(file)
991 991 if datastart == None:
992 992 self.ui.warn("No saved patch data found\n")
993 993 return 1
994 994 self.ui.warn("restoring status: %s\n" % lines[0])
995 995 self.full_series = series
996 996 self.applied = applied
997 997 self.read_series(self.full_series)
998 998 self.series_dirty = 1
999 999 self.applied_dirty = 1
1000 1000 heads = repo.changelog.heads()
1001 1001 if delete:
1002 1002 if rev not in heads:
1003 1003 self.ui.warn("save entry has children, leaving it alone\n")
1004 1004 else:
1005 1005 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1006 1006 pp = repo.dirstate.parents()
1007 1007 if rev in pp:
1008 1008 update = True
1009 1009 else:
1010 1010 update = False
1011 1011 self.strip(repo, rev, update=update, backup='strip')
1012 1012 if qpp:
1013 1013 self.ui.warn("saved queue repository parents: %s %s\n" %
1014 1014 (hg.short(qpp[0]), hg.short(qpp[1])))
1015 1015 if qupdate:
1016 1016 print "queue directory updating"
1017 1017 r = self.qrepo()
1018 1018 if not r:
1019 1019 self.ui.warn("Unable to load queue repository\n")
1020 1020 return 1
1021 1021 r.update(qpp[0], allow=False, force=True)
1022 1022
1023 1023 def save(self, repo, msg=None):
1024 1024 if len(self.applied) == 0:
1025 1025 self.ui.warn("save: no patches applied, exiting\n")
1026 1026 return 1
1027 1027 if self.issaveline(self.applied[-1]):
1028 1028 self.ui.warn("status is already saved\n")
1029 1029 return 1
1030 1030
1031 1031 ar = [ ':' + x for x in self.full_series ]
1032 1032 if not msg:
1033 1033 msg = "hg patches saved state"
1034 1034 else:
1035 1035 msg = "hg patches: " + msg.rstrip('\r\n')
1036 1036 r = self.qrepo()
1037 1037 if r:
1038 1038 pp = r.dirstate.parents()
1039 1039 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1040 1040 msg += "\n\nPatch Data:\n"
1041 1041 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1042 1042 + '\n' or "")
1043 1043 n = repo.commit(None, text, user=None, force=1)
1044 1044 if not n:
1045 1045 self.ui.warn("repo commit failed\n")
1046 1046 return 1
1047 1047 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1048 1048 self.applied_dirty = 1
1049 1049
1050 1050 def full_series_end(self):
1051 1051 if len(self.applied) > 0:
1052 1052 (top, p) = self.applied[-1].split(':')
1053 1053 end = self.find_series(p)
1054 1054 if end == None:
1055 1055 return len(self.full_series)
1056 1056 return end + 1
1057 1057 return 0
1058 1058
1059 1059 def series_end(self):
1060 1060 end = 0
1061 1061 if len(self.applied) > 0:
1062 1062 (top, p) = self.applied[-1].split(':')
1063 1063 try:
1064 1064 end = self.series.index(p)
1065 1065 except ValueError:
1066 1066 return 0
1067 1067 return end + 1
1068 1068 return end
1069 1069
1070 1070 def qapplied(self, repo, patch=None):
1071 1071 if patch and patch not in self.series:
1072 1072 raise util.Abort(_("patch %s is not in series file") % patch)
1073 1073 if not patch:
1074 1074 end = len(self.applied)
1075 1075 else:
1076 1076 end = self.series.index(patch) + 1
1077 1077 for x in xrange(end):
1078 1078 p = self.appliedname(x)
1079 1079 self.ui.write("%s\n" % p)
1080 1080
1081 1081 def appliedname(self, index):
1082 1082 p = self.applied[index]
1083 1083 pname = p.split(':')[1]
1084 1084 if not self.ui.verbose:
1085 1085 p = pname
1086 1086 else:
1087 1087 p = str(self.series.index(pname)) + " " + p
1088 1088 return p
1089 1089
1090 1090 def top(self, repo):
1091 1091 if len(self.applied):
1092 1092 p = self.appliedname(-1)
1093 1093 self.ui.write(p + '\n')
1094 1094 else:
1095 1095 self.ui.write("No patches applied\n")
1096 1096
1097 1097 def next(self, repo):
1098 1098 end = self.series_end()
1099 1099 if end == len(self.series):
1100 1100 self.ui.write("All patches applied\n")
1101 1101 else:
1102 1102 p = self.series[end]
1103 1103 if self.ui.verbose:
1104 1104 self.ui.write("%d " % self.series.index(p))
1105 1105 self.ui.write(p + '\n')
1106 1106
1107 1107 def prev(self, repo):
1108 1108 if len(self.applied) > 1:
1109 1109 p = self.appliedname(-2)
1110 1110 self.ui.write(p + '\n')
1111 1111 elif len(self.applied) == 1:
1112 1112 self.ui.write("Only one patch applied\n")
1113 1113 else:
1114 1114 self.ui.write("No patches applied\n")
1115 1115
1116 1116 def qimport(self, repo, files, patch=None, existing=None, force=None):
1117 1117 if len(files) > 1 and patch:
1118 1118 raise util.Abort(_('option "-n" not valid when importing multiple '
1119 1119 'files'))
1120 1120 i = 0
1121 1121 added = []
1122 1122 for filename in files:
1123 1123 if existing:
1124 1124 if not patch:
1125 1125 patch = filename
1126 1126 if not os.path.isfile(os.path.join(self.path, patch)):
1127 1127 raise util.Abort(_("patch %s does not exist") % patch)
1128 1128 else:
1129 1129 try:
1130 1130 text = file(filename).read()
1131 1131 except IOError:
1132 1132 raise util.Abort(_("unable to read %s") % patch)
1133 1133 if not patch:
1134 1134 patch = os.path.split(filename)[1]
1135 1135 if not force and os.path.exists(os.path.join(self.path, patch)):
1136 1136 raise util.Abort(_('patch "%s" already exists') % patch)
1137 1137 patchf = self.opener(patch, "w")
1138 1138 patchf.write(text)
1139 1139 if patch in self.series:
1140 1140 raise util.Abort(_('patch %s is already in the series file')
1141 1141 % patch)
1142 1142 index = self.full_series_end() + i
1143 1143 self.full_series[index:index] = [patch]
1144 1144 self.read_series(self.full_series)
1145 1145 self.ui.warn("adding %s to series file\n" % patch)
1146 1146 i += 1
1147 1147 added.append(patch)
1148 1148 patch = None
1149 1149 self.series_dirty = 1
1150 1150 qrepo = self.qrepo()
1151 1151 if qrepo:
1152 1152 qrepo.add(added)
1153 1153
1154 1154 def delete(ui, repo, patch, **opts):
1155 1155 """remove a patch from the series file"""
1156 1156 q = repo.mq
1157 1157 q.delete(repo, patch)
1158 1158 q.save_dirty()
1159 1159 return 0
1160 1160
1161 1161 def applied(ui, repo, patch=None, **opts):
1162 1162 """print the patches already applied"""
1163 1163 repo.mq.qapplied(repo, patch)
1164 1164 return 0
1165 1165
1166 1166 def unapplied(ui, repo, patch=None, **opts):
1167 1167 """print the patches not yet applied"""
1168 1168 repo.mq.unapplied(repo, patch)
1169 1169 return 0
1170 1170
1171 1171 def qimport(ui, repo, *filename, **opts):
1172 1172 """import a patch"""
1173 1173 q = repo.mq
1174 1174 q.qimport(repo, filename, patch=opts['name'],
1175 1175 existing=opts['existing'], force=opts['force'])
1176 1176 q.save_dirty()
1177 1177 return 0
1178 1178
1179 1179 def init(ui, repo, **opts):
1180 1180 """init a new queue repository"""
1181 1181 q = repo.mq
1182 1182 r = q.init(repo, create=opts['create_repo'])
1183 1183 q.save_dirty()
1184 1184 if r:
1185 1185 fp = r.wopener('.hgignore', 'w')
1186 1186 print >> fp, 'syntax: glob'
1187 1187 print >> fp, 'status'
1188 1188 fp.close()
1189 1189 r.wopener('series', 'w').close()
1190 1190 r.add(['.hgignore', 'series'])
1191 1191 return 0
1192 1192
1193 1193 def clone(ui, source, dest=None, **opts):
1194 1194 '''clone main and patch repository at same time
1195 1195
1196 1196 If source is local, destination will have no patches applied. If
1197 1197 source is remote, this command can not check if patches are
1198 1198 applied in source, so cannot guarantee that patches are not
1199 1199 applied in destination. If you clone remote repository, be sure
1200 1200 before that it has no patches applied.
1201 1201
1202 1202 Source patch repository is looked for in <src>/.hg/patches by
1203 1203 default. Use -p <url> to change.
1204 1204 '''
1205 1205 commands.setremoteconfig(**opts)
1206 1206 if dest is None:
1207 1207 dest = hg.defaultdest(source)
1208 1208 sr = hg.repository(ui, ui.expandpath(source))
1209 1209 qbase, destrev = None, None
1210 1210 if sr.local():
1211 1211 reposetup(ui, sr)
1212 1212 if sr.mq.applied:
1213 1213 qbase = revlog.bin(sr.mq.applied[0].split(':')[0])
1214 1214 if not hg.islocal(dest):
1215 1215 destrev = sr.parents(qbase)[0]
1216 1216 ui.note(_('cloning main repo\n'))
1217 1217 sr, dr = hg.clone(ui, sr, dest,
1218 1218 pull=opts['pull'],
1219 1219 rev=destrev,
1220 1220 update=False,
1221 1221 stream=opts['uncompressed'])
1222 1222 ui.note(_('cloning patch repo\n'))
1223 1223 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1224 1224 dr.url() + '/.hg/patches',
1225 1225 pull=opts['pull'],
1226 1226 update=not opts['noupdate'],
1227 1227 stream=opts['uncompressed'])
1228 1228 if dr.local():
1229 1229 if qbase:
1230 1230 ui.note(_('stripping applied patches from destination repo\n'))
1231 1231 reposetup(ui, dr)
1232 1232 dr.mq.strip(dr, qbase, update=False, backup=None)
1233 1233 if not opts['noupdate']:
1234 1234 ui.note(_('updating destination repo\n'))
1235 1235 dr.update(dr.changelog.tip())
1236 1236
1237 1237 def commit(ui, repo, *pats, **opts):
1238 1238 """commit changes in the queue repository"""
1239 1239 q = repo.mq
1240 1240 r = q.qrepo()
1241 1241 if not r: raise util.Abort('no queue repository')
1242 1242 commands.commit(r.ui, r, *pats, **opts)
1243 1243
1244 1244 def series(ui, repo, **opts):
1245 1245 """print the entire series file"""
1246 1246 repo.mq.qseries(repo, missing=opts['missing'])
1247 1247 return 0
1248 1248
1249 1249 def top(ui, repo, **opts):
1250 1250 """print the name of the current patch"""
1251 1251 repo.mq.top(repo)
1252 1252 return 0
1253 1253
1254 1254 def next(ui, repo, **opts):
1255 1255 """print the name of the next patch"""
1256 1256 repo.mq.next(repo)
1257 1257 return 0
1258 1258
1259 1259 def prev(ui, repo, **opts):
1260 1260 """print the name of the previous patch"""
1261 1261 repo.mq.prev(repo)
1262 1262 return 0
1263 1263
1264 1264 def new(ui, repo, patch, **opts):
1265 1265 """create a new patch"""
1266 1266 q = repo.mq
1267 1267 message=commands.logmessage(**opts)
1268 1268 q.new(repo, patch, msg=message, force=opts['force'])
1269 1269 q.save_dirty()
1270 1270 return 0
1271 1271
1272 1272 def refresh(ui, repo, **opts):
1273 1273 """update the current patch"""
1274 1274 q = repo.mq
1275 1275 message=commands.logmessage(**opts)
1276 1276 if opts['edit']:
1277 1277 if message:
1278 1278 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1279 1279 patch = q.applied[-1].split(':')[1]
1280 1280 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1281 1281 message = ui.edit('\n'.join(message), user or ui.username())
1282 1282 q.refresh(repo, msg=message, short=opts['short'])
1283 1283 q.save_dirty()
1284 1284 return 0
1285 1285
1286 1286 def diff(ui, repo, *files, **opts):
1287 1287 """diff of the current patch"""
1288 1288 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1289 1289 repo.mq.diff(repo, list(files))
1290 1290 return 0
1291 1291
1292 def header(ui, repo, patch=None):
1293 """Print the header of the topmost or specified patch"""
1294 q = repo.mq
1295
1296 if patch:
1297 patch = q.lookup(patch)
1298 else:
1299 if not q.applied:
1300 ui.write('No patches applied\n')
1301 return
1302 patch = q.lookup('qtip')
1303 message = repo.mq.readheaders(patch)[0]
1304
1305 ui.write('\n'.join(message) + '\n')
1306
1292 1307 def lastsavename(path):
1293 1308 (dir, base) = os.path.split(path)
1294 1309 names = os.listdir(dir)
1295 1310 namere = re.compile("%s.([0-9]+)" % base)
1296 1311 max = None
1297 1312 maxname = None
1298 1313 for f in names:
1299 1314 m = namere.match(f)
1300 1315 if m:
1301 1316 index = int(m.group(1))
1302 1317 if max == None or index > max:
1303 1318 max = index
1304 1319 maxname = f
1305 1320 if maxname:
1306 1321 return (os.path.join(dir, maxname), max)
1307 1322 return (None, None)
1308 1323
1309 1324 def savename(path):
1310 1325 (last, index) = lastsavename(path)
1311 1326 if last is None:
1312 1327 index = 0
1313 1328 newpath = path + ".%d" % (index + 1)
1314 1329 return newpath
1315 1330
1316 1331 def push(ui, repo, patch=None, **opts):
1317 1332 """push the next patch onto the stack"""
1318 1333 q = repo.mq
1319 1334 mergeq = None
1320 1335
1321 1336 if opts['all']:
1322 1337 patch = q.series[-1]
1323 1338 if opts['merge']:
1324 1339 if opts['name']:
1325 1340 newpath = opts['name']
1326 1341 else:
1327 1342 newpath, i = lastsavename(q.path)
1328 1343 if not newpath:
1329 1344 ui.warn("no saved queues found, please use -n\n")
1330 1345 return 1
1331 1346 mergeq = queue(ui, repo.join(""), newpath)
1332 1347 ui.warn("merging with queue at: %s\n" % mergeq.path)
1333 1348 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1334 1349 mergeq=mergeq)
1335 1350 q.save_dirty()
1336 1351 return ret
1337 1352
1338 1353 def pop(ui, repo, patch=None, **opts):
1339 1354 """pop the current patch off the stack"""
1340 1355 localupdate = True
1341 1356 if opts['name']:
1342 1357 q = queue(ui, repo.join(""), repo.join(opts['name']))
1343 1358 ui.warn('using patch queue: %s\n' % q.path)
1344 1359 localupdate = False
1345 1360 else:
1346 1361 q = repo.mq
1347 1362 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1348 1363 q.save_dirty()
1349 1364 return 0
1350 1365
1351 1366 def restore(ui, repo, rev, **opts):
1352 1367 """restore the queue state saved by a rev"""
1353 1368 rev = repo.lookup(rev)
1354 1369 q = repo.mq
1355 1370 q.restore(repo, rev, delete=opts['delete'],
1356 1371 qupdate=opts['update'])
1357 1372 q.save_dirty()
1358 1373 return 0
1359 1374
1360 1375 def save(ui, repo, **opts):
1361 1376 """save current queue state"""
1362 1377 q = repo.mq
1363 1378 message=commands.logmessage(**opts)
1364 1379 ret = q.save(repo, msg=message)
1365 1380 if ret:
1366 1381 return ret
1367 1382 q.save_dirty()
1368 1383 if opts['copy']:
1369 1384 path = q.path
1370 1385 if opts['name']:
1371 1386 newpath = os.path.join(q.basepath, opts['name'])
1372 1387 if os.path.exists(newpath):
1373 1388 if not os.path.isdir(newpath):
1374 1389 raise util.Abort(_('destination %s exists and is not '
1375 1390 'a directory') % newpath)
1376 1391 if not opts['force']:
1377 1392 raise util.Abort(_('destination %s exists, '
1378 1393 'use -f to force') % newpath)
1379 1394 else:
1380 1395 newpath = savename(path)
1381 1396 ui.warn("copy %s to %s\n" % (path, newpath))
1382 1397 util.copyfiles(path, newpath)
1383 1398 if opts['empty']:
1384 1399 try:
1385 1400 os.unlink(os.path.join(q.path, q.status_path))
1386 1401 except:
1387 1402 pass
1388 1403 return 0
1389 1404
1390 1405 def strip(ui, repo, rev, **opts):
1391 1406 """strip a revision and all later revs on the same branch"""
1392 1407 rev = repo.lookup(rev)
1393 1408 backup = 'all'
1394 1409 if opts['backup']:
1395 1410 backup = 'strip'
1396 1411 elif opts['nobackup']:
1397 1412 backup = 'none'
1398 1413 repo.mq.strip(repo, rev, backup=backup)
1399 1414 return 0
1400 1415
1401 1416 def version(ui, q=None):
1402 1417 """print the version number"""
1403 1418 ui.write("mq version %s\n" % versionstr)
1404 1419 return 0
1405 1420
1406 1421 def reposetup(ui, repo):
1407 1422 class MqRepo(repo.__class__):
1408 1423 def tags(self):
1409 1424 if self.tagscache:
1410 1425 return self.tagscache
1411 1426
1412 1427 tagscache = super(MqRepo, self).tags()
1413 1428
1414 1429 q = self.mq
1415 1430 if not q.applied:
1416 1431 return tagscache
1417 1432
1418 1433 mqtags = [patch.split(':') for patch in q.applied]
1419 1434 mqtags.append((mqtags[-1][0], 'qtip'))
1420 1435 mqtags.append((mqtags[0][0], 'qbase'))
1421 1436 for patch in mqtags:
1422 1437 if patch[1] in tagscache:
1423 1438 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1424 1439 else:
1425 1440 tagscache[patch[1]] = revlog.bin(patch[0])
1426 1441
1427 1442 return tagscache
1428 1443
1429 1444 repo.__class__ = MqRepo
1430 1445 repo.mq = queue(ui, repo.join(""))
1431 1446
1432 1447 cmdtable = {
1433 1448 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1434 1449 "qclone": (clone,
1435 1450 [('', 'pull', None, _('use pull protocol to copy metadata')),
1436 1451 ('U', 'noupdate', None, _('do not update the new working directories')),
1437 1452 ('', 'uncompressed', None,
1438 1453 _('use uncompressed transfer (fast over LAN)')),
1439 1454 ('e', 'ssh', '', _('specify ssh command to use')),
1440 1455 ('p', 'patches', '', _('location of source patch repo')),
1441 1456 ('', 'remotecmd', '',
1442 1457 _('specify hg command to run on the remote side'))],
1443 1458 'hg qclone [OPTION]... SOURCE [DEST]'),
1444 1459 "qcommit|qci":
1445 1460 (commit,
1446 1461 commands.table["^commit|ci"][1],
1447 1462 'hg qcommit [OPTION]... [FILE]...'),
1448 1463 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1449 1464 "qdelete": (delete, [], 'hg qdelete PATCH'),
1465 'qheader': (header, [],
1466 _('hg qheader [PATCH]')),
1450 1467 "^qimport":
1451 1468 (qimport,
1452 1469 [('e', 'existing', None, 'import file in patch dir'),
1453 1470 ('n', 'name', '', 'patch file name'),
1454 1471 ('f', 'force', None, 'overwrite existing files')],
1455 1472 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1456 1473 "^qinit":
1457 1474 (init,
1458 1475 [('c', 'create-repo', None, 'create queue repository')],
1459 1476 'hg qinit [-c]'),
1460 1477 "qnew":
1461 1478 (new,
1462 1479 [('m', 'message', '', _('use <text> as commit message')),
1463 1480 ('l', 'logfile', '', _('read the commit message from <file>')),
1464 1481 ('f', 'force', None, 'force')],
1465 1482 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1466 1483 "qnext": (next, [], 'hg qnext'),
1467 1484 "qprev": (prev, [], 'hg qprev'),
1468 1485 "^qpop":
1469 1486 (pop,
1470 1487 [('a', 'all', None, 'pop all patches'),
1471 1488 ('n', 'name', '', 'queue name to pop'),
1472 1489 ('f', 'force', None, 'forget any local changes')],
1473 1490 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1474 1491 "^qpush":
1475 1492 (push,
1476 1493 [('f', 'force', None, 'apply if the patch has rejects'),
1477 1494 ('l', 'list', None, 'list patch name in commit text'),
1478 1495 ('a', 'all', None, 'apply all patches'),
1479 1496 ('m', 'merge', None, 'merge from another queue'),
1480 1497 ('n', 'name', '', 'merge queue name')],
1481 1498 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1482 1499 "^qrefresh":
1483 1500 (refresh,
1484 1501 [('e', 'edit', None, _('edit commit message')),
1485 1502 ('m', 'message', '', _('change commit message with <text>')),
1486 1503 ('l', 'logfile', '', _('change commit message with <file> content')),
1487 1504 ('s', 'short', None, 'short refresh')],
1488 1505 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1489 1506 "qrestore":
1490 1507 (restore,
1491 1508 [('d', 'delete', None, 'delete save entry'),
1492 1509 ('u', 'update', None, 'update queue working dir')],
1493 1510 'hg qrestore [-d] [-u] REV'),
1494 1511 "qsave":
1495 1512 (save,
1496 1513 [('m', 'message', '', _('use <text> as commit message')),
1497 1514 ('l', 'logfile', '', _('read the commit message from <file>')),
1498 1515 ('c', 'copy', None, 'copy patch directory'),
1499 1516 ('n', 'name', '', 'copy directory name'),
1500 1517 ('e', 'empty', None, 'clear queue status file'),
1501 1518 ('f', 'force', None, 'force copy')],
1502 1519 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1503 1520 "qseries":
1504 1521 (series,
1505 1522 [('m', 'missing', None, 'print patches not in series')],
1506 1523 'hg qseries [-m]'),
1507 1524 "^strip":
1508 1525 (strip,
1509 1526 [('f', 'force', None, 'force multi-head removal'),
1510 1527 ('b', 'backup', None, 'bundle unrelated changesets'),
1511 1528 ('n', 'nobackup', None, 'no backups')],
1512 1529 'hg strip [-f] [-b] [-n] REV'),
1513 1530 "qtop": (top, [], 'hg qtop'),
1514 1531 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1515 1532 "qversion": (version, [], 'hg qversion')
1516 1533 }
1517 1534
@@ -1,111 +1,112 b''
1 1 % help
2 2 mq extension - patch management and development
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and
6 6 applied patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches
9 9 directory. Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 prepare repository to work with patches qinit
14 14 create new patch qnew
15 15 import existing patch qimport
16 16
17 17 print patch series qseries
18 18 print applied patches qapplied
19 19 print name of top applied patch qtop
20 20
21 21 add known patch to applied stack qpush
22 22 remove patch from applied stack qpop
23 23 refresh contents of top applied patch qrefresh
24 24
25 25 list of commands (use "hg help -v mq" to show aliases and global options):
26 26
27 27 qapplied print the patches already applied
28 28 qclone clone main and patch repository at same time
29 29 qcommit commit changes in the queue repository
30 30 qdelete remove a patch from the series file
31 31 qdiff diff of the current patch
32 qheader Print the header of the topmost or specified patch
32 33 qimport import a patch
33 34 qinit init a new queue repository
34 35 qnew create a new patch
35 36 qnext print the name of the next patch
36 37 qpop pop the current patch off the stack
37 38 qprev print the name of the previous patch
38 39 qpush push the next patch onto the stack
39 40 qrefresh update the current patch
40 41 qrestore restore the queue state saved by a rev
41 42 qsave save current queue state
42 43 qseries print the entire series file
43 44 qtop print the name of the current patch
44 45 qunapplied print the patches not yet applied
45 46 qversion print the version number
46 47 strip strip a revision and all later revs on the same branch
47 48 adding a
48 49 adding b/z
49 50 % qinit
50 51 % -R qinit
51 52 % qinit -c
52 53 A .hgignore
53 54 A series
54 55 % qnew implies add
55 56 A .hgignore
56 57 A series
57 58 A test.patch
58 59 % qnew -m
59 60 foo bar
60 61 % qrefresh
61 62 foo bar
62 63
63 64 diff -r xa
64 65 --- a/a
65 66 +++ b/a
66 67 @@ -1,1 +1,2 @@ a
67 68 a
68 69 +a
69 70 % qpop
70 71 Patch queue now empty
71 72 % qpush
72 73 applying test.patch
73 74 Now at: test.patch
74 75 % pop/push outside repo
75 76 Patch queue now empty
76 77 applying test.patch
77 78 Now at: test.patch
78 79 % qrefresh in subdir
79 80 % pop/push -a in subdir
80 81 Patch queue now empty
81 82 applying test.patch
82 83 applying test2.patch
83 84 Now at: test2.patch
84 85 % qseries
85 86 test.patch
86 87 test2.patch
87 88 % qapplied
88 89 test.patch
89 90 test2.patch
90 91 % qtop
91 92 test2.patch
92 93 % qprev
93 94 test.patch
94 95 % qnext
95 96 All patches applied
96 97 % pop, qnext, qprev, qapplied
97 98 Now at: test.patch
98 99 test2.patch
99 100 Only one patch applied
100 101 test.patch
101 102 % qunapplied
102 103 test2.patch
103 104 % strip
104 105 adding x
105 106 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
106 107 saving bundle to
107 108 adding changesets
108 109 adding manifests
109 110 adding file changes
110 111 added 1 changesets with 1 changes to 1 files
111 112 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now