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