comparison hgext/mq.py @ 1920:b7cc0f323a4c

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