##// END OF EJS Templates
mq: fix variables shadowing builtin
Benoit Boissinot -
r2794:86c54b7c default
parent child Browse files
Show More
@@ -1,1681 +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 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 repo.update(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 = repo.update(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 list = seen.keys()
509 list.sort()
510 for f in list:
508 seen_list = seen.keys()
509 seen_list.sort()
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 repo.update(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 tip = chlog.tip()
543 543 heads = limitheads(chlog, rev)
544 544 seen = {}
545 545
546 546 # search through all the heads, finding those where the revision
547 547 # we want to strip away is an ancestor. Also look for merges
548 548 # that might be turned into new heads by the strip.
549 549 while heads:
550 550 h = heads.pop()
551 551 n = h
552 552 while True:
553 553 seen[n] = 1
554 554 pp = chlog.parents(n)
555 555 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
556 556 if pp[1] not in seen:
557 557 heads.append(pp[1])
558 558 if pp[0] == revlog.nullid:
559 559 break
560 560 if chlog.rev(pp[0]) < revnum:
561 561 break
562 562 n = pp[0]
563 563 if n == rev:
564 564 break
565 565 r = chlog.reachable(h, rev)
566 566 if rev not in r:
567 567 saveheads.append(h)
568 568 for x in r:
569 569 if chlog.rev(x) > revnum:
570 570 savebases[x] = 1
571 571
572 572 # create a changegroup for all the branches we need to keep
573 573 if backup is "all":
574 574 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
575 575 bundle(backupch)
576 576 if saveheads:
577 577 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
578 578 chgrpfile = bundle(backupch)
579 579
580 580 stripall(rev, revnum)
581 581
582 582 change = chlog.read(rev)
583 583 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
584 584 chlog.strip(revnum, revnum)
585 585 if saveheads:
586 586 self.ui.status("adding branch\n")
587 587 commands.unbundle(self.ui, repo, chgrpfile, update=False)
588 588 if backup is not "strip":
589 589 os.unlink(chgrpfile)
590 590
591 591 def isapplied(self, patch):
592 592 """returns (index, rev, patch)"""
593 593 for i in xrange(len(self.applied)):
594 594 a = self.applied[i]
595 595 if a.name == patch:
596 596 return (i, a.rev, a.name)
597 597 return None
598 598
599 599 # if the exact patch name does not exist, we try a few
600 600 # variations. If strict is passed, we try only #1
601 601 #
602 602 # 1) a number to indicate an offset in the series file
603 603 # 2) a unique substring of the patch name was given
604 604 # 3) patchname[-+]num to indicate an offset in the series file
605 605 def lookup(self, patch, strict=False):
606 606 def partial_name(s):
607 607 if s in self.series:
608 608 return s
609 609 matches = [x for x in self.series if s in x]
610 610 if len(matches) > 1:
611 611 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
612 612 for m in matches:
613 613 self.ui.warn(' %s\n' % m)
614 614 return None
615 615 if matches:
616 616 return matches[0]
617 617 if len(self.series) > 0 and len(self.applied) > 0:
618 618 if s == 'qtip':
619 619 return self.series[self.series_end()-1]
620 620 if s == 'qbase':
621 621 return self.series[0]
622 622 return None
623 623 if patch == None:
624 624 return None
625 625
626 626 # we don't want to return a partial match until we make
627 627 # sure the file name passed in does not exist (checked below)
628 628 res = partial_name(patch)
629 629 if res and res == patch:
630 630 return res
631 631
632 632 if not os.path.isfile(os.path.join(self.path, patch)):
633 633 try:
634 634 sno = int(patch)
635 635 except(ValueError, OverflowError):
636 636 pass
637 637 else:
638 638 if sno < len(self.series):
639 639 patch = self.series[sno]
640 640 return patch
641 641 if not strict:
642 642 # return any partial match made above
643 643 if res:
644 644 return res
645 645 minus = patch.rsplit('-', 1)
646 646 if len(minus) > 1:
647 647 res = partial_name(minus[0])
648 648 if res:
649 649 i = self.series.index(res)
650 650 try:
651 651 off = int(minus[1] or 1)
652 652 except(ValueError, OverflowError):
653 653 pass
654 654 else:
655 655 if i - off >= 0:
656 656 return self.series[i - off]
657 657 plus = patch.rsplit('+', 1)
658 658 if len(plus) > 1:
659 659 res = partial_name(plus[0])
660 660 if res:
661 661 i = self.series.index(res)
662 662 try:
663 663 off = int(plus[1] or 1)
664 664 except(ValueError, OverflowError):
665 665 pass
666 666 else:
667 667 if i + off < len(self.series):
668 668 return self.series[i + off]
669 669 raise util.Abort(_("patch %s not in series") % patch)
670 670
671 671 def push(self, repo, patch=None, force=False, list=False,
672 672 mergeq=None, wlock=None):
673 673 if not wlock:
674 674 wlock = repo.wlock()
675 675 patch = self.lookup(patch)
676 676 if patch and self.isapplied(patch):
677 677 self.ui.warn(_("patch %s is already applied\n") % patch)
678 678 sys.exit(1)
679 679 if self.series_end() == len(self.series):
680 680 self.ui.warn(_("patch series fully applied\n"))
681 681 sys.exit(1)
682 682 if not force:
683 683 self.check_localchanges(repo)
684 684
685 685 self.applied_dirty = 1;
686 686 start = self.series_end()
687 687 if start > 0:
688 688 self.check_toppatch(repo)
689 689 if not patch:
690 690 patch = self.series[start]
691 691 end = start + 1
692 692 else:
693 693 end = self.series.index(patch, start) + 1
694 694 s = self.series[start:end]
695 695 if mergeq:
696 696 ret = self.mergepatch(repo, mergeq, s, wlock)
697 697 else:
698 698 ret = self.apply(repo, s, list, wlock=wlock)
699 699 top = self.applied[-1].name
700 700 if ret[0]:
701 701 self.ui.write("Errors during apply, please fix and refresh %s\n" %
702 702 top)
703 703 else:
704 704 self.ui.write("Now at: %s\n" % top)
705 705 return ret[0]
706 706
707 707 def pop(self, repo, patch=None, force=False, update=True, all=False,
708 708 wlock=None):
709 709 def getfile(f, rev):
710 710 t = repo.file(f).read(rev)
711 711 try:
712 712 repo.wfile(f, "w").write(t)
713 713 except IOError:
714 714 try:
715 715 os.makedirs(os.path.dirname(repo.wjoin(f)))
716 716 except OSError, err:
717 717 if err.errno != errno.EEXIST: raise
718 718 repo.wfile(f, "w").write(t)
719 719
720 720 if not wlock:
721 721 wlock = repo.wlock()
722 722 if patch:
723 723 # index, rev, patch
724 724 info = self.isapplied(patch)
725 725 if not info:
726 726 patch = self.lookup(patch)
727 727 info = self.isapplied(patch)
728 728 if not info:
729 729 raise util.Abort(_("patch %s is not applied") % patch)
730 730 if len(self.applied) == 0:
731 731 self.ui.warn(_("no patches applied\n"))
732 732 sys.exit(1)
733 733
734 734 if not update:
735 735 parents = repo.dirstate.parents()
736 736 rr = [ revlog.bin(x.rev) for x in self.applied ]
737 737 for p in parents:
738 738 if p in rr:
739 739 self.ui.warn("qpop: forcing dirstate update\n")
740 740 update = True
741 741
742 742 if not force and update:
743 743 self.check_localchanges(repo)
744 744
745 745 self.applied_dirty = 1;
746 746 end = len(self.applied)
747 747 if not patch:
748 748 if all:
749 749 popi = 0
750 750 else:
751 751 popi = len(self.applied) - 1
752 752 else:
753 753 popi = info[0] + 1
754 754 if popi >= end:
755 755 self.ui.warn("qpop: %s is already at the top\n" % patch)
756 756 return
757 757 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
758 758
759 759 start = info[0]
760 760 rev = revlog.bin(info[1])
761 761
762 762 # we know there are no local changes, so we can make a simplified
763 763 # form of hg.update.
764 764 if update:
765 765 top = self.check_toppatch(repo)
766 766 qp = self.qparents(repo, rev)
767 767 changes = repo.changelog.read(qp)
768 768 mf1 = repo.manifest.readflags(changes[0])
769 769 mmap = repo.manifest.read(changes[0])
770 770 (c, a, r, d, u) = repo.changes(qp, top)
771 771 if d:
772 772 raise util.Abort("deletions found between repo revs")
773 773 for f in c:
774 774 getfile(f, mmap[f])
775 775 for f in r:
776 776 getfile(f, mmap[f])
777 777 util.set_exec(repo.wjoin(f), mf1[f])
778 778 repo.dirstate.update(c + r, 'n')
779 779 for f in a:
780 780 try: os.unlink(repo.wjoin(f))
781 781 except: raise
782 782 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
783 783 except: pass
784 784 if a:
785 785 repo.dirstate.forget(a)
786 786 repo.dirstate.setparents(qp, revlog.nullid)
787 787 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
788 788 del self.applied[start:end]
789 789 if len(self.applied):
790 790 self.ui.write("Now at: %s\n" % self.applied[-1].name)
791 791 else:
792 792 self.ui.write("Patch queue now empty\n")
793 793
794 794 def diff(self, repo, files):
795 795 top = self.check_toppatch(repo)
796 796 if not top:
797 797 self.ui.write("No patches applied\n")
798 798 return
799 799 qp = self.qparents(repo, top)
800 800 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
801 801
802 802 def refresh(self, repo, msg=None, short=False):
803 803 if len(self.applied) == 0:
804 804 self.ui.write("No patches applied\n")
805 805 return
806 806 wlock = repo.wlock()
807 807 self.check_toppatch(repo)
808 808 qp = self.qparents(repo)
809 809 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
810 810 top = revlog.bin(top)
811 811 cparents = repo.changelog.parents(top)
812 812 patchparent = self.qparents(repo, top)
813 813 message, comments, user, date, patchfound = self.readheaders(patch)
814 814
815 815 patchf = self.opener(patch, "w")
816 816 msg = msg.rstrip()
817 817 if msg:
818 818 if comments:
819 819 # Remove existing message.
820 820 ci = 0
821 821 for mi in range(len(message)):
822 822 while message[mi] != comments[ci]:
823 823 ci += 1
824 824 del comments[ci]
825 825 comments.append(msg)
826 826 if comments:
827 827 comments = "\n".join(comments) + '\n\n'
828 828 patchf.write(comments)
829 829
830 830 tip = repo.changelog.tip()
831 831 if top == tip:
832 832 # if the top of our patch queue is also the tip, there is an
833 833 # optimization here. We update the dirstate in place and strip
834 834 # off the tip commit. Then just commit the current directory
835 835 # tree. We can also send repo.commit the list of files
836 836 # changed to speed up the diff
837 837 #
838 838 # in short mode, we only diff the files included in the
839 839 # patch already
840 840 #
841 841 # this should really read:
842 842 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
843 843 # but we do it backwards to take advantage of manifest/chlog
844 844 # caching against the next repo.changes call
845 845 #
846 846 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
847 847 if short:
848 848 filelist = cc + aa + dd
849 849 else:
850 850 filelist = None
851 851 (c, a, r, d, u) = repo.changes(None, None, filelist)
852 852
853 853 # we might end up with files that were added between tip and
854 854 # the dirstate parent, but then changed in the local dirstate.
855 855 # in this case, we want them to only show up in the added section
856 856 for x in c:
857 857 if x not in aa:
858 858 cc.append(x)
859 859 # we might end up with files added by the local dirstate that
860 860 # were deleted by the patch. In this case, they should only
861 861 # show up in the changed section.
862 862 for x in a:
863 863 if x in dd:
864 864 del dd[dd.index(x)]
865 865 cc.append(x)
866 866 else:
867 867 aa.append(x)
868 868 # make sure any files deleted in the local dirstate
869 869 # are not in the add or change column of the patch
870 870 forget = []
871 871 for x in d + r:
872 872 if x in aa:
873 873 del aa[aa.index(x)]
874 874 forget.append(x)
875 875 continue
876 876 elif x in cc:
877 877 del cc[cc.index(x)]
878 878 dd.append(x)
879 879
880 880 c = list(util.unique(cc))
881 881 r = list(util.unique(dd))
882 882 a = list(util.unique(aa))
883 883 filelist = list(util.unique(c + r + a ))
884 884 commands.dodiff(patchf, self.ui, repo, patchparent, None,
885 885 filelist, changes=(c, a, r, [], u))
886 886 patchf.close()
887 887
888 888 changes = repo.changelog.read(tip)
889 889 repo.dirstate.setparents(*cparents)
890 890 repo.dirstate.update(a, 'a')
891 891 repo.dirstate.update(r, 'r')
892 892 repo.dirstate.update(c, 'n')
893 893 repo.dirstate.forget(forget)
894 894
895 895 if not msg:
896 896 if not message:
897 897 message = "patch queue: %s\n" % patch
898 898 else:
899 899 message = "\n".join(message)
900 900 else:
901 901 message = msg
902 902
903 903 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
904 904 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
905 905 self.applied[-1] = StatusEntry(revlog.hex(n), patch)
906 906 self.applied_dirty = 1
907 907 else:
908 908 commands.dodiff(patchf, self.ui, repo, patchparent, None)
909 909 patchf.close()
910 910 self.pop(repo, force=True, wlock=wlock)
911 911 self.push(repo, force=True, wlock=wlock)
912 912
913 913 def init(self, repo, create=False):
914 914 if os.path.isdir(self.path):
915 915 raise util.Abort(_("patch queue directory already exists"))
916 916 os.mkdir(self.path)
917 917 if create:
918 918 return self.qrepo(create=True)
919 919
920 920 def unapplied(self, repo, patch=None):
921 921 if patch and patch not in self.series:
922 922 raise util.Abort(_("patch %s is not in series file") % patch)
923 923 if not patch:
924 924 start = self.series_end()
925 925 else:
926 926 start = self.series.index(patch) + 1
927 927 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
928 928
929 929 def qseries(self, repo, missing=None, summary=False):
930 930 start = self.series_end()
931 931 if not missing:
932 932 for i in range(len(self.series)):
933 933 patch = self.series[i]
934 934 if self.ui.verbose:
935 935 if i < start:
936 936 status = 'A'
937 937 else:
938 938 status = 'U'
939 939 self.ui.write('%d %s ' % (i, status))
940 940 if summary:
941 941 msg = self.readheaders(patch)[0]
942 942 msg = msg and ': ' + msg[0] or ': '
943 943 else:
944 944 msg = ''
945 945 self.ui.write('%s%s\n' % (patch, msg))
946 946 else:
947 list = []
947 msng_list = []
948 948 for root, dirs, files in os.walk(self.path):
949 949 d = root[len(self.path) + 1:]
950 950 for f in files:
951 951 fl = os.path.join(d, f)
952 952 if (fl not in self.series and
953 953 fl not in (self.status_path, self.series_path)
954 954 and not fl.startswith('.')):
955 list.append(fl)
956 list.sort()
957 if list:
958 for x in list:
955 msng_list.append(fl)
956 msng_list.sort()
957 if msng_list:
958 for x in msng_list:
959 959 if self.ui.verbose:
960 960 self.ui.write("D ")
961 961 self.ui.write("%s\n" % x)
962 962
963 963 def issaveline(self, l):
964 964 name = l.split(':')[1]
965 965 if name == '.hg.patches.save.line':
966 966 return True
967 967
968 968 def qrepo(self, create=False):
969 969 if create or os.path.isdir(os.path.join(self.path, ".hg")):
970 970 return hg.repository(self.ui, path=self.path, create=create)
971 971
972 972 def restore(self, repo, rev, delete=None, qupdate=None):
973 973 c = repo.changelog.read(rev)
974 974 desc = c[4].strip()
975 975 lines = desc.splitlines()
976 976 i = 0
977 977 datastart = None
978 978 series = []
979 979 applied = []
980 980 qpp = None
981 981 for i in xrange(0, len(lines)):
982 982 if lines[i] == 'Patch Data:':
983 983 datastart = i + 1
984 984 elif lines[i].startswith('Dirstate:'):
985 985 l = lines[i].rstrip()
986 986 l = l[10:].split(' ')
987 987 qpp = [ hg.bin(x) for x in l ]
988 988 elif datastart != None:
989 989 l = lines[i].rstrip()
990 990 se = StatusEntry(l)
991 id = se.rev
992 file = se.name
993 if id:
991 file_ = se.name
992 if se.rev:
994 993 applied.append(se)
995 series.append(file)
994 series.append(file_)
996 995 if datastart == None:
997 996 self.ui.warn("No saved patch data found\n")
998 997 return 1
999 998 self.ui.warn("restoring status: %s\n" % lines[0])
1000 999 self.full_series = series
1001 1000 self.applied = applied
1002 1001 self.parse_series()
1003 1002 self.series_dirty = 1
1004 1003 self.applied_dirty = 1
1005 1004 heads = repo.changelog.heads()
1006 1005 if delete:
1007 1006 if rev not in heads:
1008 1007 self.ui.warn("save entry has children, leaving it alone\n")
1009 1008 else:
1010 1009 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1011 1010 pp = repo.dirstate.parents()
1012 1011 if rev in pp:
1013 1012 update = True
1014 1013 else:
1015 1014 update = False
1016 1015 self.strip(repo, rev, update=update, backup='strip')
1017 1016 if qpp:
1018 1017 self.ui.warn("saved queue repository parents: %s %s\n" %
1019 1018 (hg.short(qpp[0]), hg.short(qpp[1])))
1020 1019 if qupdate:
1021 1020 print "queue directory updating"
1022 1021 r = self.qrepo()
1023 1022 if not r:
1024 1023 self.ui.warn("Unable to load queue repository\n")
1025 1024 return 1
1026 1025 r.update(qpp[0], allow=False, force=True)
1027 1026
1028 1027 def save(self, repo, msg=None):
1029 1028 if len(self.applied) == 0:
1030 1029 self.ui.warn("save: no patches applied, exiting\n")
1031 1030 return 1
1032 1031 if self.issaveline(self.applied[-1]):
1033 1032 self.ui.warn("status is already saved\n")
1034 1033 return 1
1035 1034
1036 1035 ar = [ ':' + x for x in self.full_series ]
1037 1036 if not msg:
1038 1037 msg = "hg patches saved state"
1039 1038 else:
1040 1039 msg = "hg patches: " + msg.rstrip('\r\n')
1041 1040 r = self.qrepo()
1042 1041 if r:
1043 1042 pp = r.dirstate.parents()
1044 1043 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1045 1044 msg += "\n\nPatch Data:\n"
1046 1045 text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
1047 1046 + '\n' or "")
1048 1047 n = repo.commit(None, text, user=None, force=1)
1049 1048 if not n:
1050 1049 self.ui.warn("repo commit failed\n")
1051 1050 return 1
1052 1051 self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
1053 1052 self.applied_dirty = 1
1054 1053
1055 1054 def full_series_end(self):
1056 1055 if len(self.applied) > 0:
1057 1056 p = self.applied[-1].name
1058 1057 end = self.find_series(p)
1059 1058 if end == None:
1060 1059 return len(self.full_series)
1061 1060 return end + 1
1062 1061 return 0
1063 1062
1064 1063 def series_end(self):
1065 1064 end = 0
1066 1065 if len(self.applied) > 0:
1067 1066 p = self.applied[-1].name
1068 1067 try:
1069 1068 end = self.series.index(p)
1070 1069 except ValueError:
1071 1070 return 0
1072 1071 return end + 1
1073 1072 return end
1074 1073
1075 1074 def qapplied(self, repo, patch=None):
1076 1075 if patch and patch not in self.series:
1077 1076 raise util.Abort(_("patch %s is not in series file") % patch)
1078 1077 if not patch:
1079 1078 end = len(self.applied)
1080 1079 else:
1081 1080 end = self.series.index(patch) + 1
1082 1081 for x in xrange(end):
1083 1082 p = self.appliedname(x)
1084 1083 self.ui.write("%s\n" % p)
1085 1084
1086 1085 def appliedname(self, index):
1087 1086 pname = self.applied[index].name
1088 1087 if not self.ui.verbose:
1089 1088 p = pname
1090 1089 else:
1091 1090 p = str(self.series.index(pname)) + " " + p
1092 1091 return p
1093 1092
1094 1093 def top(self, repo):
1095 1094 if len(self.applied):
1096 1095 p = self.appliedname(-1)
1097 1096 self.ui.write(p + '\n')
1098 1097 else:
1099 1098 self.ui.write("No patches applied\n")
1100 1099
1101 1100 def next(self, repo):
1102 1101 end = self.series_end()
1103 1102 if end == len(self.series):
1104 1103 self.ui.write("All patches applied\n")
1105 1104 else:
1106 1105 p = self.series[end]
1107 1106 if self.ui.verbose:
1108 1107 self.ui.write("%d " % self.series.index(p))
1109 1108 self.ui.write(p + '\n')
1110 1109
1111 1110 def prev(self, repo):
1112 1111 if len(self.applied) > 1:
1113 1112 p = self.appliedname(-2)
1114 1113 self.ui.write(p + '\n')
1115 1114 elif len(self.applied) == 1:
1116 1115 self.ui.write("Only one patch applied\n")
1117 1116 else:
1118 1117 self.ui.write("No patches applied\n")
1119 1118
1120 1119 def qimport(self, repo, files, patch=None, existing=None, force=None):
1121 1120 if len(files) > 1 and patch:
1122 1121 raise util.Abort(_('option "-n" not valid when importing multiple '
1123 1122 'files'))
1124 1123 i = 0
1125 1124 added = []
1126 1125 for filename in files:
1127 1126 if existing:
1128 1127 if not patch:
1129 1128 patch = filename
1130 1129 if not os.path.isfile(os.path.join(self.path, patch)):
1131 1130 raise util.Abort(_("patch %s does not exist") % patch)
1132 1131 else:
1133 1132 try:
1134 1133 text = file(filename).read()
1135 1134 except IOError:
1136 1135 raise util.Abort(_("unable to read %s") % patch)
1137 1136 if not patch:
1138 1137 patch = os.path.split(filename)[1]
1139 1138 if not force and os.path.exists(os.path.join(self.path, patch)):
1140 1139 raise util.Abort(_('patch "%s" already exists') % patch)
1141 1140 patchf = self.opener(patch, "w")
1142 1141 patchf.write(text)
1143 1142 if patch in self.series:
1144 1143 raise util.Abort(_('patch %s is already in the series file')
1145 1144 % patch)
1146 1145 index = self.full_series_end() + i
1147 1146 self.full_series[index:index] = [patch]
1148 1147 self.parse_series()
1149 1148 self.ui.warn("adding %s to series file\n" % patch)
1150 1149 i += 1
1151 1150 added.append(patch)
1152 1151 patch = None
1153 1152 self.series_dirty = 1
1154 1153 qrepo = self.qrepo()
1155 1154 if qrepo:
1156 1155 qrepo.add(added)
1157 1156
1158 1157 def delete(ui, repo, patch, **opts):
1159 1158 """remove a patch from the series file
1160 1159
1161 1160 The patch must not be applied.
1162 1161 With -f, deletes the patch file as well as the series entry."""
1163 1162 q = repo.mq
1164 1163 q.delete(repo, patch, force=opts.get('force'))
1165 1164 q.save_dirty()
1166 1165 return 0
1167 1166
1168 1167 def applied(ui, repo, patch=None, **opts):
1169 1168 """print the patches already applied"""
1170 1169 repo.mq.qapplied(repo, patch)
1171 1170 return 0
1172 1171
1173 1172 def unapplied(ui, repo, patch=None, **opts):
1174 1173 """print the patches not yet applied"""
1175 1174 for i, p in repo.mq.unapplied(repo, patch):
1176 1175 if ui.verbose:
1177 1176 ui.write("%d " % i)
1178 1177 ui.write("%s\n" % p)
1179 1178
1180 1179 def qimport(ui, repo, *filename, **opts):
1181 1180 """import a patch"""
1182 1181 q = repo.mq
1183 1182 q.qimport(repo, filename, patch=opts['name'],
1184 1183 existing=opts['existing'], force=opts['force'])
1185 1184 q.save_dirty()
1186 1185 return 0
1187 1186
1188 1187 def init(ui, repo, **opts):
1189 1188 """init a new queue repository
1190 1189
1191 1190 The queue repository is unversioned by default. If -c is
1192 1191 specified, qinit will create a separate nested repository
1193 1192 for patches. Use qcommit to commit changes to this queue
1194 1193 repository."""
1195 1194 q = repo.mq
1196 1195 r = q.init(repo, create=opts['create_repo'])
1197 1196 q.save_dirty()
1198 1197 if r:
1199 1198 fp = r.wopener('.hgignore', 'w')
1200 1199 print >> fp, 'syntax: glob'
1201 1200 print >> fp, 'status'
1202 1201 fp.close()
1203 1202 r.wopener('series', 'w').close()
1204 1203 r.add(['.hgignore', 'series'])
1205 1204 return 0
1206 1205
1207 1206 def clone(ui, source, dest=None, **opts):
1208 1207 '''clone main and patch repository at same time
1209 1208
1210 1209 If source is local, destination will have no patches applied. If
1211 1210 source is remote, this command can not check if patches are
1212 1211 applied in source, so cannot guarantee that patches are not
1213 1212 applied in destination. If you clone remote repository, be sure
1214 1213 before that it has no patches applied.
1215 1214
1216 1215 Source patch repository is looked for in <src>/.hg/patches by
1217 1216 default. Use -p <url> to change.
1218 1217 '''
1219 1218 commands.setremoteconfig(ui, opts)
1220 1219 if dest is None:
1221 1220 dest = hg.defaultdest(source)
1222 1221 sr = hg.repository(ui, ui.expandpath(source))
1223 1222 qbase, destrev = None, None
1224 1223 if sr.local():
1225 1224 reposetup(ui, sr)
1226 1225 if sr.mq.applied:
1227 1226 qbase = revlog.bin(sr.mq.applied[0].rev)
1228 1227 if not hg.islocal(dest):
1229 1228 destrev = sr.parents(qbase)[0]
1230 1229 ui.note(_('cloning main repo\n'))
1231 1230 sr, dr = hg.clone(ui, sr, dest,
1232 1231 pull=opts['pull'],
1233 1232 rev=destrev,
1234 1233 update=False,
1235 1234 stream=opts['uncompressed'])
1236 1235 ui.note(_('cloning patch repo\n'))
1237 1236 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1238 1237 dr.url() + '/.hg/patches',
1239 1238 pull=opts['pull'],
1240 1239 update=not opts['noupdate'],
1241 1240 stream=opts['uncompressed'])
1242 1241 if dr.local():
1243 1242 if qbase:
1244 1243 ui.note(_('stripping applied patches from destination repo\n'))
1245 1244 reposetup(ui, dr)
1246 1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1247 1246 if not opts['noupdate']:
1248 1247 ui.note(_('updating destination repo\n'))
1249 1248 dr.update(dr.changelog.tip())
1250 1249
1251 1250 def commit(ui, repo, *pats, **opts):
1252 1251 """commit changes in the queue repository"""
1253 1252 q = repo.mq
1254 1253 r = q.qrepo()
1255 1254 if not r: raise util.Abort('no queue repository')
1256 1255 commands.commit(r.ui, r, *pats, **opts)
1257 1256
1258 1257 def series(ui, repo, **opts):
1259 1258 """print the entire series file"""
1260 1259 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1261 1260 return 0
1262 1261
1263 1262 def top(ui, repo, **opts):
1264 1263 """print the name of the current patch"""
1265 1264 repo.mq.top(repo)
1266 1265 return 0
1267 1266
1268 1267 def next(ui, repo, **opts):
1269 1268 """print the name of the next patch"""
1270 1269 repo.mq.next(repo)
1271 1270 return 0
1272 1271
1273 1272 def prev(ui, repo, **opts):
1274 1273 """print the name of the previous patch"""
1275 1274 repo.mq.prev(repo)
1276 1275 return 0
1277 1276
1278 1277 def new(ui, repo, patch, **opts):
1279 1278 """create a new patch
1280 1279
1281 1280 qnew creates a new patch on top of the currently-applied patch
1282 1281 (if any). It will refuse to run if there are any outstanding
1283 1282 changes unless -f is specified, in which case the patch will
1284 1283 be initialised with them.
1285 1284
1286 1285 -m or -l set the patch header as well as the commit message.
1287 1286 If neither is specified, the patch header is empty and the
1288 1287 commit message is 'New patch: PATCH'"""
1289 1288 q = repo.mq
1290 1289 message=commands.logmessage(**opts)
1291 1290 q.new(repo, patch, msg=message, force=opts['force'])
1292 1291 q.save_dirty()
1293 1292 return 0
1294 1293
1295 1294 def refresh(ui, repo, **opts):
1296 1295 """update the current patch"""
1297 1296 q = repo.mq
1298 1297 message=commands.logmessage(**opts)
1299 1298 if opts['edit']:
1300 1299 if message:
1301 1300 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1302 1301 patch = q.applied[-1].name
1303 1302 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1304 1303 message = ui.edit('\n'.join(message), user or ui.username())
1305 1304 q.refresh(repo, msg=message, short=opts['short'])
1306 1305 q.save_dirty()
1307 1306 return 0
1308 1307
1309 1308 def diff(ui, repo, *files, **opts):
1310 1309 """diff of the current patch"""
1311 1310 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1312 1311 repo.mq.diff(repo, list(files))
1313 1312 return 0
1314 1313
1315 1314 def fold(ui, repo, *files, **opts):
1316 1315 """fold the named patches into the current patch
1317 1316
1318 1317 Patches must not yet be applied. Each patch will be successively
1319 1318 applied to the current patch in the order given. If all the
1320 1319 patches apply successfully, the current patch will be refreshed
1321 1320 with the new cumulative patch, and the folded patches will
1322 1321 be deleted. With -f/--force, the folded patch files will
1323 1322 be removed afterwards.
1324 1323
1325 1324 The header for each folded patch will be concatenated with
1326 1325 the current patch header, separated by a line of '* * *'."""
1327 1326
1328 1327 q = repo.mq
1329 1328
1330 1329 if not files:
1331 1330 raise util.Abort(_('qfold requires at least one patch name'))
1332 1331 if not q.check_toppatch(repo):
1333 1332 raise util.Abort(_('No patches applied\n'))
1334 1333
1335 1334 message=commands.logmessage(**opts)
1336 1335 if opts['edit']:
1337 1336 if message:
1338 1337 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1339 1338
1340 1339 parent = q.lookup('qtip')
1341 1340 patches = []
1342 1341 messages = []
1343 1342 for f in files:
1344 1343 patch = q.lookup(f)
1345 1344 if patch in patches or patch == parent:
1346 1345 self.ui.warn(_('Skipping already folded patch %s') % patch)
1347 1346 if q.isapplied(patch):
1348 1347 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1349 1348 patches.append(patch)
1350 1349
1351 1350 for patch in patches:
1352 1351 if not message:
1353 1352 messages.append(q.readheaders(patch)[0])
1354 1353 pf = os.path.join(q.path, patch)
1355 1354 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1356 1355 if not patchsuccess:
1357 1356 raise util.Abort(_('Error folding patch %s') % patch)
1358 1357
1359 1358 if not message:
1360 1359 message, comments, user = q.readheaders(parent)[0:3]
1361 1360 for msg in messages:
1362 1361 message.append('* * *')
1363 1362 message.extend(msg)
1364 1363 message = '\n'.join(message)
1365 1364
1366 1365 if opts['edit']:
1367 1366 message = ui.edit(message, user or ui.username())
1368 1367
1369 1368 q.refresh(repo, msg=message)
1370 1369
1371 1370 for patch in patches:
1372 1371 q.delete(repo, patch, force=opts['force'])
1373 1372
1374 1373 q.save_dirty()
1375 1374
1376 1375 def header(ui, repo, patch=None):
1377 1376 """Print the header of the topmost or specified patch"""
1378 1377 q = repo.mq
1379 1378
1380 1379 if patch:
1381 1380 patch = q.lookup(patch)
1382 1381 else:
1383 1382 if not q.applied:
1384 1383 ui.write('No patches applied\n')
1385 1384 return
1386 1385 patch = q.lookup('qtip')
1387 1386 message = repo.mq.readheaders(patch)[0]
1388 1387
1389 1388 ui.write('\n'.join(message) + '\n')
1390 1389
1391 1390 def lastsavename(path):
1392 (dir, base) = os.path.split(path)
1393 names = os.listdir(dir)
1391 (directory, base) = os.path.split(path)
1392 names = os.listdir(directory)
1394 1393 namere = re.compile("%s.([0-9]+)" % base)
1395 max = None
1394 maxindex = None
1396 1395 maxname = None
1397 1396 for f in names:
1398 1397 m = namere.match(f)
1399 1398 if m:
1400 1399 index = int(m.group(1))
1401 if max == None or index > max:
1402 max = index
1400 if maxindex == None or index > maxindex:
1401 maxindex = index
1403 1402 maxname = f
1404 1403 if maxname:
1405 return (os.path.join(dir, maxname), max)
1404 return (os.path.join(directory, maxname), maxindex)
1406 1405 return (None, None)
1407 1406
1408 1407 def savename(path):
1409 1408 (last, index) = lastsavename(path)
1410 1409 if last is None:
1411 1410 index = 0
1412 1411 newpath = path + ".%d" % (index + 1)
1413 1412 return newpath
1414 1413
1415 1414 def push(ui, repo, patch=None, **opts):
1416 1415 """push the next patch onto the stack"""
1417 1416 q = repo.mq
1418 1417 mergeq = None
1419 1418
1420 1419 if opts['all']:
1421 1420 patch = q.series[-1]
1422 1421 if opts['merge']:
1423 1422 if opts['name']:
1424 1423 newpath = opts['name']
1425 1424 else:
1426 1425 newpath, i = lastsavename(q.path)
1427 1426 if not newpath:
1428 1427 ui.warn("no saved queues found, please use -n\n")
1429 1428 return 1
1430 1429 mergeq = queue(ui, repo.join(""), newpath)
1431 1430 ui.warn("merging with queue at: %s\n" % mergeq.path)
1432 1431 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1433 1432 mergeq=mergeq)
1434 1433 q.save_dirty()
1435 1434 return ret
1436 1435
1437 1436 def pop(ui, repo, patch=None, **opts):
1438 1437 """pop the current patch off the stack"""
1439 1438 localupdate = True
1440 1439 if opts['name']:
1441 1440 q = queue(ui, repo.join(""), repo.join(opts['name']))
1442 1441 ui.warn('using patch queue: %s\n' % q.path)
1443 1442 localupdate = False
1444 1443 else:
1445 1444 q = repo.mq
1446 1445 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1447 1446 q.save_dirty()
1448 1447 return 0
1449 1448
1450 1449 def rename(ui, repo, patch, name=None, **opts):
1451 1450 """rename a patch
1452 1451
1453 1452 With one argument, renames the current patch to PATCH1.
1454 1453 With two arguments, renames PATCH1 to PATCH2."""
1455 1454
1456 1455 q = repo.mq
1457 1456
1458 1457 if not name:
1459 1458 name = patch
1460 1459 patch = None
1461 1460
1462 1461 if name in q.series:
1463 1462 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1464 1463
1465 1464 absdest = os.path.join(q.path, name)
1466 1465 if os.path.exists(absdest):
1467 1466 raise util.Abort(_('%s already exists') % absdest)
1468 1467
1469 1468 if patch:
1470 1469 patch = q.lookup(patch)
1471 1470 else:
1472 1471 if not q.applied:
1473 1472 ui.write(_('No patches applied\n'))
1474 1473 return
1475 1474 patch = q.lookup('qtip')
1476 1475
1477 1476 if ui.verbose:
1478 1477 ui.write('Renaming %s to %s\n' % (patch, name))
1479 1478 i = q.find_series(patch)
1480 1479 q.full_series[i] = name
1481 1480 q.parse_series()
1482 1481 q.series_dirty = 1
1483 1482
1484 1483 info = q.isapplied(patch)
1485 1484 if info:
1486 1485 q.applied[info[0]] = StatusEntry(info[1], name)
1487 1486 q.applied_dirty = 1
1488 1487
1489 1488 util.rename(os.path.join(q.path, patch), absdest)
1490 1489 r = q.qrepo()
1491 1490 if r:
1492 1491 wlock = r.wlock()
1493 1492 if r.dirstate.state(name) == 'r':
1494 1493 r.undelete([name], wlock)
1495 1494 r.copy(patch, name, wlock)
1496 1495 r.remove([patch], False, wlock)
1497 1496
1498 1497 q.save_dirty()
1499 1498
1500 1499 def restore(ui, repo, rev, **opts):
1501 1500 """restore the queue state saved by a rev"""
1502 1501 rev = repo.lookup(rev)
1503 1502 q = repo.mq
1504 1503 q.restore(repo, rev, delete=opts['delete'],
1505 1504 qupdate=opts['update'])
1506 1505 q.save_dirty()
1507 1506 return 0
1508 1507
1509 1508 def save(ui, repo, **opts):
1510 1509 """save current queue state"""
1511 1510 q = repo.mq
1512 1511 message=commands.logmessage(**opts)
1513 1512 ret = q.save(repo, msg=message)
1514 1513 if ret:
1515 1514 return ret
1516 1515 q.save_dirty()
1517 1516 if opts['copy']:
1518 1517 path = q.path
1519 1518 if opts['name']:
1520 1519 newpath = os.path.join(q.basepath, opts['name'])
1521 1520 if os.path.exists(newpath):
1522 1521 if not os.path.isdir(newpath):
1523 1522 raise util.Abort(_('destination %s exists and is not '
1524 1523 'a directory') % newpath)
1525 1524 if not opts['force']:
1526 1525 raise util.Abort(_('destination %s exists, '
1527 1526 'use -f to force') % newpath)
1528 1527 else:
1529 1528 newpath = savename(path)
1530 1529 ui.warn("copy %s to %s\n" % (path, newpath))
1531 1530 util.copyfiles(path, newpath)
1532 1531 if opts['empty']:
1533 1532 try:
1534 1533 os.unlink(os.path.join(q.path, q.status_path))
1535 1534 except:
1536 1535 pass
1537 1536 return 0
1538 1537
1539 1538 def strip(ui, repo, rev, **opts):
1540 1539 """strip a revision and all later revs on the same branch"""
1541 1540 rev = repo.lookup(rev)
1542 1541 backup = 'all'
1543 1542 if opts['backup']:
1544 1543 backup = 'strip'
1545 1544 elif opts['nobackup']:
1546 1545 backup = 'none'
1547 1546 repo.mq.strip(repo, rev, backup=backup)
1548 1547 return 0
1549 1548
1550 1549 def version(ui, q=None):
1551 1550 """print the version number of the mq extension"""
1552 1551 ui.write("mq version %s\n" % versionstr)
1553 1552 return 0
1554 1553
1555 1554 def reposetup(ui, repo):
1556 1555 class MqRepo(repo.__class__):
1557 1556 def tags(self):
1558 1557 if self.tagscache:
1559 1558 return self.tagscache
1560 1559
1561 1560 tagscache = super(MqRepo, self).tags()
1562 1561
1563 1562 q = self.mq
1564 1563 if not q.applied:
1565 1564 return tagscache
1566 1565
1567 1566 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1568 1567 mqtags.append((mqtags[-1][0], 'qtip'))
1569 1568 mqtags.append((mqtags[0][0], 'qbase'))
1570 1569 for patch in mqtags:
1571 1570 if patch[1] in tagscache:
1572 1571 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1573 1572 else:
1574 1573 tagscache[patch[1]] = revlog.bin(patch[0])
1575 1574
1576 1575 return tagscache
1577 1576
1578 1577 repo.__class__ = MqRepo
1579 1578 repo.mq = queue(ui, repo.join(""))
1580 1579
1581 1580 cmdtable = {
1582 1581 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1583 1582 "qclone": (clone,
1584 1583 [('', 'pull', None, _('use pull protocol to copy metadata')),
1585 1584 ('U', 'noupdate', None, _('do not update the new working directories')),
1586 1585 ('', 'uncompressed', None,
1587 1586 _('use uncompressed transfer (fast over LAN)')),
1588 1587 ('e', 'ssh', '', _('specify ssh command to use')),
1589 1588 ('p', 'patches', '', _('location of source patch repo')),
1590 1589 ('', 'remotecmd', '',
1591 1590 _('specify hg command to run on the remote side'))],
1592 1591 'hg qclone [OPTION]... SOURCE [DEST]'),
1593 1592 "qcommit|qci":
1594 1593 (commit,
1595 1594 commands.table["^commit|ci"][1],
1596 1595 'hg qcommit [OPTION]... [FILE]...'),
1597 1596 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1598 1597 "qdelete":
1599 1598 (delete,
1600 1599 [('f', 'force', None, _('delete patch file'))],
1601 1600 'hg qdelete [-f] PATCH'),
1602 1601 'qfold':
1603 1602 (fold,
1604 1603 [('e', 'edit', None, _('edit patch header')),
1605 1604 ('f', 'force', None, _('delete folded patch files')),
1606 1605 ('m', 'message', '', _('set patch header to <text>')),
1607 1606 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1608 1607 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1609 1608 'qheader': (header, [],
1610 1609 _('hg qheader [PATCH]')),
1611 1610 "^qimport":
1612 1611 (qimport,
1613 1612 [('e', 'existing', None, 'import file in patch dir'),
1614 1613 ('n', 'name', '', 'patch file name'),
1615 1614 ('f', 'force', None, 'overwrite existing files')],
1616 1615 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1617 1616 "^qinit":
1618 1617 (init,
1619 1618 [('c', 'create-repo', None, 'create queue repository')],
1620 1619 'hg qinit [-c]'),
1621 1620 "qnew":
1622 1621 (new,
1623 1622 [('m', 'message', '', _('use <text> as commit message')),
1624 1623 ('l', 'logfile', '', _('read the commit message from <file>')),
1625 1624 ('f', 'force', None, _('import uncommitted changes into patch'))],
1626 1625 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1627 1626 "qnext": (next, [], 'hg qnext'),
1628 1627 "qprev": (prev, [], 'hg qprev'),
1629 1628 "^qpop":
1630 1629 (pop,
1631 1630 [('a', 'all', None, 'pop all patches'),
1632 1631 ('n', 'name', '', 'queue name to pop'),
1633 1632 ('f', 'force', None, 'forget any local changes')],
1634 1633 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1635 1634 "^qpush":
1636 1635 (push,
1637 1636 [('f', 'force', None, 'apply if the patch has rejects'),
1638 1637 ('l', 'list', None, 'list patch name in commit text'),
1639 1638 ('a', 'all', None, 'apply all patches'),
1640 1639 ('m', 'merge', None, 'merge from another queue'),
1641 1640 ('n', 'name', '', 'merge queue name')],
1642 1641 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1643 1642 "^qrefresh":
1644 1643 (refresh,
1645 1644 [('e', 'edit', None, _('edit commit message')),
1646 1645 ('m', 'message', '', _('change commit message with <text>')),
1647 1646 ('l', 'logfile', '', _('change commit message with <file> content')),
1648 1647 ('s', 'short', None, 'short refresh')],
1649 1648 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1650 1649 'qrename|qmv':
1651 1650 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1652 1651 "qrestore":
1653 1652 (restore,
1654 1653 [('d', 'delete', None, 'delete save entry'),
1655 1654 ('u', 'update', None, 'update queue working dir')],
1656 1655 'hg qrestore [-d] [-u] REV'),
1657 1656 "qsave":
1658 1657 (save,
1659 1658 [('m', 'message', '', _('use <text> as commit message')),
1660 1659 ('l', 'logfile', '', _('read the commit message from <file>')),
1661 1660 ('c', 'copy', None, 'copy patch directory'),
1662 1661 ('n', 'name', '', 'copy directory name'),
1663 1662 ('e', 'empty', None, 'clear queue status file'),
1664 1663 ('f', 'force', None, 'force copy')],
1665 1664 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1666 1665 "qseries":
1667 1666 (series,
1668 1667 [('m', 'missing', None, 'print patches not in series'),
1669 1668 ('s', 'summary', None, _('print first line of patch header'))],
1670 1669 'hg qseries [-m]'),
1671 1670 "^strip":
1672 1671 (strip,
1673 1672 [('f', 'force', None, 'force multi-head removal'),
1674 1673 ('b', 'backup', None, 'bundle unrelated changesets'),
1675 1674 ('n', 'nobackup', None, 'no backups')],
1676 1675 'hg strip [-f] [-b] [-n] REV'),
1677 1676 "qtop": (top, [], 'hg qtop'),
1678 1677 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1679 1678 "qversion": (version, [], 'hg qversion')
1680 1679 }
1681 1680
General Comments 0
You need to be logged in to leave comments. Login now