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