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