##// END OF EJS Templates
mq: fix qnew and qimport to deal with series file comments...
Chris Mason -
r2698:c1123e83 default
parent child Browse files
Show More
@@ -1,1452 +1,1461 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.demandload import *
33 33 demandload(globals(), "os sys re struct traceback errno bz2")
34 34 from mercurial.i18n import gettext as _
35 35 from mercurial import ui, hg, revlog, commands, util
36 36
37 37 versionstr = "0.45"
38 38
39 39 repomap = {}
40 40
41 41 commands.norepo += " qversion"
42 42 class queue:
43 43 def __init__(self, ui, path, patchdir=None):
44 44 self.basepath = path
45 45 if patchdir:
46 46 self.path = patchdir
47 47 else:
48 48 self.path = os.path.join(path, "patches")
49 49 self.opener = util.opener(self.path)
50 50 self.ui = ui
51 51 self.applied = []
52 52 self.full_series = []
53 53 self.applied_dirty = 0
54 54 self.series_dirty = 0
55 55 self.series_path = "series"
56 56 self.status_path = "status"
57 57
58 58 if os.path.exists(os.path.join(self.path, self.series_path)):
59 59 self.full_series = self.opener(self.series_path).read().splitlines()
60 60 self.read_series(self.full_series)
61 61
62 62 if os.path.exists(os.path.join(self.path, self.status_path)):
63 63 self.applied = self.opener(self.status_path).read().splitlines()
64 64
65 65 def find_series(self, patch):
66 66 pre = re.compile("(\s*)([^#]+)")
67 67 index = 0
68 68 for l in self.full_series:
69 69 m = pre.match(l)
70 70 if m:
71 71 s = m.group(2)
72 72 s = s.rstrip()
73 73 if s == patch:
74 74 return index
75 75 index += 1
76 76 return None
77 77
78 78 def read_series(self, list):
79 79 def matcher(list):
80 80 pre = re.compile("(\s*)([^#]+)")
81 81 for l in list:
82 82 m = pre.match(l)
83 83 if m:
84 84 s = m.group(2)
85 85 s = s.rstrip()
86 86 if len(s) > 0:
87 87 yield s
88 88 self.series = []
89 89 self.series = [ x for x in matcher(list) ]
90 90
91 91 def save_dirty(self):
92 92 if self.applied_dirty:
93 93 if len(self.applied) > 0:
94 94 nl = "\n"
95 95 else:
96 96 nl = ""
97 97 f = self.opener(self.status_path, "w")
98 98 f.write("\n".join(self.applied) + nl)
99 99 if self.series_dirty:
100 100 if len(self.full_series) > 0:
101 101 nl = "\n"
102 102 else:
103 103 nl = ""
104 104 f = self.opener(self.series_path, "w")
105 105 f.write("\n".join(self.full_series) + nl)
106 106
107 107 def readheaders(self, patch):
108 108 def eatdiff(lines):
109 109 while lines:
110 110 l = lines[-1]
111 111 if (l.startswith("diff -") or
112 112 l.startswith("Index:") or
113 113 l.startswith("===========")):
114 114 del lines[-1]
115 115 else:
116 116 break
117 117 def eatempty(lines):
118 118 while lines:
119 119 l = lines[-1]
120 120 if re.match('\s*$', l):
121 121 del lines[-1]
122 122 else:
123 123 break
124 124
125 125 pf = os.path.join(self.path, patch)
126 126 message = []
127 127 comments = []
128 128 user = None
129 129 date = None
130 130 format = None
131 131 subject = None
132 132 diffstart = 0
133 133
134 134 for line in file(pf):
135 135 line = line.rstrip()
136 136 if diffstart:
137 137 if line.startswith('+++ '):
138 138 diffstart = 2
139 139 break
140 140 if line.startswith("--- "):
141 141 diffstart = 1
142 142 continue
143 143 elif format == "hgpatch":
144 144 # parse values when importing the result of an hg export
145 145 if line.startswith("# User "):
146 146 user = line[7:]
147 147 elif line.startswith("# Date "):
148 148 date = line[7:]
149 149 elif not line.startswith("# ") and line:
150 150 message.append(line)
151 151 format = None
152 152 elif line == '# HG changeset patch':
153 153 format = "hgpatch"
154 154 elif (format != "tagdone" and (line.startswith("Subject: ") or
155 155 line.startswith("subject: "))):
156 156 subject = line[9:]
157 157 format = "tag"
158 158 elif (format != "tagdone" and (line.startswith("From: ") or
159 159 line.startswith("from: "))):
160 160 user = line[6:]
161 161 format = "tag"
162 162 elif format == "tag" and line == "":
163 163 # when looking for tags (subject: from: etc) they
164 164 # end once you find a blank line in the source
165 165 format = "tagdone"
166 166 elif message or line:
167 167 message.append(line)
168 168 comments.append(line)
169 169
170 170 eatdiff(message)
171 171 eatdiff(comments)
172 172 eatempty(message)
173 173 eatempty(comments)
174 174
175 175 # make sure message isn't empty
176 176 if format and format.startswith("tag") and subject:
177 177 message.insert(0, "")
178 178 message.insert(0, subject)
179 179 return (message, comments, user, date, diffstart > 1)
180 180
181 181 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
182 182 # first try just applying the patch
183 183 (err, n) = self.apply(repo, [ patch ], update_status=False,
184 184 strict=True, merge=rev, wlock=wlock)
185 185
186 186 if err == 0:
187 187 return (err, n)
188 188
189 189 if n is None:
190 190 self.ui.warn("apply failed for patch %s\n" % patch)
191 191 sys.exit(1)
192 192
193 193 self.ui.warn("patch didn't work out, merging %s\n" % patch)
194 194
195 195 # apply failed, strip away that rev and merge.
196 196 repo.update(head, allow=False, force=True, wlock=wlock)
197 197 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
198 198
199 199 c = repo.changelog.read(rev)
200 200 ret = repo.update(rev, allow=True, wlock=wlock)
201 201 if ret:
202 202 self.ui.warn("update returned %d\n" % ret)
203 203 sys.exit(1)
204 204 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
205 205 if n == None:
206 206 self.ui.warn("repo commit failed\n")
207 207 sys.exit(1)
208 208 try:
209 209 message, comments, user, date, patchfound = mergeq.readheaders(patch)
210 210 except:
211 211 self.ui.warn("Unable to read %s\n" % patch)
212 212 sys.exit(1)
213 213
214 214 patchf = self.opener(patch, "w")
215 215 if comments:
216 216 comments = "\n".join(comments) + '\n\n'
217 217 patchf.write(comments)
218 218 commands.dodiff(patchf, self.ui, repo, head, n)
219 219 patchf.close()
220 220 return (0, n)
221 221
222 222 def qparents(self, repo, rev=None):
223 223 if rev is None:
224 224 (p1, p2) = repo.dirstate.parents()
225 225 if p2 == revlog.nullid:
226 226 return p1
227 227 if len(self.applied) == 0:
228 228 return None
229 229 (top, patch) = self.applied[-1].split(':')
230 230 top = revlog.bin(top)
231 231 return top
232 232 pp = repo.changelog.parents(rev)
233 233 if pp[1] != revlog.nullid:
234 234 arevs = [ x.split(':')[0] for x in self.applied ]
235 235 p0 = revlog.hex(pp[0])
236 236 p1 = revlog.hex(pp[1])
237 237 if p0 in arevs:
238 238 return pp[0]
239 239 if p1 in arevs:
240 240 return pp[1]
241 241 return pp[0]
242 242
243 243 def mergepatch(self, repo, mergeq, series, wlock):
244 244 if len(self.applied) == 0:
245 245 # each of the patches merged in will have two parents. This
246 246 # can confuse the qrefresh, qdiff, and strip code because it
247 247 # needs to know which parent is actually in the patch queue.
248 248 # so, we insert a merge marker with only one parent. This way
249 249 # the first patch in the queue is never a merge patch
250 250 #
251 251 pname = ".hg.patches.merge.marker"
252 252 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
253 253 wlock=wlock)
254 254 self.applied.append(revlog.hex(n) + ":" + pname)
255 255 self.applied_dirty = 1
256 256
257 257 head = self.qparents(repo)
258 258
259 259 for patch in series:
260 260 patch = mergeq.lookup(patch, 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 insert = self.series_end()
421 insert = self.full_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 711 def pop(self, repo, patch=None, force=False, update=True, all=False,
712 712 wlock=None):
713 713 def getfile(f, rev):
714 714 t = repo.file(f).read(rev)
715 715 try:
716 716 repo.wfile(f, "w").write(t)
717 717 except IOError:
718 718 try:
719 719 os.makedirs(os.path.dirname(repo.wjoin(f)))
720 720 except OSError, err:
721 721 if err.errno != errno.EEXIST: raise
722 722 repo.wfile(f, "w").write(t)
723 723
724 724 if not wlock:
725 725 wlock = repo.wlock()
726 726 if patch:
727 727 # index, rev, patch
728 728 info = self.isapplied(patch)
729 729 if not info:
730 730 patch = self.lookup(patch)
731 731 info = self.isapplied(patch)
732 732 if not info:
733 733 self.ui.warn("patch %s is not applied\n" % patch)
734 734 sys.exit(1)
735 735 if len(self.applied) == 0:
736 736 self.ui.warn("No patches applied\n")
737 737 sys.exit(1)
738 738
739 739 if not update:
740 740 parents = repo.dirstate.parents()
741 741 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
742 742 for p in parents:
743 743 if p in rr:
744 744 self.ui.warn("qpop: forcing dirstate update\n")
745 745 update = True
746 746
747 747 if not force and update:
748 748 self.check_localchanges(repo)
749 749
750 750 self.applied_dirty = 1;
751 751 end = len(self.applied)
752 752 if not patch:
753 753 if all:
754 754 popi = 0
755 755 else:
756 756 popi = len(self.applied) - 1
757 757 else:
758 758 popi = info[0] + 1
759 759 if popi >= end:
760 760 self.ui.warn("qpop: %s is already at the top\n" % patch)
761 761 return
762 762 info = [ popi ] + self.applied[popi].split(':')
763 763
764 764 start = info[0]
765 765 rev = revlog.bin(info[1])
766 766
767 767 # we know there are no local changes, so we can make a simplified
768 768 # form of hg.update.
769 769 if update:
770 770 top = self.check_toppatch(repo)
771 771 qp = self.qparents(repo, rev)
772 772 changes = repo.changelog.read(qp)
773 773 mf1 = repo.manifest.readflags(changes[0])
774 774 mmap = repo.manifest.read(changes[0])
775 775 (c, a, r, d, u) = repo.changes(qp, top)
776 776 if d:
777 777 raise util.Abort("deletions found between repo revs")
778 778 for f in c:
779 779 getfile(f, mmap[f])
780 780 for f in r:
781 781 getfile(f, mmap[f])
782 782 util.set_exec(repo.wjoin(f), mf1[f])
783 783 repo.dirstate.update(c + r, 'n')
784 784 for f in a:
785 785 try: os.unlink(repo.wjoin(f))
786 786 except: raise
787 787 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
788 788 except: pass
789 789 if a:
790 790 repo.dirstate.forget(a)
791 791 repo.dirstate.setparents(qp, revlog.nullid)
792 792 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
793 793 del self.applied[start:end]
794 794 if len(self.applied):
795 795 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
796 796 else:
797 797 self.ui.write("Patch queue now empty\n")
798 798
799 799 def diff(self, repo, files):
800 800 top = self.check_toppatch(repo)
801 801 if not top:
802 802 self.ui.write("No patches applied\n")
803 803 return
804 804 qp = self.qparents(repo, top)
805 805 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
806 806
807 807 def refresh(self, repo, msg=None, short=False):
808 808 if len(self.applied) == 0:
809 809 self.ui.write("No patches applied\n")
810 810 return
811 811 wlock = repo.wlock()
812 812 self.check_toppatch(repo)
813 813 qp = self.qparents(repo)
814 814 (top, patch) = self.applied[-1].split(':')
815 815 top = revlog.bin(top)
816 816 cparents = repo.changelog.parents(top)
817 817 patchparent = self.qparents(repo, top)
818 818 message, comments, user, date, patchfound = self.readheaders(patch)
819 819
820 820 patchf = self.opener(patch, "w")
821 821 if comments:
822 822 comments = "\n".join(comments) + '\n\n'
823 823 patchf.write(comments)
824 824
825 825 tip = repo.changelog.tip()
826 826 if top == tip:
827 827 # if the top of our patch queue is also the tip, there is an
828 828 # optimization here. We update the dirstate in place and strip
829 829 # off the tip commit. Then just commit the current directory
830 830 # tree. We can also send repo.commit the list of files
831 831 # changed to speed up the diff
832 832 #
833 833 # in short mode, we only diff the files included in the
834 834 # patch already
835 835 #
836 836 # this should really read:
837 837 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
838 838 # but we do it backwards to take advantage of manifest/chlog
839 839 # caching against the next repo.changes call
840 840 #
841 841 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
842 842 if short:
843 843 filelist = cc + aa + dd
844 844 else:
845 845 filelist = None
846 846 (c, a, r, d, u) = repo.changes(None, None, filelist)
847 847
848 848 # we might end up with files that were added between tip and
849 849 # the dirstate parent, but then changed in the local dirstate.
850 850 # in this case, we want them to only show up in the added section
851 851 for x in c:
852 852 if x not in aa:
853 853 cc.append(x)
854 854 # we might end up with files added by the local dirstate that
855 855 # were deleted by the patch. In this case, they should only
856 856 # show up in the changed section.
857 857 for x in a:
858 858 if x in dd:
859 859 del dd[dd.index(x)]
860 860 cc.append(x)
861 861 else:
862 862 aa.append(x)
863 863 # make sure any files deleted in the local dirstate
864 864 # are not in the add or change column of the patch
865 865 forget = []
866 866 for x in d + r:
867 867 if x in aa:
868 868 del aa[aa.index(x)]
869 869 forget.append(x)
870 870 continue
871 871 elif x in cc:
872 872 del cc[cc.index(x)]
873 873 dd.append(x)
874 874
875 875 c = list(util.unique(cc))
876 876 r = list(util.unique(dd))
877 877 a = list(util.unique(aa))
878 878 filelist = list(util.unique(c + r + a ))
879 879 commands.dodiff(patchf, self.ui, repo, patchparent, None,
880 880 filelist, changes=(c, a, r, [], u))
881 881 patchf.close()
882 882
883 883 changes = repo.changelog.read(tip)
884 884 repo.dirstate.setparents(*cparents)
885 885 repo.dirstate.update(a, 'a')
886 886 repo.dirstate.update(r, 'r')
887 887 repo.dirstate.update(c, 'n')
888 888 repo.dirstate.forget(forget)
889 889
890 890 if not msg:
891 891 if not message:
892 892 message = "patch queue: %s\n" % patch
893 893 else:
894 894 message = "\n".join(message)
895 895 else:
896 896 message = msg
897 897
898 898 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
899 899 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
900 900 self.applied[-1] = revlog.hex(n) + ':' + patch
901 901 self.applied_dirty = 1
902 902 else:
903 903 commands.dodiff(patchf, self.ui, repo, patchparent, None)
904 904 patchf.close()
905 905 self.pop(repo, force=True, wlock=wlock)
906 906 self.push(repo, force=True, wlock=wlock)
907 907
908 908 def init(self, repo, create=False):
909 909 if os.path.isdir(self.path):
910 910 raise util.Abort("patch queue directory already exists")
911 911 os.mkdir(self.path)
912 912 if create:
913 913 return self.qrepo(create=True)
914 914
915 915 def unapplied(self, repo, patch=None):
916 916 if patch and patch not in self.series:
917 917 self.ui.warn("%s not in the series file\n" % patch)
918 918 sys.exit(1)
919 919 if not patch:
920 920 start = self.series_end()
921 921 else:
922 922 start = self.series.index(patch) + 1
923 923 for p in self.series[start:]:
924 924 if self.ui.verbose:
925 925 self.ui.write("%d " % self.series.index(p))
926 926 self.ui.write("%s\n" % p)
927 927
928 928 def qseries(self, repo, missing=None):
929 929 start = self.series_end()
930 930 if not missing:
931 931 for p in self.series[:start]:
932 932 if self.ui.verbose:
933 933 self.ui.write("%d A " % self.series.index(p))
934 934 self.ui.write("%s\n" % p)
935 935 for p in self.series[start:]:
936 936 if self.ui.verbose:
937 937 self.ui.write("%d U " % self.series.index(p))
938 938 self.ui.write("%s\n" % p)
939 939 else:
940 940 list = []
941 941 for root, dirs, files in os.walk(self.path):
942 942 d = root[len(self.path) + 1:]
943 943 for f in files:
944 944 fl = os.path.join(d, f)
945 945 if (fl not in self.series and
946 946 fl not in (self.status_path, self.series_path)
947 947 and not fl.startswith('.')):
948 948 list.append(fl)
949 949 list.sort()
950 950 if list:
951 951 for x in list:
952 952 if self.ui.verbose:
953 953 self.ui.write("D ")
954 954 self.ui.write("%s\n" % x)
955 955
956 956 def issaveline(self, l):
957 957 name = l.split(':')[1]
958 958 if name == '.hg.patches.save.line':
959 959 return True
960 960
961 961 def qrepo(self, create=False):
962 962 if create or os.path.isdir(os.path.join(self.path, ".hg")):
963 963 return hg.repository(self.ui, path=self.path, create=create)
964 964
965 965 def restore(self, repo, rev, delete=None, qupdate=None):
966 966 c = repo.changelog.read(rev)
967 967 desc = c[4].strip()
968 968 lines = desc.splitlines()
969 969 i = 0
970 970 datastart = None
971 971 series = []
972 972 applied = []
973 973 qpp = None
974 974 for i in xrange(0, len(lines)):
975 975 if lines[i] == 'Patch Data:':
976 976 datastart = i + 1
977 977 elif lines[i].startswith('Dirstate:'):
978 978 l = lines[i].rstrip()
979 979 l = l[10:].split(' ')
980 980 qpp = [ hg.bin(x) for x in l ]
981 981 elif datastart != None:
982 982 l = lines[i].rstrip()
983 983 index = l.index(':')
984 984 id = l[:index]
985 985 file = l[index + 1:]
986 986 if id:
987 987 applied.append(l)
988 988 series.append(file)
989 989 if datastart == None:
990 990 self.ui.warn("No saved patch data found\n")
991 991 return 1
992 992 self.ui.warn("restoring status: %s\n" % lines[0])
993 993 self.full_series = series
994 994 self.applied = applied
995 995 self.read_series(self.full_series)
996 996 self.series_dirty = 1
997 997 self.applied_dirty = 1
998 998 heads = repo.changelog.heads()
999 999 if delete:
1000 1000 if rev not in heads:
1001 1001 self.ui.warn("save entry has children, leaving it alone\n")
1002 1002 else:
1003 1003 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1004 1004 pp = repo.dirstate.parents()
1005 1005 if rev in pp:
1006 1006 update = True
1007 1007 else:
1008 1008 update = False
1009 1009 self.strip(repo, rev, update=update, backup='strip')
1010 1010 if qpp:
1011 1011 self.ui.warn("saved queue repository parents: %s %s\n" %
1012 1012 (hg.short(qpp[0]), hg.short(qpp[1])))
1013 1013 if qupdate:
1014 1014 print "queue directory updating"
1015 1015 r = self.qrepo()
1016 1016 if not r:
1017 1017 self.ui.warn("Unable to load queue repository\n")
1018 1018 return 1
1019 1019 r.update(qpp[0], allow=False, force=True)
1020 1020
1021 1021 def save(self, repo, msg=None):
1022 1022 if len(self.applied) == 0:
1023 1023 self.ui.warn("save: no patches applied, exiting\n")
1024 1024 return 1
1025 1025 if self.issaveline(self.applied[-1]):
1026 1026 self.ui.warn("status is already saved\n")
1027 1027 return 1
1028 1028
1029 1029 ar = [ ':' + x for x in self.full_series ]
1030 1030 if not msg:
1031 1031 msg = "hg patches saved state"
1032 1032 else:
1033 1033 msg = "hg patches: " + msg.rstrip('\r\n')
1034 1034 r = self.qrepo()
1035 1035 if r:
1036 1036 pp = r.dirstate.parents()
1037 1037 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1038 1038 msg += "\n\nPatch Data:\n"
1039 1039 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1040 1040 + '\n' or "")
1041 1041 n = repo.commit(None, text, user=None, force=1)
1042 1042 if not n:
1043 1043 self.ui.warn("repo commit failed\n")
1044 1044 return 1
1045 1045 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1046 1046 self.applied_dirty = 1
1047 1047
1048 def full_series_end(self):
1049 if len(self.applied) > 0:
1050 (top, p) = self.applied[-1].split(':')
1051 end = self.find_series(p)
1052 if end == None:
1053 return len(self.full_series)
1054 return end + 1
1055 return 0
1056
1048 1057 def series_end(self):
1049 1058 end = 0
1050 1059 if len(self.applied) > 0:
1051 1060 (top, p) = self.applied[-1].split(':')
1052 1061 try:
1053 1062 end = self.series.index(p)
1054 1063 except ValueError:
1055 1064 return 0
1056 1065 return end + 1
1057 1066 return end
1058 1067
1059 1068 def qapplied(self, repo, patch=None):
1060 1069 if patch and patch not in self.series:
1061 1070 self.ui.warn("%s not in the series file\n" % patch)
1062 1071 sys.exit(1)
1063 1072 if not patch:
1064 1073 end = len(self.applied)
1065 1074 else:
1066 1075 end = self.series.index(patch) + 1
1067 1076 for x in xrange(end):
1068 1077 p = self.appliedname(x)
1069 1078 self.ui.write("%s\n" % p)
1070 1079
1071 1080 def appliedname(self, index):
1072 1081 p = self.applied[index]
1073 1082 pname = p.split(':')[1]
1074 1083 if not self.ui.verbose:
1075 1084 p = pname
1076 1085 else:
1077 1086 p = str(self.series.index(pname)) + " " + p
1078 1087 return p
1079 1088
1080 1089 def top(self, repo):
1081 1090 if len(self.applied):
1082 1091 p = self.appliedname(-1)
1083 1092 self.ui.write(p + '\n')
1084 1093 else:
1085 1094 self.ui.write("No patches applied\n")
1086 1095
1087 1096 def next(self, repo):
1088 1097 end = self.series_end()
1089 1098 if end == len(self.series):
1090 1099 self.ui.write("All patches applied\n")
1091 1100 else:
1092 1101 p = self.series[end]
1093 1102 if self.ui.verbose:
1094 1103 self.ui.write("%d " % self.series.index(p))
1095 1104 self.ui.write(p + '\n')
1096 1105
1097 1106 def prev(self, repo):
1098 1107 if len(self.applied) > 1:
1099 1108 p = self.appliedname(-2)
1100 1109 self.ui.write(p + '\n')
1101 1110 elif len(self.applied) == 1:
1102 1111 self.ui.write("Only one patch applied\n")
1103 1112 else:
1104 1113 self.ui.write("No patches applied\n")
1105 1114
1106 1115 def qimport(self, repo, files, patch=None, existing=None, force=None):
1107 1116 if len(files) > 1 and patch:
1108 1117 self.ui.warn("-n option not valid when importing multiple files\n")
1109 1118 sys.exit(1)
1110 1119 i = 0
1111 1120 added = []
1112 1121 for filename in files:
1113 1122 if existing:
1114 1123 if not patch:
1115 1124 patch = filename
1116 1125 if not os.path.isfile(os.path.join(self.path, patch)):
1117 1126 self.ui.warn("patch %s does not exist\n" % patch)
1118 1127 sys.exit(1)
1119 1128 else:
1120 1129 try:
1121 1130 text = file(filename).read()
1122 1131 except IOError:
1123 1132 self.ui.warn("Unable to read %s\n" % patch)
1124 1133 sys.exit(1)
1125 1134 if not patch:
1126 1135 patch = os.path.split(filename)[1]
1127 1136 if not force and os.path.isfile(os.path.join(self.path, patch)):
1128 1137 self.ui.warn("patch %s already exists\n" % patch)
1129 1138 sys.exit(1)
1130 1139 patchf = self.opener(patch, "w")
1131 1140 patchf.write(text)
1132 1141 if patch in self.series:
1133 1142 self.ui.warn("patch %s is already in the series file\n" % patch)
1134 1143 sys.exit(1)
1135 index = self.series_end() + i
1144 index = self.full_series_end() + i
1136 1145 self.full_series[index:index] = [patch]
1137 1146 self.read_series(self.full_series)
1138 1147 self.ui.warn("adding %s to series file\n" % patch)
1139 1148 i += 1
1140 1149 added.append(patch)
1141 1150 patch = None
1142 1151 self.series_dirty = 1
1143 1152 qrepo = self.qrepo()
1144 1153 if qrepo:
1145 1154 qrepo.add(added)
1146 1155
1147 1156 def delete(ui, repo, patch, **opts):
1148 1157 """remove a patch from the series file"""
1149 1158 q = repomap[repo]
1150 1159 q.delete(repo, patch)
1151 1160 q.save_dirty()
1152 1161 return 0
1153 1162
1154 1163 def applied(ui, repo, patch=None, **opts):
1155 1164 """print the patches already applied"""
1156 1165 repomap[repo].qapplied(repo, patch)
1157 1166 return 0
1158 1167
1159 1168 def unapplied(ui, repo, patch=None, **opts):
1160 1169 """print the patches not yet applied"""
1161 1170 repomap[repo].unapplied(repo, patch)
1162 1171 return 0
1163 1172
1164 1173 def qimport(ui, repo, *filename, **opts):
1165 1174 """import a patch"""
1166 1175 q = repomap[repo]
1167 1176 q.qimport(repo, filename, patch=opts['name'],
1168 1177 existing=opts['existing'], force=opts['force'])
1169 1178 q.save_dirty()
1170 1179 return 0
1171 1180
1172 1181 def init(ui, repo, **opts):
1173 1182 """init a new queue repository"""
1174 1183 q = repomap[repo]
1175 1184 r = q.init(repo, create=opts['create_repo'])
1176 1185 q.save_dirty()
1177 1186 if r:
1178 1187 fp = r.wopener('.hgignore', 'w')
1179 1188 print >> fp, 'syntax: glob'
1180 1189 print >> fp, 'status'
1181 1190 fp.close()
1182 1191 r.wopener('series', 'w').close()
1183 1192 r.add(['.hgignore', 'series'])
1184 1193 return 0
1185 1194
1186 1195 def commit(ui, repo, *pats, **opts):
1187 1196 """commit changes in the queue repository"""
1188 1197 q = repomap[repo]
1189 1198 r = q.qrepo()
1190 1199 if not r: raise util.Abort('no queue repository')
1191 1200 commands.commit(r.ui, r, *pats, **opts)
1192 1201
1193 1202 def series(ui, repo, **opts):
1194 1203 """print the entire series file"""
1195 1204 repomap[repo].qseries(repo, missing=opts['missing'])
1196 1205 return 0
1197 1206
1198 1207 def top(ui, repo, **opts):
1199 1208 """print the name of the current patch"""
1200 1209 repomap[repo].top(repo)
1201 1210 return 0
1202 1211
1203 1212 def next(ui, repo, **opts):
1204 1213 """print the name of the next patch"""
1205 1214 repomap[repo].next(repo)
1206 1215 return 0
1207 1216
1208 1217 def prev(ui, repo, **opts):
1209 1218 """print the name of the previous patch"""
1210 1219 repomap[repo].prev(repo)
1211 1220 return 0
1212 1221
1213 1222 def new(ui, repo, patch, **opts):
1214 1223 """create a new patch"""
1215 1224 q = repomap[repo]
1216 1225 message=commands.logmessage(**opts)
1217 1226 q.new(repo, patch, msg=message, force=opts['force'])
1218 1227 q.save_dirty()
1219 1228 return 0
1220 1229
1221 1230 def refresh(ui, repo, **opts):
1222 1231 """update the current patch"""
1223 1232 q = repomap[repo]
1224 1233 message=commands.logmessage(**opts)
1225 1234 q.refresh(repo, msg=message, short=opts['short'])
1226 1235 q.save_dirty()
1227 1236 return 0
1228 1237
1229 1238 def diff(ui, repo, *files, **opts):
1230 1239 """diff of the current patch"""
1231 1240 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1232 1241 repomap[repo].diff(repo, list(files))
1233 1242 return 0
1234 1243
1235 1244 def lastsavename(path):
1236 1245 (dir, base) = os.path.split(path)
1237 1246 names = os.listdir(dir)
1238 1247 namere = re.compile("%s.([0-9]+)" % base)
1239 1248 max = None
1240 1249 maxname = None
1241 1250 for f in names:
1242 1251 m = namere.match(f)
1243 1252 if m:
1244 1253 index = int(m.group(1))
1245 1254 if max == None or index > max:
1246 1255 max = index
1247 1256 maxname = f
1248 1257 if maxname:
1249 1258 return (os.path.join(dir, maxname), max)
1250 1259 return (None, None)
1251 1260
1252 1261 def savename(path):
1253 1262 (last, index) = lastsavename(path)
1254 1263 if last is None:
1255 1264 index = 0
1256 1265 newpath = path + ".%d" % (index + 1)
1257 1266 return newpath
1258 1267
1259 1268 def push(ui, repo, patch=None, **opts):
1260 1269 """push the next patch onto the stack"""
1261 1270 q = repomap[repo]
1262 1271 mergeq = None
1263 1272
1264 1273 if opts['all']:
1265 1274 patch = q.series[-1]
1266 1275 if opts['merge']:
1267 1276 if opts['name']:
1268 1277 newpath = opts['name']
1269 1278 else:
1270 1279 newpath, i = lastsavename(q.path)
1271 1280 if not newpath:
1272 1281 ui.warn("no saved queues found, please use -n\n")
1273 1282 return 1
1274 1283 mergeq = queue(ui, repo.join(""), newpath)
1275 1284 ui.warn("merging with queue at: %s\n" % mergeq.path)
1276 1285 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1277 1286 mergeq=mergeq)
1278 1287 q.save_dirty()
1279 1288 return ret
1280 1289
1281 1290 def pop(ui, repo, patch=None, **opts):
1282 1291 """pop the current patch off the stack"""
1283 1292 localupdate = True
1284 1293 if opts['name']:
1285 1294 q = queue(ui, repo.join(""), repo.join(opts['name']))
1286 1295 ui.warn('using patch queue: %s\n' % q.path)
1287 1296 localupdate = False
1288 1297 else:
1289 1298 q = repomap[repo]
1290 1299 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1291 1300 q.save_dirty()
1292 1301 return 0
1293 1302
1294 1303 def restore(ui, repo, rev, **opts):
1295 1304 """restore the queue state saved by a rev"""
1296 1305 rev = repo.lookup(rev)
1297 1306 q = repomap[repo]
1298 1307 q.restore(repo, rev, delete=opts['delete'],
1299 1308 qupdate=opts['update'])
1300 1309 q.save_dirty()
1301 1310 return 0
1302 1311
1303 1312 def save(ui, repo, **opts):
1304 1313 """save current queue state"""
1305 1314 q = repomap[repo]
1306 1315 message=commands.logmessage(**opts)
1307 1316 ret = q.save(repo, msg=message)
1308 1317 if ret:
1309 1318 return ret
1310 1319 q.save_dirty()
1311 1320 if opts['copy']:
1312 1321 path = q.path
1313 1322 if opts['name']:
1314 1323 newpath = os.path.join(q.basepath, opts['name'])
1315 1324 if os.path.exists(newpath):
1316 1325 if not os.path.isdir(newpath):
1317 1326 ui.warn("destination %s exists and is not a directory\n" %
1318 1327 newpath)
1319 1328 sys.exit(1)
1320 1329 if not opts['force']:
1321 1330 ui.warn("destination %s exists, use -f to force\n" %
1322 1331 newpath)
1323 1332 sys.exit(1)
1324 1333 else:
1325 1334 newpath = savename(path)
1326 1335 ui.warn("copy %s to %s\n" % (path, newpath))
1327 1336 util.copyfiles(path, newpath)
1328 1337 if opts['empty']:
1329 1338 try:
1330 1339 os.unlink(os.path.join(q.path, q.status_path))
1331 1340 except:
1332 1341 pass
1333 1342 return 0
1334 1343
1335 1344 def strip(ui, repo, rev, **opts):
1336 1345 """strip a revision and all later revs on the same branch"""
1337 1346 rev = repo.lookup(rev)
1338 1347 backup = 'all'
1339 1348 if opts['backup']:
1340 1349 backup = 'strip'
1341 1350 elif opts['nobackup']:
1342 1351 backup = 'none'
1343 1352 repomap[repo].strip(repo, rev, backup=backup)
1344 1353 return 0
1345 1354
1346 1355 def version(ui, q=None):
1347 1356 """print the version number"""
1348 1357 ui.write("mq version %s\n" % versionstr)
1349 1358 return 0
1350 1359
1351 1360 def reposetup(ui, repo):
1352 1361 repomap[repo] = queue(ui, repo.join(""))
1353 1362 oldtags = repo.tags
1354 1363
1355 1364 def qtags():
1356 1365 if repo.tagscache:
1357 1366 return repo.tagscache
1358 1367
1359 1368 tagscache = oldtags()
1360 1369
1361 1370 q = repomap[repo]
1362 1371 if len(q.applied) == 0:
1363 1372 return tagscache
1364 1373
1365 1374 mqtags = [patch.split(':') for patch in q.applied]
1366 1375 mqtags.append((mqtags[-1][0], 'qtip'))
1367 1376 mqtags.append((mqtags[0][0], 'qbase'))
1368 1377 for patch in mqtags:
1369 1378 if patch[1] in tagscache:
1370 1379 repo.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1371 1380 else:
1372 1381 tagscache[patch[1]] = revlog.bin(patch[0])
1373 1382
1374 1383 return tagscache
1375 1384
1376 1385 repo.tags = qtags
1377 1386
1378 1387 cmdtable = {
1379 1388 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1380 1389 "qcommit|qci":
1381 1390 (commit,
1382 1391 commands.table["^commit|ci"][1],
1383 1392 'hg qcommit [OPTION]... [FILE]...'),
1384 1393 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1385 1394 "qdelete": (delete, [], 'hg qdelete PATCH'),
1386 1395 "^qimport":
1387 1396 (qimport,
1388 1397 [('e', 'existing', None, 'import file in patch dir'),
1389 1398 ('n', 'name', '', 'patch file name'),
1390 1399 ('f', 'force', None, 'overwrite existing files')],
1391 1400 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1392 1401 "^qinit":
1393 1402 (init,
1394 1403 [('c', 'create-repo', None, 'create queue repository')],
1395 1404 'hg qinit [-c]'),
1396 1405 "qnew":
1397 1406 (new,
1398 1407 [('m', 'message', '', _('use <text> as commit message')),
1399 1408 ('l', 'logfile', '', _('read the commit message from <file>')),
1400 1409 ('f', 'force', None, 'force')],
1401 1410 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1402 1411 "qnext": (next, [], 'hg qnext'),
1403 1412 "qprev": (prev, [], 'hg qprev'),
1404 1413 "^qpop":
1405 1414 (pop,
1406 1415 [('a', 'all', None, 'pop all patches'),
1407 1416 ('n', 'name', '', 'queue name to pop'),
1408 1417 ('f', 'force', None, 'forget any local changes')],
1409 1418 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1410 1419 "^qpush":
1411 1420 (push,
1412 1421 [('f', 'force', None, 'apply if the patch has rejects'),
1413 1422 ('l', 'list', None, 'list patch name in commit text'),
1414 1423 ('a', 'all', None, 'apply all patches'),
1415 1424 ('m', 'merge', None, 'merge from another queue'),
1416 1425 ('n', 'name', '', 'merge queue name')],
1417 1426 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1418 1427 "^qrefresh":
1419 1428 (refresh,
1420 1429 [('m', 'message', '', _('change commit message with <text>')),
1421 1430 ('l', 'logfile', '', _('change commit message with <file> content')),
1422 1431 ('s', 'short', None, 'short refresh')],
1423 1432 'hg qrefresh [-m TEXT] [-l FILE] [-s]'),
1424 1433 "qrestore":
1425 1434 (restore,
1426 1435 [('d', 'delete', None, 'delete save entry'),
1427 1436 ('u', 'update', None, 'update queue working dir')],
1428 1437 'hg qrestore [-d] [-u] REV'),
1429 1438 "qsave":
1430 1439 (save,
1431 1440 [('m', 'message', '', _('use <text> as commit message')),
1432 1441 ('l', 'logfile', '', _('read the commit message from <file>')),
1433 1442 ('c', 'copy', None, 'copy patch directory'),
1434 1443 ('n', 'name', '', 'copy directory name'),
1435 1444 ('e', 'empty', None, 'clear queue status file'),
1436 1445 ('f', 'force', None, 'force copy')],
1437 1446 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1438 1447 "qseries":
1439 1448 (series,
1440 1449 [('m', 'missing', None, 'print patches not in series')],
1441 1450 'hg qseries [-m]'),
1442 1451 "^strip":
1443 1452 (strip,
1444 1453 [('f', 'force', None, 'force multi-head removal'),
1445 1454 ('b', 'backup', None, 'bundle unrelated changesets'),
1446 1455 ('n', 'nobackup', None, 'no backups')],
1447 1456 'hg strip [-f] [-b] [-n] REV'),
1448 1457 "qtop": (top, [], 'hg qtop'),
1449 1458 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1450 1459 "qversion": (version, [], 'hg qversion')
1451 1460 }
1452 1461
General Comments 0
You need to be logged in to leave comments. Login now