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