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