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