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