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