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