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