##// END OF EJS Templates
Change patch header as well as commit message with qrefresh -m or -l.
Brendan Cully -
r2745:1bac2bfe default
parent child Browse files
Show More
@@ -1,1500 +1,1510
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.demandload import *
33 33 demandload(globals(), "os sys re struct traceback errno bz2")
34 34 from mercurial.i18n import gettext as _
35 35 from mercurial import ui, hg, revlog, commands, util
36 36
37 37 versionstr = "0.45"
38 38
39 39 commands.norepo += " qclone qversion"
40 40
41 41 class queue:
42 42 def __init__(self, ui, path, patchdir=None):
43 43 self.basepath = path
44 44 if patchdir:
45 45 self.path = patchdir
46 46 else:
47 47 self.path = os.path.join(path, "patches")
48 48 self.opener = util.opener(self.path)
49 49 self.ui = ui
50 50 self.applied = []
51 51 self.full_series = []
52 52 self.applied_dirty = 0
53 53 self.series_dirty = 0
54 54 self.series_path = "series"
55 55 self.status_path = "status"
56 56
57 57 if os.path.exists(os.path.join(self.path, self.series_path)):
58 58 self.full_series = self.opener(self.series_path).read().splitlines()
59 59 self.read_series(self.full_series)
60 60
61 61 if os.path.exists(os.path.join(self.path, self.status_path)):
62 62 self.applied = self.opener(self.status_path).read().splitlines()
63 63
64 64 def find_series(self, patch):
65 65 pre = re.compile("(\s*)([^#]+)")
66 66 index = 0
67 67 for l in self.full_series:
68 68 m = pre.match(l)
69 69 if m:
70 70 s = m.group(2)
71 71 s = s.rstrip()
72 72 if s == patch:
73 73 return index
74 74 index += 1
75 75 return None
76 76
77 77 def read_series(self, list):
78 78 def matcher(list):
79 79 pre = re.compile("(\s*)([^#]+)")
80 80 for l in list:
81 81 m = pre.match(l)
82 82 if m:
83 83 s = m.group(2)
84 84 s = s.rstrip()
85 85 if len(s) > 0:
86 86 yield s
87 87 self.series = []
88 88 self.series = [ x for x in matcher(list) ]
89 89
90 90 def save_dirty(self):
91 91 if self.applied_dirty:
92 92 if len(self.applied) > 0:
93 93 nl = "\n"
94 94 else:
95 95 nl = ""
96 96 f = self.opener(self.status_path, "w")
97 97 f.write("\n".join(self.applied) + nl)
98 98 if self.series_dirty:
99 99 if len(self.full_series) > 0:
100 100 nl = "\n"
101 101 else:
102 102 nl = ""
103 103 f = self.opener(self.series_path, "w")
104 104 f.write("\n".join(self.full_series) + nl)
105 105
106 106 def readheaders(self, patch):
107 107 def eatdiff(lines):
108 108 while lines:
109 109 l = lines[-1]
110 110 if (l.startswith("diff -") or
111 111 l.startswith("Index:") or
112 112 l.startswith("===========")):
113 113 del lines[-1]
114 114 else:
115 115 break
116 116 def eatempty(lines):
117 117 while lines:
118 118 l = lines[-1]
119 119 if re.match('\s*$', l):
120 120 del lines[-1]
121 121 else:
122 122 break
123 123
124 124 pf = os.path.join(self.path, patch)
125 125 message = []
126 126 comments = []
127 127 user = None
128 128 date = None
129 129 format = None
130 130 subject = None
131 131 diffstart = 0
132 132
133 133 for line in file(pf):
134 134 line = line.rstrip()
135 135 if diffstart:
136 136 if line.startswith('+++ '):
137 137 diffstart = 2
138 138 break
139 139 if line.startswith("--- "):
140 140 diffstart = 1
141 141 continue
142 142 elif format == "hgpatch":
143 143 # parse values when importing the result of an hg export
144 144 if line.startswith("# User "):
145 145 user = line[7:]
146 146 elif line.startswith("# Date "):
147 147 date = line[7:]
148 148 elif not line.startswith("# ") and line:
149 149 message.append(line)
150 150 format = None
151 151 elif line == '# HG changeset patch':
152 152 format = "hgpatch"
153 153 elif (format != "tagdone" and (line.startswith("Subject: ") or
154 154 line.startswith("subject: "))):
155 155 subject = line[9:]
156 156 format = "tag"
157 157 elif (format != "tagdone" and (line.startswith("From: ") or
158 158 line.startswith("from: "))):
159 159 user = line[6:]
160 160 format = "tag"
161 161 elif format == "tag" and line == "":
162 162 # when looking for tags (subject: from: etc) they
163 163 # end once you find a blank line in the source
164 164 format = "tagdone"
165 165 elif message or line:
166 166 message.append(line)
167 167 comments.append(line)
168 168
169 169 eatdiff(message)
170 170 eatdiff(comments)
171 171 eatempty(message)
172 172 eatempty(comments)
173 173
174 174 # make sure message isn't empty
175 175 if format and format.startswith("tag") and subject:
176 176 message.insert(0, "")
177 177 message.insert(0, subject)
178 178 return (message, comments, user, date, diffstart > 1)
179 179
180 180 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
181 181 # first try just applying the patch
182 182 (err, n) = self.apply(repo, [ patch ], update_status=False,
183 183 strict=True, merge=rev, wlock=wlock)
184 184
185 185 if err == 0:
186 186 return (err, n)
187 187
188 188 if n is None:
189 189 raise util.Abort(_("apply failed for patch %s") % patch)
190 190
191 191 self.ui.warn("patch didn't work out, merging %s\n" % patch)
192 192
193 193 # apply failed, strip away that rev and merge.
194 194 repo.update(head, allow=False, force=True, wlock=wlock)
195 195 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
196 196
197 197 c = repo.changelog.read(rev)
198 198 ret = repo.update(rev, allow=True, wlock=wlock)
199 199 if ret:
200 200 raise util.Abort(_("update returned %d") % ret)
201 201 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
202 202 if n == None:
203 203 raise util.Abort(_("repo commit failed"))
204 204 try:
205 205 message, comments, user, date, patchfound = mergeq.readheaders(patch)
206 206 except:
207 207 raise util.Abort(_("unable to read %s") % patch)
208 208
209 209 patchf = self.opener(patch, "w")
210 210 if comments:
211 211 comments = "\n".join(comments) + '\n\n'
212 212 patchf.write(comments)
213 213 commands.dodiff(patchf, self.ui, repo, head, n)
214 214 patchf.close()
215 215 return (0, n)
216 216
217 217 def qparents(self, repo, rev=None):
218 218 if rev is None:
219 219 (p1, p2) = repo.dirstate.parents()
220 220 if p2 == revlog.nullid:
221 221 return p1
222 222 if len(self.applied) == 0:
223 223 return None
224 224 (top, patch) = self.applied[-1].split(':')
225 225 top = revlog.bin(top)
226 226 return top
227 227 pp = repo.changelog.parents(rev)
228 228 if pp[1] != revlog.nullid:
229 229 arevs = [ x.split(':')[0] for x in self.applied ]
230 230 p0 = revlog.hex(pp[0])
231 231 p1 = revlog.hex(pp[1])
232 232 if p0 in arevs:
233 233 return pp[0]
234 234 if p1 in arevs:
235 235 return pp[1]
236 236 return pp[0]
237 237
238 238 def mergepatch(self, repo, mergeq, series, wlock):
239 239 if len(self.applied) == 0:
240 240 # each of the patches merged in will have two parents. This
241 241 # can confuse the qrefresh, qdiff, and strip code because it
242 242 # needs to know which parent is actually in the patch queue.
243 243 # so, we insert a merge marker with only one parent. This way
244 244 # the first patch in the queue is never a merge patch
245 245 #
246 246 pname = ".hg.patches.merge.marker"
247 247 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
248 248 wlock=wlock)
249 249 self.applied.append(revlog.hex(n) + ":" + pname)
250 250 self.applied_dirty = 1
251 251
252 252 head = self.qparents(repo)
253 253
254 254 for patch in series:
255 255 patch = mergeq.lookup(patch, strict=True)
256 256 if not patch:
257 257 self.ui.warn("patch %s does not exist\n" % patch)
258 258 return (1, None)
259 259
260 260 info = mergeq.isapplied(patch)
261 261 if not info:
262 262 self.ui.warn("patch %s is not applied\n" % patch)
263 263 return (1, None)
264 264 rev = revlog.bin(info[1])
265 265 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
266 266 if head:
267 267 self.applied.append(revlog.hex(head) + ":" + patch)
268 268 self.applied_dirty = 1
269 269 if err:
270 270 return (err, head)
271 271 return (0, head)
272 272
273 273 def apply(self, repo, series, list=False, update_status=True,
274 274 strict=False, patchdir=None, merge=None, wlock=None):
275 275 # TODO unify with commands.py
276 276 if not patchdir:
277 277 patchdir = self.path
278 278 err = 0
279 279 if not wlock:
280 280 wlock = repo.wlock()
281 281 lock = repo.lock()
282 282 tr = repo.transaction()
283 283 n = None
284 284 for patch in series:
285 285 self.ui.warn("applying %s\n" % patch)
286 286 pf = os.path.join(patchdir, patch)
287 287
288 288 try:
289 289 message, comments, user, date, patchfound = self.readheaders(patch)
290 290 except:
291 291 self.ui.warn("Unable to read %s\n" % pf)
292 292 err = 1
293 293 break
294 294
295 295 if not message:
296 296 message = "imported patch %s\n" % patch
297 297 else:
298 298 if list:
299 299 message.append("\nimported patch %s" % patch)
300 300 message = '\n'.join(message)
301 301
302 302 try:
303 303 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
304 304 f = os.popen("%s -d '%s' -p1 --no-backup-if-mismatch < '%s'" %
305 305 (pp, repo.root, pf))
306 306 except:
307 307 self.ui.warn("patch failed, unable to continue (try -v)\n")
308 308 err = 1
309 309 break
310 310 files = []
311 311 fuzz = False
312 312 for l in f:
313 313 l = l.rstrip('\r\n');
314 314 if self.ui.verbose:
315 315 self.ui.warn(l + "\n")
316 316 if l[:14] == 'patching file ':
317 317 pf = os.path.normpath(l[14:])
318 318 # when patch finds a space in the file name, it puts
319 319 # single quotes around the filename. strip them off
320 320 if pf[0] == "'" and pf[-1] == "'":
321 321 pf = pf[1:-1]
322 322 if pf not in files:
323 323 files.append(pf)
324 324 printed_file = False
325 325 file_str = l
326 326 elif l.find('with fuzz') >= 0:
327 327 if not printed_file:
328 328 self.ui.warn(file_str + '\n')
329 329 printed_file = True
330 330 self.ui.warn(l + '\n')
331 331 fuzz = True
332 332 elif l.find('saving rejects to file') >= 0:
333 333 self.ui.warn(l + '\n')
334 334 elif l.find('FAILED') >= 0:
335 335 if not printed_file:
336 336 self.ui.warn(file_str + '\n')
337 337 printed_file = True
338 338 self.ui.warn(l + '\n')
339 339 patcherr = f.close()
340 340
341 341 if merge and len(files) > 0:
342 342 # Mark as merged and update dirstate parent info
343 343 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
344 344 p1, p2 = repo.dirstate.parents()
345 345 repo.dirstate.setparents(p1, merge)
346 346 if len(files) > 0:
347 347 cwd = repo.getcwd()
348 348 cfiles = files
349 349 if cwd:
350 350 cfiles = [util.pathto(cwd, f) for f in files]
351 351 commands.addremove_lock(self.ui, repo, cfiles,
352 352 opts={}, wlock=wlock)
353 353 n = repo.commit(files, message, user, date, force=1, lock=lock,
354 354 wlock=wlock)
355 355
356 356 if n == None:
357 357 raise util.Abort(_("repo commit failed"))
358 358
359 359 if update_status:
360 360 self.applied.append(revlog.hex(n) + ":" + patch)
361 361
362 362 if patcherr:
363 363 if not patchfound:
364 364 self.ui.warn("patch %s is empty\n" % patch)
365 365 err = 0
366 366 else:
367 367 self.ui.warn("patch failed, rejects left in working dir\n")
368 368 err = 1
369 369 break
370 370
371 371 if fuzz and strict:
372 372 self.ui.warn("fuzz found when applying patch, stopping\n")
373 373 err = 1
374 374 break
375 375 tr.close()
376 376 return (err, n)
377 377
378 378 def delete(self, repo, patch):
379 379 patch = self.lookup(patch, strict=True)
380 380 info = self.isapplied(patch)
381 381 if info:
382 382 raise util.Abort(_("cannot delete applied patch %s") % patch)
383 383 if patch not in self.series:
384 384 raise util.Abort(_("patch %s not in series file") % patch)
385 385 i = self.find_series(patch)
386 386 del self.full_series[i]
387 387 self.read_series(self.full_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.read_series(self.full_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 count = 0
605 605 if s in self.series:
606 606 return s
607 607 for x in self.series:
608 608 if s in x:
609 609 count += 1
610 610 last = x
611 611 if count > 1:
612 612 return None
613 613 if count:
614 614 return last
615 615 if len(self.series) > 0 and len(self.applied) > 0:
616 616 if s == 'qtip':
617 617 return self.series[self.series_end()-1]
618 618 if s == 'qbase':
619 619 return self.series[0]
620 620 return None
621 621 if patch == None:
622 622 return None
623 623
624 624 # we don't want to return a partial match until we make
625 625 # sure the file name passed in does not exist (checked below)
626 626 res = partial_name(patch)
627 627 if res and res == patch:
628 628 return res
629 629
630 630 if not os.path.isfile(os.path.join(self.path, patch)):
631 631 try:
632 632 sno = int(patch)
633 633 except(ValueError, OverflowError):
634 634 pass
635 635 else:
636 636 if sno < len(self.series):
637 637 patch = self.series[sno]
638 638 return patch
639 639 if not strict:
640 640 # return any partial match made above
641 641 if res:
642 642 return res
643 643 minus = patch.rsplit('-', 1)
644 644 if len(minus) > 1:
645 645 res = partial_name(minus[0])
646 646 if res:
647 647 i = self.series.index(res)
648 648 try:
649 649 off = int(minus[1] or 1)
650 650 except(ValueError, OverflowError):
651 651 pass
652 652 else:
653 653 if i - off >= 0:
654 654 return self.series[i - off]
655 655 plus = patch.rsplit('+', 1)
656 656 if len(plus) > 1:
657 657 res = partial_name(plus[0])
658 658 if res:
659 659 i = self.series.index(res)
660 660 try:
661 661 off = int(plus[1] or 1)
662 662 except(ValueError, OverflowError):
663 663 pass
664 664 else:
665 665 if i + off < len(self.series):
666 666 return self.series[i + off]
667 667 raise util.Abort(_("patch %s not in series") % patch)
668 668
669 669 def push(self, repo, patch=None, force=False, list=False,
670 670 mergeq=None, wlock=None):
671 671 if not wlock:
672 672 wlock = repo.wlock()
673 673 patch = self.lookup(patch)
674 674 if patch and self.isapplied(patch):
675 675 self.ui.warn(_("patch %s is already applied\n") % patch)
676 676 sys.exit(1)
677 677 if self.series_end() == len(self.series):
678 678 self.ui.warn(_("patch series fully applied\n"))
679 679 sys.exit(1)
680 680 if not force:
681 681 self.check_localchanges(repo)
682 682
683 683 self.applied_dirty = 1;
684 684 start = self.series_end()
685 685 if start > 0:
686 686 self.check_toppatch(repo)
687 687 if not patch:
688 688 patch = self.series[start]
689 689 end = start + 1
690 690 else:
691 691 end = self.series.index(patch, start) + 1
692 692 s = self.series[start:end]
693 693 if mergeq:
694 694 ret = self.mergepatch(repo, mergeq, s, wlock)
695 695 else:
696 696 ret = self.apply(repo, s, list, wlock=wlock)
697 697 top = self.applied[-1].split(':')[1]
698 698 if ret[0]:
699 699 self.ui.write("Errors during apply, please fix and refresh %s\n" %
700 700 top)
701 701 else:
702 702 self.ui.write("Now at: %s\n" % top)
703 703 return ret[0]
704 704
705 705 def pop(self, repo, patch=None, force=False, update=True, all=False,
706 706 wlock=None):
707 707 def getfile(f, rev):
708 708 t = repo.file(f).read(rev)
709 709 try:
710 710 repo.wfile(f, "w").write(t)
711 711 except IOError:
712 712 try:
713 713 os.makedirs(os.path.dirname(repo.wjoin(f)))
714 714 except OSError, err:
715 715 if err.errno != errno.EEXIST: raise
716 716 repo.wfile(f, "w").write(t)
717 717
718 718 if not wlock:
719 719 wlock = repo.wlock()
720 720 if patch:
721 721 # index, rev, patch
722 722 info = self.isapplied(patch)
723 723 if not info:
724 724 patch = self.lookup(patch)
725 725 info = self.isapplied(patch)
726 726 if not info:
727 727 raise util.Abort(_("patch %s is not applied") % patch)
728 728 if len(self.applied) == 0:
729 729 self.ui.warn(_("no patches applied\n"))
730 730 sys.exit(1)
731 731
732 732 if not update:
733 733 parents = repo.dirstate.parents()
734 734 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
735 735 for p in parents:
736 736 if p in rr:
737 737 self.ui.warn("qpop: forcing dirstate update\n")
738 738 update = True
739 739
740 740 if not force and update:
741 741 self.check_localchanges(repo)
742 742
743 743 self.applied_dirty = 1;
744 744 end = len(self.applied)
745 745 if not patch:
746 746 if all:
747 747 popi = 0
748 748 else:
749 749 popi = len(self.applied) - 1
750 750 else:
751 751 popi = info[0] + 1
752 752 if popi >= end:
753 753 self.ui.warn("qpop: %s is already at the top\n" % patch)
754 754 return
755 755 info = [ popi ] + self.applied[popi].split(':')
756 756
757 757 start = info[0]
758 758 rev = revlog.bin(info[1])
759 759
760 760 # we know there are no local changes, so we can make a simplified
761 761 # form of hg.update.
762 762 if update:
763 763 top = self.check_toppatch(repo)
764 764 qp = self.qparents(repo, rev)
765 765 changes = repo.changelog.read(qp)
766 766 mf1 = repo.manifest.readflags(changes[0])
767 767 mmap = repo.manifest.read(changes[0])
768 768 (c, a, r, d, u) = repo.changes(qp, top)
769 769 if d:
770 770 raise util.Abort("deletions found between repo revs")
771 771 for f in c:
772 772 getfile(f, mmap[f])
773 773 for f in r:
774 774 getfile(f, mmap[f])
775 775 util.set_exec(repo.wjoin(f), mf1[f])
776 776 repo.dirstate.update(c + r, 'n')
777 777 for f in a:
778 778 try: os.unlink(repo.wjoin(f))
779 779 except: raise
780 780 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
781 781 except: pass
782 782 if a:
783 783 repo.dirstate.forget(a)
784 784 repo.dirstate.setparents(qp, revlog.nullid)
785 785 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
786 786 del self.applied[start:end]
787 787 if len(self.applied):
788 788 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
789 789 else:
790 790 self.ui.write("Patch queue now empty\n")
791 791
792 792 def diff(self, repo, files):
793 793 top = self.check_toppatch(repo)
794 794 if not top:
795 795 self.ui.write("No patches applied\n")
796 796 return
797 797 qp = self.qparents(repo, top)
798 798 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
799 799
800 800 def refresh(self, repo, msg=None, short=False):
801 801 if len(self.applied) == 0:
802 802 self.ui.write("No patches applied\n")
803 803 return
804 804 wlock = repo.wlock()
805 805 self.check_toppatch(repo)
806 806 qp = self.qparents(repo)
807 807 (top, patch) = self.applied[-1].split(':')
808 808 top = revlog.bin(top)
809 809 cparents = repo.changelog.parents(top)
810 810 patchparent = self.qparents(repo, top)
811 811 message, comments, user, date, patchfound = self.readheaders(patch)
812 812
813 813 patchf = self.opener(patch, "w")
814 msg = msg.rstrip()
815 if msg:
816 if comments:
817 # Remove existing message.
818 ci = 0
819 for mi in range(len(message)):
820 while message[mi] != comments[ci]:
821 ci += 1
822 del comments[ci]
823 comments.append(msg)
814 824 if comments:
815 825 comments = "\n".join(comments) + '\n\n'
816 826 patchf.write(comments)
817 827
818 828 tip = repo.changelog.tip()
819 829 if top == tip:
820 830 # if the top of our patch queue is also the tip, there is an
821 831 # optimization here. We update the dirstate in place and strip
822 832 # off the tip commit. Then just commit the current directory
823 833 # tree. We can also send repo.commit the list of files
824 834 # changed to speed up the diff
825 835 #
826 836 # in short mode, we only diff the files included in the
827 837 # patch already
828 838 #
829 839 # this should really read:
830 840 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
831 841 # but we do it backwards to take advantage of manifest/chlog
832 842 # caching against the next repo.changes call
833 843 #
834 844 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
835 845 if short:
836 846 filelist = cc + aa + dd
837 847 else:
838 848 filelist = None
839 849 (c, a, r, d, u) = repo.changes(None, None, filelist)
840 850
841 851 # we might end up with files that were added between tip and
842 852 # the dirstate parent, but then changed in the local dirstate.
843 853 # in this case, we want them to only show up in the added section
844 854 for x in c:
845 855 if x not in aa:
846 856 cc.append(x)
847 857 # we might end up with files added by the local dirstate that
848 858 # were deleted by the patch. In this case, they should only
849 859 # show up in the changed section.
850 860 for x in a:
851 861 if x in dd:
852 862 del dd[dd.index(x)]
853 863 cc.append(x)
854 864 else:
855 865 aa.append(x)
856 866 # make sure any files deleted in the local dirstate
857 867 # are not in the add or change column of the patch
858 868 forget = []
859 869 for x in d + r:
860 870 if x in aa:
861 871 del aa[aa.index(x)]
862 872 forget.append(x)
863 873 continue
864 874 elif x in cc:
865 875 del cc[cc.index(x)]
866 876 dd.append(x)
867 877
868 878 c = list(util.unique(cc))
869 879 r = list(util.unique(dd))
870 880 a = list(util.unique(aa))
871 881 filelist = list(util.unique(c + r + a ))
872 882 commands.dodiff(patchf, self.ui, repo, patchparent, None,
873 883 filelist, changes=(c, a, r, [], u))
874 884 patchf.close()
875 885
876 886 changes = repo.changelog.read(tip)
877 887 repo.dirstate.setparents(*cparents)
878 888 repo.dirstate.update(a, 'a')
879 889 repo.dirstate.update(r, 'r')
880 890 repo.dirstate.update(c, 'n')
881 891 repo.dirstate.forget(forget)
882 892
883 893 if not msg:
884 894 if not message:
885 895 message = "patch queue: %s\n" % patch
886 896 else:
887 897 message = "\n".join(message)
888 898 else:
889 899 message = msg
890 900
891 901 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
892 902 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
893 903 self.applied[-1] = revlog.hex(n) + ':' + patch
894 904 self.applied_dirty = 1
895 905 else:
896 906 commands.dodiff(patchf, self.ui, repo, patchparent, None)
897 907 patchf.close()
898 908 self.pop(repo, force=True, wlock=wlock)
899 909 self.push(repo, force=True, wlock=wlock)
900 910
901 911 def init(self, repo, create=False):
902 912 if os.path.isdir(self.path):
903 913 raise util.Abort(_("patch queue directory already exists"))
904 914 os.mkdir(self.path)
905 915 if create:
906 916 return self.qrepo(create=True)
907 917
908 918 def unapplied(self, repo, patch=None):
909 919 if patch and patch not in self.series:
910 920 raise util.Abort(_("patch %s is not in series file") % patch)
911 921 if not patch:
912 922 start = self.series_end()
913 923 else:
914 924 start = self.series.index(patch) + 1
915 925 for p in self.series[start:]:
916 926 if self.ui.verbose:
917 927 self.ui.write("%d " % self.series.index(p))
918 928 self.ui.write("%s\n" % p)
919 929
920 930 def qseries(self, repo, missing=None):
921 931 start = self.series_end()
922 932 if not missing:
923 933 for p in self.series[:start]:
924 934 if self.ui.verbose:
925 935 self.ui.write("%d A " % self.series.index(p))
926 936 self.ui.write("%s\n" % p)
927 937 for p in self.series[start:]:
928 938 if self.ui.verbose:
929 939 self.ui.write("%d U " % self.series.index(p))
930 940 self.ui.write("%s\n" % p)
931 941 else:
932 942 list = []
933 943 for root, dirs, files in os.walk(self.path):
934 944 d = root[len(self.path) + 1:]
935 945 for f in files:
936 946 fl = os.path.join(d, f)
937 947 if (fl not in self.series and
938 948 fl not in (self.status_path, self.series_path)
939 949 and not fl.startswith('.')):
940 950 list.append(fl)
941 951 list.sort()
942 952 if list:
943 953 for x in list:
944 954 if self.ui.verbose:
945 955 self.ui.write("D ")
946 956 self.ui.write("%s\n" % x)
947 957
948 958 def issaveline(self, l):
949 959 name = l.split(':')[1]
950 960 if name == '.hg.patches.save.line':
951 961 return True
952 962
953 963 def qrepo(self, create=False):
954 964 if create or os.path.isdir(os.path.join(self.path, ".hg")):
955 965 return hg.repository(self.ui, path=self.path, create=create)
956 966
957 967 def restore(self, repo, rev, delete=None, qupdate=None):
958 968 c = repo.changelog.read(rev)
959 969 desc = c[4].strip()
960 970 lines = desc.splitlines()
961 971 i = 0
962 972 datastart = None
963 973 series = []
964 974 applied = []
965 975 qpp = None
966 976 for i in xrange(0, len(lines)):
967 977 if lines[i] == 'Patch Data:':
968 978 datastart = i + 1
969 979 elif lines[i].startswith('Dirstate:'):
970 980 l = lines[i].rstrip()
971 981 l = l[10:].split(' ')
972 982 qpp = [ hg.bin(x) for x in l ]
973 983 elif datastart != None:
974 984 l = lines[i].rstrip()
975 985 index = l.index(':')
976 986 id = l[:index]
977 987 file = l[index + 1:]
978 988 if id:
979 989 applied.append(l)
980 990 series.append(file)
981 991 if datastart == None:
982 992 self.ui.warn("No saved patch data found\n")
983 993 return 1
984 994 self.ui.warn("restoring status: %s\n" % lines[0])
985 995 self.full_series = series
986 996 self.applied = applied
987 997 self.read_series(self.full_series)
988 998 self.series_dirty = 1
989 999 self.applied_dirty = 1
990 1000 heads = repo.changelog.heads()
991 1001 if delete:
992 1002 if rev not in heads:
993 1003 self.ui.warn("save entry has children, leaving it alone\n")
994 1004 else:
995 1005 self.ui.warn("removing save entry %s\n" % hg.short(rev))
996 1006 pp = repo.dirstate.parents()
997 1007 if rev in pp:
998 1008 update = True
999 1009 else:
1000 1010 update = False
1001 1011 self.strip(repo, rev, update=update, backup='strip')
1002 1012 if qpp:
1003 1013 self.ui.warn("saved queue repository parents: %s %s\n" %
1004 1014 (hg.short(qpp[0]), hg.short(qpp[1])))
1005 1015 if qupdate:
1006 1016 print "queue directory updating"
1007 1017 r = self.qrepo()
1008 1018 if not r:
1009 1019 self.ui.warn("Unable to load queue repository\n")
1010 1020 return 1
1011 1021 r.update(qpp[0], allow=False, force=True)
1012 1022
1013 1023 def save(self, repo, msg=None):
1014 1024 if len(self.applied) == 0:
1015 1025 self.ui.warn("save: no patches applied, exiting\n")
1016 1026 return 1
1017 1027 if self.issaveline(self.applied[-1]):
1018 1028 self.ui.warn("status is already saved\n")
1019 1029 return 1
1020 1030
1021 1031 ar = [ ':' + x for x in self.full_series ]
1022 1032 if not msg:
1023 1033 msg = "hg patches saved state"
1024 1034 else:
1025 1035 msg = "hg patches: " + msg.rstrip('\r\n')
1026 1036 r = self.qrepo()
1027 1037 if r:
1028 1038 pp = r.dirstate.parents()
1029 1039 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1030 1040 msg += "\n\nPatch Data:\n"
1031 1041 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1032 1042 + '\n' or "")
1033 1043 n = repo.commit(None, text, user=None, force=1)
1034 1044 if not n:
1035 1045 self.ui.warn("repo commit failed\n")
1036 1046 return 1
1037 1047 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1038 1048 self.applied_dirty = 1
1039 1049
1040 1050 def full_series_end(self):
1041 1051 if len(self.applied) > 0:
1042 1052 (top, p) = self.applied[-1].split(':')
1043 1053 end = self.find_series(p)
1044 1054 if end == None:
1045 1055 return len(self.full_series)
1046 1056 return end + 1
1047 1057 return 0
1048 1058
1049 1059 def series_end(self):
1050 1060 end = 0
1051 1061 if len(self.applied) > 0:
1052 1062 (top, p) = self.applied[-1].split(':')
1053 1063 try:
1054 1064 end = self.series.index(p)
1055 1065 except ValueError:
1056 1066 return 0
1057 1067 return end + 1
1058 1068 return end
1059 1069
1060 1070 def qapplied(self, repo, patch=None):
1061 1071 if patch and patch not in self.series:
1062 1072 raise util.Abort(_("patch %s is not in series file") % patch)
1063 1073 if not patch:
1064 1074 end = len(self.applied)
1065 1075 else:
1066 1076 end = self.series.index(patch) + 1
1067 1077 for x in xrange(end):
1068 1078 p = self.appliedname(x)
1069 1079 self.ui.write("%s\n" % p)
1070 1080
1071 1081 def appliedname(self, index):
1072 1082 p = self.applied[index]
1073 1083 pname = p.split(':')[1]
1074 1084 if not self.ui.verbose:
1075 1085 p = pname
1076 1086 else:
1077 1087 p = str(self.series.index(pname)) + " " + p
1078 1088 return p
1079 1089
1080 1090 def top(self, repo):
1081 1091 if len(self.applied):
1082 1092 p = self.appliedname(-1)
1083 1093 self.ui.write(p + '\n')
1084 1094 else:
1085 1095 self.ui.write("No patches applied\n")
1086 1096
1087 1097 def next(self, repo):
1088 1098 end = self.series_end()
1089 1099 if end == len(self.series):
1090 1100 self.ui.write("All patches applied\n")
1091 1101 else:
1092 1102 p = self.series[end]
1093 1103 if self.ui.verbose:
1094 1104 self.ui.write("%d " % self.series.index(p))
1095 1105 self.ui.write(p + '\n')
1096 1106
1097 1107 def prev(self, repo):
1098 1108 if len(self.applied) > 1:
1099 1109 p = self.appliedname(-2)
1100 1110 self.ui.write(p + '\n')
1101 1111 elif len(self.applied) == 1:
1102 1112 self.ui.write("Only one patch applied\n")
1103 1113 else:
1104 1114 self.ui.write("No patches applied\n")
1105 1115
1106 1116 def qimport(self, repo, files, patch=None, existing=None, force=None):
1107 1117 if len(files) > 1 and patch:
1108 1118 raise util.Abort(_('option "-n" not valid when importing multiple '
1109 1119 'files'))
1110 1120 i = 0
1111 1121 added = []
1112 1122 for filename in files:
1113 1123 if existing:
1114 1124 if not patch:
1115 1125 patch = filename
1116 1126 if not os.path.isfile(os.path.join(self.path, patch)):
1117 1127 raise util.Abort(_("patch %s does not exist") % patch)
1118 1128 else:
1119 1129 try:
1120 1130 text = file(filename).read()
1121 1131 except IOError:
1122 1132 raise util.Abort(_("unable to read %s") % patch)
1123 1133 if not patch:
1124 1134 patch = os.path.split(filename)[1]
1125 1135 if not force and os.path.exists(os.path.join(self.path, patch)):
1126 1136 raise util.Abort(_('patch "%s" already exists') % patch)
1127 1137 patchf = self.opener(patch, "w")
1128 1138 patchf.write(text)
1129 1139 if patch in self.series:
1130 1140 raise util.Abort(_('patch %s is already in the series file')
1131 1141 % patch)
1132 1142 index = self.full_series_end() + i
1133 1143 self.full_series[index:index] = [patch]
1134 1144 self.read_series(self.full_series)
1135 1145 self.ui.warn("adding %s to series file\n" % patch)
1136 1146 i += 1
1137 1147 added.append(patch)
1138 1148 patch = None
1139 1149 self.series_dirty = 1
1140 1150 qrepo = self.qrepo()
1141 1151 if qrepo:
1142 1152 qrepo.add(added)
1143 1153
1144 1154 def delete(ui, repo, patch, **opts):
1145 1155 """remove a patch from the series file"""
1146 1156 q = repo.mq
1147 1157 q.delete(repo, patch)
1148 1158 q.save_dirty()
1149 1159 return 0
1150 1160
1151 1161 def applied(ui, repo, patch=None, **opts):
1152 1162 """print the patches already applied"""
1153 1163 repo.mq.qapplied(repo, patch)
1154 1164 return 0
1155 1165
1156 1166 def unapplied(ui, repo, patch=None, **opts):
1157 1167 """print the patches not yet applied"""
1158 1168 repo.mq.unapplied(repo, patch)
1159 1169 return 0
1160 1170
1161 1171 def qimport(ui, repo, *filename, **opts):
1162 1172 """import a patch"""
1163 1173 q = repo.mq
1164 1174 q.qimport(repo, filename, patch=opts['name'],
1165 1175 existing=opts['existing'], force=opts['force'])
1166 1176 q.save_dirty()
1167 1177 return 0
1168 1178
1169 1179 def init(ui, repo, **opts):
1170 1180 """init a new queue repository"""
1171 1181 q = repo.mq
1172 1182 r = q.init(repo, create=opts['create_repo'])
1173 1183 q.save_dirty()
1174 1184 if r:
1175 1185 fp = r.wopener('.hgignore', 'w')
1176 1186 print >> fp, 'syntax: glob'
1177 1187 print >> fp, 'status'
1178 1188 fp.close()
1179 1189 r.wopener('series', 'w').close()
1180 1190 r.add(['.hgignore', 'series'])
1181 1191 return 0
1182 1192
1183 1193 def clone(ui, source, dest=None, **opts):
1184 1194 '''clone main and patch repository at same time
1185 1195
1186 1196 If source is local, destination will have no patches applied. If
1187 1197 source is remote, this command can not check if patches are
1188 1198 applied in source, so cannot guarantee that patches are not
1189 1199 applied in destination. If you clone remote repository, be sure
1190 1200 before that it has no patches applied.
1191 1201
1192 1202 Source patch repository is looked for in <src>/.hg/patches by
1193 1203 default. Use -p <url> to change.
1194 1204 '''
1195 1205 commands.setremoteconfig(**opts)
1196 1206 if dest is None:
1197 1207 dest = hg.defaultdest(source)
1198 1208 sr = hg.repository(ui, ui.expandpath(source))
1199 1209 qbase, destrev = None, None
1200 1210 if sr.local():
1201 1211 reposetup(ui, sr)
1202 1212 if sr.mq.applied:
1203 1213 qbase = revlog.bin(sr.mq.applied[0].split(':')[0])
1204 1214 if not hg.islocal(dest):
1205 1215 destrev = sr.parents(qbase)[0]
1206 1216 ui.note(_('cloning main repo\n'))
1207 1217 sr, dr = hg.clone(ui, sr, dest,
1208 1218 pull=opts['pull'],
1209 1219 rev=destrev,
1210 1220 update=False,
1211 1221 stream=opts['uncompressed'])
1212 1222 ui.note(_('cloning patch repo\n'))
1213 1223 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1214 1224 dr.url() + '/.hg/patches',
1215 1225 pull=opts['pull'],
1216 1226 update=not opts['noupdate'],
1217 1227 stream=opts['uncompressed'])
1218 1228 if dr.local():
1219 1229 if qbase:
1220 1230 ui.note(_('stripping applied patches from destination repo\n'))
1221 1231 reposetup(ui, dr)
1222 1232 dr.mq.strip(dr, qbase, update=False, backup=None)
1223 1233 if not opts['noupdate']:
1224 1234 ui.note(_('updating destination repo\n'))
1225 1235 dr.update(dr.changelog.tip())
1226 1236
1227 1237 def commit(ui, repo, *pats, **opts):
1228 1238 """commit changes in the queue repository"""
1229 1239 q = repo.mq
1230 1240 r = q.qrepo()
1231 1241 if not r: raise util.Abort('no queue repository')
1232 1242 commands.commit(r.ui, r, *pats, **opts)
1233 1243
1234 1244 def series(ui, repo, **opts):
1235 1245 """print the entire series file"""
1236 1246 repo.mq.qseries(repo, missing=opts['missing'])
1237 1247 return 0
1238 1248
1239 1249 def top(ui, repo, **opts):
1240 1250 """print the name of the current patch"""
1241 1251 repo.mq.top(repo)
1242 1252 return 0
1243 1253
1244 1254 def next(ui, repo, **opts):
1245 1255 """print the name of the next patch"""
1246 1256 repo.mq.next(repo)
1247 1257 return 0
1248 1258
1249 1259 def prev(ui, repo, **opts):
1250 1260 """print the name of the previous patch"""
1251 1261 repo.mq.prev(repo)
1252 1262 return 0
1253 1263
1254 1264 def new(ui, repo, patch, **opts):
1255 1265 """create a new patch"""
1256 1266 q = repo.mq
1257 1267 message=commands.logmessage(**opts)
1258 1268 q.new(repo, patch, msg=message, force=opts['force'])
1259 1269 q.save_dirty()
1260 1270 return 0
1261 1271
1262 1272 def refresh(ui, repo, **opts):
1263 1273 """update the current patch"""
1264 1274 q = repo.mq
1265 1275 message=commands.logmessage(**opts)
1266 1276 q.refresh(repo, msg=message, short=opts['short'])
1267 1277 q.save_dirty()
1268 1278 return 0
1269 1279
1270 1280 def diff(ui, repo, *files, **opts):
1271 1281 """diff of the current patch"""
1272 1282 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1273 1283 repo.mq.diff(repo, list(files))
1274 1284 return 0
1275 1285
1276 1286 def lastsavename(path):
1277 1287 (dir, base) = os.path.split(path)
1278 1288 names = os.listdir(dir)
1279 1289 namere = re.compile("%s.([0-9]+)" % base)
1280 1290 max = None
1281 1291 maxname = None
1282 1292 for f in names:
1283 1293 m = namere.match(f)
1284 1294 if m:
1285 1295 index = int(m.group(1))
1286 1296 if max == None or index > max:
1287 1297 max = index
1288 1298 maxname = f
1289 1299 if maxname:
1290 1300 return (os.path.join(dir, maxname), max)
1291 1301 return (None, None)
1292 1302
1293 1303 def savename(path):
1294 1304 (last, index) = lastsavename(path)
1295 1305 if last is None:
1296 1306 index = 0
1297 1307 newpath = path + ".%d" % (index + 1)
1298 1308 return newpath
1299 1309
1300 1310 def push(ui, repo, patch=None, **opts):
1301 1311 """push the next patch onto the stack"""
1302 1312 q = repo.mq
1303 1313 mergeq = None
1304 1314
1305 1315 if opts['all']:
1306 1316 patch = q.series[-1]
1307 1317 if opts['merge']:
1308 1318 if opts['name']:
1309 1319 newpath = opts['name']
1310 1320 else:
1311 1321 newpath, i = lastsavename(q.path)
1312 1322 if not newpath:
1313 1323 ui.warn("no saved queues found, please use -n\n")
1314 1324 return 1
1315 1325 mergeq = queue(ui, repo.join(""), newpath)
1316 1326 ui.warn("merging with queue at: %s\n" % mergeq.path)
1317 1327 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1318 1328 mergeq=mergeq)
1319 1329 q.save_dirty()
1320 1330 return ret
1321 1331
1322 1332 def pop(ui, repo, patch=None, **opts):
1323 1333 """pop the current patch off the stack"""
1324 1334 localupdate = True
1325 1335 if opts['name']:
1326 1336 q = queue(ui, repo.join(""), repo.join(opts['name']))
1327 1337 ui.warn('using patch queue: %s\n' % q.path)
1328 1338 localupdate = False
1329 1339 else:
1330 1340 q = repo.mq
1331 1341 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1332 1342 q.save_dirty()
1333 1343 return 0
1334 1344
1335 1345 def restore(ui, repo, rev, **opts):
1336 1346 """restore the queue state saved by a rev"""
1337 1347 rev = repo.lookup(rev)
1338 1348 q = repo.mq
1339 1349 q.restore(repo, rev, delete=opts['delete'],
1340 1350 qupdate=opts['update'])
1341 1351 q.save_dirty()
1342 1352 return 0
1343 1353
1344 1354 def save(ui, repo, **opts):
1345 1355 """save current queue state"""
1346 1356 q = repo.mq
1347 1357 message=commands.logmessage(**opts)
1348 1358 ret = q.save(repo, msg=message)
1349 1359 if ret:
1350 1360 return ret
1351 1361 q.save_dirty()
1352 1362 if opts['copy']:
1353 1363 path = q.path
1354 1364 if opts['name']:
1355 1365 newpath = os.path.join(q.basepath, opts['name'])
1356 1366 if os.path.exists(newpath):
1357 1367 if not os.path.isdir(newpath):
1358 1368 raise util.Abort(_('destination %s exists and is not '
1359 1369 'a directory') % newpath)
1360 1370 if not opts['force']:
1361 1371 raise util.Abort(_('destination %s exists, '
1362 1372 'use -f to force') % newpath)
1363 1373 else:
1364 1374 newpath = savename(path)
1365 1375 ui.warn("copy %s to %s\n" % (path, newpath))
1366 1376 util.copyfiles(path, newpath)
1367 1377 if opts['empty']:
1368 1378 try:
1369 1379 os.unlink(os.path.join(q.path, q.status_path))
1370 1380 except:
1371 1381 pass
1372 1382 return 0
1373 1383
1374 1384 def strip(ui, repo, rev, **opts):
1375 1385 """strip a revision and all later revs on the same branch"""
1376 1386 rev = repo.lookup(rev)
1377 1387 backup = 'all'
1378 1388 if opts['backup']:
1379 1389 backup = 'strip'
1380 1390 elif opts['nobackup']:
1381 1391 backup = 'none'
1382 1392 repo.mq.strip(repo, rev, backup=backup)
1383 1393 return 0
1384 1394
1385 1395 def version(ui, q=None):
1386 1396 """print the version number"""
1387 1397 ui.write("mq version %s\n" % versionstr)
1388 1398 return 0
1389 1399
1390 1400 def reposetup(ui, repo):
1391 1401 class MqRepo(repo.__class__):
1392 1402 def tags(self):
1393 1403 if self.tagscache:
1394 1404 return self.tagscache
1395 1405
1396 1406 tagscache = super(MqRepo, self).tags()
1397 1407
1398 1408 q = self.mq
1399 1409 if not q.applied:
1400 1410 return tagscache
1401 1411
1402 1412 mqtags = [patch.split(':') for patch in q.applied]
1403 1413 mqtags.append((mqtags[-1][0], 'qtip'))
1404 1414 mqtags.append((mqtags[0][0], 'qbase'))
1405 1415 for patch in mqtags:
1406 1416 if patch[1] in tagscache:
1407 1417 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1408 1418 else:
1409 1419 tagscache[patch[1]] = revlog.bin(patch[0])
1410 1420
1411 1421 return tagscache
1412 1422
1413 1423 repo.__class__ = MqRepo
1414 1424 repo.mq = queue(ui, repo.join(""))
1415 1425
1416 1426 cmdtable = {
1417 1427 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1418 1428 "qclone": (clone,
1419 1429 [('', 'pull', None, _('use pull protocol to copy metadata')),
1420 1430 ('U', 'noupdate', None, _('do not update the new working directories')),
1421 1431 ('', 'uncompressed', None,
1422 1432 _('use uncompressed transfer (fast over LAN)')),
1423 1433 ('e', 'ssh', '', _('specify ssh command to use')),
1424 1434 ('p', 'patches', '', _('location of source patch repo')),
1425 1435 ('', 'remotecmd', '',
1426 1436 _('specify hg command to run on the remote side'))],
1427 1437 'hg qclone [OPTION]... SOURCE [DEST]'),
1428 1438 "qcommit|qci":
1429 1439 (commit,
1430 1440 commands.table["^commit|ci"][1],
1431 1441 'hg qcommit [OPTION]... [FILE]...'),
1432 1442 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1433 1443 "qdelete": (delete, [], 'hg qdelete PATCH'),
1434 1444 "^qimport":
1435 1445 (qimport,
1436 1446 [('e', 'existing', None, 'import file in patch dir'),
1437 1447 ('n', 'name', '', 'patch file name'),
1438 1448 ('f', 'force', None, 'overwrite existing files')],
1439 1449 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1440 1450 "^qinit":
1441 1451 (init,
1442 1452 [('c', 'create-repo', None, 'create queue repository')],
1443 1453 'hg qinit [-c]'),
1444 1454 "qnew":
1445 1455 (new,
1446 1456 [('m', 'message', '', _('use <text> as commit message')),
1447 1457 ('l', 'logfile', '', _('read the commit message from <file>')),
1448 1458 ('f', 'force', None, 'force')],
1449 1459 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1450 1460 "qnext": (next, [], 'hg qnext'),
1451 1461 "qprev": (prev, [], 'hg qprev'),
1452 1462 "^qpop":
1453 1463 (pop,
1454 1464 [('a', 'all', None, 'pop all patches'),
1455 1465 ('n', 'name', '', 'queue name to pop'),
1456 1466 ('f', 'force', None, 'forget any local changes')],
1457 1467 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1458 1468 "^qpush":
1459 1469 (push,
1460 1470 [('f', 'force', None, 'apply if the patch has rejects'),
1461 1471 ('l', 'list', None, 'list patch name in commit text'),
1462 1472 ('a', 'all', None, 'apply all patches'),
1463 1473 ('m', 'merge', None, 'merge from another queue'),
1464 1474 ('n', 'name', '', 'merge queue name')],
1465 1475 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1466 1476 "^qrefresh":
1467 1477 (refresh,
1468 1478 [('m', 'message', '', _('change commit message with <text>')),
1469 1479 ('l', 'logfile', '', _('change commit message with <file> content')),
1470 1480 ('s', 'short', None, 'short refresh')],
1471 1481 'hg qrefresh [-m TEXT] [-l FILE] [-s]'),
1472 1482 "qrestore":
1473 1483 (restore,
1474 1484 [('d', 'delete', None, 'delete save entry'),
1475 1485 ('u', 'update', None, 'update queue working dir')],
1476 1486 'hg qrestore [-d] [-u] REV'),
1477 1487 "qsave":
1478 1488 (save,
1479 1489 [('m', 'message', '', _('use <text> as commit message')),
1480 1490 ('l', 'logfile', '', _('read the commit message from <file>')),
1481 1491 ('c', 'copy', None, 'copy patch directory'),
1482 1492 ('n', 'name', '', 'copy directory name'),
1483 1493 ('e', 'empty', None, 'clear queue status file'),
1484 1494 ('f', 'force', None, 'force copy')],
1485 1495 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1486 1496 "qseries":
1487 1497 (series,
1488 1498 [('m', 'missing', None, 'print patches not in series')],
1489 1499 'hg qseries [-m]'),
1490 1500 "^strip":
1491 1501 (strip,
1492 1502 [('f', 'force', None, 'force multi-head removal'),
1493 1503 ('b', 'backup', None, 'bundle unrelated changesets'),
1494 1504 ('n', 'nobackup', None, 'no backups')],
1495 1505 'hg strip [-f] [-b] [-n] REV'),
1496 1506 "qtop": (top, [], 'hg qtop'),
1497 1507 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1498 1508 "qversion": (version, [], 'hg qversion')
1499 1509 }
1500 1510
General Comments 0
You need to be logged in to leave comments. Login now