##// END OF EJS Templates
mq: preserve --git flag when merging patches...
Patrick Mezard -
r10185:7637fe4f stable
parent child Browse files
Show More
@@ -1,2696 +1,2706 b''
1 1 # mq.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 of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''manage a stack of patches
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
26 26 add known patch to applied stack qpush
27 27 remove patch from applied stack qpop
28 28 refresh contents of top applied patch qrefresh
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import bin, hex, short, nullid, nullrev
33 33 from mercurial.lock import release
34 34 from mercurial import commands, cmdutil, hg, patch, util
35 35 from mercurial import repair, extensions, url, error
36 36 import os, sys, re, errno
37 37
38 38 commands.norepo += " qclone"
39 39
40 40 # Patch names looks like unix-file names.
41 41 # They must be joinable with queue directory and result in the patch path.
42 42 normname = util.normpath
43 43
44 44 class statusentry(object):
45 45 def __init__(self, rev, name=None):
46 46 if not name:
47 47 fields = rev.split(':', 1)
48 48 if len(fields) == 2:
49 49 self.rev, self.name = fields
50 50 else:
51 51 self.rev, self.name = None, None
52 52 else:
53 53 self.rev, self.name = rev, name
54 54
55 55 def __str__(self):
56 56 return self.rev + ':' + self.name
57 57
58 58 class patchheader(object):
59 59 def __init__(self, pf):
60 60 def eatdiff(lines):
61 61 while lines:
62 62 l = lines[-1]
63 63 if (l.startswith("diff -") or
64 64 l.startswith("Index:") or
65 65 l.startswith("===========")):
66 66 del lines[-1]
67 67 else:
68 68 break
69 69 def eatempty(lines):
70 70 while lines:
71 71 l = lines[-1]
72 72 if re.match('\s*$', l):
73 73 del lines[-1]
74 74 else:
75 75 break
76 76
77 77 message = []
78 78 comments = []
79 79 user = None
80 80 date = None
81 81 format = None
82 82 subject = None
83 83 diffstart = 0
84 84
85 85 for line in file(pf):
86 86 line = line.rstrip()
87 87 if line.startswith('diff --git'):
88 88 diffstart = 2
89 89 break
90 90 if diffstart:
91 91 if line.startswith('+++ '):
92 92 diffstart = 2
93 93 break
94 94 if line.startswith("--- "):
95 95 diffstart = 1
96 96 continue
97 97 elif format == "hgpatch":
98 98 # parse values when importing the result of an hg export
99 99 if line.startswith("# User "):
100 100 user = line[7:]
101 101 elif line.startswith("# Date "):
102 102 date = line[7:]
103 103 elif not line.startswith("# ") and line:
104 104 message.append(line)
105 105 format = None
106 106 elif line == '# HG changeset patch':
107 107 message = []
108 108 format = "hgpatch"
109 109 elif (format != "tagdone" and (line.startswith("Subject: ") or
110 110 line.startswith("subject: "))):
111 111 subject = line[9:]
112 112 format = "tag"
113 113 elif (format != "tagdone" and (line.startswith("From: ") or
114 114 line.startswith("from: "))):
115 115 user = line[6:]
116 116 format = "tag"
117 117 elif format == "tag" and line == "":
118 118 # when looking for tags (subject: from: etc) they
119 119 # end once you find a blank line in the source
120 120 format = "tagdone"
121 121 elif message or line:
122 122 message.append(line)
123 123 comments.append(line)
124 124
125 125 eatdiff(message)
126 126 eatdiff(comments)
127 127 eatempty(message)
128 128 eatempty(comments)
129 129
130 130 # make sure message isn't empty
131 131 if format and format.startswith("tag") and subject:
132 132 message.insert(0, "")
133 133 message.insert(0, subject)
134 134
135 135 self.message = message
136 136 self.comments = comments
137 137 self.user = user
138 138 self.date = date
139 139 self.haspatch = diffstart > 1
140 140
141 141 def setuser(self, user):
142 142 if not self.updateheader(['From: ', '# User '], user):
143 143 try:
144 144 patchheaderat = self.comments.index('# HG changeset patch')
145 145 self.comments.insert(patchheaderat + 1, '# User ' + user)
146 146 except ValueError:
147 147 if self._hasheader(['Date: ']):
148 148 self.comments = ['From: ' + user] + self.comments
149 149 else:
150 150 tmp = ['# HG changeset patch', '# User ' + user, '']
151 151 self.comments = tmp + self.comments
152 152 self.user = user
153 153
154 154 def setdate(self, date):
155 155 if not self.updateheader(['Date: ', '# Date '], date):
156 156 try:
157 157 patchheaderat = self.comments.index('# HG changeset patch')
158 158 self.comments.insert(patchheaderat + 1, '# Date ' + date)
159 159 except ValueError:
160 160 if self._hasheader(['From: ']):
161 161 self.comments = ['Date: ' + date] + self.comments
162 162 else:
163 163 tmp = ['# HG changeset patch', '# Date ' + date, '']
164 164 self.comments = tmp + self.comments
165 165 self.date = date
166 166
167 167 def setmessage(self, message):
168 168 if self.comments:
169 169 self._delmsg()
170 170 self.message = [message]
171 171 self.comments += self.message
172 172
173 173 def updateheader(self, prefixes, new):
174 174 '''Update all references to a field in the patch header.
175 175 Return whether the field is present.'''
176 176 res = False
177 177 for prefix in prefixes:
178 178 for i in xrange(len(self.comments)):
179 179 if self.comments[i].startswith(prefix):
180 180 self.comments[i] = prefix + new
181 181 res = True
182 182 break
183 183 return res
184 184
185 185 def _hasheader(self, prefixes):
186 186 '''Check if a header starts with any of the given prefixes.'''
187 187 for prefix in prefixes:
188 188 for comment in self.comments:
189 189 if comment.startswith(prefix):
190 190 return True
191 191 return False
192 192
193 193 def __str__(self):
194 194 if not self.comments:
195 195 return ''
196 196 return '\n'.join(self.comments) + '\n\n'
197 197
198 198 def _delmsg(self):
199 199 '''Remove existing message, keeping the rest of the comments fields.
200 200 If comments contains 'subject: ', message will prepend
201 201 the field and a blank line.'''
202 202 if self.message:
203 203 subj = 'subject: ' + self.message[0].lower()
204 204 for i in xrange(len(self.comments)):
205 205 if subj == self.comments[i].lower():
206 206 del self.comments[i]
207 207 self.message = self.message[2:]
208 208 break
209 209 ci = 0
210 210 for mi in self.message:
211 211 while mi != self.comments[ci]:
212 212 ci += 1
213 213 del self.comments[ci]
214 214
215 215 class queue(object):
216 216 def __init__(self, ui, path, patchdir=None):
217 217 self.basepath = path
218 218 self.path = patchdir or os.path.join(path, "patches")
219 219 self.opener = util.opener(self.path)
220 220 self.ui = ui
221 221 self.applied_dirty = 0
222 222 self.series_dirty = 0
223 223 self.series_path = "series"
224 224 self.status_path = "status"
225 225 self.guards_path = "guards"
226 226 self.active_guards = None
227 227 self.guards_dirty = False
228 228
229 229 @util.propertycache
230 230 def applied(self):
231 231 if os.path.exists(self.join(self.status_path)):
232 232 lines = self.opener(self.status_path).read().splitlines()
233 233 return [statusentry(l) for l in lines]
234 234 return []
235 235
236 236 @util.propertycache
237 237 def full_series(self):
238 238 if os.path.exists(self.join(self.series_path)):
239 239 return self.opener(self.series_path).read().splitlines()
240 240 return []
241 241
242 242 @util.propertycache
243 243 def series(self):
244 244 self.parse_series()
245 245 return self.series
246 246
247 247 @util.propertycache
248 248 def series_guards(self):
249 249 self.parse_series()
250 250 return self.series_guards
251 251
252 252 def invalidate(self):
253 253 for a in 'applied full_series series series_guards'.split():
254 254 if a in self.__dict__:
255 255 delattr(self, a)
256 256 self.applied_dirty = 0
257 257 self.series_dirty = 0
258 258 self.guards_dirty = False
259 259 self.active_guards = None
260 260
261 261 def diffopts(self, opts={}, patchfn=None):
262 262 diffopts = patch.diffopts(self.ui, opts)
263 263 if patchfn:
264 # if the patch was a git patch, refresh it as a git patch
265 patchf = self.opener(patchfn, 'r')
266 for line in patchf:
267 if line.startswith('diff --git'):
268 diffopts.git = True
269 break
264 diffopts = self.patchopts(diffopts, patchfn)
265 return diffopts
266
267 def patchopts(self, diffopts, patchfn):
268 """Return a copy of input diff options with git set to true if
269 referenced patch is a git patch.
270 """
271 diffopts = diffopts.copy()
272 patchf = self.opener(patchfn, 'r')
273 # if the patch was a git patch, refresh it as a git patch
274 for line in patchf:
275 if line.startswith('diff --git'):
276 diffopts.git = True
277 break
278 patchf.close()
270 279 return diffopts
271 280
272 281 def join(self, *p):
273 282 return os.path.join(self.path, *p)
274 283
275 284 def find_series(self, patch):
276 285 pre = re.compile("(\s*)([^#]+)")
277 286 index = 0
278 287 for l in self.full_series:
279 288 m = pre.match(l)
280 289 if m:
281 290 s = m.group(2)
282 291 s = s.rstrip()
283 292 if s == patch:
284 293 return index
285 294 index += 1
286 295 return None
287 296
288 297 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
289 298
290 299 def parse_series(self):
291 300 self.series = []
292 301 self.series_guards = []
293 302 for l in self.full_series:
294 303 h = l.find('#')
295 304 if h == -1:
296 305 patch = l
297 306 comment = ''
298 307 elif h == 0:
299 308 continue
300 309 else:
301 310 patch = l[:h]
302 311 comment = l[h:]
303 312 patch = patch.strip()
304 313 if patch:
305 314 if patch in self.series:
306 315 raise util.Abort(_('%s appears more than once in %s') %
307 316 (patch, self.join(self.series_path)))
308 317 self.series.append(patch)
309 318 self.series_guards.append(self.guard_re.findall(comment))
310 319
311 320 def check_guard(self, guard):
312 321 if not guard:
313 322 return _('guard cannot be an empty string')
314 323 bad_chars = '# \t\r\n\f'
315 324 first = guard[0]
316 325 if first in '-+':
317 326 return (_('guard %r starts with invalid character: %r') %
318 327 (guard, first))
319 328 for c in bad_chars:
320 329 if c in guard:
321 330 return _('invalid character in guard %r: %r') % (guard, c)
322 331
323 332 def set_active(self, guards):
324 333 for guard in guards:
325 334 bad = self.check_guard(guard)
326 335 if bad:
327 336 raise util.Abort(bad)
328 337 guards = sorted(set(guards))
329 338 self.ui.debug('active guards: %s\n' % ' '.join(guards))
330 339 self.active_guards = guards
331 340 self.guards_dirty = True
332 341
333 342 def active(self):
334 343 if self.active_guards is None:
335 344 self.active_guards = []
336 345 try:
337 346 guards = self.opener(self.guards_path).read().split()
338 347 except IOError, err:
339 348 if err.errno != errno.ENOENT: raise
340 349 guards = []
341 350 for i, guard in enumerate(guards):
342 351 bad = self.check_guard(guard)
343 352 if bad:
344 353 self.ui.warn('%s:%d: %s\n' %
345 354 (self.join(self.guards_path), i + 1, bad))
346 355 else:
347 356 self.active_guards.append(guard)
348 357 return self.active_guards
349 358
350 359 def set_guards(self, idx, guards):
351 360 for g in guards:
352 361 if len(g) < 2:
353 362 raise util.Abort(_('guard %r too short') % g)
354 363 if g[0] not in '-+':
355 364 raise util.Abort(_('guard %r starts with invalid char') % g)
356 365 bad = self.check_guard(g[1:])
357 366 if bad:
358 367 raise util.Abort(bad)
359 368 drop = self.guard_re.sub('', self.full_series[idx])
360 369 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
361 370 self.parse_series()
362 371 self.series_dirty = True
363 372
364 373 def pushable(self, idx):
365 374 if isinstance(idx, str):
366 375 idx = self.series.index(idx)
367 376 patchguards = self.series_guards[idx]
368 377 if not patchguards:
369 378 return True, None
370 379 guards = self.active()
371 380 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
372 381 if exactneg:
373 382 return False, exactneg[0]
374 383 pos = [g for g in patchguards if g[0] == '+']
375 384 exactpos = [g for g in pos if g[1:] in guards]
376 385 if pos:
377 386 if exactpos:
378 387 return True, exactpos[0]
379 388 return False, pos
380 389 return True, ''
381 390
382 391 def explain_pushable(self, idx, all_patches=False):
383 392 write = all_patches and self.ui.write or self.ui.warn
384 393 if all_patches or self.ui.verbose:
385 394 if isinstance(idx, str):
386 395 idx = self.series.index(idx)
387 396 pushable, why = self.pushable(idx)
388 397 if all_patches and pushable:
389 398 if why is None:
390 399 write(_('allowing %s - no guards in effect\n') %
391 400 self.series[idx])
392 401 else:
393 402 if not why:
394 403 write(_('allowing %s - no matching negative guards\n') %
395 404 self.series[idx])
396 405 else:
397 406 write(_('allowing %s - guarded by %r\n') %
398 407 (self.series[idx], why))
399 408 if not pushable:
400 409 if why:
401 410 write(_('skipping %s - guarded by %r\n') %
402 411 (self.series[idx], why))
403 412 else:
404 413 write(_('skipping %s - no matching guards\n') %
405 414 self.series[idx])
406 415
407 416 def save_dirty(self):
408 417 def write_list(items, path):
409 418 fp = self.opener(path, 'w')
410 419 for i in items:
411 420 fp.write("%s\n" % i)
412 421 fp.close()
413 422 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
414 423 if self.series_dirty: write_list(self.full_series, self.series_path)
415 424 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
416 425
417 426 def removeundo(self, repo):
418 427 undo = repo.sjoin('undo')
419 428 if not os.path.exists(undo):
420 429 return
421 430 try:
422 431 os.unlink(undo)
423 432 except OSError, inst:
424 433 self.ui.warn(_('error removing undo: %s\n') % str(inst))
425 434
426 435 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
427 436 fp=None, changes=None, opts={}):
428 437 stat = opts.get('stat')
429 438 if stat:
430 439 opts['unified'] = '0'
431 440
432 441 m = cmdutil.match(repo, files, opts)
433 442 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
434 443 write = fp is None and repo.ui.write or fp.write
435 444 if stat:
436 445 width = self.ui.interactive() and util.termwidth() or 80
437 446 write(patch.diffstat(util.iterlines(chunks), width=width,
438 447 git=diffopts.git))
439 448 else:
440 449 for chunk in chunks:
441 450 write(chunk)
442 451
443 452 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
444 453 # first try just applying the patch
445 454 (err, n) = self.apply(repo, [ patch ], update_status=False,
446 455 strict=True, merge=rev)
447 456
448 457 if err == 0:
449 458 return (err, n)
450 459
451 460 if n is None:
452 461 raise util.Abort(_("apply failed for patch %s") % patch)
453 462
454 463 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
455 464
456 465 # apply failed, strip away that rev and merge.
457 466 hg.clean(repo, head)
458 467 self.strip(repo, n, update=False, backup='strip')
459 468
460 469 ctx = repo[rev]
461 470 ret = hg.merge(repo, rev)
462 471 if ret:
463 472 raise util.Abort(_("update returned %d") % ret)
464 473 n = repo.commit(ctx.description(), ctx.user(), force=True)
465 474 if n is None:
466 475 raise util.Abort(_("repo commit failed"))
467 476 try:
468 477 ph = patchheader(mergeq.join(patch))
469 478 except:
470 479 raise util.Abort(_("unable to read %s") % patch)
471 480
481 diffopts = self.patchopts(diffopts, patch)
472 482 patchf = self.opener(patch, "w")
473 483 comments = str(ph)
474 484 if comments:
475 485 patchf.write(comments)
476 486 self.printdiff(repo, diffopts, head, n, fp=patchf)
477 487 patchf.close()
478 488 self.removeundo(repo)
479 489 return (0, n)
480 490
481 491 def qparents(self, repo, rev=None):
482 492 if rev is None:
483 493 (p1, p2) = repo.dirstate.parents()
484 494 if p2 == nullid:
485 495 return p1
486 496 if len(self.applied) == 0:
487 497 return None
488 498 return bin(self.applied[-1].rev)
489 499 pp = repo.changelog.parents(rev)
490 500 if pp[1] != nullid:
491 501 arevs = [ x.rev for x in self.applied ]
492 502 p0 = hex(pp[0])
493 503 p1 = hex(pp[1])
494 504 if p0 in arevs:
495 505 return pp[0]
496 506 if p1 in arevs:
497 507 return pp[1]
498 508 return pp[0]
499 509
500 510 def mergepatch(self, repo, mergeq, series, diffopts):
501 511 if len(self.applied) == 0:
502 512 # each of the patches merged in will have two parents. This
503 513 # can confuse the qrefresh, qdiff, and strip code because it
504 514 # needs to know which parent is actually in the patch queue.
505 515 # so, we insert a merge marker with only one parent. This way
506 516 # the first patch in the queue is never a merge patch
507 517 #
508 518 pname = ".hg.patches.merge.marker"
509 519 n = repo.commit('[mq]: merge marker', force=True)
510 520 self.removeundo(repo)
511 521 self.applied.append(statusentry(hex(n), pname))
512 522 self.applied_dirty = 1
513 523
514 524 head = self.qparents(repo)
515 525
516 526 for patch in series:
517 527 patch = mergeq.lookup(patch, strict=True)
518 528 if not patch:
519 529 self.ui.warn(_("patch %s does not exist\n") % patch)
520 530 return (1, None)
521 531 pushable, reason = self.pushable(patch)
522 532 if not pushable:
523 533 self.explain_pushable(patch, all_patches=True)
524 534 continue
525 535 info = mergeq.isapplied(patch)
526 536 if not info:
527 537 self.ui.warn(_("patch %s is not applied\n") % patch)
528 538 return (1, None)
529 539 rev = bin(info[1])
530 540 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
531 541 if head:
532 542 self.applied.append(statusentry(hex(head), patch))
533 543 self.applied_dirty = 1
534 544 if err:
535 545 return (err, head)
536 546 self.save_dirty()
537 547 return (0, head)
538 548
539 549 def patch(self, repo, patchfile):
540 550 '''Apply patchfile to the working directory.
541 551 patchfile: name of patch file'''
542 552 files = {}
543 553 try:
544 554 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
545 555 files=files, eolmode=None)
546 556 except Exception, inst:
547 557 self.ui.note(str(inst) + '\n')
548 558 if not self.ui.verbose:
549 559 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
550 560 return (False, files, False)
551 561
552 562 return (True, files, fuzz)
553 563
554 564 def apply(self, repo, series, list=False, update_status=True,
555 565 strict=False, patchdir=None, merge=None, all_files={}):
556 566 wlock = lock = tr = None
557 567 try:
558 568 wlock = repo.wlock()
559 569 lock = repo.lock()
560 570 tr = repo.transaction()
561 571 try:
562 572 ret = self._apply(repo, series, list, update_status,
563 573 strict, patchdir, merge, all_files=all_files)
564 574 tr.close()
565 575 self.save_dirty()
566 576 return ret
567 577 except:
568 578 try:
569 579 tr.abort()
570 580 finally:
571 581 repo.invalidate()
572 582 repo.dirstate.invalidate()
573 583 raise
574 584 finally:
575 585 del tr
576 586 release(lock, wlock)
577 587 self.removeundo(repo)
578 588
579 589 def _apply(self, repo, series, list=False, update_status=True,
580 590 strict=False, patchdir=None, merge=None, all_files={}):
581 591 '''returns (error, hash)
582 592 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
583 593 # TODO unify with commands.py
584 594 if not patchdir:
585 595 patchdir = self.path
586 596 err = 0
587 597 n = None
588 598 for patchname in series:
589 599 pushable, reason = self.pushable(patchname)
590 600 if not pushable:
591 601 self.explain_pushable(patchname, all_patches=True)
592 602 continue
593 603 self.ui.status(_("applying %s\n") % patchname)
594 604 pf = os.path.join(patchdir, patchname)
595 605
596 606 try:
597 607 ph = patchheader(self.join(patchname))
598 608 except:
599 609 self.ui.warn(_("unable to read %s\n") % patchname)
600 610 err = 1
601 611 break
602 612
603 613 message = ph.message
604 614 if not message:
605 615 message = _("imported patch %s\n") % patchname
606 616 else:
607 617 if list:
608 618 message.append(_("\nimported patch %s") % patchname)
609 619 message = '\n'.join(message)
610 620
611 621 if ph.haspatch:
612 622 (patcherr, files, fuzz) = self.patch(repo, pf)
613 623 all_files.update(files)
614 624 patcherr = not patcherr
615 625 else:
616 626 self.ui.warn(_("patch %s is empty\n") % patchname)
617 627 patcherr, files, fuzz = 0, [], 0
618 628
619 629 if merge and files:
620 630 # Mark as removed/merged and update dirstate parent info
621 631 removed = []
622 632 merged = []
623 633 for f in files:
624 634 if os.path.exists(repo.wjoin(f)):
625 635 merged.append(f)
626 636 else:
627 637 removed.append(f)
628 638 for f in removed:
629 639 repo.dirstate.remove(f)
630 640 for f in merged:
631 641 repo.dirstate.merge(f)
632 642 p1, p2 = repo.dirstate.parents()
633 643 repo.dirstate.setparents(p1, merge)
634 644
635 645 files = patch.updatedir(self.ui, repo, files)
636 646 match = cmdutil.matchfiles(repo, files or [])
637 647 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
638 648
639 649 if n is None:
640 650 raise util.Abort(_("repo commit failed"))
641 651
642 652 if update_status:
643 653 self.applied.append(statusentry(hex(n), patchname))
644 654
645 655 if patcherr:
646 656 self.ui.warn(_("patch failed, rejects left in working dir\n"))
647 657 err = 2
648 658 break
649 659
650 660 if fuzz and strict:
651 661 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
652 662 err = 3
653 663 break
654 664 return (err, n)
655 665
656 666 def _cleanup(self, patches, numrevs, keep=False):
657 667 if not keep:
658 668 r = self.qrepo()
659 669 if r:
660 670 r.remove(patches, True)
661 671 else:
662 672 for p in patches:
663 673 os.unlink(self.join(p))
664 674
665 675 if numrevs:
666 676 del self.applied[:numrevs]
667 677 self.applied_dirty = 1
668 678
669 679 for i in sorted([self.find_series(p) for p in patches], reverse=True):
670 680 del self.full_series[i]
671 681 self.parse_series()
672 682 self.series_dirty = 1
673 683
674 684 def _revpatches(self, repo, revs):
675 685 firstrev = repo[self.applied[0].rev].rev()
676 686 patches = []
677 687 for i, rev in enumerate(revs):
678 688
679 689 if rev < firstrev:
680 690 raise util.Abort(_('revision %d is not managed') % rev)
681 691
682 692 ctx = repo[rev]
683 693 base = bin(self.applied[i].rev)
684 694 if ctx.node() != base:
685 695 msg = _('cannot delete revision %d above applied patches')
686 696 raise util.Abort(msg % rev)
687 697
688 698 patch = self.applied[i].name
689 699 for fmt in ('[mq]: %s', 'imported patch %s'):
690 700 if ctx.description() == fmt % patch:
691 701 msg = _('patch %s finalized without changeset message\n')
692 702 repo.ui.status(msg % patch)
693 703 break
694 704
695 705 patches.append(patch)
696 706 return patches
697 707
698 708 def finish(self, repo, revs):
699 709 patches = self._revpatches(repo, sorted(revs))
700 710 self._cleanup(patches, len(patches))
701 711
702 712 def delete(self, repo, patches, opts):
703 713 if not patches and not opts.get('rev'):
704 714 raise util.Abort(_('qdelete requires at least one revision or '
705 715 'patch name'))
706 716
707 717 realpatches = []
708 718 for patch in patches:
709 719 patch = self.lookup(patch, strict=True)
710 720 info = self.isapplied(patch)
711 721 if info:
712 722 raise util.Abort(_("cannot delete applied patch %s") % patch)
713 723 if patch not in self.series:
714 724 raise util.Abort(_("patch %s not in series file") % patch)
715 725 realpatches.append(patch)
716 726
717 727 numrevs = 0
718 728 if opts.get('rev'):
719 729 if not self.applied:
720 730 raise util.Abort(_('no patches applied'))
721 731 revs = cmdutil.revrange(repo, opts['rev'])
722 732 if len(revs) > 1 and revs[0] > revs[1]:
723 733 revs.reverse()
724 734 revpatches = self._revpatches(repo, revs)
725 735 realpatches += revpatches
726 736 numrevs = len(revpatches)
727 737
728 738 self._cleanup(realpatches, numrevs, opts.get('keep'))
729 739
730 740 def check_toppatch(self, repo):
731 741 if len(self.applied) > 0:
732 742 top = bin(self.applied[-1].rev)
733 743 pp = repo.dirstate.parents()
734 744 if top not in pp:
735 745 raise util.Abort(_("working directory revision is not qtip"))
736 746 return top
737 747 return None
738 748 def check_localchanges(self, repo, force=False, refresh=True):
739 749 m, a, r, d = repo.status()[:4]
740 750 if m or a or r or d:
741 751 if not force:
742 752 if refresh:
743 753 raise util.Abort(_("local changes found, refresh first"))
744 754 else:
745 755 raise util.Abort(_("local changes found"))
746 756 return m, a, r, d
747 757
748 758 _reserved = ('series', 'status', 'guards')
749 759 def check_reserved_name(self, name):
750 760 if (name in self._reserved or name.startswith('.hg')
751 761 or name.startswith('.mq')):
752 762 raise util.Abort(_('"%s" cannot be used as the name of a patch')
753 763 % name)
754 764
755 765 def new(self, repo, patchfn, *pats, **opts):
756 766 """options:
757 767 msg: a string or a no-argument function returning a string
758 768 """
759 769 msg = opts.get('msg')
760 770 force = opts.get('force')
761 771 user = opts.get('user')
762 772 date = opts.get('date')
763 773 if date:
764 774 date = util.parsedate(date)
765 775 diffopts = self.diffopts({'git': opts.get('git')})
766 776 self.check_reserved_name(patchfn)
767 777 if os.path.exists(self.join(patchfn)):
768 778 raise util.Abort(_('patch "%s" already exists') % patchfn)
769 779 if opts.get('include') or opts.get('exclude') or pats:
770 780 match = cmdutil.match(repo, pats, opts)
771 781 # detect missing files in pats
772 782 def badfn(f, msg):
773 783 raise util.Abort('%s: %s' % (f, msg))
774 784 match.bad = badfn
775 785 m, a, r, d = repo.status(match=match)[:4]
776 786 else:
777 787 m, a, r, d = self.check_localchanges(repo, force)
778 788 match = cmdutil.matchfiles(repo, m + a + r)
779 789 if force:
780 790 p = repo[None].parents()
781 791 if len(p) > 1:
782 792 raise util.Abort(_('cannot manage merge changesets'))
783 793 commitfiles = m + a + r
784 794 self.check_toppatch(repo)
785 795 insert = self.full_series_end()
786 796 wlock = repo.wlock()
787 797 try:
788 798 # if patch file write fails, abort early
789 799 p = self.opener(patchfn, "w")
790 800 try:
791 801 if date:
792 802 p.write("# HG changeset patch\n")
793 803 if user:
794 804 p.write("# User " + user + "\n")
795 805 p.write("# Date %d %d\n\n" % date)
796 806 elif user:
797 807 p.write("From: " + user + "\n\n")
798 808
799 809 if hasattr(msg, '__call__'):
800 810 msg = msg()
801 811 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
802 812 n = repo.commit(commitmsg, user, date, match=match, force=True)
803 813 if n is None:
804 814 raise util.Abort(_("repo commit failed"))
805 815 try:
806 816 self.full_series[insert:insert] = [patchfn]
807 817 self.applied.append(statusentry(hex(n), patchfn))
808 818 self.parse_series()
809 819 self.series_dirty = 1
810 820 self.applied_dirty = 1
811 821 if msg:
812 822 msg = msg + "\n\n"
813 823 p.write(msg)
814 824 if commitfiles:
815 825 parent = self.qparents(repo, n)
816 826 chunks = patch.diff(repo, node1=parent, node2=n,
817 827 match=match, opts=diffopts)
818 828 for chunk in chunks:
819 829 p.write(chunk)
820 830 p.close()
821 831 wlock.release()
822 832 wlock = None
823 833 r = self.qrepo()
824 834 if r: r.add([patchfn])
825 835 except:
826 836 repo.rollback()
827 837 raise
828 838 except Exception:
829 839 patchpath = self.join(patchfn)
830 840 try:
831 841 os.unlink(patchpath)
832 842 except:
833 843 self.ui.warn(_('error unlinking %s\n') % patchpath)
834 844 raise
835 845 self.removeundo(repo)
836 846 finally:
837 847 release(wlock)
838 848
839 849 def strip(self, repo, rev, update=True, backup="all", force=None):
840 850 wlock = lock = None
841 851 try:
842 852 wlock = repo.wlock()
843 853 lock = repo.lock()
844 854
845 855 if update:
846 856 self.check_localchanges(repo, force=force, refresh=False)
847 857 urev = self.qparents(repo, rev)
848 858 hg.clean(repo, urev)
849 859 repo.dirstate.write()
850 860
851 861 self.removeundo(repo)
852 862 repair.strip(self.ui, repo, rev, backup)
853 863 # strip may have unbundled a set of backed up revisions after
854 864 # the actual strip
855 865 self.removeundo(repo)
856 866 finally:
857 867 release(lock, wlock)
858 868
859 869 def isapplied(self, patch):
860 870 """returns (index, rev, patch)"""
861 871 for i, a in enumerate(self.applied):
862 872 if a.name == patch:
863 873 return (i, a.rev, a.name)
864 874 return None
865 875
866 876 # if the exact patch name does not exist, we try a few
867 877 # variations. If strict is passed, we try only #1
868 878 #
869 879 # 1) a number to indicate an offset in the series file
870 880 # 2) a unique substring of the patch name was given
871 881 # 3) patchname[-+]num to indicate an offset in the series file
872 882 def lookup(self, patch, strict=False):
873 883 patch = patch and str(patch)
874 884
875 885 def partial_name(s):
876 886 if s in self.series:
877 887 return s
878 888 matches = [x for x in self.series if s in x]
879 889 if len(matches) > 1:
880 890 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
881 891 for m in matches:
882 892 self.ui.warn(' %s\n' % m)
883 893 return None
884 894 if matches:
885 895 return matches[0]
886 896 if len(self.series) > 0 and len(self.applied) > 0:
887 897 if s == 'qtip':
888 898 return self.series[self.series_end(True)-1]
889 899 if s == 'qbase':
890 900 return self.series[0]
891 901 return None
892 902
893 903 if patch is None:
894 904 return None
895 905 if patch in self.series:
896 906 return patch
897 907
898 908 if not os.path.isfile(self.join(patch)):
899 909 try:
900 910 sno = int(patch)
901 911 except(ValueError, OverflowError):
902 912 pass
903 913 else:
904 914 if -len(self.series) <= sno < len(self.series):
905 915 return self.series[sno]
906 916
907 917 if not strict:
908 918 res = partial_name(patch)
909 919 if res:
910 920 return res
911 921 minus = patch.rfind('-')
912 922 if minus >= 0:
913 923 res = partial_name(patch[:minus])
914 924 if res:
915 925 i = self.series.index(res)
916 926 try:
917 927 off = int(patch[minus+1:] or 1)
918 928 except(ValueError, OverflowError):
919 929 pass
920 930 else:
921 931 if i - off >= 0:
922 932 return self.series[i - off]
923 933 plus = patch.rfind('+')
924 934 if plus >= 0:
925 935 res = partial_name(patch[:plus])
926 936 if res:
927 937 i = self.series.index(res)
928 938 try:
929 939 off = int(patch[plus+1:] or 1)
930 940 except(ValueError, OverflowError):
931 941 pass
932 942 else:
933 943 if i + off < len(self.series):
934 944 return self.series[i + off]
935 945 raise util.Abort(_("patch %s not in series") % patch)
936 946
937 947 def push(self, repo, patch=None, force=False, list=False,
938 948 mergeq=None, all=False):
939 949 diffopts = self.diffopts()
940 950 wlock = repo.wlock()
941 951 try:
942 952 if repo.dirstate.parents()[0] not in repo.heads():
943 953 self.ui.status(_("(working directory not at a head)\n"))
944 954
945 955 if not self.series:
946 956 self.ui.warn(_('no patches in series\n'))
947 957 return 0
948 958
949 959 patch = self.lookup(patch)
950 960 # Suppose our series file is: A B C and the current 'top'
951 961 # patch is B. qpush C should be performed (moving forward)
952 962 # qpush B is a NOP (no change) qpush A is an error (can't
953 963 # go backwards with qpush)
954 964 if patch:
955 965 info = self.isapplied(patch)
956 966 if info:
957 967 if info[0] < len(self.applied) - 1:
958 968 raise util.Abort(
959 969 _("cannot push to a previous patch: %s") % patch)
960 970 self.ui.warn(
961 971 _('qpush: %s is already at the top\n') % patch)
962 972 return
963 973 pushable, reason = self.pushable(patch)
964 974 if not pushable:
965 975 if reason:
966 976 reason = _('guarded by %r') % reason
967 977 else:
968 978 reason = _('no matching guards')
969 979 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
970 980 return 1
971 981 elif all:
972 982 patch = self.series[-1]
973 983 if self.isapplied(patch):
974 984 self.ui.warn(_('all patches are currently applied\n'))
975 985 return 0
976 986
977 987 # Following the above example, starting at 'top' of B:
978 988 # qpush should be performed (pushes C), but a subsequent
979 989 # qpush without an argument is an error (nothing to
980 990 # apply). This allows a loop of "...while hg qpush..." to
981 991 # work as it detects an error when done
982 992 start = self.series_end()
983 993 if start == len(self.series):
984 994 self.ui.warn(_('patch series already fully applied\n'))
985 995 return 1
986 996 if not force:
987 997 self.check_localchanges(repo)
988 998
989 999 self.applied_dirty = 1
990 1000 if start > 0:
991 1001 self.check_toppatch(repo)
992 1002 if not patch:
993 1003 patch = self.series[start]
994 1004 end = start + 1
995 1005 else:
996 1006 end = self.series.index(patch, start) + 1
997 1007
998 1008 s = self.series[start:end]
999 1009 all_files = {}
1000 1010 try:
1001 1011 if mergeq:
1002 1012 ret = self.mergepatch(repo, mergeq, s, diffopts)
1003 1013 else:
1004 1014 ret = self.apply(repo, s, list, all_files=all_files)
1005 1015 except:
1006 1016 self.ui.warn(_('cleaning up working directory...'))
1007 1017 node = repo.dirstate.parents()[0]
1008 1018 hg.revert(repo, node, None)
1009 1019 unknown = repo.status(unknown=True)[4]
1010 1020 # only remove unknown files that we know we touched or
1011 1021 # created while patching
1012 1022 for f in unknown:
1013 1023 if f in all_files:
1014 1024 util.unlink(repo.wjoin(f))
1015 1025 self.ui.warn(_('done\n'))
1016 1026 raise
1017 1027
1018 1028 if not self.applied:
1019 1029 return ret[0]
1020 1030 top = self.applied[-1].name
1021 1031 if ret[0] and ret[0] > 1:
1022 1032 msg = _("errors during apply, please fix and refresh %s\n")
1023 1033 self.ui.write(msg % top)
1024 1034 else:
1025 1035 self.ui.write(_("now at: %s\n") % top)
1026 1036 return ret[0]
1027 1037
1028 1038 finally:
1029 1039 wlock.release()
1030 1040
1031 1041 def pop(self, repo, patch=None, force=False, update=True, all=False):
1032 1042 def getfile(f, rev, flags):
1033 1043 t = repo.file(f).read(rev)
1034 1044 repo.wwrite(f, t, flags)
1035 1045
1036 1046 wlock = repo.wlock()
1037 1047 try:
1038 1048 if patch:
1039 1049 # index, rev, patch
1040 1050 info = self.isapplied(patch)
1041 1051 if not info:
1042 1052 patch = self.lookup(patch)
1043 1053 info = self.isapplied(patch)
1044 1054 if not info:
1045 1055 raise util.Abort(_("patch %s is not applied") % patch)
1046 1056
1047 1057 if len(self.applied) == 0:
1048 1058 # Allow qpop -a to work repeatedly,
1049 1059 # but not qpop without an argument
1050 1060 self.ui.warn(_("no patches applied\n"))
1051 1061 return not all
1052 1062
1053 1063 if all:
1054 1064 start = 0
1055 1065 elif patch:
1056 1066 start = info[0] + 1
1057 1067 else:
1058 1068 start = len(self.applied) - 1
1059 1069
1060 1070 if start >= len(self.applied):
1061 1071 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1062 1072 return
1063 1073
1064 1074 if not update:
1065 1075 parents = repo.dirstate.parents()
1066 1076 rr = [ bin(x.rev) for x in self.applied ]
1067 1077 for p in parents:
1068 1078 if p in rr:
1069 1079 self.ui.warn(_("qpop: forcing dirstate update\n"))
1070 1080 update = True
1071 1081 else:
1072 1082 parents = [p.hex() for p in repo[None].parents()]
1073 1083 needupdate = False
1074 1084 for entry in self.applied[start:]:
1075 1085 if entry.rev in parents:
1076 1086 needupdate = True
1077 1087 break
1078 1088 update = needupdate
1079 1089
1080 1090 if not force and update:
1081 1091 self.check_localchanges(repo)
1082 1092
1083 1093 self.applied_dirty = 1
1084 1094 end = len(self.applied)
1085 1095 rev = bin(self.applied[start].rev)
1086 1096 if update:
1087 1097 top = self.check_toppatch(repo)
1088 1098
1089 1099 try:
1090 1100 heads = repo.changelog.heads(rev)
1091 1101 except error.LookupError:
1092 1102 node = short(rev)
1093 1103 raise util.Abort(_('trying to pop unknown node %s') % node)
1094 1104
1095 1105 if heads != [bin(self.applied[-1].rev)]:
1096 1106 raise util.Abort(_("popping would remove a revision not "
1097 1107 "managed by this patch queue"))
1098 1108
1099 1109 # we know there are no local changes, so we can make a simplified
1100 1110 # form of hg.update.
1101 1111 if update:
1102 1112 qp = self.qparents(repo, rev)
1103 1113 changes = repo.changelog.read(qp)
1104 1114 mmap = repo.manifest.read(changes[0])
1105 1115 m, a, r, d = repo.status(qp, top)[:4]
1106 1116 if d:
1107 1117 raise util.Abort(_("deletions found between repo revs"))
1108 1118 for f in a:
1109 1119 try:
1110 1120 os.unlink(repo.wjoin(f))
1111 1121 except OSError, e:
1112 1122 if e.errno != errno.ENOENT:
1113 1123 raise
1114 1124 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1115 1125 except: pass
1116 1126 repo.dirstate.forget(f)
1117 1127 for f in m:
1118 1128 getfile(f, mmap[f], mmap.flags(f))
1119 1129 for f in r:
1120 1130 getfile(f, mmap[f], mmap.flags(f))
1121 1131 for f in m + r:
1122 1132 repo.dirstate.normal(f)
1123 1133 repo.dirstate.setparents(qp, nullid)
1124 1134 for patch in reversed(self.applied[start:end]):
1125 1135 self.ui.status(_("popping %s\n") % patch.name)
1126 1136 del self.applied[start:end]
1127 1137 self.strip(repo, rev, update=False, backup='strip')
1128 1138 if len(self.applied):
1129 1139 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1130 1140 else:
1131 1141 self.ui.write(_("patch queue now empty\n"))
1132 1142 finally:
1133 1143 wlock.release()
1134 1144
1135 1145 def diff(self, repo, pats, opts):
1136 1146 top = self.check_toppatch(repo)
1137 1147 if not top:
1138 1148 self.ui.write(_("no patches applied\n"))
1139 1149 return
1140 1150 qp = self.qparents(repo, top)
1141 1151 if opts.get('reverse'):
1142 1152 node1, node2 = None, qp
1143 1153 else:
1144 1154 node1, node2 = qp, None
1145 1155 diffopts = self.diffopts(opts)
1146 1156 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1147 1157
1148 1158 def refresh(self, repo, pats=None, **opts):
1149 1159 if len(self.applied) == 0:
1150 1160 self.ui.write(_("no patches applied\n"))
1151 1161 return 1
1152 1162 msg = opts.get('msg', '').rstrip()
1153 1163 newuser = opts.get('user')
1154 1164 newdate = opts.get('date')
1155 1165 if newdate:
1156 1166 newdate = '%d %d' % util.parsedate(newdate)
1157 1167 wlock = repo.wlock()
1158 1168 try:
1159 1169 self.check_toppatch(repo)
1160 1170 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1161 1171 top = bin(top)
1162 1172 if repo.changelog.heads(top) != [top]:
1163 1173 raise util.Abort(_("cannot refresh a revision with children"))
1164 1174 cparents = repo.changelog.parents(top)
1165 1175 patchparent = self.qparents(repo, top)
1166 1176 ph = patchheader(self.join(patchfn))
1167 1177
1168 1178 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1169 1179 if msg:
1170 1180 ph.setmessage(msg)
1171 1181 if newuser:
1172 1182 ph.setuser(newuser)
1173 1183 if newdate:
1174 1184 ph.setdate(newdate)
1175 1185
1176 1186 # only commit new patch when write is complete
1177 1187 patchf = self.opener(patchfn, 'w', atomictemp=True)
1178 1188
1179 1189 comments = str(ph)
1180 1190 if comments:
1181 1191 patchf.write(comments)
1182 1192
1183 1193 tip = repo.changelog.tip()
1184 1194 if top == tip:
1185 1195 # if the top of our patch queue is also the tip, there is an
1186 1196 # optimization here. We update the dirstate in place and strip
1187 1197 # off the tip commit. Then just commit the current directory
1188 1198 # tree. We can also send repo.commit the list of files
1189 1199 # changed to speed up the diff
1190 1200 #
1191 1201 # in short mode, we only diff the files included in the
1192 1202 # patch already plus specified files
1193 1203 #
1194 1204 # this should really read:
1195 1205 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1196 1206 # but we do it backwards to take advantage of manifest/chlog
1197 1207 # caching against the next repo.status call
1198 1208 #
1199 1209 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1200 1210 changes = repo.changelog.read(tip)
1201 1211 man = repo.manifest.read(changes[0])
1202 1212 aaa = aa[:]
1203 1213 matchfn = cmdutil.match(repo, pats, opts)
1204 1214 if opts.get('short'):
1205 1215 # if amending a patch, we start with existing
1206 1216 # files plus specified files - unfiltered
1207 1217 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1208 1218 # filter with inc/exl options
1209 1219 matchfn = cmdutil.match(repo, opts=opts)
1210 1220 else:
1211 1221 match = cmdutil.matchall(repo)
1212 1222 m, a, r, d = repo.status(match=match)[:4]
1213 1223
1214 1224 # we might end up with files that were added between
1215 1225 # tip and the dirstate parent, but then changed in the
1216 1226 # local dirstate. in this case, we want them to only
1217 1227 # show up in the added section
1218 1228 for x in m:
1219 1229 if x not in aa:
1220 1230 mm.append(x)
1221 1231 # we might end up with files added by the local dirstate that
1222 1232 # were deleted by the patch. In this case, they should only
1223 1233 # show up in the changed section.
1224 1234 for x in a:
1225 1235 if x in dd:
1226 1236 del dd[dd.index(x)]
1227 1237 mm.append(x)
1228 1238 else:
1229 1239 aa.append(x)
1230 1240 # make sure any files deleted in the local dirstate
1231 1241 # are not in the add or change column of the patch
1232 1242 forget = []
1233 1243 for x in d + r:
1234 1244 if x in aa:
1235 1245 del aa[aa.index(x)]
1236 1246 forget.append(x)
1237 1247 continue
1238 1248 elif x in mm:
1239 1249 del mm[mm.index(x)]
1240 1250 dd.append(x)
1241 1251
1242 1252 m = list(set(mm))
1243 1253 r = list(set(dd))
1244 1254 a = list(set(aa))
1245 1255 c = [filter(matchfn, l) for l in (m, a, r)]
1246 1256 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1247 1257 chunks = patch.diff(repo, patchparent, match=match,
1248 1258 changes=c, opts=diffopts)
1249 1259 for chunk in chunks:
1250 1260 patchf.write(chunk)
1251 1261
1252 1262 try:
1253 1263 if diffopts.git:
1254 1264 copies = {}
1255 1265 for dst in a:
1256 1266 src = repo.dirstate.copied(dst)
1257 1267 # during qfold, the source file for copies may
1258 1268 # be removed. Treat this as a simple add.
1259 1269 if src is not None and src in repo.dirstate:
1260 1270 copies.setdefault(src, []).append(dst)
1261 1271 repo.dirstate.add(dst)
1262 1272 # remember the copies between patchparent and tip
1263 1273 for dst in aaa:
1264 1274 f = repo.file(dst)
1265 1275 src = f.renamed(man[dst])
1266 1276 if src:
1267 1277 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1268 1278 if dst in a:
1269 1279 copies[src[0]].append(dst)
1270 1280 # we can't copy a file created by the patch itself
1271 1281 if dst in copies:
1272 1282 del copies[dst]
1273 1283 for src, dsts in copies.iteritems():
1274 1284 for dst in dsts:
1275 1285 repo.dirstate.copy(src, dst)
1276 1286 else:
1277 1287 for dst in a:
1278 1288 repo.dirstate.add(dst)
1279 1289 # Drop useless copy information
1280 1290 for f in list(repo.dirstate.copies()):
1281 1291 repo.dirstate.copy(None, f)
1282 1292 for f in r:
1283 1293 repo.dirstate.remove(f)
1284 1294 # if the patch excludes a modified file, mark that
1285 1295 # file with mtime=0 so status can see it.
1286 1296 mm = []
1287 1297 for i in xrange(len(m)-1, -1, -1):
1288 1298 if not matchfn(m[i]):
1289 1299 mm.append(m[i])
1290 1300 del m[i]
1291 1301 for f in m:
1292 1302 repo.dirstate.normal(f)
1293 1303 for f in mm:
1294 1304 repo.dirstate.normallookup(f)
1295 1305 for f in forget:
1296 1306 repo.dirstate.forget(f)
1297 1307
1298 1308 if not msg:
1299 1309 if not ph.message:
1300 1310 message = "[mq]: %s\n" % patchfn
1301 1311 else:
1302 1312 message = "\n".join(ph.message)
1303 1313 else:
1304 1314 message = msg
1305 1315
1306 1316 user = ph.user or changes[1]
1307 1317
1308 1318 # assumes strip can roll itself back if interrupted
1309 1319 repo.dirstate.setparents(*cparents)
1310 1320 self.applied.pop()
1311 1321 self.applied_dirty = 1
1312 1322 self.strip(repo, top, update=False,
1313 1323 backup='strip')
1314 1324 except:
1315 1325 repo.dirstate.invalidate()
1316 1326 raise
1317 1327
1318 1328 try:
1319 1329 # might be nice to attempt to roll back strip after this
1320 1330 patchf.rename()
1321 1331 n = repo.commit(message, user, ph.date, match=match,
1322 1332 force=True)
1323 1333 self.applied.append(statusentry(hex(n), patchfn))
1324 1334 except:
1325 1335 ctx = repo[cparents[0]]
1326 1336 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1327 1337 self.save_dirty()
1328 1338 self.ui.warn(_('refresh interrupted while patch was popped! '
1329 1339 '(revert --all, qpush to recover)\n'))
1330 1340 raise
1331 1341 else:
1332 1342 self.printdiff(repo, diffopts, patchparent, fp=patchf)
1333 1343 patchf.rename()
1334 1344 added = repo.status()[1]
1335 1345 for a in added:
1336 1346 f = repo.wjoin(a)
1337 1347 try:
1338 1348 os.unlink(f)
1339 1349 except OSError, e:
1340 1350 if e.errno != errno.ENOENT:
1341 1351 raise
1342 1352 try: os.removedirs(os.path.dirname(f))
1343 1353 except: pass
1344 1354 # forget the file copies in the dirstate
1345 1355 # push should readd the files later on
1346 1356 repo.dirstate.forget(a)
1347 1357 self.pop(repo, force=True)
1348 1358 self.push(repo, force=True)
1349 1359 finally:
1350 1360 wlock.release()
1351 1361 self.removeundo(repo)
1352 1362
1353 1363 def init(self, repo, create=False):
1354 1364 if not create and os.path.isdir(self.path):
1355 1365 raise util.Abort(_("patch queue directory already exists"))
1356 1366 try:
1357 1367 os.mkdir(self.path)
1358 1368 except OSError, inst:
1359 1369 if inst.errno != errno.EEXIST or not create:
1360 1370 raise
1361 1371 if create:
1362 1372 return self.qrepo(create=True)
1363 1373
1364 1374 def unapplied(self, repo, patch=None):
1365 1375 if patch and patch not in self.series:
1366 1376 raise util.Abort(_("patch %s is not in series file") % patch)
1367 1377 if not patch:
1368 1378 start = self.series_end()
1369 1379 else:
1370 1380 start = self.series.index(patch) + 1
1371 1381 unapplied = []
1372 1382 for i in xrange(start, len(self.series)):
1373 1383 pushable, reason = self.pushable(i)
1374 1384 if pushable:
1375 1385 unapplied.append((i, self.series[i]))
1376 1386 self.explain_pushable(i)
1377 1387 return unapplied
1378 1388
1379 1389 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1380 1390 summary=False):
1381 1391 def displayname(pfx, patchname):
1382 1392 if summary:
1383 1393 ph = patchheader(self.join(patchname))
1384 1394 msg = ph.message and ph.message[0] or ''
1385 1395 if self.ui.interactive():
1386 1396 width = util.termwidth() - len(pfx) - len(patchname) - 2
1387 1397 if width > 0:
1388 1398 msg = util.ellipsis(msg, width)
1389 1399 else:
1390 1400 msg = ''
1391 1401 msg = "%s%s: %s" % (pfx, patchname, msg)
1392 1402 else:
1393 1403 msg = pfx + patchname
1394 1404 self.ui.write(msg + '\n')
1395 1405
1396 1406 applied = set([p.name for p in self.applied])
1397 1407 if length is None:
1398 1408 length = len(self.series) - start
1399 1409 if not missing:
1400 1410 if self.ui.verbose:
1401 1411 idxwidth = len(str(start+length - 1))
1402 1412 for i in xrange(start, start+length):
1403 1413 patch = self.series[i]
1404 1414 if patch in applied:
1405 1415 stat = 'A'
1406 1416 elif self.pushable(i)[0]:
1407 1417 stat = 'U'
1408 1418 else:
1409 1419 stat = 'G'
1410 1420 pfx = ''
1411 1421 if self.ui.verbose:
1412 1422 pfx = '%*d %s ' % (idxwidth, i, stat)
1413 1423 elif status and status != stat:
1414 1424 continue
1415 1425 displayname(pfx, patch)
1416 1426 else:
1417 1427 msng_list = []
1418 1428 for root, dirs, files in os.walk(self.path):
1419 1429 d = root[len(self.path) + 1:]
1420 1430 for f in files:
1421 1431 fl = os.path.join(d, f)
1422 1432 if (fl not in self.series and
1423 1433 fl not in (self.status_path, self.series_path,
1424 1434 self.guards_path)
1425 1435 and not fl.startswith('.')):
1426 1436 msng_list.append(fl)
1427 1437 for x in sorted(msng_list):
1428 1438 pfx = self.ui.verbose and ('D ') or ''
1429 1439 displayname(pfx, x)
1430 1440
1431 1441 def issaveline(self, l):
1432 1442 if l.name == '.hg.patches.save.line':
1433 1443 return True
1434 1444
1435 1445 def qrepo(self, create=False):
1436 1446 if create or os.path.isdir(self.join(".hg")):
1437 1447 return hg.repository(self.ui, path=self.path, create=create)
1438 1448
1439 1449 def restore(self, repo, rev, delete=None, qupdate=None):
1440 1450 c = repo.changelog.read(rev)
1441 1451 desc = c[4].strip()
1442 1452 lines = desc.splitlines()
1443 1453 i = 0
1444 1454 datastart = None
1445 1455 series = []
1446 1456 applied = []
1447 1457 qpp = None
1448 1458 for i, line in enumerate(lines):
1449 1459 if line == 'Patch Data:':
1450 1460 datastart = i + 1
1451 1461 elif line.startswith('Dirstate:'):
1452 1462 l = line.rstrip()
1453 1463 l = l[10:].split(' ')
1454 1464 qpp = [ bin(x) for x in l ]
1455 1465 elif datastart != None:
1456 1466 l = line.rstrip()
1457 1467 se = statusentry(l)
1458 1468 file_ = se.name
1459 1469 if se.rev:
1460 1470 applied.append(se)
1461 1471 else:
1462 1472 series.append(file_)
1463 1473 if datastart is None:
1464 1474 self.ui.warn(_("No saved patch data found\n"))
1465 1475 return 1
1466 1476 self.ui.warn(_("restoring status: %s\n") % lines[0])
1467 1477 self.full_series = series
1468 1478 self.applied = applied
1469 1479 self.parse_series()
1470 1480 self.series_dirty = 1
1471 1481 self.applied_dirty = 1
1472 1482 heads = repo.changelog.heads()
1473 1483 if delete:
1474 1484 if rev not in heads:
1475 1485 self.ui.warn(_("save entry has children, leaving it alone\n"))
1476 1486 else:
1477 1487 self.ui.warn(_("removing save entry %s\n") % short(rev))
1478 1488 pp = repo.dirstate.parents()
1479 1489 if rev in pp:
1480 1490 update = True
1481 1491 else:
1482 1492 update = False
1483 1493 self.strip(repo, rev, update=update, backup='strip')
1484 1494 if qpp:
1485 1495 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1486 1496 (short(qpp[0]), short(qpp[1])))
1487 1497 if qupdate:
1488 1498 self.ui.status(_("queue directory updating\n"))
1489 1499 r = self.qrepo()
1490 1500 if not r:
1491 1501 self.ui.warn(_("Unable to load queue repository\n"))
1492 1502 return 1
1493 1503 hg.clean(r, qpp[0])
1494 1504
1495 1505 def save(self, repo, msg=None):
1496 1506 if len(self.applied) == 0:
1497 1507 self.ui.warn(_("save: no patches applied, exiting\n"))
1498 1508 return 1
1499 1509 if self.issaveline(self.applied[-1]):
1500 1510 self.ui.warn(_("status is already saved\n"))
1501 1511 return 1
1502 1512
1503 1513 ar = [ ':' + x for x in self.full_series ]
1504 1514 if not msg:
1505 1515 msg = _("hg patches saved state")
1506 1516 else:
1507 1517 msg = "hg patches: " + msg.rstrip('\r\n')
1508 1518 r = self.qrepo()
1509 1519 if r:
1510 1520 pp = r.dirstate.parents()
1511 1521 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1512 1522 msg += "\n\nPatch Data:\n"
1513 1523 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1514 1524 "\n".join(ar) + '\n' or "")
1515 1525 n = repo.commit(text, force=True)
1516 1526 if not n:
1517 1527 self.ui.warn(_("repo commit failed\n"))
1518 1528 return 1
1519 1529 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1520 1530 self.applied_dirty = 1
1521 1531 self.removeundo(repo)
1522 1532
1523 1533 def full_series_end(self):
1524 1534 if len(self.applied) > 0:
1525 1535 p = self.applied[-1].name
1526 1536 end = self.find_series(p)
1527 1537 if end is None:
1528 1538 return len(self.full_series)
1529 1539 return end + 1
1530 1540 return 0
1531 1541
1532 1542 def series_end(self, all_patches=False):
1533 1543 """If all_patches is False, return the index of the next pushable patch
1534 1544 in the series, or the series length. If all_patches is True, return the
1535 1545 index of the first patch past the last applied one.
1536 1546 """
1537 1547 end = 0
1538 1548 def next(start):
1539 1549 if all_patches:
1540 1550 return start
1541 1551 i = start
1542 1552 while i < len(self.series):
1543 1553 p, reason = self.pushable(i)
1544 1554 if p:
1545 1555 break
1546 1556 self.explain_pushable(i)
1547 1557 i += 1
1548 1558 return i
1549 1559 if len(self.applied) > 0:
1550 1560 p = self.applied[-1].name
1551 1561 try:
1552 1562 end = self.series.index(p)
1553 1563 except ValueError:
1554 1564 return 0
1555 1565 return next(end + 1)
1556 1566 return next(end)
1557 1567
1558 1568 def appliedname(self, index):
1559 1569 pname = self.applied[index].name
1560 1570 if not self.ui.verbose:
1561 1571 p = pname
1562 1572 else:
1563 1573 p = str(self.series.index(pname)) + " " + pname
1564 1574 return p
1565 1575
1566 1576 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1567 1577 force=None, git=False):
1568 1578 def checkseries(patchname):
1569 1579 if patchname in self.series:
1570 1580 raise util.Abort(_('patch %s is already in the series file')
1571 1581 % patchname)
1572 1582 def checkfile(patchname):
1573 1583 if not force and os.path.exists(self.join(patchname)):
1574 1584 raise util.Abort(_('patch "%s" already exists')
1575 1585 % patchname)
1576 1586
1577 1587 if rev:
1578 1588 if files:
1579 1589 raise util.Abort(_('option "-r" not valid when importing '
1580 1590 'files'))
1581 1591 rev = cmdutil.revrange(repo, rev)
1582 1592 rev.sort(reverse=True)
1583 1593 if (len(files) > 1 or len(rev) > 1) and patchname:
1584 1594 raise util.Abort(_('option "-n" not valid when importing multiple '
1585 1595 'patches'))
1586 1596 i = 0
1587 1597 added = []
1588 1598 if rev:
1589 1599 # If mq patches are applied, we can only import revisions
1590 1600 # that form a linear path to qbase.
1591 1601 # Otherwise, they should form a linear path to a head.
1592 1602 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1593 1603 if len(heads) > 1:
1594 1604 raise util.Abort(_('revision %d is the root of more than one '
1595 1605 'branch') % rev[-1])
1596 1606 if self.applied:
1597 1607 base = hex(repo.changelog.node(rev[0]))
1598 1608 if base in [n.rev for n in self.applied]:
1599 1609 raise util.Abort(_('revision %d is already managed')
1600 1610 % rev[0])
1601 1611 if heads != [bin(self.applied[-1].rev)]:
1602 1612 raise util.Abort(_('revision %d is not the parent of '
1603 1613 'the queue') % rev[0])
1604 1614 base = repo.changelog.rev(bin(self.applied[0].rev))
1605 1615 lastparent = repo.changelog.parentrevs(base)[0]
1606 1616 else:
1607 1617 if heads != [repo.changelog.node(rev[0])]:
1608 1618 raise util.Abort(_('revision %d has unmanaged children')
1609 1619 % rev[0])
1610 1620 lastparent = None
1611 1621
1612 1622 diffopts = self.diffopts({'git': git})
1613 1623 for r in rev:
1614 1624 p1, p2 = repo.changelog.parentrevs(r)
1615 1625 n = repo.changelog.node(r)
1616 1626 if p2 != nullrev:
1617 1627 raise util.Abort(_('cannot import merge revision %d') % r)
1618 1628 if lastparent and lastparent != r:
1619 1629 raise util.Abort(_('revision %d is not the parent of %d')
1620 1630 % (r, lastparent))
1621 1631 lastparent = p1
1622 1632
1623 1633 if not patchname:
1624 1634 patchname = normname('%d.diff' % r)
1625 1635 self.check_reserved_name(patchname)
1626 1636 checkseries(patchname)
1627 1637 checkfile(patchname)
1628 1638 self.full_series.insert(0, patchname)
1629 1639
1630 1640 patchf = self.opener(patchname, "w")
1631 1641 patch.export(repo, [n], fp=patchf, opts=diffopts)
1632 1642 patchf.close()
1633 1643
1634 1644 se = statusentry(hex(n), patchname)
1635 1645 self.applied.insert(0, se)
1636 1646
1637 1647 added.append(patchname)
1638 1648 patchname = None
1639 1649 self.parse_series()
1640 1650 self.applied_dirty = 1
1641 1651
1642 1652 for filename in files:
1643 1653 if existing:
1644 1654 if filename == '-':
1645 1655 raise util.Abort(_('-e is incompatible with import from -'))
1646 1656 if not patchname:
1647 1657 patchname = normname(filename)
1648 1658 self.check_reserved_name(patchname)
1649 1659 if not os.path.isfile(self.join(patchname)):
1650 1660 raise util.Abort(_("patch %s does not exist") % patchname)
1651 1661 else:
1652 1662 try:
1653 1663 if filename == '-':
1654 1664 if not patchname:
1655 1665 raise util.Abort(_('need --name to import a patch from -'))
1656 1666 text = sys.stdin.read()
1657 1667 else:
1658 1668 text = url.open(self.ui, filename).read()
1659 1669 except (OSError, IOError):
1660 1670 raise util.Abort(_("unable to read %s") % filename)
1661 1671 if not patchname:
1662 1672 patchname = normname(os.path.basename(filename))
1663 1673 self.check_reserved_name(patchname)
1664 1674 checkfile(patchname)
1665 1675 patchf = self.opener(patchname, "w")
1666 1676 patchf.write(text)
1667 1677 if not force:
1668 1678 checkseries(patchname)
1669 1679 if patchname not in self.series:
1670 1680 index = self.full_series_end() + i
1671 1681 self.full_series[index:index] = [patchname]
1672 1682 self.parse_series()
1673 1683 self.ui.warn(_("adding %s to series file\n") % patchname)
1674 1684 i += 1
1675 1685 added.append(patchname)
1676 1686 patchname = None
1677 1687 self.series_dirty = 1
1678 1688 qrepo = self.qrepo()
1679 1689 if qrepo:
1680 1690 qrepo.add(added)
1681 1691
1682 1692 def delete(ui, repo, *patches, **opts):
1683 1693 """remove patches from queue
1684 1694
1685 1695 The patches must not be applied, and at least one patch is required. With
1686 1696 -k/--keep, the patch files are preserved in the patch directory.
1687 1697
1688 1698 To stop managing a patch and move it into permanent history,
1689 1699 use the qfinish command."""
1690 1700 q = repo.mq
1691 1701 q.delete(repo, patches, opts)
1692 1702 q.save_dirty()
1693 1703 return 0
1694 1704
1695 1705 def applied(ui, repo, patch=None, **opts):
1696 1706 """print the patches already applied"""
1697 1707
1698 1708 q = repo.mq
1699 1709 l = len(q.applied)
1700 1710
1701 1711 if patch:
1702 1712 if patch not in q.series:
1703 1713 raise util.Abort(_("patch %s is not in series file") % patch)
1704 1714 end = q.series.index(patch) + 1
1705 1715 else:
1706 1716 end = q.series_end(True)
1707 1717
1708 1718 if opts.get('last') and not end:
1709 1719 ui.write(_("no patches applied\n"))
1710 1720 return 1
1711 1721 elif opts.get('last') and end == 1:
1712 1722 ui.write(_("only one patch applied\n"))
1713 1723 return 1
1714 1724 elif opts.get('last'):
1715 1725 start = end - 2
1716 1726 end = 1
1717 1727 else:
1718 1728 start = 0
1719 1729
1720 1730 return q.qseries(repo, length=end, start=start, status='A',
1721 1731 summary=opts.get('summary'))
1722 1732
1723 1733 def unapplied(ui, repo, patch=None, **opts):
1724 1734 """print the patches not yet applied"""
1725 1735
1726 1736 q = repo.mq
1727 1737 if patch:
1728 1738 if patch not in q.series:
1729 1739 raise util.Abort(_("patch %s is not in series file") % patch)
1730 1740 start = q.series.index(patch) + 1
1731 1741 else:
1732 1742 start = q.series_end(True)
1733 1743
1734 1744 if start == len(q.series) and opts.get('first'):
1735 1745 ui.write(_("all patches applied\n"))
1736 1746 return 1
1737 1747
1738 1748 length = opts.get('first') and 1 or None
1739 1749 return q.qseries(repo, start=start, length=length, status='U',
1740 1750 summary=opts.get('summary'))
1741 1751
1742 1752 def qimport(ui, repo, *filename, **opts):
1743 1753 """import a patch
1744 1754
1745 1755 The patch is inserted into the series after the last applied
1746 1756 patch. If no patches have been applied, qimport prepends the patch
1747 1757 to the series.
1748 1758
1749 1759 The patch will have the same name as its source file unless you
1750 1760 give it a new one with -n/--name.
1751 1761
1752 1762 You can register an existing patch inside the patch directory with
1753 1763 the -e/--existing flag.
1754 1764
1755 1765 With -f/--force, an existing patch of the same name will be
1756 1766 overwritten.
1757 1767
1758 1768 An existing changeset may be placed under mq control with -r/--rev
1759 1769 (e.g. qimport --rev tip -n patch will place tip under mq control).
1760 1770 With -g/--git, patches imported with --rev will use the git diff
1761 1771 format. See the diffs help topic for information on why this is
1762 1772 important for preserving rename/copy information and permission
1763 1773 changes.
1764 1774
1765 1775 To import a patch from standard input, pass - as the patch file.
1766 1776 When importing from standard input, a patch name must be specified
1767 1777 using the --name flag.
1768 1778 """
1769 1779 q = repo.mq
1770 1780 q.qimport(repo, filename, patchname=opts['name'],
1771 1781 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1772 1782 git=opts['git'])
1773 1783 q.save_dirty()
1774 1784
1775 1785 if opts.get('push') and not opts.get('rev'):
1776 1786 return q.push(repo, None)
1777 1787 return 0
1778 1788
1779 1789 def init(ui, repo, **opts):
1780 1790 """init a new queue repository
1781 1791
1782 1792 The queue repository is unversioned by default. If
1783 1793 -c/--create-repo is specified, qinit will create a separate nested
1784 1794 repository for patches (qinit -c may also be run later to convert
1785 1795 an unversioned patch repository into a versioned one). You can use
1786 1796 qcommit to commit changes to this queue repository."""
1787 1797 q = repo.mq
1788 1798 r = q.init(repo, create=opts['create_repo'])
1789 1799 q.save_dirty()
1790 1800 if r:
1791 1801 if not os.path.exists(r.wjoin('.hgignore')):
1792 1802 fp = r.wopener('.hgignore', 'w')
1793 1803 fp.write('^\\.hg\n')
1794 1804 fp.write('^\\.mq\n')
1795 1805 fp.write('syntax: glob\n')
1796 1806 fp.write('status\n')
1797 1807 fp.write('guards\n')
1798 1808 fp.close()
1799 1809 if not os.path.exists(r.wjoin('series')):
1800 1810 r.wopener('series', 'w').close()
1801 1811 r.add(['.hgignore', 'series'])
1802 1812 commands.add(ui, r)
1803 1813 return 0
1804 1814
1805 1815 def clone(ui, source, dest=None, **opts):
1806 1816 '''clone main and patch repository at same time
1807 1817
1808 1818 If source is local, destination will have no patches applied. If
1809 1819 source is remote, this command can not check if patches are
1810 1820 applied in source, so cannot guarantee that patches are not
1811 1821 applied in destination. If you clone remote repository, be sure
1812 1822 before that it has no patches applied.
1813 1823
1814 1824 Source patch repository is looked for in <src>/.hg/patches by
1815 1825 default. Use -p <url> to change.
1816 1826
1817 1827 The patch directory must be a nested Mercurial repository, as
1818 1828 would be created by qinit -c.
1819 1829 '''
1820 1830 def patchdir(repo):
1821 1831 url = repo.url()
1822 1832 if url.endswith('/'):
1823 1833 url = url[:-1]
1824 1834 return url + '/.hg/patches'
1825 1835 if dest is None:
1826 1836 dest = hg.defaultdest(source)
1827 1837 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1828 1838 if opts['patches']:
1829 1839 patchespath = ui.expandpath(opts['patches'])
1830 1840 else:
1831 1841 patchespath = patchdir(sr)
1832 1842 try:
1833 1843 hg.repository(ui, patchespath)
1834 1844 except error.RepoError:
1835 1845 raise util.Abort(_('versioned patch repository not found'
1836 1846 ' (see qinit -c)'))
1837 1847 qbase, destrev = None, None
1838 1848 if sr.local():
1839 1849 if sr.mq.applied:
1840 1850 qbase = bin(sr.mq.applied[0].rev)
1841 1851 if not hg.islocal(dest):
1842 1852 heads = set(sr.heads())
1843 1853 destrev = list(heads.difference(sr.heads(qbase)))
1844 1854 destrev.append(sr.changelog.parents(qbase)[0])
1845 1855 elif sr.capable('lookup'):
1846 1856 try:
1847 1857 qbase = sr.lookup('qbase')
1848 1858 except error.RepoError:
1849 1859 pass
1850 1860 ui.note(_('cloning main repository\n'))
1851 1861 sr, dr = hg.clone(ui, sr.url(), dest,
1852 1862 pull=opts['pull'],
1853 1863 rev=destrev,
1854 1864 update=False,
1855 1865 stream=opts['uncompressed'])
1856 1866 ui.note(_('cloning patch repository\n'))
1857 1867 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1858 1868 pull=opts['pull'], update=not opts['noupdate'],
1859 1869 stream=opts['uncompressed'])
1860 1870 if dr.local():
1861 1871 if qbase:
1862 1872 ui.note(_('stripping applied patches from destination '
1863 1873 'repository\n'))
1864 1874 dr.mq.strip(dr, qbase, update=False, backup=None)
1865 1875 if not opts['noupdate']:
1866 1876 ui.note(_('updating destination repository\n'))
1867 1877 hg.update(dr, dr.changelog.tip())
1868 1878
1869 1879 def commit(ui, repo, *pats, **opts):
1870 1880 """commit changes in the queue repository"""
1871 1881 q = repo.mq
1872 1882 r = q.qrepo()
1873 1883 if not r: raise util.Abort('no queue repository')
1874 1884 commands.commit(r.ui, r, *pats, **opts)
1875 1885
1876 1886 def series(ui, repo, **opts):
1877 1887 """print the entire series file"""
1878 1888 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1879 1889 return 0
1880 1890
1881 1891 def top(ui, repo, **opts):
1882 1892 """print the name of the current patch"""
1883 1893 q = repo.mq
1884 1894 t = q.applied and q.series_end(True) or 0
1885 1895 if t:
1886 1896 return q.qseries(repo, start=t-1, length=1, status='A',
1887 1897 summary=opts.get('summary'))
1888 1898 else:
1889 1899 ui.write(_("no patches applied\n"))
1890 1900 return 1
1891 1901
1892 1902 def next(ui, repo, **opts):
1893 1903 """print the name of the next patch"""
1894 1904 q = repo.mq
1895 1905 end = q.series_end()
1896 1906 if end == len(q.series):
1897 1907 ui.write(_("all patches applied\n"))
1898 1908 return 1
1899 1909 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1900 1910
1901 1911 def prev(ui, repo, **opts):
1902 1912 """print the name of the previous patch"""
1903 1913 q = repo.mq
1904 1914 l = len(q.applied)
1905 1915 if l == 1:
1906 1916 ui.write(_("only one patch applied\n"))
1907 1917 return 1
1908 1918 if not l:
1909 1919 ui.write(_("no patches applied\n"))
1910 1920 return 1
1911 1921 return q.qseries(repo, start=l-2, length=1, status='A',
1912 1922 summary=opts.get('summary'))
1913 1923
1914 1924 def setupheaderopts(ui, opts):
1915 1925 if not opts.get('user') and opts.get('currentuser'):
1916 1926 opts['user'] = ui.username()
1917 1927 if not opts.get('date') and opts.get('currentdate'):
1918 1928 opts['date'] = "%d %d" % util.makedate()
1919 1929
1920 1930 def new(ui, repo, patch, *args, **opts):
1921 1931 """create a new patch
1922 1932
1923 1933 qnew creates a new patch on top of the currently-applied patch (if
1924 1934 any). It will refuse to run if there are any outstanding changes
1925 1935 unless -f/--force is specified, in which case the patch will be
1926 1936 initialized with them. You may also use -I/--include,
1927 1937 -X/--exclude, and/or a list of files after the patch name to add
1928 1938 only changes to matching files to the new patch, leaving the rest
1929 1939 as uncommitted modifications.
1930 1940
1931 1941 -u/--user and -d/--date can be used to set the (given) user and
1932 1942 date, respectively. -U/--currentuser and -D/--currentdate set user
1933 1943 to current user and date to current date.
1934 1944
1935 1945 -e/--edit, -m/--message or -l/--logfile set the patch header as
1936 1946 well as the commit message. If none is specified, the header is
1937 1947 empty and the commit message is '[mq]: PATCH'.
1938 1948
1939 1949 Use the -g/--git option to keep the patch in the git extended diff
1940 1950 format. Read the diffs help topic for more information on why this
1941 1951 is important for preserving permission changes and copy/rename
1942 1952 information.
1943 1953 """
1944 1954 msg = cmdutil.logmessage(opts)
1945 1955 def getmsg(): return ui.edit(msg, ui.username())
1946 1956 q = repo.mq
1947 1957 opts['msg'] = msg
1948 1958 if opts.get('edit'):
1949 1959 opts['msg'] = getmsg
1950 1960 else:
1951 1961 opts['msg'] = msg
1952 1962 setupheaderopts(ui, opts)
1953 1963 q.new(repo, patch, *args, **opts)
1954 1964 q.save_dirty()
1955 1965 return 0
1956 1966
1957 1967 def refresh(ui, repo, *pats, **opts):
1958 1968 """update the current patch
1959 1969
1960 1970 If any file patterns are provided, the refreshed patch will
1961 1971 contain only the modifications that match those patterns; the
1962 1972 remaining modifications will remain in the working directory.
1963 1973
1964 1974 If -s/--short is specified, files currently included in the patch
1965 1975 will be refreshed just like matched files and remain in the patch.
1966 1976
1967 1977 hg add/remove/copy/rename work as usual, though you might want to
1968 1978 use git-style patches (-g/--git or [diff] git=1) to track copies
1969 1979 and renames. See the diffs help topic for more information on the
1970 1980 git diff format.
1971 1981 """
1972 1982 q = repo.mq
1973 1983 message = cmdutil.logmessage(opts)
1974 1984 if opts['edit']:
1975 1985 if not q.applied:
1976 1986 ui.write(_("no patches applied\n"))
1977 1987 return 1
1978 1988 if message:
1979 1989 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1980 1990 patch = q.applied[-1].name
1981 1991 ph = patchheader(q.join(patch))
1982 1992 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1983 1993 setupheaderopts(ui, opts)
1984 1994 ret = q.refresh(repo, pats, msg=message, **opts)
1985 1995 q.save_dirty()
1986 1996 return ret
1987 1997
1988 1998 def diff(ui, repo, *pats, **opts):
1989 1999 """diff of the current patch and subsequent modifications
1990 2000
1991 2001 Shows a diff which includes the current patch as well as any
1992 2002 changes which have been made in the working directory since the
1993 2003 last refresh (thus showing what the current patch would become
1994 2004 after a qrefresh).
1995 2005
1996 2006 Use 'hg diff' if you only want to see the changes made since the
1997 2007 last qrefresh, or 'hg export qtip' if you want to see changes made
1998 2008 by the current patch without including changes made since the
1999 2009 qrefresh.
2000 2010 """
2001 2011 repo.mq.diff(repo, pats, opts)
2002 2012 return 0
2003 2013
2004 2014 def fold(ui, repo, *files, **opts):
2005 2015 """fold the named patches into the current patch
2006 2016
2007 2017 Patches must not yet be applied. Each patch will be successively
2008 2018 applied to the current patch in the order given. If all the
2009 2019 patches apply successfully, the current patch will be refreshed
2010 2020 with the new cumulative patch, and the folded patches will be
2011 2021 deleted. With -k/--keep, the folded patch files will not be
2012 2022 removed afterwards.
2013 2023
2014 2024 The header for each folded patch will be concatenated with the
2015 2025 current patch header, separated by a line of '* * *'."""
2016 2026
2017 2027 q = repo.mq
2018 2028
2019 2029 if not files:
2020 2030 raise util.Abort(_('qfold requires at least one patch name'))
2021 2031 if not q.check_toppatch(repo):
2022 2032 raise util.Abort(_('No patches applied'))
2023 2033 q.check_localchanges(repo)
2024 2034
2025 2035 message = cmdutil.logmessage(opts)
2026 2036 if opts['edit']:
2027 2037 if message:
2028 2038 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2029 2039
2030 2040 parent = q.lookup('qtip')
2031 2041 patches = []
2032 2042 messages = []
2033 2043 for f in files:
2034 2044 p = q.lookup(f)
2035 2045 if p in patches or p == parent:
2036 2046 ui.warn(_('Skipping already folded patch %s') % p)
2037 2047 if q.isapplied(p):
2038 2048 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2039 2049 patches.append(p)
2040 2050
2041 2051 for p in patches:
2042 2052 if not message:
2043 2053 ph = patchheader(q.join(p))
2044 2054 if ph.message:
2045 2055 messages.append(ph.message)
2046 2056 pf = q.join(p)
2047 2057 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2048 2058 if not patchsuccess:
2049 2059 raise util.Abort(_('Error folding patch %s') % p)
2050 2060 patch.updatedir(ui, repo, files)
2051 2061
2052 2062 if not message:
2053 2063 ph = patchheader(q.join(parent))
2054 2064 message, user = ph.message, ph.user
2055 2065 for msg in messages:
2056 2066 message.append('* * *')
2057 2067 message.extend(msg)
2058 2068 message = '\n'.join(message)
2059 2069
2060 2070 if opts['edit']:
2061 2071 message = ui.edit(message, user or ui.username())
2062 2072
2063 2073 q.refresh(repo, msg=message)
2064 2074 q.delete(repo, patches, opts)
2065 2075 q.save_dirty()
2066 2076
2067 2077 def goto(ui, repo, patch, **opts):
2068 2078 '''push or pop patches until named patch is at top of stack'''
2069 2079 q = repo.mq
2070 2080 patch = q.lookup(patch)
2071 2081 if q.isapplied(patch):
2072 2082 ret = q.pop(repo, patch, force=opts['force'])
2073 2083 else:
2074 2084 ret = q.push(repo, patch, force=opts['force'])
2075 2085 q.save_dirty()
2076 2086 return ret
2077 2087
2078 2088 def guard(ui, repo, *args, **opts):
2079 2089 '''set or print guards for a patch
2080 2090
2081 2091 Guards control whether a patch can be pushed. A patch with no
2082 2092 guards is always pushed. A patch with a positive guard ("+foo") is
2083 2093 pushed only if the qselect command has activated it. A patch with
2084 2094 a negative guard ("-foo") is never pushed if the qselect command
2085 2095 has activated it.
2086 2096
2087 2097 With no arguments, print the currently active guards.
2088 2098 With arguments, set guards for the named patch.
2089 2099 NOTE: Specifying negative guards now requires '--'.
2090 2100
2091 2101 To set guards on another patch::
2092 2102
2093 2103 hg qguard -- other.patch +2.6.17 -stable
2094 2104 '''
2095 2105 def status(idx):
2096 2106 guards = q.series_guards[idx] or ['unguarded']
2097 2107 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2098 2108 q = repo.mq
2099 2109 patch = None
2100 2110 args = list(args)
2101 2111 if opts['list']:
2102 2112 if args or opts['none']:
2103 2113 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2104 2114 for i in xrange(len(q.series)):
2105 2115 status(i)
2106 2116 return
2107 2117 if not args or args[0][0:1] in '-+':
2108 2118 if not q.applied:
2109 2119 raise util.Abort(_('no patches applied'))
2110 2120 patch = q.applied[-1].name
2111 2121 if patch is None and args[0][0:1] not in '-+':
2112 2122 patch = args.pop(0)
2113 2123 if patch is None:
2114 2124 raise util.Abort(_('no patch to work with'))
2115 2125 if args or opts['none']:
2116 2126 idx = q.find_series(patch)
2117 2127 if idx is None:
2118 2128 raise util.Abort(_('no patch named %s') % patch)
2119 2129 q.set_guards(idx, args)
2120 2130 q.save_dirty()
2121 2131 else:
2122 2132 status(q.series.index(q.lookup(patch)))
2123 2133
2124 2134 def header(ui, repo, patch=None):
2125 2135 """print the header of the topmost or specified patch"""
2126 2136 q = repo.mq
2127 2137
2128 2138 if patch:
2129 2139 patch = q.lookup(patch)
2130 2140 else:
2131 2141 if not q.applied:
2132 2142 ui.write('no patches applied\n')
2133 2143 return 1
2134 2144 patch = q.lookup('qtip')
2135 2145 ph = patchheader(repo.mq.join(patch))
2136 2146
2137 2147 ui.write('\n'.join(ph.message) + '\n')
2138 2148
2139 2149 def lastsavename(path):
2140 2150 (directory, base) = os.path.split(path)
2141 2151 names = os.listdir(directory)
2142 2152 namere = re.compile("%s.([0-9]+)" % base)
2143 2153 maxindex = None
2144 2154 maxname = None
2145 2155 for f in names:
2146 2156 m = namere.match(f)
2147 2157 if m:
2148 2158 index = int(m.group(1))
2149 2159 if maxindex is None or index > maxindex:
2150 2160 maxindex = index
2151 2161 maxname = f
2152 2162 if maxname:
2153 2163 return (os.path.join(directory, maxname), maxindex)
2154 2164 return (None, None)
2155 2165
2156 2166 def savename(path):
2157 2167 (last, index) = lastsavename(path)
2158 2168 if last is None:
2159 2169 index = 0
2160 2170 newpath = path + ".%d" % (index + 1)
2161 2171 return newpath
2162 2172
2163 2173 def push(ui, repo, patch=None, **opts):
2164 2174 """push the next patch onto the stack
2165 2175
2166 2176 When -f/--force is applied, all local changes in patched files
2167 2177 will be lost.
2168 2178 """
2169 2179 q = repo.mq
2170 2180 mergeq = None
2171 2181
2172 2182 if opts['merge']:
2173 2183 if opts['name']:
2174 2184 newpath = repo.join(opts['name'])
2175 2185 else:
2176 2186 newpath, i = lastsavename(q.path)
2177 2187 if not newpath:
2178 2188 ui.warn(_("no saved queues found, please use -n\n"))
2179 2189 return 1
2180 2190 mergeq = queue(ui, repo.join(""), newpath)
2181 2191 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2182 2192 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2183 2193 mergeq=mergeq, all=opts.get('all'))
2184 2194 return ret
2185 2195
2186 2196 def pop(ui, repo, patch=None, **opts):
2187 2197 """pop the current patch off the stack
2188 2198
2189 2199 By default, pops off the top of the patch stack. If given a patch
2190 2200 name, keeps popping off patches until the named patch is at the
2191 2201 top of the stack.
2192 2202 """
2193 2203 localupdate = True
2194 2204 if opts['name']:
2195 2205 q = queue(ui, repo.join(""), repo.join(opts['name']))
2196 2206 ui.warn(_('using patch queue: %s\n') % q.path)
2197 2207 localupdate = False
2198 2208 else:
2199 2209 q = repo.mq
2200 2210 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2201 2211 all=opts['all'])
2202 2212 q.save_dirty()
2203 2213 return ret
2204 2214
2205 2215 def rename(ui, repo, patch, name=None, **opts):
2206 2216 """rename a patch
2207 2217
2208 2218 With one argument, renames the current patch to PATCH1.
2209 2219 With two arguments, renames PATCH1 to PATCH2."""
2210 2220
2211 2221 q = repo.mq
2212 2222
2213 2223 if not name:
2214 2224 name = patch
2215 2225 patch = None
2216 2226
2217 2227 if patch:
2218 2228 patch = q.lookup(patch)
2219 2229 else:
2220 2230 if not q.applied:
2221 2231 ui.write(_('no patches applied\n'))
2222 2232 return
2223 2233 patch = q.lookup('qtip')
2224 2234 absdest = q.join(name)
2225 2235 if os.path.isdir(absdest):
2226 2236 name = normname(os.path.join(name, os.path.basename(patch)))
2227 2237 absdest = q.join(name)
2228 2238 if os.path.exists(absdest):
2229 2239 raise util.Abort(_('%s already exists') % absdest)
2230 2240
2231 2241 if name in q.series:
2232 2242 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2233 2243
2234 2244 if ui.verbose:
2235 2245 ui.write('renaming %s to %s\n' % (patch, name))
2236 2246 i = q.find_series(patch)
2237 2247 guards = q.guard_re.findall(q.full_series[i])
2238 2248 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2239 2249 q.parse_series()
2240 2250 q.series_dirty = 1
2241 2251
2242 2252 info = q.isapplied(patch)
2243 2253 if info:
2244 2254 q.applied[info[0]] = statusentry(info[1], name)
2245 2255 q.applied_dirty = 1
2246 2256
2247 2257 util.rename(q.join(patch), absdest)
2248 2258 r = q.qrepo()
2249 2259 if r:
2250 2260 wlock = r.wlock()
2251 2261 try:
2252 2262 if r.dirstate[patch] == 'a':
2253 2263 r.dirstate.forget(patch)
2254 2264 r.dirstate.add(name)
2255 2265 else:
2256 2266 if r.dirstate[name] == 'r':
2257 2267 r.undelete([name])
2258 2268 r.copy(patch, name)
2259 2269 r.remove([patch], False)
2260 2270 finally:
2261 2271 wlock.release()
2262 2272
2263 2273 q.save_dirty()
2264 2274
2265 2275 def restore(ui, repo, rev, **opts):
2266 2276 """restore the queue state saved by a revision"""
2267 2277 rev = repo.lookup(rev)
2268 2278 q = repo.mq
2269 2279 q.restore(repo, rev, delete=opts['delete'],
2270 2280 qupdate=opts['update'])
2271 2281 q.save_dirty()
2272 2282 return 0
2273 2283
2274 2284 def save(ui, repo, **opts):
2275 2285 """save current queue state"""
2276 2286 q = repo.mq
2277 2287 message = cmdutil.logmessage(opts)
2278 2288 ret = q.save(repo, msg=message)
2279 2289 if ret:
2280 2290 return ret
2281 2291 q.save_dirty()
2282 2292 if opts['copy']:
2283 2293 path = q.path
2284 2294 if opts['name']:
2285 2295 newpath = os.path.join(q.basepath, opts['name'])
2286 2296 if os.path.exists(newpath):
2287 2297 if not os.path.isdir(newpath):
2288 2298 raise util.Abort(_('destination %s exists and is not '
2289 2299 'a directory') % newpath)
2290 2300 if not opts['force']:
2291 2301 raise util.Abort(_('destination %s exists, '
2292 2302 'use -f to force') % newpath)
2293 2303 else:
2294 2304 newpath = savename(path)
2295 2305 ui.warn(_("copy %s to %s\n") % (path, newpath))
2296 2306 util.copyfiles(path, newpath)
2297 2307 if opts['empty']:
2298 2308 try:
2299 2309 os.unlink(q.join(q.status_path))
2300 2310 except:
2301 2311 pass
2302 2312 return 0
2303 2313
2304 2314 def strip(ui, repo, rev, **opts):
2305 2315 """strip a revision and all its descendants from the repository
2306 2316
2307 2317 If one of the working directory's parent revisions is stripped, the
2308 2318 working directory will be updated to the parent of the stripped
2309 2319 revision.
2310 2320 """
2311 2321 backup = 'all'
2312 2322 if opts['backup']:
2313 2323 backup = 'strip'
2314 2324 elif opts['nobackup']:
2315 2325 backup = 'none'
2316 2326
2317 2327 rev = repo.lookup(rev)
2318 2328 p = repo.dirstate.parents()
2319 2329 cl = repo.changelog
2320 2330 update = True
2321 2331 if p[0] == nullid:
2322 2332 update = False
2323 2333 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2324 2334 update = False
2325 2335 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2326 2336 update = False
2327 2337
2328 2338 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2329 2339 return 0
2330 2340
2331 2341 def select(ui, repo, *args, **opts):
2332 2342 '''set or print guarded patches to push
2333 2343
2334 2344 Use the qguard command to set or print guards on patch, then use
2335 2345 qselect to tell mq which guards to use. A patch will be pushed if
2336 2346 it has no guards or any positive guards match the currently
2337 2347 selected guard, but will not be pushed if any negative guards
2338 2348 match the current guard. For example::
2339 2349
2340 2350 qguard foo.patch -stable (negative guard)
2341 2351 qguard bar.patch +stable (positive guard)
2342 2352 qselect stable
2343 2353
2344 2354 This activates the "stable" guard. mq will skip foo.patch (because
2345 2355 it has a negative match) but push bar.patch (because it has a
2346 2356 positive match).
2347 2357
2348 2358 With no arguments, prints the currently active guards.
2349 2359 With one argument, sets the active guard.
2350 2360
2351 2361 Use -n/--none to deactivate guards (no other arguments needed).
2352 2362 When no guards are active, patches with positive guards are
2353 2363 skipped and patches with negative guards are pushed.
2354 2364
2355 2365 qselect can change the guards on applied patches. It does not pop
2356 2366 guarded patches by default. Use --pop to pop back to the last
2357 2367 applied patch that is not guarded. Use --reapply (which implies
2358 2368 --pop) to push back to the current patch afterwards, but skip
2359 2369 guarded patches.
2360 2370
2361 2371 Use -s/--series to print a list of all guards in the series file
2362 2372 (no other arguments needed). Use -v for more information.'''
2363 2373
2364 2374 q = repo.mq
2365 2375 guards = q.active()
2366 2376 if args or opts['none']:
2367 2377 old_unapplied = q.unapplied(repo)
2368 2378 old_guarded = [i for i in xrange(len(q.applied)) if
2369 2379 not q.pushable(i)[0]]
2370 2380 q.set_active(args)
2371 2381 q.save_dirty()
2372 2382 if not args:
2373 2383 ui.status(_('guards deactivated\n'))
2374 2384 if not opts['pop'] and not opts['reapply']:
2375 2385 unapplied = q.unapplied(repo)
2376 2386 guarded = [i for i in xrange(len(q.applied))
2377 2387 if not q.pushable(i)[0]]
2378 2388 if len(unapplied) != len(old_unapplied):
2379 2389 ui.status(_('number of unguarded, unapplied patches has '
2380 2390 'changed from %d to %d\n') %
2381 2391 (len(old_unapplied), len(unapplied)))
2382 2392 if len(guarded) != len(old_guarded):
2383 2393 ui.status(_('number of guarded, applied patches has changed '
2384 2394 'from %d to %d\n') %
2385 2395 (len(old_guarded), len(guarded)))
2386 2396 elif opts['series']:
2387 2397 guards = {}
2388 2398 noguards = 0
2389 2399 for gs in q.series_guards:
2390 2400 if not gs:
2391 2401 noguards += 1
2392 2402 for g in gs:
2393 2403 guards.setdefault(g, 0)
2394 2404 guards[g] += 1
2395 2405 if ui.verbose:
2396 2406 guards['NONE'] = noguards
2397 2407 guards = guards.items()
2398 2408 guards.sort(key=lambda x: x[0][1:])
2399 2409 if guards:
2400 2410 ui.note(_('guards in series file:\n'))
2401 2411 for guard, count in guards:
2402 2412 ui.note('%2d ' % count)
2403 2413 ui.write(guard, '\n')
2404 2414 else:
2405 2415 ui.note(_('no guards in series file\n'))
2406 2416 else:
2407 2417 if guards:
2408 2418 ui.note(_('active guards:\n'))
2409 2419 for g in guards:
2410 2420 ui.write(g, '\n')
2411 2421 else:
2412 2422 ui.write(_('no active guards\n'))
2413 2423 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2414 2424 popped = False
2415 2425 if opts['pop'] or opts['reapply']:
2416 2426 for i in xrange(len(q.applied)):
2417 2427 pushable, reason = q.pushable(i)
2418 2428 if not pushable:
2419 2429 ui.status(_('popping guarded patches\n'))
2420 2430 popped = True
2421 2431 if i == 0:
2422 2432 q.pop(repo, all=True)
2423 2433 else:
2424 2434 q.pop(repo, i-1)
2425 2435 break
2426 2436 if popped:
2427 2437 try:
2428 2438 if reapply:
2429 2439 ui.status(_('reapplying unguarded patches\n'))
2430 2440 q.push(repo, reapply)
2431 2441 finally:
2432 2442 q.save_dirty()
2433 2443
2434 2444 def finish(ui, repo, *revrange, **opts):
2435 2445 """move applied patches into repository history
2436 2446
2437 2447 Finishes the specified revisions (corresponding to applied
2438 2448 patches) by moving them out of mq control into regular repository
2439 2449 history.
2440 2450
2441 2451 Accepts a revision range or the -a/--applied option. If --applied
2442 2452 is specified, all applied mq revisions are removed from mq
2443 2453 control. Otherwise, the given revisions must be at the base of the
2444 2454 stack of applied patches.
2445 2455
2446 2456 This can be especially useful if your changes have been applied to
2447 2457 an upstream repository, or if you are about to push your changes
2448 2458 to upstream.
2449 2459 """
2450 2460 if not opts['applied'] and not revrange:
2451 2461 raise util.Abort(_('no revisions specified'))
2452 2462 elif opts['applied']:
2453 2463 revrange = ('qbase:qtip',) + revrange
2454 2464
2455 2465 q = repo.mq
2456 2466 if not q.applied:
2457 2467 ui.status(_('no patches applied\n'))
2458 2468 return 0
2459 2469
2460 2470 revs = cmdutil.revrange(repo, revrange)
2461 2471 q.finish(repo, revs)
2462 2472 q.save_dirty()
2463 2473 return 0
2464 2474
2465 2475 def reposetup(ui, repo):
2466 2476 class mqrepo(repo.__class__):
2467 2477 @util.propertycache
2468 2478 def mq(self):
2469 2479 return queue(self.ui, self.join(""))
2470 2480
2471 2481 def abort_if_wdir_patched(self, errmsg, force=False):
2472 2482 if self.mq.applied and not force:
2473 2483 parent = hex(self.dirstate.parents()[0])
2474 2484 if parent in [s.rev for s in self.mq.applied]:
2475 2485 raise util.Abort(errmsg)
2476 2486
2477 2487 def commit(self, text="", user=None, date=None, match=None,
2478 2488 force=False, editor=False, extra={}):
2479 2489 self.abort_if_wdir_patched(
2480 2490 _('cannot commit over an applied mq patch'),
2481 2491 force)
2482 2492
2483 2493 return super(mqrepo, self).commit(text, user, date, match, force,
2484 2494 editor, extra)
2485 2495
2486 2496 def push(self, remote, force=False, revs=None):
2487 2497 if self.mq.applied and not force and not revs:
2488 2498 raise util.Abort(_('source has mq patches applied'))
2489 2499 return super(mqrepo, self).push(remote, force, revs)
2490 2500
2491 2501 def _findtags(self):
2492 2502 '''augment tags from base class with patch tags'''
2493 2503 result = super(mqrepo, self)._findtags()
2494 2504
2495 2505 q = self.mq
2496 2506 if not q.applied:
2497 2507 return result
2498 2508
2499 2509 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2500 2510
2501 2511 if mqtags[-1][0] not in self.changelog.nodemap:
2502 2512 self.ui.warn(_('mq status file refers to unknown node %s\n')
2503 2513 % short(mqtags[-1][0]))
2504 2514 return result
2505 2515
2506 2516 mqtags.append((mqtags[-1][0], 'qtip'))
2507 2517 mqtags.append((mqtags[0][0], 'qbase'))
2508 2518 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2509 2519 tags = result[0]
2510 2520 for patch in mqtags:
2511 2521 if patch[1] in tags:
2512 2522 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2513 2523 % patch[1])
2514 2524 else:
2515 2525 tags[patch[1]] = patch[0]
2516 2526
2517 2527 return result
2518 2528
2519 2529 def _branchtags(self, partial, lrev):
2520 2530 q = self.mq
2521 2531 if not q.applied:
2522 2532 return super(mqrepo, self)._branchtags(partial, lrev)
2523 2533
2524 2534 cl = self.changelog
2525 2535 qbasenode = bin(q.applied[0].rev)
2526 2536 if qbasenode not in cl.nodemap:
2527 2537 self.ui.warn(_('mq status file refers to unknown node %s\n')
2528 2538 % short(qbasenode))
2529 2539 return super(mqrepo, self)._branchtags(partial, lrev)
2530 2540
2531 2541 qbase = cl.rev(qbasenode)
2532 2542 start = lrev + 1
2533 2543 if start < qbase:
2534 2544 # update the cache (excluding the patches) and save it
2535 2545 self._updatebranchcache(partial, lrev+1, qbase)
2536 2546 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2537 2547 start = qbase
2538 2548 # if start = qbase, the cache is as updated as it should be.
2539 2549 # if start > qbase, the cache includes (part of) the patches.
2540 2550 # we might as well use it, but we won't save it.
2541 2551
2542 2552 # update the cache up to the tip
2543 2553 self._updatebranchcache(partial, start, len(cl))
2544 2554
2545 2555 return partial
2546 2556
2547 2557 if repo.local():
2548 2558 repo.__class__ = mqrepo
2549 2559
2550 2560 def mqimport(orig, ui, repo, *args, **kwargs):
2551 2561 if hasattr(repo, 'abort_if_wdir_patched') and not kwargs.get('no_commit', False):
2552 2562 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2553 2563 kwargs.get('force'))
2554 2564 return orig(ui, repo, *args, **kwargs)
2555 2565
2556 2566 def uisetup(ui):
2557 2567 extensions.wrapcommand(commands.table, 'import', mqimport)
2558 2568
2559 2569 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2560 2570
2561 2571 cmdtable = {
2562 2572 "qapplied":
2563 2573 (applied,
2564 2574 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2565 2575 _('hg qapplied [-1] [-s] [PATCH]')),
2566 2576 "qclone":
2567 2577 (clone,
2568 2578 [('', 'pull', None, _('use pull protocol to copy metadata')),
2569 2579 ('U', 'noupdate', None, _('do not update the new working directories')),
2570 2580 ('', 'uncompressed', None,
2571 2581 _('use uncompressed transfer (fast over LAN)')),
2572 2582 ('p', 'patches', '', _('location of source patch repository')),
2573 2583 ] + commands.remoteopts,
2574 2584 _('hg qclone [OPTION]... SOURCE [DEST]')),
2575 2585 "qcommit|qci":
2576 2586 (commit,
2577 2587 commands.table["^commit|ci"][1],
2578 2588 _('hg qcommit [OPTION]... [FILE]...')),
2579 2589 "^qdiff":
2580 2590 (diff,
2581 2591 commands.diffopts + commands.diffopts2 + commands.walkopts,
2582 2592 _('hg qdiff [OPTION]... [FILE]...')),
2583 2593 "qdelete|qremove|qrm":
2584 2594 (delete,
2585 2595 [('k', 'keep', None, _('keep patch file')),
2586 2596 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2587 2597 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2588 2598 'qfold':
2589 2599 (fold,
2590 2600 [('e', 'edit', None, _('edit patch header')),
2591 2601 ('k', 'keep', None, _('keep folded patch files')),
2592 2602 ] + commands.commitopts,
2593 2603 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2594 2604 'qgoto':
2595 2605 (goto,
2596 2606 [('f', 'force', None, _('overwrite any local changes'))],
2597 2607 _('hg qgoto [OPTION]... PATCH')),
2598 2608 'qguard':
2599 2609 (guard,
2600 2610 [('l', 'list', None, _('list all patches and guards')),
2601 2611 ('n', 'none', None, _('drop all guards'))],
2602 2612 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2603 2613 'qheader': (header, [], _('hg qheader [PATCH]')),
2604 2614 "^qimport":
2605 2615 (qimport,
2606 2616 [('e', 'existing', None, _('import file in patch directory')),
2607 2617 ('n', 'name', '', _('name of patch file')),
2608 2618 ('f', 'force', None, _('overwrite existing files')),
2609 2619 ('r', 'rev', [], _('place existing revisions under mq control')),
2610 2620 ('g', 'git', None, _('use git extended diff format')),
2611 2621 ('P', 'push', None, _('qpush after importing'))],
2612 2622 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2613 2623 "^qinit":
2614 2624 (init,
2615 2625 [('c', 'create-repo', None, _('create queue repository'))],
2616 2626 _('hg qinit [-c]')),
2617 2627 "qnew":
2618 2628 (new,
2619 2629 [('e', 'edit', None, _('edit commit message')),
2620 2630 ('f', 'force', None, _('import uncommitted changes into patch')),
2621 2631 ('g', 'git', None, _('use git extended diff format')),
2622 2632 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2623 2633 ('u', 'user', '', _('add "From: <given user>" to patch')),
2624 2634 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2625 2635 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2626 2636 ] + commands.walkopts + commands.commitopts,
2627 2637 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2628 2638 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2629 2639 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2630 2640 "^qpop":
2631 2641 (pop,
2632 2642 [('a', 'all', None, _('pop all patches')),
2633 2643 ('n', 'name', '', _('queue name to pop')),
2634 2644 ('f', 'force', None, _('forget any local changes to patched files'))],
2635 2645 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2636 2646 "^qpush":
2637 2647 (push,
2638 2648 [('f', 'force', None, _('apply if the patch has rejects')),
2639 2649 ('l', 'list', None, _('list patch name in commit text')),
2640 2650 ('a', 'all', None, _('apply all patches')),
2641 2651 ('m', 'merge', None, _('merge from another queue')),
2642 2652 ('n', 'name', '', _('merge queue name'))],
2643 2653 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2644 2654 "^qrefresh":
2645 2655 (refresh,
2646 2656 [('e', 'edit', None, _('edit commit message')),
2647 2657 ('g', 'git', None, _('use git extended diff format')),
2648 2658 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2649 2659 ('U', 'currentuser', None, _('add/update author field in patch with current user')),
2650 2660 ('u', 'user', '', _('add/update author field in patch with given user')),
2651 2661 ('D', 'currentdate', None, _('add/update date field in patch with current date')),
2652 2662 ('d', 'date', '', _('add/update date field in patch with given date'))
2653 2663 ] + commands.walkopts + commands.commitopts,
2654 2664 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2655 2665 'qrename|qmv':
2656 2666 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2657 2667 "qrestore":
2658 2668 (restore,
2659 2669 [('d', 'delete', None, _('delete save entry')),
2660 2670 ('u', 'update', None, _('update queue working directory'))],
2661 2671 _('hg qrestore [-d] [-u] REV')),
2662 2672 "qsave":
2663 2673 (save,
2664 2674 [('c', 'copy', None, _('copy patch directory')),
2665 2675 ('n', 'name', '', _('copy directory name')),
2666 2676 ('e', 'empty', None, _('clear queue status file')),
2667 2677 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2668 2678 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2669 2679 "qselect":
2670 2680 (select,
2671 2681 [('n', 'none', None, _('disable all guards')),
2672 2682 ('s', 'series', None, _('list all guards in series file')),
2673 2683 ('', 'pop', None, _('pop to before first guarded applied patch')),
2674 2684 ('', 'reapply', None, _('pop, then reapply patches'))],
2675 2685 _('hg qselect [OPTION]... [GUARD]...')),
2676 2686 "qseries":
2677 2687 (series,
2678 2688 [('m', 'missing', None, _('print patches not in series')),
2679 2689 ] + seriesopts,
2680 2690 _('hg qseries [-ms]')),
2681 2691 "^strip":
2682 2692 (strip,
2683 2693 [('f', 'force', None, _('force removal with local changes')),
2684 2694 ('b', 'backup', None, _('bundle unrelated changesets')),
2685 2695 ('n', 'nobackup', None, _('no backups'))],
2686 2696 _('hg strip [-f] [-b] [-n] REV')),
2687 2697 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2688 2698 "qunapplied":
2689 2699 (unapplied,
2690 2700 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2691 2701 _('hg qunapplied [-1] [-s] [PATCH]')),
2692 2702 "qfinish":
2693 2703 (finish,
2694 2704 [('a', 'applied', None, _('finish all applied changesets'))],
2695 2705 _('hg qfinish [-a] [REV]...')),
2696 2706 }
@@ -1,273 +1,278 b''
1 1 # mdiff.py - diff and patch routines for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import bdiff, mpatch, util
10 10 import re, struct
11 11
12 12 def splitnewlines(text):
13 13 '''like str.splitlines, but only split on newlines.'''
14 14 lines = [l + '\n' for l in text.split('\n')]
15 15 if lines:
16 16 if lines[-1] == '\n':
17 17 lines.pop()
18 18 else:
19 19 lines[-1] = lines[-1][:-1]
20 20 return lines
21 21
22 22 class diffopts(object):
23 23 '''context is the number of context lines
24 24 text treats all files as text
25 25 showfunc enables diff -p output
26 26 git enables the git extended patch format
27 27 nodates removes dates from diff headers
28 28 ignorews ignores all whitespace changes in the diff
29 29 ignorewsamount ignores changes in the amount of whitespace
30 30 ignoreblanklines ignores changes whose lines are all blank'''
31 31
32 32 defaults = {
33 33 'context': 3,
34 34 'text': False,
35 35 'showfunc': False,
36 36 'git': False,
37 37 'nodates': False,
38 38 'ignorews': False,
39 39 'ignorewsamount': False,
40 40 'ignoreblanklines': False,
41 41 }
42 42
43 43 __slots__ = defaults.keys()
44 44
45 45 def __init__(self, **opts):
46 46 for k in self.__slots__:
47 47 v = opts.get(k)
48 48 if v is None:
49 49 v = self.defaults[k]
50 50 setattr(self, k, v)
51 51
52 52 try:
53 53 self.context = int(self.context)
54 54 except ValueError:
55 55 raise util.Abort(_('diff context lines count must be '
56 56 'an integer, not %r') % self.context)
57 57
58 def copy(self, **kwargs):
59 opts = dict((k, getattr(self, k)) for k in self.defaults)
60 opts.update(kwargs)
61 return diffopts(**opts)
62
58 63 defaultopts = diffopts()
59 64
60 65 def wsclean(opts, text, blank=True):
61 66 if opts.ignorews:
62 67 text = re.sub('[ \t]+', '', text)
63 68 elif opts.ignorewsamount:
64 69 text = re.sub('[ \t]+', ' ', text)
65 70 text = re.sub('[ \t]+\n', '\n', text)
66 71 if blank and opts.ignoreblanklines:
67 72 text = re.sub('\n+', '', text)
68 73 return text
69 74
70 75 def diffline(revs, a, b, opts):
71 76 parts = ['diff']
72 77 if opts.git:
73 78 parts.append('--git')
74 79 if revs and not opts.git:
75 80 parts.append(' '.join(["-r %s" % rev for rev in revs]))
76 81 if opts.git:
77 82 parts.append('a/%s' % a)
78 83 parts.append('b/%s' % b)
79 84 else:
80 85 parts.append(a)
81 86 return ' '.join(parts) + '\n'
82 87
83 88 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
84 89 def datetag(date, addtab=True):
85 90 if not opts.git and not opts.nodates:
86 91 return '\t%s\n' % date
87 92 if addtab and ' ' in fn1:
88 93 return '\t\n'
89 94 return '\n'
90 95
91 96 if not a and not b: return ""
92 97 epoch = util.datestr((0, 0))
93 98
94 99 if not opts.text and (util.binary(a) or util.binary(b)):
95 100 if a and b and len(a) == len(b) and a == b:
96 101 return ""
97 102 l = ['Binary file %s has changed\n' % fn1]
98 103 elif not a:
99 104 b = splitnewlines(b)
100 105 if a is None:
101 106 l1 = '--- /dev/null%s' % datetag(epoch, False)
102 107 else:
103 108 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
104 109 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
105 110 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
106 111 l = [l1, l2, l3] + ["+" + e for e in b]
107 112 elif not b:
108 113 a = splitnewlines(a)
109 114 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
110 115 if b is None:
111 116 l2 = '+++ /dev/null%s' % datetag(epoch, False)
112 117 else:
113 118 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
114 119 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
115 120 l = [l1, l2, l3] + ["-" + e for e in a]
116 121 else:
117 122 al = splitnewlines(a)
118 123 bl = splitnewlines(b)
119 124 l = list(bunidiff(a, b, al, bl, "a/" + fn1, "b/" + fn2, opts=opts))
120 125 if not l: return ""
121 126 # difflib uses a space, rather than a tab
122 127 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
123 128 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
124 129
125 130 for ln in xrange(len(l)):
126 131 if l[ln][-1] != '\n':
127 132 l[ln] += "\n\ No newline at end of file\n"
128 133
129 134 if r:
130 135 l.insert(0, diffline(r, fn1, fn2, opts))
131 136
132 137 return "".join(l)
133 138
134 139 # somewhat self contained replacement for difflib.unified_diff
135 140 # t1 and t2 are the text to be diffed
136 141 # l1 and l2 are the text broken up into lines
137 142 # header1 and header2 are the filenames for the diff output
138 143 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
139 144 def contextend(l, len):
140 145 ret = l + opts.context
141 146 if ret > len:
142 147 ret = len
143 148 return ret
144 149
145 150 def contextstart(l):
146 151 ret = l - opts.context
147 152 if ret < 0:
148 153 return 0
149 154 return ret
150 155
151 156 def yieldhunk(hunk, header):
152 157 if header:
153 158 for x in header:
154 159 yield x
155 160 (astart, a2, bstart, b2, delta) = hunk
156 161 aend = contextend(a2, len(l1))
157 162 alen = aend - astart
158 163 blen = b2 - bstart + aend - a2
159 164
160 165 func = ""
161 166 if opts.showfunc:
162 167 # walk backwards from the start of the context
163 168 # to find a line starting with an alphanumeric char.
164 169 for x in xrange(astart - 1, -1, -1):
165 170 t = l1[x].rstrip()
166 171 if funcre.match(t):
167 172 func = ' ' + t[:40]
168 173 break
169 174
170 175 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
171 176 bstart + 1, blen, func)
172 177 for x in delta:
173 178 yield x
174 179 for x in xrange(a2, aend):
175 180 yield ' ' + l1[x]
176 181
177 182 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
178 183
179 184 if opts.showfunc:
180 185 funcre = re.compile('\w')
181 186
182 187 # bdiff.blocks gives us the matching sequences in the files. The loop
183 188 # below finds the spaces between those matching sequences and translates
184 189 # them into diff output.
185 190 #
186 191 if opts.ignorews or opts.ignorewsamount:
187 192 t1 = wsclean(opts, t1, False)
188 193 t2 = wsclean(opts, t2, False)
189 194
190 195 diff = bdiff.blocks(t1, t2)
191 196 hunk = None
192 197 for i, s1 in enumerate(diff):
193 198 # The first match is special.
194 199 # we've either found a match starting at line 0 or a match later
195 200 # in the file. If it starts later, old and new below will both be
196 201 # empty and we'll continue to the next match.
197 202 if i > 0:
198 203 s = diff[i-1]
199 204 else:
200 205 s = [0, 0, 0, 0]
201 206 delta = []
202 207 a1 = s[1]
203 208 a2 = s1[0]
204 209 b1 = s[3]
205 210 b2 = s1[2]
206 211
207 212 old = l1[a1:a2]
208 213 new = l2[b1:b2]
209 214
210 215 # bdiff sometimes gives huge matches past eof, this check eats them,
211 216 # and deals with the special first match case described above
212 217 if not old and not new:
213 218 continue
214 219
215 220 if opts.ignoreblanklines:
216 221 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
217 222 continue
218 223
219 224 astart = contextstart(a1)
220 225 bstart = contextstart(b1)
221 226 prev = None
222 227 if hunk:
223 228 # join with the previous hunk if it falls inside the context
224 229 if astart < hunk[1] + opts.context + 1:
225 230 prev = hunk
226 231 astart = hunk[1]
227 232 bstart = hunk[3]
228 233 else:
229 234 for x in yieldhunk(hunk, header):
230 235 yield x
231 236 # we only want to yield the header if the files differ, and
232 237 # we only want to yield it once.
233 238 header = None
234 239 if prev:
235 240 # we've joined the previous hunk, record the new ending points.
236 241 hunk[1] = a2
237 242 hunk[3] = b2
238 243 delta = hunk[4]
239 244 else:
240 245 # create a new hunk
241 246 hunk = [ astart, a2, bstart, b2, delta ]
242 247
243 248 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
244 249 delta[len(delta):] = [ '-' + x for x in old ]
245 250 delta[len(delta):] = [ '+' + x for x in new ]
246 251
247 252 if hunk:
248 253 for x in yieldhunk(hunk, header):
249 254 yield x
250 255
251 256 def patchtext(bin):
252 257 pos = 0
253 258 t = []
254 259 while pos < len(bin):
255 260 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
256 261 pos += 12
257 262 t.append(bin[pos:pos + l])
258 263 pos += l
259 264 return "".join(t)
260 265
261 266 def patch(a, bin):
262 267 return mpatch.patches(a, [bin])
263 268
264 269 # similar to difflib.SequenceMatcher.get_matching_blocks
265 270 def get_matching_blocks(a, b):
266 271 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
267 272
268 273 def trivialdiffheader(length):
269 274 return struct.pack(">lll", 0, 0, length)
270 275
271 276 patches = mpatch.patches
272 277 patchedsize = mpatch.patchedsize
273 278 textdiff = bdiff.bdiff
@@ -1,73 +1,82 b''
1 1 #!/bin/sh
2 2
3 3 # Test issue 529 - mq aborts when merging patch deleting files
4 4
5 5 rewrite_path()
6 6 {
7 7 sed -e 's:\\:/:g' -e 's:[^ ]*/t/::g'
8 8 }
9 9
10 10 checkundo()
11 11 {
12 12 if [ -f .hg/store/undo ]; then
13 13 echo ".hg/store/undo still exists after $1"
14 14 fi
15 15 }
16 16
17 17 echo "[extensions]" >> $HGRCPATH
18 18 echo "hgext.mq=" >> $HGRCPATH
19 19
20 20 # Commit two dummy files in "init" changeset
21 21 hg init t
22 22 cd t
23 23 echo a > a
24 24 echo b > b
25 25 hg ci -Am init
26 26 hg tag -l init
27 27
28 28 # Create a patch removing a
29 29 hg qnew rm_a
30 30 hg rm a
31 31 hg qrefresh -m "rm a"
32 32
33 33 # Save the patch queue so we can merge it later
34 34 hg qsave -c -e 2>&1 | rewrite_path
35 35 checkundo qsave
36 36
37 37 # Update b and commit in an "update" changeset
38 38 hg up -C init
39 39 echo b >> b
40 40 hg st
41 41 hg ci -m update
42 42
43 43 # Here, qpush used to abort with :
44 44 # The system cannot find the file specified => a
45 45 hg manifest
46 46 hg qpush -a -m 2>&1 | rewrite_path
47 47 checkundo 'qpush -m'
48 48 hg manifest
49 49
50 50 # ensure status is correct after merge
51 51 hg qpop -a
52 52 cd ..
53 53
54 54 # Classic MQ merge sequence *with an explicit named queue*
55 55 echo
56 56 echo % init t2
57 57 hg init t2
58 58 cd t2
59 echo '[diff]' > .hg/hgrc
60 echo 'nodates = 1' >> .hg/hgrc
59 61 echo a > a
60 62 hg ci -Am init
61 echo b >> a
63 echo b > a
62 64 hg ci -m changea
63 65 hg up -C 0
66 hg cp a aa
64 67 echo c >> a
65 hg qnew -f -e patcha
68 hg qnew --git -f -e patcha
69 echo d >> a
70 hg qnew -d '0 0' -f -e patcha2
66 71 echo % create the reference queue
67 72 hg qsave -c -e -n refqueue 2> /dev/null
68 73 hg up -C 1
69 74 echo % merge
70 hg qpush -m -n refqueue 2>&1 | \
75 HGMERGE=internal:other hg qpush -a -m -n refqueue 2>&1 | \
71 76 sed 's/merging with queue at.*refqueue/merging with queue at refqueue/'
77 echo % check patcha is still a git patch
78 cat .hg/patches/patcha
79 echo % check patcha2 is still a regular patch
80 grep git .hg/patches/patcha2 && echo 'git patch found!'
72 81 cd ..
73 82
@@ -1,25 +1,52 b''
1 1 adding a
2 2 adding b
3 3 copy .hg/patches to .hg/patches.1
4 4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 5 M b
6 6 created new head
7 7 a
8 8 b
9 9 merging with queue at: .hg/patches.1
10 10 applying rm_a
11 11 now at: rm_a
12 12 b
13 13 popping rm_a
14 14 popping .hg.patches.merge.marker
15 15 patch queue now empty
16 16
17 17 % init t2
18 18 adding a
19 19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 20 % create the reference queue
21 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
21 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 22 % merge
23 23 merging with queue at refqueue
24 24 applying patcha
25 now at: patcha
25 patching file a
26 Hunk #1 FAILED at 0
27 1 out of 1 hunks FAILED -- saving rejects to file a.rej
28 patch failed, unable to continue (try -v)
29 patch failed, rejects left in working dir
30 patch didn't work out, merging patcha
31 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
32 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
33 (branch merge, don't forget to commit)
34 applying patcha2
35 now at: patcha2
36 % check patcha is still a git patch
37 diff --git a/a b/a
38 --- a/a
39 +++ b/a
40 @@ -1,1 +1,2 @@
41 -b
42 +a
43 +c
44 diff --git a/a b/aa
45 copy from a
46 copy to aa
47 --- a/a
48 +++ b/aa
49 @@ -1,1 +1,1 @@
50 -b
51 +a
52 % check patcha2 is still a regular patch
General Comments 0
You need to be logged in to leave comments. Login now