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