##// END OF EJS Templates
transplant: restore dirstate correctly at unexpected failure...
FUJIWARA Katsunori -
r25879:99e88320 stable
parent child Browse files
Show More
@@ -1,710 +1,716 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant changes to another parent revision,
10 This extension allows you to transplant changes to another parent revision,
11 possibly in another repository. The transplant is done using 'diff' patches.
11 possibly in another repository. The transplant is done using 'diff' patches.
12
12
13 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 Transplanted patches are recorded in .hg/transplant/transplants, as a
14 map from a changeset hash to its hash in the source repository.
14 map from a changeset hash to its hash in the source repository.
15 '''
15 '''
16
16
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18 import os, tempfile
18 import os, tempfile
19 from mercurial.node import short
19 from mercurial.node import short
20 from mercurial import bundlerepo, hg, merge, match
20 from mercurial import bundlerepo, hg, merge, match
21 from mercurial import patch, revlog, scmutil, util, error, cmdutil
21 from mercurial import patch, revlog, scmutil, util, error, cmdutil
22 from mercurial import revset, templatekw, exchange
22 from mercurial import revset, templatekw, exchange
23
23
24 class TransplantError(error.Abort):
24 class TransplantError(error.Abort):
25 pass
25 pass
26
26
27 cmdtable = {}
27 cmdtable = {}
28 command = cmdutil.command(cmdtable)
28 command = cmdutil.command(cmdtable)
29 # Note for extension authors: ONLY specify testedwith = 'internal' for
29 # Note for extension authors: ONLY specify testedwith = 'internal' for
30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
31 # be specifying the version(s) of Mercurial they are tested with, or
31 # be specifying the version(s) of Mercurial they are tested with, or
32 # leave the attribute unspecified.
32 # leave the attribute unspecified.
33 testedwith = 'internal'
33 testedwith = 'internal'
34
34
35 class transplantentry(object):
35 class transplantentry(object):
36 def __init__(self, lnode, rnode):
36 def __init__(self, lnode, rnode):
37 self.lnode = lnode
37 self.lnode = lnode
38 self.rnode = rnode
38 self.rnode = rnode
39
39
40 class transplants(object):
40 class transplants(object):
41 def __init__(self, path=None, transplantfile=None, opener=None):
41 def __init__(self, path=None, transplantfile=None, opener=None):
42 self.path = path
42 self.path = path
43 self.transplantfile = transplantfile
43 self.transplantfile = transplantfile
44 self.opener = opener
44 self.opener = opener
45
45
46 if not opener:
46 if not opener:
47 self.opener = scmutil.opener(self.path)
47 self.opener = scmutil.opener(self.path)
48 self.transplants = {}
48 self.transplants = {}
49 self.dirty = False
49 self.dirty = False
50 self.read()
50 self.read()
51
51
52 def read(self):
52 def read(self):
53 abspath = os.path.join(self.path, self.transplantfile)
53 abspath = os.path.join(self.path, self.transplantfile)
54 if self.transplantfile and os.path.exists(abspath):
54 if self.transplantfile and os.path.exists(abspath):
55 for line in self.opener.read(self.transplantfile).splitlines():
55 for line in self.opener.read(self.transplantfile).splitlines():
56 lnode, rnode = map(revlog.bin, line.split(':'))
56 lnode, rnode = map(revlog.bin, line.split(':'))
57 list = self.transplants.setdefault(rnode, [])
57 list = self.transplants.setdefault(rnode, [])
58 list.append(transplantentry(lnode, rnode))
58 list.append(transplantentry(lnode, rnode))
59
59
60 def write(self):
60 def write(self):
61 if self.dirty and self.transplantfile:
61 if self.dirty and self.transplantfile:
62 if not os.path.isdir(self.path):
62 if not os.path.isdir(self.path):
63 os.mkdir(self.path)
63 os.mkdir(self.path)
64 fp = self.opener(self.transplantfile, 'w')
64 fp = self.opener(self.transplantfile, 'w')
65 for list in self.transplants.itervalues():
65 for list in self.transplants.itervalues():
66 for t in list:
66 for t in list:
67 l, r = map(revlog.hex, (t.lnode, t.rnode))
67 l, r = map(revlog.hex, (t.lnode, t.rnode))
68 fp.write(l + ':' + r + '\n')
68 fp.write(l + ':' + r + '\n')
69 fp.close()
69 fp.close()
70 self.dirty = False
70 self.dirty = False
71
71
72 def get(self, rnode):
72 def get(self, rnode):
73 return self.transplants.get(rnode) or []
73 return self.transplants.get(rnode) or []
74
74
75 def set(self, lnode, rnode):
75 def set(self, lnode, rnode):
76 list = self.transplants.setdefault(rnode, [])
76 list = self.transplants.setdefault(rnode, [])
77 list.append(transplantentry(lnode, rnode))
77 list.append(transplantentry(lnode, rnode))
78 self.dirty = True
78 self.dirty = True
79
79
80 def remove(self, transplant):
80 def remove(self, transplant):
81 list = self.transplants.get(transplant.rnode)
81 list = self.transplants.get(transplant.rnode)
82 if list:
82 if list:
83 del list[list.index(transplant)]
83 del list[list.index(transplant)]
84 self.dirty = True
84 self.dirty = True
85
85
86 class transplanter(object):
86 class transplanter(object):
87 def __init__(self, ui, repo, opts):
87 def __init__(self, ui, repo, opts):
88 self.ui = ui
88 self.ui = ui
89 self.path = repo.join('transplant')
89 self.path = repo.join('transplant')
90 self.opener = scmutil.opener(self.path)
90 self.opener = scmutil.opener(self.path)
91 self.transplants = transplants(self.path, 'transplants',
91 self.transplants = transplants(self.path, 'transplants',
92 opener=self.opener)
92 opener=self.opener)
93 def getcommiteditor():
93 def getcommiteditor():
94 editform = cmdutil.mergeeditform(repo[None], 'transplant')
94 editform = cmdutil.mergeeditform(repo[None], 'transplant')
95 return cmdutil.getcommiteditor(editform=editform, **opts)
95 return cmdutil.getcommiteditor(editform=editform, **opts)
96 self.getcommiteditor = getcommiteditor
96 self.getcommiteditor = getcommiteditor
97
97
98 def applied(self, repo, node, parent):
98 def applied(self, repo, node, parent):
99 '''returns True if a node is already an ancestor of parent
99 '''returns True if a node is already an ancestor of parent
100 or is parent or has already been transplanted'''
100 or is parent or has already been transplanted'''
101 if hasnode(repo, parent):
101 if hasnode(repo, parent):
102 parentrev = repo.changelog.rev(parent)
102 parentrev = repo.changelog.rev(parent)
103 if hasnode(repo, node):
103 if hasnode(repo, node):
104 rev = repo.changelog.rev(node)
104 rev = repo.changelog.rev(node)
105 reachable = repo.changelog.ancestors([parentrev], rev,
105 reachable = repo.changelog.ancestors([parentrev], rev,
106 inclusive=True)
106 inclusive=True)
107 if rev in reachable:
107 if rev in reachable:
108 return True
108 return True
109 for t in self.transplants.get(node):
109 for t in self.transplants.get(node):
110 # it might have been stripped
110 # it might have been stripped
111 if not hasnode(repo, t.lnode):
111 if not hasnode(repo, t.lnode):
112 self.transplants.remove(t)
112 self.transplants.remove(t)
113 return False
113 return False
114 lnoderev = repo.changelog.rev(t.lnode)
114 lnoderev = repo.changelog.rev(t.lnode)
115 if lnoderev in repo.changelog.ancestors([parentrev], lnoderev,
115 if lnoderev in repo.changelog.ancestors([parentrev], lnoderev,
116 inclusive=True):
116 inclusive=True):
117 return True
117 return True
118 return False
118 return False
119
119
120 def apply(self, repo, source, revmap, merges, opts={}):
120 def apply(self, repo, source, revmap, merges, opts={}):
121 '''apply the revisions in revmap one by one in revision order'''
121 '''apply the revisions in revmap one by one in revision order'''
122 revs = sorted(revmap)
122 revs = sorted(revmap)
123 p1, p2 = repo.dirstate.parents()
123 p1, p2 = repo.dirstate.parents()
124 pulls = []
124 pulls = []
125 diffopts = patch.difffeatureopts(self.ui, opts)
125 diffopts = patch.difffeatureopts(self.ui, opts)
126 diffopts.git = True
126 diffopts.git = True
127
127
128 lock = wlock = tr = None
128 lock = wlock = tr = dsguard = None
129 try:
129 try:
130 wlock = repo.wlock()
130 wlock = repo.wlock()
131 dsguard = cmdutil.dirstateguard(repo, 'transplant')
131 lock = repo.lock()
132 lock = repo.lock()
132 tr = repo.transaction('transplant')
133 tr = repo.transaction('transplant')
133 for rev in revs:
134 for rev in revs:
134 node = revmap[rev]
135 node = revmap[rev]
135 revstr = '%s:%s' % (rev, short(node))
136 revstr = '%s:%s' % (rev, short(node))
136
137
137 if self.applied(repo, node, p1):
138 if self.applied(repo, node, p1):
138 self.ui.warn(_('skipping already applied revision %s\n') %
139 self.ui.warn(_('skipping already applied revision %s\n') %
139 revstr)
140 revstr)
140 continue
141 continue
141
142
142 parents = source.changelog.parents(node)
143 parents = source.changelog.parents(node)
143 if not (opts.get('filter') or opts.get('log')):
144 if not (opts.get('filter') or opts.get('log')):
144 # If the changeset parent is the same as the
145 # If the changeset parent is the same as the
145 # wdir's parent, just pull it.
146 # wdir's parent, just pull it.
146 if parents[0] == p1:
147 if parents[0] == p1:
147 pulls.append(node)
148 pulls.append(node)
148 p1 = node
149 p1 = node
149 continue
150 continue
150 if pulls:
151 if pulls:
151 if source != repo:
152 if source != repo:
152 exchange.pull(repo, source.peer(), heads=pulls)
153 exchange.pull(repo, source.peer(), heads=pulls)
153 merge.update(repo, pulls[-1], False, False, None)
154 merge.update(repo, pulls[-1], False, False, None)
154 p1, p2 = repo.dirstate.parents()
155 p1, p2 = repo.dirstate.parents()
155 pulls = []
156 pulls = []
156
157
157 domerge = False
158 domerge = False
158 if node in merges:
159 if node in merges:
159 # pulling all the merge revs at once would mean we
160 # pulling all the merge revs at once would mean we
160 # couldn't transplant after the latest even if
161 # couldn't transplant after the latest even if
161 # transplants before them fail.
162 # transplants before them fail.
162 domerge = True
163 domerge = True
163 if not hasnode(repo, node):
164 if not hasnode(repo, node):
164 exchange.pull(repo, source.peer(), heads=[node])
165 exchange.pull(repo, source.peer(), heads=[node])
165
166
166 skipmerge = False
167 skipmerge = False
167 if parents[1] != revlog.nullid:
168 if parents[1] != revlog.nullid:
168 if not opts.get('parent'):
169 if not opts.get('parent'):
169 self.ui.note(_('skipping merge changeset %s:%s\n')
170 self.ui.note(_('skipping merge changeset %s:%s\n')
170 % (rev, short(node)))
171 % (rev, short(node)))
171 skipmerge = True
172 skipmerge = True
172 else:
173 else:
173 parent = source.lookup(opts['parent'])
174 parent = source.lookup(opts['parent'])
174 if parent not in parents:
175 if parent not in parents:
175 raise util.Abort(_('%s is not a parent of %s') %
176 raise util.Abort(_('%s is not a parent of %s') %
176 (short(parent), short(node)))
177 (short(parent), short(node)))
177 else:
178 else:
178 parent = parents[0]
179 parent = parents[0]
179
180
180 if skipmerge:
181 if skipmerge:
181 patchfile = None
182 patchfile = None
182 else:
183 else:
183 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
184 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
184 fp = os.fdopen(fd, 'w')
185 fp = os.fdopen(fd, 'w')
185 gen = patch.diff(source, parent, node, opts=diffopts)
186 gen = patch.diff(source, parent, node, opts=diffopts)
186 for chunk in gen:
187 for chunk in gen:
187 fp.write(chunk)
188 fp.write(chunk)
188 fp.close()
189 fp.close()
189
190
190 del revmap[rev]
191 del revmap[rev]
191 if patchfile or domerge:
192 if patchfile or domerge:
192 try:
193 try:
193 try:
194 try:
194 n = self.applyone(repo, node,
195 n = self.applyone(repo, node,
195 source.changelog.read(node),
196 source.changelog.read(node),
196 patchfile, merge=domerge,
197 patchfile, merge=domerge,
197 log=opts.get('log'),
198 log=opts.get('log'),
198 filter=opts.get('filter'))
199 filter=opts.get('filter'))
199 except TransplantError:
200 except TransplantError:
200 # Do not rollback, it is up to the user to
201 # Do not rollback, it is up to the user to
201 # fix the merge or cancel everything
202 # fix the merge or cancel everything
202 tr.close()
203 tr.close()
204 dsguard.close()
203 raise
205 raise
204 if n and domerge:
206 if n and domerge:
205 self.ui.status(_('%s merged at %s\n') % (revstr,
207 self.ui.status(_('%s merged at %s\n') % (revstr,
206 short(n)))
208 short(n)))
207 elif n:
209 elif n:
208 self.ui.status(_('%s transplanted to %s\n')
210 self.ui.status(_('%s transplanted to %s\n')
209 % (short(node),
211 % (short(node),
210 short(n)))
212 short(n)))
211 finally:
213 finally:
212 if patchfile:
214 if patchfile:
213 os.unlink(patchfile)
215 os.unlink(patchfile)
214 tr.close()
216 tr.close()
217 dsguard.close()
215 if pulls:
218 if pulls:
216 exchange.pull(repo, source.peer(), heads=pulls)
219 exchange.pull(repo, source.peer(), heads=pulls)
217 merge.update(repo, pulls[-1], False, False, None)
220 merge.update(repo, pulls[-1], False, False, None)
218 finally:
221 finally:
219 self.saveseries(revmap, merges)
222 self.saveseries(revmap, merges)
220 self.transplants.write()
223 self.transplants.write()
221 if tr:
224 if tr:
222 tr.release()
225 tr.release()
223 lock.release()
226 if lock:
227 lock.release()
228 if dsguard:
229 dsguard.release()
224 wlock.release()
230 wlock.release()
225
231
226 def filter(self, filter, node, changelog, patchfile):
232 def filter(self, filter, node, changelog, patchfile):
227 '''arbitrarily rewrite changeset before applying it'''
233 '''arbitrarily rewrite changeset before applying it'''
228
234
229 self.ui.status(_('filtering %s\n') % patchfile)
235 self.ui.status(_('filtering %s\n') % patchfile)
230 user, date, msg = (changelog[1], changelog[2], changelog[4])
236 user, date, msg = (changelog[1], changelog[2], changelog[4])
231 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
237 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
232 fp = os.fdopen(fd, 'w')
238 fp = os.fdopen(fd, 'w')
233 fp.write("# HG changeset patch\n")
239 fp.write("# HG changeset patch\n")
234 fp.write("# User %s\n" % user)
240 fp.write("# User %s\n" % user)
235 fp.write("# Date %d %d\n" % date)
241 fp.write("# Date %d %d\n" % date)
236 fp.write(msg + '\n')
242 fp.write(msg + '\n')
237 fp.close()
243 fp.close()
238
244
239 try:
245 try:
240 self.ui.system('%s %s %s' % (filter, util.shellquote(headerfile),
246 self.ui.system('%s %s %s' % (filter, util.shellquote(headerfile),
241 util.shellquote(patchfile)),
247 util.shellquote(patchfile)),
242 environ={'HGUSER': changelog[1],
248 environ={'HGUSER': changelog[1],
243 'HGREVISION': revlog.hex(node),
249 'HGREVISION': revlog.hex(node),
244 },
250 },
245 onerr=util.Abort, errprefix=_('filter failed'))
251 onerr=util.Abort, errprefix=_('filter failed'))
246 user, date, msg = self.parselog(file(headerfile))[1:4]
252 user, date, msg = self.parselog(file(headerfile))[1:4]
247 finally:
253 finally:
248 os.unlink(headerfile)
254 os.unlink(headerfile)
249
255
250 return (user, date, msg)
256 return (user, date, msg)
251
257
252 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
258 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
253 filter=None):
259 filter=None):
254 '''apply the patch in patchfile to the repository as a transplant'''
260 '''apply the patch in patchfile to the repository as a transplant'''
255 (manifest, user, (time, timezone), files, message) = cl[:5]
261 (manifest, user, (time, timezone), files, message) = cl[:5]
256 date = "%d %d" % (time, timezone)
262 date = "%d %d" % (time, timezone)
257 extra = {'transplant_source': node}
263 extra = {'transplant_source': node}
258 if filter:
264 if filter:
259 (user, date, message) = self.filter(filter, node, cl, patchfile)
265 (user, date, message) = self.filter(filter, node, cl, patchfile)
260
266
261 if log:
267 if log:
262 # we don't translate messages inserted into commits
268 # we don't translate messages inserted into commits
263 message += '\n(transplanted from %s)' % revlog.hex(node)
269 message += '\n(transplanted from %s)' % revlog.hex(node)
264
270
265 self.ui.status(_('applying %s\n') % short(node))
271 self.ui.status(_('applying %s\n') % short(node))
266 self.ui.note('%s %s\n%s\n' % (user, date, message))
272 self.ui.note('%s %s\n%s\n' % (user, date, message))
267
273
268 if not patchfile and not merge:
274 if not patchfile and not merge:
269 raise util.Abort(_('can only omit patchfile if merging'))
275 raise util.Abort(_('can only omit patchfile if merging'))
270 if patchfile:
276 if patchfile:
271 try:
277 try:
272 files = set()
278 files = set()
273 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
279 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
274 files = list(files)
280 files = list(files)
275 except Exception as inst:
281 except Exception as inst:
276 seriespath = os.path.join(self.path, 'series')
282 seriespath = os.path.join(self.path, 'series')
277 if os.path.exists(seriespath):
283 if os.path.exists(seriespath):
278 os.unlink(seriespath)
284 os.unlink(seriespath)
279 p1 = repo.dirstate.p1()
285 p1 = repo.dirstate.p1()
280 p2 = node
286 p2 = node
281 self.log(user, date, message, p1, p2, merge=merge)
287 self.log(user, date, message, p1, p2, merge=merge)
282 self.ui.write(str(inst) + '\n')
288 self.ui.write(str(inst) + '\n')
283 raise TransplantError(_('fix up the merge and run '
289 raise TransplantError(_('fix up the merge and run '
284 'hg transplant --continue'))
290 'hg transplant --continue'))
285 else:
291 else:
286 files = None
292 files = None
287 if merge:
293 if merge:
288 p1, p2 = repo.dirstate.parents()
294 p1, p2 = repo.dirstate.parents()
289 repo.setparents(p1, node)
295 repo.setparents(p1, node)
290 m = match.always(repo.root, '')
296 m = match.always(repo.root, '')
291 else:
297 else:
292 m = match.exact(repo.root, '', files)
298 m = match.exact(repo.root, '', files)
293
299
294 n = repo.commit(message, user, date, extra=extra, match=m,
300 n = repo.commit(message, user, date, extra=extra, match=m,
295 editor=self.getcommiteditor())
301 editor=self.getcommiteditor())
296 if not n:
302 if not n:
297 self.ui.warn(_('skipping emptied changeset %s\n') % short(node))
303 self.ui.warn(_('skipping emptied changeset %s\n') % short(node))
298 return None
304 return None
299 if not merge:
305 if not merge:
300 self.transplants.set(n, node)
306 self.transplants.set(n, node)
301
307
302 return n
308 return n
303
309
304 def resume(self, repo, source, opts):
310 def resume(self, repo, source, opts):
305 '''recover last transaction and apply remaining changesets'''
311 '''recover last transaction and apply remaining changesets'''
306 if os.path.exists(os.path.join(self.path, 'journal')):
312 if os.path.exists(os.path.join(self.path, 'journal')):
307 n, node = self.recover(repo, source, opts)
313 n, node = self.recover(repo, source, opts)
308 if n:
314 if n:
309 self.ui.status(_('%s transplanted as %s\n') % (short(node),
315 self.ui.status(_('%s transplanted as %s\n') % (short(node),
310 short(n)))
316 short(n)))
311 else:
317 else:
312 self.ui.status(_('%s skipped due to empty diff\n')
318 self.ui.status(_('%s skipped due to empty diff\n')
313 % (short(node),))
319 % (short(node),))
314 seriespath = os.path.join(self.path, 'series')
320 seriespath = os.path.join(self.path, 'series')
315 if not os.path.exists(seriespath):
321 if not os.path.exists(seriespath):
316 self.transplants.write()
322 self.transplants.write()
317 return
323 return
318 nodes, merges = self.readseries()
324 nodes, merges = self.readseries()
319 revmap = {}
325 revmap = {}
320 for n in nodes:
326 for n in nodes:
321 revmap[source.changelog.rev(n)] = n
327 revmap[source.changelog.rev(n)] = n
322 os.unlink(seriespath)
328 os.unlink(seriespath)
323
329
324 self.apply(repo, source, revmap, merges, opts)
330 self.apply(repo, source, revmap, merges, opts)
325
331
326 def recover(self, repo, source, opts):
332 def recover(self, repo, source, opts):
327 '''commit working directory using journal metadata'''
333 '''commit working directory using journal metadata'''
328 node, user, date, message, parents = self.readlog()
334 node, user, date, message, parents = self.readlog()
329 merge = False
335 merge = False
330
336
331 if not user or not date or not message or not parents[0]:
337 if not user or not date or not message or not parents[0]:
332 raise util.Abort(_('transplant log file is corrupt'))
338 raise util.Abort(_('transplant log file is corrupt'))
333
339
334 parent = parents[0]
340 parent = parents[0]
335 if len(parents) > 1:
341 if len(parents) > 1:
336 if opts.get('parent'):
342 if opts.get('parent'):
337 parent = source.lookup(opts['parent'])
343 parent = source.lookup(opts['parent'])
338 if parent not in parents:
344 if parent not in parents:
339 raise util.Abort(_('%s is not a parent of %s') %
345 raise util.Abort(_('%s is not a parent of %s') %
340 (short(parent), short(node)))
346 (short(parent), short(node)))
341 else:
347 else:
342 merge = True
348 merge = True
343
349
344 extra = {'transplant_source': node}
350 extra = {'transplant_source': node}
345 wlock = repo.wlock()
351 wlock = repo.wlock()
346 try:
352 try:
347 p1, p2 = repo.dirstate.parents()
353 p1, p2 = repo.dirstate.parents()
348 if p1 != parent:
354 if p1 != parent:
349 raise util.Abort(_('working directory not at transplant '
355 raise util.Abort(_('working directory not at transplant '
350 'parent %s') % revlog.hex(parent))
356 'parent %s') % revlog.hex(parent))
351 if merge:
357 if merge:
352 repo.setparents(p1, parents[1])
358 repo.setparents(p1, parents[1])
353 modified, added, removed, deleted = repo.status()[:4]
359 modified, added, removed, deleted = repo.status()[:4]
354 if merge or modified or added or removed or deleted:
360 if merge or modified or added or removed or deleted:
355 n = repo.commit(message, user, date, extra=extra,
361 n = repo.commit(message, user, date, extra=extra,
356 editor=self.getcommiteditor())
362 editor=self.getcommiteditor())
357 if not n:
363 if not n:
358 raise util.Abort(_('commit failed'))
364 raise util.Abort(_('commit failed'))
359 if not merge:
365 if not merge:
360 self.transplants.set(n, node)
366 self.transplants.set(n, node)
361 else:
367 else:
362 n = None
368 n = None
363 self.unlog()
369 self.unlog()
364
370
365 return n, node
371 return n, node
366 finally:
372 finally:
367 wlock.release()
373 wlock.release()
368
374
369 def readseries(self):
375 def readseries(self):
370 nodes = []
376 nodes = []
371 merges = []
377 merges = []
372 cur = nodes
378 cur = nodes
373 for line in self.opener.read('series').splitlines():
379 for line in self.opener.read('series').splitlines():
374 if line.startswith('# Merges'):
380 if line.startswith('# Merges'):
375 cur = merges
381 cur = merges
376 continue
382 continue
377 cur.append(revlog.bin(line))
383 cur.append(revlog.bin(line))
378
384
379 return (nodes, merges)
385 return (nodes, merges)
380
386
381 def saveseries(self, revmap, merges):
387 def saveseries(self, revmap, merges):
382 if not revmap:
388 if not revmap:
383 return
389 return
384
390
385 if not os.path.isdir(self.path):
391 if not os.path.isdir(self.path):
386 os.mkdir(self.path)
392 os.mkdir(self.path)
387 series = self.opener('series', 'w')
393 series = self.opener('series', 'w')
388 for rev in sorted(revmap):
394 for rev in sorted(revmap):
389 series.write(revlog.hex(revmap[rev]) + '\n')
395 series.write(revlog.hex(revmap[rev]) + '\n')
390 if merges:
396 if merges:
391 series.write('# Merges\n')
397 series.write('# Merges\n')
392 for m in merges:
398 for m in merges:
393 series.write(revlog.hex(m) + '\n')
399 series.write(revlog.hex(m) + '\n')
394 series.close()
400 series.close()
395
401
396 def parselog(self, fp):
402 def parselog(self, fp):
397 parents = []
403 parents = []
398 message = []
404 message = []
399 node = revlog.nullid
405 node = revlog.nullid
400 inmsg = False
406 inmsg = False
401 user = None
407 user = None
402 date = None
408 date = None
403 for line in fp.read().splitlines():
409 for line in fp.read().splitlines():
404 if inmsg:
410 if inmsg:
405 message.append(line)
411 message.append(line)
406 elif line.startswith('# User '):
412 elif line.startswith('# User '):
407 user = line[7:]
413 user = line[7:]
408 elif line.startswith('# Date '):
414 elif line.startswith('# Date '):
409 date = line[7:]
415 date = line[7:]
410 elif line.startswith('# Node ID '):
416 elif line.startswith('# Node ID '):
411 node = revlog.bin(line[10:])
417 node = revlog.bin(line[10:])
412 elif line.startswith('# Parent '):
418 elif line.startswith('# Parent '):
413 parents.append(revlog.bin(line[9:]))
419 parents.append(revlog.bin(line[9:]))
414 elif not line.startswith('# '):
420 elif not line.startswith('# '):
415 inmsg = True
421 inmsg = True
416 message.append(line)
422 message.append(line)
417 if None in (user, date):
423 if None in (user, date):
418 raise util.Abort(_("filter corrupted changeset (no user or date)"))
424 raise util.Abort(_("filter corrupted changeset (no user or date)"))
419 return (node, user, date, '\n'.join(message), parents)
425 return (node, user, date, '\n'.join(message), parents)
420
426
421 def log(self, user, date, message, p1, p2, merge=False):
427 def log(self, user, date, message, p1, p2, merge=False):
422 '''journal changelog metadata for later recover'''
428 '''journal changelog metadata for later recover'''
423
429
424 if not os.path.isdir(self.path):
430 if not os.path.isdir(self.path):
425 os.mkdir(self.path)
431 os.mkdir(self.path)
426 fp = self.opener('journal', 'w')
432 fp = self.opener('journal', 'w')
427 fp.write('# User %s\n' % user)
433 fp.write('# User %s\n' % user)
428 fp.write('# Date %s\n' % date)
434 fp.write('# Date %s\n' % date)
429 fp.write('# Node ID %s\n' % revlog.hex(p2))
435 fp.write('# Node ID %s\n' % revlog.hex(p2))
430 fp.write('# Parent ' + revlog.hex(p1) + '\n')
436 fp.write('# Parent ' + revlog.hex(p1) + '\n')
431 if merge:
437 if merge:
432 fp.write('# Parent ' + revlog.hex(p2) + '\n')
438 fp.write('# Parent ' + revlog.hex(p2) + '\n')
433 fp.write(message.rstrip() + '\n')
439 fp.write(message.rstrip() + '\n')
434 fp.close()
440 fp.close()
435
441
436 def readlog(self):
442 def readlog(self):
437 return self.parselog(self.opener('journal'))
443 return self.parselog(self.opener('journal'))
438
444
439 def unlog(self):
445 def unlog(self):
440 '''remove changelog journal'''
446 '''remove changelog journal'''
441 absdst = os.path.join(self.path, 'journal')
447 absdst = os.path.join(self.path, 'journal')
442 if os.path.exists(absdst):
448 if os.path.exists(absdst):
443 os.unlink(absdst)
449 os.unlink(absdst)
444
450
445 def transplantfilter(self, repo, source, root):
451 def transplantfilter(self, repo, source, root):
446 def matchfn(node):
452 def matchfn(node):
447 if self.applied(repo, node, root):
453 if self.applied(repo, node, root):
448 return False
454 return False
449 if source.changelog.parents(node)[1] != revlog.nullid:
455 if source.changelog.parents(node)[1] != revlog.nullid:
450 return False
456 return False
451 extra = source.changelog.read(node)[5]
457 extra = source.changelog.read(node)[5]
452 cnode = extra.get('transplant_source')
458 cnode = extra.get('transplant_source')
453 if cnode and self.applied(repo, cnode, root):
459 if cnode and self.applied(repo, cnode, root):
454 return False
460 return False
455 return True
461 return True
456
462
457 return matchfn
463 return matchfn
458
464
459 def hasnode(repo, node):
465 def hasnode(repo, node):
460 try:
466 try:
461 return repo.changelog.rev(node) is not None
467 return repo.changelog.rev(node) is not None
462 except error.RevlogError:
468 except error.RevlogError:
463 return False
469 return False
464
470
465 def browserevs(ui, repo, nodes, opts):
471 def browserevs(ui, repo, nodes, opts):
466 '''interactively transplant changesets'''
472 '''interactively transplant changesets'''
467 displayer = cmdutil.show_changeset(ui, repo, opts)
473 displayer = cmdutil.show_changeset(ui, repo, opts)
468 transplants = []
474 transplants = []
469 merges = []
475 merges = []
470 prompt = _('apply changeset? [ynmpcq?]:'
476 prompt = _('apply changeset? [ynmpcq?]:'
471 '$$ &yes, transplant this changeset'
477 '$$ &yes, transplant this changeset'
472 '$$ &no, skip this changeset'
478 '$$ &no, skip this changeset'
473 '$$ &merge at this changeset'
479 '$$ &merge at this changeset'
474 '$$ show &patch'
480 '$$ show &patch'
475 '$$ &commit selected changesets'
481 '$$ &commit selected changesets'
476 '$$ &quit and cancel transplant'
482 '$$ &quit and cancel transplant'
477 '$$ &? (show this help)')
483 '$$ &? (show this help)')
478 for node in nodes:
484 for node in nodes:
479 displayer.show(repo[node])
485 displayer.show(repo[node])
480 action = None
486 action = None
481 while not action:
487 while not action:
482 action = 'ynmpcq?'[ui.promptchoice(prompt)]
488 action = 'ynmpcq?'[ui.promptchoice(prompt)]
483 if action == '?':
489 if action == '?':
484 for c, t in ui.extractchoices(prompt)[1]:
490 for c, t in ui.extractchoices(prompt)[1]:
485 ui.write('%s: %s\n' % (c, t))
491 ui.write('%s: %s\n' % (c, t))
486 action = None
492 action = None
487 elif action == 'p':
493 elif action == 'p':
488 parent = repo.changelog.parents(node)[0]
494 parent = repo.changelog.parents(node)[0]
489 for chunk in patch.diff(repo, parent, node):
495 for chunk in patch.diff(repo, parent, node):
490 ui.write(chunk)
496 ui.write(chunk)
491 action = None
497 action = None
492 if action == 'y':
498 if action == 'y':
493 transplants.append(node)
499 transplants.append(node)
494 elif action == 'm':
500 elif action == 'm':
495 merges.append(node)
501 merges.append(node)
496 elif action == 'c':
502 elif action == 'c':
497 break
503 break
498 elif action == 'q':
504 elif action == 'q':
499 transplants = ()
505 transplants = ()
500 merges = ()
506 merges = ()
501 break
507 break
502 displayer.close()
508 displayer.close()
503 return (transplants, merges)
509 return (transplants, merges)
504
510
505 @command('transplant',
511 @command('transplant',
506 [('s', 'source', '', _('transplant changesets from REPO'), _('REPO')),
512 [('s', 'source', '', _('transplant changesets from REPO'), _('REPO')),
507 ('b', 'branch', [], _('use this source changeset as head'), _('REV')),
513 ('b', 'branch', [], _('use this source changeset as head'), _('REV')),
508 ('a', 'all', None, _('pull all changesets up to the --branch revisions')),
514 ('a', 'all', None, _('pull all changesets up to the --branch revisions')),
509 ('p', 'prune', [], _('skip over REV'), _('REV')),
515 ('p', 'prune', [], _('skip over REV'), _('REV')),
510 ('m', 'merge', [], _('merge at REV'), _('REV')),
516 ('m', 'merge', [], _('merge at REV'), _('REV')),
511 ('', 'parent', '',
517 ('', 'parent', '',
512 _('parent to choose when transplanting merge'), _('REV')),
518 _('parent to choose when transplanting merge'), _('REV')),
513 ('e', 'edit', False, _('invoke editor on commit messages')),
519 ('e', 'edit', False, _('invoke editor on commit messages')),
514 ('', 'log', None, _('append transplant info to log message')),
520 ('', 'log', None, _('append transplant info to log message')),
515 ('c', 'continue', None, _('continue last transplant session '
521 ('c', 'continue', None, _('continue last transplant session '
516 'after fixing conflicts')),
522 'after fixing conflicts')),
517 ('', 'filter', '',
523 ('', 'filter', '',
518 _('filter changesets through command'), _('CMD'))],
524 _('filter changesets through command'), _('CMD'))],
519 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
525 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
520 '[-m REV] [REV]...'))
526 '[-m REV] [REV]...'))
521 def transplant(ui, repo, *revs, **opts):
527 def transplant(ui, repo, *revs, **opts):
522 '''transplant changesets from another branch
528 '''transplant changesets from another branch
523
529
524 Selected changesets will be applied on top of the current working
530 Selected changesets will be applied on top of the current working
525 directory with the log of the original changeset. The changesets
531 directory with the log of the original changeset. The changesets
526 are copied and will thus appear twice in the history with different
532 are copied and will thus appear twice in the history with different
527 identities.
533 identities.
528
534
529 Consider using the graft command if everything is inside the same
535 Consider using the graft command if everything is inside the same
530 repository - it will use merges and will usually give a better result.
536 repository - it will use merges and will usually give a better result.
531 Use the rebase extension if the changesets are unpublished and you want
537 Use the rebase extension if the changesets are unpublished and you want
532 to move them instead of copying them.
538 to move them instead of copying them.
533
539
534 If --log is specified, log messages will have a comment appended
540 If --log is specified, log messages will have a comment appended
535 of the form::
541 of the form::
536
542
537 (transplanted from CHANGESETHASH)
543 (transplanted from CHANGESETHASH)
538
544
539 You can rewrite the changelog message with the --filter option.
545 You can rewrite the changelog message with the --filter option.
540 Its argument will be invoked with the current changelog message as
546 Its argument will be invoked with the current changelog message as
541 $1 and the patch as $2.
547 $1 and the patch as $2.
542
548
543 --source/-s specifies another repository to use for selecting changesets,
549 --source/-s specifies another repository to use for selecting changesets,
544 just as if it temporarily had been pulled.
550 just as if it temporarily had been pulled.
545 If --branch/-b is specified, these revisions will be used as
551 If --branch/-b is specified, these revisions will be used as
546 heads when deciding which changesets to transplant, just as if only
552 heads when deciding which changesets to transplant, just as if only
547 these revisions had been pulled.
553 these revisions had been pulled.
548 If --all/-a is specified, all the revisions up to the heads specified
554 If --all/-a is specified, all the revisions up to the heads specified
549 with --branch will be transplanted.
555 with --branch will be transplanted.
550
556
551 Example:
557 Example:
552
558
553 - transplant all changes up to REV on top of your current revision::
559 - transplant all changes up to REV on top of your current revision::
554
560
555 hg transplant --branch REV --all
561 hg transplant --branch REV --all
556
562
557 You can optionally mark selected transplanted changesets as merge
563 You can optionally mark selected transplanted changesets as merge
558 changesets. You will not be prompted to transplant any ancestors
564 changesets. You will not be prompted to transplant any ancestors
559 of a merged transplant, and you can merge descendants of them
565 of a merged transplant, and you can merge descendants of them
560 normally instead of transplanting them.
566 normally instead of transplanting them.
561
567
562 Merge changesets may be transplanted directly by specifying the
568 Merge changesets may be transplanted directly by specifying the
563 proper parent changeset by calling :hg:`transplant --parent`.
569 proper parent changeset by calling :hg:`transplant --parent`.
564
570
565 If no merges or revisions are provided, :hg:`transplant` will
571 If no merges or revisions are provided, :hg:`transplant` will
566 start an interactive changeset browser.
572 start an interactive changeset browser.
567
573
568 If a changeset application fails, you can fix the merge by hand
574 If a changeset application fails, you can fix the merge by hand
569 and then resume where you left off by calling :hg:`transplant
575 and then resume where you left off by calling :hg:`transplant
570 --continue/-c`.
576 --continue/-c`.
571 '''
577 '''
572 def incwalk(repo, csets, match=util.always):
578 def incwalk(repo, csets, match=util.always):
573 for node in csets:
579 for node in csets:
574 if match(node):
580 if match(node):
575 yield node
581 yield node
576
582
577 def transplantwalk(repo, dest, heads, match=util.always):
583 def transplantwalk(repo, dest, heads, match=util.always):
578 '''Yield all nodes that are ancestors of a head but not ancestors
584 '''Yield all nodes that are ancestors of a head but not ancestors
579 of dest.
585 of dest.
580 If no heads are specified, the heads of repo will be used.'''
586 If no heads are specified, the heads of repo will be used.'''
581 if not heads:
587 if not heads:
582 heads = repo.heads()
588 heads = repo.heads()
583 ancestors = []
589 ancestors = []
584 ctx = repo[dest]
590 ctx = repo[dest]
585 for head in heads:
591 for head in heads:
586 ancestors.append(ctx.ancestor(repo[head]).node())
592 ancestors.append(ctx.ancestor(repo[head]).node())
587 for node in repo.changelog.nodesbetween(ancestors, heads)[0]:
593 for node in repo.changelog.nodesbetween(ancestors, heads)[0]:
588 if match(node):
594 if match(node):
589 yield node
595 yield node
590
596
591 def checkopts(opts, revs):
597 def checkopts(opts, revs):
592 if opts.get('continue'):
598 if opts.get('continue'):
593 if opts.get('branch') or opts.get('all') or opts.get('merge'):
599 if opts.get('branch') or opts.get('all') or opts.get('merge'):
594 raise util.Abort(_('--continue is incompatible with '
600 raise util.Abort(_('--continue is incompatible with '
595 '--branch, --all and --merge'))
601 '--branch, --all and --merge'))
596 return
602 return
597 if not (opts.get('source') or revs or
603 if not (opts.get('source') or revs or
598 opts.get('merge') or opts.get('branch')):
604 opts.get('merge') or opts.get('branch')):
599 raise util.Abort(_('no source URL, branch revision or revision '
605 raise util.Abort(_('no source URL, branch revision or revision '
600 'list provided'))
606 'list provided'))
601 if opts.get('all'):
607 if opts.get('all'):
602 if not opts.get('branch'):
608 if not opts.get('branch'):
603 raise util.Abort(_('--all requires a branch revision'))
609 raise util.Abort(_('--all requires a branch revision'))
604 if revs:
610 if revs:
605 raise util.Abort(_('--all is incompatible with a '
611 raise util.Abort(_('--all is incompatible with a '
606 'revision list'))
612 'revision list'))
607
613
608 checkopts(opts, revs)
614 checkopts(opts, revs)
609
615
610 if not opts.get('log'):
616 if not opts.get('log'):
611 # deprecated config: transplant.log
617 # deprecated config: transplant.log
612 opts['log'] = ui.config('transplant', 'log')
618 opts['log'] = ui.config('transplant', 'log')
613 if not opts.get('filter'):
619 if not opts.get('filter'):
614 # deprecated config: transplant.filter
620 # deprecated config: transplant.filter
615 opts['filter'] = ui.config('transplant', 'filter')
621 opts['filter'] = ui.config('transplant', 'filter')
616
622
617 tp = transplanter(ui, repo, opts)
623 tp = transplanter(ui, repo, opts)
618
624
619 cmdutil.checkunfinished(repo)
625 cmdutil.checkunfinished(repo)
620 p1, p2 = repo.dirstate.parents()
626 p1, p2 = repo.dirstate.parents()
621 if len(repo) > 0 and p1 == revlog.nullid:
627 if len(repo) > 0 and p1 == revlog.nullid:
622 raise util.Abort(_('no revision checked out'))
628 raise util.Abort(_('no revision checked out'))
623 if not opts.get('continue'):
629 if not opts.get('continue'):
624 if p2 != revlog.nullid:
630 if p2 != revlog.nullid:
625 raise util.Abort(_('outstanding uncommitted merges'))
631 raise util.Abort(_('outstanding uncommitted merges'))
626 m, a, r, d = repo.status()[:4]
632 m, a, r, d = repo.status()[:4]
627 if m or a or r or d:
633 if m or a or r or d:
628 raise util.Abort(_('outstanding local changes'))
634 raise util.Abort(_('outstanding local changes'))
629
635
630 sourcerepo = opts.get('source')
636 sourcerepo = opts.get('source')
631 if sourcerepo:
637 if sourcerepo:
632 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
638 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
633 heads = map(peer.lookup, opts.get('branch', ()))
639 heads = map(peer.lookup, opts.get('branch', ()))
634 target = set(heads)
640 target = set(heads)
635 for r in revs:
641 for r in revs:
636 try:
642 try:
637 target.add(peer.lookup(r))
643 target.add(peer.lookup(r))
638 except error.RepoError:
644 except error.RepoError:
639 pass
645 pass
640 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
646 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
641 onlyheads=sorted(target), force=True)
647 onlyheads=sorted(target), force=True)
642 else:
648 else:
643 source = repo
649 source = repo
644 heads = map(source.lookup, opts.get('branch', ()))
650 heads = map(source.lookup, opts.get('branch', ()))
645 cleanupfn = None
651 cleanupfn = None
646
652
647 try:
653 try:
648 if opts.get('continue'):
654 if opts.get('continue'):
649 tp.resume(repo, source, opts)
655 tp.resume(repo, source, opts)
650 return
656 return
651
657
652 tf = tp.transplantfilter(repo, source, p1)
658 tf = tp.transplantfilter(repo, source, p1)
653 if opts.get('prune'):
659 if opts.get('prune'):
654 prune = set(source.lookup(r)
660 prune = set(source.lookup(r)
655 for r in scmutil.revrange(source, opts.get('prune')))
661 for r in scmutil.revrange(source, opts.get('prune')))
656 matchfn = lambda x: tf(x) and x not in prune
662 matchfn = lambda x: tf(x) and x not in prune
657 else:
663 else:
658 matchfn = tf
664 matchfn = tf
659 merges = map(source.lookup, opts.get('merge', ()))
665 merges = map(source.lookup, opts.get('merge', ()))
660 revmap = {}
666 revmap = {}
661 if revs:
667 if revs:
662 for r in scmutil.revrange(source, revs):
668 for r in scmutil.revrange(source, revs):
663 revmap[int(r)] = source.lookup(r)
669 revmap[int(r)] = source.lookup(r)
664 elif opts.get('all') or not merges:
670 elif opts.get('all') or not merges:
665 if source != repo:
671 if source != repo:
666 alltransplants = incwalk(source, csets, match=matchfn)
672 alltransplants = incwalk(source, csets, match=matchfn)
667 else:
673 else:
668 alltransplants = transplantwalk(source, p1, heads,
674 alltransplants = transplantwalk(source, p1, heads,
669 match=matchfn)
675 match=matchfn)
670 if opts.get('all'):
676 if opts.get('all'):
671 revs = alltransplants
677 revs = alltransplants
672 else:
678 else:
673 revs, newmerges = browserevs(ui, source, alltransplants, opts)
679 revs, newmerges = browserevs(ui, source, alltransplants, opts)
674 merges.extend(newmerges)
680 merges.extend(newmerges)
675 for r in revs:
681 for r in revs:
676 revmap[source.changelog.rev(r)] = r
682 revmap[source.changelog.rev(r)] = r
677 for r in merges:
683 for r in merges:
678 revmap[source.changelog.rev(r)] = r
684 revmap[source.changelog.rev(r)] = r
679
685
680 tp.apply(repo, source, revmap, merges, opts)
686 tp.apply(repo, source, revmap, merges, opts)
681 finally:
687 finally:
682 if cleanupfn:
688 if cleanupfn:
683 cleanupfn()
689 cleanupfn()
684
690
685 def revsettransplanted(repo, subset, x):
691 def revsettransplanted(repo, subset, x):
686 """``transplanted([set])``
692 """``transplanted([set])``
687 Transplanted changesets in set, or all transplanted changesets.
693 Transplanted changesets in set, or all transplanted changesets.
688 """
694 """
689 if x:
695 if x:
690 s = revset.getset(repo, subset, x)
696 s = revset.getset(repo, subset, x)
691 else:
697 else:
692 s = subset
698 s = subset
693 return revset.baseset([r for r in s if
699 return revset.baseset([r for r in s if
694 repo[r].extra().get('transplant_source')])
700 repo[r].extra().get('transplant_source')])
695
701
696 def kwtransplanted(repo, ctx, **args):
702 def kwtransplanted(repo, ctx, **args):
697 """:transplanted: String. The node identifier of the transplanted
703 """:transplanted: String. The node identifier of the transplanted
698 changeset if any."""
704 changeset if any."""
699 n = ctx.extra().get('transplant_source')
705 n = ctx.extra().get('transplant_source')
700 return n and revlog.hex(n) or ''
706 return n and revlog.hex(n) or ''
701
707
702 def extsetup(ui):
708 def extsetup(ui):
703 revset.symbols['transplanted'] = revsettransplanted
709 revset.symbols['transplanted'] = revsettransplanted
704 templatekw.keywords['transplanted'] = kwtransplanted
710 templatekw.keywords['transplanted'] = kwtransplanted
705 cmdutil.unfinishedstates.append(
711 cmdutil.unfinishedstates.append(
706 ['series', True, False, _('transplant in progress'),
712 ['series', True, False, _('transplant in progress'),
707 _("use 'hg transplant --continue' or 'hg update' to abort")])
713 _("use 'hg transplant --continue' or 'hg update' to abort")])
708
714
709 # tell hggettext to extract docstrings from these functions:
715 # tell hggettext to extract docstrings from these functions:
710 i18nfunctions = [revsettransplanted, kwtransplanted]
716 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,880 +1,918 b''
1 #require killdaemons
1 #require killdaemons
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 (this also tests that editor is not invoked if '--edit' is not specified)
46 (this also tests that editor is not invoked if '--edit' is not specified)
47
47
48 $ HGEDITOR=cat hg transplant -a -b tip
48 $ HGEDITOR=cat hg transplant -a -b tip
49 applying 37a1297eb21b
49 applying 37a1297eb21b
50 37a1297eb21b transplanted to e234d668f844
50 37a1297eb21b transplanted to e234d668f844
51 applying 722f4667af76
51 applying 722f4667af76
52 722f4667af76 transplanted to 539f377d78df
52 722f4667af76 transplanted to 539f377d78df
53 applying a53251cdf717
53 applying a53251cdf717
54 a53251cdf717 transplanted to ffd6818a3975
54 a53251cdf717 transplanted to ffd6818a3975
55 $ hg log --template '{rev} {parents} {desc}\n'
55 $ hg log --template '{rev} {parents} {desc}\n'
56 7 b3
56 7 b3
57 6 b2
57 6 b2
58 5 1:d11e3596cc1a b1
58 5 1:d11e3596cc1a b1
59 4 b3
59 4 b3
60 3 b2
60 3 b2
61 2 0:17ab29e464c6 b1
61 2 0:17ab29e464c6 b1
62 1 r2
62 1 r2
63 0 r1
63 0 r1
64
64
65 test transplanted revset
65 test transplanted revset
66
66
67 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
67 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
68 5 1:d11e3596cc1a b1
68 5 1:d11e3596cc1a b1
69 6 b2
69 6 b2
70 7 b3
70 7 b3
71 $ hg help revsets | grep transplanted
71 $ hg help revsets | grep transplanted
72 "transplanted([set])"
72 "transplanted([set])"
73 Transplanted changesets in set, or all transplanted changesets.
73 Transplanted changesets in set, or all transplanted changesets.
74
74
75 test transplanted keyword
75 test transplanted keyword
76
76
77 $ hg log --template '{rev} {transplanted}\n'
77 $ hg log --template '{rev} {transplanted}\n'
78 7 a53251cdf717679d1907b289f991534be05c997a
78 7 a53251cdf717679d1907b289f991534be05c997a
79 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
79 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
80 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
80 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
81 4
81 4
82 3
82 3
83 2
83 2
84 1
84 1
85 0
85 0
86
86
87 test destination() revset predicate with a transplant of a transplant; new
87 test destination() revset predicate with a transplant of a transplant; new
88 clone so subsequent rollback isn't affected
88 clone so subsequent rollback isn't affected
89 (this also tests that editor is invoked if '--edit' is specified)
89 (this also tests that editor is invoked if '--edit' is specified)
90
90
91 $ hg clone -q . ../destination
91 $ hg clone -q . ../destination
92 $ cd ../destination
92 $ cd ../destination
93 $ hg up -Cq 0
93 $ hg up -Cq 0
94 $ hg branch -q b4
94 $ hg branch -q b4
95 $ hg ci -qm "b4"
95 $ hg ci -qm "b4"
96 $ hg status --rev "7^1" --rev 7
96 $ hg status --rev "7^1" --rev 7
97 A b3
97 A b3
98 $ cat > $TESTTMP/checkeditform.sh <<EOF
98 $ cat > $TESTTMP/checkeditform.sh <<EOF
99 > env | grep HGEDITFORM
99 > env | grep HGEDITFORM
100 > true
100 > true
101 > EOF
101 > EOF
102 $ cat > $TESTTMP/checkeditform-n-cat.sh <<EOF
102 $ cat > $TESTTMP/checkeditform-n-cat.sh <<EOF
103 > env | grep HGEDITFORM
103 > env | grep HGEDITFORM
104 > cat \$*
104 > cat \$*
105 > EOF
105 > EOF
106 $ HGEDITOR="sh $TESTTMP/checkeditform-n-cat.sh" hg transplant --edit 7
106 $ HGEDITOR="sh $TESTTMP/checkeditform-n-cat.sh" hg transplant --edit 7
107 applying ffd6818a3975
107 applying ffd6818a3975
108 HGEDITFORM=transplant.normal
108 HGEDITFORM=transplant.normal
109 b3
109 b3
110
110
111
111
112 HG: Enter commit message. Lines beginning with 'HG:' are removed.
112 HG: Enter commit message. Lines beginning with 'HG:' are removed.
113 HG: Leave message empty to abort commit.
113 HG: Leave message empty to abort commit.
114 HG: --
114 HG: --
115 HG: user: test
115 HG: user: test
116 HG: branch 'b4'
116 HG: branch 'b4'
117 HG: added b3
117 HG: added b3
118 ffd6818a3975 transplanted to 502236fa76bb
118 ffd6818a3975 transplanted to 502236fa76bb
119
119
120
120
121 $ hg log -r 'destination()'
121 $ hg log -r 'destination()'
122 changeset: 5:e234d668f844
122 changeset: 5:e234d668f844
123 parent: 1:d11e3596cc1a
123 parent: 1:d11e3596cc1a
124 user: test
124 user: test
125 date: Thu Jan 01 00:00:00 1970 +0000
125 date: Thu Jan 01 00:00:00 1970 +0000
126 summary: b1
126 summary: b1
127
127
128 changeset: 6:539f377d78df
128 changeset: 6:539f377d78df
129 user: test
129 user: test
130 date: Thu Jan 01 00:00:01 1970 +0000
130 date: Thu Jan 01 00:00:01 1970 +0000
131 summary: b2
131 summary: b2
132
132
133 changeset: 7:ffd6818a3975
133 changeset: 7:ffd6818a3975
134 user: test
134 user: test
135 date: Thu Jan 01 00:00:02 1970 +0000
135 date: Thu Jan 01 00:00:02 1970 +0000
136 summary: b3
136 summary: b3
137
137
138 changeset: 9:502236fa76bb
138 changeset: 9:502236fa76bb
139 branch: b4
139 branch: b4
140 tag: tip
140 tag: tip
141 user: test
141 user: test
142 date: Thu Jan 01 00:00:02 1970 +0000
142 date: Thu Jan 01 00:00:02 1970 +0000
143 summary: b3
143 summary: b3
144
144
145 $ hg log -r 'destination(a53251cdf717)'
145 $ hg log -r 'destination(a53251cdf717)'
146 changeset: 7:ffd6818a3975
146 changeset: 7:ffd6818a3975
147 user: test
147 user: test
148 date: Thu Jan 01 00:00:02 1970 +0000
148 date: Thu Jan 01 00:00:02 1970 +0000
149 summary: b3
149 summary: b3
150
150
151 changeset: 9:502236fa76bb
151 changeset: 9:502236fa76bb
152 branch: b4
152 branch: b4
153 tag: tip
153 tag: tip
154 user: test
154 user: test
155 date: Thu Jan 01 00:00:02 1970 +0000
155 date: Thu Jan 01 00:00:02 1970 +0000
156 summary: b3
156 summary: b3
157
157
158
158
159 test subset parameter in reverse order
159 test subset parameter in reverse order
160 $ hg log -r 'reverse(all()) and destination(a53251cdf717)'
160 $ hg log -r 'reverse(all()) and destination(a53251cdf717)'
161 changeset: 9:502236fa76bb
161 changeset: 9:502236fa76bb
162 branch: b4
162 branch: b4
163 tag: tip
163 tag: tip
164 user: test
164 user: test
165 date: Thu Jan 01 00:00:02 1970 +0000
165 date: Thu Jan 01 00:00:02 1970 +0000
166 summary: b3
166 summary: b3
167
167
168 changeset: 7:ffd6818a3975
168 changeset: 7:ffd6818a3975
169 user: test
169 user: test
170 date: Thu Jan 01 00:00:02 1970 +0000
170 date: Thu Jan 01 00:00:02 1970 +0000
171 summary: b3
171 summary: b3
172
172
173
173
174 back to the original dir
174 back to the original dir
175 $ cd ../rebase
175 $ cd ../rebase
176
176
177 rollback the transplant
177 rollback the transplant
178 $ hg rollback
178 $ hg rollback
179 repository tip rolled back to revision 4 (undo transplant)
179 repository tip rolled back to revision 4 (undo transplant)
180 working directory now based on revision 1
180 working directory now based on revision 1
181 $ hg tip -q
181 $ hg tip -q
182 4:a53251cdf717
182 4:a53251cdf717
183 $ hg parents -q
183 $ hg parents -q
184 1:d11e3596cc1a
184 1:d11e3596cc1a
185 $ hg status
185 $ hg status
186 ? b1
186 ? b1
187 ? b2
187 ? b2
188 ? b3
188 ? b3
189
189
190 $ hg clone ../t ../prune
190 $ hg clone ../t ../prune
191 updating to branch default
191 updating to branch default
192 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 $ cd ../prune
193 $ cd ../prune
194
194
195 $ hg up -C 1
195 $ hg up -C 1
196 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
196 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
197
197
198 rebase b onto r1, skipping b2
198 rebase b onto r1, skipping b2
199
199
200 $ hg transplant -a -b tip -p 3
200 $ hg transplant -a -b tip -p 3
201 applying 37a1297eb21b
201 applying 37a1297eb21b
202 37a1297eb21b transplanted to e234d668f844
202 37a1297eb21b transplanted to e234d668f844
203 applying a53251cdf717
203 applying a53251cdf717
204 a53251cdf717 transplanted to 7275fda4d04f
204 a53251cdf717 transplanted to 7275fda4d04f
205 $ hg log --template '{rev} {parents} {desc}\n'
205 $ hg log --template '{rev} {parents} {desc}\n'
206 6 b3
206 6 b3
207 5 1:d11e3596cc1a b1
207 5 1:d11e3596cc1a b1
208 4 b3
208 4 b3
209 3 b2
209 3 b2
210 2 0:17ab29e464c6 b1
210 2 0:17ab29e464c6 b1
211 1 r2
211 1 r2
212 0 r1
212 0 r1
213
213
214 test same-parent transplant with --log
214 test same-parent transplant with --log
215
215
216 $ hg clone -r 1 ../t ../sameparent
216 $ hg clone -r 1 ../t ../sameparent
217 adding changesets
217 adding changesets
218 adding manifests
218 adding manifests
219 adding file changes
219 adding file changes
220 added 2 changesets with 2 changes to 2 files
220 added 2 changesets with 2 changes to 2 files
221 updating to branch default
221 updating to branch default
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 $ cd ../sameparent
223 $ cd ../sameparent
224 $ hg transplant --log -s ../prune 5
224 $ hg transplant --log -s ../prune 5
225 searching for changes
225 searching for changes
226 applying e234d668f844
226 applying e234d668f844
227 e234d668f844 transplanted to e07aea8ecf9c
227 e234d668f844 transplanted to e07aea8ecf9c
228 $ hg log --template '{rev} {parents} {desc}\n'
228 $ hg log --template '{rev} {parents} {desc}\n'
229 2 b1
229 2 b1
230 (transplanted from e234d668f844e1b1a765f01db83a32c0c7bfa170)
230 (transplanted from e234d668f844e1b1a765f01db83a32c0c7bfa170)
231 1 r2
231 1 r2
232 0 r1
232 0 r1
233 remote transplant, and also test that transplant doesn't break with
233 remote transplant, and also test that transplant doesn't break with
234 format-breaking diffopts
234 format-breaking diffopts
235
235
236 $ hg clone -r 1 ../t ../remote
236 $ hg clone -r 1 ../t ../remote
237 adding changesets
237 adding changesets
238 adding manifests
238 adding manifests
239 adding file changes
239 adding file changes
240 added 2 changesets with 2 changes to 2 files
240 added 2 changesets with 2 changes to 2 files
241 updating to branch default
241 updating to branch default
242 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
242 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 $ cd ../remote
243 $ cd ../remote
244 $ hg --config diff.noprefix=True transplant --log -s ../t 2 4
244 $ hg --config diff.noprefix=True transplant --log -s ../t 2 4
245 searching for changes
245 searching for changes
246 applying 37a1297eb21b
246 applying 37a1297eb21b
247 37a1297eb21b transplanted to c19cf0ccb069
247 37a1297eb21b transplanted to c19cf0ccb069
248 applying a53251cdf717
248 applying a53251cdf717
249 a53251cdf717 transplanted to f7fe5bf98525
249 a53251cdf717 transplanted to f7fe5bf98525
250 $ hg log --template '{rev} {parents} {desc}\n'
250 $ hg log --template '{rev} {parents} {desc}\n'
251 3 b3
251 3 b3
252 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
252 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
253 2 b1
253 2 b1
254 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
254 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
255 1 r2
255 1 r2
256 0 r1
256 0 r1
257
257
258 skip previous transplants
258 skip previous transplants
259
259
260 $ hg transplant -s ../t -a -b 4
260 $ hg transplant -s ../t -a -b 4
261 searching for changes
261 searching for changes
262 applying 722f4667af76
262 applying 722f4667af76
263 722f4667af76 transplanted to 47156cd86c0b
263 722f4667af76 transplanted to 47156cd86c0b
264 $ hg log --template '{rev} {parents} {desc}\n'
264 $ hg log --template '{rev} {parents} {desc}\n'
265 4 b2
265 4 b2
266 3 b3
266 3 b3
267 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
267 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
268 2 b1
268 2 b1
269 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
269 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
270 1 r2
270 1 r2
271 0 r1
271 0 r1
272
272
273 skip local changes transplanted to the source
273 skip local changes transplanted to the source
274
274
275 $ echo b4 > b4
275 $ echo b4 > b4
276 $ hg ci -Amb4 -d '3 0'
276 $ hg ci -Amb4 -d '3 0'
277 adding b4
277 adding b4
278 $ hg clone ../t ../pullback
278 $ hg clone ../t ../pullback
279 updating to branch default
279 updating to branch default
280 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 $ cd ../pullback
281 $ cd ../pullback
282 $ hg transplant -s ../remote -a -b tip
282 $ hg transplant -s ../remote -a -b tip
283 searching for changes
283 searching for changes
284 applying 4333daefcb15
284 applying 4333daefcb15
285 4333daefcb15 transplanted to 5f42c04e07cc
285 4333daefcb15 transplanted to 5f42c04e07cc
286
286
287
287
288 remote transplant with pull
288 remote transplant with pull
289
289
290 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
290 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
291 $ cat ../t.pid >> $DAEMON_PIDS
291 $ cat ../t.pid >> $DAEMON_PIDS
292
292
293 $ hg clone -r 0 ../t ../rp
293 $ hg clone -r 0 ../t ../rp
294 adding changesets
294 adding changesets
295 adding manifests
295 adding manifests
296 adding file changes
296 adding file changes
297 added 1 changesets with 1 changes to 1 files
297 added 1 changesets with 1 changes to 1 files
298 updating to branch default
298 updating to branch default
299 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
299 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
300 $ cd ../rp
300 $ cd ../rp
301 $ hg transplant -s http://localhost:$HGPORT/ 37a1297eb21b a53251cdf717
301 $ hg transplant -s http://localhost:$HGPORT/ 37a1297eb21b a53251cdf717
302 searching for changes
302 searching for changes
303 searching for changes
303 searching for changes
304 adding changesets
304 adding changesets
305 adding manifests
305 adding manifests
306 adding file changes
306 adding file changes
307 added 1 changesets with 1 changes to 1 files
307 added 1 changesets with 1 changes to 1 files
308 applying a53251cdf717
308 applying a53251cdf717
309 a53251cdf717 transplanted to 8d9279348abb
309 a53251cdf717 transplanted to 8d9279348abb
310 $ hg log --template '{rev} {parents} {desc}\n'
310 $ hg log --template '{rev} {parents} {desc}\n'
311 2 b3
311 2 b3
312 1 b1
312 1 b1
313 0 r1
313 0 r1
314
314
315 remote transplant without pull
315 remote transplant without pull
316 (It was using "2" and "4" (as the previous transplant used to) which referenced
316 (It was using "2" and "4" (as the previous transplant used to) which referenced
317 revision different from one run to another)
317 revision different from one run to another)
318
318
319 $ hg pull -q http://localhost:$HGPORT/
319 $ hg pull -q http://localhost:$HGPORT/
320 $ hg transplant -s http://localhost:$HGPORT/ 8d9279348abb 722f4667af76
320 $ hg transplant -s http://localhost:$HGPORT/ 8d9279348abb 722f4667af76
321 skipping already applied revision 2:8d9279348abb
321 skipping already applied revision 2:8d9279348abb
322 applying 722f4667af76
322 applying 722f4667af76
323 722f4667af76 transplanted to 76e321915884
323 722f4667af76 transplanted to 76e321915884
324
324
325 transplant --continue
325 transplant --continue
326
326
327 $ hg init ../tc
327 $ hg init ../tc
328 $ cd ../tc
328 $ cd ../tc
329 $ cat <<EOF > foo
329 $ cat <<EOF > foo
330 > foo
330 > foo
331 > bar
331 > bar
332 > baz
332 > baz
333 > EOF
333 > EOF
334 $ echo toremove > toremove
334 $ echo toremove > toremove
335 $ echo baz > baz
335 $ echo baz > baz
336 $ hg ci -Amfoo
336 $ hg ci -Amfoo
337 adding baz
337 adding baz
338 adding foo
338 adding foo
339 adding toremove
339 adding toremove
340 $ cat <<EOF > foo
340 $ cat <<EOF > foo
341 > foo2
341 > foo2
342 > bar2
342 > bar2
343 > baz2
343 > baz2
344 > EOF
344 > EOF
345 $ rm toremove
345 $ rm toremove
346 $ echo added > added
346 $ echo added > added
347 $ hg ci -Amfoo2
347 $ hg ci -Amfoo2
348 adding added
348 adding added
349 removing toremove
349 removing toremove
350 $ echo bar > bar
350 $ echo bar > bar
351 $ cat > baz <<EOF
351 $ cat > baz <<EOF
352 > before baz
352 > before baz
353 > baz
353 > baz
354 > after baz
354 > after baz
355 > EOF
355 > EOF
356 $ hg ci -Ambar
356 $ hg ci -Ambar
357 adding bar
357 adding bar
358 $ echo bar2 >> bar
358 $ echo bar2 >> bar
359 $ hg ci -mbar2
359 $ hg ci -mbar2
360 $ hg up 0
360 $ hg up 0
361 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
361 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
362 $ echo foobar > foo
362 $ echo foobar > foo
363 $ hg ci -mfoobar
363 $ hg ci -mfoobar
364 created new head
364 created new head
365 $ hg transplant 1:3
365 $ hg transplant 1:3
366 applying 46ae92138f3c
366 applying 46ae92138f3c
367 patching file foo
367 patching file foo
368 Hunk #1 FAILED at 0
368 Hunk #1 FAILED at 0
369 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
369 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
370 patch failed to apply
370 patch failed to apply
371 abort: fix up the merge and run hg transplant --continue
371 abort: fix up the merge and run hg transplant --continue
372 [255]
372 [255]
373
373
374 transplant -c shouldn't use an old changeset
374 transplant -c shouldn't use an old changeset
375
375
376 $ hg up -C
376 $ hg up -C
377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 $ rm added
378 $ rm added
379 $ hg transplant 1
379 $ hg transplant 1
380 applying 46ae92138f3c
380 applying 46ae92138f3c
381 patching file foo
381 patching file foo
382 Hunk #1 FAILED at 0
382 Hunk #1 FAILED at 0
383 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
383 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
384 patch failed to apply
384 patch failed to apply
385 abort: fix up the merge and run hg transplant --continue
385 abort: fix up the merge and run hg transplant --continue
386 [255]
386 [255]
387 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
387 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
388 HGEDITFORM=transplant.normal
388 HGEDITFORM=transplant.normal
389 46ae92138f3c transplanted as 9159dada197d
389 46ae92138f3c transplanted as 9159dada197d
390 $ hg transplant 1:3
390 $ hg transplant 1:3
391 skipping already applied revision 1:46ae92138f3c
391 skipping already applied revision 1:46ae92138f3c
392 applying 9d6d6b5a8275
392 applying 9d6d6b5a8275
393 9d6d6b5a8275 transplanted to 2d17a10c922f
393 9d6d6b5a8275 transplanted to 2d17a10c922f
394 applying 1dab759070cf
394 applying 1dab759070cf
395 1dab759070cf transplanted to e06a69927eb0
395 1dab759070cf transplanted to e06a69927eb0
396 $ hg locate
396 $ hg locate
397 added
397 added
398 bar
398 bar
399 baz
399 baz
400 foo
400 foo
401
401
402 test multiple revisions and --continue
402 test multiple revisions and --continue
403
403
404 $ hg up -qC 0
404 $ hg up -qC 0
405 $ echo bazbaz > baz
405 $ echo bazbaz > baz
406 $ hg ci -Am anotherbaz baz
406 $ hg ci -Am anotherbaz baz
407 created new head
407 created new head
408 $ hg transplant 1:3
408 $ hg transplant 1:3
409 applying 46ae92138f3c
409 applying 46ae92138f3c
410 46ae92138f3c transplanted to 1024233ea0ba
410 46ae92138f3c transplanted to 1024233ea0ba
411 applying 9d6d6b5a8275
411 applying 9d6d6b5a8275
412 patching file baz
412 patching file baz
413 Hunk #1 FAILED at 0
413 Hunk #1 FAILED at 0
414 1 out of 1 hunks FAILED -- saving rejects to file baz.rej
414 1 out of 1 hunks FAILED -- saving rejects to file baz.rej
415 patch failed to apply
415 patch failed to apply
416 abort: fix up the merge and run hg transplant --continue
416 abort: fix up the merge and run hg transplant --continue
417 [255]
417 [255]
418 $ echo fixed > baz
418 $ echo fixed > baz
419 $ hg transplant --continue
419 $ hg transplant --continue
420 9d6d6b5a8275 transplanted as d80c49962290
420 9d6d6b5a8275 transplanted as d80c49962290
421 applying 1dab759070cf
421 applying 1dab759070cf
422 1dab759070cf transplanted to aa0ffe6bd5ae
422 1dab759070cf transplanted to aa0ffe6bd5ae
423
423
424 $ cd ..
424 $ cd ..
425
425
426 Issue1111: Test transplant --merge
426 Issue1111: Test transplant --merge
427
427
428 $ hg init t1111
428 $ hg init t1111
429 $ cd t1111
429 $ cd t1111
430 $ echo a > a
430 $ echo a > a
431 $ hg ci -Am adda
431 $ hg ci -Am adda
432 adding a
432 adding a
433 $ echo b >> a
433 $ echo b >> a
434 $ hg ci -m appendb
434 $ hg ci -m appendb
435 $ echo c >> a
435 $ echo c >> a
436 $ hg ci -m appendc
436 $ hg ci -m appendc
437 $ hg up -C 0
437 $ hg up -C 0
438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 $ echo d >> a
439 $ echo d >> a
440 $ hg ci -m appendd
440 $ hg ci -m appendd
441 created new head
441 created new head
442
442
443 transplant
443 transplant
444
444
445 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant -m 1 -e
445 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant -m 1 -e
446 applying 42dc4432fd35
446 applying 42dc4432fd35
447 HGEDITFORM=transplant.merge
447 HGEDITFORM=transplant.merge
448 1:42dc4432fd35 merged at a9f4acbac129
448 1:42dc4432fd35 merged at a9f4acbac129
449 $ hg update -q -C 2
449 $ hg update -q -C 2
450 $ cat > a <<EOF
450 $ cat > a <<EOF
451 > x
451 > x
452 > y
452 > y
453 > z
453 > z
454 > EOF
454 > EOF
455 $ hg commit -m replace
455 $ hg commit -m replace
456 $ hg update -q -C 4
456 $ hg update -q -C 4
457 $ hg transplant -m 5
457 $ hg transplant -m 5
458 applying 600a3cdcb41d
458 applying 600a3cdcb41d
459 patching file a
459 patching file a
460 Hunk #1 FAILED at 0
460 Hunk #1 FAILED at 0
461 1 out of 1 hunks FAILED -- saving rejects to file a.rej
461 1 out of 1 hunks FAILED -- saving rejects to file a.rej
462 patch failed to apply
462 patch failed to apply
463 abort: fix up the merge and run hg transplant --continue
463 abort: fix up the merge and run hg transplant --continue
464 [255]
464 [255]
465 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
465 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
466 HGEDITFORM=transplant.merge
466 HGEDITFORM=transplant.merge
467 600a3cdcb41d transplanted as a3f88be652e0
467 600a3cdcb41d transplanted as a3f88be652e0
468
468
469 $ cd ..
469 $ cd ..
470
470
471 test transplant into empty repository
471 test transplant into empty repository
472
472
473 $ hg init empty
473 $ hg init empty
474 $ cd empty
474 $ cd empty
475 $ hg transplant -s ../t -b tip -a
475 $ hg transplant -s ../t -b tip -a
476 adding changesets
476 adding changesets
477 adding manifests
477 adding manifests
478 adding file changes
478 adding file changes
479 added 4 changesets with 4 changes to 4 files
479 added 4 changesets with 4 changes to 4 files
480
480
481 test "--merge" causing pull from source repository on local host
481 test "--merge" causing pull from source repository on local host
482
482
483 $ hg --config extensions.mq= -q strip 2
483 $ hg --config extensions.mq= -q strip 2
484 $ hg transplant -s ../t --merge tip
484 $ hg transplant -s ../t --merge tip
485 searching for changes
485 searching for changes
486 searching for changes
486 searching for changes
487 adding changesets
487 adding changesets
488 adding manifests
488 adding manifests
489 adding file changes
489 adding file changes
490 added 2 changesets with 2 changes to 2 files
490 added 2 changesets with 2 changes to 2 files
491 applying a53251cdf717
491 applying a53251cdf717
492 4:a53251cdf717 merged at 4831f4dc831a
492 4:a53251cdf717 merged at 4831f4dc831a
493
493
494 test interactive transplant
494 test interactive transplant
495
495
496 $ hg --config extensions.strip= -q strip 0
496 $ hg --config extensions.strip= -q strip 0
497 $ hg -R ../t log -G --template "{rev}:{node|short}"
497 $ hg -R ../t log -G --template "{rev}:{node|short}"
498 @ 4:a53251cdf717
498 @ 4:a53251cdf717
499 |
499 |
500 o 3:722f4667af76
500 o 3:722f4667af76
501 |
501 |
502 o 2:37a1297eb21b
502 o 2:37a1297eb21b
503 |
503 |
504 | o 1:d11e3596cc1a
504 | o 1:d11e3596cc1a
505 |/
505 |/
506 o 0:17ab29e464c6
506 o 0:17ab29e464c6
507
507
508 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
508 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
509 > p
509 > p
510 > y
510 > y
511 > n
511 > n
512 > n
512 > n
513 > m
513 > m
514 > c
514 > c
515 > EOF
515 > EOF
516 0:17ab29e464c6
516 0:17ab29e464c6
517 apply changeset? [ynmpcq?]: p
517 apply changeset? [ynmpcq?]: p
518 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
518 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
519 +++ b/r1 Thu Jan 01 00:00:00 1970 +0000
519 +++ b/r1 Thu Jan 01 00:00:00 1970 +0000
520 @@ -0,0 +1,1 @@
520 @@ -0,0 +1,1 @@
521 +r1
521 +r1
522 apply changeset? [ynmpcq?]: y
522 apply changeset? [ynmpcq?]: y
523 1:d11e3596cc1a
523 1:d11e3596cc1a
524 apply changeset? [ynmpcq?]: n
524 apply changeset? [ynmpcq?]: n
525 2:37a1297eb21b
525 2:37a1297eb21b
526 apply changeset? [ynmpcq?]: n
526 apply changeset? [ynmpcq?]: n
527 3:722f4667af76
527 3:722f4667af76
528 apply changeset? [ynmpcq?]: m
528 apply changeset? [ynmpcq?]: m
529 4:a53251cdf717
529 4:a53251cdf717
530 apply changeset? [ynmpcq?]: c
530 apply changeset? [ynmpcq?]: c
531 $ hg log -G --template "{node|short}"
531 $ hg log -G --template "{node|short}"
532 @ 88be5dde5260
532 @ 88be5dde5260
533 |\
533 |\
534 | o 722f4667af76
534 | o 722f4667af76
535 | |
535 | |
536 | o 37a1297eb21b
536 | o 37a1297eb21b
537 |/
537 |/
538 o 17ab29e464c6
538 o 17ab29e464c6
539
539
540 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
540 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
541 > x
541 > x
542 > ?
542 > ?
543 > y
543 > y
544 > q
544 > q
545 > EOF
545 > EOF
546 1:d11e3596cc1a
546 1:d11e3596cc1a
547 apply changeset? [ynmpcq?]: x
547 apply changeset? [ynmpcq?]: x
548 unrecognized response
548 unrecognized response
549 apply changeset? [ynmpcq?]: ?
549 apply changeset? [ynmpcq?]: ?
550 y: yes, transplant this changeset
550 y: yes, transplant this changeset
551 n: no, skip this changeset
551 n: no, skip this changeset
552 m: merge at this changeset
552 m: merge at this changeset
553 p: show patch
553 p: show patch
554 c: commit selected changesets
554 c: commit selected changesets
555 q: quit and cancel transplant
555 q: quit and cancel transplant
556 ?: ? (show this help)
556 ?: ? (show this help)
557 apply changeset? [ynmpcq?]: y
557 apply changeset? [ynmpcq?]: y
558 4:a53251cdf717
558 4:a53251cdf717
559 apply changeset? [ynmpcq?]: q
559 apply changeset? [ynmpcq?]: q
560 $ hg heads --template "{node|short}\n"
560 $ hg heads --template "{node|short}\n"
561 88be5dde5260
561 88be5dde5260
562
562
563 $ cd ..
563 $ cd ..
564
564
565
565
566 #if unix-permissions system-sh
566 #if unix-permissions system-sh
567
567
568 test filter
568 test filter
569
569
570 $ hg init filter
570 $ hg init filter
571 $ cd filter
571 $ cd filter
572 $ cat <<'EOF' >test-filter
572 $ cat <<'EOF' >test-filter
573 > #!/bin/sh
573 > #!/bin/sh
574 > sed 's/r1/r2/' $1 > $1.new
574 > sed 's/r1/r2/' $1 > $1.new
575 > mv $1.new $1
575 > mv $1.new $1
576 > EOF
576 > EOF
577 $ chmod +x test-filter
577 $ chmod +x test-filter
578 $ hg transplant -s ../t -b tip -a --filter ./test-filter
578 $ hg transplant -s ../t -b tip -a --filter ./test-filter
579 filtering * (glob)
579 filtering * (glob)
580 applying 17ab29e464c6
580 applying 17ab29e464c6
581 17ab29e464c6 transplanted to e9ffc54ea104
581 17ab29e464c6 transplanted to e9ffc54ea104
582 filtering * (glob)
582 filtering * (glob)
583 applying 37a1297eb21b
583 applying 37a1297eb21b
584 37a1297eb21b transplanted to 348b36d0b6a5
584 37a1297eb21b transplanted to 348b36d0b6a5
585 filtering * (glob)
585 filtering * (glob)
586 applying 722f4667af76
586 applying 722f4667af76
587 722f4667af76 transplanted to 0aa6979afb95
587 722f4667af76 transplanted to 0aa6979afb95
588 filtering * (glob)
588 filtering * (glob)
589 applying a53251cdf717
589 applying a53251cdf717
590 a53251cdf717 transplanted to 14f8512272b5
590 a53251cdf717 transplanted to 14f8512272b5
591 $ hg log --template '{rev} {parents} {desc}\n'
591 $ hg log --template '{rev} {parents} {desc}\n'
592 3 b3
592 3 b3
593 2 b2
593 2 b2
594 1 b1
594 1 b1
595 0 r2
595 0 r2
596 $ cd ..
596 $ cd ..
597
597
598
598
599 test filter with failed patch
599 test filter with failed patch
600
600
601 $ cd filter
601 $ cd filter
602 $ hg up 0
602 $ hg up 0
603 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
603 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
604 $ echo foo > b1
604 $ echo foo > b1
605 $ hg ci -Am foo
605 $ hg ci -Am foo
606 adding b1
606 adding b1
607 adding test-filter
607 adding test-filter
608 created new head
608 created new head
609 $ hg transplant 1 --filter ./test-filter
609 $ hg transplant 1 --filter ./test-filter
610 filtering * (glob)
610 filtering * (glob)
611 applying 348b36d0b6a5
611 applying 348b36d0b6a5
612 file b1 already exists
612 file b1 already exists
613 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
613 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
614 patch failed to apply
614 patch failed to apply
615 abort: fix up the merge and run hg transplant --continue
615 abort: fix up the merge and run hg transplant --continue
616 [255]
616 [255]
617 $ cd ..
617 $ cd ..
618
618
619 test environment passed to filter
619 test environment passed to filter
620
620
621 $ hg init filter-environment
621 $ hg init filter-environment
622 $ cd filter-environment
622 $ cd filter-environment
623 $ cat <<'EOF' >test-filter-environment
623 $ cat <<'EOF' >test-filter-environment
624 > #!/bin/sh
624 > #!/bin/sh
625 > echo "Transplant by $HGUSER" >> $1
625 > echo "Transplant by $HGUSER" >> $1
626 > echo "Transplant from rev $HGREVISION" >> $1
626 > echo "Transplant from rev $HGREVISION" >> $1
627 > EOF
627 > EOF
628 $ chmod +x test-filter-environment
628 $ chmod +x test-filter-environment
629 $ hg transplant -s ../t --filter ./test-filter-environment 0
629 $ hg transplant -s ../t --filter ./test-filter-environment 0
630 filtering * (glob)
630 filtering * (glob)
631 applying 17ab29e464c6
631 applying 17ab29e464c6
632 17ab29e464c6 transplanted to 5190e68026a0
632 17ab29e464c6 transplanted to 5190e68026a0
633
633
634 $ hg log --template '{rev} {parents} {desc}\n'
634 $ hg log --template '{rev} {parents} {desc}\n'
635 0 r1
635 0 r1
636 Transplant by test
636 Transplant by test
637 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
637 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
638 $ cd ..
638 $ cd ..
639
639
640 test transplant with filter handles invalid changelog
640 test transplant with filter handles invalid changelog
641
641
642 $ hg init filter-invalid-log
642 $ hg init filter-invalid-log
643 $ cd filter-invalid-log
643 $ cd filter-invalid-log
644 $ cat <<'EOF' >test-filter-invalid-log
644 $ cat <<'EOF' >test-filter-invalid-log
645 > #!/bin/sh
645 > #!/bin/sh
646 > echo "" > $1
646 > echo "" > $1
647 > EOF
647 > EOF
648 $ chmod +x test-filter-invalid-log
648 $ chmod +x test-filter-invalid-log
649 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
649 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
650 filtering * (glob)
650 filtering * (glob)
651 abort: filter corrupted changeset (no user or date)
651 abort: filter corrupted changeset (no user or date)
652 [255]
652 [255]
653 $ cd ..
653 $ cd ..
654
654
655 #endif
655 #endif
656
656
657
657
658 test with a win32ext like setup (differing EOLs)
658 test with a win32ext like setup (differing EOLs)
659
659
660 $ hg init twin1
660 $ hg init twin1
661 $ cd twin1
661 $ cd twin1
662 $ echo a > a
662 $ echo a > a
663 $ echo b > b
663 $ echo b > b
664 $ echo b >> b
664 $ echo b >> b
665 $ hg ci -Am t
665 $ hg ci -Am t
666 adding a
666 adding a
667 adding b
667 adding b
668 $ echo a > b
668 $ echo a > b
669 $ echo b >> b
669 $ echo b >> b
670 $ hg ci -m changeb
670 $ hg ci -m changeb
671 $ cd ..
671 $ cd ..
672
672
673 $ hg init twin2
673 $ hg init twin2
674 $ cd twin2
674 $ cd twin2
675 $ echo '[patch]' >> .hg/hgrc
675 $ echo '[patch]' >> .hg/hgrc
676 $ echo 'eol = crlf' >> .hg/hgrc
676 $ echo 'eol = crlf' >> .hg/hgrc
677 $ $PYTHON -c "file('b', 'wb').write('b\r\nb\r\n')"
677 $ $PYTHON -c "file('b', 'wb').write('b\r\nb\r\n')"
678 $ hg ci -Am addb
678 $ hg ci -Am addb
679 adding b
679 adding b
680 $ hg transplant -s ../twin1 tip
680 $ hg transplant -s ../twin1 tip
681 searching for changes
681 searching for changes
682 warning: repository is unrelated
682 warning: repository is unrelated
683 applying 2e849d776c17
683 applying 2e849d776c17
684 2e849d776c17 transplanted to 8e65bebc063e
684 2e849d776c17 transplanted to 8e65bebc063e
685 $ cat b
685 $ cat b
686 a\r (esc)
686 a\r (esc)
687 b\r (esc)
687 b\r (esc)
688 $ cd ..
688 $ cd ..
689
689
690 test transplant with merge changeset is skipped
690 test transplant with merge changeset is skipped
691
691
692 $ hg init merge1a
692 $ hg init merge1a
693 $ cd merge1a
693 $ cd merge1a
694 $ echo a > a
694 $ echo a > a
695 $ hg ci -Am a
695 $ hg ci -Am a
696 adding a
696 adding a
697 $ hg branch b
697 $ hg branch b
698 marked working directory as branch b
698 marked working directory as branch b
699 (branches are permanent and global, did you want a bookmark?)
699 (branches are permanent and global, did you want a bookmark?)
700 $ hg ci -m branchb
700 $ hg ci -m branchb
701 $ echo b > b
701 $ echo b > b
702 $ hg ci -Am b
702 $ hg ci -Am b
703 adding b
703 adding b
704 $ hg update default
704 $ hg update default
705 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
705 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
706 $ hg merge b
706 $ hg merge b
707 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
707 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
708 (branch merge, don't forget to commit)
708 (branch merge, don't forget to commit)
709 $ hg ci -m mergeb
709 $ hg ci -m mergeb
710 $ cd ..
710 $ cd ..
711
711
712 $ hg init merge1b
712 $ hg init merge1b
713 $ cd merge1b
713 $ cd merge1b
714 $ hg transplant -s ../merge1a tip
714 $ hg transplant -s ../merge1a tip
715 $ cd ..
715 $ cd ..
716
716
717 test transplant with merge changeset accepts --parent
717 test transplant with merge changeset accepts --parent
718
718
719 $ hg init merge2a
719 $ hg init merge2a
720 $ cd merge2a
720 $ cd merge2a
721 $ echo a > a
721 $ echo a > a
722 $ hg ci -Am a
722 $ hg ci -Am a
723 adding a
723 adding a
724 $ hg branch b
724 $ hg branch b
725 marked working directory as branch b
725 marked working directory as branch b
726 (branches are permanent and global, did you want a bookmark?)
726 (branches are permanent and global, did you want a bookmark?)
727 $ hg ci -m branchb
727 $ hg ci -m branchb
728 $ echo b > b
728 $ echo b > b
729 $ hg ci -Am b
729 $ hg ci -Am b
730 adding b
730 adding b
731 $ hg update default
731 $ hg update default
732 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
732 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
733 $ hg merge b
733 $ hg merge b
734 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
734 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
735 (branch merge, don't forget to commit)
735 (branch merge, don't forget to commit)
736 $ hg ci -m mergeb
736 $ hg ci -m mergeb
737 $ cd ..
737 $ cd ..
738
738
739 $ hg init merge2b
739 $ hg init merge2b
740 $ cd merge2b
740 $ cd merge2b
741 $ hg transplant -s ../merge2a --parent 0 tip
741 $ hg transplant -s ../merge2a --parent 0 tip
742 applying be9f9b39483f
742 applying be9f9b39483f
743 be9f9b39483f transplanted to 9959e51f94d1
743 be9f9b39483f transplanted to 9959e51f94d1
744 $ cd ..
744 $ cd ..
745
745
746 test transplanting a patch turning into a no-op
746 test transplanting a patch turning into a no-op
747
747
748 $ hg init binarysource
748 $ hg init binarysource
749 $ cd binarysource
749 $ cd binarysource
750 $ echo a > a
750 $ echo a > a
751 $ hg ci -Am adda a
751 $ hg ci -Am adda a
752 >>> file('b', 'wb').write('\0b1')
752 >>> file('b', 'wb').write('\0b1')
753 $ hg ci -Am addb b
753 $ hg ci -Am addb b
754 >>> file('b', 'wb').write('\0b2')
754 >>> file('b', 'wb').write('\0b2')
755 $ hg ci -m changeb b
755 $ hg ci -m changeb b
756 $ cd ..
756 $ cd ..
757
757
758 $ hg clone -r0 binarysource binarydest
758 $ hg clone -r0 binarysource binarydest
759 adding changesets
759 adding changesets
760 adding manifests
760 adding manifests
761 adding file changes
761 adding file changes
762 added 1 changesets with 1 changes to 1 files
762 added 1 changesets with 1 changes to 1 files
763 updating to branch default
763 updating to branch default
764 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
764 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
765 $ cd binarydest
765 $ cd binarydest
766 $ cp ../binarysource/b b
766 $ cp ../binarysource/b b
767 $ hg ci -Am addb2 b
767 $ hg ci -Am addb2 b
768 $ hg transplant -s ../binarysource 2
768 $ hg transplant -s ../binarysource 2
769 searching for changes
769 searching for changes
770 applying 7a7d57e15850
770 applying 7a7d57e15850
771 skipping emptied changeset 7a7d57e15850
771 skipping emptied changeset 7a7d57e15850
772
772
773 Test empty result in --continue
773 Test empty result in --continue
774
774
775 $ hg transplant -s ../binarysource 1
775 $ hg transplant -s ../binarysource 1
776 searching for changes
776 searching for changes
777 applying 645035761929
777 applying 645035761929
778 file b already exists
778 file b already exists
779 1 out of 1 hunks FAILED -- saving rejects to file b.rej
779 1 out of 1 hunks FAILED -- saving rejects to file b.rej
780 patch failed to apply
780 patch failed to apply
781 abort: fix up the merge and run hg transplant --continue
781 abort: fix up the merge and run hg transplant --continue
782 [255]
782 [255]
783 $ hg status
783 $ hg status
784 ? b.rej
784 ? b.rej
785 $ hg transplant --continue
785 $ hg transplant --continue
786 645035761929 skipped due to empty diff
786 645035761929 skipped due to empty diff
787
787
788 $ cd ..
788 $ cd ..
789
789
790 Explicitly kill daemons to let the test exit on Windows
790 Explicitly kill daemons to let the test exit on Windows
791
791
792 $ killdaemons.py
792 $ killdaemons.py
793
793
794 Test that patch-ed files are treated as "modified", when transplant is
794 Test that patch-ed files are treated as "modified", when transplant is
795 aborted by failure of patching, even if none of mode, size and
795 aborted by failure of patching, even if none of mode, size and
796 timestamp of them isn't changed on the filesystem (see also issue4583)
796 timestamp of them isn't changed on the filesystem (see also issue4583)
797
797
798 $ cd t
798 $ cd t
799
799
800 $ cat > $TESTTMP/abort.py <<EOF
800 $ cat > $TESTTMP/abort.py <<EOF
801 > # emulate that patch.patch() is aborted at patching on "abort" file
801 > # emulate that patch.patch() is aborted at patching on "abort" file
802 > from mercurial import extensions, patch as patchmod
802 > from mercurial import extensions, patch as patchmod
803 > def patch(orig, ui, repo, patchname,
803 > def patch(orig, ui, repo, patchname,
804 > strip=1, prefix='', files=None,
804 > strip=1, prefix='', files=None,
805 > eolmode='strict', similarity=0):
805 > eolmode='strict', similarity=0):
806 > if files is None:
806 > if files is None:
807 > files = set()
807 > files = set()
808 > r = orig(ui, repo, patchname,
808 > r = orig(ui, repo, patchname,
809 > strip=strip, prefix=prefix, files=files,
809 > strip=strip, prefix=prefix, files=files,
810 > eolmode=eolmode, similarity=similarity)
810 > eolmode=eolmode, similarity=similarity)
811 > if 'abort' in files:
811 > if 'abort' in files:
812 > raise patchmod.PatchError('intentional error while patching')
812 > raise patchmod.PatchError('intentional error while patching')
813 > return r
813 > return r
814 > def extsetup(ui):
814 > def extsetup(ui):
815 > extensions.wrapfunction(patchmod, 'patch', patch)
815 > extensions.wrapfunction(patchmod, 'patch', patch)
816 > EOF
816 > EOF
817
817
818 $ echo X1 > r1
818 $ echo X1 > r1
819 $ hg diff --nodates r1
819 $ hg diff --nodates r1
820 diff -r a53251cdf717 r1
820 diff -r a53251cdf717 r1
821 --- a/r1
821 --- a/r1
822 +++ b/r1
822 +++ b/r1
823 @@ -1,1 +1,1 @@
823 @@ -1,1 +1,1 @@
824 -r1
824 -r1
825 +X1
825 +X1
826 $ hg commit -m "X1 as r1"
826 $ hg commit -m "X1 as r1"
827
827
828 $ echo 'marking to abort patching' > abort
828 $ echo 'marking to abort patching' > abort
829 $ hg add abort
829 $ hg add abort
830 $ echo Y1 > r1
830 $ echo Y1 > r1
831 $ hg diff --nodates r1
831 $ hg diff --nodates r1
832 diff -r 22c515968f13 r1
832 diff -r 22c515968f13 r1
833 --- a/r1
833 --- a/r1
834 +++ b/r1
834 +++ b/r1
835 @@ -1,1 +1,1 @@
835 @@ -1,1 +1,1 @@
836 -X1
836 -X1
837 +Y1
837 +Y1
838 $ hg commit -m "Y1 as r1"
838 $ hg commit -m "Y1 as r1"
839
839
840 $ hg update -q -C d11e3596cc1a
840 $ hg update -q -C d11e3596cc1a
841 $ cat r1
841 $ cat r1
842 r1
842 r1
843
843
844 $ cat >> .hg/hgrc <<EOF
844 $ cat >> .hg/hgrc <<EOF
845 > [fakedirstatewritetime]
845 > [fakedirstatewritetime]
846 > # emulate invoking dirstate.write() via repo.status() or markcommitted()
846 > # emulate invoking dirstate.write() via repo.status() or markcommitted()
847 > # at 2000-01-01 00:00
847 > # at 2000-01-01 00:00
848 > fakenow = 200001010000
848 > fakenow = 200001010000
849 >
849 >
850 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
850 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
851 > [fakepatchtime]
851 > [fakepatchtime]
852 > fakenow = 200001010000
852 > fakenow = 200001010000
853 >
853 >
854 > [extensions]
854 > [extensions]
855 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
855 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
856 > fakepatchtime = $TESTDIR/fakepatchtime.py
856 > fakepatchtime = $TESTDIR/fakepatchtime.py
857 > abort = $TESTTMP/abort.py
857 > abort = $TESTTMP/abort.py
858 > EOF
858 > EOF
859 $ hg transplant "22c515968f13::"
859 $ hg transplant "22c515968f13::"
860 applying 22c515968f13
860 applying 22c515968f13
861 22c515968f13 transplanted to * (glob)
861 22c515968f13 transplanted to * (glob)
862 applying e38700ba9dd3
862 applying e38700ba9dd3
863 intentional error while patching
863 intentional error while patching
864 abort: fix up the merge and run hg transplant --continue
864 abort: fix up the merge and run hg transplant --continue
865 [255]
865 [255]
866 $ cat >> .hg/hgrc <<EOF
866 $ cat >> .hg/hgrc <<EOF
867 > [hooks]
867 > [hooks]
868 > fakedirstatewritetime = !
868 > fakedirstatewritetime = !
869 > fakepatchtime = !
869 > fakepatchtime = !
870 > [extensions]
870 > abort = !
871 > abort = !
871 > EOF
872 > EOF
872
873
873 $ cat r1
874 $ cat r1
874 Y1
875 Y1
875 $ hg debugstate | grep ' r1$'
876 $ hg debugstate | grep ' r1$'
876 n 644 3 unset r1
877 n 644 3 unset r1
877 $ hg status -A r1
878 $ hg status -A r1
878 M r1
879 M r1
879
880
881 Test that rollback by unexpected failure after transplanting the first
882 revision restores dirstate correctly.
883
884 $ hg rollback -q
885 $ rm -f abort
886 $ hg update -q -C d11e3596cc1a
887 $ hg parents -T "{node|short}\n"
888 d11e3596cc1a
889 $ hg status -A
890 C r1
891 C r2
892
893 $ cat >> .hg/hgrc <<EOF
894 > [hooks]
895 > # emulate failure at transplanting the 2nd revision
896 > pretxncommit.abort = test ! -f abort
897 > EOF
898 $ hg transplant "22c515968f13::"
899 applying 22c515968f13
900 22c515968f13 transplanted to * (glob)
901 applying e38700ba9dd3
902 transaction abort!
903 rollback completed
904 abort: pretxncommit.abort hook exited with status 1
905 [255]
906 $ cat >> .hg/hgrc <<EOF
907 > [hooks]
908 > pretxncommit.abort = !
909 > EOF
910
911 $ hg parents -T "{node|short}\n"
912 d11e3596cc1a
913 $ hg status -A
914 M r1
915 ? abort
916 C r2
917
880 $ cd ..
918 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now