##// END OF EJS Templates
transplant: fix crash if filter script munges log file...
Luke Plant -
r13789:7e503118 default
parent child Browse files
Show More
@@ -1,643 +1,647 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 or any later version.
6 # GNU General Public License version 2 or any later version.
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, cmdutil, hg, merge, match
18 from mercurial import bundlerepo, cmdutil, hg, merge, match
19 from mercurial import patch, revlog, util, error
19 from mercurial import patch, revlog, util, error
20 from mercurial import revset, templatekw
20 from mercurial import revset, templatekw
21
21
22 class transplantentry(object):
22 class transplantentry(object):
23 def __init__(self, lnode, rnode):
23 def __init__(self, lnode, rnode):
24 self.lnode = lnode
24 self.lnode = lnode
25 self.rnode = rnode
25 self.rnode = rnode
26
26
27 class transplants(object):
27 class transplants(object):
28 def __init__(self, path=None, transplantfile=None, opener=None):
28 def __init__(self, path=None, transplantfile=None, opener=None):
29 self.path = path
29 self.path = path
30 self.transplantfile = transplantfile
30 self.transplantfile = transplantfile
31 self.opener = opener
31 self.opener = opener
32
32
33 if not opener:
33 if not opener:
34 self.opener = util.opener(self.path)
34 self.opener = util.opener(self.path)
35 self.transplants = {}
35 self.transplants = {}
36 self.dirty = False
36 self.dirty = False
37 self.read()
37 self.read()
38
38
39 def read(self):
39 def read(self):
40 abspath = os.path.join(self.path, self.transplantfile)
40 abspath = os.path.join(self.path, self.transplantfile)
41 if self.transplantfile and os.path.exists(abspath):
41 if self.transplantfile and os.path.exists(abspath):
42 for line in self.opener(self.transplantfile).read().splitlines():
42 for line in self.opener(self.transplantfile).read().splitlines():
43 lnode, rnode = map(revlog.bin, line.split(':'))
43 lnode, rnode = map(revlog.bin, line.split(':'))
44 list = self.transplants.setdefault(rnode, [])
44 list = self.transplants.setdefault(rnode, [])
45 list.append(transplantentry(lnode, rnode))
45 list.append(transplantentry(lnode, rnode))
46
46
47 def write(self):
47 def write(self):
48 if self.dirty and self.transplantfile:
48 if self.dirty and self.transplantfile:
49 if not os.path.isdir(self.path):
49 if not os.path.isdir(self.path):
50 os.mkdir(self.path)
50 os.mkdir(self.path)
51 fp = self.opener(self.transplantfile, 'w')
51 fp = self.opener(self.transplantfile, 'w')
52 for list in self.transplants.itervalues():
52 for list in self.transplants.itervalues():
53 for t in list:
53 for t in list:
54 l, r = map(revlog.hex, (t.lnode, t.rnode))
54 l, r = map(revlog.hex, (t.lnode, t.rnode))
55 fp.write(l + ':' + r + '\n')
55 fp.write(l + ':' + r + '\n')
56 fp.close()
56 fp.close()
57 self.dirty = False
57 self.dirty = False
58
58
59 def get(self, rnode):
59 def get(self, rnode):
60 return self.transplants.get(rnode) or []
60 return self.transplants.get(rnode) or []
61
61
62 def set(self, lnode, rnode):
62 def set(self, lnode, rnode):
63 list = self.transplants.setdefault(rnode, [])
63 list = self.transplants.setdefault(rnode, [])
64 list.append(transplantentry(lnode, rnode))
64 list.append(transplantentry(lnode, rnode))
65 self.dirty = True
65 self.dirty = True
66
66
67 def remove(self, transplant):
67 def remove(self, transplant):
68 list = self.transplants.get(transplant.rnode)
68 list = self.transplants.get(transplant.rnode)
69 if list:
69 if list:
70 del list[list.index(transplant)]
70 del list[list.index(transplant)]
71 self.dirty = True
71 self.dirty = True
72
72
73 class transplanter(object):
73 class transplanter(object):
74 def __init__(self, ui, repo):
74 def __init__(self, ui, repo):
75 self.ui = ui
75 self.ui = ui
76 self.path = repo.join('transplant')
76 self.path = repo.join('transplant')
77 self.opener = util.opener(self.path)
77 self.opener = util.opener(self.path)
78 self.transplants = transplants(self.path, 'transplants',
78 self.transplants = transplants(self.path, 'transplants',
79 opener=self.opener)
79 opener=self.opener)
80
80
81 def applied(self, repo, node, parent):
81 def applied(self, repo, node, parent):
82 '''returns True if a node is already an ancestor of parent
82 '''returns True if a node is already an ancestor of parent
83 or has already been transplanted'''
83 or has already been transplanted'''
84 if hasnode(repo, node):
84 if hasnode(repo, node):
85 if node in repo.changelog.reachable(parent, stop=node):
85 if node in repo.changelog.reachable(parent, stop=node):
86 return True
86 return True
87 for t in self.transplants.get(node):
87 for t in self.transplants.get(node):
88 # it might have been stripped
88 # it might have been stripped
89 if not hasnode(repo, t.lnode):
89 if not hasnode(repo, t.lnode):
90 self.transplants.remove(t)
90 self.transplants.remove(t)
91 return False
91 return False
92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
93 return True
93 return True
94 return False
94 return False
95
95
96 def apply(self, repo, source, revmap, merges, opts={}):
96 def apply(self, repo, source, revmap, merges, opts={}):
97 '''apply the revisions in revmap one by one in revision order'''
97 '''apply the revisions in revmap one by one in revision order'''
98 revs = sorted(revmap)
98 revs = sorted(revmap)
99 p1, p2 = repo.dirstate.parents()
99 p1, p2 = repo.dirstate.parents()
100 pulls = []
100 pulls = []
101 diffopts = patch.diffopts(self.ui, opts)
101 diffopts = patch.diffopts(self.ui, opts)
102 diffopts.git = True
102 diffopts.git = True
103
103
104 lock = wlock = None
104 lock = wlock = None
105 try:
105 try:
106 wlock = repo.wlock()
106 wlock = repo.wlock()
107 lock = repo.lock()
107 lock = repo.lock()
108 for rev in revs:
108 for rev in revs:
109 node = revmap[rev]
109 node = revmap[rev]
110 revstr = '%s:%s' % (rev, revlog.short(node))
110 revstr = '%s:%s' % (rev, revlog.short(node))
111
111
112 if self.applied(repo, node, p1):
112 if self.applied(repo, node, p1):
113 self.ui.warn(_('skipping already applied revision %s\n') %
113 self.ui.warn(_('skipping already applied revision %s\n') %
114 revstr)
114 revstr)
115 continue
115 continue
116
116
117 parents = source.changelog.parents(node)
117 parents = source.changelog.parents(node)
118 if not opts.get('filter'):
118 if not opts.get('filter'):
119 # If the changeset parent is the same as the
119 # If the changeset parent is the same as the
120 # wdir's parent, just pull it.
120 # wdir's parent, just pull it.
121 if parents[0] == p1:
121 if parents[0] == p1:
122 pulls.append(node)
122 pulls.append(node)
123 p1 = node
123 p1 = node
124 continue
124 continue
125 if pulls:
125 if pulls:
126 if source != repo:
126 if source != repo:
127 repo.pull(source, heads=pulls)
127 repo.pull(source, heads=pulls)
128 merge.update(repo, pulls[-1], False, False, None)
128 merge.update(repo, pulls[-1], False, False, None)
129 p1, p2 = repo.dirstate.parents()
129 p1, p2 = repo.dirstate.parents()
130 pulls = []
130 pulls = []
131
131
132 domerge = False
132 domerge = False
133 if node in merges:
133 if node in merges:
134 # pulling all the merge revs at once would mean we
134 # pulling all the merge revs at once would mean we
135 # couldn't transplant after the latest even if
135 # couldn't transplant after the latest even if
136 # transplants before them fail.
136 # transplants before them fail.
137 domerge = True
137 domerge = True
138 if not hasnode(repo, node):
138 if not hasnode(repo, node):
139 repo.pull(source, heads=[node])
139 repo.pull(source, heads=[node])
140
140
141 if parents[1] != revlog.nullid:
141 if parents[1] != revlog.nullid:
142 self.ui.note(_('skipping merge changeset %s:%s\n')
142 self.ui.note(_('skipping merge changeset %s:%s\n')
143 % (rev, revlog.short(node)))
143 % (rev, revlog.short(node)))
144 patchfile = None
144 patchfile = None
145 else:
145 else:
146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
147 fp = os.fdopen(fd, 'w')
147 fp = os.fdopen(fd, 'w')
148 gen = patch.diff(source, parents[0], node, opts=diffopts)
148 gen = patch.diff(source, parents[0], node, opts=diffopts)
149 for chunk in gen:
149 for chunk in gen:
150 fp.write(chunk)
150 fp.write(chunk)
151 fp.close()
151 fp.close()
152
152
153 del revmap[rev]
153 del revmap[rev]
154 if patchfile or domerge:
154 if patchfile or domerge:
155 try:
155 try:
156 n = self.applyone(repo, node,
156 n = self.applyone(repo, node,
157 source.changelog.read(node),
157 source.changelog.read(node),
158 patchfile, merge=domerge,
158 patchfile, merge=domerge,
159 log=opts.get('log'),
159 log=opts.get('log'),
160 filter=opts.get('filter'))
160 filter=opts.get('filter'))
161 if n and domerge:
161 if n and domerge:
162 self.ui.status(_('%s merged at %s\n') % (revstr,
162 self.ui.status(_('%s merged at %s\n') % (revstr,
163 revlog.short(n)))
163 revlog.short(n)))
164 elif n:
164 elif n:
165 self.ui.status(_('%s transplanted to %s\n')
165 self.ui.status(_('%s transplanted to %s\n')
166 % (revlog.short(node),
166 % (revlog.short(node),
167 revlog.short(n)))
167 revlog.short(n)))
168 finally:
168 finally:
169 if patchfile:
169 if patchfile:
170 os.unlink(patchfile)
170 os.unlink(patchfile)
171 if pulls:
171 if pulls:
172 repo.pull(source, heads=pulls)
172 repo.pull(source, heads=pulls)
173 merge.update(repo, pulls[-1], False, False, None)
173 merge.update(repo, pulls[-1], False, False, None)
174 finally:
174 finally:
175 self.saveseries(revmap, merges)
175 self.saveseries(revmap, merges)
176 self.transplants.write()
176 self.transplants.write()
177 lock.release()
177 lock.release()
178 wlock.release()
178 wlock.release()
179
179
180 def filter(self, filter, node, changelog, patchfile):
180 def filter(self, filter, node, changelog, patchfile):
181 '''arbitrarily rewrite changeset before applying it'''
181 '''arbitrarily rewrite changeset before applying it'''
182
182
183 self.ui.status(_('filtering %s\n') % patchfile)
183 self.ui.status(_('filtering %s\n') % patchfile)
184 user, date, msg = (changelog[1], changelog[2], changelog[4])
184 user, date, msg = (changelog[1], changelog[2], changelog[4])
185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
186 fp = os.fdopen(fd, 'w')
186 fp = os.fdopen(fd, 'w')
187 fp.write("# HG changeset patch\n")
187 fp.write("# HG changeset patch\n")
188 fp.write("# User %s\n" % user)
188 fp.write("# User %s\n" % user)
189 fp.write("# Date %d %d\n" % date)
189 fp.write("# Date %d %d\n" % date)
190 fp.write(msg + '\n')
190 fp.write(msg + '\n')
191 fp.close()
191 fp.close()
192
192
193 try:
193 try:
194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
195 util.shellquote(patchfile)),
195 util.shellquote(patchfile)),
196 environ={'HGUSER': changelog[1],
196 environ={'HGUSER': changelog[1],
197 'HGREVISION': revlog.hex(node),
197 'HGREVISION': revlog.hex(node),
198 },
198 },
199 onerr=util.Abort, errprefix=_('filter failed'))
199 onerr=util.Abort, errprefix=_('filter failed'))
200 user, date, msg = self.parselog(file(headerfile))[1:4]
200 user, date, msg = self.parselog(file(headerfile))[1:4]
201 finally:
201 finally:
202 os.unlink(headerfile)
202 os.unlink(headerfile)
203
203
204 return (user, date, msg)
204 return (user, date, msg)
205
205
206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
207 filter=None):
207 filter=None):
208 '''apply the patch in patchfile to the repository as a transplant'''
208 '''apply the patch in patchfile to the repository as a transplant'''
209 (manifest, user, (time, timezone), files, message) = cl[:5]
209 (manifest, user, (time, timezone), files, message) = cl[:5]
210 date = "%d %d" % (time, timezone)
210 date = "%d %d" % (time, timezone)
211 extra = {'transplant_source': node}
211 extra = {'transplant_source': node}
212 if filter:
212 if filter:
213 (user, date, message) = self.filter(filter, node, cl, patchfile)
213 (user, date, message) = self.filter(filter, node, cl, patchfile)
214
214
215 if log:
215 if log:
216 # we don't translate messages inserted into commits
216 # we don't translate messages inserted into commits
217 message += '\n(transplanted from %s)' % revlog.hex(node)
217 message += '\n(transplanted from %s)' % revlog.hex(node)
218
218
219 self.ui.status(_('applying %s\n') % revlog.short(node))
219 self.ui.status(_('applying %s\n') % revlog.short(node))
220 self.ui.note('%s %s\n%s\n' % (user, date, message))
220 self.ui.note('%s %s\n%s\n' % (user, date, message))
221
221
222 if not patchfile and not merge:
222 if not patchfile and not merge:
223 raise util.Abort(_('can only omit patchfile if merging'))
223 raise util.Abort(_('can only omit patchfile if merging'))
224 if patchfile:
224 if patchfile:
225 try:
225 try:
226 files = {}
226 files = {}
227 try:
227 try:
228 patch.patch(patchfile, self.ui, cwd=repo.root,
228 patch.patch(patchfile, self.ui, cwd=repo.root,
229 files=files, eolmode=None)
229 files=files, eolmode=None)
230 if not files:
230 if not files:
231 self.ui.warn(_('%s: empty changeset')
231 self.ui.warn(_('%s: empty changeset')
232 % revlog.hex(node))
232 % revlog.hex(node))
233 return None
233 return None
234 finally:
234 finally:
235 files = cmdutil.updatedir(self.ui, repo, files)
235 files = cmdutil.updatedir(self.ui, repo, files)
236 except Exception, inst:
236 except Exception, inst:
237 seriespath = os.path.join(self.path, 'series')
237 seriespath = os.path.join(self.path, 'series')
238 if os.path.exists(seriespath):
238 if os.path.exists(seriespath):
239 os.unlink(seriespath)
239 os.unlink(seriespath)
240 p1 = repo.dirstate.parents()[0]
240 p1 = repo.dirstate.parents()[0]
241 p2 = node
241 p2 = node
242 self.log(user, date, message, p1, p2, merge=merge)
242 self.log(user, date, message, p1, p2, merge=merge)
243 self.ui.write(str(inst) + '\n')
243 self.ui.write(str(inst) + '\n')
244 raise util.Abort(_('fix up the merge and run '
244 raise util.Abort(_('fix up the merge and run '
245 'hg transplant --continue'))
245 'hg transplant --continue'))
246 else:
246 else:
247 files = None
247 files = None
248 if merge:
248 if merge:
249 p1, p2 = repo.dirstate.parents()
249 p1, p2 = repo.dirstate.parents()
250 repo.dirstate.setparents(p1, node)
250 repo.dirstate.setparents(p1, node)
251 m = match.always(repo.root, '')
251 m = match.always(repo.root, '')
252 else:
252 else:
253 m = match.exact(repo.root, '', files)
253 m = match.exact(repo.root, '', files)
254
254
255 n = repo.commit(message, user, date, extra=extra, match=m)
255 n = repo.commit(message, user, date, extra=extra, match=m)
256 if not n:
256 if not n:
257 # Crash here to prevent an unclear crash later, in
257 # Crash here to prevent an unclear crash later, in
258 # transplants.write(). This can happen if patch.patch()
258 # transplants.write(). This can happen if patch.patch()
259 # does nothing but claims success or if repo.status() fails
259 # does nothing but claims success or if repo.status() fails
260 # to report changes done by patch.patch(). These both
260 # to report changes done by patch.patch(). These both
261 # appear to be bugs in other parts of Mercurial, but dying
261 # appear to be bugs in other parts of Mercurial, but dying
262 # here, as soon as we can detect the problem, is preferable
262 # here, as soon as we can detect the problem, is preferable
263 # to silently dropping changesets on the floor.
263 # to silently dropping changesets on the floor.
264 raise RuntimeError('nothing committed after transplant')
264 raise RuntimeError('nothing committed after transplant')
265 if not merge:
265 if not merge:
266 self.transplants.set(n, node)
266 self.transplants.set(n, node)
267
267
268 return n
268 return n
269
269
270 def resume(self, repo, source, opts=None):
270 def resume(self, repo, source, opts=None):
271 '''recover last transaction and apply remaining changesets'''
271 '''recover last transaction and apply remaining changesets'''
272 if os.path.exists(os.path.join(self.path, 'journal')):
272 if os.path.exists(os.path.join(self.path, 'journal')):
273 n, node = self.recover(repo)
273 n, node = self.recover(repo)
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 revlog.short(n)))
275 revlog.short(n)))
276 seriespath = os.path.join(self.path, 'series')
276 seriespath = os.path.join(self.path, 'series')
277 if not os.path.exists(seriespath):
277 if not os.path.exists(seriespath):
278 self.transplants.write()
278 self.transplants.write()
279 return
279 return
280 nodes, merges = self.readseries()
280 nodes, merges = self.readseries()
281 revmap = {}
281 revmap = {}
282 for n in nodes:
282 for n in nodes:
283 revmap[source.changelog.rev(n)] = n
283 revmap[source.changelog.rev(n)] = n
284 os.unlink(seriespath)
284 os.unlink(seriespath)
285
285
286 self.apply(repo, source, revmap, merges, opts)
286 self.apply(repo, source, revmap, merges, opts)
287
287
288 def recover(self, repo):
288 def recover(self, repo):
289 '''commit working directory using journal metadata'''
289 '''commit working directory using journal metadata'''
290 node, user, date, message, parents = self.readlog()
290 node, user, date, message, parents = self.readlog()
291 merge = len(parents) == 2
291 merge = len(parents) == 2
292
292
293 if not user or not date or not message or not parents[0]:
293 if not user or not date or not message or not parents[0]:
294 raise util.Abort(_('transplant log file is corrupt'))
294 raise util.Abort(_('transplant log file is corrupt'))
295
295
296 extra = {'transplant_source': node}
296 extra = {'transplant_source': node}
297 wlock = repo.wlock()
297 wlock = repo.wlock()
298 try:
298 try:
299 p1, p2 = repo.dirstate.parents()
299 p1, p2 = repo.dirstate.parents()
300 if p1 != parents[0]:
300 if p1 != parents[0]:
301 raise util.Abort(
301 raise util.Abort(
302 _('working dir not at transplant parent %s') %
302 _('working dir not at transplant parent %s') %
303 revlog.hex(parents[0]))
303 revlog.hex(parents[0]))
304 if merge:
304 if merge:
305 repo.dirstate.setparents(p1, parents[1])
305 repo.dirstate.setparents(p1, parents[1])
306 n = repo.commit(message, user, date, extra=extra)
306 n = repo.commit(message, user, date, extra=extra)
307 if not n:
307 if not n:
308 raise util.Abort(_('commit failed'))
308 raise util.Abort(_('commit failed'))
309 if not merge:
309 if not merge:
310 self.transplants.set(n, node)
310 self.transplants.set(n, node)
311 self.unlog()
311 self.unlog()
312
312
313 return n, node
313 return n, node
314 finally:
314 finally:
315 wlock.release()
315 wlock.release()
316
316
317 def readseries(self):
317 def readseries(self):
318 nodes = []
318 nodes = []
319 merges = []
319 merges = []
320 cur = nodes
320 cur = nodes
321 for line in self.opener('series').read().splitlines():
321 for line in self.opener('series').read().splitlines():
322 if line.startswith('# Merges'):
322 if line.startswith('# Merges'):
323 cur = merges
323 cur = merges
324 continue
324 continue
325 cur.append(revlog.bin(line))
325 cur.append(revlog.bin(line))
326
326
327 return (nodes, merges)
327 return (nodes, merges)
328
328
329 def saveseries(self, revmap, merges):
329 def saveseries(self, revmap, merges):
330 if not revmap:
330 if not revmap:
331 return
331 return
332
332
333 if not os.path.isdir(self.path):
333 if not os.path.isdir(self.path):
334 os.mkdir(self.path)
334 os.mkdir(self.path)
335 series = self.opener('series', 'w')
335 series = self.opener('series', 'w')
336 for rev in sorted(revmap):
336 for rev in sorted(revmap):
337 series.write(revlog.hex(revmap[rev]) + '\n')
337 series.write(revlog.hex(revmap[rev]) + '\n')
338 if merges:
338 if merges:
339 series.write('# Merges\n')
339 series.write('# Merges\n')
340 for m in merges:
340 for m in merges:
341 series.write(revlog.hex(m) + '\n')
341 series.write(revlog.hex(m) + '\n')
342 series.close()
342 series.close()
343
343
344 def parselog(self, fp):
344 def parselog(self, fp):
345 parents = []
345 parents = []
346 message = []
346 message = []
347 node = revlog.nullid
347 node = revlog.nullid
348 inmsg = False
348 inmsg = False
349 user = None
350 date = None
349 for line in fp.read().splitlines():
351 for line in fp.read().splitlines():
350 if inmsg:
352 if inmsg:
351 message.append(line)
353 message.append(line)
352 elif line.startswith('# User '):
354 elif line.startswith('# User '):
353 user = line[7:]
355 user = line[7:]
354 elif line.startswith('# Date '):
356 elif line.startswith('# Date '):
355 date = line[7:]
357 date = line[7:]
356 elif line.startswith('# Node ID '):
358 elif line.startswith('# Node ID '):
357 node = revlog.bin(line[10:])
359 node = revlog.bin(line[10:])
358 elif line.startswith('# Parent '):
360 elif line.startswith('# Parent '):
359 parents.append(revlog.bin(line[9:]))
361 parents.append(revlog.bin(line[9:]))
360 elif not line.startswith('# '):
362 elif not line.startswith('# '):
361 inmsg = True
363 inmsg = True
362 message.append(line)
364 message.append(line)
365 if None in (user, date):
366 raise util.Abort(_("filter produced garbled log file"))
363 return (node, user, date, '\n'.join(message), parents)
367 return (node, user, date, '\n'.join(message), parents)
364
368
365 def log(self, user, date, message, p1, p2, merge=False):
369 def log(self, user, date, message, p1, p2, merge=False):
366 '''journal changelog metadata for later recover'''
370 '''journal changelog metadata for later recover'''
367
371
368 if not os.path.isdir(self.path):
372 if not os.path.isdir(self.path):
369 os.mkdir(self.path)
373 os.mkdir(self.path)
370 fp = self.opener('journal', 'w')
374 fp = self.opener('journal', 'w')
371 fp.write('# User %s\n' % user)
375 fp.write('# User %s\n' % user)
372 fp.write('# Date %s\n' % date)
376 fp.write('# Date %s\n' % date)
373 fp.write('# Node ID %s\n' % revlog.hex(p2))
377 fp.write('# Node ID %s\n' % revlog.hex(p2))
374 fp.write('# Parent ' + revlog.hex(p1) + '\n')
378 fp.write('# Parent ' + revlog.hex(p1) + '\n')
375 if merge:
379 if merge:
376 fp.write('# Parent ' + revlog.hex(p2) + '\n')
380 fp.write('# Parent ' + revlog.hex(p2) + '\n')
377 fp.write(message.rstrip() + '\n')
381 fp.write(message.rstrip() + '\n')
378 fp.close()
382 fp.close()
379
383
380 def readlog(self):
384 def readlog(self):
381 return self.parselog(self.opener('journal'))
385 return self.parselog(self.opener('journal'))
382
386
383 def unlog(self):
387 def unlog(self):
384 '''remove changelog journal'''
388 '''remove changelog journal'''
385 absdst = os.path.join(self.path, 'journal')
389 absdst = os.path.join(self.path, 'journal')
386 if os.path.exists(absdst):
390 if os.path.exists(absdst):
387 os.unlink(absdst)
391 os.unlink(absdst)
388
392
389 def transplantfilter(self, repo, source, root):
393 def transplantfilter(self, repo, source, root):
390 def matchfn(node):
394 def matchfn(node):
391 if self.applied(repo, node, root):
395 if self.applied(repo, node, root):
392 return False
396 return False
393 if source.changelog.parents(node)[1] != revlog.nullid:
397 if source.changelog.parents(node)[1] != revlog.nullid:
394 return False
398 return False
395 extra = source.changelog.read(node)[5]
399 extra = source.changelog.read(node)[5]
396 cnode = extra.get('transplant_source')
400 cnode = extra.get('transplant_source')
397 if cnode and self.applied(repo, cnode, root):
401 if cnode and self.applied(repo, cnode, root):
398 return False
402 return False
399 return True
403 return True
400
404
401 return matchfn
405 return matchfn
402
406
403 def hasnode(repo, node):
407 def hasnode(repo, node):
404 try:
408 try:
405 return repo.changelog.rev(node) is not None
409 return repo.changelog.rev(node) is not None
406 except error.RevlogError:
410 except error.RevlogError:
407 return False
411 return False
408
412
409 def browserevs(ui, repo, nodes, opts):
413 def browserevs(ui, repo, nodes, opts):
410 '''interactively transplant changesets'''
414 '''interactively transplant changesets'''
411 def browsehelp(ui):
415 def browsehelp(ui):
412 ui.write(_('y: transplant this changeset\n'
416 ui.write(_('y: transplant this changeset\n'
413 'n: skip this changeset\n'
417 'n: skip this changeset\n'
414 'm: merge at this changeset\n'
418 'm: merge at this changeset\n'
415 'p: show patch\n'
419 'p: show patch\n'
416 'c: commit selected changesets\n'
420 'c: commit selected changesets\n'
417 'q: cancel transplant\n'
421 'q: cancel transplant\n'
418 '?: show this help\n'))
422 '?: show this help\n'))
419
423
420 displayer = cmdutil.show_changeset(ui, repo, opts)
424 displayer = cmdutil.show_changeset(ui, repo, opts)
421 transplants = []
425 transplants = []
422 merges = []
426 merges = []
423 for node in nodes:
427 for node in nodes:
424 displayer.show(repo[node])
428 displayer.show(repo[node])
425 action = None
429 action = None
426 while not action:
430 while not action:
427 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
431 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
428 if action == '?':
432 if action == '?':
429 browsehelp(ui)
433 browsehelp(ui)
430 action = None
434 action = None
431 elif action == 'p':
435 elif action == 'p':
432 parent = repo.changelog.parents(node)[0]
436 parent = repo.changelog.parents(node)[0]
433 for chunk in patch.diff(repo, parent, node):
437 for chunk in patch.diff(repo, parent, node):
434 ui.write(chunk)
438 ui.write(chunk)
435 action = None
439 action = None
436 elif action not in ('y', 'n', 'm', 'c', 'q'):
440 elif action not in ('y', 'n', 'm', 'c', 'q'):
437 ui.write(_('no such option\n'))
441 ui.write(_('no such option\n'))
438 action = None
442 action = None
439 if action == 'y':
443 if action == 'y':
440 transplants.append(node)
444 transplants.append(node)
441 elif action == 'm':
445 elif action == 'm':
442 merges.append(node)
446 merges.append(node)
443 elif action == 'c':
447 elif action == 'c':
444 break
448 break
445 elif action == 'q':
449 elif action == 'q':
446 transplants = ()
450 transplants = ()
447 merges = ()
451 merges = ()
448 break
452 break
449 displayer.close()
453 displayer.close()
450 return (transplants, merges)
454 return (transplants, merges)
451
455
452 def transplant(ui, repo, *revs, **opts):
456 def transplant(ui, repo, *revs, **opts):
453 '''transplant changesets from another branch
457 '''transplant changesets from another branch
454
458
455 Selected changesets will be applied on top of the current working
459 Selected changesets will be applied on top of the current working
456 directory with the log of the original changeset. The changesets
460 directory with the log of the original changeset. The changesets
457 are copied and will thus appear twice in the history. Use the
461 are copied and will thus appear twice in the history. Use the
458 rebase extension instead if you want to move a whole branch of
462 rebase extension instead if you want to move a whole branch of
459 unpublished changesets.
463 unpublished changesets.
460
464
461 If --log is specified, log messages will have a comment appended
465 If --log is specified, log messages will have a comment appended
462 of the form::
466 of the form::
463
467
464 (transplanted from CHANGESETHASH)
468 (transplanted from CHANGESETHASH)
465
469
466 You can rewrite the changelog message with the --filter option.
470 You can rewrite the changelog message with the --filter option.
467 Its argument will be invoked with the current changelog message as
471 Its argument will be invoked with the current changelog message as
468 $1 and the patch as $2.
472 $1 and the patch as $2.
469
473
470 If --source/-s is specified, selects changesets from the named
474 If --source/-s is specified, selects changesets from the named
471 repository. If --branch/-b is specified, selects changesets from
475 repository. If --branch/-b is specified, selects changesets from
472 the branch holding the named revision, up to that revision. If
476 the branch holding the named revision, up to that revision. If
473 --all/-a is specified, all changesets on the branch will be
477 --all/-a is specified, all changesets on the branch will be
474 transplanted, otherwise you will be prompted to select the
478 transplanted, otherwise you will be prompted to select the
475 changesets you want.
479 changesets you want.
476
480
477 :hg:`transplant --branch REVISION --all` will transplant the
481 :hg:`transplant --branch REVISION --all` will transplant the
478 selected branch (up to the named revision) onto your current
482 selected branch (up to the named revision) onto your current
479 working directory.
483 working directory.
480
484
481 You can optionally mark selected transplanted changesets as merge
485 You can optionally mark selected transplanted changesets as merge
482 changesets. You will not be prompted to transplant any ancestors
486 changesets. You will not be prompted to transplant any ancestors
483 of a merged transplant, and you can merge descendants of them
487 of a merged transplant, and you can merge descendants of them
484 normally instead of transplanting them.
488 normally instead of transplanting them.
485
489
486 If no merges or revisions are provided, :hg:`transplant` will
490 If no merges or revisions are provided, :hg:`transplant` will
487 start an interactive changeset browser.
491 start an interactive changeset browser.
488
492
489 If a changeset application fails, you can fix the merge by hand
493 If a changeset application fails, you can fix the merge by hand
490 and then resume where you left off by calling :hg:`transplant
494 and then resume where you left off by calling :hg:`transplant
491 --continue/-c`.
495 --continue/-c`.
492 '''
496 '''
493 def incwalk(repo, incoming, branches, match=util.always):
497 def incwalk(repo, incoming, branches, match=util.always):
494 if not branches:
498 if not branches:
495 branches = None
499 branches = None
496 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
500 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
497 if match(node):
501 if match(node):
498 yield node
502 yield node
499
503
500 def transplantwalk(repo, root, branches, match=util.always):
504 def transplantwalk(repo, root, branches, match=util.always):
501 if not branches:
505 if not branches:
502 branches = repo.heads()
506 branches = repo.heads()
503 ancestors = []
507 ancestors = []
504 for branch in branches:
508 for branch in branches:
505 ancestors.append(repo.changelog.ancestor(root, branch))
509 ancestors.append(repo.changelog.ancestor(root, branch))
506 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
510 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
507 if match(node):
511 if match(node):
508 yield node
512 yield node
509
513
510 def checkopts(opts, revs):
514 def checkopts(opts, revs):
511 if opts.get('continue'):
515 if opts.get('continue'):
512 if opts.get('branch') or opts.get('all') or opts.get('merge'):
516 if opts.get('branch') or opts.get('all') or opts.get('merge'):
513 raise util.Abort(_('--continue is incompatible with '
517 raise util.Abort(_('--continue is incompatible with '
514 'branch, all or merge'))
518 'branch, all or merge'))
515 return
519 return
516 if not (opts.get('source') or revs or
520 if not (opts.get('source') or revs or
517 opts.get('merge') or opts.get('branch')):
521 opts.get('merge') or opts.get('branch')):
518 raise util.Abort(_('no source URL, branch tag or revision '
522 raise util.Abort(_('no source URL, branch tag or revision '
519 'list provided'))
523 'list provided'))
520 if opts.get('all'):
524 if opts.get('all'):
521 if not opts.get('branch'):
525 if not opts.get('branch'):
522 raise util.Abort(_('--all requires a branch revision'))
526 raise util.Abort(_('--all requires a branch revision'))
523 if revs:
527 if revs:
524 raise util.Abort(_('--all is incompatible with a '
528 raise util.Abort(_('--all is incompatible with a '
525 'revision list'))
529 'revision list'))
526
530
527 checkopts(opts, revs)
531 checkopts(opts, revs)
528
532
529 if not opts.get('log'):
533 if not opts.get('log'):
530 opts['log'] = ui.config('transplant', 'log')
534 opts['log'] = ui.config('transplant', 'log')
531 if not opts.get('filter'):
535 if not opts.get('filter'):
532 opts['filter'] = ui.config('transplant', 'filter')
536 opts['filter'] = ui.config('transplant', 'filter')
533
537
534 tp = transplanter(ui, repo)
538 tp = transplanter(ui, repo)
535
539
536 p1, p2 = repo.dirstate.parents()
540 p1, p2 = repo.dirstate.parents()
537 if len(repo) > 0 and p1 == revlog.nullid:
541 if len(repo) > 0 and p1 == revlog.nullid:
538 raise util.Abort(_('no revision checked out'))
542 raise util.Abort(_('no revision checked out'))
539 if not opts.get('continue'):
543 if not opts.get('continue'):
540 if p2 != revlog.nullid:
544 if p2 != revlog.nullid:
541 raise util.Abort(_('outstanding uncommitted merges'))
545 raise util.Abort(_('outstanding uncommitted merges'))
542 m, a, r, d = repo.status()[:4]
546 m, a, r, d = repo.status()[:4]
543 if m or a or r or d:
547 if m or a or r or d:
544 raise util.Abort(_('outstanding local changes'))
548 raise util.Abort(_('outstanding local changes'))
545
549
546 bundle = None
550 bundle = None
547 source = opts.get('source')
551 source = opts.get('source')
548 if source:
552 if source:
549 sourcerepo = ui.expandpath(source)
553 sourcerepo = ui.expandpath(source)
550 source = hg.repository(ui, sourcerepo)
554 source = hg.repository(ui, sourcerepo)
551 source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
555 source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
552 source, force=True)
556 source, force=True)
553 else:
557 else:
554 source = repo
558 source = repo
555
559
556 try:
560 try:
557 if opts.get('continue'):
561 if opts.get('continue'):
558 tp.resume(repo, source, opts)
562 tp.resume(repo, source, opts)
559 return
563 return
560
564
561 tf = tp.transplantfilter(repo, source, p1)
565 tf = tp.transplantfilter(repo, source, p1)
562 if opts.get('prune'):
566 if opts.get('prune'):
563 prune = [source.lookup(r)
567 prune = [source.lookup(r)
564 for r in cmdutil.revrange(source, opts.get('prune'))]
568 for r in cmdutil.revrange(source, opts.get('prune'))]
565 matchfn = lambda x: tf(x) and x not in prune
569 matchfn = lambda x: tf(x) and x not in prune
566 else:
570 else:
567 matchfn = tf
571 matchfn = tf
568 branches = map(source.lookup, opts.get('branch', ()))
572 branches = map(source.lookup, opts.get('branch', ()))
569 merges = map(source.lookup, opts.get('merge', ()))
573 merges = map(source.lookup, opts.get('merge', ()))
570 revmap = {}
574 revmap = {}
571 if revs:
575 if revs:
572 for r in cmdutil.revrange(source, revs):
576 for r in cmdutil.revrange(source, revs):
573 revmap[int(r)] = source.lookup(r)
577 revmap[int(r)] = source.lookup(r)
574 elif opts.get('all') or not merges:
578 elif opts.get('all') or not merges:
575 if source != repo:
579 if source != repo:
576 alltransplants = incwalk(source, incoming, branches,
580 alltransplants = incwalk(source, incoming, branches,
577 match=matchfn)
581 match=matchfn)
578 else:
582 else:
579 alltransplants = transplantwalk(source, p1, branches,
583 alltransplants = transplantwalk(source, p1, branches,
580 match=matchfn)
584 match=matchfn)
581 if opts.get('all'):
585 if opts.get('all'):
582 revs = alltransplants
586 revs = alltransplants
583 else:
587 else:
584 revs, newmerges = browserevs(ui, source, alltransplants, opts)
588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
585 merges.extend(newmerges)
589 merges.extend(newmerges)
586 for r in revs:
590 for r in revs:
587 revmap[source.changelog.rev(r)] = r
591 revmap[source.changelog.rev(r)] = r
588 for r in merges:
592 for r in merges:
589 revmap[source.changelog.rev(r)] = r
593 revmap[source.changelog.rev(r)] = r
590
594
591 tp.apply(repo, source, revmap, merges, opts)
595 tp.apply(repo, source, revmap, merges, opts)
592 finally:
596 finally:
593 if bundle:
597 if bundle:
594 source.close()
598 source.close()
595 os.unlink(bundle)
599 os.unlink(bundle)
596
600
597 def revsettransplanted(repo, subset, x):
601 def revsettransplanted(repo, subset, x):
598 """``transplanted(set)``
602 """``transplanted(set)``
599 Transplanted changesets in set.
603 Transplanted changesets in set.
600 """
604 """
601 if x:
605 if x:
602 s = revset.getset(repo, subset, x)
606 s = revset.getset(repo, subset, x)
603 else:
607 else:
604 s = subset
608 s = subset
605 cs = set()
609 cs = set()
606 for r in xrange(0, len(repo)):
610 for r in xrange(0, len(repo)):
607 if repo[r].extra().get('transplant_source'):
611 if repo[r].extra().get('transplant_source'):
608 cs.add(r)
612 cs.add(r)
609 return [r for r in s if r in cs]
613 return [r for r in s if r in cs]
610
614
611 def kwtransplanted(repo, ctx, **args):
615 def kwtransplanted(repo, ctx, **args):
612 """:transplanted: String. The node identifier of the transplanted
616 """:transplanted: String. The node identifier of the transplanted
613 changeset if any."""
617 changeset if any."""
614 n = ctx.extra().get('transplant_source')
618 n = ctx.extra().get('transplant_source')
615 return n and revlog.hex(n) or ''
619 return n and revlog.hex(n) or ''
616
620
617 def extsetup(ui):
621 def extsetup(ui):
618 revset.symbols['transplanted'] = revsettransplanted
622 revset.symbols['transplanted'] = revsettransplanted
619 templatekw.keywords['transplanted'] = kwtransplanted
623 templatekw.keywords['transplanted'] = kwtransplanted
620
624
621 cmdtable = {
625 cmdtable = {
622 "transplant":
626 "transplant":
623 (transplant,
627 (transplant,
624 [('s', 'source', '',
628 [('s', 'source', '',
625 _('pull patches from REPO'), _('REPO')),
629 _('pull patches from REPO'), _('REPO')),
626 ('b', 'branch', [],
630 ('b', 'branch', [],
627 _('pull patches from branch BRANCH'), _('BRANCH')),
631 _('pull patches from branch BRANCH'), _('BRANCH')),
628 ('a', 'all', None, _('pull all changesets up to BRANCH')),
632 ('a', 'all', None, _('pull all changesets up to BRANCH')),
629 ('p', 'prune', [],
633 ('p', 'prune', [],
630 _('skip over REV'), _('REV')),
634 _('skip over REV'), _('REV')),
631 ('m', 'merge', [],
635 ('m', 'merge', [],
632 _('merge at REV'), _('REV')),
636 _('merge at REV'), _('REV')),
633 ('', 'log', None, _('append transplant info to log message')),
637 ('', 'log', None, _('append transplant info to log message')),
634 ('c', 'continue', None, _('continue last transplant session '
638 ('c', 'continue', None, _('continue last transplant session '
635 'after repair')),
639 'after repair')),
636 ('', 'filter', '',
640 ('', 'filter', '',
637 _('filter changesets through command'), _('CMD'))],
641 _('filter changesets through command'), _('CMD'))],
638 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
642 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
639 '[-m REV] [REV]...'))
643 '[-m REV] [REV]...'))
640 }
644 }
641
645
642 # tell hggettext to extract docstrings from these functions:
646 # tell hggettext to extract docstrings from these functions:
643 i18nfunctions = [revsettransplanted, kwtransplanted]
647 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,396 +1,409 b''
1 $ cat <<EOF >> $HGRCPATH
1 $ cat <<EOF >> $HGRCPATH
2 > [extensions]
2 > [extensions]
3 > transplant=
3 > transplant=
4 > EOF
4 > EOF
5
5
6 $ hg init t
6 $ hg init t
7 $ cd t
7 $ cd t
8 $ echo r1 > r1
8 $ echo r1 > r1
9 $ hg ci -Amr1 -d'0 0'
9 $ hg ci -Amr1 -d'0 0'
10 adding r1
10 adding r1
11 $ echo r2 > r2
11 $ echo r2 > r2
12 $ hg ci -Amr2 -d'1 0'
12 $ hg ci -Amr2 -d'1 0'
13 adding r2
13 adding r2
14 $ hg up 0
14 $ hg up 0
15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
16
16
17 $ echo b1 > b1
17 $ echo b1 > b1
18 $ hg ci -Amb1 -d '0 0'
18 $ hg ci -Amb1 -d '0 0'
19 adding b1
19 adding b1
20 created new head
20 created new head
21 $ echo b2 > b2
21 $ echo b2 > b2
22 $ hg ci -Amb2 -d '1 0'
22 $ hg ci -Amb2 -d '1 0'
23 adding b2
23 adding b2
24 $ echo b3 > b3
24 $ echo b3 > b3
25 $ hg ci -Amb3 -d '2 0'
25 $ hg ci -Amb3 -d '2 0'
26 adding b3
26 adding b3
27
27
28 $ hg log --template '{rev} {parents} {desc}\n'
28 $ hg log --template '{rev} {parents} {desc}\n'
29 4 b3
29 4 b3
30 3 b2
30 3 b2
31 2 0:17ab29e464c6 b1
31 2 0:17ab29e464c6 b1
32 1 r2
32 1 r2
33 0 r1
33 0 r1
34
34
35 $ hg clone . ../rebase
35 $ hg clone . ../rebase
36 updating to branch default
36 updating to branch default
37 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 $ cd ../rebase
38 $ cd ../rebase
39
39
40 $ hg up -C 1
40 $ hg up -C 1
41 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
41 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
42
42
43 rebase b onto r1
43 rebase b onto r1
44
44
45 $ hg transplant -a -b tip
45 $ hg transplant -a -b tip
46 applying 37a1297eb21b
46 applying 37a1297eb21b
47 37a1297eb21b transplanted to e234d668f844
47 37a1297eb21b transplanted to e234d668f844
48 applying 722f4667af76
48 applying 722f4667af76
49 722f4667af76 transplanted to 539f377d78df
49 722f4667af76 transplanted to 539f377d78df
50 applying a53251cdf717
50 applying a53251cdf717
51 a53251cdf717 transplanted to ffd6818a3975
51 a53251cdf717 transplanted to ffd6818a3975
52 $ hg log --template '{rev} {parents} {desc}\n'
52 $ hg log --template '{rev} {parents} {desc}\n'
53 7 b3
53 7 b3
54 6 b2
54 6 b2
55 5 1:d11e3596cc1a b1
55 5 1:d11e3596cc1a b1
56 4 b3
56 4 b3
57 3 b2
57 3 b2
58 2 0:17ab29e464c6 b1
58 2 0:17ab29e464c6 b1
59 1 r2
59 1 r2
60 0 r1
60 0 r1
61
61
62 test transplanted revset
62 test transplanted revset
63
63
64 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
64 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
65 5 1:d11e3596cc1a b1
65 5 1:d11e3596cc1a b1
66 6 b2
66 6 b2
67 7 b3
67 7 b3
68 $ hg help revsets | grep transplanted
68 $ hg help revsets | grep transplanted
69 "transplanted(set)"
69 "transplanted(set)"
70
70
71 test tranplanted keyword
71 test tranplanted keyword
72
72
73 $ hg log --template '{rev} {transplanted}\n'
73 $ hg log --template '{rev} {transplanted}\n'
74 7 a53251cdf717679d1907b289f991534be05c997a
74 7 a53251cdf717679d1907b289f991534be05c997a
75 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
75 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
76 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
76 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
77 4
77 4
78 3
78 3
79 2
79 2
80 1
80 1
81 0
81 0
82
82
83 $ hg clone ../t ../prune
83 $ hg clone ../t ../prune
84 updating to branch default
84 updating to branch default
85 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 $ cd ../prune
86 $ cd ../prune
87
87
88 $ hg up -C 1
88 $ hg up -C 1
89 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
89 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
90
90
91 rebase b onto r1, skipping b2
91 rebase b onto r1, skipping b2
92
92
93 $ hg transplant -a -b tip -p 3
93 $ hg transplant -a -b tip -p 3
94 applying 37a1297eb21b
94 applying 37a1297eb21b
95 37a1297eb21b transplanted to e234d668f844
95 37a1297eb21b transplanted to e234d668f844
96 applying a53251cdf717
96 applying a53251cdf717
97 a53251cdf717 transplanted to 7275fda4d04f
97 a53251cdf717 transplanted to 7275fda4d04f
98 $ hg log --template '{rev} {parents} {desc}\n'
98 $ hg log --template '{rev} {parents} {desc}\n'
99 6 b3
99 6 b3
100 5 1:d11e3596cc1a b1
100 5 1:d11e3596cc1a b1
101 4 b3
101 4 b3
102 3 b2
102 3 b2
103 2 0:17ab29e464c6 b1
103 2 0:17ab29e464c6 b1
104 1 r2
104 1 r2
105 0 r1
105 0 r1
106
106
107
107
108 remote transplant
108 remote transplant
109
109
110 $ hg clone -r 1 ../t ../remote
110 $ hg clone -r 1 ../t ../remote
111 adding changesets
111 adding changesets
112 adding manifests
112 adding manifests
113 adding file changes
113 adding file changes
114 added 2 changesets with 2 changes to 2 files
114 added 2 changesets with 2 changes to 2 files
115 updating to branch default
115 updating to branch default
116 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 $ cd ../remote
117 $ cd ../remote
118 $ hg transplant --log -s ../t 2 4
118 $ hg transplant --log -s ../t 2 4
119 searching for changes
119 searching for changes
120 applying 37a1297eb21b
120 applying 37a1297eb21b
121 37a1297eb21b transplanted to c19cf0ccb069
121 37a1297eb21b transplanted to c19cf0ccb069
122 applying a53251cdf717
122 applying a53251cdf717
123 a53251cdf717 transplanted to f7fe5bf98525
123 a53251cdf717 transplanted to f7fe5bf98525
124 $ hg log --template '{rev} {parents} {desc}\n'
124 $ hg log --template '{rev} {parents} {desc}\n'
125 3 b3
125 3 b3
126 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
126 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
127 2 b1
127 2 b1
128 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
128 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
129 1 r2
129 1 r2
130 0 r1
130 0 r1
131
131
132 skip previous transplants
132 skip previous transplants
133
133
134 $ hg transplant -s ../t -a -b 4
134 $ hg transplant -s ../t -a -b 4
135 searching for changes
135 searching for changes
136 applying 722f4667af76
136 applying 722f4667af76
137 722f4667af76 transplanted to 47156cd86c0b
137 722f4667af76 transplanted to 47156cd86c0b
138 $ hg log --template '{rev} {parents} {desc}\n'
138 $ hg log --template '{rev} {parents} {desc}\n'
139 4 b2
139 4 b2
140 3 b3
140 3 b3
141 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
141 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
142 2 b1
142 2 b1
143 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
143 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
144 1 r2
144 1 r2
145 0 r1
145 0 r1
146
146
147 skip local changes transplanted to the source
147 skip local changes transplanted to the source
148
148
149 $ echo b4 > b4
149 $ echo b4 > b4
150 $ hg ci -Amb4 -d '3 0'
150 $ hg ci -Amb4 -d '3 0'
151 adding b4
151 adding b4
152 $ hg clone ../t ../pullback
152 $ hg clone ../t ../pullback
153 updating to branch default
153 updating to branch default
154 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 $ cd ../pullback
155 $ cd ../pullback
156 $ hg transplant -s ../remote -a -b tip
156 $ hg transplant -s ../remote -a -b tip
157 searching for changes
157 searching for changes
158 applying 4333daefcb15
158 applying 4333daefcb15
159 4333daefcb15 transplanted to 5f42c04e07cc
159 4333daefcb15 transplanted to 5f42c04e07cc
160
160
161
161
162 remote transplant with pull
162 remote transplant with pull
163
163
164 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
164 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
165 $ cat ../t.pid >> $DAEMON_PIDS
165 $ cat ../t.pid >> $DAEMON_PIDS
166
166
167 $ hg clone -r 0 ../t ../rp
167 $ hg clone -r 0 ../t ../rp
168 adding changesets
168 adding changesets
169 adding manifests
169 adding manifests
170 adding file changes
170 adding file changes
171 added 1 changesets with 1 changes to 1 files
171 added 1 changesets with 1 changes to 1 files
172 updating to branch default
172 updating to branch default
173 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 $ cd ../rp
174 $ cd ../rp
175 $ hg transplant -s http://localhost:$HGPORT/ 2 4
175 $ hg transplant -s http://localhost:$HGPORT/ 2 4
176 searching for changes
176 searching for changes
177 searching for changes
177 searching for changes
178 adding changesets
178 adding changesets
179 adding manifests
179 adding manifests
180 adding file changes
180 adding file changes
181 added 1 changesets with 1 changes to 1 files
181 added 1 changesets with 1 changes to 1 files
182 applying a53251cdf717
182 applying a53251cdf717
183 a53251cdf717 transplanted to 8d9279348abb
183 a53251cdf717 transplanted to 8d9279348abb
184 $ hg log --template '{rev} {parents} {desc}\n'
184 $ hg log --template '{rev} {parents} {desc}\n'
185 2 b3
185 2 b3
186 1 b1
186 1 b1
187 0 r1
187 0 r1
188
188
189 transplant --continue
189 transplant --continue
190
190
191 $ hg init ../tc
191 $ hg init ../tc
192 $ cd ../tc
192 $ cd ../tc
193 $ cat <<EOF > foo
193 $ cat <<EOF > foo
194 > foo
194 > foo
195 > bar
195 > bar
196 > baz
196 > baz
197 > EOF
197 > EOF
198 $ echo toremove > toremove
198 $ echo toremove > toremove
199 $ hg ci -Amfoo
199 $ hg ci -Amfoo
200 adding foo
200 adding foo
201 adding toremove
201 adding toremove
202 $ cat <<EOF > foo
202 $ cat <<EOF > foo
203 > foo2
203 > foo2
204 > bar2
204 > bar2
205 > baz2
205 > baz2
206 > EOF
206 > EOF
207 $ rm toremove
207 $ rm toremove
208 $ echo added > added
208 $ echo added > added
209 $ hg ci -Amfoo2
209 $ hg ci -Amfoo2
210 adding added
210 adding added
211 removing toremove
211 removing toremove
212 $ echo bar > bar
212 $ echo bar > bar
213 $ hg ci -Ambar
213 $ hg ci -Ambar
214 adding bar
214 adding bar
215 $ echo bar2 >> bar
215 $ echo bar2 >> bar
216 $ hg ci -mbar2
216 $ hg ci -mbar2
217 $ hg up 0
217 $ hg up 0
218 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
218 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
219 $ echo foobar > foo
219 $ echo foobar > foo
220 $ hg ci -mfoobar
220 $ hg ci -mfoobar
221 created new head
221 created new head
222 $ hg transplant 1:3
222 $ hg transplant 1:3
223 applying a1e30dd1b8e7
223 applying a1e30dd1b8e7
224 patching file foo
224 patching file foo
225 Hunk #1 FAILED at 0
225 Hunk #1 FAILED at 0
226 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
226 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
227 patch failed to apply
227 patch failed to apply
228 abort: fix up the merge and run hg transplant --continue
228 abort: fix up the merge and run hg transplant --continue
229 [255]
229 [255]
230
230
231 transplant -c shouldn't use an old changeset
231 transplant -c shouldn't use an old changeset
232
232
233 $ hg up -C
233 $ hg up -C
234 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 $ rm added
235 $ rm added
236 $ hg transplant 1
236 $ hg transplant 1
237 applying a1e30dd1b8e7
237 applying a1e30dd1b8e7
238 patching file foo
238 patching file foo
239 Hunk #1 FAILED at 0
239 Hunk #1 FAILED at 0
240 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
240 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
241 patch failed to apply
241 patch failed to apply
242 abort: fix up the merge and run hg transplant --continue
242 abort: fix up the merge and run hg transplant --continue
243 [255]
243 [255]
244 $ hg transplant --continue
244 $ hg transplant --continue
245 a1e30dd1b8e7 transplanted as f1563cf27039
245 a1e30dd1b8e7 transplanted as f1563cf27039
246 $ hg transplant 1:3
246 $ hg transplant 1:3
247 skipping already applied revision 1:a1e30dd1b8e7
247 skipping already applied revision 1:a1e30dd1b8e7
248 applying 1739ac5f6139
248 applying 1739ac5f6139
249 1739ac5f6139 transplanted to d649c221319f
249 1739ac5f6139 transplanted to d649c221319f
250 applying 0282d5fbbe02
250 applying 0282d5fbbe02
251 0282d5fbbe02 transplanted to 77418277ccb3
251 0282d5fbbe02 transplanted to 77418277ccb3
252 $ hg locate
252 $ hg locate
253 added
253 added
254 bar
254 bar
255 foo
255 foo
256 $ cd ..
256 $ cd ..
257
257
258 Issue1111: Test transplant --merge
258 Issue1111: Test transplant --merge
259
259
260 $ hg init t1111
260 $ hg init t1111
261 $ cd t1111
261 $ cd t1111
262 $ echo a > a
262 $ echo a > a
263 $ hg ci -Am adda
263 $ hg ci -Am adda
264 adding a
264 adding a
265 $ echo b >> a
265 $ echo b >> a
266 $ hg ci -m appendb
266 $ hg ci -m appendb
267 $ echo c >> a
267 $ echo c >> a
268 $ hg ci -m appendc
268 $ hg ci -m appendc
269 $ hg up -C 0
269 $ hg up -C 0
270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
271 $ echo d >> a
271 $ echo d >> a
272 $ hg ci -m appendd
272 $ hg ci -m appendd
273 created new head
273 created new head
274
274
275 tranplant
275 tranplant
276
276
277 $ hg transplant -m 1
277 $ hg transplant -m 1
278 applying 42dc4432fd35
278 applying 42dc4432fd35
279 1:42dc4432fd35 merged at a9f4acbac129
279 1:42dc4432fd35 merged at a9f4acbac129
280 $ cd ..
280 $ cd ..
281
281
282 test transplant into empty repository
282 test transplant into empty repository
283
283
284 $ hg init empty
284 $ hg init empty
285 $ cd empty
285 $ cd empty
286 $ hg transplant -s ../t -b tip -a
286 $ hg transplant -s ../t -b tip -a
287 adding changesets
287 adding changesets
288 adding manifests
288 adding manifests
289 adding file changes
289 adding file changes
290 added 4 changesets with 4 changes to 4 files
290 added 4 changesets with 4 changes to 4 files
291 $ cd ..
291 $ cd ..
292
292
293
293
294 test filter
294 test filter
295
295
296 $ hg init filter
296 $ hg init filter
297 $ cd filter
297 $ cd filter
298 $ cat <<'EOF' >test-filter
298 $ cat <<'EOF' >test-filter
299 > #!/bin/sh
299 > #!/bin/sh
300 > sed 's/r1/r2/' $1 > $1.new
300 > sed 's/r1/r2/' $1 > $1.new
301 > mv $1.new $1
301 > mv $1.new $1
302 > EOF
302 > EOF
303 $ chmod +x test-filter
303 $ chmod +x test-filter
304 $ hg transplant -s ../t -b tip -a --filter ./test-filter
304 $ hg transplant -s ../t -b tip -a --filter ./test-filter
305 filtering * (glob)
305 filtering * (glob)
306 applying 17ab29e464c6
306 applying 17ab29e464c6
307 17ab29e464c6 transplanted to e9ffc54ea104
307 17ab29e464c6 transplanted to e9ffc54ea104
308 filtering * (glob)
308 filtering * (glob)
309 applying 37a1297eb21b
309 applying 37a1297eb21b
310 37a1297eb21b transplanted to 348b36d0b6a5
310 37a1297eb21b transplanted to 348b36d0b6a5
311 filtering * (glob)
311 filtering * (glob)
312 applying 722f4667af76
312 applying 722f4667af76
313 722f4667af76 transplanted to 0aa6979afb95
313 722f4667af76 transplanted to 0aa6979afb95
314 filtering * (glob)
314 filtering * (glob)
315 applying a53251cdf717
315 applying a53251cdf717
316 a53251cdf717 transplanted to 14f8512272b5
316 a53251cdf717 transplanted to 14f8512272b5
317 $ hg log --template '{rev} {parents} {desc}\n'
317 $ hg log --template '{rev} {parents} {desc}\n'
318 3 b3
318 3 b3
319 2 b2
319 2 b2
320 1 b1
320 1 b1
321 0 r2
321 0 r2
322 $ cd ..
322 $ cd ..
323
323
324
324
325 test filter with failed patch
325 test filter with failed patch
326
326
327 $ cd filter
327 $ cd filter
328 $ hg up 0
328 $ hg up 0
329 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
329 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
330 $ echo foo > b1
330 $ echo foo > b1
331 $ hg ci -Am foo
331 $ hg ci -Am foo
332 adding b1
332 adding b1
333 adding test-filter
333 adding test-filter
334 created new head
334 created new head
335 $ hg transplant 1 --filter ./test-filter
335 $ hg transplant 1 --filter ./test-filter
336 filtering * (glob)
336 filtering * (glob)
337 applying 348b36d0b6a5
337 applying 348b36d0b6a5
338 file b1 already exists
338 file b1 already exists
339 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
339 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
340 patch failed to apply
340 patch failed to apply
341 abort: fix up the merge and run hg transplant --continue
341 abort: fix up the merge and run hg transplant --continue
342 [255]
342 [255]
343 $ cd ..
343 $ cd ..
344
344
345 test environment passed to filter
345 test environment passed to filter
346
346
347 $ hg init filter-environment
347 $ hg init filter-environment
348 $ cd filter-environment
348 $ cd filter-environment
349 $ cat <<'EOF' >test-filter-environment
349 $ cat <<'EOF' >test-filter-environment
350 > #!/bin/sh
350 > #!/bin/sh
351 > echo "Transplant by $HGUSER" >> $1
351 > echo "Transplant by $HGUSER" >> $1
352 > echo "Transplant from rev $HGREVISION" >> $1
352 > echo "Transplant from rev $HGREVISION" >> $1
353 > EOF
353 > EOF
354 $ chmod +x test-filter-environment
354 $ chmod +x test-filter-environment
355 $ hg transplant -s ../t --filter ./test-filter-environment 0
355 $ hg transplant -s ../t --filter ./test-filter-environment 0
356 filtering * (glob)
356 filtering * (glob)
357 applying 17ab29e464c6
357 applying 17ab29e464c6
358 17ab29e464c6 transplanted to 5190e68026a0
358 17ab29e464c6 transplanted to 5190e68026a0
359
359
360 $ hg log --template '{rev} {parents} {desc}\n'
360 $ hg log --template '{rev} {parents} {desc}\n'
361 0 r1
361 0 r1
362 Transplant by test
362 Transplant by test
363 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
363 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
364 $ cd ..
364 $ cd ..
365
365
366 test transplant with filter handles invalid changelog
367
368 $ hg init filter-invalid-log
369 $ cd filter-invalid-log
370 $ cat <<'EOF' >test-filter-invalid-log
371 > #!/bin/sh
372 > echo "" > $1
373 > EOF
374 $ chmod +x test-filter-invalid-log
375 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
376 filtering * (glob)
377 abort: filter failed
378 [255]
366
379
367 test with a win32ext like setup (differing EOLs)
380 test with a win32ext like setup (differing EOLs)
368
381
369 $ hg init twin1
382 $ hg init twin1
370 $ cd twin1
383 $ cd twin1
371 $ echo a > a
384 $ echo a > a
372 $ echo b > b
385 $ echo b > b
373 $ echo b >> b
386 $ echo b >> b
374 $ hg ci -Am t
387 $ hg ci -Am t
375 adding a
388 adding a
376 adding b
389 adding b
377 $ echo a > b
390 $ echo a > b
378 $ echo b >> b
391 $ echo b >> b
379 $ hg ci -m changeb
392 $ hg ci -m changeb
380 $ cd ..
393 $ cd ..
381
394
382 $ hg init twin2
395 $ hg init twin2
383 $ cd twin2
396 $ cd twin2
384 $ echo '[patch]' >> .hg/hgrc
397 $ echo '[patch]' >> .hg/hgrc
385 $ echo 'eol = crlf' >> .hg/hgrc
398 $ echo 'eol = crlf' >> .hg/hgrc
386 $ python -c "file('b', 'wb').write('b\r\nb\r\n')"
399 $ python -c "file('b', 'wb').write('b\r\nb\r\n')"
387 $ hg ci -m addb
400 $ hg ci -m addb
388 nothing changed
401 nothing changed
389 [1]
402 [1]
390 $ hg transplant -s ../twin1 tip
403 $ hg transplant -s ../twin1 tip
391 applying 2e849d776c17
404 applying 2e849d776c17
392 2e849d776c17 transplanted to 589cea8ba85b
405 2e849d776c17 transplanted to 589cea8ba85b
393 $ cat b
406 $ cat b
394 a\r (esc)
407 a\r (esc)
395 b\r (esc)
408 b\r (esc)
396 $ cd ..
409 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now