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