3714
|
1 |
# Patch transplanting extension for Mercurial
|
|
2 |
#
|
|
3 |
# Copyright 2006 Brendan Cully <brendan@kublai.com>
|
|
4 |
#
|
|
5 |
# This software may be used and distributed according to the terms
|
|
6 |
# of the GNU General Public License, incorporated herein by reference.
|
|
7 |
|
|
8 |
from mercurial.demandload import *
|
|
9 |
from mercurial.i18n import gettext as _
|
|
10 |
demandload(globals(), 'os tempfile')
|
|
11 |
demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch')
|
|
12 |
demandload(globals(), 'mercurial:revlog,util')
|
|
13 |
|
|
14 |
'''patch transplanting tool
|
|
15 |
|
|
16 |
This extension allows you to transplant patches from another branch.
|
|
17 |
|
|
18 |
Transplanted patches are recorded in .hg/transplant/transplants, as a map
|
|
19 |
from a changeset hash to its hash in the source repository.
|
|
20 |
'''
|
|
21 |
|
|
22 |
class transplantentry:
|
|
23 |
def __init__(self, lnode, rnode):
|
|
24 |
self.lnode = lnode
|
|
25 |
self.rnode = rnode
|
|
26 |
|
|
27 |
class transplants:
|
|
28 |
def __init__(self, path=None, transplantfile=None, opener=None):
|
|
29 |
self.path = path
|
|
30 |
self.transplantfile = transplantfile
|
|
31 |
self.opener = opener
|
|
32 |
|
|
33 |
if not opener:
|
|
34 |
self.opener = util.opener(self.path)
|
|
35 |
self.transplants = []
|
|
36 |
self.dirty = False
|
|
37 |
self.read()
|
|
38 |
|
|
39 |
def read(self):
|
|
40 |
abspath = os.path.join(self.path, self.transplantfile)
|
|
41 |
if self.transplantfile and os.path.exists(abspath):
|
|
42 |
for line in self.opener(self.transplantfile).read().splitlines():
|
|
43 |
lnode, rnode = map(revlog.bin, line.split(':'))
|
|
44 |
self.transplants.append(transplantentry(lnode, rnode))
|
|
45 |
|
|
46 |
def write(self):
|
|
47 |
if self.dirty and self.transplantfile:
|
|
48 |
if not os.path.isdir(self.path):
|
|
49 |
os.mkdir(self.path)
|
|
50 |
fp = self.opener(self.transplantfile, 'w')
|
|
51 |
for c in self.transplants:
|
|
52 |
l, r = map(revlog.hex, (c.lnode, c.rnode))
|
|
53 |
fp.write(l + ':' + r + '\n')
|
|
54 |
fp.close()
|
|
55 |
self.dirty = False
|
|
56 |
|
|
57 |
def get(self, rnode):
|
|
58 |
return [t for t in self.transplants if t.rnode == rnode]
|
|
59 |
|
|
60 |
def set(self, lnode, rnode):
|
|
61 |
self.transplants.append(transplantentry(lnode, rnode))
|
|
62 |
self.dirty = True
|
|
63 |
|
|
64 |
def remove(self, transplant):
|
|
65 |
del self.transplants[self.transplants.index(transplant)]
|
|
66 |
self.dirty = True
|
|
67 |
|
|
68 |
class transplanter:
|
|
69 |
def __init__(self, ui, repo):
|
|
70 |
self.ui = ui
|
|
71 |
self.path = repo.join('transplant')
|
|
72 |
self.opener = util.opener(self.path)
|
|
73 |
self.transplants = transplants(self.path, 'transplants', opener=self.opener)
|
|
74 |
|
|
75 |
def applied(self, repo, node, parent):
|
|
76 |
'''returns True if a node is already an ancestor of parent
|
|
77 |
or has already been transplanted'''
|
|
78 |
if hasnode(repo, node):
|
|
79 |
if node in repo.changelog.reachable(parent, stop=node):
|
|
80 |
return True
|
|
81 |
for t in self.transplants.get(node):
|
|
82 |
# it might have been stripped
|
|
83 |
if not hasnode(repo, t.lnode):
|
|
84 |
self.transplants.remove(t)
|
|
85 |
return False
|
|
86 |
if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
|
|
87 |
return True
|
|
88 |
return False
|
|
89 |
|
|
90 |
def apply(self, repo, source, revmap, merges, opts={}):
|
|
91 |
'''apply the revisions in revmap one by one in revision order'''
|
|
92 |
revs = revmap.keys()
|
|
93 |
revs.sort()
|
|
94 |
|
|
95 |
p1, p2 = repo.dirstate.parents()
|
|
96 |
pulls = []
|
|
97 |
diffopts = patch.diffopts(self.ui, opts)
|
|
98 |
diffopts.git = True
|
|
99 |
|
|
100 |
lock = repo.lock()
|
|
101 |
wlock = repo.wlock()
|
|
102 |
try:
|
|
103 |
for rev in revs:
|
|
104 |
node = revmap[rev]
|
|
105 |
revstr = '%s:%s' % (rev, revlog.short(node))
|
|
106 |
|
|
107 |
if self.applied(repo, node, p1):
|
|
108 |
self.ui.warn(_('skipping already applied revision %s\n') %
|
|
109 |
revstr)
|
|
110 |
continue
|
|
111 |
|
|
112 |
parents = source.changelog.parents(node)
|
|
113 |
if not opts.get('filter'):
|
|
114 |
# If the changeset parent is the same as the wdir's parent,
|
|
115 |
# just pull it.
|
|
116 |
if parents[0] == p1:
|
|
117 |
pulls.append(node)
|
|
118 |
p1 = node
|
|
119 |
continue
|
|
120 |
if pulls:
|
|
121 |
if source != repo:
|
|
122 |
repo.pull(source, heads=pulls, lock=lock)
|
|
123 |
merge.update(repo, pulls[-1], wlock=wlock)
|
|
124 |
p1, p2 = repo.dirstate.parents()
|
|
125 |
pulls = []
|
|
126 |
|
|
127 |
domerge = False
|
|
128 |
if node in merges:
|
|
129 |
# pulling all the merge revs at once would mean we couldn't
|
|
130 |
# transplant after the latest even if transplants before them
|
|
131 |
# fail.
|
|
132 |
domerge = True
|
|
133 |
if not hasnode(repo, node):
|
|
134 |
repo.pull(source, heads=[node], lock=lock)
|
|
135 |
|
|
136 |
if parents[1] != revlog.nullid:
|
|
137 |
self.ui.note(_('skipping merge changeset %s:%s\n')
|
|
138 |
% (rev, revlog.short(node)))
|
|
139 |
patchfile = None
|
|
140 |
else:
|
|
141 |
fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
|
|
142 |
fp = os.fdopen(fd, 'w')
|
|
143 |
patch.export(source, [node], fp=fp, opts=diffopts)
|
|
144 |
fp.close()
|
|
145 |
|
|
146 |
del revmap[rev]
|
|
147 |
if patchfile or domerge:
|
|
148 |
try:
|
|
149 |
n = self.applyone(repo, node, source.changelog.read(node),
|
|
150 |
patchfile, merge=domerge,
|
|
151 |
log=opts.get('log'),
|
|
152 |
filter=opts.get('filter'),
|
|
153 |
lock=lock, wlock=wlock)
|
|
154 |
if domerge:
|
|
155 |
self.ui.status(_('%s merged at %s\n') % (revstr,
|
|
156 |
revlog.short(n)))
|
|
157 |
else:
|
|
158 |
self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node),
|
|
159 |
revlog.short(n)))
|
|
160 |
finally:
|
|
161 |
if patchfile:
|
|
162 |
os.unlink(patchfile)
|
|
163 |
if pulls:
|
|
164 |
repo.pull(source, heads=pulls, lock=lock)
|
|
165 |
merge.update(repo, pulls[-1], wlock=wlock)
|
|
166 |
finally:
|
|
167 |
self.saveseries(revmap, merges)
|
|
168 |
self.transplants.write()
|
|
169 |
|
|
170 |
def filter(self, filter, changelog, patchfile):
|
|
171 |
'''arbitrarily rewrite changeset before applying it'''
|
|
172 |
|
|
173 |
self.ui.status('filtering %s' % patchfile)
|
|
174 |
util.system('%s %s' % (filter, util.shellquote(patchfile)),
|
|
175 |
environ={'HGUSER': changelog[1]},
|
|
176 |
onerr=util.Abort, errprefix=_('filter failed'))
|
|
177 |
|
|
178 |
def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
|
|
179 |
filter=None, lock=None, wlock=None):
|
|
180 |
'''apply the patch in patchfile to the repository as a transplant'''
|
|
181 |
(manifest, user, (time, timezone), files, message) = cl[:5]
|
|
182 |
date = "%d %d" % (time, timezone)
|
|
183 |
extra = {'transplant_source': node}
|
|
184 |
if filter:
|
|
185 |
self.filter(filter, cl, patchfile)
|
|
186 |
patchfile, message, user, date = patch.extract(self.ui, file(patchfile))
|
|
187 |
|
|
188 |
if log:
|
|
189 |
message += '\n(transplanted from %s)' % revlog.hex(node)
|
|
190 |
cl = list(cl)
|
|
191 |
cl[4] = message
|
|
192 |
|
|
193 |
self.ui.status(_('applying %s\n') % revlog.short(node))
|
|
194 |
self.ui.note('%s %s\n%s\n' % (user, date, message))
|
|
195 |
|
|
196 |
if not patchfile and not merge:
|
|
197 |
raise util.Abort(_('can only omit patchfile if merging'))
|
|
198 |
if patchfile:
|
|
199 |
try:
|
|
200 |
files = {}
|
|
201 |
fuzz = patch.patch(patchfile, self.ui, cwd=repo.root,
|
|
202 |
files=files)
|
|
203 |
if not files:
|
|
204 |
self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
|
|
205 |
return
|
|
206 |
files = patch.updatedir(self.ui, repo, files, wlock=wlock)
|
|
207 |
if filter:
|
|
208 |
os.unlink(patchfile)
|
|
209 |
except Exception, inst:
|
|
210 |
if filter:
|
|
211 |
os.unlink(patchfile)
|
|
212 |
p1 = repo.dirstate.parents()[0]
|
|
213 |
p2 = node
|
|
214 |
self.log(cl, p1, p2, merge=merge)
|
|
215 |
self.ui.write(str(inst) + '\n')
|
|
216 |
raise util.Abort(_('Fix up the merge and run hg transplant --continue'))
|
|
217 |
else:
|
|
218 |
files = None
|
|
219 |
if merge:
|
|
220 |
p1, p2 = repo.dirstate.parents()
|
|
221 |
repo.dirstate.setparents(p1, node)
|
|
222 |
|
|
223 |
n = repo.commit(files, message, user, date, lock=lock, wlock=wlock,
|
|
224 |
extra=extra)
|
|
225 |
if not merge:
|
|
226 |
self.transplants.set(n, node)
|
|
227 |
|
|
228 |
return n
|
|
229 |
|
|
230 |
def resume(self, repo, source, opts=None):
|
|
231 |
'''recover last transaction and apply remaining changesets'''
|
|
232 |
if os.path.exists(os.path.join(self.path, 'journal')):
|
|
233 |
n, node = self.recover(repo)
|
|
234 |
self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
|
|
235 |
revlog.short(n)))
|
|
236 |
seriespath = os.path.join(self.path, 'series')
|
|
237 |
if not os.path.exists(seriespath):
|
|
238 |
return
|
|
239 |
nodes, merges = self.readseries()
|
|
240 |
revmap = {}
|
|
241 |
for n in nodes:
|
|
242 |
revmap[source.changelog.rev(n)] = n
|
|
243 |
os.unlink(seriespath)
|
|
244 |
|
|
245 |
self.apply(repo, source, revmap, merges, opts)
|
|
246 |
|
|
247 |
def recover(self, repo):
|
|
248 |
'''commit working directory using journal metadata'''
|
|
249 |
node, user, date, message, parents = self.readlog()
|
|
250 |
merge = len(parents) == 2
|
|
251 |
|
|
252 |
if not user or not date or not message or not parents[0]:
|
|
253 |
raise util.Abort(_('transplant log file is corrupt'))
|
|
254 |
|
|
255 |
wlock = repo.wlock()
|
|
256 |
p1, p2 = repo.dirstate.parents()
|
|
257 |
if p1 != parents[0]:
|
|
258 |
raise util.Abort(_('working dir not at transplant parent %s') %
|
|
259 |
revlog.hex(parents[0]))
|
|
260 |
if merge:
|
|
261 |
repo.dirstate.setparents(p1, parents[1])
|
|
262 |
n = repo.commit(None, message, user, date, wlock=wlock)
|
|
263 |
if not n:
|
|
264 |
raise util.Abort(_('commit failed'))
|
|
265 |
if not merge:
|
|
266 |
self.transplants.set(n, node)
|
|
267 |
self.unlog()
|
|
268 |
|
|
269 |
return n, node
|
|
270 |
|
|
271 |
def readseries(self):
|
|
272 |
nodes = []
|
|
273 |
merges = []
|
|
274 |
cur = nodes
|
|
275 |
for line in self.opener('series').read().splitlines():
|
|
276 |
if line.startswith('# Merges'):
|
|
277 |
cur = merges
|
|
278 |
continue
|
|
279 |
cur.append(revlog.bin(line))
|
|
280 |
|
|
281 |
return (nodes, merges)
|
|
282 |
|
|
283 |
def saveseries(self, revmap, merges):
|
|
284 |
if not revmap:
|
|
285 |
return
|
|
286 |
|
|
287 |
if not os.path.isdir(self.path):
|
|
288 |
os.mkdir(self.path)
|
|
289 |
series = self.opener('series', 'w')
|
|
290 |
revs = revmap.keys()
|
|
291 |
revs.sort()
|
|
292 |
for rev in revs:
|
|
293 |
series.write(revlog.hex(revmap[rev]) + '\n')
|
|
294 |
if merges:
|
|
295 |
series.write('# Merges\n')
|
|
296 |
for m in merges:
|
|
297 |
series.write(revlog.hex(m) + '\n')
|
|
298 |
series.close()
|
|
299 |
|
|
300 |
def log(self, changelog, p1, p2, merge=False):
|
|
301 |
'''journal changelog metadata for later recover'''
|
|
302 |
|
|
303 |
if not os.path.isdir(self.path):
|
|
304 |
os.mkdir(self.path)
|
|
305 |
fp = self.opener('journal', 'w')
|
|
306 |
fp.write('# User %s\n' % changelog[1])
|
|
307 |
fp.write('# Date %d %d\n' % changelog[2])
|
|
308 |
fp.write('# Node ID %s\n' % revlog.hex(p2))
|
|
309 |
fp.write('# Parent ' + revlog.hex(p1) + '\n')
|
|
310 |
if merge:
|
|
311 |
fp.write('# Parent ' + revlog.hex(p2) + '\n')
|
|
312 |
fp.write(changelog[4].rstrip() + '\n')
|
|
313 |
fp.close()
|
|
314 |
|
|
315 |
def readlog(self):
|
|
316 |
parents = []
|
|
317 |
message = []
|
|
318 |
for line in self.opener('journal').read().splitlines():
|
|
319 |
if line.startswith('# User '):
|
|
320 |
user = line[7:]
|
|
321 |
elif line.startswith('# Date '):
|
|
322 |
date = line[7:]
|
|
323 |
elif line.startswith('# Node ID '):
|
|
324 |
node = revlog.bin(line[10:])
|
|
325 |
elif line.startswith('# Parent '):
|
|
326 |
parents.append(revlog.bin(line[9:]))
|
|
327 |
else:
|
|
328 |
message.append(line)
|
|
329 |
return (node, user, date, '\n'.join(message), parents)
|
|
330 |
|
|
331 |
def unlog(self):
|
|
332 |
'''remove changelog journal'''
|
|
333 |
absdst = os.path.join(self.path, 'journal')
|
|
334 |
if os.path.exists(absdst):
|
|
335 |
os.unlink(absdst)
|
|
336 |
|
|
337 |
def transplantfilter(self, repo, source, root):
|
|
338 |
def matchfn(node):
|
|
339 |
if self.applied(repo, node, root):
|
|
340 |
return False
|
|
341 |
if source.changelog.parents(node)[1] != revlog.nullid:
|
|
342 |
return False
|
|
343 |
extra = source.changelog.read(node)[5]
|
|
344 |
cnode = extra.get('transplant_source')
|
|
345 |
if cnode and self.applied(repo, cnode, root):
|
|
346 |
return False
|
|
347 |
return True
|
|
348 |
|
|
349 |
return matchfn
|
|
350 |
|
|
351 |
def hasnode(repo, node):
|
|
352 |
try:
|
|
353 |
return repo.changelog.rev(node) != None
|
|
354 |
except revlog.RevlogError:
|
|
355 |
return False
|
|
356 |
|
|
357 |
def browserevs(ui, repo, nodes, opts):
|
|
358 |
'''interactively transplant changesets'''
|
|
359 |
def browsehelp(ui):
|
|
360 |
ui.write('y: transplant this changeset\n'
|
|
361 |
'n: skip this changeset\n'
|
|
362 |
'm: merge at this changeset\n'
|
|
363 |
'p: show patch\n'
|
|
364 |
'c: commit selected changesets\n'
|
|
365 |
'q: cancel transplant\n'
|
|
366 |
'?: show this help\n')
|
|
367 |
|
|
368 |
displayer = commands.show_changeset(ui, repo, opts)
|
|
369 |
transplants = []
|
|
370 |
merges = []
|
|
371 |
for node in nodes:
|
|
372 |
displayer.show(changenode=node)
|
|
373 |
action = None
|
|
374 |
while not action:
|
|
375 |
action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
|
|
376 |
if action == '?':
|
|
377 |
browsehelp(ui)
|
|
378 |
action = None
|
|
379 |
elif action == 'p':
|
|
380 |
parent = repo.changelog.parents(node)[0]
|
|
381 |
patch.diff(repo, parent, node)
|
|
382 |
action = None
|
|
383 |
elif action not in ('y', 'n', 'm', 'c', 'q'):
|
|
384 |
ui.write('no such option\n')
|
|
385 |
action = None
|
|
386 |
if action == 'y':
|
|
387 |
transplants.append(node)
|
|
388 |
elif action == 'm':
|
|
389 |
merges.append(node)
|
|
390 |
elif action == 'c':
|
|
391 |
break
|
|
392 |
elif action == 'q':
|
|
393 |
transplants = ()
|
|
394 |
merges = ()
|
|
395 |
break
|
|
396 |
return (transplants, merges)
|
|
397 |
|
|
398 |
def transplant(ui, repo, *revs, **opts):
|
|
399 |
'''transplant changesets from another branch
|
|
400 |
|
|
401 |
Selected changesets will be applied on top of the current working
|
|
402 |
directory with the log of the original changeset. If --log is
|
|
403 |
specified, log messages will have a comment appended of the form:
|
|
404 |
|
|
405 |
(transplanted from CHANGESETHASH)
|
|
406 |
|
|
407 |
You can rewrite the changelog message with the --filter option.
|
|
408 |
Its argument will be invoked with the current changelog message
|
|
409 |
as $1 and the patch as $2.
|
|
410 |
|
|
411 |
If --source is specified, selects changesets from the named
|
|
412 |
repository. If --branch is specified, selects changesets from the
|
|
413 |
branch holding the named revision, up to that revision. If --all
|
|
414 |
is specified, all changesets on the branch will be transplanted,
|
|
415 |
otherwise you will be prompted to select the changesets you want.
|
|
416 |
|
|
417 |
hg transplant --branch REVISION --all will rebase the selected branch
|
|
418 |
(up to the named revision) onto your current working directory.
|
|
419 |
|
|
420 |
You can optionally mark selected transplanted changesets as
|
|
421 |
merge changesets. You will not be prompted to transplant any
|
|
422 |
ancestors of a merged transplant, and you can merge descendants
|
|
423 |
of them normally instead of transplanting them.
|
|
424 |
|
|
425 |
If no merges or revisions are provided, hg transplant will start
|
|
426 |
an interactive changeset browser.
|
|
427 |
|
|
428 |
If a changeset application fails, you can fix the merge by hand and
|
|
429 |
then resume where you left off by calling hg transplant --continue.
|
|
430 |
'''
|
|
431 |
def getoneitem(opts, item, errmsg):
|
|
432 |
val = opts.get(item)
|
|
433 |
if val:
|
|
434 |
if len(val) > 1:
|
|
435 |
raise util.Abort(errmsg)
|
|
436 |
else:
|
|
437 |
return val[0]
|
|
438 |
|
|
439 |
def getremotechanges(repo, url):
|
|
440 |
sourcerepo = ui.expandpath(url)
|
|
441 |
source = hg.repository(ui, sourcerepo)
|
|
442 |
incoming = repo.findincoming(source, force=True)
|
|
443 |
if not incoming:
|
|
444 |
return (source, None, None)
|
|
445 |
|
|
446 |
bundle = None
|
|
447 |
if not source.local():
|
|
448 |
cg = source.changegroup(incoming, 'incoming')
|
|
449 |
bundle = commands.write_bundle(cg, compress=False)
|
|
450 |
source = bundlerepo.bundlerepository(ui, repo.root, bundle)
|
|
451 |
|
|
452 |
return (source, incoming, bundle)
|
|
453 |
|
|
454 |
def incwalk(repo, incoming, branches, match=util.always):
|
|
455 |
if not branches:
|
|
456 |
branches=None
|
|
457 |
for node in repo.changelog.nodesbetween(incoming, branches)[0]:
|
|
458 |
if match(node):
|
|
459 |
yield node
|
|
460 |
|
|
461 |
def transplantwalk(repo, root, branches, match=util.always):
|
|
462 |
if not branches:
|
|
463 |
branches = repo.heads()
|
|
464 |
ancestors = []
|
|
465 |
for branch in branches:
|
|
466 |
ancestors.append(repo.changelog.ancestor(root, branch))
|
|
467 |
for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
|
|
468 |
if match(node):
|
|
469 |
yield node
|
|
470 |
|
|
471 |
def checkopts(opts, revs):
|
|
472 |
if opts.get('continue'):
|
|
473 |
if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
|
|
474 |
raise util.Abort(_('--continue is incompatible with branch, all or merge'))
|
|
475 |
return
|
|
476 |
if not (opts.get('source') or revs or
|
|
477 |
opts.get('merge') or opts.get('branch')):
|
|
478 |
raise util.Abort(_('no source URL, branch tag or revision list provided'))
|
|
479 |
if opts.get('all'):
|
|
480 |
if not opts.get('branch'):
|
|
481 |
raise util.Abort(_('--all requires a branch revision'))
|
|
482 |
if revs:
|
|
483 |
raise util.Abort(_('--all is incompatible with a revision list'))
|
|
484 |
|
|
485 |
checkopts(opts, revs)
|
|
486 |
|
|
487 |
if not opts.get('log'):
|
|
488 |
opts['log'] = ui.config('transplant', 'log')
|
|
489 |
if not opts.get('filter'):
|
|
490 |
opts['filter'] = ui.config('transplant', 'filter')
|
|
491 |
|
|
492 |
tp = transplanter(ui, repo)
|
|
493 |
|
|
494 |
p1, p2 = repo.dirstate.parents()
|
|
495 |
if p1 == revlog.nullid:
|
|
496 |
raise util.Abort(_('no revision checked out'))
|
|
497 |
if not opts.get('continue'):
|
|
498 |
if p2 != revlog.nullid:
|
|
499 |
raise util.Abort(_('outstanding uncommitted merges'))
|
|
500 |
m, a, r, d = repo.status()[:4]
|
|
501 |
if m or a or r or d:
|
|
502 |
raise util.Abort(_('outstanding local changes'))
|
|
503 |
|
|
504 |
bundle = None
|
|
505 |
source = opts.get('source')
|
|
506 |
if source:
|
|
507 |
(source, incoming, bundle) = getremotechanges(repo, source)
|
|
508 |
else:
|
|
509 |
source = repo
|
|
510 |
|
|
511 |
try:
|
|
512 |
if opts.get('continue'):
|
|
513 |
n, node = tp.resume(repo, source, opts)
|
|
514 |
return
|
|
515 |
|
|
516 |
tf=tp.transplantfilter(repo, source, p1)
|
|
517 |
if opts.get('prune'):
|
|
518 |
prune = [source.lookup(r)
|
|
519 |
for r in cmdutil.revrange(source, opts.get('prune'))]
|
|
520 |
matchfn = lambda x: tf(x) and x not in prune
|
|
521 |
else:
|
|
522 |
matchfn = tf
|
|
523 |
branches = map(source.lookup, opts.get('branch', ()))
|
|
524 |
merges = map(source.lookup, opts.get('merge', ()))
|
|
525 |
revmap = {}
|
|
526 |
if revs:
|
|
527 |
for r in cmdutil.revrange(source, revs):
|
|
528 |
revmap[int(r)] = source.lookup(r)
|
|
529 |
elif opts.get('all') or not merges:
|
|
530 |
if source != repo:
|
|
531 |
alltransplants = incwalk(source, incoming, branches, match=matchfn)
|
|
532 |
else:
|
|
533 |
alltransplants = transplantwalk(source, p1, branches, match=matchfn)
|
|
534 |
if opts.get('all'):
|
|
535 |
revs = alltransplants
|
|
536 |
else:
|
|
537 |
revs, newmerges = browserevs(ui, source, alltransplants, opts)
|
|
538 |
merges.extend(newmerges)
|
|
539 |
for r in revs:
|
|
540 |
revmap[source.changelog.rev(r)] = r
|
|
541 |
for r in merges:
|
|
542 |
revmap[source.changelog.rev(r)] = r
|
|
543 |
|
|
544 |
revs = revmap.keys()
|
|
545 |
revs.sort()
|
|
546 |
pulls = []
|
|
547 |
|
|
548 |
tp.apply(repo, source, revmap, merges, opts)
|
|
549 |
finally:
|
|
550 |
if bundle:
|
|
551 |
os.unlink(bundle)
|
|
552 |
|
|
553 |
cmdtable = {
|
|
554 |
"transplant":
|
|
555 |
(transplant,
|
|
556 |
[('s', 'source', '', _('pull patches from REPOSITORY')),
|
|
557 |
('b', 'branch', [], _('pull patches from branch BRANCH')),
|
|
558 |
('a', 'all', None, _('pull all changesets up to BRANCH')),
|
|
559 |
('p', 'prune', [], _('skip over REV')),
|
|
560 |
('m', 'merge', [], _('merge at REV')),
|
|
561 |
('', 'log', None, _('append transplant info to log message')),
|
|
562 |
('c', 'continue', None, _('continue last transplant session after repair')),
|
|
563 |
('', 'filter', '', _('filter changesets through FILTER'))],
|
|
564 |
_('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...'))
|
|
565 |
}
|