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