##// END OF EJS Templates
Add -f option to qdelete, to remove patch file.
Brendan Cully -
r2752:5dfeda16 default
parent child Browse files
Show More
@@ -1,1635 +1,1647 b''
1 1
2 2 # queue.py - patch queues for mercurial
3 3 #
4 4 # Copyright 2005 Chris Mason <mason@suse.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 '''patch management and development
10 10
11 11 This extension lets you work with a stack of patches in a Mercurial
12 12 repository. It manages two stacks of patches - all known patches, and
13 13 applied patches (subset of known patches).
14 14
15 15 Known patches are represented as patch files in the .hg/patches
16 16 directory. Applied patches are both patch files and changesets.
17 17
18 18 Common tasks (use "hg help command" for more details):
19 19
20 20 prepare repository to work with patches qinit
21 21 create new patch qnew
22 22 import existing patch qimport
23 23
24 24 print patch series qseries
25 25 print applied patches qapplied
26 26 print name of top applied patch qtop
27 27
28 28 add known patch to applied stack qpush
29 29 remove patch from applied stack qpop
30 30 refresh contents of top applied patch qrefresh
31 31 '''
32 32
33 33 from mercurial.demandload import *
34 34 demandload(globals(), "os sys re struct traceback errno bz2")
35 35 from mercurial.i18n import gettext as _
36 36 from mercurial import ui, hg, revlog, commands, util
37 37
38 38 versionstr = "0.45"
39 39
40 40 commands.norepo += " qclone qversion"
41 41
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 raise util.Abort(_("apply failed for patch %s") % patch)
191 191
192 192 self.ui.warn("patch didn't work out, merging %s\n" % patch)
193 193
194 194 # apply failed, strip away that rev and merge.
195 195 repo.update(head, allow=False, force=True, wlock=wlock)
196 196 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
197 197
198 198 c = repo.changelog.read(rev)
199 199 ret = repo.update(rev, allow=True, wlock=wlock)
200 200 if ret:
201 201 raise util.Abort(_("update returned %d") % ret)
202 202 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
203 203 if n == None:
204 204 raise util.Abort(_("repo commit failed"))
205 205 try:
206 206 message, comments, user, date, patchfound = mergeq.readheaders(patch)
207 207 except:
208 208 raise util.Abort(_("unable to read %s") % patch)
209 209
210 210 patchf = self.opener(patch, "w")
211 211 if comments:
212 212 comments = "\n".join(comments) + '\n\n'
213 213 patchf.write(comments)
214 214 commands.dodiff(patchf, self.ui, repo, head, n)
215 215 patchf.close()
216 216 return (0, n)
217 217
218 218 def qparents(self, repo, rev=None):
219 219 if rev is None:
220 220 (p1, p2) = repo.dirstate.parents()
221 221 if p2 == revlog.nullid:
222 222 return p1
223 223 if len(self.applied) == 0:
224 224 return None
225 225 (top, patch) = self.applied[-1].split(':')
226 226 top = revlog.bin(top)
227 227 return top
228 228 pp = repo.changelog.parents(rev)
229 229 if pp[1] != revlog.nullid:
230 230 arevs = [ x.split(':')[0] for x in self.applied ]
231 231 p0 = revlog.hex(pp[0])
232 232 p1 = revlog.hex(pp[1])
233 233 if p0 in arevs:
234 234 return pp[0]
235 235 if p1 in arevs:
236 236 return pp[1]
237 237 return pp[0]
238 238
239 239 def mergepatch(self, repo, mergeq, series, wlock):
240 240 if len(self.applied) == 0:
241 241 # each of the patches merged in will have two parents. This
242 242 # can confuse the qrefresh, qdiff, and strip code because it
243 243 # needs to know which parent is actually in the patch queue.
244 244 # so, we insert a merge marker with only one parent. This way
245 245 # the first patch in the queue is never a merge patch
246 246 #
247 247 pname = ".hg.patches.merge.marker"
248 248 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
249 249 wlock=wlock)
250 250 self.applied.append(revlog.hex(n) + ":" + pname)
251 251 self.applied_dirty = 1
252 252
253 253 head = self.qparents(repo)
254 254
255 255 for patch in series:
256 256 patch = mergeq.lookup(patch, strict=True)
257 257 if not patch:
258 258 self.ui.warn("patch %s does not exist\n" % patch)
259 259 return (1, None)
260 260
261 261 info = mergeq.isapplied(patch)
262 262 if not info:
263 263 self.ui.warn("patch %s is not applied\n" % patch)
264 264 return (1, None)
265 265 rev = revlog.bin(info[1])
266 266 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
267 267 if head:
268 268 self.applied.append(revlog.hex(head) + ":" + patch)
269 269 self.applied_dirty = 1
270 270 if err:
271 271 return (err, head)
272 272 return (0, head)
273 273
274 274 def patch(self, repo, patchfile):
275 275 '''Apply patchfile to the working directory.
276 276 patchfile: file name of patch'''
277 277 try:
278 278 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
279 279 f = os.popen("%s -d '%s' -p1 --no-backup-if-mismatch < '%s'" %
280 280 (pp, repo.root, patchfile))
281 281 except:
282 282 self.ui.warn("patch failed, unable to continue (try -v)\n")
283 283 return (None, [], False)
284 284 files = []
285 285 fuzz = False
286 286 for l in f:
287 287 l = l.rstrip('\r\n');
288 288 if self.ui.verbose:
289 289 self.ui.warn(l + "\n")
290 290 if l[:14] == 'patching file ':
291 291 pf = os.path.normpath(l[14:])
292 292 # when patch finds a space in the file name, it puts
293 293 # single quotes around the filename. strip them off
294 294 if pf[0] == "'" and pf[-1] == "'":
295 295 pf = pf[1:-1]
296 296 if pf not in files:
297 297 files.append(pf)
298 298 printed_file = False
299 299 file_str = l
300 300 elif l.find('with fuzz') >= 0:
301 301 if not printed_file:
302 302 self.ui.warn(file_str + '\n')
303 303 printed_file = True
304 304 self.ui.warn(l + '\n')
305 305 fuzz = True
306 306 elif l.find('saving rejects to file') >= 0:
307 307 self.ui.warn(l + '\n')
308 308 elif l.find('FAILED') >= 0:
309 309 if not printed_file:
310 310 self.ui.warn(file_str + '\n')
311 311 printed_file = True
312 312 self.ui.warn(l + '\n')
313 313
314 314 return (not f.close(), files, fuzz)
315 315
316 316 def apply(self, repo, series, list=False, update_status=True,
317 317 strict=False, patchdir=None, merge=None, wlock=None):
318 318 # TODO unify with commands.py
319 319 if not patchdir:
320 320 patchdir = self.path
321 321 err = 0
322 322 if not wlock:
323 323 wlock = repo.wlock()
324 324 lock = repo.lock()
325 325 tr = repo.transaction()
326 326 n = None
327 327 for patch in series:
328 328 self.ui.warn("applying %s\n" % patch)
329 329 pf = os.path.join(patchdir, patch)
330 330
331 331 try:
332 332 message, comments, user, date, patchfound = self.readheaders(patch)
333 333 except:
334 334 self.ui.warn("Unable to read %s\n" % pf)
335 335 err = 1
336 336 break
337 337
338 338 if not message:
339 339 message = "imported patch %s\n" % patch
340 340 else:
341 341 if list:
342 342 message.append("\nimported patch %s" % patch)
343 343 message = '\n'.join(message)
344 344
345 345 (patcherr, files, fuzz) = self.patch(repo, pf)
346 346 patcherr = not patcherr
347 347
348 348 if merge and len(files) > 0:
349 349 # Mark as merged and update dirstate parent info
350 350 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
351 351 p1, p2 = repo.dirstate.parents()
352 352 repo.dirstate.setparents(p1, merge)
353 353 if len(files) > 0:
354 354 cwd = repo.getcwd()
355 355 cfiles = files
356 356 if cwd:
357 357 cfiles = [util.pathto(cwd, f) for f in files]
358 358 commands.addremove_lock(self.ui, repo, cfiles,
359 359 opts={}, wlock=wlock)
360 360 n = repo.commit(files, message, user, date, force=1, lock=lock,
361 361 wlock=wlock)
362 362
363 363 if n == None:
364 364 raise util.Abort(_("repo commit failed"))
365 365
366 366 if update_status:
367 367 self.applied.append(revlog.hex(n) + ":" + patch)
368 368
369 369 if patcherr:
370 370 if not patchfound:
371 371 self.ui.warn("patch %s is empty\n" % patch)
372 372 err = 0
373 373 else:
374 374 self.ui.warn("patch failed, rejects left in working dir\n")
375 375 err = 1
376 376 break
377 377
378 378 if fuzz and strict:
379 379 self.ui.warn("fuzz found when applying patch, stopping\n")
380 380 err = 1
381 381 break
382 382 tr.close()
383 383 return (err, n)
384 384
385 def delete(self, repo, patch):
385 def delete(self, repo, patch, force=False):
386 386 patch = self.lookup(patch, strict=True)
387 387 info = self.isapplied(patch)
388 388 if info:
389 389 raise util.Abort(_("cannot delete applied patch %s") % patch)
390 390 if patch not in self.series:
391 391 raise util.Abort(_("patch %s not in series file") % patch)
392 if force:
393 r = self.qrepo()
394 if r:
395 r.remove([patch], True)
396 else:
397 os.unlink(os.path.join(self.path, patch))
392 398 i = self.find_series(patch)
393 399 del self.full_series[i]
394 400 self.read_series(self.full_series)
395 401 self.series_dirty = 1
396 402
397 403 def check_toppatch(self, repo):
398 404 if len(self.applied) > 0:
399 405 (top, patch) = self.applied[-1].split(':')
400 406 top = revlog.bin(top)
401 407 pp = repo.dirstate.parents()
402 408 if top not in pp:
403 409 raise util.Abort(_("queue top not at same revision as working directory"))
404 410 return top
405 411 return None
406 412 def check_localchanges(self, repo):
407 413 (c, a, r, d, u) = repo.changes(None, None)
408 414 if c or a or d or r:
409 415 raise util.Abort(_("local changes found, refresh first"))
410 416 def new(self, repo, patch, msg=None, force=None):
411 417 if os.path.exists(os.path.join(self.path, patch)):
412 418 raise util.Abort(_('patch "%s" already exists') % patch)
413 419 commitfiles = []
414 420 (c, a, r, d, u) = repo.changes(None, None)
415 421 if c or a or d or r:
416 422 if not force:
417 423 raise util.Abort(_("local changes found, refresh first"))
418 424 commitfiles = c + a + r
419 425 self.check_toppatch(repo)
420 426 wlock = repo.wlock()
421 427 insert = self.full_series_end()
422 428 if msg:
423 429 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
424 430 wlock=wlock)
425 431 else:
426 432 n = repo.commit(commitfiles,
427 433 "New patch: %s" % patch, force=True, wlock=wlock)
428 434 if n == None:
429 435 raise util.Abort(_("repo commit failed"))
430 436 self.full_series[insert:insert] = [patch]
431 437 self.applied.append(revlog.hex(n) + ":" + patch)
432 438 self.read_series(self.full_series)
433 439 self.series_dirty = 1
434 440 self.applied_dirty = 1
435 441 p = self.opener(patch, "w")
436 442 if msg:
437 443 msg = msg + "\n"
438 444 p.write(msg)
439 445 p.close()
440 446 wlock = None
441 447 r = self.qrepo()
442 448 if r: r.add([patch])
443 449 if commitfiles:
444 450 self.refresh(repo, msg=None, short=True)
445 451
446 452 def strip(self, repo, rev, update=True, backup="all", wlock=None):
447 453 def limitheads(chlog, stop):
448 454 """return the list of all nodes that have no children"""
449 455 p = {}
450 456 h = []
451 457 stoprev = 0
452 458 if stop in chlog.nodemap:
453 459 stoprev = chlog.rev(stop)
454 460
455 461 for r in range(chlog.count() - 1, -1, -1):
456 462 n = chlog.node(r)
457 463 if n not in p:
458 464 h.append(n)
459 465 if n == stop:
460 466 break
461 467 if r < stoprev:
462 468 break
463 469 for pn in chlog.parents(n):
464 470 p[pn] = 1
465 471 return h
466 472
467 473 def bundle(cg):
468 474 backupdir = repo.join("strip-backup")
469 475 if not os.path.isdir(backupdir):
470 476 os.mkdir(backupdir)
471 477 name = os.path.join(backupdir, "%s" % revlog.short(rev))
472 478 name = savename(name)
473 479 self.ui.warn("saving bundle to %s\n" % name)
474 480 # TODO, exclusive open
475 481 f = open(name, "wb")
476 482 try:
477 483 f.write("HG10")
478 484 z = bz2.BZ2Compressor(9)
479 485 while 1:
480 486 chunk = cg.read(4096)
481 487 if not chunk:
482 488 break
483 489 f.write(z.compress(chunk))
484 490 f.write(z.flush())
485 491 except:
486 492 os.unlink(name)
487 493 raise
488 494 f.close()
489 495 return name
490 496
491 497 def stripall(rev, revnum):
492 498 cl = repo.changelog
493 499 c = cl.read(rev)
494 500 mm = repo.manifest.read(c[0])
495 501 seen = {}
496 502
497 503 for x in xrange(revnum, cl.count()):
498 504 c = cl.read(cl.node(x))
499 505 for f in c[3]:
500 506 if f in seen:
501 507 continue
502 508 seen[f] = 1
503 509 if f in mm:
504 510 filerev = mm[f]
505 511 else:
506 512 filerev = 0
507 513 seen[f] = filerev
508 514 # we go in two steps here so the strip loop happens in a
509 515 # sensible order. When stripping many files, this helps keep
510 516 # our disk access patterns under control.
511 517 list = seen.keys()
512 518 list.sort()
513 519 for f in list:
514 520 ff = repo.file(f)
515 521 filerev = seen[f]
516 522 if filerev != 0:
517 523 if filerev in ff.nodemap:
518 524 filerev = ff.rev(filerev)
519 525 else:
520 526 filerev = 0
521 527 ff.strip(filerev, revnum)
522 528
523 529 if not wlock:
524 530 wlock = repo.wlock()
525 531 lock = repo.lock()
526 532 chlog = repo.changelog
527 533 # TODO delete the undo files, and handle undo of merge sets
528 534 pp = chlog.parents(rev)
529 535 revnum = chlog.rev(rev)
530 536
531 537 if update:
532 538 (c, a, r, d, u) = repo.changes(None, None)
533 539 if c or a or d or r:
534 540 raise util.Abort(_("local changes found"))
535 541 urev = self.qparents(repo, rev)
536 542 repo.update(urev, allow=False, force=True, wlock=wlock)
537 543 repo.dirstate.write()
538 544
539 545 # save is a list of all the branches we are truncating away
540 546 # that we actually want to keep. changegroup will be used
541 547 # to preserve them and add them back after the truncate
542 548 saveheads = []
543 549 savebases = {}
544 550
545 551 tip = chlog.tip()
546 552 heads = limitheads(chlog, rev)
547 553 seen = {}
548 554
549 555 # search through all the heads, finding those where the revision
550 556 # we want to strip away is an ancestor. Also look for merges
551 557 # that might be turned into new heads by the strip.
552 558 while heads:
553 559 h = heads.pop()
554 560 n = h
555 561 while True:
556 562 seen[n] = 1
557 563 pp = chlog.parents(n)
558 564 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
559 565 if pp[1] not in seen:
560 566 heads.append(pp[1])
561 567 if pp[0] == revlog.nullid:
562 568 break
563 569 if chlog.rev(pp[0]) < revnum:
564 570 break
565 571 n = pp[0]
566 572 if n == rev:
567 573 break
568 574 r = chlog.reachable(h, rev)
569 575 if rev not in r:
570 576 saveheads.append(h)
571 577 for x in r:
572 578 if chlog.rev(x) > revnum:
573 579 savebases[x] = 1
574 580
575 581 # create a changegroup for all the branches we need to keep
576 582 if backup is "all":
577 583 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
578 584 bundle(backupch)
579 585 if saveheads:
580 586 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
581 587 chgrpfile = bundle(backupch)
582 588
583 589 stripall(rev, revnum)
584 590
585 591 change = chlog.read(rev)
586 592 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
587 593 chlog.strip(revnum, revnum)
588 594 if saveheads:
589 595 self.ui.status("adding branch\n")
590 596 commands.unbundle(self.ui, repo, chgrpfile, update=False)
591 597 if backup is not "strip":
592 598 os.unlink(chgrpfile)
593 599
594 600 def isapplied(self, patch):
595 601 """returns (index, rev, patch)"""
596 602 for i in xrange(len(self.applied)):
597 603 p = self.applied[i]
598 604 a = p.split(':')
599 605 if a[1] == patch:
600 606 return (i, a[0], a[1])
601 607 return None
602 608
603 609 # if the exact patch name does not exist, we try a few
604 610 # variations. If strict is passed, we try only #1
605 611 #
606 612 # 1) a number to indicate an offset in the series file
607 613 # 2) a unique substring of the patch name was given
608 614 # 3) patchname[-+]num to indicate an offset in the series file
609 615 def lookup(self, patch, strict=False):
610 616 def partial_name(s):
611 617 count = 0
612 618 if s in self.series:
613 619 return s
614 620 for x in self.series:
615 621 if s in x:
616 622 count += 1
617 623 last = x
618 624 if count > 1:
619 625 return None
620 626 if count:
621 627 return last
622 628 if len(self.series) > 0 and len(self.applied) > 0:
623 629 if s == 'qtip':
624 630 return self.series[self.series_end()-1]
625 631 if s == 'qbase':
626 632 return self.series[0]
627 633 return None
628 634 if patch == None:
629 635 return None
630 636
631 637 # we don't want to return a partial match until we make
632 638 # sure the file name passed in does not exist (checked below)
633 639 res = partial_name(patch)
634 640 if res and res == patch:
635 641 return res
636 642
637 643 if not os.path.isfile(os.path.join(self.path, patch)):
638 644 try:
639 645 sno = int(patch)
640 646 except(ValueError, OverflowError):
641 647 pass
642 648 else:
643 649 if sno < len(self.series):
644 650 patch = self.series[sno]
645 651 return patch
646 652 if not strict:
647 653 # return any partial match made above
648 654 if res:
649 655 return res
650 656 minus = patch.rsplit('-', 1)
651 657 if len(minus) > 1:
652 658 res = partial_name(minus[0])
653 659 if res:
654 660 i = self.series.index(res)
655 661 try:
656 662 off = int(minus[1] or 1)
657 663 except(ValueError, OverflowError):
658 664 pass
659 665 else:
660 666 if i - off >= 0:
661 667 return self.series[i - off]
662 668 plus = patch.rsplit('+', 1)
663 669 if len(plus) > 1:
664 670 res = partial_name(plus[0])
665 671 if res:
666 672 i = self.series.index(res)
667 673 try:
668 674 off = int(plus[1] or 1)
669 675 except(ValueError, OverflowError):
670 676 pass
671 677 else:
672 678 if i + off < len(self.series):
673 679 return self.series[i + off]
674 680 raise util.Abort(_("patch %s not in series") % patch)
675 681
676 682 def push(self, repo, patch=None, force=False, list=False,
677 683 mergeq=None, wlock=None):
678 684 if not wlock:
679 685 wlock = repo.wlock()
680 686 patch = self.lookup(patch)
681 687 if patch and self.isapplied(patch):
682 688 self.ui.warn(_("patch %s is already applied\n") % patch)
683 689 sys.exit(1)
684 690 if self.series_end() == len(self.series):
685 691 self.ui.warn(_("patch series fully applied\n"))
686 692 sys.exit(1)
687 693 if not force:
688 694 self.check_localchanges(repo)
689 695
690 696 self.applied_dirty = 1;
691 697 start = self.series_end()
692 698 if start > 0:
693 699 self.check_toppatch(repo)
694 700 if not patch:
695 701 patch = self.series[start]
696 702 end = start + 1
697 703 else:
698 704 end = self.series.index(patch, start) + 1
699 705 s = self.series[start:end]
700 706 if mergeq:
701 707 ret = self.mergepatch(repo, mergeq, s, wlock)
702 708 else:
703 709 ret = self.apply(repo, s, list, wlock=wlock)
704 710 top = self.applied[-1].split(':')[1]
705 711 if ret[0]:
706 712 self.ui.write("Errors during apply, please fix and refresh %s\n" %
707 713 top)
708 714 else:
709 715 self.ui.write("Now at: %s\n" % top)
710 716 return ret[0]
711 717
712 718 def pop(self, repo, patch=None, force=False, update=True, all=False,
713 719 wlock=None):
714 720 def getfile(f, rev):
715 721 t = repo.file(f).read(rev)
716 722 try:
717 723 repo.wfile(f, "w").write(t)
718 724 except IOError:
719 725 try:
720 726 os.makedirs(os.path.dirname(repo.wjoin(f)))
721 727 except OSError, err:
722 728 if err.errno != errno.EEXIST: raise
723 729 repo.wfile(f, "w").write(t)
724 730
725 731 if not wlock:
726 732 wlock = repo.wlock()
727 733 if patch:
728 734 # index, rev, patch
729 735 info = self.isapplied(patch)
730 736 if not info:
731 737 patch = self.lookup(patch)
732 738 info = self.isapplied(patch)
733 739 if not info:
734 740 raise util.Abort(_("patch %s is not applied") % patch)
735 741 if len(self.applied) == 0:
736 742 self.ui.warn(_("no patches applied\n"))
737 743 sys.exit(1)
738 744
739 745 if not update:
740 746 parents = repo.dirstate.parents()
741 747 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
742 748 for p in parents:
743 749 if p in rr:
744 750 self.ui.warn("qpop: forcing dirstate update\n")
745 751 update = True
746 752
747 753 if not force and update:
748 754 self.check_localchanges(repo)
749 755
750 756 self.applied_dirty = 1;
751 757 end = len(self.applied)
752 758 if not patch:
753 759 if all:
754 760 popi = 0
755 761 else:
756 762 popi = len(self.applied) - 1
757 763 else:
758 764 popi = info[0] + 1
759 765 if popi >= end:
760 766 self.ui.warn("qpop: %s is already at the top\n" % patch)
761 767 return
762 768 info = [ popi ] + self.applied[popi].split(':')
763 769
764 770 start = info[0]
765 771 rev = revlog.bin(info[1])
766 772
767 773 # we know there are no local changes, so we can make a simplified
768 774 # form of hg.update.
769 775 if update:
770 776 top = self.check_toppatch(repo)
771 777 qp = self.qparents(repo, rev)
772 778 changes = repo.changelog.read(qp)
773 779 mf1 = repo.manifest.readflags(changes[0])
774 780 mmap = repo.manifest.read(changes[0])
775 781 (c, a, r, d, u) = repo.changes(qp, top)
776 782 if d:
777 783 raise util.Abort("deletions found between repo revs")
778 784 for f in c:
779 785 getfile(f, mmap[f])
780 786 for f in r:
781 787 getfile(f, mmap[f])
782 788 util.set_exec(repo.wjoin(f), mf1[f])
783 789 repo.dirstate.update(c + r, 'n')
784 790 for f in a:
785 791 try: os.unlink(repo.wjoin(f))
786 792 except: raise
787 793 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
788 794 except: pass
789 795 if a:
790 796 repo.dirstate.forget(a)
791 797 repo.dirstate.setparents(qp, revlog.nullid)
792 798 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
793 799 del self.applied[start:end]
794 800 if len(self.applied):
795 801 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
796 802 else:
797 803 self.ui.write("Patch queue now empty\n")
798 804
799 805 def diff(self, repo, files):
800 806 top = self.check_toppatch(repo)
801 807 if not top:
802 808 self.ui.write("No patches applied\n")
803 809 return
804 810 qp = self.qparents(repo, top)
805 811 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
806 812
807 813 def refresh(self, repo, msg=None, short=False):
808 814 if len(self.applied) == 0:
809 815 self.ui.write("No patches applied\n")
810 816 return
811 817 wlock = repo.wlock()
812 818 self.check_toppatch(repo)
813 819 qp = self.qparents(repo)
814 820 (top, patch) = self.applied[-1].split(':')
815 821 top = revlog.bin(top)
816 822 cparents = repo.changelog.parents(top)
817 823 patchparent = self.qparents(repo, top)
818 824 message, comments, user, date, patchfound = self.readheaders(patch)
819 825
820 826 patchf = self.opener(patch, "w")
821 827 msg = msg.rstrip()
822 828 if msg:
823 829 if comments:
824 830 # Remove existing message.
825 831 ci = 0
826 832 for mi in range(len(message)):
827 833 while message[mi] != comments[ci]:
828 834 ci += 1
829 835 del comments[ci]
830 836 comments.append(msg)
831 837 if comments:
832 838 comments = "\n".join(comments) + '\n\n'
833 839 patchf.write(comments)
834 840
835 841 tip = repo.changelog.tip()
836 842 if top == tip:
837 843 # if the top of our patch queue is also the tip, there is an
838 844 # optimization here. We update the dirstate in place and strip
839 845 # off the tip commit. Then just commit the current directory
840 846 # tree. We can also send repo.commit the list of files
841 847 # changed to speed up the diff
842 848 #
843 849 # in short mode, we only diff the files included in the
844 850 # patch already
845 851 #
846 852 # this should really read:
847 853 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
848 854 # but we do it backwards to take advantage of manifest/chlog
849 855 # caching against the next repo.changes call
850 856 #
851 857 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
852 858 if short:
853 859 filelist = cc + aa + dd
854 860 else:
855 861 filelist = None
856 862 (c, a, r, d, u) = repo.changes(None, None, filelist)
857 863
858 864 # we might end up with files that were added between tip and
859 865 # the dirstate parent, but then changed in the local dirstate.
860 866 # in this case, we want them to only show up in the added section
861 867 for x in c:
862 868 if x not in aa:
863 869 cc.append(x)
864 870 # we might end up with files added by the local dirstate that
865 871 # were deleted by the patch. In this case, they should only
866 872 # show up in the changed section.
867 873 for x in a:
868 874 if x in dd:
869 875 del dd[dd.index(x)]
870 876 cc.append(x)
871 877 else:
872 878 aa.append(x)
873 879 # make sure any files deleted in the local dirstate
874 880 # are not in the add or change column of the patch
875 881 forget = []
876 882 for x in d + r:
877 883 if x in aa:
878 884 del aa[aa.index(x)]
879 885 forget.append(x)
880 886 continue
881 887 elif x in cc:
882 888 del cc[cc.index(x)]
883 889 dd.append(x)
884 890
885 891 c = list(util.unique(cc))
886 892 r = list(util.unique(dd))
887 893 a = list(util.unique(aa))
888 894 filelist = list(util.unique(c + r + a ))
889 895 commands.dodiff(patchf, self.ui, repo, patchparent, None,
890 896 filelist, changes=(c, a, r, [], u))
891 897 patchf.close()
892 898
893 899 changes = repo.changelog.read(tip)
894 900 repo.dirstate.setparents(*cparents)
895 901 repo.dirstate.update(a, 'a')
896 902 repo.dirstate.update(r, 'r')
897 903 repo.dirstate.update(c, 'n')
898 904 repo.dirstate.forget(forget)
899 905
900 906 if not msg:
901 907 if not message:
902 908 message = "patch queue: %s\n" % patch
903 909 else:
904 910 message = "\n".join(message)
905 911 else:
906 912 message = msg
907 913
908 914 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
909 915 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
910 916 self.applied[-1] = revlog.hex(n) + ':' + patch
911 917 self.applied_dirty = 1
912 918 else:
913 919 commands.dodiff(patchf, self.ui, repo, patchparent, None)
914 920 patchf.close()
915 921 self.pop(repo, force=True, wlock=wlock)
916 922 self.push(repo, force=True, wlock=wlock)
917 923
918 924 def init(self, repo, create=False):
919 925 if os.path.isdir(self.path):
920 926 raise util.Abort(_("patch queue directory already exists"))
921 927 os.mkdir(self.path)
922 928 if create:
923 929 return self.qrepo(create=True)
924 930
925 931 def unapplied(self, repo, patch=None):
926 932 if patch and patch not in self.series:
927 933 raise util.Abort(_("patch %s is not in series file") % patch)
928 934 if not patch:
929 935 start = self.series_end()
930 936 else:
931 937 start = self.series.index(patch) + 1
932 938 for p in self.series[start:]:
933 939 if self.ui.verbose:
934 940 self.ui.write("%d " % self.series.index(p))
935 941 self.ui.write("%s\n" % p)
936 942
937 943 def qseries(self, repo, missing=None):
938 944 start = self.series_end()
939 945 if not missing:
940 946 for p in self.series[:start]:
941 947 if self.ui.verbose:
942 948 self.ui.write("%d A " % self.series.index(p))
943 949 self.ui.write("%s\n" % p)
944 950 for p in self.series[start:]:
945 951 if self.ui.verbose:
946 952 self.ui.write("%d U " % self.series.index(p))
947 953 self.ui.write("%s\n" % p)
948 954 else:
949 955 list = []
950 956 for root, dirs, files in os.walk(self.path):
951 957 d = root[len(self.path) + 1:]
952 958 for f in files:
953 959 fl = os.path.join(d, f)
954 960 if (fl not in self.series and
955 961 fl not in (self.status_path, self.series_path)
956 962 and not fl.startswith('.')):
957 963 list.append(fl)
958 964 list.sort()
959 965 if list:
960 966 for x in list:
961 967 if self.ui.verbose:
962 968 self.ui.write("D ")
963 969 self.ui.write("%s\n" % x)
964 970
965 971 def issaveline(self, l):
966 972 name = l.split(':')[1]
967 973 if name == '.hg.patches.save.line':
968 974 return True
969 975
970 976 def qrepo(self, create=False):
971 977 if create or os.path.isdir(os.path.join(self.path, ".hg")):
972 978 return hg.repository(self.ui, path=self.path, create=create)
973 979
974 980 def restore(self, repo, rev, delete=None, qupdate=None):
975 981 c = repo.changelog.read(rev)
976 982 desc = c[4].strip()
977 983 lines = desc.splitlines()
978 984 i = 0
979 985 datastart = None
980 986 series = []
981 987 applied = []
982 988 qpp = None
983 989 for i in xrange(0, len(lines)):
984 990 if lines[i] == 'Patch Data:':
985 991 datastart = i + 1
986 992 elif lines[i].startswith('Dirstate:'):
987 993 l = lines[i].rstrip()
988 994 l = l[10:].split(' ')
989 995 qpp = [ hg.bin(x) for x in l ]
990 996 elif datastart != None:
991 997 l = lines[i].rstrip()
992 998 index = l.index(':')
993 999 id = l[:index]
994 1000 file = l[index + 1:]
995 1001 if id:
996 1002 applied.append(l)
997 1003 series.append(file)
998 1004 if datastart == None:
999 1005 self.ui.warn("No saved patch data found\n")
1000 1006 return 1
1001 1007 self.ui.warn("restoring status: %s\n" % lines[0])
1002 1008 self.full_series = series
1003 1009 self.applied = applied
1004 1010 self.read_series(self.full_series)
1005 1011 self.series_dirty = 1
1006 1012 self.applied_dirty = 1
1007 1013 heads = repo.changelog.heads()
1008 1014 if delete:
1009 1015 if rev not in heads:
1010 1016 self.ui.warn("save entry has children, leaving it alone\n")
1011 1017 else:
1012 1018 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1013 1019 pp = repo.dirstate.parents()
1014 1020 if rev in pp:
1015 1021 update = True
1016 1022 else:
1017 1023 update = False
1018 1024 self.strip(repo, rev, update=update, backup='strip')
1019 1025 if qpp:
1020 1026 self.ui.warn("saved queue repository parents: %s %s\n" %
1021 1027 (hg.short(qpp[0]), hg.short(qpp[1])))
1022 1028 if qupdate:
1023 1029 print "queue directory updating"
1024 1030 r = self.qrepo()
1025 1031 if not r:
1026 1032 self.ui.warn("Unable to load queue repository\n")
1027 1033 return 1
1028 1034 r.update(qpp[0], allow=False, force=True)
1029 1035
1030 1036 def save(self, repo, msg=None):
1031 1037 if len(self.applied) == 0:
1032 1038 self.ui.warn("save: no patches applied, exiting\n")
1033 1039 return 1
1034 1040 if self.issaveline(self.applied[-1]):
1035 1041 self.ui.warn("status is already saved\n")
1036 1042 return 1
1037 1043
1038 1044 ar = [ ':' + x for x in self.full_series ]
1039 1045 if not msg:
1040 1046 msg = "hg patches saved state"
1041 1047 else:
1042 1048 msg = "hg patches: " + msg.rstrip('\r\n')
1043 1049 r = self.qrepo()
1044 1050 if r:
1045 1051 pp = r.dirstate.parents()
1046 1052 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1047 1053 msg += "\n\nPatch Data:\n"
1048 1054 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1049 1055 + '\n' or "")
1050 1056 n = repo.commit(None, text, user=None, force=1)
1051 1057 if not n:
1052 1058 self.ui.warn("repo commit failed\n")
1053 1059 return 1
1054 1060 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1055 1061 self.applied_dirty = 1
1056 1062
1057 1063 def full_series_end(self):
1058 1064 if len(self.applied) > 0:
1059 1065 (top, p) = self.applied[-1].split(':')
1060 1066 end = self.find_series(p)
1061 1067 if end == None:
1062 1068 return len(self.full_series)
1063 1069 return end + 1
1064 1070 return 0
1065 1071
1066 1072 def series_end(self):
1067 1073 end = 0
1068 1074 if len(self.applied) > 0:
1069 1075 (top, p) = self.applied[-1].split(':')
1070 1076 try:
1071 1077 end = self.series.index(p)
1072 1078 except ValueError:
1073 1079 return 0
1074 1080 return end + 1
1075 1081 return end
1076 1082
1077 1083 def qapplied(self, repo, patch=None):
1078 1084 if patch and patch not in self.series:
1079 1085 raise util.Abort(_("patch %s is not in series file") % patch)
1080 1086 if not patch:
1081 1087 end = len(self.applied)
1082 1088 else:
1083 1089 end = self.series.index(patch) + 1
1084 1090 for x in xrange(end):
1085 1091 p = self.appliedname(x)
1086 1092 self.ui.write("%s\n" % p)
1087 1093
1088 1094 def appliedname(self, index):
1089 1095 p = self.applied[index]
1090 1096 pname = p.split(':')[1]
1091 1097 if not self.ui.verbose:
1092 1098 p = pname
1093 1099 else:
1094 1100 p = str(self.series.index(pname)) + " " + p
1095 1101 return p
1096 1102
1097 1103 def top(self, repo):
1098 1104 if len(self.applied):
1099 1105 p = self.appliedname(-1)
1100 1106 self.ui.write(p + '\n')
1101 1107 else:
1102 1108 self.ui.write("No patches applied\n")
1103 1109
1104 1110 def next(self, repo):
1105 1111 end = self.series_end()
1106 1112 if end == len(self.series):
1107 1113 self.ui.write("All patches applied\n")
1108 1114 else:
1109 1115 p = self.series[end]
1110 1116 if self.ui.verbose:
1111 1117 self.ui.write("%d " % self.series.index(p))
1112 1118 self.ui.write(p + '\n')
1113 1119
1114 1120 def prev(self, repo):
1115 1121 if len(self.applied) > 1:
1116 1122 p = self.appliedname(-2)
1117 1123 self.ui.write(p + '\n')
1118 1124 elif len(self.applied) == 1:
1119 1125 self.ui.write("Only one patch applied\n")
1120 1126 else:
1121 1127 self.ui.write("No patches applied\n")
1122 1128
1123 1129 def qimport(self, repo, files, patch=None, existing=None, force=None):
1124 1130 if len(files) > 1 and patch:
1125 1131 raise util.Abort(_('option "-n" not valid when importing multiple '
1126 1132 'files'))
1127 1133 i = 0
1128 1134 added = []
1129 1135 for filename in files:
1130 1136 if existing:
1131 1137 if not patch:
1132 1138 patch = filename
1133 1139 if not os.path.isfile(os.path.join(self.path, patch)):
1134 1140 raise util.Abort(_("patch %s does not exist") % patch)
1135 1141 else:
1136 1142 try:
1137 1143 text = file(filename).read()
1138 1144 except IOError:
1139 1145 raise util.Abort(_("unable to read %s") % patch)
1140 1146 if not patch:
1141 1147 patch = os.path.split(filename)[1]
1142 1148 if not force and os.path.exists(os.path.join(self.path, patch)):
1143 1149 raise util.Abort(_('patch "%s" already exists') % patch)
1144 1150 patchf = self.opener(patch, "w")
1145 1151 patchf.write(text)
1146 1152 if patch in self.series:
1147 1153 raise util.Abort(_('patch %s is already in the series file')
1148 1154 % patch)
1149 1155 index = self.full_series_end() + i
1150 1156 self.full_series[index:index] = [patch]
1151 1157 self.read_series(self.full_series)
1152 1158 self.ui.warn("adding %s to series file\n" % patch)
1153 1159 i += 1
1154 1160 added.append(patch)
1155 1161 patch = None
1156 1162 self.series_dirty = 1
1157 1163 qrepo = self.qrepo()
1158 1164 if qrepo:
1159 1165 qrepo.add(added)
1160 1166
1161 1167 def delete(ui, repo, patch, **opts):
1162 """remove a patch from the series file"""
1168 """remove a patch from the series file
1169
1170 The patch must not be applied.
1171 With -f, deletes the patch file as well as the series entry."""
1163 1172 q = repo.mq
1164 q.delete(repo, patch)
1173 q.delete(repo, patch, force=opts.get('force'))
1165 1174 q.save_dirty()
1166 1175 return 0
1167 1176
1168 1177 def applied(ui, repo, patch=None, **opts):
1169 1178 """print the patches already applied"""
1170 1179 repo.mq.qapplied(repo, patch)
1171 1180 return 0
1172 1181
1173 1182 def unapplied(ui, repo, patch=None, **opts):
1174 1183 """print the patches not yet applied"""
1175 1184 repo.mq.unapplied(repo, patch)
1176 1185 return 0
1177 1186
1178 1187 def qimport(ui, repo, *filename, **opts):
1179 1188 """import a patch"""
1180 1189 q = repo.mq
1181 1190 q.qimport(repo, filename, patch=opts['name'],
1182 1191 existing=opts['existing'], force=opts['force'])
1183 1192 q.save_dirty()
1184 1193 return 0
1185 1194
1186 1195 def init(ui, repo, **opts):
1187 1196 """init a new queue repository"""
1188 1197 q = repo.mq
1189 1198 r = q.init(repo, create=opts['create_repo'])
1190 1199 q.save_dirty()
1191 1200 if r:
1192 1201 fp = r.wopener('.hgignore', 'w')
1193 1202 print >> fp, 'syntax: glob'
1194 1203 print >> fp, 'status'
1195 1204 fp.close()
1196 1205 r.wopener('series', 'w').close()
1197 1206 r.add(['.hgignore', 'series'])
1198 1207 return 0
1199 1208
1200 1209 def clone(ui, source, dest=None, **opts):
1201 1210 '''clone main and patch repository at same time
1202 1211
1203 1212 If source is local, destination will have no patches applied. If
1204 1213 source is remote, this command can not check if patches are
1205 1214 applied in source, so cannot guarantee that patches are not
1206 1215 applied in destination. If you clone remote repository, be sure
1207 1216 before that it has no patches applied.
1208 1217
1209 1218 Source patch repository is looked for in <src>/.hg/patches by
1210 1219 default. Use -p <url> to change.
1211 1220 '''
1212 1221 commands.setremoteconfig(**opts)
1213 1222 if dest is None:
1214 1223 dest = hg.defaultdest(source)
1215 1224 sr = hg.repository(ui, ui.expandpath(source))
1216 1225 qbase, destrev = None, None
1217 1226 if sr.local():
1218 1227 reposetup(ui, sr)
1219 1228 if sr.mq.applied:
1220 1229 qbase = revlog.bin(sr.mq.applied[0].split(':')[0])
1221 1230 if not hg.islocal(dest):
1222 1231 destrev = sr.parents(qbase)[0]
1223 1232 ui.note(_('cloning main repo\n'))
1224 1233 sr, dr = hg.clone(ui, sr, dest,
1225 1234 pull=opts['pull'],
1226 1235 rev=destrev,
1227 1236 update=False,
1228 1237 stream=opts['uncompressed'])
1229 1238 ui.note(_('cloning patch repo\n'))
1230 1239 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1231 1240 dr.url() + '/.hg/patches',
1232 1241 pull=opts['pull'],
1233 1242 update=not opts['noupdate'],
1234 1243 stream=opts['uncompressed'])
1235 1244 if dr.local():
1236 1245 if qbase:
1237 1246 ui.note(_('stripping applied patches from destination repo\n'))
1238 1247 reposetup(ui, dr)
1239 1248 dr.mq.strip(dr, qbase, update=False, backup=None)
1240 1249 if not opts['noupdate']:
1241 1250 ui.note(_('updating destination repo\n'))
1242 1251 dr.update(dr.changelog.tip())
1243 1252
1244 1253 def commit(ui, repo, *pats, **opts):
1245 1254 """commit changes in the queue repository"""
1246 1255 q = repo.mq
1247 1256 r = q.qrepo()
1248 1257 if not r: raise util.Abort('no queue repository')
1249 1258 commands.commit(r.ui, r, *pats, **opts)
1250 1259
1251 1260 def series(ui, repo, **opts):
1252 1261 """print the entire series file"""
1253 1262 repo.mq.qseries(repo, missing=opts['missing'])
1254 1263 return 0
1255 1264
1256 1265 def top(ui, repo, **opts):
1257 1266 """print the name of the current patch"""
1258 1267 repo.mq.top(repo)
1259 1268 return 0
1260 1269
1261 1270 def next(ui, repo, **opts):
1262 1271 """print the name of the next patch"""
1263 1272 repo.mq.next(repo)
1264 1273 return 0
1265 1274
1266 1275 def prev(ui, repo, **opts):
1267 1276 """print the name of the previous patch"""
1268 1277 repo.mq.prev(repo)
1269 1278 return 0
1270 1279
1271 1280 def new(ui, repo, patch, **opts):
1272 1281 """create a new patch"""
1273 1282 q = repo.mq
1274 1283 message=commands.logmessage(**opts)
1275 1284 q.new(repo, patch, msg=message, force=opts['force'])
1276 1285 q.save_dirty()
1277 1286 return 0
1278 1287
1279 1288 def refresh(ui, repo, **opts):
1280 1289 """update the current patch"""
1281 1290 q = repo.mq
1282 1291 message=commands.logmessage(**opts)
1283 1292 if opts['edit']:
1284 1293 if message:
1285 1294 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1286 1295 patch = q.applied[-1].split(':')[1]
1287 1296 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1288 1297 message = ui.edit('\n'.join(message), user or ui.username())
1289 1298 q.refresh(repo, msg=message, short=opts['short'])
1290 1299 q.save_dirty()
1291 1300 return 0
1292 1301
1293 1302 def diff(ui, repo, *files, **opts):
1294 1303 """diff of the current patch"""
1295 1304 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1296 1305 repo.mq.diff(repo, list(files))
1297 1306 return 0
1298 1307
1299 1308 def fold(ui, repo, *files):
1300 1309 """fold the named patches into the current patch
1301 1310 Patches must not yet be applied."""
1302 1311 q = repo.mq
1303 1312
1304 1313 if not files:
1305 1314 raise util.Abort(_('qfold requires at least one patch name'))
1306 1315 if not q.check_toppatch(repo):
1307 1316 raise util.Abort(_('No patches applied\n'))
1308 1317
1309 1318 parent = q.lookup('qtip')
1310 1319 patches = []
1311 1320 messages = []
1312 1321 for f in files:
1313 1322 patch = q.lookup(f)
1314 1323 if patch in patches or patch == parent:
1315 1324 self.ui.warn(_('Skipping already folded patch %s') % patch)
1316 1325 if q.isapplied(patch):
1317 1326 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1318 1327 patches.append(patch)
1319 1328
1320 1329 for patch in patches:
1321 1330 messages.append(q.readheaders(patch)[0])
1322 1331 pf = os.path.join(q.path, patch)
1323 1332 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1324 1333 if not patchsuccess:
1325 1334 raise util.Abort(_('Error folding patch %s') % patch)
1326 1335
1327 1336 message = q.readheaders(parent)[0]
1328 1337 for msg in messages:
1329 1338 message.append('* * *')
1330 1339 message.extend(msg)
1331 1340 message = '\n'.join(message)
1332 1341
1333 1342 q.refresh(repo, msg=message)
1334 1343
1335 1344 for patch in patches:
1336 1345 q.delete(repo, patch)
1337 1346
1338 1347 q.save_dirty()
1339 1348
1340 1349 def header(ui, repo, patch=None):
1341 1350 """Print the header of the topmost or specified patch"""
1342 1351 q = repo.mq
1343 1352
1344 1353 if patch:
1345 1354 patch = q.lookup(patch)
1346 1355 else:
1347 1356 if not q.applied:
1348 1357 ui.write('No patches applied\n')
1349 1358 return
1350 1359 patch = q.lookup('qtip')
1351 1360 message = repo.mq.readheaders(patch)[0]
1352 1361
1353 1362 ui.write('\n'.join(message) + '\n')
1354 1363
1355 1364 def lastsavename(path):
1356 1365 (dir, base) = os.path.split(path)
1357 1366 names = os.listdir(dir)
1358 1367 namere = re.compile("%s.([0-9]+)" % base)
1359 1368 max = None
1360 1369 maxname = None
1361 1370 for f in names:
1362 1371 m = namere.match(f)
1363 1372 if m:
1364 1373 index = int(m.group(1))
1365 1374 if max == None or index > max:
1366 1375 max = index
1367 1376 maxname = f
1368 1377 if maxname:
1369 1378 return (os.path.join(dir, maxname), max)
1370 1379 return (None, None)
1371 1380
1372 1381 def savename(path):
1373 1382 (last, index) = lastsavename(path)
1374 1383 if last is None:
1375 1384 index = 0
1376 1385 newpath = path + ".%d" % (index + 1)
1377 1386 return newpath
1378 1387
1379 1388 def push(ui, repo, patch=None, **opts):
1380 1389 """push the next patch onto the stack"""
1381 1390 q = repo.mq
1382 1391 mergeq = None
1383 1392
1384 1393 if opts['all']:
1385 1394 patch = q.series[-1]
1386 1395 if opts['merge']:
1387 1396 if opts['name']:
1388 1397 newpath = opts['name']
1389 1398 else:
1390 1399 newpath, i = lastsavename(q.path)
1391 1400 if not newpath:
1392 1401 ui.warn("no saved queues found, please use -n\n")
1393 1402 return 1
1394 1403 mergeq = queue(ui, repo.join(""), newpath)
1395 1404 ui.warn("merging with queue at: %s\n" % mergeq.path)
1396 1405 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1397 1406 mergeq=mergeq)
1398 1407 q.save_dirty()
1399 1408 return ret
1400 1409
1401 1410 def pop(ui, repo, patch=None, **opts):
1402 1411 """pop the current patch off the stack"""
1403 1412 localupdate = True
1404 1413 if opts['name']:
1405 1414 q = queue(ui, repo.join(""), repo.join(opts['name']))
1406 1415 ui.warn('using patch queue: %s\n' % q.path)
1407 1416 localupdate = False
1408 1417 else:
1409 1418 q = repo.mq
1410 1419 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1411 1420 q.save_dirty()
1412 1421 return 0
1413 1422
1414 1423 def rename(ui, repo, patch, name=None, **opts):
1415 1424 """rename a patch
1416 1425
1417 1426 With one argument, renames the current patch to PATCH1.
1418 1427 With two arguments, renames PATCH1 to PATCH2."""
1419 1428
1420 1429 q = repo.mq
1421 1430
1422 1431 if not name:
1423 1432 name = patch
1424 1433 patch = None
1425 1434
1426 1435 if name in q.series:
1427 1436 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1428 1437
1429 1438 absdest = os.path.join(q.path, name)
1430 1439 if os.path.exists(absdest):
1431 1440 raise util.Abort(_('%s already exists') % absdest)
1432 1441
1433 1442 if patch:
1434 1443 patch = q.lookup(patch)
1435 1444 else:
1436 1445 if not q.applied:
1437 1446 ui.write(_('No patches applied\n'))
1438 1447 return
1439 1448 patch = q.lookup('qtip')
1440 1449
1441 1450 if ui.verbose:
1442 1451 ui.write('Renaming %s to %s\n' % (patch, name))
1443 1452 i = q.find_series(patch)
1444 1453 q.full_series[i] = name
1445 1454 q.read_series(q.full_series)
1446 1455 q.series_dirty = 1
1447 1456
1448 1457 info = q.isapplied(patch)
1449 1458 if info:
1450 1459 q.applied[info[0]] = info[1] + ':' + name
1451 1460 q.applied_dirty = 1
1452 1461
1453 1462 util.rename(os.path.join(q.path, patch), absdest)
1454 1463 r = q.qrepo()
1455 1464 if r:
1456 1465 wlock = r.wlock()
1457 1466 if r.dirstate.state(name) == 'r':
1458 1467 r.undelete([name], wlock)
1459 1468 r.copy(patch, name, wlock)
1460 1469 r.remove([patch], False, wlock)
1461 1470
1462 1471 q.save_dirty()
1463 1472
1464 1473 def restore(ui, repo, rev, **opts):
1465 1474 """restore the queue state saved by a rev"""
1466 1475 rev = repo.lookup(rev)
1467 1476 q = repo.mq
1468 1477 q.restore(repo, rev, delete=opts['delete'],
1469 1478 qupdate=opts['update'])
1470 1479 q.save_dirty()
1471 1480 return 0
1472 1481
1473 1482 def save(ui, repo, **opts):
1474 1483 """save current queue state"""
1475 1484 q = repo.mq
1476 1485 message=commands.logmessage(**opts)
1477 1486 ret = q.save(repo, msg=message)
1478 1487 if ret:
1479 1488 return ret
1480 1489 q.save_dirty()
1481 1490 if opts['copy']:
1482 1491 path = q.path
1483 1492 if opts['name']:
1484 1493 newpath = os.path.join(q.basepath, opts['name'])
1485 1494 if os.path.exists(newpath):
1486 1495 if not os.path.isdir(newpath):
1487 1496 raise util.Abort(_('destination %s exists and is not '
1488 1497 'a directory') % newpath)
1489 1498 if not opts['force']:
1490 1499 raise util.Abort(_('destination %s exists, '
1491 1500 'use -f to force') % newpath)
1492 1501 else:
1493 1502 newpath = savename(path)
1494 1503 ui.warn("copy %s to %s\n" % (path, newpath))
1495 1504 util.copyfiles(path, newpath)
1496 1505 if opts['empty']:
1497 1506 try:
1498 1507 os.unlink(os.path.join(q.path, q.status_path))
1499 1508 except:
1500 1509 pass
1501 1510 return 0
1502 1511
1503 1512 def strip(ui, repo, rev, **opts):
1504 1513 """strip a revision and all later revs on the same branch"""
1505 1514 rev = repo.lookup(rev)
1506 1515 backup = 'all'
1507 1516 if opts['backup']:
1508 1517 backup = 'strip'
1509 1518 elif opts['nobackup']:
1510 1519 backup = 'none'
1511 1520 repo.mq.strip(repo, rev, backup=backup)
1512 1521 return 0
1513 1522
1514 1523 def version(ui, q=None):
1515 1524 """print the version number"""
1516 1525 ui.write("mq version %s\n" % versionstr)
1517 1526 return 0
1518 1527
1519 1528 def reposetup(ui, repo):
1520 1529 class MqRepo(repo.__class__):
1521 1530 def tags(self):
1522 1531 if self.tagscache:
1523 1532 return self.tagscache
1524 1533
1525 1534 tagscache = super(MqRepo, self).tags()
1526 1535
1527 1536 q = self.mq
1528 1537 if not q.applied:
1529 1538 return tagscache
1530 1539
1531 1540 mqtags = [patch.split(':') for patch in q.applied]
1532 1541 mqtags.append((mqtags[-1][0], 'qtip'))
1533 1542 mqtags.append((mqtags[0][0], 'qbase'))
1534 1543 for patch in mqtags:
1535 1544 if patch[1] in tagscache:
1536 1545 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1537 1546 else:
1538 1547 tagscache[patch[1]] = revlog.bin(patch[0])
1539 1548
1540 1549 return tagscache
1541 1550
1542 1551 repo.__class__ = MqRepo
1543 1552 repo.mq = queue(ui, repo.join(""))
1544 1553
1545 1554 cmdtable = {
1546 1555 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1547 1556 "qclone": (clone,
1548 1557 [('', 'pull', None, _('use pull protocol to copy metadata')),
1549 1558 ('U', 'noupdate', None, _('do not update the new working directories')),
1550 1559 ('', 'uncompressed', None,
1551 1560 _('use uncompressed transfer (fast over LAN)')),
1552 1561 ('e', 'ssh', '', _('specify ssh command to use')),
1553 1562 ('p', 'patches', '', _('location of source patch repo')),
1554 1563 ('', 'remotecmd', '',
1555 1564 _('specify hg command to run on the remote side'))],
1556 1565 'hg qclone [OPTION]... SOURCE [DEST]'),
1557 1566 "qcommit|qci":
1558 1567 (commit,
1559 1568 commands.table["^commit|ci"][1],
1560 1569 'hg qcommit [OPTION]... [FILE]...'),
1561 1570 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1562 "qdelete": (delete, [], 'hg qdelete PATCH'),
1571 "qdelete":
1572 (delete,
1573 [('f', 'force', None, _('delete patch file'))],
1574 'hg qdelete [-f] PATCH'),
1563 1575 'qfold': (fold, [], 'hg qfold PATCH...'),
1564 1576 'qheader': (header, [],
1565 1577 _('hg qheader [PATCH]')),
1566 1578 "^qimport":
1567 1579 (qimport,
1568 1580 [('e', 'existing', None, 'import file in patch dir'),
1569 1581 ('n', 'name', '', 'patch file name'),
1570 1582 ('f', 'force', None, 'overwrite existing files')],
1571 1583 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1572 1584 "^qinit":
1573 1585 (init,
1574 1586 [('c', 'create-repo', None, 'create queue repository')],
1575 1587 'hg qinit [-c]'),
1576 1588 "qnew":
1577 1589 (new,
1578 1590 [('m', 'message', '', _('use <text> as commit message')),
1579 1591 ('l', 'logfile', '', _('read the commit message from <file>')),
1580 1592 ('f', 'force', None, 'force')],
1581 1593 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1582 1594 "qnext": (next, [], 'hg qnext'),
1583 1595 "qprev": (prev, [], 'hg qprev'),
1584 1596 "^qpop":
1585 1597 (pop,
1586 1598 [('a', 'all', None, 'pop all patches'),
1587 1599 ('n', 'name', '', 'queue name to pop'),
1588 1600 ('f', 'force', None, 'forget any local changes')],
1589 1601 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1590 1602 "^qpush":
1591 1603 (push,
1592 1604 [('f', 'force', None, 'apply if the patch has rejects'),
1593 1605 ('l', 'list', None, 'list patch name in commit text'),
1594 1606 ('a', 'all', None, 'apply all patches'),
1595 1607 ('m', 'merge', None, 'merge from another queue'),
1596 1608 ('n', 'name', '', 'merge queue name')],
1597 1609 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1598 1610 "^qrefresh":
1599 1611 (refresh,
1600 1612 [('e', 'edit', None, _('edit commit message')),
1601 1613 ('m', 'message', '', _('change commit message with <text>')),
1602 1614 ('l', 'logfile', '', _('change commit message with <file> content')),
1603 1615 ('s', 'short', None, 'short refresh')],
1604 1616 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1605 1617 'qrename|qmv':
1606 1618 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1607 1619 "qrestore":
1608 1620 (restore,
1609 1621 [('d', 'delete', None, 'delete save entry'),
1610 1622 ('u', 'update', None, 'update queue working dir')],
1611 1623 'hg qrestore [-d] [-u] REV'),
1612 1624 "qsave":
1613 1625 (save,
1614 1626 [('m', 'message', '', _('use <text> as commit message')),
1615 1627 ('l', 'logfile', '', _('read the commit message from <file>')),
1616 1628 ('c', 'copy', None, 'copy patch directory'),
1617 1629 ('n', 'name', '', 'copy directory name'),
1618 1630 ('e', 'empty', None, 'clear queue status file'),
1619 1631 ('f', 'force', None, 'force copy')],
1620 1632 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1621 1633 "qseries":
1622 1634 (series,
1623 1635 [('m', 'missing', None, 'print patches not in series')],
1624 1636 'hg qseries [-m]'),
1625 1637 "^strip":
1626 1638 (strip,
1627 1639 [('f', 'force', None, 'force multi-head removal'),
1628 1640 ('b', 'backup', None, 'bundle unrelated changesets'),
1629 1641 ('n', 'nobackup', None, 'no backups')],
1630 1642 'hg strip [-f] [-b] [-n] REV'),
1631 1643 "qtop": (top, [], 'hg qtop'),
1632 1644 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1633 1645 "qversion": (version, [], 'hg qversion')
1634 1646 }
1635 1647
General Comments 0
You need to be logged in to leave comments. Login now