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