##// END OF EJS Templates
mq: qpop should act like quilt pop...
Chris Mason -
r2697:6c540dd1 default
parent child Browse files
Show More
@@ -1,1443 +1,1452
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, strict=True)
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, strict=True)
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, msg=None, 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 # if the exact patch name does not exist, we try a few
602 602 # variations. If strict is passed, we try only #1
603 603 #
604 604 # 1) a number to indicate an offset in the series file
605 605 # 2) a unique substring of the patch name was given
606 606 # 3) patchname[-+]num to indicate an offset in the series file
607 607 def lookup(self, patch, strict=False):
608 608 def partial_name(s):
609 609 count = 0
610 610 if s in self.series:
611 611 return s
612 612 for x in self.series:
613 613 if s in x:
614 614 count += 1
615 615 last = x
616 616 if count > 1:
617 617 return None
618 618 if count:
619 619 return last
620 620 if len(self.series) > 0 and len(self.applied) > 0:
621 621 if s == 'qtip':
622 622 return self.series[self.series_end()-1]
623 623 if s == 'qbase':
624 624 return self.series[0]
625 625 return None
626 626 if patch == None:
627 627 return None
628 628
629 629 # we don't want to return a partial match until we make
630 630 # sure the file name passed in does not exist (checked below)
631 631 res = partial_name(patch)
632 632 if res and res == patch:
633 633 return res
634 634
635 635 if not os.path.isfile(os.path.join(self.path, patch)):
636 636 try:
637 637 sno = int(patch)
638 638 except(ValueError, OverflowError):
639 639 pass
640 640 else:
641 641 if sno < len(self.series):
642 642 patch = self.series[sno]
643 643 return patch
644 644 if not strict:
645 645 # return any partial match made above
646 646 if res:
647 647 return res
648 648 minus = patch.rsplit('-', 1)
649 649 if len(minus) > 1:
650 650 res = partial_name(minus[0])
651 651 if res:
652 652 i = self.series.index(res)
653 653 try:
654 654 off = int(minus[1] or 1)
655 655 except(ValueError, OverflowError):
656 656 pass
657 657 else:
658 658 if i - off >= 0:
659 659 return self.series[i - off]
660 660 plus = patch.rsplit('+', 1)
661 661 if len(plus) > 1:
662 662 res = partial_name(plus[0])
663 663 if res:
664 664 i = self.series.index(res)
665 665 try:
666 666 off = int(plus[1] or 1)
667 667 except(ValueError, OverflowError):
668 668 pass
669 669 else:
670 670 if i + off < len(self.series):
671 671 return self.series[i + off]
672 672 self.ui.warn("patch %s not in series\n" % patch)
673 673 sys.exit(1)
674 674
675 675 def push(self, repo, patch=None, force=False, list=False,
676 676 mergeq=None, wlock=None):
677 677 if not wlock:
678 678 wlock = repo.wlock()
679 679 patch = self.lookup(patch)
680 680 if patch and self.isapplied(patch):
681 681 self.ui.warn("patch %s is already applied\n" % patch)
682 682 sys.exit(1)
683 683 if self.series_end() == len(self.series):
684 684 self.ui.warn("File series fully applied\n")
685 685 sys.exit(1)
686 686 if not force:
687 687 self.check_localchanges(repo)
688 688
689 689 self.applied_dirty = 1;
690 690 start = self.series_end()
691 691 if start > 0:
692 692 self.check_toppatch(repo)
693 693 if not patch:
694 694 patch = self.series[start]
695 695 end = start + 1
696 696 else:
697 697 end = self.series.index(patch, start) + 1
698 698 s = self.series[start:end]
699 699 if mergeq:
700 700 ret = self.mergepatch(repo, mergeq, s, wlock)
701 701 else:
702 702 ret = self.apply(repo, s, list, wlock=wlock)
703 703 top = self.applied[-1].split(':')[1]
704 704 if ret[0]:
705 705 self.ui.write("Errors during apply, please fix and refresh %s\n" %
706 706 top)
707 707 else:
708 708 self.ui.write("Now at: %s\n" % top)
709 709 return ret[0]
710 710
711 def pop(self, repo, patch=None, force=False, update=True, wlock=None):
711 def pop(self, repo, patch=None, force=False, update=True, all=False,
712 wlock=None):
712 713 def getfile(f, rev):
713 714 t = repo.file(f).read(rev)
714 715 try:
715 716 repo.wfile(f, "w").write(t)
716 717 except IOError:
717 718 try:
718 719 os.makedirs(os.path.dirname(repo.wjoin(f)))
719 720 except OSError, err:
720 721 if err.errno != errno.EEXIST: raise
721 722 repo.wfile(f, "w").write(t)
722 723
723 724 if not wlock:
724 725 wlock = repo.wlock()
725 726 if patch:
726 727 # index, rev, patch
727 728 info = self.isapplied(patch)
728 729 if not info:
729 730 patch = self.lookup(patch)
730 731 info = self.isapplied(patch)
731 732 if not info:
732 733 self.ui.warn("patch %s is not applied\n" % patch)
733 734 sys.exit(1)
734 735 if len(self.applied) == 0:
735 736 self.ui.warn("No patches applied\n")
736 737 sys.exit(1)
737 738
738 739 if not update:
739 740 parents = repo.dirstate.parents()
740 741 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
741 742 for p in parents:
742 743 if p in rr:
743 744 self.ui.warn("qpop: forcing dirstate update\n")
744 745 update = True
745 746
746 747 if not force and update:
747 748 self.check_localchanges(repo)
748 749
749 750 self.applied_dirty = 1;
750 751 end = len(self.applied)
751 752 if not patch:
752 info = [len(self.applied) - 1] + self.applied[-1].split(':')
753 if all:
754 popi = 0
755 else:
756 popi = len(self.applied) - 1
757 else:
758 popi = info[0] + 1
759 if popi >= end:
760 self.ui.warn("qpop: %s is already at the top\n" % patch)
761 return
762 info = [ popi ] + self.applied[popi].split(':')
763
753 764 start = info[0]
754 765 rev = revlog.bin(info[1])
755 766
756 767 # we know there are no local changes, so we can make a simplified
757 768 # form of hg.update.
758 769 if update:
759 770 top = self.check_toppatch(repo)
760 771 qp = self.qparents(repo, rev)
761 772 changes = repo.changelog.read(qp)
762 773 mf1 = repo.manifest.readflags(changes[0])
763 774 mmap = repo.manifest.read(changes[0])
764 775 (c, a, r, d, u) = repo.changes(qp, top)
765 776 if d:
766 777 raise util.Abort("deletions found between repo revs")
767 778 for f in c:
768 779 getfile(f, mmap[f])
769 780 for f in r:
770 781 getfile(f, mmap[f])
771 782 util.set_exec(repo.wjoin(f), mf1[f])
772 783 repo.dirstate.update(c + r, 'n')
773 784 for f in a:
774 785 try: os.unlink(repo.wjoin(f))
775 786 except: raise
776 787 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
777 788 except: pass
778 789 if a:
779 790 repo.dirstate.forget(a)
780 791 repo.dirstate.setparents(qp, revlog.nullid)
781 792 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
782 793 del self.applied[start:end]
783 794 if len(self.applied):
784 795 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
785 796 else:
786 797 self.ui.write("Patch queue now empty\n")
787 798
788 799 def diff(self, repo, files):
789 800 top = self.check_toppatch(repo)
790 801 if not top:
791 802 self.ui.write("No patches applied\n")
792 803 return
793 804 qp = self.qparents(repo, top)
794 805 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
795 806
796 807 def refresh(self, repo, msg=None, short=False):
797 808 if len(self.applied) == 0:
798 809 self.ui.write("No patches applied\n")
799 810 return
800 811 wlock = repo.wlock()
801 812 self.check_toppatch(repo)
802 813 qp = self.qparents(repo)
803 814 (top, patch) = self.applied[-1].split(':')
804 815 top = revlog.bin(top)
805 816 cparents = repo.changelog.parents(top)
806 817 patchparent = self.qparents(repo, top)
807 818 message, comments, user, date, patchfound = self.readheaders(patch)
808 819
809 820 patchf = self.opener(patch, "w")
810 821 if comments:
811 822 comments = "\n".join(comments) + '\n\n'
812 823 patchf.write(comments)
813 824
814 825 tip = repo.changelog.tip()
815 826 if top == tip:
816 827 # if the top of our patch queue is also the tip, there is an
817 828 # optimization here. We update the dirstate in place and strip
818 829 # off the tip commit. Then just commit the current directory
819 830 # tree. We can also send repo.commit the list of files
820 831 # changed to speed up the diff
821 832 #
822 833 # in short mode, we only diff the files included in the
823 834 # patch already
824 835 #
825 836 # this should really read:
826 837 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
827 838 # but we do it backwards to take advantage of manifest/chlog
828 839 # caching against the next repo.changes call
829 840 #
830 841 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
831 842 if short:
832 843 filelist = cc + aa + dd
833 844 else:
834 845 filelist = None
835 846 (c, a, r, d, u) = repo.changes(None, None, filelist)
836 847
837 848 # we might end up with files that were added between tip and
838 849 # the dirstate parent, but then changed in the local dirstate.
839 850 # in this case, we want them to only show up in the added section
840 851 for x in c:
841 852 if x not in aa:
842 853 cc.append(x)
843 854 # we might end up with files added by the local dirstate that
844 855 # were deleted by the patch. In this case, they should only
845 856 # show up in the changed section.
846 857 for x in a:
847 858 if x in dd:
848 859 del dd[dd.index(x)]
849 860 cc.append(x)
850 861 else:
851 862 aa.append(x)
852 863 # make sure any files deleted in the local dirstate
853 864 # are not in the add or change column of the patch
854 865 forget = []
855 866 for x in d + r:
856 867 if x in aa:
857 868 del aa[aa.index(x)]
858 869 forget.append(x)
859 870 continue
860 871 elif x in cc:
861 872 del cc[cc.index(x)]
862 873 dd.append(x)
863 874
864 875 c = list(util.unique(cc))
865 876 r = list(util.unique(dd))
866 877 a = list(util.unique(aa))
867 878 filelist = list(util.unique(c + r + a ))
868 879 commands.dodiff(patchf, self.ui, repo, patchparent, None,
869 880 filelist, changes=(c, a, r, [], u))
870 881 patchf.close()
871 882
872 883 changes = repo.changelog.read(tip)
873 884 repo.dirstate.setparents(*cparents)
874 885 repo.dirstate.update(a, 'a')
875 886 repo.dirstate.update(r, 'r')
876 887 repo.dirstate.update(c, 'n')
877 888 repo.dirstate.forget(forget)
878 889
879 890 if not msg:
880 891 if not message:
881 892 message = "patch queue: %s\n" % patch
882 893 else:
883 894 message = "\n".join(message)
884 895 else:
885 896 message = msg
886 897
887 898 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
888 899 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
889 900 self.applied[-1] = revlog.hex(n) + ':' + patch
890 901 self.applied_dirty = 1
891 902 else:
892 903 commands.dodiff(patchf, self.ui, repo, patchparent, None)
893 904 patchf.close()
894 905 self.pop(repo, force=True, wlock=wlock)
895 906 self.push(repo, force=True, wlock=wlock)
896 907
897 908 def init(self, repo, create=False):
898 909 if os.path.isdir(self.path):
899 910 raise util.Abort("patch queue directory already exists")
900 911 os.mkdir(self.path)
901 912 if create:
902 913 return self.qrepo(create=True)
903 914
904 915 def unapplied(self, repo, patch=None):
905 916 if patch and patch not in self.series:
906 917 self.ui.warn("%s not in the series file\n" % patch)
907 918 sys.exit(1)
908 919 if not patch:
909 920 start = self.series_end()
910 921 else:
911 922 start = self.series.index(patch) + 1
912 923 for p in self.series[start:]:
913 924 if self.ui.verbose:
914 925 self.ui.write("%d " % self.series.index(p))
915 926 self.ui.write("%s\n" % p)
916 927
917 928 def qseries(self, repo, missing=None):
918 929 start = self.series_end()
919 930 if not missing:
920 931 for p in self.series[:start]:
921 932 if self.ui.verbose:
922 933 self.ui.write("%d A " % self.series.index(p))
923 934 self.ui.write("%s\n" % p)
924 935 for p in self.series[start:]:
925 936 if self.ui.verbose:
926 937 self.ui.write("%d U " % self.series.index(p))
927 938 self.ui.write("%s\n" % p)
928 939 else:
929 940 list = []
930 941 for root, dirs, files in os.walk(self.path):
931 942 d = root[len(self.path) + 1:]
932 943 for f in files:
933 944 fl = os.path.join(d, f)
934 945 if (fl not in self.series and
935 946 fl not in (self.status_path, self.series_path)
936 947 and not fl.startswith('.')):
937 948 list.append(fl)
938 949 list.sort()
939 950 if list:
940 951 for x in list:
941 952 if self.ui.verbose:
942 953 self.ui.write("D ")
943 954 self.ui.write("%s\n" % x)
944 955
945 956 def issaveline(self, l):
946 957 name = l.split(':')[1]
947 958 if name == '.hg.patches.save.line':
948 959 return True
949 960
950 961 def qrepo(self, create=False):
951 962 if create or os.path.isdir(os.path.join(self.path, ".hg")):
952 963 return hg.repository(self.ui, path=self.path, create=create)
953 964
954 965 def restore(self, repo, rev, delete=None, qupdate=None):
955 966 c = repo.changelog.read(rev)
956 967 desc = c[4].strip()
957 968 lines = desc.splitlines()
958 969 i = 0
959 970 datastart = None
960 971 series = []
961 972 applied = []
962 973 qpp = None
963 974 for i in xrange(0, len(lines)):
964 975 if lines[i] == 'Patch Data:':
965 976 datastart = i + 1
966 977 elif lines[i].startswith('Dirstate:'):
967 978 l = lines[i].rstrip()
968 979 l = l[10:].split(' ')
969 980 qpp = [ hg.bin(x) for x in l ]
970 981 elif datastart != None:
971 982 l = lines[i].rstrip()
972 983 index = l.index(':')
973 984 id = l[:index]
974 985 file = l[index + 1:]
975 986 if id:
976 987 applied.append(l)
977 988 series.append(file)
978 989 if datastart == None:
979 990 self.ui.warn("No saved patch data found\n")
980 991 return 1
981 992 self.ui.warn("restoring status: %s\n" % lines[0])
982 993 self.full_series = series
983 994 self.applied = applied
984 995 self.read_series(self.full_series)
985 996 self.series_dirty = 1
986 997 self.applied_dirty = 1
987 998 heads = repo.changelog.heads()
988 999 if delete:
989 1000 if rev not in heads:
990 1001 self.ui.warn("save entry has children, leaving it alone\n")
991 1002 else:
992 1003 self.ui.warn("removing save entry %s\n" % hg.short(rev))
993 1004 pp = repo.dirstate.parents()
994 1005 if rev in pp:
995 1006 update = True
996 1007 else:
997 1008 update = False
998 1009 self.strip(repo, rev, update=update, backup='strip')
999 1010 if qpp:
1000 1011 self.ui.warn("saved queue repository parents: %s %s\n" %
1001 1012 (hg.short(qpp[0]), hg.short(qpp[1])))
1002 1013 if qupdate:
1003 1014 print "queue directory updating"
1004 1015 r = self.qrepo()
1005 1016 if not r:
1006 1017 self.ui.warn("Unable to load queue repository\n")
1007 1018 return 1
1008 1019 r.update(qpp[0], allow=False, force=True)
1009 1020
1010 1021 def save(self, repo, msg=None):
1011 1022 if len(self.applied) == 0:
1012 1023 self.ui.warn("save: no patches applied, exiting\n")
1013 1024 return 1
1014 1025 if self.issaveline(self.applied[-1]):
1015 1026 self.ui.warn("status is already saved\n")
1016 1027 return 1
1017 1028
1018 1029 ar = [ ':' + x for x in self.full_series ]
1019 1030 if not msg:
1020 1031 msg = "hg patches saved state"
1021 1032 else:
1022 1033 msg = "hg patches: " + msg.rstrip('\r\n')
1023 1034 r = self.qrepo()
1024 1035 if r:
1025 1036 pp = r.dirstate.parents()
1026 1037 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1027 1038 msg += "\n\nPatch Data:\n"
1028 1039 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1029 1040 + '\n' or "")
1030 1041 n = repo.commit(None, text, user=None, force=1)
1031 1042 if not n:
1032 1043 self.ui.warn("repo commit failed\n")
1033 1044 return 1
1034 1045 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1035 1046 self.applied_dirty = 1
1036 1047
1037 1048 def series_end(self):
1038 1049 end = 0
1039 1050 if len(self.applied) > 0:
1040 1051 (top, p) = self.applied[-1].split(':')
1041 1052 try:
1042 1053 end = self.series.index(p)
1043 1054 except ValueError:
1044 1055 return 0
1045 1056 return end + 1
1046 1057 return end
1047 1058
1048 1059 def qapplied(self, repo, patch=None):
1049 1060 if patch and patch not in self.series:
1050 1061 self.ui.warn("%s not in the series file\n" % patch)
1051 1062 sys.exit(1)
1052 1063 if not patch:
1053 1064 end = len(self.applied)
1054 1065 else:
1055 1066 end = self.series.index(patch) + 1
1056 1067 for x in xrange(end):
1057 1068 p = self.appliedname(x)
1058 1069 self.ui.write("%s\n" % p)
1059 1070
1060 1071 def appliedname(self, index):
1061 1072 p = self.applied[index]
1062 1073 pname = p.split(':')[1]
1063 1074 if not self.ui.verbose:
1064 1075 p = pname
1065 1076 else:
1066 1077 p = str(self.series.index(pname)) + " " + p
1067 1078 return p
1068 1079
1069 1080 def top(self, repo):
1070 1081 if len(self.applied):
1071 1082 p = self.appliedname(-1)
1072 1083 self.ui.write(p + '\n')
1073 1084 else:
1074 1085 self.ui.write("No patches applied\n")
1075 1086
1076 1087 def next(self, repo):
1077 1088 end = self.series_end()
1078 1089 if end == len(self.series):
1079 1090 self.ui.write("All patches applied\n")
1080 1091 else:
1081 1092 p = self.series[end]
1082 1093 if self.ui.verbose:
1083 1094 self.ui.write("%d " % self.series.index(p))
1084 1095 self.ui.write(p + '\n')
1085 1096
1086 1097 def prev(self, repo):
1087 1098 if len(self.applied) > 1:
1088 1099 p = self.appliedname(-2)
1089 1100 self.ui.write(p + '\n')
1090 1101 elif len(self.applied) == 1:
1091 1102 self.ui.write("Only one patch applied\n")
1092 1103 else:
1093 1104 self.ui.write("No patches applied\n")
1094 1105
1095 1106 def qimport(self, repo, files, patch=None, existing=None, force=None):
1096 1107 if len(files) > 1 and patch:
1097 1108 self.ui.warn("-n option not valid when importing multiple files\n")
1098 1109 sys.exit(1)
1099 1110 i = 0
1100 1111 added = []
1101 1112 for filename in files:
1102 1113 if existing:
1103 1114 if not patch:
1104 1115 patch = filename
1105 1116 if not os.path.isfile(os.path.join(self.path, patch)):
1106 1117 self.ui.warn("patch %s does not exist\n" % patch)
1107 1118 sys.exit(1)
1108 1119 else:
1109 1120 try:
1110 1121 text = file(filename).read()
1111 1122 except IOError:
1112 1123 self.ui.warn("Unable to read %s\n" % patch)
1113 1124 sys.exit(1)
1114 1125 if not patch:
1115 1126 patch = os.path.split(filename)[1]
1116 1127 if not force and os.path.isfile(os.path.join(self.path, patch)):
1117 1128 self.ui.warn("patch %s already exists\n" % patch)
1118 1129 sys.exit(1)
1119 1130 patchf = self.opener(patch, "w")
1120 1131 patchf.write(text)
1121 1132 if patch in self.series:
1122 1133 self.ui.warn("patch %s is already in the series file\n" % patch)
1123 1134 sys.exit(1)
1124 1135 index = self.series_end() + i
1125 1136 self.full_series[index:index] = [patch]
1126 1137 self.read_series(self.full_series)
1127 1138 self.ui.warn("adding %s to series file\n" % patch)
1128 1139 i += 1
1129 1140 added.append(patch)
1130 1141 patch = None
1131 1142 self.series_dirty = 1
1132 1143 qrepo = self.qrepo()
1133 1144 if qrepo:
1134 1145 qrepo.add(added)
1135 1146
1136 1147 def delete(ui, repo, patch, **opts):
1137 1148 """remove a patch from the series file"""
1138 1149 q = repomap[repo]
1139 1150 q.delete(repo, patch)
1140 1151 q.save_dirty()
1141 1152 return 0
1142 1153
1143 1154 def applied(ui, repo, patch=None, **opts):
1144 1155 """print the patches already applied"""
1145 1156 repomap[repo].qapplied(repo, patch)
1146 1157 return 0
1147 1158
1148 1159 def unapplied(ui, repo, patch=None, **opts):
1149 1160 """print the patches not yet applied"""
1150 1161 repomap[repo].unapplied(repo, patch)
1151 1162 return 0
1152 1163
1153 1164 def qimport(ui, repo, *filename, **opts):
1154 1165 """import a patch"""
1155 1166 q = repomap[repo]
1156 1167 q.qimport(repo, filename, patch=opts['name'],
1157 1168 existing=opts['existing'], force=opts['force'])
1158 1169 q.save_dirty()
1159 1170 return 0
1160 1171
1161 1172 def init(ui, repo, **opts):
1162 1173 """init a new queue repository"""
1163 1174 q = repomap[repo]
1164 1175 r = q.init(repo, create=opts['create_repo'])
1165 1176 q.save_dirty()
1166 1177 if r:
1167 1178 fp = r.wopener('.hgignore', 'w')
1168 1179 print >> fp, 'syntax: glob'
1169 1180 print >> fp, 'status'
1170 1181 fp.close()
1171 1182 r.wopener('series', 'w').close()
1172 1183 r.add(['.hgignore', 'series'])
1173 1184 return 0
1174 1185
1175 1186 def commit(ui, repo, *pats, **opts):
1176 1187 """commit changes in the queue repository"""
1177 1188 q = repomap[repo]
1178 1189 r = q.qrepo()
1179 1190 if not r: raise util.Abort('no queue repository')
1180 1191 commands.commit(r.ui, r, *pats, **opts)
1181 1192
1182 1193 def series(ui, repo, **opts):
1183 1194 """print the entire series file"""
1184 1195 repomap[repo].qseries(repo, missing=opts['missing'])
1185 1196 return 0
1186 1197
1187 1198 def top(ui, repo, **opts):
1188 1199 """print the name of the current patch"""
1189 1200 repomap[repo].top(repo)
1190 1201 return 0
1191 1202
1192 1203 def next(ui, repo, **opts):
1193 1204 """print the name of the next patch"""
1194 1205 repomap[repo].next(repo)
1195 1206 return 0
1196 1207
1197 1208 def prev(ui, repo, **opts):
1198 1209 """print the name of the previous patch"""
1199 1210 repomap[repo].prev(repo)
1200 1211 return 0
1201 1212
1202 1213 def new(ui, repo, patch, **opts):
1203 1214 """create a new patch"""
1204 1215 q = repomap[repo]
1205 1216 message=commands.logmessage(**opts)
1206 1217 q.new(repo, patch, msg=message, force=opts['force'])
1207 1218 q.save_dirty()
1208 1219 return 0
1209 1220
1210 1221 def refresh(ui, repo, **opts):
1211 1222 """update the current patch"""
1212 1223 q = repomap[repo]
1213 1224 message=commands.logmessage(**opts)
1214 1225 q.refresh(repo, msg=message, short=opts['short'])
1215 1226 q.save_dirty()
1216 1227 return 0
1217 1228
1218 1229 def diff(ui, repo, *files, **opts):
1219 1230 """diff of the current patch"""
1220 1231 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1221 1232 repomap[repo].diff(repo, list(files))
1222 1233 return 0
1223 1234
1224 1235 def lastsavename(path):
1225 1236 (dir, base) = os.path.split(path)
1226 1237 names = os.listdir(dir)
1227 1238 namere = re.compile("%s.([0-9]+)" % base)
1228 1239 max = None
1229 1240 maxname = None
1230 1241 for f in names:
1231 1242 m = namere.match(f)
1232 1243 if m:
1233 1244 index = int(m.group(1))
1234 1245 if max == None or index > max:
1235 1246 max = index
1236 1247 maxname = f
1237 1248 if maxname:
1238 1249 return (os.path.join(dir, maxname), max)
1239 1250 return (None, None)
1240 1251
1241 1252 def savename(path):
1242 1253 (last, index) = lastsavename(path)
1243 1254 if last is None:
1244 1255 index = 0
1245 1256 newpath = path + ".%d" % (index + 1)
1246 1257 return newpath
1247 1258
1248 1259 def push(ui, repo, patch=None, **opts):
1249 1260 """push the next patch onto the stack"""
1250 1261 q = repomap[repo]
1251 1262 mergeq = None
1252 1263
1253 1264 if opts['all']:
1254 1265 patch = q.series[-1]
1255 1266 if opts['merge']:
1256 1267 if opts['name']:
1257 1268 newpath = opts['name']
1258 1269 else:
1259 1270 newpath, i = lastsavename(q.path)
1260 1271 if not newpath:
1261 1272 ui.warn("no saved queues found, please use -n\n")
1262 1273 return 1
1263 1274 mergeq = queue(ui, repo.join(""), newpath)
1264 1275 ui.warn("merging with queue at: %s\n" % mergeq.path)
1265 1276 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1266 1277 mergeq=mergeq)
1267 1278 q.save_dirty()
1268 1279 return ret
1269 1280
1270 1281 def pop(ui, repo, patch=None, **opts):
1271 1282 """pop the current patch off the stack"""
1272 1283 localupdate = True
1273 1284 if opts['name']:
1274 1285 q = queue(ui, repo.join(""), repo.join(opts['name']))
1275 1286 ui.warn('using patch queue: %s\n' % q.path)
1276 1287 localupdate = False
1277 1288 else:
1278 1289 q = repomap[repo]
1279 if opts['all'] and len(q.applied) > 0:
1280 patch = q.applied[0].split(':')[1]
1281 q.pop(repo, patch, force=opts['force'], update=localupdate)
1290 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1282 1291 q.save_dirty()
1283 1292 return 0
1284 1293
1285 1294 def restore(ui, repo, rev, **opts):
1286 1295 """restore the queue state saved by a rev"""
1287 1296 rev = repo.lookup(rev)
1288 1297 q = repomap[repo]
1289 1298 q.restore(repo, rev, delete=opts['delete'],
1290 1299 qupdate=opts['update'])
1291 1300 q.save_dirty()
1292 1301 return 0
1293 1302
1294 1303 def save(ui, repo, **opts):
1295 1304 """save current queue state"""
1296 1305 q = repomap[repo]
1297 1306 message=commands.logmessage(**opts)
1298 1307 ret = q.save(repo, msg=message)
1299 1308 if ret:
1300 1309 return ret
1301 1310 q.save_dirty()
1302 1311 if opts['copy']:
1303 1312 path = q.path
1304 1313 if opts['name']:
1305 1314 newpath = os.path.join(q.basepath, opts['name'])
1306 1315 if os.path.exists(newpath):
1307 1316 if not os.path.isdir(newpath):
1308 1317 ui.warn("destination %s exists and is not a directory\n" %
1309 1318 newpath)
1310 1319 sys.exit(1)
1311 1320 if not opts['force']:
1312 1321 ui.warn("destination %s exists, use -f to force\n" %
1313 1322 newpath)
1314 1323 sys.exit(1)
1315 1324 else:
1316 1325 newpath = savename(path)
1317 1326 ui.warn("copy %s to %s\n" % (path, newpath))
1318 1327 util.copyfiles(path, newpath)
1319 1328 if opts['empty']:
1320 1329 try:
1321 1330 os.unlink(os.path.join(q.path, q.status_path))
1322 1331 except:
1323 1332 pass
1324 1333 return 0
1325 1334
1326 1335 def strip(ui, repo, rev, **opts):
1327 1336 """strip a revision and all later revs on the same branch"""
1328 1337 rev = repo.lookup(rev)
1329 1338 backup = 'all'
1330 1339 if opts['backup']:
1331 1340 backup = 'strip'
1332 1341 elif opts['nobackup']:
1333 1342 backup = 'none'
1334 1343 repomap[repo].strip(repo, rev, backup=backup)
1335 1344 return 0
1336 1345
1337 1346 def version(ui, q=None):
1338 1347 """print the version number"""
1339 1348 ui.write("mq version %s\n" % versionstr)
1340 1349 return 0
1341 1350
1342 1351 def reposetup(ui, repo):
1343 1352 repomap[repo] = queue(ui, repo.join(""))
1344 1353 oldtags = repo.tags
1345 1354
1346 1355 def qtags():
1347 1356 if repo.tagscache:
1348 1357 return repo.tagscache
1349 1358
1350 1359 tagscache = oldtags()
1351 1360
1352 1361 q = repomap[repo]
1353 1362 if len(q.applied) == 0:
1354 1363 return tagscache
1355 1364
1356 1365 mqtags = [patch.split(':') for patch in q.applied]
1357 1366 mqtags.append((mqtags[-1][0], 'qtip'))
1358 1367 mqtags.append((mqtags[0][0], 'qbase'))
1359 1368 for patch in mqtags:
1360 1369 if patch[1] in tagscache:
1361 1370 repo.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1362 1371 else:
1363 1372 tagscache[patch[1]] = revlog.bin(patch[0])
1364 1373
1365 1374 return tagscache
1366 1375
1367 1376 repo.tags = qtags
1368 1377
1369 1378 cmdtable = {
1370 1379 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1371 1380 "qcommit|qci":
1372 1381 (commit,
1373 1382 commands.table["^commit|ci"][1],
1374 1383 'hg qcommit [OPTION]... [FILE]...'),
1375 1384 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1376 1385 "qdelete": (delete, [], 'hg qdelete PATCH'),
1377 1386 "^qimport":
1378 1387 (qimport,
1379 1388 [('e', 'existing', None, 'import file in patch dir'),
1380 1389 ('n', 'name', '', 'patch file name'),
1381 1390 ('f', 'force', None, 'overwrite existing files')],
1382 1391 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1383 1392 "^qinit":
1384 1393 (init,
1385 1394 [('c', 'create-repo', None, 'create queue repository')],
1386 1395 'hg qinit [-c]'),
1387 1396 "qnew":
1388 1397 (new,
1389 1398 [('m', 'message', '', _('use <text> as commit message')),
1390 1399 ('l', 'logfile', '', _('read the commit message from <file>')),
1391 1400 ('f', 'force', None, 'force')],
1392 1401 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1393 1402 "qnext": (next, [], 'hg qnext'),
1394 1403 "qprev": (prev, [], 'hg qprev'),
1395 1404 "^qpop":
1396 1405 (pop,
1397 1406 [('a', 'all', None, 'pop all patches'),
1398 1407 ('n', 'name', '', 'queue name to pop'),
1399 1408 ('f', 'force', None, 'forget any local changes')],
1400 1409 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1401 1410 "^qpush":
1402 1411 (push,
1403 1412 [('f', 'force', None, 'apply if the patch has rejects'),
1404 1413 ('l', 'list', None, 'list patch name in commit text'),
1405 1414 ('a', 'all', None, 'apply all patches'),
1406 1415 ('m', 'merge', None, 'merge from another queue'),
1407 1416 ('n', 'name', '', 'merge queue name')],
1408 1417 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1409 1418 "^qrefresh":
1410 1419 (refresh,
1411 1420 [('m', 'message', '', _('change commit message with <text>')),
1412 1421 ('l', 'logfile', '', _('change commit message with <file> content')),
1413 1422 ('s', 'short', None, 'short refresh')],
1414 1423 'hg qrefresh [-m TEXT] [-l FILE] [-s]'),
1415 1424 "qrestore":
1416 1425 (restore,
1417 1426 [('d', 'delete', None, 'delete save entry'),
1418 1427 ('u', 'update', None, 'update queue working dir')],
1419 1428 'hg qrestore [-d] [-u] REV'),
1420 1429 "qsave":
1421 1430 (save,
1422 1431 [('m', 'message', '', _('use <text> as commit message')),
1423 1432 ('l', 'logfile', '', _('read the commit message from <file>')),
1424 1433 ('c', 'copy', None, 'copy patch directory'),
1425 1434 ('n', 'name', '', 'copy directory name'),
1426 1435 ('e', 'empty', None, 'clear queue status file'),
1427 1436 ('f', 'force', None, 'force copy')],
1428 1437 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1429 1438 "qseries":
1430 1439 (series,
1431 1440 [('m', 'missing', None, 'print patches not in series')],
1432 1441 'hg qseries [-m]'),
1433 1442 "^strip":
1434 1443 (strip,
1435 1444 [('f', 'force', None, 'force multi-head removal'),
1436 1445 ('b', 'backup', None, 'bundle unrelated changesets'),
1437 1446 ('n', 'nobackup', None, 'no backups')],
1438 1447 'hg strip [-f] [-b] [-n] REV'),
1439 1448 "qtop": (top, [], 'hg qtop'),
1440 1449 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1441 1450 "qversion": (version, [], 'hg qversion')
1442 1451 }
1443 1452
General Comments 0
You need to be logged in to leave comments. Login now