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