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