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