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