comparison hgext/mq.py @ 1808:7036cd7f770d

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