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