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