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