##// END OF EJS Templates
Teach mq about git patches
Brendan Cully -
r2934:2f190e99 default
parent child Browse files
Show More
@@ -1,1980 +1,1981 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 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 from mercurial.i18n import gettext as _
34 34 demandload(globals(), "os sys re struct traceback errno bz2")
35 35 demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util")
36 36
37 37 commands.norepo += " qclone qversion"
38 38
39 39 class statusentry:
40 40 def __init__(self, rev, name=None):
41 41 if not name:
42 42 fields = rev.split(':')
43 43 if len(fields) == 2:
44 44 self.rev, self.name = fields
45 45 else:
46 46 self.rev, self.name = None, None
47 47 else:
48 48 self.rev, self.name = rev, name
49 49
50 50 def __str__(self):
51 51 return self.rev + ':' + self.name
52 52
53 53 class queue:
54 54 def __init__(self, ui, path, patchdir=None):
55 55 self.basepath = path
56 56 self.path = patchdir or os.path.join(path, "patches")
57 57 self.opener = util.opener(self.path)
58 58 self.ui = ui
59 59 self.applied = []
60 60 self.full_series = []
61 61 self.applied_dirty = 0
62 62 self.series_dirty = 0
63 63 self.series_path = "series"
64 64 self.status_path = "status"
65 65 self.guards_path = "guards"
66 66 self.active_guards = None
67 67 self.guards_dirty = False
68 68 self._diffopts = None
69 69
70 70 if os.path.exists(self.join(self.series_path)):
71 71 self.full_series = self.opener(self.series_path).read().splitlines()
72 72 self.parse_series()
73 73
74 74 if os.path.exists(self.join(self.status_path)):
75 75 lines = self.opener(self.status_path).read().splitlines()
76 76 self.applied = [statusentry(l) for l in lines]
77 77
78 78 def diffopts(self):
79 79 if self._diffopts is None:
80 80 self._diffopts = patch.diffopts(self.ui)
81 81 return self._diffopts
82 82
83 83 def join(self, *p):
84 84 return os.path.join(self.path, *p)
85 85
86 86 def find_series(self, patch):
87 87 pre = re.compile("(\s*)([^#]+)")
88 88 index = 0
89 89 for l in self.full_series:
90 90 m = pre.match(l)
91 91 if m:
92 92 s = m.group(2)
93 93 s = s.rstrip()
94 94 if s == patch:
95 95 return index
96 96 index += 1
97 97 return None
98 98
99 99 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
100 100
101 101 def parse_series(self):
102 102 self.series = []
103 103 self.series_guards = []
104 104 for l in self.full_series:
105 105 h = l.find('#')
106 106 if h == -1:
107 107 patch = l
108 108 comment = ''
109 109 elif h == 0:
110 110 continue
111 111 else:
112 112 patch = l[:h]
113 113 comment = l[h:]
114 114 patch = patch.strip()
115 115 if patch:
116 116 self.series.append(patch)
117 117 self.series_guards.append(self.guard_re.findall(comment))
118 118
119 119 def check_guard(self, guard):
120 120 bad_chars = '# \t\r\n\f'
121 121 first = guard[0]
122 122 for c in '-+':
123 123 if first == c:
124 124 return (_('guard %r starts with invalid character: %r') %
125 125 (guard, c))
126 126 for c in bad_chars:
127 127 if c in guard:
128 128 return _('invalid character in guard %r: %r') % (guard, c)
129 129
130 130 def set_active(self, guards):
131 131 for guard in guards:
132 132 bad = self.check_guard(guard)
133 133 if bad:
134 134 raise util.Abort(bad)
135 135 guards = dict.fromkeys(guards).keys()
136 136 guards.sort()
137 137 self.ui.debug('active guards: %s\n' % ' '.join(guards))
138 138 self.active_guards = guards
139 139 self.guards_dirty = True
140 140
141 141 def active(self):
142 142 if self.active_guards is None:
143 143 self.active_guards = []
144 144 try:
145 145 guards = self.opener(self.guards_path).read().split()
146 146 except IOError, err:
147 147 if err.errno != errno.ENOENT: raise
148 148 guards = []
149 149 for i, guard in enumerate(guards):
150 150 bad = self.check_guard(guard)
151 151 if bad:
152 152 self.ui.warn('%s:%d: %s\n' %
153 153 (self.join(self.guards_path), i + 1, bad))
154 154 else:
155 155 self.active_guards.append(guard)
156 156 return self.active_guards
157 157
158 158 def set_guards(self, idx, guards):
159 159 for g in guards:
160 160 if len(g) < 2:
161 161 raise util.Abort(_('guard %r too short') % g)
162 162 if g[0] not in '-+':
163 163 raise util.Abort(_('guard %r starts with invalid char') % g)
164 164 bad = self.check_guard(g[1:])
165 165 if bad:
166 166 raise util.Abort(bad)
167 167 drop = self.guard_re.sub('', self.full_series[idx])
168 168 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
169 169 self.parse_series()
170 170 self.series_dirty = True
171 171
172 172 def pushable(self, idx):
173 173 if isinstance(idx, str):
174 174 idx = self.series.index(idx)
175 175 patchguards = self.series_guards[idx]
176 176 if not patchguards:
177 177 return True, None
178 178 default = False
179 179 guards = self.active()
180 180 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
181 181 if exactneg:
182 182 return False, exactneg[0]
183 183 pos = [g for g in patchguards if g[0] == '+']
184 184 exactpos = [g for g in pos if g[1:] in guards]
185 185 if pos:
186 186 if exactpos:
187 187 return True, exactpos[0]
188 188 return False, pos
189 189 return True, ''
190 190
191 191 def explain_pushable(self, idx, all_patches=False):
192 192 write = all_patches and self.ui.write or self.ui.warn
193 193 if all_patches or self.ui.verbose:
194 194 if isinstance(idx, str):
195 195 idx = self.series.index(idx)
196 196 pushable, why = self.pushable(idx)
197 197 if all_patches and pushable:
198 198 if why is None:
199 199 write(_('allowing %s - no guards in effect\n') %
200 200 self.series[idx])
201 201 else:
202 202 if not why:
203 203 write(_('allowing %s - no matching negative guards\n') %
204 204 self.series[idx])
205 205 else:
206 206 write(_('allowing %s - guarded by %r\n') %
207 207 (self.series[idx], why))
208 208 if not pushable:
209 209 if why:
210 210 write(_('skipping %s - guarded by %r\n') %
211 211 (self.series[idx], ' '.join(why)))
212 212 else:
213 213 write(_('skipping %s - no matching guards\n') %
214 214 self.series[idx])
215 215
216 216 def save_dirty(self):
217 217 def write_list(items, path):
218 218 fp = self.opener(path, 'w')
219 219 for i in items:
220 220 print >> fp, i
221 221 fp.close()
222 222 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
223 223 if self.series_dirty: write_list(self.full_series, self.series_path)
224 224 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
225 225
226 226 def readheaders(self, patch):
227 227 def eatdiff(lines):
228 228 while lines:
229 229 l = lines[-1]
230 230 if (l.startswith("diff -") or
231 231 l.startswith("Index:") or
232 232 l.startswith("===========")):
233 233 del lines[-1]
234 234 else:
235 235 break
236 236 def eatempty(lines):
237 237 while lines:
238 238 l = lines[-1]
239 239 if re.match('\s*$', l):
240 240 del lines[-1]
241 241 else:
242 242 break
243 243
244 244 pf = self.join(patch)
245 245 message = []
246 246 comments = []
247 247 user = None
248 248 date = None
249 249 format = None
250 250 subject = None
251 251 diffstart = 0
252 252
253 253 for line in file(pf):
254 254 line = line.rstrip()
255 if line.startswith('diff --git'):
256 diffstart = 2
257 break
255 258 if diffstart:
256 259 if line.startswith('+++ '):
257 260 diffstart = 2
258 261 break
259 262 if line.startswith("--- "):
260 263 diffstart = 1
261 264 continue
262 265 elif format == "hgpatch":
263 266 # parse values when importing the result of an hg export
264 267 if line.startswith("# User "):
265 268 user = line[7:]
266 269 elif line.startswith("# Date "):
267 270 date = line[7:]
268 271 elif not line.startswith("# ") and line:
269 272 message.append(line)
270 273 format = None
271 274 elif line == '# HG changeset patch':
272 275 format = "hgpatch"
273 276 elif (format != "tagdone" and (line.startswith("Subject: ") or
274 277 line.startswith("subject: "))):
275 278 subject = line[9:]
276 279 format = "tag"
277 280 elif (format != "tagdone" and (line.startswith("From: ") or
278 281 line.startswith("from: "))):
279 282 user = line[6:]
280 283 format = "tag"
281 284 elif format == "tag" and line == "":
282 285 # when looking for tags (subject: from: etc) they
283 286 # end once you find a blank line in the source
284 287 format = "tagdone"
285 288 elif message or line:
286 289 message.append(line)
287 290 comments.append(line)
288 291
289 292 eatdiff(message)
290 293 eatdiff(comments)
291 294 eatempty(message)
292 295 eatempty(comments)
293 296
294 297 # make sure message isn't empty
295 298 if format and format.startswith("tag") and subject:
296 299 message.insert(0, "")
297 300 message.insert(0, subject)
298 301 return (message, comments, user, date, diffstart > 1)
299 302
300 303 def printdiff(self, repo, node1, node2=None, files=None,
301 304 fp=None, changes=None, opts=None):
302 305 patch.diff(repo, node1, node2, files,
303 306 fp=fp, changes=changes, opts=self.diffopts())
304 307
305 308 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
306 309 # first try just applying the patch
307 310 (err, n) = self.apply(repo, [ patch ], update_status=False,
308 311 strict=True, merge=rev, wlock=wlock)
309 312
310 313 if err == 0:
311 314 return (err, n)
312 315
313 316 if n is None:
314 317 raise util.Abort(_("apply failed for patch %s") % patch)
315 318
316 319 self.ui.warn("patch didn't work out, merging %s\n" % patch)
317 320
318 321 # apply failed, strip away that rev and merge.
319 322 hg.clean(repo, head, wlock=wlock)
320 323 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
321 324
322 325 c = repo.changelog.read(rev)
323 326 ret = hg.merge(repo, rev, wlock=wlock)
324 327 if ret:
325 328 raise util.Abort(_("update returned %d") % ret)
326 329 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
327 330 if n == None:
328 331 raise util.Abort(_("repo commit failed"))
329 332 try:
330 333 message, comments, user, date, patchfound = mergeq.readheaders(patch)
331 334 except:
332 335 raise util.Abort(_("unable to read %s") % patch)
333 336
334 337 patchf = self.opener(patch, "w")
335 338 if comments:
336 339 comments = "\n".join(comments) + '\n\n'
337 340 patchf.write(comments)
338 341 self.printdiff(repo, head, n, fp=patchf)
339 342 patchf.close()
340 343 return (0, n)
341 344
342 345 def qparents(self, repo, rev=None):
343 346 if rev is None:
344 347 (p1, p2) = repo.dirstate.parents()
345 348 if p2 == revlog.nullid:
346 349 return p1
347 350 if len(self.applied) == 0:
348 351 return None
349 352 return revlog.bin(self.applied[-1].rev)
350 353 pp = repo.changelog.parents(rev)
351 354 if pp[1] != revlog.nullid:
352 355 arevs = [ x.rev for x in self.applied ]
353 356 p0 = revlog.hex(pp[0])
354 357 p1 = revlog.hex(pp[1])
355 358 if p0 in arevs:
356 359 return pp[0]
357 360 if p1 in arevs:
358 361 return pp[1]
359 362 return pp[0]
360 363
361 364 def mergepatch(self, repo, mergeq, series, wlock):
362 365 if len(self.applied) == 0:
363 366 # each of the patches merged in will have two parents. This
364 367 # can confuse the qrefresh, qdiff, and strip code because it
365 368 # needs to know which parent is actually in the patch queue.
366 369 # so, we insert a merge marker with only one parent. This way
367 370 # the first patch in the queue is never a merge patch
368 371 #
369 372 pname = ".hg.patches.merge.marker"
370 373 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
371 374 wlock=wlock)
372 375 self.applied.append(statusentry(revlog.hex(n), pname))
373 376 self.applied_dirty = 1
374 377
375 378 head = self.qparents(repo)
376 379
377 380 for patch in series:
378 381 patch = mergeq.lookup(patch, strict=True)
379 382 if not patch:
380 383 self.ui.warn("patch %s does not exist\n" % patch)
381 384 return (1, None)
382 385 pushable, reason = self.pushable(patch)
383 386 if not pushable:
384 387 self.explain_pushable(patch, all_patches=True)
385 388 continue
386 389 info = mergeq.isapplied(patch)
387 390 if not info:
388 391 self.ui.warn("patch %s is not applied\n" % patch)
389 392 return (1, None)
390 393 rev = revlog.bin(info[1])
391 394 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
392 395 if head:
393 396 self.applied.append(statusentry(revlog.hex(head), patch))
394 397 self.applied_dirty = 1
395 398 if err:
396 399 return (err, head)
397 400 return (0, head)
398 401
399 402 def patch(self, repo, patchfile):
400 403 '''Apply patchfile to the working directory.
401 404 patchfile: file name of patch'''
402 405 try:
403 406 (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
404 407 cwd=repo.root)
405 408 except Exception, inst:
406 409 self.ui.note(str(inst) + '\n')
407 410 if not self.ui.verbose:
408 411 self.ui.warn("patch failed, unable to continue (try -v)\n")
409 412 return (False, [], False)
410 413
411 return (True, files.keys(), fuzz)
414 return (True, files, fuzz)
412 415
413 416 def apply(self, repo, series, list=False, update_status=True,
414 417 strict=False, patchdir=None, merge=None, wlock=None):
415 418 # TODO unify with commands.py
416 419 if not patchdir:
417 420 patchdir = self.path
418 421 err = 0
419 422 if not wlock:
420 423 wlock = repo.wlock()
421 424 lock = repo.lock()
422 425 tr = repo.transaction()
423 426 n = None
424 for patch in series:
425 pushable, reason = self.pushable(patch)
427 for patchname in series:
428 pushable, reason = self.pushable(patchname)
426 429 if not pushable:
427 self.explain_pushable(patch, all_patches=True)
430 self.explain_pushable(patchname, all_patches=True)
428 431 continue
429 self.ui.warn("applying %s\n" % patch)
430 pf = os.path.join(patchdir, patch)
432 self.ui.warn("applying %s\n" % patchname)
433 pf = os.path.join(patchdir, patchname)
431 434
432 435 try:
433 message, comments, user, date, patchfound = self.readheaders(patch)
436 message, comments, user, date, patchfound = self.readheaders(patchname)
434 437 except:
435 self.ui.warn("Unable to read %s\n" % pf)
438 self.ui.warn("Unable to read %s\n" % patchname)
436 439 err = 1
437 440 break
438 441
439 442 if not message:
440 message = "imported patch %s\n" % patch
443 message = "imported patch %s\n" % patchname
441 444 else:
442 445 if list:
443 message.append("\nimported patch %s" % patch)
446 message.append("\nimported patch %s" % patchname)
444 447 message = '\n'.join(message)
445 448
446 449 (patcherr, files, fuzz) = self.patch(repo, pf)
447 450 patcherr = not patcherr
448 451
449 if merge and len(files) > 0:
452 if merge and files:
450 453 # Mark as merged and update dirstate parent info
451 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
454 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
452 455 p1, p2 = repo.dirstate.parents()
453 456 repo.dirstate.setparents(p1, merge)
454 if len(files) > 0:
455 cwd = repo.getcwd()
456 cfiles = files
457 if cwd:
458 cfiles = [util.pathto(cwd, f) for f in files]
459 cmdutil.addremove(repo, cfiles, wlock=wlock)
457 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
460 458 n = repo.commit(files, message, user, date, force=1, lock=lock,
461 459 wlock=wlock)
462 460
463 461 if n == None:
464 462 raise util.Abort(_("repo commit failed"))
465 463
466 464 if update_status:
467 self.applied.append(statusentry(revlog.hex(n), patch))
465 self.applied.append(statusentry(revlog.hex(n), patchname))
468 466
469 467 if patcherr:
470 468 if not patchfound:
471 self.ui.warn("patch %s is empty\n" % patch)
469 self.ui.warn("patch %s is empty\n" % patchname)
472 470 err = 0
473 471 else:
474 472 self.ui.warn("patch failed, rejects left in working dir\n")
475 473 err = 1
476 474 break
477 475
478 476 if fuzz and strict:
479 477 self.ui.warn("fuzz found when applying patch, stopping\n")
480 478 err = 1
481 479 break
482 480 tr.close()
483 481 return (err, n)
484 482
485 483 def delete(self, repo, patches, keep=False):
486 484 realpatches = []
487 485 for patch in patches:
488 486 patch = self.lookup(patch, strict=True)
489 487 info = self.isapplied(patch)
490 488 if info:
491 489 raise util.Abort(_("cannot delete applied patch %s") % patch)
492 490 if patch not in self.series:
493 491 raise util.Abort(_("patch %s not in series file") % patch)
494 492 realpatches.append(patch)
495 493
496 494 if not keep:
497 495 r = self.qrepo()
498 496 if r:
499 497 r.remove(realpatches, True)
500 498 else:
501 499 os.unlink(self.join(patch))
502 500
503 501 indices = [self.find_series(p) for p in realpatches]
504 502 indices.sort()
505 503 for i in indices[-1::-1]:
506 504 del self.full_series[i]
507 505 self.parse_series()
508 506 self.series_dirty = 1
509 507
510 508 def check_toppatch(self, repo):
511 509 if len(self.applied) > 0:
512 510 top = revlog.bin(self.applied[-1].rev)
513 511 pp = repo.dirstate.parents()
514 512 if top not in pp:
515 513 raise util.Abort(_("queue top not at same revision as working directory"))
516 514 return top
517 515 return None
518 516 def check_localchanges(self, repo, force=False, refresh=True):
519 517 m, a, r, d = repo.status()[:4]
520 518 if m or a or r or d:
521 519 if not force:
522 520 if refresh:
523 521 raise util.Abort(_("local changes found, refresh first"))
524 522 else:
525 523 raise util.Abort(_("local changes found"))
526 524 return m, a, r, d
527 525 def new(self, repo, patch, msg=None, force=None):
528 526 if os.path.exists(self.join(patch)):
529 527 raise util.Abort(_('patch "%s" already exists') % patch)
530 528 m, a, r, d = self.check_localchanges(repo, force)
531 529 commitfiles = m + a + r
532 530 self.check_toppatch(repo)
533 531 wlock = repo.wlock()
534 532 insert = self.full_series_end()
535 533 if msg:
536 534 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
537 535 wlock=wlock)
538 536 else:
539 537 n = repo.commit(commitfiles,
540 538 "New patch: %s" % patch, force=True, wlock=wlock)
541 539 if n == None:
542 540 raise util.Abort(_("repo commit failed"))
543 541 self.full_series[insert:insert] = [patch]
544 542 self.applied.append(statusentry(revlog.hex(n), patch))
545 543 self.parse_series()
546 544 self.series_dirty = 1
547 545 self.applied_dirty = 1
548 546 p = self.opener(patch, "w")
549 547 if msg:
550 548 msg = msg + "\n"
551 549 p.write(msg)
552 550 p.close()
553 551 wlock = None
554 552 r = self.qrepo()
555 553 if r: r.add([patch])
556 554 if commitfiles:
557 555 self.refresh(repo, short=True)
558 556
559 557 def strip(self, repo, rev, update=True, backup="all", wlock=None):
560 558 def limitheads(chlog, stop):
561 559 """return the list of all nodes that have no children"""
562 560 p = {}
563 561 h = []
564 562 stoprev = 0
565 563 if stop in chlog.nodemap:
566 564 stoprev = chlog.rev(stop)
567 565
568 566 for r in range(chlog.count() - 1, -1, -1):
569 567 n = chlog.node(r)
570 568 if n not in p:
571 569 h.append(n)
572 570 if n == stop:
573 571 break
574 572 if r < stoprev:
575 573 break
576 574 for pn in chlog.parents(n):
577 575 p[pn] = 1
578 576 return h
579 577
580 578 def bundle(cg):
581 579 backupdir = repo.join("strip-backup")
582 580 if not os.path.isdir(backupdir):
583 581 os.mkdir(backupdir)
584 582 name = os.path.join(backupdir, "%s" % revlog.short(rev))
585 583 name = savename(name)
586 584 self.ui.warn("saving bundle to %s\n" % name)
587 585 # TODO, exclusive open
588 586 f = open(name, "wb")
589 587 try:
590 588 f.write("HG10")
591 589 z = bz2.BZ2Compressor(9)
592 590 while 1:
593 591 chunk = cg.read(4096)
594 592 if not chunk:
595 593 break
596 594 f.write(z.compress(chunk))
597 595 f.write(z.flush())
598 596 except:
599 597 os.unlink(name)
600 598 raise
601 599 f.close()
602 600 return name
603 601
604 602 def stripall(rev, revnum):
605 603 cl = repo.changelog
606 604 c = cl.read(rev)
607 605 mm = repo.manifest.read(c[0])
608 606 seen = {}
609 607
610 608 for x in xrange(revnum, cl.count()):
611 609 c = cl.read(cl.node(x))
612 610 for f in c[3]:
613 611 if f in seen:
614 612 continue
615 613 seen[f] = 1
616 614 if f in mm:
617 615 filerev = mm[f]
618 616 else:
619 617 filerev = 0
620 618 seen[f] = filerev
621 619 # we go in two steps here so the strip loop happens in a
622 620 # sensible order. When stripping many files, this helps keep
623 621 # our disk access patterns under control.
624 622 seen_list = seen.keys()
625 623 seen_list.sort()
626 624 for f in seen_list:
627 625 ff = repo.file(f)
628 626 filerev = seen[f]
629 627 if filerev != 0:
630 628 if filerev in ff.nodemap:
631 629 filerev = ff.rev(filerev)
632 630 else:
633 631 filerev = 0
634 632 ff.strip(filerev, revnum)
635 633
636 634 if not wlock:
637 635 wlock = repo.wlock()
638 636 lock = repo.lock()
639 637 chlog = repo.changelog
640 638 # TODO delete the undo files, and handle undo of merge sets
641 639 pp = chlog.parents(rev)
642 640 revnum = chlog.rev(rev)
643 641
644 642 if update:
645 643 self.check_localchanges(repo, refresh=False)
646 644 urev = self.qparents(repo, rev)
647 645 hg.clean(repo, urev, wlock=wlock)
648 646 repo.dirstate.write()
649 647
650 648 # save is a list of all the branches we are truncating away
651 649 # that we actually want to keep. changegroup will be used
652 650 # to preserve them and add them back after the truncate
653 651 saveheads = []
654 652 savebases = {}
655 653
656 654 heads = limitheads(chlog, rev)
657 655 seen = {}
658 656
659 657 # search through all the heads, finding those where the revision
660 658 # we want to strip away is an ancestor. Also look for merges
661 659 # that might be turned into new heads by the strip.
662 660 while heads:
663 661 h = heads.pop()
664 662 n = h
665 663 while True:
666 664 seen[n] = 1
667 665 pp = chlog.parents(n)
668 666 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
669 667 if pp[1] not in seen:
670 668 heads.append(pp[1])
671 669 if pp[0] == revlog.nullid:
672 670 break
673 671 if chlog.rev(pp[0]) < revnum:
674 672 break
675 673 n = pp[0]
676 674 if n == rev:
677 675 break
678 676 r = chlog.reachable(h, rev)
679 677 if rev not in r:
680 678 saveheads.append(h)
681 679 for x in r:
682 680 if chlog.rev(x) > revnum:
683 681 savebases[x] = 1
684 682
685 683 # create a changegroup for all the branches we need to keep
686 684 if backup == "all":
687 685 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
688 686 bundle(backupch)
689 687 if saveheads:
690 688 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
691 689 chgrpfile = bundle(backupch)
692 690
693 691 stripall(rev, revnum)
694 692
695 693 change = chlog.read(rev)
696 694 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
697 695 chlog.strip(revnum, revnum)
698 696 if saveheads:
699 697 self.ui.status("adding branch\n")
700 698 commands.unbundle(self.ui, repo, chgrpfile, update=False)
701 699 if backup != "strip":
702 700 os.unlink(chgrpfile)
703 701
704 702 def isapplied(self, patch):
705 703 """returns (index, rev, patch)"""
706 704 for i in xrange(len(self.applied)):
707 705 a = self.applied[i]
708 706 if a.name == patch:
709 707 return (i, a.rev, a.name)
710 708 return None
711 709
712 710 # if the exact patch name does not exist, we try a few
713 711 # variations. If strict is passed, we try only #1
714 712 #
715 713 # 1) a number to indicate an offset in the series file
716 714 # 2) a unique substring of the patch name was given
717 715 # 3) patchname[-+]num to indicate an offset in the series file
718 716 def lookup(self, patch, strict=False):
719 717 patch = patch and str(patch)
720 718
721 719 def partial_name(s):
722 720 if s in self.series:
723 721 return s
724 722 matches = [x for x in self.series if s in x]
725 723 if len(matches) > 1:
726 724 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
727 725 for m in matches:
728 726 self.ui.warn(' %s\n' % m)
729 727 return None
730 728 if matches:
731 729 return matches[0]
732 730 if len(self.series) > 0 and len(self.applied) > 0:
733 731 if s == 'qtip':
734 732 return self.series[self.series_end()-1]
735 733 if s == 'qbase':
736 734 return self.series[0]
737 735 return None
738 736 if patch == None:
739 737 return None
740 738
741 739 # we don't want to return a partial match until we make
742 740 # sure the file name passed in does not exist (checked below)
743 741 res = partial_name(patch)
744 742 if res and res == patch:
745 743 return res
746 744
747 745 if not os.path.isfile(self.join(patch)):
748 746 try:
749 747 sno = int(patch)
750 748 except(ValueError, OverflowError):
751 749 pass
752 750 else:
753 751 if sno < len(self.series):
754 752 return self.series[sno]
755 753 if not strict:
756 754 # return any partial match made above
757 755 if res:
758 756 return res
759 757 minus = patch.rsplit('-', 1)
760 758 if len(minus) > 1:
761 759 res = partial_name(minus[0])
762 760 if res:
763 761 i = self.series.index(res)
764 762 try:
765 763 off = int(minus[1] or 1)
766 764 except(ValueError, OverflowError):
767 765 pass
768 766 else:
769 767 if i - off >= 0:
770 768 return self.series[i - off]
771 769 plus = patch.rsplit('+', 1)
772 770 if len(plus) > 1:
773 771 res = partial_name(plus[0])
774 772 if res:
775 773 i = self.series.index(res)
776 774 try:
777 775 off = int(plus[1] or 1)
778 776 except(ValueError, OverflowError):
779 777 pass
780 778 else:
781 779 if i + off < len(self.series):
782 780 return self.series[i + off]
783 781 raise util.Abort(_("patch %s not in series") % patch)
784 782
785 783 def push(self, repo, patch=None, force=False, list=False,
786 784 mergeq=None, wlock=None):
787 785 if not wlock:
788 786 wlock = repo.wlock()
789 787 patch = self.lookup(patch)
790 788 if patch and self.isapplied(patch):
791 789 self.ui.warn(_("patch %s is already applied\n") % patch)
792 790 sys.exit(1)
793 791 if self.series_end() == len(self.series):
794 792 self.ui.warn(_("patch series fully applied\n"))
795 793 sys.exit(1)
796 794 if not force:
797 795 self.check_localchanges(repo)
798 796
799 797 self.applied_dirty = 1;
800 798 start = self.series_end()
801 799 if start > 0:
802 800 self.check_toppatch(repo)
803 801 if not patch:
804 802 patch = self.series[start]
805 803 end = start + 1
806 804 else:
807 805 end = self.series.index(patch, start) + 1
808 806 s = self.series[start:end]
809 807 if mergeq:
810 808 ret = self.mergepatch(repo, mergeq, s, wlock)
811 809 else:
812 810 ret = self.apply(repo, s, list, wlock=wlock)
813 811 top = self.applied[-1].name
814 812 if ret[0]:
815 813 self.ui.write("Errors during apply, please fix and refresh %s\n" %
816 814 top)
817 815 else:
818 816 self.ui.write("Now at: %s\n" % top)
819 817 return ret[0]
820 818
821 819 def pop(self, repo, patch=None, force=False, update=True, all=False,
822 820 wlock=None):
823 821 def getfile(f, rev):
824 822 t = repo.file(f).read(rev)
825 823 try:
826 824 repo.wfile(f, "w").write(t)
827 825 except IOError:
828 826 try:
829 827 os.makedirs(os.path.dirname(repo.wjoin(f)))
830 828 except OSError, err:
831 829 if err.errno != errno.EEXIST: raise
832 830 repo.wfile(f, "w").write(t)
833 831
834 832 if not wlock:
835 833 wlock = repo.wlock()
836 834 if patch:
837 835 # index, rev, patch
838 836 info = self.isapplied(patch)
839 837 if not info:
840 838 patch = self.lookup(patch)
841 839 info = self.isapplied(patch)
842 840 if not info:
843 841 raise util.Abort(_("patch %s is not applied") % patch)
844 842 if len(self.applied) == 0:
845 843 self.ui.warn(_("no patches applied\n"))
846 844 sys.exit(1)
847 845
848 846 if not update:
849 847 parents = repo.dirstate.parents()
850 848 rr = [ revlog.bin(x.rev) for x in self.applied ]
851 849 for p in parents:
852 850 if p in rr:
853 851 self.ui.warn("qpop: forcing dirstate update\n")
854 852 update = True
855 853
856 854 if not force and update:
857 855 self.check_localchanges(repo)
858 856
859 857 self.applied_dirty = 1;
860 858 end = len(self.applied)
861 859 if not patch:
862 860 if all:
863 861 popi = 0
864 862 else:
865 863 popi = len(self.applied) - 1
866 864 else:
867 865 popi = info[0] + 1
868 866 if popi >= end:
869 867 self.ui.warn("qpop: %s is already at the top\n" % patch)
870 868 return
871 869 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
872 870
873 871 start = info[0]
874 872 rev = revlog.bin(info[1])
875 873
876 874 # we know there are no local changes, so we can make a simplified
877 875 # form of hg.update.
878 876 if update:
879 877 top = self.check_toppatch(repo)
880 878 qp = self.qparents(repo, rev)
881 879 changes = repo.changelog.read(qp)
882 880 mmap = repo.manifest.read(changes[0])
883 881 m, a, r, d, u = repo.status(qp, top)[:5]
884 882 if d:
885 883 raise util.Abort("deletions found between repo revs")
886 884 for f in m:
887 885 getfile(f, mmap[f])
888 886 for f in r:
889 887 getfile(f, mmap[f])
890 888 util.set_exec(repo.wjoin(f), mmap.execf(f))
891 889 repo.dirstate.update(m + r, 'n')
892 890 for f in a:
893 891 try: os.unlink(repo.wjoin(f))
894 892 except: raise
895 893 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
896 894 except: pass
897 895 if a:
898 896 repo.dirstate.forget(a)
899 897 repo.dirstate.setparents(qp, revlog.nullid)
900 898 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
901 899 del self.applied[start:end]
902 900 if len(self.applied):
903 901 self.ui.write("Now at: %s\n" % self.applied[-1].name)
904 902 else:
905 903 self.ui.write("Patch queue now empty\n")
906 904
907 905 def diff(self, repo, files):
908 906 top = self.check_toppatch(repo)
909 907 if not top:
910 908 self.ui.write("No patches applied\n")
911 909 return
912 910 qp = self.qparents(repo, top)
913 911 self.printdiff(repo, qp, files=files)
914 912
915 913 def refresh(self, repo, msg='', short=False):
916 914 if len(self.applied) == 0:
917 915 self.ui.write("No patches applied\n")
918 916 return
919 917 wlock = repo.wlock()
920 918 self.check_toppatch(repo)
921 919 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
922 920 top = revlog.bin(top)
923 921 cparents = repo.changelog.parents(top)
924 922 patchparent = self.qparents(repo, top)
925 923 message, comments, user, date, patchfound = self.readheaders(patch)
926 924
927 925 patchf = self.opener(patch, "w")
928 926 msg = msg.rstrip()
929 927 if msg:
930 928 if comments:
931 929 # Remove existing message.
932 930 ci = 0
933 931 for mi in range(len(message)):
934 932 while message[mi] != comments[ci]:
935 933 ci += 1
936 934 del comments[ci]
937 935 comments.append(msg)
938 936 if comments:
939 937 comments = "\n".join(comments) + '\n\n'
940 938 patchf.write(comments)
941 939
942 940 tip = repo.changelog.tip()
943 941 if top == tip:
944 942 # if the top of our patch queue is also the tip, there is an
945 943 # optimization here. We update the dirstate in place and strip
946 944 # off the tip commit. Then just commit the current directory
947 945 # tree. We can also send repo.commit the list of files
948 946 # changed to speed up the diff
949 947 #
950 948 # in short mode, we only diff the files included in the
951 949 # patch already
952 950 #
953 951 # this should really read:
954 952 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
955 953 # but we do it backwards to take advantage of manifest/chlog
956 954 # caching against the next repo.status call
957 955 #
958 956 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
959 957 if short:
960 958 filelist = mm + aa + dd
961 959 else:
962 960 filelist = None
963 961 m, a, r, d, u = repo.status(files=filelist)[:5]
964 962
965 963 # we might end up with files that were added between tip and
966 964 # the dirstate parent, but then changed in the local dirstate.
967 965 # in this case, we want them to only show up in the added section
968 966 for x in m:
969 967 if x not in aa:
970 968 mm.append(x)
971 969 # we might end up with files added by the local dirstate that
972 970 # were deleted by the patch. In this case, they should only
973 971 # show up in the changed section.
974 972 for x in a:
975 973 if x in dd:
976 974 del dd[dd.index(x)]
977 975 mm.append(x)
978 976 else:
979 977 aa.append(x)
980 978 # make sure any files deleted in the local dirstate
981 979 # are not in the add or change column of the patch
982 980 forget = []
983 981 for x in d + r:
984 982 if x in aa:
985 983 del aa[aa.index(x)]
986 984 forget.append(x)
987 985 continue
988 986 elif x in mm:
989 987 del mm[mm.index(x)]
990 988 dd.append(x)
991 989
992 990 m = list(util.unique(mm))
993 991 r = list(util.unique(dd))
994 992 a = list(util.unique(aa))
995 993 filelist = list(util.unique(m + r + a))
996 994 self.printdiff(repo, patchparent, files=filelist,
997 995 changes=(m, a, r, [], u), fp=patchf)
998 996 patchf.close()
999 997
1000 998 changes = repo.changelog.read(tip)
1001 999 repo.dirstate.setparents(*cparents)
1000 copies = [(f, repo.dirstate.copied(f)) for f in a]
1002 1001 repo.dirstate.update(a, 'a')
1002 for dst, src in copies:
1003 repo.dirstate.copy(src, dst)
1003 1004 repo.dirstate.update(r, 'r')
1004 1005 repo.dirstate.update(m, 'n')
1005 1006 repo.dirstate.forget(forget)
1006 1007
1007 1008 if not msg:
1008 1009 if not message:
1009 1010 message = "patch queue: %s\n" % patch
1010 1011 else:
1011 1012 message = "\n".join(message)
1012 1013 else:
1013 1014 message = msg
1014 1015
1015 1016 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1016 1017 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1017 1018 self.applied[-1] = statusentry(revlog.hex(n), patch)
1018 1019 self.applied_dirty = 1
1019 1020 else:
1020 1021 self.printdiff(repo, patchparent, fp=patchf)
1021 1022 patchf.close()
1022 1023 self.pop(repo, force=True, wlock=wlock)
1023 1024 self.push(repo, force=True, wlock=wlock)
1024 1025
1025 1026 def init(self, repo, create=False):
1026 1027 if os.path.isdir(self.path):
1027 1028 raise util.Abort(_("patch queue directory already exists"))
1028 1029 os.mkdir(self.path)
1029 1030 if create:
1030 1031 return self.qrepo(create=True)
1031 1032
1032 1033 def unapplied(self, repo, patch=None):
1033 1034 if patch and patch not in self.series:
1034 1035 raise util.Abort(_("patch %s is not in series file") % patch)
1035 1036 if not patch:
1036 1037 start = self.series_end()
1037 1038 else:
1038 1039 start = self.series.index(patch) + 1
1039 1040 unapplied = []
1040 1041 for i in xrange(start, len(self.series)):
1041 1042 pushable, reason = self.pushable(i)
1042 1043 if pushable:
1043 1044 unapplied.append((i, self.series[i]))
1044 1045 self.explain_pushable(i)
1045 1046 return unapplied
1046 1047
1047 1048 def qseries(self, repo, missing=None, summary=False):
1048 1049 start = self.series_end(all_patches=True)
1049 1050 if not missing:
1050 1051 for i in range(len(self.series)):
1051 1052 patch = self.series[i]
1052 1053 if self.ui.verbose:
1053 1054 if i < start:
1054 1055 status = 'A'
1055 1056 elif self.pushable(i)[0]:
1056 1057 status = 'U'
1057 1058 else:
1058 1059 status = 'G'
1059 1060 self.ui.write('%d %s ' % (i, status))
1060 1061 if summary:
1061 1062 msg = self.readheaders(patch)[0]
1062 1063 msg = msg and ': ' + msg[0] or ': '
1063 1064 else:
1064 1065 msg = ''
1065 1066 self.ui.write('%s%s\n' % (patch, msg))
1066 1067 else:
1067 1068 msng_list = []
1068 1069 for root, dirs, files in os.walk(self.path):
1069 1070 d = root[len(self.path) + 1:]
1070 1071 for f in files:
1071 1072 fl = os.path.join(d, f)
1072 1073 if (fl not in self.series and
1073 1074 fl not in (self.status_path, self.series_path)
1074 1075 and not fl.startswith('.')):
1075 1076 msng_list.append(fl)
1076 1077 msng_list.sort()
1077 1078 for x in msng_list:
1078 1079 if self.ui.verbose:
1079 1080 self.ui.write("D ")
1080 1081 self.ui.write("%s\n" % x)
1081 1082
1082 1083 def issaveline(self, l):
1083 1084 if l.name == '.hg.patches.save.line':
1084 1085 return True
1085 1086
1086 1087 def qrepo(self, create=False):
1087 1088 if create or os.path.isdir(self.join(".hg")):
1088 1089 return hg.repository(self.ui, path=self.path, create=create)
1089 1090
1090 1091 def restore(self, repo, rev, delete=None, qupdate=None):
1091 1092 c = repo.changelog.read(rev)
1092 1093 desc = c[4].strip()
1093 1094 lines = desc.splitlines()
1094 1095 i = 0
1095 1096 datastart = None
1096 1097 series = []
1097 1098 applied = []
1098 1099 qpp = None
1099 1100 for i in xrange(0, len(lines)):
1100 1101 if lines[i] == 'Patch Data:':
1101 1102 datastart = i + 1
1102 1103 elif lines[i].startswith('Dirstate:'):
1103 1104 l = lines[i].rstrip()
1104 1105 l = l[10:].split(' ')
1105 1106 qpp = [ hg.bin(x) for x in l ]
1106 1107 elif datastart != None:
1107 1108 l = lines[i].rstrip()
1108 1109 se = statusentry(l)
1109 1110 file_ = se.name
1110 1111 if se.rev:
1111 1112 applied.append(se)
1112 1113 series.append(file_)
1113 1114 if datastart == None:
1114 1115 self.ui.warn("No saved patch data found\n")
1115 1116 return 1
1116 1117 self.ui.warn("restoring status: %s\n" % lines[0])
1117 1118 self.full_series = series
1118 1119 self.applied = applied
1119 1120 self.parse_series()
1120 1121 self.series_dirty = 1
1121 1122 self.applied_dirty = 1
1122 1123 heads = repo.changelog.heads()
1123 1124 if delete:
1124 1125 if rev not in heads:
1125 1126 self.ui.warn("save entry has children, leaving it alone\n")
1126 1127 else:
1127 1128 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1128 1129 pp = repo.dirstate.parents()
1129 1130 if rev in pp:
1130 1131 update = True
1131 1132 else:
1132 1133 update = False
1133 1134 self.strip(repo, rev, update=update, backup='strip')
1134 1135 if qpp:
1135 1136 self.ui.warn("saved queue repository parents: %s %s\n" %
1136 1137 (hg.short(qpp[0]), hg.short(qpp[1])))
1137 1138 if qupdate:
1138 1139 print "queue directory updating"
1139 1140 r = self.qrepo()
1140 1141 if not r:
1141 1142 self.ui.warn("Unable to load queue repository\n")
1142 1143 return 1
1143 1144 hg.clean(r, qpp[0])
1144 1145
1145 1146 def save(self, repo, msg=None):
1146 1147 if len(self.applied) == 0:
1147 1148 self.ui.warn("save: no patches applied, exiting\n")
1148 1149 return 1
1149 1150 if self.issaveline(self.applied[-1]):
1150 1151 self.ui.warn("status is already saved\n")
1151 1152 return 1
1152 1153
1153 1154 ar = [ ':' + x for x in self.full_series ]
1154 1155 if not msg:
1155 1156 msg = "hg patches saved state"
1156 1157 else:
1157 1158 msg = "hg patches: " + msg.rstrip('\r\n')
1158 1159 r = self.qrepo()
1159 1160 if r:
1160 1161 pp = r.dirstate.parents()
1161 1162 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1162 1163 msg += "\n\nPatch Data:\n"
1163 1164 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1164 1165 "\n".join(ar) + '\n' or "")
1165 1166 n = repo.commit(None, text, user=None, force=1)
1166 1167 if not n:
1167 1168 self.ui.warn("repo commit failed\n")
1168 1169 return 1
1169 1170 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1170 1171 self.applied_dirty = 1
1171 1172
1172 1173 def full_series_end(self):
1173 1174 if len(self.applied) > 0:
1174 1175 p = self.applied[-1].name
1175 1176 end = self.find_series(p)
1176 1177 if end == None:
1177 1178 return len(self.full_series)
1178 1179 return end + 1
1179 1180 return 0
1180 1181
1181 1182 def series_end(self, all_patches=False):
1182 1183 end = 0
1183 1184 def next(start):
1184 1185 if all_patches:
1185 1186 return start
1186 1187 i = start
1187 1188 while i < len(self.series):
1188 1189 p, reason = self.pushable(i)
1189 1190 if p:
1190 1191 break
1191 1192 self.explain_pushable(i)
1192 1193 i += 1
1193 1194 return i
1194 1195 if len(self.applied) > 0:
1195 1196 p = self.applied[-1].name
1196 1197 try:
1197 1198 end = self.series.index(p)
1198 1199 except ValueError:
1199 1200 return 0
1200 1201 return next(end + 1)
1201 1202 return next(end)
1202 1203
1203 1204 def qapplied(self, repo, patch=None):
1204 1205 if patch and patch not in self.series:
1205 1206 raise util.Abort(_("patch %s is not in series file") % patch)
1206 1207 if not patch:
1207 1208 end = len(self.applied)
1208 1209 else:
1209 1210 end = self.series.index(patch) + 1
1210 1211 for x in xrange(end):
1211 1212 p = self.appliedname(x)
1212 1213 self.ui.write("%s\n" % p)
1213 1214
1214 1215 def appliedname(self, index):
1215 1216 pname = self.applied[index].name
1216 1217 if not self.ui.verbose:
1217 1218 p = pname
1218 1219 else:
1219 1220 p = str(self.series.index(pname)) + " " + p
1220 1221 return p
1221 1222
1222 1223 def top(self, repo):
1223 1224 if len(self.applied):
1224 1225 p = self.appliedname(-1)
1225 1226 self.ui.write(p + '\n')
1226 1227 else:
1227 1228 self.ui.write("No patches applied\n")
1228 1229
1229 1230 def next(self, repo):
1230 1231 end = self.series_end()
1231 1232 if end == len(self.series):
1232 1233 self.ui.write("All patches applied\n")
1233 1234 else:
1234 1235 p = self.series[end]
1235 1236 if self.ui.verbose:
1236 1237 self.ui.write("%d " % self.series.index(p))
1237 1238 self.ui.write(p + '\n')
1238 1239
1239 1240 def prev(self, repo):
1240 1241 if len(self.applied) > 1:
1241 1242 p = self.appliedname(-2)
1242 1243 self.ui.write(p + '\n')
1243 1244 elif len(self.applied) == 1:
1244 1245 self.ui.write("Only one patch applied\n")
1245 1246 else:
1246 1247 self.ui.write("No patches applied\n")
1247 1248
1248 1249 def qimport(self, repo, files, patch=None, existing=None, force=None):
1249 1250 if len(files) > 1 and patch:
1250 1251 raise util.Abort(_('option "-n" not valid when importing multiple '
1251 1252 'files'))
1252 1253 i = 0
1253 1254 added = []
1254 1255 for filename in files:
1255 1256 if existing:
1256 1257 if not patch:
1257 1258 patch = filename
1258 1259 if not os.path.isfile(self.join(patch)):
1259 1260 raise util.Abort(_("patch %s does not exist") % patch)
1260 1261 else:
1261 1262 try:
1262 1263 text = file(filename).read()
1263 1264 except IOError:
1264 1265 raise util.Abort(_("unable to read %s") % patch)
1265 1266 if not patch:
1266 1267 patch = os.path.split(filename)[1]
1267 1268 if not force and os.path.exists(self.join(patch)):
1268 1269 raise util.Abort(_('patch "%s" already exists') % patch)
1269 1270 patchf = self.opener(patch, "w")
1270 1271 patchf.write(text)
1271 1272 if patch in self.series:
1272 1273 raise util.Abort(_('patch %s is already in the series file')
1273 1274 % patch)
1274 1275 index = self.full_series_end() + i
1275 1276 self.full_series[index:index] = [patch]
1276 1277 self.parse_series()
1277 1278 self.ui.warn("adding %s to series file\n" % patch)
1278 1279 i += 1
1279 1280 added.append(patch)
1280 1281 patch = None
1281 1282 self.series_dirty = 1
1282 1283 qrepo = self.qrepo()
1283 1284 if qrepo:
1284 1285 qrepo.add(added)
1285 1286
1286 1287 def delete(ui, repo, patch, *patches, **opts):
1287 1288 """remove patches from queue
1288 1289
1289 1290 The patches must not be applied.
1290 1291 With -k, the patch files are preserved in the patch directory."""
1291 1292 q = repo.mq
1292 1293 q.delete(repo, (patch,) + patches, keep=opts.get('keep'))
1293 1294 q.save_dirty()
1294 1295 return 0
1295 1296
1296 1297 def applied(ui, repo, patch=None, **opts):
1297 1298 """print the patches already applied"""
1298 1299 repo.mq.qapplied(repo, patch)
1299 1300 return 0
1300 1301
1301 1302 def unapplied(ui, repo, patch=None, **opts):
1302 1303 """print the patches not yet applied"""
1303 1304 for i, p in repo.mq.unapplied(repo, patch):
1304 1305 if ui.verbose:
1305 1306 ui.write("%d " % i)
1306 1307 ui.write("%s\n" % p)
1307 1308
1308 1309 def qimport(ui, repo, *filename, **opts):
1309 1310 """import a patch"""
1310 1311 q = repo.mq
1311 1312 q.qimport(repo, filename, patch=opts['name'],
1312 1313 existing=opts['existing'], force=opts['force'])
1313 1314 q.save_dirty()
1314 1315 return 0
1315 1316
1316 1317 def init(ui, repo, **opts):
1317 1318 """init a new queue repository
1318 1319
1319 1320 The queue repository is unversioned by default. If -c is
1320 1321 specified, qinit will create a separate nested repository
1321 1322 for patches. Use qcommit to commit changes to this queue
1322 1323 repository."""
1323 1324 q = repo.mq
1324 1325 r = q.init(repo, create=opts['create_repo'])
1325 1326 q.save_dirty()
1326 1327 if r:
1327 1328 fp = r.wopener('.hgignore', 'w')
1328 1329 print >> fp, 'syntax: glob'
1329 1330 print >> fp, 'status'
1330 1331 fp.close()
1331 1332 r.wopener('series', 'w').close()
1332 1333 r.add(['.hgignore', 'series'])
1333 1334 return 0
1334 1335
1335 1336 def clone(ui, source, dest=None, **opts):
1336 1337 '''clone main and patch repository at same time
1337 1338
1338 1339 If source is local, destination will have no patches applied. If
1339 1340 source is remote, this command can not check if patches are
1340 1341 applied in source, so cannot guarantee that patches are not
1341 1342 applied in destination. If you clone remote repository, be sure
1342 1343 before that it has no patches applied.
1343 1344
1344 1345 Source patch repository is looked for in <src>/.hg/patches by
1345 1346 default. Use -p <url> to change.
1346 1347 '''
1347 1348 commands.setremoteconfig(ui, opts)
1348 1349 if dest is None:
1349 1350 dest = hg.defaultdest(source)
1350 1351 sr = hg.repository(ui, ui.expandpath(source))
1351 1352 qbase, destrev = None, None
1352 1353 if sr.local():
1353 1354 reposetup(ui, sr)
1354 1355 if sr.mq.applied:
1355 1356 qbase = revlog.bin(sr.mq.applied[0].rev)
1356 1357 if not hg.islocal(dest):
1357 1358 destrev = sr.parents(qbase)[0]
1358 1359 ui.note(_('cloning main repo\n'))
1359 1360 sr, dr = hg.clone(ui, sr, dest,
1360 1361 pull=opts['pull'],
1361 1362 rev=destrev,
1362 1363 update=False,
1363 1364 stream=opts['uncompressed'])
1364 1365 ui.note(_('cloning patch repo\n'))
1365 1366 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1366 1367 dr.url() + '/.hg/patches',
1367 1368 pull=opts['pull'],
1368 1369 update=not opts['noupdate'],
1369 1370 stream=opts['uncompressed'])
1370 1371 if dr.local():
1371 1372 if qbase:
1372 1373 ui.note(_('stripping applied patches from destination repo\n'))
1373 1374 reposetup(ui, dr)
1374 1375 dr.mq.strip(dr, qbase, update=False, backup=None)
1375 1376 if not opts['noupdate']:
1376 1377 ui.note(_('updating destination repo\n'))
1377 1378 hg.update(dr, dr.changelog.tip())
1378 1379
1379 1380 def commit(ui, repo, *pats, **opts):
1380 1381 """commit changes in the queue repository"""
1381 1382 q = repo.mq
1382 1383 r = q.qrepo()
1383 1384 if not r: raise util.Abort('no queue repository')
1384 1385 commands.commit(r.ui, r, *pats, **opts)
1385 1386
1386 1387 def series(ui, repo, **opts):
1387 1388 """print the entire series file"""
1388 1389 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1389 1390 return 0
1390 1391
1391 1392 def top(ui, repo, **opts):
1392 1393 """print the name of the current patch"""
1393 1394 repo.mq.top(repo)
1394 1395 return 0
1395 1396
1396 1397 def next(ui, repo, **opts):
1397 1398 """print the name of the next patch"""
1398 1399 repo.mq.next(repo)
1399 1400 return 0
1400 1401
1401 1402 def prev(ui, repo, **opts):
1402 1403 """print the name of the previous patch"""
1403 1404 repo.mq.prev(repo)
1404 1405 return 0
1405 1406
1406 1407 def new(ui, repo, patch, **opts):
1407 1408 """create a new patch
1408 1409
1409 1410 qnew creates a new patch on top of the currently-applied patch
1410 1411 (if any). It will refuse to run if there are any outstanding
1411 1412 changes unless -f is specified, in which case the patch will
1412 1413 be initialised with them.
1413 1414
1414 1415 -m or -l set the patch header as well as the commit message.
1415 1416 If neither is specified, the patch header is empty and the
1416 1417 commit message is 'New patch: PATCH'"""
1417 1418 q = repo.mq
1418 1419 message = commands.logmessage(opts)
1419 1420 q.new(repo, patch, msg=message, force=opts['force'])
1420 1421 q.save_dirty()
1421 1422 return 0
1422 1423
1423 1424 def refresh(ui, repo, **opts):
1424 1425 """update the current patch"""
1425 1426 q = repo.mq
1426 1427 message = commands.logmessage(opts)
1427 1428 if opts['edit']:
1428 1429 if message:
1429 1430 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1430 1431 patch = q.applied[-1].name
1431 1432 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1432 1433 message = ui.edit('\n'.join(message), user or ui.username())
1433 1434 q.refresh(repo, msg=message, short=opts['short'])
1434 1435 q.save_dirty()
1435 1436 return 0
1436 1437
1437 1438 def diff(ui, repo, *files, **opts):
1438 1439 """diff of the current patch"""
1439 1440 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1440 1441 repo.mq.diff(repo, list(files))
1441 1442 return 0
1442 1443
1443 1444 def fold(ui, repo, *files, **opts):
1444 1445 """fold the named patches into the current patch
1445 1446
1446 1447 Patches must not yet be applied. Each patch will be successively
1447 1448 applied to the current patch in the order given. If all the
1448 1449 patches apply successfully, the current patch will be refreshed
1449 1450 with the new cumulative patch, and the folded patches will
1450 1451 be deleted. With -k/--keep, the folded patch files will not
1451 1452 be removed afterwards.
1452 1453
1453 1454 The header for each folded patch will be concatenated with
1454 1455 the current patch header, separated by a line of '* * *'."""
1455 1456
1456 1457 q = repo.mq
1457 1458
1458 1459 if not files:
1459 1460 raise util.Abort(_('qfold requires at least one patch name'))
1460 1461 if not q.check_toppatch(repo):
1461 1462 raise util.Abort(_('No patches applied\n'))
1462 1463
1463 1464 message = commands.logmessage(opts)
1464 1465 if opts['edit']:
1465 1466 if message:
1466 1467 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1467 1468
1468 1469 parent = q.lookup('qtip')
1469 1470 patches = []
1470 1471 messages = []
1471 1472 for f in files:
1472 1473 patch = q.lookup(f)
1473 1474 if patch in patches or patch == parent:
1474 1475 ui.warn(_('Skipping already folded patch %s') % patch)
1475 1476 if q.isapplied(patch):
1476 1477 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1477 1478 patches.append(patch)
1478 1479
1479 1480 for patch in patches:
1480 1481 if not message:
1481 1482 messages.append(q.readheaders(patch)[0])
1482 1483 pf = q.join(patch)
1483 1484 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1484 1485 if not patchsuccess:
1485 1486 raise util.Abort(_('Error folding patch %s') % patch)
1486 1487
1487 1488 if not message:
1488 1489 message, comments, user = q.readheaders(parent)[0:3]
1489 1490 for msg in messages:
1490 1491 message.append('* * *')
1491 1492 message.extend(msg)
1492 1493 message = '\n'.join(message)
1493 1494
1494 1495 if opts['edit']:
1495 1496 message = ui.edit(message, user or ui.username())
1496 1497
1497 1498 q.refresh(repo, msg=message)
1498 1499
1499 1500 for patch in patches:
1500 1501 q.delete(repo, patch, keep=opts['keep'])
1501 1502
1502 1503 q.save_dirty()
1503 1504
1504 1505 def guard(ui, repo, *args, **opts):
1505 1506 '''set or print guards for a patch
1506 1507
1507 1508 guards control whether a patch can be pushed. a patch with no
1508 1509 guards is aways pushed. a patch with posative guard ("+foo") is
1509 1510 pushed only if qselect command enables guard "foo". a patch with
1510 1511 nagative guard ("-foo") is never pushed if qselect command enables
1511 1512 guard "foo".
1512 1513
1513 1514 with no arguments, default is to print current active guards.
1514 1515 with arguments, set active guards for patch.
1515 1516
1516 1517 to set nagative guard "-foo" on topmost patch ("--" is needed so
1517 1518 hg will not interpret "-foo" as argument):
1518 1519 hg qguard -- -foo
1519 1520
1520 1521 to set guards on other patch:
1521 1522 hg qguard other.patch +2.6.17 -stable
1522 1523 '''
1523 1524 def status(idx):
1524 1525 guards = q.series_guards[idx] or ['unguarded']
1525 1526 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1526 1527 q = repo.mq
1527 1528 patch = None
1528 1529 args = list(args)
1529 1530 if opts['list']:
1530 1531 if args or opts['none']:
1531 1532 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1532 1533 for i in xrange(len(q.series)):
1533 1534 status(i)
1534 1535 return
1535 1536 if not args or args[0][0:1] in '-+':
1536 1537 if not q.applied:
1537 1538 raise util.Abort(_('no patches applied'))
1538 1539 patch = q.applied[-1].name
1539 1540 if patch is None and args[0][0:1] not in '-+':
1540 1541 patch = args.pop(0)
1541 1542 if patch is None:
1542 1543 raise util.Abort(_('no patch to work with'))
1543 1544 if args or opts['none']:
1544 1545 q.set_guards(q.find_series(patch), args)
1545 1546 q.save_dirty()
1546 1547 else:
1547 1548 status(q.series.index(q.lookup(patch)))
1548 1549
1549 1550 def header(ui, repo, patch=None):
1550 1551 """Print the header of the topmost or specified patch"""
1551 1552 q = repo.mq
1552 1553
1553 1554 if patch:
1554 1555 patch = q.lookup(patch)
1555 1556 else:
1556 1557 if not q.applied:
1557 1558 ui.write('No patches applied\n')
1558 1559 return
1559 1560 patch = q.lookup('qtip')
1560 1561 message = repo.mq.readheaders(patch)[0]
1561 1562
1562 1563 ui.write('\n'.join(message) + '\n')
1563 1564
1564 1565 def lastsavename(path):
1565 1566 (directory, base) = os.path.split(path)
1566 1567 names = os.listdir(directory)
1567 1568 namere = re.compile("%s.([0-9]+)" % base)
1568 1569 maxindex = None
1569 1570 maxname = None
1570 1571 for f in names:
1571 1572 m = namere.match(f)
1572 1573 if m:
1573 1574 index = int(m.group(1))
1574 1575 if maxindex == None or index > maxindex:
1575 1576 maxindex = index
1576 1577 maxname = f
1577 1578 if maxname:
1578 1579 return (os.path.join(directory, maxname), maxindex)
1579 1580 return (None, None)
1580 1581
1581 1582 def savename(path):
1582 1583 (last, index) = lastsavename(path)
1583 1584 if last is None:
1584 1585 index = 0
1585 1586 newpath = path + ".%d" % (index + 1)
1586 1587 return newpath
1587 1588
1588 1589 def push(ui, repo, patch=None, **opts):
1589 1590 """push the next patch onto the stack"""
1590 1591 q = repo.mq
1591 1592 mergeq = None
1592 1593
1593 1594 if opts['all']:
1594 1595 patch = q.series[-1]
1595 1596 if opts['merge']:
1596 1597 if opts['name']:
1597 1598 newpath = opts['name']
1598 1599 else:
1599 1600 newpath, i = lastsavename(q.path)
1600 1601 if not newpath:
1601 1602 ui.warn("no saved queues found, please use -n\n")
1602 1603 return 1
1603 1604 mergeq = queue(ui, repo.join(""), newpath)
1604 1605 ui.warn("merging with queue at: %s\n" % mergeq.path)
1605 1606 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1606 1607 mergeq=mergeq)
1607 1608 q.save_dirty()
1608 1609 return ret
1609 1610
1610 1611 def pop(ui, repo, patch=None, **opts):
1611 1612 """pop the current patch off the stack"""
1612 1613 localupdate = True
1613 1614 if opts['name']:
1614 1615 q = queue(ui, repo.join(""), repo.join(opts['name']))
1615 1616 ui.warn('using patch queue: %s\n' % q.path)
1616 1617 localupdate = False
1617 1618 else:
1618 1619 q = repo.mq
1619 1620 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1620 1621 q.save_dirty()
1621 1622 return 0
1622 1623
1623 1624 def rename(ui, repo, patch, name=None, **opts):
1624 1625 """rename a patch
1625 1626
1626 1627 With one argument, renames the current patch to PATCH1.
1627 1628 With two arguments, renames PATCH1 to PATCH2."""
1628 1629
1629 1630 q = repo.mq
1630 1631
1631 1632 if not name:
1632 1633 name = patch
1633 1634 patch = None
1634 1635
1635 1636 if name in q.series:
1636 1637 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1637 1638
1638 1639 absdest = q.join(name)
1639 1640 if os.path.exists(absdest):
1640 1641 raise util.Abort(_('%s already exists') % absdest)
1641 1642
1642 1643 if patch:
1643 1644 patch = q.lookup(patch)
1644 1645 else:
1645 1646 if not q.applied:
1646 1647 ui.write(_('No patches applied\n'))
1647 1648 return
1648 1649 patch = q.lookup('qtip')
1649 1650
1650 1651 if ui.verbose:
1651 1652 ui.write('Renaming %s to %s\n' % (patch, name))
1652 1653 i = q.find_series(patch)
1653 1654 q.full_series[i] = name
1654 1655 q.parse_series()
1655 1656 q.series_dirty = 1
1656 1657
1657 1658 info = q.isapplied(patch)
1658 1659 if info:
1659 1660 q.applied[info[0]] = statusentry(info[1], name)
1660 1661 q.applied_dirty = 1
1661 1662
1662 1663 util.rename(q.join(patch), absdest)
1663 1664 r = q.qrepo()
1664 1665 if r:
1665 1666 wlock = r.wlock()
1666 1667 if r.dirstate.state(name) == 'r':
1667 1668 r.undelete([name], wlock)
1668 1669 r.copy(patch, name, wlock)
1669 1670 r.remove([patch], False, wlock)
1670 1671
1671 1672 q.save_dirty()
1672 1673
1673 1674 def restore(ui, repo, rev, **opts):
1674 1675 """restore the queue state saved by a rev"""
1675 1676 rev = repo.lookup(rev)
1676 1677 q = repo.mq
1677 1678 q.restore(repo, rev, delete=opts['delete'],
1678 1679 qupdate=opts['update'])
1679 1680 q.save_dirty()
1680 1681 return 0
1681 1682
1682 1683 def save(ui, repo, **opts):
1683 1684 """save current queue state"""
1684 1685 q = repo.mq
1685 1686 message = commands.logmessage(opts)
1686 1687 ret = q.save(repo, msg=message)
1687 1688 if ret:
1688 1689 return ret
1689 1690 q.save_dirty()
1690 1691 if opts['copy']:
1691 1692 path = q.path
1692 1693 if opts['name']:
1693 1694 newpath = os.path.join(q.basepath, opts['name'])
1694 1695 if os.path.exists(newpath):
1695 1696 if not os.path.isdir(newpath):
1696 1697 raise util.Abort(_('destination %s exists and is not '
1697 1698 'a directory') % newpath)
1698 1699 if not opts['force']:
1699 1700 raise util.Abort(_('destination %s exists, '
1700 1701 'use -f to force') % newpath)
1701 1702 else:
1702 1703 newpath = savename(path)
1703 1704 ui.warn("copy %s to %s\n" % (path, newpath))
1704 1705 util.copyfiles(path, newpath)
1705 1706 if opts['empty']:
1706 1707 try:
1707 1708 os.unlink(q.join(q.status_path))
1708 1709 except:
1709 1710 pass
1710 1711 return 0
1711 1712
1712 1713 def strip(ui, repo, rev, **opts):
1713 1714 """strip a revision and all later revs on the same branch"""
1714 1715 rev = repo.lookup(rev)
1715 1716 backup = 'all'
1716 1717 if opts['backup']:
1717 1718 backup = 'strip'
1718 1719 elif opts['nobackup']:
1719 1720 backup = 'none'
1720 1721 repo.mq.strip(repo, rev, backup=backup)
1721 1722 return 0
1722 1723
1723 1724 def select(ui, repo, *args, **opts):
1724 1725 '''set or print guarded patches to push
1725 1726
1726 1727 use qguard command to set or print guards on patch. then use
1727 1728 qselect to tell mq which guards to use. example:
1728 1729
1729 1730 qguard foo.patch -stable (nagative guard)
1730 1731 qguard bar.patch +stable (posative guard)
1731 1732 qselect stable
1732 1733
1733 1734 this sets "stable" guard. mq will skip foo.patch (because it has
1734 1735 nagative match) but push bar.patch (because it has posative
1735 1736 match). patch is pushed if any posative guards match and no
1736 1737 nagative guards match.
1737 1738
1738 1739 with no arguments, default is to print current active guards.
1739 1740 with arguments, set active guards as given.
1740 1741
1741 1742 use -n/--none to deactivate guards (no other arguments needed).
1742 1743 when no guards active, patches with posative guards are skipped,
1743 1744 patches with nagative guards are pushed.
1744 1745
1745 1746 qselect can change guards of applied patches. it does not pop
1746 1747 guarded patches by default. use --pop to pop back to last applied
1747 1748 patch that is not guarded. use --reapply (implies --pop) to push
1748 1749 back to current patch afterwards, but skip guarded patches.
1749 1750
1750 1751 use -s/--series to print list of all guards in series file (no
1751 1752 other arguments needed). use -v for more information.'''
1752 1753
1753 1754 q = repo.mq
1754 1755 guards = q.active()
1755 1756 if args or opts['none']:
1756 1757 old_unapplied = q.unapplied(repo)
1757 1758 old_guarded = [i for i in xrange(len(q.applied)) if
1758 1759 not q.pushable(i)[0]]
1759 1760 q.set_active(args)
1760 1761 q.save_dirty()
1761 1762 if not args:
1762 1763 ui.status(_('guards deactivated\n'))
1763 1764 if not opts['pop'] and not opts['reapply']:
1764 1765 unapplied = q.unapplied(repo)
1765 1766 guarded = [i for i in xrange(len(q.applied))
1766 1767 if not q.pushable(i)[0]]
1767 1768 if len(unapplied) != len(old_unapplied):
1768 1769 ui.status(_('number of unguarded, unapplied patches has '
1769 1770 'changed from %d to %d\n') %
1770 1771 (len(old_unapplied), len(unapplied)))
1771 1772 if len(guarded) != len(old_guarded):
1772 1773 ui.status(_('number of guarded, applied patches has changed '
1773 1774 'from %d to %d\n') %
1774 1775 (len(old_guarded), len(guarded)))
1775 1776 elif opts['series']:
1776 1777 guards = {}
1777 1778 noguards = 0
1778 1779 for gs in q.series_guards:
1779 1780 if not gs:
1780 1781 noguards += 1
1781 1782 for g in gs:
1782 1783 guards.setdefault(g, 0)
1783 1784 guards[g] += 1
1784 1785 if ui.verbose:
1785 1786 guards['NONE'] = noguards
1786 1787 guards = guards.items()
1787 1788 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1788 1789 if guards:
1789 1790 ui.note(_('guards in series file:\n'))
1790 1791 for guard, count in guards:
1791 1792 ui.note('%2d ' % count)
1792 1793 ui.write(guard, '\n')
1793 1794 else:
1794 1795 ui.note(_('no guards in series file\n'))
1795 1796 else:
1796 1797 if guards:
1797 1798 ui.note(_('active guards:\n'))
1798 1799 for g in guards:
1799 1800 ui.write(g, '\n')
1800 1801 else:
1801 1802 ui.write(_('no active guards\n'))
1802 1803 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1803 1804 popped = False
1804 1805 if opts['pop'] or opts['reapply']:
1805 1806 for i in xrange(len(q.applied)):
1806 1807 pushable, reason = q.pushable(i)
1807 1808 if not pushable:
1808 1809 ui.status(_('popping guarded patches\n'))
1809 1810 popped = True
1810 1811 if i == 0:
1811 1812 q.pop(repo, all=True)
1812 1813 else:
1813 1814 q.pop(repo, i-1)
1814 1815 break
1815 1816 if popped:
1816 1817 try:
1817 1818 if reapply:
1818 1819 ui.status(_('reapplying unguarded patches\n'))
1819 1820 q.push(repo, reapply)
1820 1821 finally:
1821 1822 q.save_dirty()
1822 1823
1823 1824 def reposetup(ui, repo):
1824 1825 class mqrepo(repo.__class__):
1825 1826 def abort_if_wdir_patched(self, errmsg, force=False):
1826 1827 if self.mq.applied and not force:
1827 1828 parent = revlog.hex(self.dirstate.parents()[0])
1828 1829 if parent in [s.rev for s in self.mq.applied]:
1829 1830 raise util.Abort(errmsg)
1830 1831
1831 1832 def commit(self, *args, **opts):
1832 1833 if len(args) >= 6:
1833 1834 force = args[5]
1834 1835 else:
1835 1836 force = opts.get('force')
1836 1837 self.abort_if_wdir_patched(
1837 1838 _('cannot commit over an applied mq patch'),
1838 1839 force)
1839 1840
1840 1841 return super(mqrepo, self).commit(*args, **opts)
1841 1842
1842 1843 def push(self, remote, force=False, revs=None):
1843 1844 if self.mq.applied and not force:
1844 1845 raise util.Abort(_('source has mq patches applied'))
1845 1846 return super(mqrepo, self).push(remote, force, revs)
1846 1847
1847 1848 def tags(self):
1848 1849 if self.tagscache:
1849 1850 return self.tagscache
1850 1851
1851 1852 tagscache = super(mqrepo, self).tags()
1852 1853
1853 1854 q = self.mq
1854 1855 if not q.applied:
1855 1856 return tagscache
1856 1857
1857 1858 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1858 1859 mqtags.append((mqtags[-1][0], 'qtip'))
1859 1860 mqtags.append((mqtags[0][0], 'qbase'))
1860 1861 for patch in mqtags:
1861 1862 if patch[1] in tagscache:
1862 1863 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1863 1864 else:
1864 1865 tagscache[patch[1]] = revlog.bin(patch[0])
1865 1866
1866 1867 return tagscache
1867 1868
1868 1869 if repo.local():
1869 1870 repo.__class__ = mqrepo
1870 1871 repo.mq = queue(ui, repo.join(""))
1871 1872
1872 1873 cmdtable = {
1873 1874 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1874 1875 "qclone": (clone,
1875 1876 [('', 'pull', None, _('use pull protocol to copy metadata')),
1876 1877 ('U', 'noupdate', None, _('do not update the new working directories')),
1877 1878 ('', 'uncompressed', None,
1878 1879 _('use uncompressed transfer (fast over LAN)')),
1879 1880 ('e', 'ssh', '', _('specify ssh command to use')),
1880 1881 ('p', 'patches', '', _('location of source patch repo')),
1881 1882 ('', 'remotecmd', '',
1882 1883 _('specify hg command to run on the remote side'))],
1883 1884 'hg qclone [OPTION]... SOURCE [DEST]'),
1884 1885 "qcommit|qci":
1885 1886 (commit,
1886 1887 commands.table["^commit|ci"][1],
1887 1888 'hg qcommit [OPTION]... [FILE]...'),
1888 1889 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1889 1890 "qdelete|qremove|qrm":
1890 1891 (delete,
1891 1892 [('k', 'keep', None, _('keep patch file'))],
1892 1893 'hg qdelete [-k] PATCH'),
1893 1894 'qfold':
1894 1895 (fold,
1895 1896 [('e', 'edit', None, _('edit patch header')),
1896 1897 ('k', 'keep', None, _('keep folded patch files')),
1897 1898 ('m', 'message', '', _('set patch header to <text>')),
1898 1899 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1899 1900 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1900 1901 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1901 1902 ('n', 'none', None, _('drop all guards'))],
1902 1903 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1903 1904 'qheader': (header, [],
1904 1905 _('hg qheader [PATCH]')),
1905 1906 "^qimport":
1906 1907 (qimport,
1907 1908 [('e', 'existing', None, 'import file in patch dir'),
1908 1909 ('n', 'name', '', 'patch file name'),
1909 1910 ('f', 'force', None, 'overwrite existing files')],
1910 1911 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1911 1912 "^qinit":
1912 1913 (init,
1913 1914 [('c', 'create-repo', None, 'create queue repository')],
1914 1915 'hg qinit [-c]'),
1915 1916 "qnew":
1916 1917 (new,
1917 1918 [('m', 'message', '', _('use <text> as commit message')),
1918 1919 ('l', 'logfile', '', _('read the commit message from <file>')),
1919 1920 ('f', 'force', None, _('import uncommitted changes into patch'))],
1920 1921 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1921 1922 "qnext": (next, [], 'hg qnext'),
1922 1923 "qprev": (prev, [], 'hg qprev'),
1923 1924 "^qpop":
1924 1925 (pop,
1925 1926 [('a', 'all', None, 'pop all patches'),
1926 1927 ('n', 'name', '', 'queue name to pop'),
1927 1928 ('f', 'force', None, 'forget any local changes')],
1928 1929 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1929 1930 "^qpush":
1930 1931 (push,
1931 1932 [('f', 'force', None, 'apply if the patch has rejects'),
1932 1933 ('l', 'list', None, 'list patch name in commit text'),
1933 1934 ('a', 'all', None, 'apply all patches'),
1934 1935 ('m', 'merge', None, 'merge from another queue'),
1935 1936 ('n', 'name', '', 'merge queue name')],
1936 1937 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1937 1938 "^qrefresh":
1938 1939 (refresh,
1939 1940 [('e', 'edit', None, _('edit commit message')),
1940 1941 ('m', 'message', '', _('change commit message with <text>')),
1941 1942 ('l', 'logfile', '', _('change commit message with <file> content')),
1942 1943 ('s', 'short', None, 'short refresh')],
1943 1944 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1944 1945 'qrename|qmv':
1945 1946 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1946 1947 "qrestore":
1947 1948 (restore,
1948 1949 [('d', 'delete', None, 'delete save entry'),
1949 1950 ('u', 'update', None, 'update queue working dir')],
1950 1951 'hg qrestore [-d] [-u] REV'),
1951 1952 "qsave":
1952 1953 (save,
1953 1954 [('m', 'message', '', _('use <text> as commit message')),
1954 1955 ('l', 'logfile', '', _('read the commit message from <file>')),
1955 1956 ('c', 'copy', None, 'copy patch directory'),
1956 1957 ('n', 'name', '', 'copy directory name'),
1957 1958 ('e', 'empty', None, 'clear queue status file'),
1958 1959 ('f', 'force', None, 'force copy')],
1959 1960 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1960 1961 "qselect": (select,
1961 1962 [('n', 'none', None, _('disable all guards')),
1962 1963 ('s', 'series', None, _('list all guards in series file')),
1963 1964 ('', 'pop', None,
1964 1965 _('pop to before first guarded applied patch')),
1965 1966 ('', 'reapply', None, _('pop, then reapply patches'))],
1966 1967 'hg qselect [OPTION...] [GUARD...]'),
1967 1968 "qseries":
1968 1969 (series,
1969 1970 [('m', 'missing', None, 'print patches not in series'),
1970 1971 ('s', 'summary', None, _('print first line of patch header'))],
1971 1972 'hg qseries [-m]'),
1972 1973 "^strip":
1973 1974 (strip,
1974 1975 [('f', 'force', None, 'force multi-head removal'),
1975 1976 ('b', 'backup', None, 'bundle unrelated changesets'),
1976 1977 ('n', 'nobackup', None, 'no backups')],
1977 1978 'hg strip [-f] [-b] [-n] REV'),
1978 1979 "qtop": (top, [], 'hg qtop'),
1979 1980 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1980 1981 }
@@ -1,496 +1,539 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.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 from demandload import demandload
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), "cmdutil mdiff util")
12 12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
13 13
14 14 # helper functions
15 15
16 16 def copyfile(src, dst, basedir=None):
17 17 if not basedir:
18 18 basedir = os.getcwd()
19 19
20 20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 21 if os.path.exists(absdst):
22 22 raise util.Abort(_("cannot create %s: destination already exists") %
23 23 dst)
24 24
25 25 targetdir = os.path.dirname(absdst)
26 26 if not os.path.isdir(targetdir):
27 27 os.makedirs(targetdir)
28 28 try:
29 29 shutil.copyfile(abssrc, absdst)
30 30 shutil.copymode(abssrc, absdst)
31 31 except shutil.Error, inst:
32 32 raise util.Abort(str(inst))
33 33
34 34 # public functions
35 35
36 36 def extract(ui, fileobj):
37 37 '''extract patch from data read from fileobj.
38 38
39 39 patch can be normal patch or contained in email message.
40 40
41 41 return tuple (filename, message, user, date). any item in returned
42 42 tuple can be None. if filename is None, fileobj did not contain
43 43 patch. caller must unlink filename when done.'''
44 44
45 45 # attempt to detect the start of a patch
46 46 # (this heuristic is borrowed from quilt)
47 47 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
48 48 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
49 49 '(---|\*\*\*)[ \t])', re.MULTILINE)
50 50
51 51 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
52 52 tmpfp = os.fdopen(fd, 'w')
53 53 try:
54 54 hgpatch = False
55 55
56 56 msg = email.Parser.Parser().parse(fileobj)
57 57
58 58 message = msg['Subject']
59 59 user = msg['From']
60 60 # should try to parse msg['Date']
61 61 date = None
62 62
63 63 if message:
64 64 message = message.replace('\n\t', ' ')
65 65 ui.debug('Subject: %s\n' % message)
66 66 if user:
67 67 ui.debug('From: %s\n' % user)
68 68 diffs_seen = 0
69 69 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
70 70
71 71 for part in msg.walk():
72 72 content_type = part.get_content_type()
73 73 ui.debug('Content-Type: %s\n' % content_type)
74 74 if content_type not in ok_types:
75 75 continue
76 76 payload = part.get_payload(decode=True)
77 77 m = diffre.search(payload)
78 78 if m:
79 79 ui.debug(_('found patch at byte %d\n') % m.start(0))
80 80 diffs_seen += 1
81 81 cfp = cStringIO.StringIO()
82 82 if message:
83 83 cfp.write(message)
84 84 cfp.write('\n')
85 85 for line in payload[:m.start(0)].splitlines():
86 86 if line.startswith('# HG changeset patch'):
87 87 ui.debug(_('patch generated by hg export\n'))
88 88 hgpatch = True
89 89 # drop earlier commit message content
90 90 cfp.seek(0)
91 91 cfp.truncate()
92 92 elif hgpatch:
93 93 if line.startswith('# User '):
94 94 user = line[7:]
95 95 ui.debug('From: %s\n' % user)
96 96 elif line.startswith("# Date "):
97 97 date = line[7:]
98 98 if not line.startswith('# '):
99 99 cfp.write(line)
100 100 cfp.write('\n')
101 101 message = cfp.getvalue()
102 102 if tmpfp:
103 103 tmpfp.write(payload)
104 104 if not payload.endswith('\n'):
105 105 tmpfp.write('\n')
106 106 elif not diffs_seen and message and content_type == 'text/plain':
107 107 message += '\n' + payload
108 108 except:
109 109 tmpfp.close()
110 110 os.unlink(tmpname)
111 111 raise
112 112
113 113 tmpfp.close()
114 114 if not diffs_seen:
115 115 os.unlink(tmpname)
116 116 return None, message, user, date
117 117 return tmpname, message, user, date
118 118
119 119 def readgitpatch(patchname):
120 120 """extract git-style metadata about patches from <patchname>"""
121 121 class gitpatch:
122 122 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
123 123 def __init__(self, path):
124 124 self.path = path
125 125 self.oldpath = None
126 126 self.mode = None
127 127 self.op = 'MODIFY'
128 128 self.copymod = False
129 129 self.lineno = 0
130 130
131 131 # Filter patch for git information
132 132 gitre = re.compile('diff --git a/(.*) b/(.*)')
133 133 pf = file(patchname)
134 134 gp = None
135 135 gitpatches = []
136 136 # Can have a git patch with only metadata, causing patch to complain
137 137 dopatch = False
138 138
139 139 lineno = 0
140 140 for line in pf:
141 141 lineno += 1
142 142 if line.startswith('diff --git'):
143 143 m = gitre.match(line)
144 144 if m:
145 145 if gp:
146 146 gitpatches.append(gp)
147 147 src, dst = m.group(1,2)
148 148 gp = gitpatch(dst)
149 149 gp.lineno = lineno
150 150 elif gp:
151 151 if line.startswith('--- '):
152 152 if gp.op in ('COPY', 'RENAME'):
153 153 gp.copymod = True
154 154 dopatch = 'filter'
155 155 gitpatches.append(gp)
156 156 gp = None
157 157 if not dopatch:
158 158 dopatch = True
159 159 continue
160 160 if line.startswith('rename from '):
161 161 gp.op = 'RENAME'
162 162 gp.oldpath = line[12:].rstrip()
163 163 elif line.startswith('rename to '):
164 164 gp.path = line[10:].rstrip()
165 165 elif line.startswith('copy from '):
166 166 gp.op = 'COPY'
167 167 gp.oldpath = line[10:].rstrip()
168 168 elif line.startswith('copy to '):
169 169 gp.path = line[8:].rstrip()
170 170 elif line.startswith('deleted file'):
171 171 gp.op = 'DELETE'
172 172 elif line.startswith('new file mode '):
173 173 gp.op = 'ADD'
174 174 gp.mode = int(line.rstrip()[-3:], 8)
175 175 elif line.startswith('new mode '):
176 176 gp.mode = int(line.rstrip()[-3:], 8)
177 177 if gp:
178 178 gitpatches.append(gp)
179 179
180 180 if not gitpatches:
181 181 dopatch = True
182 182
183 183 return (dopatch, gitpatches)
184 184
185 185 def dogitpatch(patchname, gitpatches):
186 186 """Preprocess git patch so that vanilla patch can handle it"""
187 187 pf = file(patchname)
188 188 pfline = 1
189 189
190 190 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
191 191 tmpfp = os.fdopen(fd, 'w')
192 192
193 193 try:
194 194 for i in range(len(gitpatches)):
195 195 p = gitpatches[i]
196 196 if not p.copymod:
197 197 continue
198 198
199 199 copyfile(p.oldpath, p.path)
200 200
201 201 # rewrite patch hunk
202 202 while pfline < p.lineno:
203 203 tmpfp.write(pf.readline())
204 204 pfline += 1
205 205 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
206 206 line = pf.readline()
207 207 pfline += 1
208 208 while not line.startswith('--- a/'):
209 209 tmpfp.write(line)
210 210 line = pf.readline()
211 211 pfline += 1
212 212 tmpfp.write('--- a/%s\n' % p.path)
213 213
214 214 line = pf.readline()
215 215 while line:
216 216 tmpfp.write(line)
217 217 line = pf.readline()
218 218 except:
219 219 tmpfp.close()
220 220 os.unlink(patchname)
221 221 raise
222 222
223 223 tmpfp.close()
224 224 return patchname
225 225
226 226 def patch(patchname, ui, strip=1, cwd=None):
227 227 """apply the patch <patchname> to the working directory.
228 228 a list of patched files is returned"""
229 229
230 230 (dopatch, gitpatches) = readgitpatch(patchname)
231 231
232 232 files = {}
233 233 fuzz = False
234 234 if dopatch:
235 235 if dopatch == 'filter':
236 236 patchname = dogitpatch(patchname, gitpatches)
237 237 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
238 238 args = []
239 239 if cwd:
240 240 args.append('-d %s' % util.shellquote(cwd))
241 241 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
242 242 util.shellquote(patchname)))
243 243
244 244 if dopatch == 'filter':
245 245 False and os.unlink(patchname)
246 246
247 247 for line in fp:
248 248 line = line.rstrip()
249 249 ui.note(line + '\n')
250 250 if line.startswith('patching file '):
251 251 pf = util.parse_patch_output(line)
252 252 printed_file = False
253 253 files.setdefault(pf, (None, None))
254 254 elif line.find('with fuzz') >= 0:
255 255 fuzz = True
256 256 if not printed_file:
257 257 ui.warn(pf + '\n')
258 258 printed_file = True
259 259 ui.warn(line + '\n')
260 260 elif line.find('saving rejects to file') >= 0:
261 261 ui.warn(line + '\n')
262 262 elif line.find('FAILED') >= 0:
263 263 if not printed_file:
264 264 ui.warn(pf + '\n')
265 265 printed_file = True
266 266 ui.warn(line + '\n')
267 267
268 268 code = fp.close()
269 269 if code:
270 270 raise util.Abort(_("patch command failed: %s") %
271 271 util.explain_exit(code)[0])
272 272
273 273 for gp in gitpatches:
274 274 files[gp.path] = (gp.op, gp)
275 275
276 276 return (files, fuzz)
277 277
278 278 def diffopts(ui, opts={}):
279 279 return mdiff.diffopts(
280 280 text=opts.get('text'),
281 281 git=(opts.get('git') or
282 282 ui.configbool('diff', 'git', None)),
283 283 showfunc=(opts.get('show_function') or
284 284 ui.configbool('diff', 'showfunc', None)),
285 285 ignorews=(opts.get('ignore_all_space') or
286 286 ui.configbool('diff', 'ignorews', None)),
287 287 ignorewsamount=(opts.get('ignore_space_change') or
288 288 ui.configbool('diff', 'ignorewsamount', None)),
289 289 ignoreblanklines=(opts.get('ignore_blank_lines') or
290 290 ui.configbool('diff', 'ignoreblanklines', None)))
291 291
292 292 def updatedir(ui, repo, patches, wlock=None):
293 293 '''Update dirstate after patch application according to metadata'''
294 294 if not patches:
295 295 return
296 296 copies = []
297 297 removes = []
298 298 cfiles = patches.keys()
299 299 copts = {'after': False, 'force': False}
300 300 cwd = repo.getcwd()
301 301 if cwd:
302 302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
303 303 for f in patches:
304 304 ctype, gp = patches[f]
305 305 if ctype == 'RENAME':
306 306 copies.append((gp.oldpath, gp.path, gp.copymod))
307 307 removes.append(gp.oldpath)
308 308 elif ctype == 'COPY':
309 309 copies.append((gp.oldpath, gp.path, gp.copymod))
310 310 elif ctype == 'DELETE':
311 311 removes.append(gp.path)
312 312 for src, dst, after in copies:
313 313 if not after:
314 314 copyfile(src, dst, repo.root)
315 315 repo.copy(src, dst, wlock=wlock)
316 316 if removes:
317 317 repo.remove(removes, True, wlock=wlock)
318 318 for f in patches:
319 319 ctype, gp = patches[f]
320 320 if gp and gp.mode:
321 321 x = gp.mode & 0100 != 0
322 322 dst = os.path.join(repo.root, gp.path)
323 323 util.set_exec(dst, x)
324 324 cmdutil.addremove(repo, cfiles, wlock=wlock)
325 325 files = patches.keys()
326 326 files.extend([r for r in removes if r not in files])
327 327 files.sort()
328 328
329 329 return files
330 330
331 331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
332 332 fp=None, changes=None, opts=None):
333 333 '''print diff of changes to files between two nodes, or node and
334 334 working directory.
335 335
336 336 if node1 is None, use first dirstate parent instead.
337 337 if node2 is None, compare node1 with working directory.'''
338 338
339 339 if opts is None:
340 340 opts = mdiff.defaultopts
341 341 if fp is None:
342 342 fp = repo.ui
343 343
344 344 if not node1:
345 345 node1 = repo.dirstate.parents()[0]
346
347 clcache = {}
348 def getchangelog(n):
349 if n not in clcache:
350 clcache[n] = repo.changelog.read(n)
351 return clcache[n]
352 mcache = {}
353 def getmanifest(n):
354 if n not in mcache:
355 mcache[n] = repo.manifest.read(n)
356 return mcache[n]
357 fcache = {}
358 def getfile(f):
359 if f not in fcache:
360 fcache[f] = repo.file(f)
361 return fcache[f]
362
346 363 # reading the data for node1 early allows it to play nicely
347 364 # with repo.status and the revlog cache.
348 change = repo.changelog.read(node1)
349 mmap = repo.manifest.read(change[0])
365 change = getchangelog(node1)
366 mmap = getmanifest(change[0])
350 367 date1 = util.datestr(change[2])
351 368
352 369 if not changes:
353 370 changes = repo.status(node1, node2, files, match=match)[:5]
354 371 modified, added, removed, deleted, unknown = changes
355 372 if files:
356 373 def filterfiles(filters):
357 374 l = [x for x in filters if x in files]
358 375
359 376 for t in files:
360 377 if not t.endswith("/"):
361 378 t += "/"
362 379 l += [x for x in filters if x.startswith(t)]
363 380 return l
364 381
365 382 modified, added, removed = map(filterfiles, (modified, added, removed))
366 383
367 384 if not modified and not added and not removed:
368 385 return
369 386
387 def renamedbetween(f, n1, n2):
388 r1, r2 = map(repo.changelog.rev, (n1, n2))
389 src = None
390 while r2 > r1:
391 cl = getchangelog(n2)[0]
392 m = getmanifest(cl)
393 try:
394 src = getfile(f).renamed(m[f])
395 except KeyError:
396 return None
397 if src:
398 f = src[0]
399 n2 = repo.changelog.parents(n2)[0]
400 r2 = repo.changelog.rev(n2)
401 return src
402
370 403 if node2:
371 change = repo.changelog.read(node2)
372 mmap2 = repo.manifest.read(change[0])
404 change = getchangelog(node2)
405 mmap2 = getmanifest(change[0])
373 406 _date2 = util.datestr(change[2])
374 407 def date2(f):
375 408 return _date2
376 409 def read(f):
377 return repo.file(f).read(mmap2[f])
410 return getfile(f).read(mmap2[f])
378 411 def renamed(f):
379 src = repo.file(f).renamed(mmap2[f])
380 return src and src[0] or None
412 return renamedbetween(f, node1, node2)
381 413 else:
382 414 tz = util.makedate()[1]
383 415 _date2 = util.datestr()
384 416 def date2(f):
385 417 try:
386 418 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
387 419 except OSError, err:
388 420 if err.errno != errno.ENOENT: raise
389 421 return _date2
390 422 def read(f):
391 423 return repo.wread(f)
392 424 def renamed(f):
393 return repo.dirstate.copies.get(f)
425 src = repo.dirstate.copies.get(f)
426 parent = repo.dirstate.parents()[0]
427 if src:
428 f = src[0]
429 of = renamedbetween(f, node1, parent)
430 if of:
431 return of
432 elif src:
433 cl = getchangelog(parent)[0]
434 return (src, getmanifest(cl)[src])
435 else:
436 return None
394 437
395 438 if repo.ui.quiet:
396 439 r = None
397 440 else:
398 441 hexfunc = repo.ui.verbose and hex or short
399 442 r = [hexfunc(node) for node in [node1, node2] if node]
400 443
401 444 if opts.git:
402 445 copied = {}
403 446 for f in added:
404 447 src = renamed(f)
405 448 if src:
406 449 copied[f] = src
407 srcs = [x[1] for x in copied.items()]
450 srcs = [x[1][0] for x in copied.items()]
408 451
409 452 all = modified + added + removed
410 453 all.sort()
411 454 for f in all:
412 455 to = None
413 456 tn = None
414 457 dodiff = True
415 458 if f in mmap:
416 to = repo.file(f).read(mmap[f])
459 to = getfile(f).read(mmap[f])
417 460 if f not in removed:
418 461 tn = read(f)
419 462 if opts.git:
420 463 def gitmode(x):
421 464 return x and '100755' or '100644'
422 465 def addmodehdr(header, omode, nmode):
423 466 if omode != nmode:
424 467 header.append('old mode %s\n' % omode)
425 468 header.append('new mode %s\n' % nmode)
426 469
427 470 a, b = f, f
428 471 header = []
429 472 if f in added:
430 473 if node2:
431 474 mode = gitmode(mmap2.execf(f))
432 475 else:
433 476 mode = gitmode(util.is_exec(repo.wjoin(f), None))
434 477 if f in copied:
435 a = copied[f]
478 a, arev = copied[f]
436 479 omode = gitmode(mmap.execf(a))
437 480 addmodehdr(header, omode, mode)
438 481 op = a in removed and 'rename' or 'copy'
439 482 header.append('%s from %s\n' % (op, a))
440 483 header.append('%s to %s\n' % (op, f))
441 to = repo.file(a).read(mmap[a])
484 to = getfile(a).read(arev)
442 485 else:
443 486 header.append('new file mode %s\n' % mode)
444 487 elif f in removed:
445 488 if f in srcs:
446 489 dodiff = False
447 490 else:
448 491 mode = gitmode(mmap.execf(f))
449 492 header.append('deleted file mode %s\n' % mode)
450 493 else:
451 494 omode = gitmode(mmap.execf(f))
452 495 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
453 496 addmodehdr(header, omode, nmode)
454 497 r = None
455 498 if dodiff:
456 499 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
457 500 fp.write(''.join(header))
458 501 if dodiff:
459 502 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
460 503
461 504 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
462 505 opts=None):
463 506 '''export changesets as hg patches.'''
464 507
465 508 total = len(revs)
466 509 revwidth = max(map(len, revs))
467 510
468 511 def single(node, seqno, fp):
469 512 parents = [p for p in repo.changelog.parents(node) if p != nullid]
470 513 if switch_parent:
471 514 parents.reverse()
472 515 prev = (parents and parents[0]) or nullid
473 516 change = repo.changelog.read(node)
474 517
475 518 if not fp:
476 519 fp = cmdutil.make_file(repo, template, node, total=total,
477 520 seqno=seqno, revwidth=revwidth)
478 521 if fp not in (sys.stdout, repo.ui):
479 522 repo.ui.note("%s\n" % fp.name)
480 523
481 524 fp.write("# HG changeset patch\n")
482 525 fp.write("# User %s\n" % change[1])
483 526 fp.write("# Date %d %d\n" % change[2])
484 527 fp.write("# Node ID %s\n" % hex(node))
485 528 fp.write("# Parent %s\n" % hex(prev))
486 529 if len(parents) > 1:
487 530 fp.write("# Parent %s\n" % hex(parents[1]))
488 531 fp.write(change[4].rstrip())
489 532 fp.write("\n\n")
490 533
491 534 diff(repo, prev, node, fp=fp, opts=opts)
492 535 if fp not in (sys.stdout, repo.ui):
493 536 fp.close()
494 537
495 538 for seqno, cset in enumerate(revs):
496 539 single(cset, seqno, fp)
@@ -1,128 +1,153 b''
1 1 #!/bin/sh
2 2
3 3 HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
4 4 echo "[extensions]" >> $HGTMP/.hgrc
5 5 echo "mq=" >> $HGTMP/.hgrc
6 6
7 7 echo % help
8 8 hg help mq
9 9
10 10 hg init a
11 11 cd a
12 12 echo a > a
13 13 hg ci -Ama
14 14
15 15 hg clone . ../k
16 16
17 17 mkdir b
18 18 echo z > b/z
19 19 hg ci -Ama
20 20
21 21 echo % qinit
22 22
23 23 hg qinit
24 24
25 25 cd ..
26 26 hg init b
27 27
28 28 echo % -R qinit
29 29
30 30 hg -R b qinit
31 31
32 32 hg init c
33 33
34 34 echo % qinit -c
35 35
36 36 hg --cwd c qinit -c
37 37 hg -R c/.hg/patches st
38 38
39 39 echo % qnew implies add
40 40
41 41 hg -R c qnew test.patch
42 42 hg -R c/.hg/patches st
43 43
44 44 cd a
45 45
46 46 echo % qnew -m
47 47
48 48 hg qnew -m 'foo bar' test.patch
49 49 cat .hg/patches/test.patch
50 50
51 51 echo % qrefresh
52 52
53 53 echo a >> a
54 54 hg qrefresh
55 55 sed -e "s/^\(diff -r \)\([a-f0-9]* \)/\1 x/" \
56 56 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
57 57 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/test.patch
58 58
59 59 echo % qpop
60 60
61 61 hg qpop
62 62
63 63 echo % qpush
64 64
65 65 hg qpush
66 66
67 67 cd ..
68 68
69 69 echo % pop/push outside repo
70 70
71 71 hg -R a qpop
72 72 hg -R a qpush
73 73
74 74 cd a
75 75 hg qnew test2.patch
76 76
77 77 echo % qrefresh in subdir
78 78
79 79 cd b
80 80 echo a > a
81 81 hg add a
82 82 hg qrefresh
83 83
84 84 echo % pop/push -a in subdir
85 85
86 86 hg qpop -a
87 87 hg --traceback qpush -a
88 88
89 89 echo % qseries
90 90 hg qseries
91 91
92 92 echo % qapplied
93 93 hg qapplied
94 94
95 95 echo % qtop
96 96 hg qtop
97 97
98 98 echo % qprev
99 99 hg qprev
100 100
101 101 echo % qnext
102 102 hg qnext
103 103
104 104 echo % pop, qnext, qprev, qapplied
105 105 hg qpop
106 106 hg qnext
107 107 hg qprev
108 108 hg qapplied
109 109
110 110 echo % commit should fail
111 111 hg commit
112 112
113 113 echo % push should fail
114 114 hg push ../../k
115 115
116 116 echo % qunapplied
117 117 hg qunapplied
118 118
119 119 echo % push should succeed
120 120 hg qpop -a
121 121 hg push ../../k
122 122
123 123 echo % strip
124 124 cd ../../b
125 125 echo x>x
126 126 hg ci -Ama
127 127 hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/'
128 128 hg unbundle .hg/strip-backup/*
129
130 cat >>$HGTMP/.hgrc <<EOF
131 [diff]
132 git = True
133 EOF
134 cd ..
135 hg init git
136 cd git
137 hg qinit
138
139 hg qnew -m'new file' new
140 echo foo > new
141 chmod +x new
142 hg add new
143 hg qrefresh
144 sed -e "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/new
145
146 hg qnew -m'copy file' copy
147 hg cp new copy
148 hg qrefresh
149 sed -e "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/copy
150
151 hg qpop
152 hg qpush
153 hg qdiff
@@ -1,129 +1,148 b''
1 1 % help
2 2 mq extension - patch management and development
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and
6 6 applied patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches
9 9 directory. Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 prepare repository to work with patches qinit
14 14 create new patch qnew
15 15 import existing patch qimport
16 16
17 17 print patch series qseries
18 18 print applied patches qapplied
19 19 print name of top applied patch qtop
20 20
21 21 add known patch to applied stack qpush
22 22 remove patch from applied stack qpop
23 23 refresh contents of top applied patch qrefresh
24 24
25 25 list of commands (use "hg help -v mq" to show aliases and global options):
26 26
27 27 qapplied print the patches already applied
28 28 qclone clone main and patch repository at same time
29 29 qcommit commit changes in the queue repository
30 30 qdelete remove patches from queue
31 31 qdiff diff of the current patch
32 32 qfold fold the named patches into the current patch
33 33 qguard set or print guards for a patch
34 34 qheader Print the header of the topmost or specified patch
35 35 qimport import a patch
36 36 qinit init a new queue repository
37 37 qnew create a new patch
38 38 qnext print the name of the next patch
39 39 qpop pop the current patch off the stack
40 40 qprev print the name of the previous patch
41 41 qpush push the next patch onto the stack
42 42 qrefresh update the current patch
43 43 qrename rename a patch
44 44 qrestore restore the queue state saved by a rev
45 45 qsave save current queue state
46 46 qselect set or print guarded patches to push
47 47 qseries print the entire series file
48 48 qtop print the name of the current patch
49 49 qunapplied print the patches not yet applied
50 50 strip strip a revision and all later revs on the same branch
51 51 adding a
52 52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 53 adding b/z
54 54 % qinit
55 55 % -R qinit
56 56 % qinit -c
57 57 A .hgignore
58 58 A series
59 59 % qnew implies add
60 60 A .hgignore
61 61 A series
62 62 A test.patch
63 63 % qnew -m
64 64 foo bar
65 65 % qrefresh
66 66 foo bar
67 67
68 68 diff -r xa
69 69 --- a/a
70 70 +++ b/a
71 71 @@ -1,1 +1,2 @@ a
72 72 a
73 73 +a
74 74 % qpop
75 75 Patch queue now empty
76 76 % qpush
77 77 applying test.patch
78 78 Now at: test.patch
79 79 % pop/push outside repo
80 80 Patch queue now empty
81 81 applying test.patch
82 82 Now at: test.patch
83 83 % qrefresh in subdir
84 84 % pop/push -a in subdir
85 85 Patch queue now empty
86 86 applying test.patch
87 87 applying test2.patch
88 88 Now at: test2.patch
89 89 % qseries
90 90 test.patch
91 91 test2.patch
92 92 % qapplied
93 93 test.patch
94 94 test2.patch
95 95 % qtop
96 96 test2.patch
97 97 % qprev
98 98 test.patch
99 99 % qnext
100 100 All patches applied
101 101 % pop, qnext, qprev, qapplied
102 102 Now at: test.patch
103 103 test2.patch
104 104 Only one patch applied
105 105 test.patch
106 106 % commit should fail
107 107 abort: cannot commit over an applied mq patch
108 108 % push should fail
109 109 pushing to ../../k
110 110 abort: source has mq patches applied
111 111 % qunapplied
112 112 test2.patch
113 113 % push should succeed
114 114 Patch queue now empty
115 115 pushing to ../../k
116 116 searching for changes
117 117 adding changesets
118 118 adding manifests
119 119 adding file changes
120 120 added 1 changesets with 1 changes to 1 files
121 121 % strip
122 122 adding x
123 123 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
124 124 saving bundle to
125 125 adding changesets
126 126 adding manifests
127 127 adding file changes
128 128 added 1 changesets with 1 changes to 1 files
129 129 (run 'hg update' to get a working copy)
130 new file
131
132 diff --git a/new b/new
133 new file mode 100755
134 --- /dev/null
135 +++ b/new
136 @@ -0,0 +1,1 @@
137 +foo
138 copy file
139
140 diff --git a/new b/copy
141 copy from new
142 copy to copy
143 Now at: new
144 applying copy
145 Now at: copy
146 diff --git a/new b/copy
147 copy from new
148 copy to copy
General Comments 0
You need to be logged in to leave comments. Login now