##// END OF EJS Templates
transplant: recover added/removed files after failed application
Brendan Cully -
r3726:752884db default
parent child Browse files
Show More
@@ -1,563 +1,565 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from mercurial.demandload import *
8 from mercurial.demandload import *
9 from mercurial.i18n import gettext as _
9 from mercurial.i18n import gettext as _
10 demandload(globals(), 'os tempfile')
10 demandload(globals(), 'os tempfile')
11 demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch')
11 demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch')
12 demandload(globals(), 'mercurial:revlog,util')
12 demandload(globals(), 'mercurial:revlog,util')
13
13
14 '''patch transplanting tool
14 '''patch transplanting tool
15
15
16 This extension allows you to transplant patches from another branch.
16 This extension allows you to transplant patches from another branch.
17
17
18 Transplanted patches are recorded in .hg/transplant/transplants, as a map
18 Transplanted patches are recorded in .hg/transplant/transplants, as a map
19 from a changeset hash to its hash in the source repository.
19 from a changeset hash to its hash in the source repository.
20 '''
20 '''
21
21
22 class transplantentry:
22 class transplantentry:
23 def __init__(self, lnode, rnode):
23 def __init__(self, lnode, rnode):
24 self.lnode = lnode
24 self.lnode = lnode
25 self.rnode = rnode
25 self.rnode = rnode
26
26
27 class transplants:
27 class transplants:
28 def __init__(self, path=None, transplantfile=None, opener=None):
28 def __init__(self, path=None, transplantfile=None, opener=None):
29 self.path = path
29 self.path = path
30 self.transplantfile = transplantfile
30 self.transplantfile = transplantfile
31 self.opener = opener
31 self.opener = opener
32
32
33 if not opener:
33 if not opener:
34 self.opener = util.opener(self.path)
34 self.opener = util.opener(self.path)
35 self.transplants = []
35 self.transplants = []
36 self.dirty = False
36 self.dirty = False
37 self.read()
37 self.read()
38
38
39 def read(self):
39 def read(self):
40 abspath = os.path.join(self.path, self.transplantfile)
40 abspath = os.path.join(self.path, self.transplantfile)
41 if self.transplantfile and os.path.exists(abspath):
41 if self.transplantfile and os.path.exists(abspath):
42 for line in self.opener(self.transplantfile).read().splitlines():
42 for line in self.opener(self.transplantfile).read().splitlines():
43 lnode, rnode = map(revlog.bin, line.split(':'))
43 lnode, rnode = map(revlog.bin, line.split(':'))
44 self.transplants.append(transplantentry(lnode, rnode))
44 self.transplants.append(transplantentry(lnode, rnode))
45
45
46 def write(self):
46 def write(self):
47 if self.dirty and self.transplantfile:
47 if self.dirty and self.transplantfile:
48 if not os.path.isdir(self.path):
48 if not os.path.isdir(self.path):
49 os.mkdir(self.path)
49 os.mkdir(self.path)
50 fp = self.opener(self.transplantfile, 'w')
50 fp = self.opener(self.transplantfile, 'w')
51 for c in self.transplants:
51 for c in self.transplants:
52 l, r = map(revlog.hex, (c.lnode, c.rnode))
52 l, r = map(revlog.hex, (c.lnode, c.rnode))
53 fp.write(l + ':' + r + '\n')
53 fp.write(l + ':' + r + '\n')
54 fp.close()
54 fp.close()
55 self.dirty = False
55 self.dirty = False
56
56
57 def get(self, rnode):
57 def get(self, rnode):
58 return [t for t in self.transplants if t.rnode == rnode]
58 return [t for t in self.transplants if t.rnode == rnode]
59
59
60 def set(self, lnode, rnode):
60 def set(self, lnode, rnode):
61 self.transplants.append(transplantentry(lnode, rnode))
61 self.transplants.append(transplantentry(lnode, rnode))
62 self.dirty = True
62 self.dirty = True
63
63
64 def remove(self, transplant):
64 def remove(self, transplant):
65 del self.transplants[self.transplants.index(transplant)]
65 del self.transplants[self.transplants.index(transplant)]
66 self.dirty = True
66 self.dirty = True
67
67
68 class transplanter:
68 class transplanter:
69 def __init__(self, ui, repo):
69 def __init__(self, ui, repo):
70 self.ui = ui
70 self.ui = ui
71 self.path = repo.join('transplant')
71 self.path = repo.join('transplant')
72 self.opener = util.opener(self.path)
72 self.opener = util.opener(self.path)
73 self.transplants = transplants(self.path, 'transplants', opener=self.opener)
73 self.transplants = transplants(self.path, 'transplants', opener=self.opener)
74
74
75 def applied(self, repo, node, parent):
75 def applied(self, repo, node, parent):
76 '''returns True if a node is already an ancestor of parent
76 '''returns True if a node is already an ancestor of parent
77 or has already been transplanted'''
77 or has already been transplanted'''
78 if hasnode(repo, node):
78 if hasnode(repo, node):
79 if node in repo.changelog.reachable(parent, stop=node):
79 if node in repo.changelog.reachable(parent, stop=node):
80 return True
80 return True
81 for t in self.transplants.get(node):
81 for t in self.transplants.get(node):
82 # it might have been stripped
82 # it might have been stripped
83 if not hasnode(repo, t.lnode):
83 if not hasnode(repo, t.lnode):
84 self.transplants.remove(t)
84 self.transplants.remove(t)
85 return False
85 return False
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 return True
87 return True
88 return False
88 return False
89
89
90 def apply(self, repo, source, revmap, merges, opts={}):
90 def apply(self, repo, source, revmap, merges, opts={}):
91 '''apply the revisions in revmap one by one in revision order'''
91 '''apply the revisions in revmap one by one in revision order'''
92 revs = revmap.keys()
92 revs = revmap.keys()
93 revs.sort()
93 revs.sort()
94
94
95 p1, p2 = repo.dirstate.parents()
95 p1, p2 = repo.dirstate.parents()
96 pulls = []
96 pulls = []
97 diffopts = patch.diffopts(self.ui, opts)
97 diffopts = patch.diffopts(self.ui, opts)
98 diffopts.git = True
98 diffopts.git = True
99
99
100 lock = repo.lock()
100 lock = repo.lock()
101 wlock = repo.wlock()
101 wlock = repo.wlock()
102 try:
102 try:
103 for rev in revs:
103 for rev in revs:
104 node = revmap[rev]
104 node = revmap[rev]
105 revstr = '%s:%s' % (rev, revlog.short(node))
105 revstr = '%s:%s' % (rev, revlog.short(node))
106
106
107 if self.applied(repo, node, p1):
107 if self.applied(repo, node, p1):
108 self.ui.warn(_('skipping already applied revision %s\n') %
108 self.ui.warn(_('skipping already applied revision %s\n') %
109 revstr)
109 revstr)
110 continue
110 continue
111
111
112 parents = source.changelog.parents(node)
112 parents = source.changelog.parents(node)
113 if not opts.get('filter'):
113 if not opts.get('filter'):
114 # If the changeset parent is the same as the wdir's parent,
114 # If the changeset parent is the same as the wdir's parent,
115 # just pull it.
115 # just pull it.
116 if parents[0] == p1:
116 if parents[0] == p1:
117 pulls.append(node)
117 pulls.append(node)
118 p1 = node
118 p1 = node
119 continue
119 continue
120 if pulls:
120 if pulls:
121 if source != repo:
121 if source != repo:
122 repo.pull(source, heads=pulls, lock=lock)
122 repo.pull(source, heads=pulls, lock=lock)
123 merge.update(repo, pulls[-1], wlock=wlock)
123 merge.update(repo, pulls[-1], wlock=wlock)
124 p1, p2 = repo.dirstate.parents()
124 p1, p2 = repo.dirstate.parents()
125 pulls = []
125 pulls = []
126
126
127 domerge = False
127 domerge = False
128 if node in merges:
128 if node in merges:
129 # pulling all the merge revs at once would mean we couldn't
129 # pulling all the merge revs at once would mean we couldn't
130 # transplant after the latest even if transplants before them
130 # transplant after the latest even if transplants before them
131 # fail.
131 # fail.
132 domerge = True
132 domerge = True
133 if not hasnode(repo, node):
133 if not hasnode(repo, node):
134 repo.pull(source, heads=[node], lock=lock)
134 repo.pull(source, heads=[node], lock=lock)
135
135
136 if parents[1] != revlog.nullid:
136 if parents[1] != revlog.nullid:
137 self.ui.note(_('skipping merge changeset %s:%s\n')
137 self.ui.note(_('skipping merge changeset %s:%s\n')
138 % (rev, revlog.short(node)))
138 % (rev, revlog.short(node)))
139 patchfile = None
139 patchfile = None
140 else:
140 else:
141 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
141 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
142 fp = os.fdopen(fd, 'w')
142 fp = os.fdopen(fd, 'w')
143 patch.export(source, [node], fp=fp, opts=diffopts)
143 patch.export(source, [node], fp=fp, opts=diffopts)
144 fp.close()
144 fp.close()
145
145
146 del revmap[rev]
146 del revmap[rev]
147 if patchfile or domerge:
147 if patchfile or domerge:
148 try:
148 try:
149 n = self.applyone(repo, node, source.changelog.read(node),
149 n = self.applyone(repo, node, source.changelog.read(node),
150 patchfile, merge=domerge,
150 patchfile, merge=domerge,
151 log=opts.get('log'),
151 log=opts.get('log'),
152 filter=opts.get('filter'),
152 filter=opts.get('filter'),
153 lock=lock, wlock=wlock)
153 lock=lock, wlock=wlock)
154 if domerge:
154 if domerge:
155 self.ui.status(_('%s merged at %s\n') % (revstr,
155 self.ui.status(_('%s merged at %s\n') % (revstr,
156 revlog.short(n)))
156 revlog.short(n)))
157 else:
157 else:
158 self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node),
158 self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node),
159 revlog.short(n)))
159 revlog.short(n)))
160 finally:
160 finally:
161 if patchfile:
161 if patchfile:
162 os.unlink(patchfile)
162 os.unlink(patchfile)
163 if pulls:
163 if pulls:
164 repo.pull(source, heads=pulls, lock=lock)
164 repo.pull(source, heads=pulls, lock=lock)
165 merge.update(repo, pulls[-1], wlock=wlock)
165 merge.update(repo, pulls[-1], wlock=wlock)
166 finally:
166 finally:
167 self.saveseries(revmap, merges)
167 self.saveseries(revmap, merges)
168 self.transplants.write()
168 self.transplants.write()
169
169
170 def filter(self, filter, changelog, patchfile):
170 def filter(self, filter, changelog, patchfile):
171 '''arbitrarily rewrite changeset before applying it'''
171 '''arbitrarily rewrite changeset before applying it'''
172
172
173 self.ui.status('filtering %s' % patchfile)
173 self.ui.status('filtering %s' % patchfile)
174 util.system('%s %s' % (filter, util.shellquote(patchfile)),
174 util.system('%s %s' % (filter, util.shellquote(patchfile)),
175 environ={'HGUSER': changelog[1]},
175 environ={'HGUSER': changelog[1]},
176 onerr=util.Abort, errprefix=_('filter failed'))
176 onerr=util.Abort, errprefix=_('filter failed'))
177
177
178 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
178 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
179 filter=None, lock=None, wlock=None):
179 filter=None, lock=None, wlock=None):
180 '''apply the patch in patchfile to the repository as a transplant'''
180 '''apply the patch in patchfile to the repository as a transplant'''
181 (manifest, user, (time, timezone), files, message) = cl[:5]
181 (manifest, user, (time, timezone), files, message) = cl[:5]
182 date = "%d %d" % (time, timezone)
182 date = "%d %d" % (time, timezone)
183 extra = {'transplant_source': node}
183 extra = {'transplant_source': node}
184 if filter:
184 if filter:
185 self.filter(filter, cl, patchfile)
185 self.filter(filter, cl, patchfile)
186 patchfile, message, user, date = patch.extract(self.ui, file(patchfile))
186 patchfile, message, user, date = patch.extract(self.ui, file(patchfile))
187
187
188 if log:
188 if log:
189 message += '\n(transplanted from %s)' % revlog.hex(node)
189 message += '\n(transplanted from %s)' % revlog.hex(node)
190
190
191 self.ui.status(_('applying %s\n') % revlog.short(node))
191 self.ui.status(_('applying %s\n') % revlog.short(node))
192 self.ui.note('%s %s\n%s\n' % (user, date, message))
192 self.ui.note('%s %s\n%s\n' % (user, date, message))
193
193
194 if not patchfile and not merge:
194 if not patchfile and not merge:
195 raise util.Abort(_('can only omit patchfile if merging'))
195 raise util.Abort(_('can only omit patchfile if merging'))
196 if patchfile:
196 if patchfile:
197 try:
197 try:
198 files = {}
198 files = {}
199 fuzz = patch.patch(patchfile, self.ui, cwd=repo.root,
199 try:
200 files=files)
200 fuzz = patch.patch(patchfile, self.ui, cwd=repo.root,
201 if not files:
201 files=files)
202 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
202 if not files:
203 return
203 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
204 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
204 return
205 finally:
206 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
205 if filter:
207 if filter:
206 os.unlink(patchfile)
208 os.unlink(patchfile)
207 except Exception, inst:
209 except Exception, inst:
208 if filter:
210 if filter:
209 os.unlink(patchfile)
211 os.unlink(patchfile)
210 p1 = repo.dirstate.parents()[0]
212 p1 = repo.dirstate.parents()[0]
211 p2 = node
213 p2 = node
212 self.log(user, date, message, p1, p2, merge=merge)
214 self.log(user, date, message, p1, p2, merge=merge)
213 self.ui.write(str(inst) + '\n')
215 self.ui.write(str(inst) + '\n')
214 raise util.Abort(_('Fix up the merge and run hg transplant --continue'))
216 raise util.Abort(_('Fix up the merge and run hg transplant --continue'))
215 else:
217 else:
216 files = None
218 files = None
217 if merge:
219 if merge:
218 p1, p2 = repo.dirstate.parents()
220 p1, p2 = repo.dirstate.parents()
219 repo.dirstate.setparents(p1, node)
221 repo.dirstate.setparents(p1, node)
220
222
221 n = repo.commit(files, message, user, date, lock=lock, wlock=wlock,
223 n = repo.commit(files, message, user, date, lock=lock, wlock=wlock,
222 extra=extra)
224 extra=extra)
223 if not merge:
225 if not merge:
224 self.transplants.set(n, node)
226 self.transplants.set(n, node)
225
227
226 return n
228 return n
227
229
228 def resume(self, repo, source, opts=None):
230 def resume(self, repo, source, opts=None):
229 '''recover last transaction and apply remaining changesets'''
231 '''recover last transaction and apply remaining changesets'''
230 if os.path.exists(os.path.join(self.path, 'journal')):
232 if os.path.exists(os.path.join(self.path, 'journal')):
231 n, node = self.recover(repo)
233 n, node = self.recover(repo)
232 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
234 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
233 revlog.short(n)))
235 revlog.short(n)))
234 seriespath = os.path.join(self.path, 'series')
236 seriespath = os.path.join(self.path, 'series')
235 if not os.path.exists(seriespath):
237 if not os.path.exists(seriespath):
236 return
238 return
237 nodes, merges = self.readseries()
239 nodes, merges = self.readseries()
238 revmap = {}
240 revmap = {}
239 for n in nodes:
241 for n in nodes:
240 revmap[source.changelog.rev(n)] = n
242 revmap[source.changelog.rev(n)] = n
241 os.unlink(seriespath)
243 os.unlink(seriespath)
242
244
243 self.apply(repo, source, revmap, merges, opts)
245 self.apply(repo, source, revmap, merges, opts)
244
246
245 def recover(self, repo):
247 def recover(self, repo):
246 '''commit working directory using journal metadata'''
248 '''commit working directory using journal metadata'''
247 node, user, date, message, parents = self.readlog()
249 node, user, date, message, parents = self.readlog()
248 merge = len(parents) == 2
250 merge = len(parents) == 2
249
251
250 if not user or not date or not message or not parents[0]:
252 if not user or not date or not message or not parents[0]:
251 raise util.Abort(_('transplant log file is corrupt'))
253 raise util.Abort(_('transplant log file is corrupt'))
252
254
253 wlock = repo.wlock()
255 wlock = repo.wlock()
254 p1, p2 = repo.dirstate.parents()
256 p1, p2 = repo.dirstate.parents()
255 if p1 != parents[0]:
257 if p1 != parents[0]:
256 raise util.Abort(_('working dir not at transplant parent %s') %
258 raise util.Abort(_('working dir not at transplant parent %s') %
257 revlog.hex(parents[0]))
259 revlog.hex(parents[0]))
258 if merge:
260 if merge:
259 repo.dirstate.setparents(p1, parents[1])
261 repo.dirstate.setparents(p1, parents[1])
260 n = repo.commit(None, message, user, date, wlock=wlock)
262 n = repo.commit(None, message, user, date, wlock=wlock)
261 if not n:
263 if not n:
262 raise util.Abort(_('commit failed'))
264 raise util.Abort(_('commit failed'))
263 if not merge:
265 if not merge:
264 self.transplants.set(n, node)
266 self.transplants.set(n, node)
265 self.unlog()
267 self.unlog()
266
268
267 return n, node
269 return n, node
268
270
269 def readseries(self):
271 def readseries(self):
270 nodes = []
272 nodes = []
271 merges = []
273 merges = []
272 cur = nodes
274 cur = nodes
273 for line in self.opener('series').read().splitlines():
275 for line in self.opener('series').read().splitlines():
274 if line.startswith('# Merges'):
276 if line.startswith('# Merges'):
275 cur = merges
277 cur = merges
276 continue
278 continue
277 cur.append(revlog.bin(line))
279 cur.append(revlog.bin(line))
278
280
279 return (nodes, merges)
281 return (nodes, merges)
280
282
281 def saveseries(self, revmap, merges):
283 def saveseries(self, revmap, merges):
282 if not revmap:
284 if not revmap:
283 return
285 return
284
286
285 if not os.path.isdir(self.path):
287 if not os.path.isdir(self.path):
286 os.mkdir(self.path)
288 os.mkdir(self.path)
287 series = self.opener('series', 'w')
289 series = self.opener('series', 'w')
288 revs = revmap.keys()
290 revs = revmap.keys()
289 revs.sort()
291 revs.sort()
290 for rev in revs:
292 for rev in revs:
291 series.write(revlog.hex(revmap[rev]) + '\n')
293 series.write(revlog.hex(revmap[rev]) + '\n')
292 if merges:
294 if merges:
293 series.write('# Merges\n')
295 series.write('# Merges\n')
294 for m in merges:
296 for m in merges:
295 series.write(revlog.hex(m) + '\n')
297 series.write(revlog.hex(m) + '\n')
296 series.close()
298 series.close()
297
299
298 def log(self, user, date, message, p1, p2, merge=False):
300 def log(self, user, date, message, p1, p2, merge=False):
299 '''journal changelog metadata for later recover'''
301 '''journal changelog metadata for later recover'''
300
302
301 if not os.path.isdir(self.path):
303 if not os.path.isdir(self.path):
302 os.mkdir(self.path)
304 os.mkdir(self.path)
303 fp = self.opener('journal', 'w')
305 fp = self.opener('journal', 'w')
304 fp.write('# User %s\n' % user)
306 fp.write('# User %s\n' % user)
305 fp.write('# Date %s\n' % date)
307 fp.write('# Date %s\n' % date)
306 fp.write('# Node ID %s\n' % revlog.hex(p2))
308 fp.write('# Node ID %s\n' % revlog.hex(p2))
307 fp.write('# Parent ' + revlog.hex(p1) + '\n')
309 fp.write('# Parent ' + revlog.hex(p1) + '\n')
308 if merge:
310 if merge:
309 fp.write('# Parent ' + revlog.hex(p2) + '\n')
311 fp.write('# Parent ' + revlog.hex(p2) + '\n')
310 fp.write(message.rstrip() + '\n')
312 fp.write(message.rstrip() + '\n')
311 fp.close()
313 fp.close()
312
314
313 def readlog(self):
315 def readlog(self):
314 parents = []
316 parents = []
315 message = []
317 message = []
316 for line in self.opener('journal').read().splitlines():
318 for line in self.opener('journal').read().splitlines():
317 if line.startswith('# User '):
319 if line.startswith('# User '):
318 user = line[7:]
320 user = line[7:]
319 elif line.startswith('# Date '):
321 elif line.startswith('# Date '):
320 date = line[7:]
322 date = line[7:]
321 elif line.startswith('# Node ID '):
323 elif line.startswith('# Node ID '):
322 node = revlog.bin(line[10:])
324 node = revlog.bin(line[10:])
323 elif line.startswith('# Parent '):
325 elif line.startswith('# Parent '):
324 parents.append(revlog.bin(line[9:]))
326 parents.append(revlog.bin(line[9:]))
325 else:
327 else:
326 message.append(line)
328 message.append(line)
327 return (node, user, date, '\n'.join(message), parents)
329 return (node, user, date, '\n'.join(message), parents)
328
330
329 def unlog(self):
331 def unlog(self):
330 '''remove changelog journal'''
332 '''remove changelog journal'''
331 absdst = os.path.join(self.path, 'journal')
333 absdst = os.path.join(self.path, 'journal')
332 if os.path.exists(absdst):
334 if os.path.exists(absdst):
333 os.unlink(absdst)
335 os.unlink(absdst)
334
336
335 def transplantfilter(self, repo, source, root):
337 def transplantfilter(self, repo, source, root):
336 def matchfn(node):
338 def matchfn(node):
337 if self.applied(repo, node, root):
339 if self.applied(repo, node, root):
338 return False
340 return False
339 if source.changelog.parents(node)[1] != revlog.nullid:
341 if source.changelog.parents(node)[1] != revlog.nullid:
340 return False
342 return False
341 extra = source.changelog.read(node)[5]
343 extra = source.changelog.read(node)[5]
342 cnode = extra.get('transplant_source')
344 cnode = extra.get('transplant_source')
343 if cnode and self.applied(repo, cnode, root):
345 if cnode and self.applied(repo, cnode, root):
344 return False
346 return False
345 return True
347 return True
346
348
347 return matchfn
349 return matchfn
348
350
349 def hasnode(repo, node):
351 def hasnode(repo, node):
350 try:
352 try:
351 return repo.changelog.rev(node) != None
353 return repo.changelog.rev(node) != None
352 except revlog.RevlogError:
354 except revlog.RevlogError:
353 return False
355 return False
354
356
355 def browserevs(ui, repo, nodes, opts):
357 def browserevs(ui, repo, nodes, opts):
356 '''interactively transplant changesets'''
358 '''interactively transplant changesets'''
357 def browsehelp(ui):
359 def browsehelp(ui):
358 ui.write('y: transplant this changeset\n'
360 ui.write('y: transplant this changeset\n'
359 'n: skip this changeset\n'
361 'n: skip this changeset\n'
360 'm: merge at this changeset\n'
362 'm: merge at this changeset\n'
361 'p: show patch\n'
363 'p: show patch\n'
362 'c: commit selected changesets\n'
364 'c: commit selected changesets\n'
363 'q: cancel transplant\n'
365 'q: cancel transplant\n'
364 '?: show this help\n')
366 '?: show this help\n')
365
367
366 displayer = cmdutil.show_changeset(ui, repo, opts)
368 displayer = cmdutil.show_changeset(ui, repo, opts)
367 transplants = []
369 transplants = []
368 merges = []
370 merges = []
369 for node in nodes:
371 for node in nodes:
370 displayer.show(changenode=node)
372 displayer.show(changenode=node)
371 action = None
373 action = None
372 while not action:
374 while not action:
373 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
375 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
374 if action == '?':
376 if action == '?':
375 browsehelp(ui)
377 browsehelp(ui)
376 action = None
378 action = None
377 elif action == 'p':
379 elif action == 'p':
378 parent = repo.changelog.parents(node)[0]
380 parent = repo.changelog.parents(node)[0]
379 patch.diff(repo, parent, node)
381 patch.diff(repo, parent, node)
380 action = None
382 action = None
381 elif action not in ('y', 'n', 'm', 'c', 'q'):
383 elif action not in ('y', 'n', 'm', 'c', 'q'):
382 ui.write('no such option\n')
384 ui.write('no such option\n')
383 action = None
385 action = None
384 if action == 'y':
386 if action == 'y':
385 transplants.append(node)
387 transplants.append(node)
386 elif action == 'm':
388 elif action == 'm':
387 merges.append(node)
389 merges.append(node)
388 elif action == 'c':
390 elif action == 'c':
389 break
391 break
390 elif action == 'q':
392 elif action == 'q':
391 transplants = ()
393 transplants = ()
392 merges = ()
394 merges = ()
393 break
395 break
394 return (transplants, merges)
396 return (transplants, merges)
395
397
396 def transplant(ui, repo, *revs, **opts):
398 def transplant(ui, repo, *revs, **opts):
397 '''transplant changesets from another branch
399 '''transplant changesets from another branch
398
400
399 Selected changesets will be applied on top of the current working
401 Selected changesets will be applied on top of the current working
400 directory with the log of the original changeset. If --log is
402 directory with the log of the original changeset. If --log is
401 specified, log messages will have a comment appended of the form:
403 specified, log messages will have a comment appended of the form:
402
404
403 (transplanted from CHANGESETHASH)
405 (transplanted from CHANGESETHASH)
404
406
405 You can rewrite the changelog message with the --filter option.
407 You can rewrite the changelog message with the --filter option.
406 Its argument will be invoked with the current changelog message
408 Its argument will be invoked with the current changelog message
407 as $1 and the patch as $2.
409 as $1 and the patch as $2.
408
410
409 If --source is specified, selects changesets from the named
411 If --source is specified, selects changesets from the named
410 repository. If --branch is specified, selects changesets from the
412 repository. If --branch is specified, selects changesets from the
411 branch holding the named revision, up to that revision. If --all
413 branch holding the named revision, up to that revision. If --all
412 is specified, all changesets on the branch will be transplanted,
414 is specified, all changesets on the branch will be transplanted,
413 otherwise you will be prompted to select the changesets you want.
415 otherwise you will be prompted to select the changesets you want.
414
416
415 hg transplant --branch REVISION --all will rebase the selected branch
417 hg transplant --branch REVISION --all will rebase the selected branch
416 (up to the named revision) onto your current working directory.
418 (up to the named revision) onto your current working directory.
417
419
418 You can optionally mark selected transplanted changesets as
420 You can optionally mark selected transplanted changesets as
419 merge changesets. You will not be prompted to transplant any
421 merge changesets. You will not be prompted to transplant any
420 ancestors of a merged transplant, and you can merge descendants
422 ancestors of a merged transplant, and you can merge descendants
421 of them normally instead of transplanting them.
423 of them normally instead of transplanting them.
422
424
423 If no merges or revisions are provided, hg transplant will start
425 If no merges or revisions are provided, hg transplant will start
424 an interactive changeset browser.
426 an interactive changeset browser.
425
427
426 If a changeset application fails, you can fix the merge by hand and
428 If a changeset application fails, you can fix the merge by hand and
427 then resume where you left off by calling hg transplant --continue.
429 then resume where you left off by calling hg transplant --continue.
428 '''
430 '''
429 def getoneitem(opts, item, errmsg):
431 def getoneitem(opts, item, errmsg):
430 val = opts.get(item)
432 val = opts.get(item)
431 if val:
433 if val:
432 if len(val) > 1:
434 if len(val) > 1:
433 raise util.Abort(errmsg)
435 raise util.Abort(errmsg)
434 else:
436 else:
435 return val[0]
437 return val[0]
436
438
437 def getremotechanges(repo, url):
439 def getremotechanges(repo, url):
438 sourcerepo = ui.expandpath(url)
440 sourcerepo = ui.expandpath(url)
439 source = hg.repository(ui, sourcerepo)
441 source = hg.repository(ui, sourcerepo)
440 incoming = repo.findincoming(source, force=True)
442 incoming = repo.findincoming(source, force=True)
441 if not incoming:
443 if not incoming:
442 return (source, None, None)
444 return (source, None, None)
443
445
444 bundle = None
446 bundle = None
445 if not source.local():
447 if not source.local():
446 cg = source.changegroup(incoming, 'incoming')
448 cg = source.changegroup(incoming, 'incoming')
447 bundle = commands.write_bundle(cg, compress=False)
449 bundle = commands.write_bundle(cg, compress=False)
448 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
450 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
449
451
450 return (source, incoming, bundle)
452 return (source, incoming, bundle)
451
453
452 def incwalk(repo, incoming, branches, match=util.always):
454 def incwalk(repo, incoming, branches, match=util.always):
453 if not branches:
455 if not branches:
454 branches=None
456 branches=None
455 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
457 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
456 if match(node):
458 if match(node):
457 yield node
459 yield node
458
460
459 def transplantwalk(repo, root, branches, match=util.always):
461 def transplantwalk(repo, root, branches, match=util.always):
460 if not branches:
462 if not branches:
461 branches = repo.heads()
463 branches = repo.heads()
462 ancestors = []
464 ancestors = []
463 for branch in branches:
465 for branch in branches:
464 ancestors.append(repo.changelog.ancestor(root, branch))
466 ancestors.append(repo.changelog.ancestor(root, branch))
465 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
467 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
466 if match(node):
468 if match(node):
467 yield node
469 yield node
468
470
469 def checkopts(opts, revs):
471 def checkopts(opts, revs):
470 if opts.get('continue'):
472 if opts.get('continue'):
471 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
473 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
472 raise util.Abort(_('--continue is incompatible with branch, all or merge'))
474 raise util.Abort(_('--continue is incompatible with branch, all or merge'))
473 return
475 return
474 if not (opts.get('source') or revs or
476 if not (opts.get('source') or revs or
475 opts.get('merge') or opts.get('branch')):
477 opts.get('merge') or opts.get('branch')):
476 raise util.Abort(_('no source URL, branch tag or revision list provided'))
478 raise util.Abort(_('no source URL, branch tag or revision list provided'))
477 if opts.get('all'):
479 if opts.get('all'):
478 if not opts.get('branch'):
480 if not opts.get('branch'):
479 raise util.Abort(_('--all requires a branch revision'))
481 raise util.Abort(_('--all requires a branch revision'))
480 if revs:
482 if revs:
481 raise util.Abort(_('--all is incompatible with a revision list'))
483 raise util.Abort(_('--all is incompatible with a revision list'))
482
484
483 checkopts(opts, revs)
485 checkopts(opts, revs)
484
486
485 if not opts.get('log'):
487 if not opts.get('log'):
486 opts['log'] = ui.config('transplant', 'log')
488 opts['log'] = ui.config('transplant', 'log')
487 if not opts.get('filter'):
489 if not opts.get('filter'):
488 opts['filter'] = ui.config('transplant', 'filter')
490 opts['filter'] = ui.config('transplant', 'filter')
489
491
490 tp = transplanter(ui, repo)
492 tp = transplanter(ui, repo)
491
493
492 p1, p2 = repo.dirstate.parents()
494 p1, p2 = repo.dirstate.parents()
493 if p1 == revlog.nullid:
495 if p1 == revlog.nullid:
494 raise util.Abort(_('no revision checked out'))
496 raise util.Abort(_('no revision checked out'))
495 if not opts.get('continue'):
497 if not opts.get('continue'):
496 if p2 != revlog.nullid:
498 if p2 != revlog.nullid:
497 raise util.Abort(_('outstanding uncommitted merges'))
499 raise util.Abort(_('outstanding uncommitted merges'))
498 m, a, r, d = repo.status()[:4]
500 m, a, r, d = repo.status()[:4]
499 if m or a or r or d:
501 if m or a or r or d:
500 raise util.Abort(_('outstanding local changes'))
502 raise util.Abort(_('outstanding local changes'))
501
503
502 bundle = None
504 bundle = None
503 source = opts.get('source')
505 source = opts.get('source')
504 if source:
506 if source:
505 (source, incoming, bundle) = getremotechanges(repo, source)
507 (source, incoming, bundle) = getremotechanges(repo, source)
506 else:
508 else:
507 source = repo
509 source = repo
508
510
509 try:
511 try:
510 if opts.get('continue'):
512 if opts.get('continue'):
511 tp.resume(repo, source, opts)
513 tp.resume(repo, source, opts)
512 return
514 return
513
515
514 tf=tp.transplantfilter(repo, source, p1)
516 tf=tp.transplantfilter(repo, source, p1)
515 if opts.get('prune'):
517 if opts.get('prune'):
516 prune = [source.lookup(r)
518 prune = [source.lookup(r)
517 for r in cmdutil.revrange(source, opts.get('prune'))]
519 for r in cmdutil.revrange(source, opts.get('prune'))]
518 matchfn = lambda x: tf(x) and x not in prune
520 matchfn = lambda x: tf(x) and x not in prune
519 else:
521 else:
520 matchfn = tf
522 matchfn = tf
521 branches = map(source.lookup, opts.get('branch', ()))
523 branches = map(source.lookup, opts.get('branch', ()))
522 merges = map(source.lookup, opts.get('merge', ()))
524 merges = map(source.lookup, opts.get('merge', ()))
523 revmap = {}
525 revmap = {}
524 if revs:
526 if revs:
525 for r in cmdutil.revrange(source, revs):
527 for r in cmdutil.revrange(source, revs):
526 revmap[int(r)] = source.lookup(r)
528 revmap[int(r)] = source.lookup(r)
527 elif opts.get('all') or not merges:
529 elif opts.get('all') or not merges:
528 if source != repo:
530 if source != repo:
529 alltransplants = incwalk(source, incoming, branches, match=matchfn)
531 alltransplants = incwalk(source, incoming, branches, match=matchfn)
530 else:
532 else:
531 alltransplants = transplantwalk(source, p1, branches, match=matchfn)
533 alltransplants = transplantwalk(source, p1, branches, match=matchfn)
532 if opts.get('all'):
534 if opts.get('all'):
533 revs = alltransplants
535 revs = alltransplants
534 else:
536 else:
535 revs, newmerges = browserevs(ui, source, alltransplants, opts)
537 revs, newmerges = browserevs(ui, source, alltransplants, opts)
536 merges.extend(newmerges)
538 merges.extend(newmerges)
537 for r in revs:
539 for r in revs:
538 revmap[source.changelog.rev(r)] = r
540 revmap[source.changelog.rev(r)] = r
539 for r in merges:
541 for r in merges:
540 revmap[source.changelog.rev(r)] = r
542 revmap[source.changelog.rev(r)] = r
541
543
542 revs = revmap.keys()
544 revs = revmap.keys()
543 revs.sort()
545 revs.sort()
544 pulls = []
546 pulls = []
545
547
546 tp.apply(repo, source, revmap, merges, opts)
548 tp.apply(repo, source, revmap, merges, opts)
547 finally:
549 finally:
548 if bundle:
550 if bundle:
549 os.unlink(bundle)
551 os.unlink(bundle)
550
552
551 cmdtable = {
553 cmdtable = {
552 "transplant":
554 "transplant":
553 (transplant,
555 (transplant,
554 [('s', 'source', '', _('pull patches from REPOSITORY')),
556 [('s', 'source', '', _('pull patches from REPOSITORY')),
555 ('b', 'branch', [], _('pull patches from branch BRANCH')),
557 ('b', 'branch', [], _('pull patches from branch BRANCH')),
556 ('a', 'all', None, _('pull all changesets up to BRANCH')),
558 ('a', 'all', None, _('pull all changesets up to BRANCH')),
557 ('p', 'prune', [], _('skip over REV')),
559 ('p', 'prune', [], _('skip over REV')),
558 ('m', 'merge', [], _('merge at REV')),
560 ('m', 'merge', [], _('merge at REV')),
559 ('', 'log', None, _('append transplant info to log message')),
561 ('', 'log', None, _('append transplant info to log message')),
560 ('c', 'continue', None, _('continue last transplant session after repair')),
562 ('c', 'continue', None, _('continue last transplant session after repair')),
561 ('', 'filter', '', _('filter changesets through FILTER'))],
563 ('', 'filter', '', _('filter changesets through FILTER'))],
562 _('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...'))
564 _('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...'))
563 }
565 }
@@ -1,86 +1,90 b''
1 #!/bin/sh
1 #!/bin/sh
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 echo r2 > r2
12 echo r2 > r2
13 hg ci -Amr2 -d'1 0'
13 hg ci -Amr2 -d'1 0'
14 hg up 0
14 hg up 0
15
15
16 echo b1 > b1
16 echo b1 > b1
17 hg ci -Amb1 -d '0 0'
17 hg ci -Amb1 -d '0 0'
18 echo b2 > b2
18 echo b2 > b2
19 hg ci -Amb2 -d '1 0'
19 hg ci -Amb2 -d '1 0'
20 echo b3 > b3
20 echo b3 > b3
21 hg ci -Amb3 -d '2 0'
21 hg ci -Amb3 -d '2 0'
22
22
23 hg log --template '{rev} {parents} {desc}\n'
23 hg log --template '{rev} {parents} {desc}\n'
24
24
25 cd ..
25 cd ..
26 hg clone t rebase
26 hg clone t rebase
27 cd rebase
27 cd rebase
28
28
29 hg up -C 1
29 hg up -C 1
30 echo '% rebase b onto r1'
30 echo '% rebase b onto r1'
31 hg transplant -a -b tip
31 hg transplant -a -b tip
32 hg log --template '{rev} {parents} {desc}\n'
32 hg log --template '{rev} {parents} {desc}\n'
33
33
34 cd ..
34 cd ..
35 hg clone t prune
35 hg clone t prune
36 cd prune
36 cd prune
37
37
38 hg up -C 1
38 hg up -C 1
39 echo '% rebase b onto r1, skipping b2'
39 echo '% rebase b onto r1, skipping b2'
40 hg transplant -a -b tip -p 3
40 hg transplant -a -b tip -p 3
41 hg log --template '{rev} {parents} {desc}\n'
41 hg log --template '{rev} {parents} {desc}\n'
42
42
43 cd ..
43 cd ..
44 echo '% remote transplant'
44 echo '% remote transplant'
45 hg clone -r 1 t remote
45 hg clone -r 1 t remote
46 cd remote
46 cd remote
47 hg transplant --log -s ../t 2 4
47 hg transplant --log -s ../t 2 4
48 hg log --template '{rev} {parents} {desc}\n'
48 hg log --template '{rev} {parents} {desc}\n'
49
49
50 echo '% skip previous transplants'
50 echo '% skip previous transplants'
51 hg transplant -s ../t -a -b 4
51 hg transplant -s ../t -a -b 4
52 hg log --template '{rev} {parents} {desc}\n'
52 hg log --template '{rev} {parents} {desc}\n'
53
53
54 echo '% skip local changes transplanted to the source'
54 echo '% skip local changes transplanted to the source'
55 echo b4 > b4
55 echo b4 > b4
56 hg ci -Amb4 -d '3 0'
56 hg ci -Amb4 -d '3 0'
57 cd ..
57 cd ..
58 hg clone t pullback
58 hg clone t pullback
59 cd pullback
59 cd pullback
60 hg transplant -s ../remote -a -b tip
60 hg transplant -s ../remote -a -b tip
61
61
62 echo '% transplant --continue'
62 echo '% transplant --continue'
63 hg init ../tc
63 hg init ../tc
64 cd ../tc
64 cd ../tc
65 cat <<EOF > foo
65 cat <<EOF > foo
66 foo
66 foo
67 bar
67 bar
68 baz
68 baz
69 EOF
69 EOF
70 echo toremove > toremove
70 hg ci -Amfoo -d '0 0'
71 hg ci -Amfoo -d '0 0'
71 cat <<EOF > foo
72 cat <<EOF > foo
72 foo2
73 foo2
73 bar2
74 bar2
74 baz2
75 baz2
75 EOF
76 EOF
76 hg ci -mfoo2 -d '0 0'
77 rm toremove
78 echo added > added
79 hg ci -Amfoo2 -d '0 0'
77 echo bar > bar
80 echo bar > bar
78 hg ci -Ambar -d '0 0'
81 hg ci -Ambar -d '0 0'
79 echo bar2 >> bar
82 echo bar2 >> bar
80 hg ci -mbar2 -d '0 0'
83 hg ci -mbar2 -d '0 0'
81 hg up 0
84 hg up 0
82 echo foobar > foo
85 echo foobar > foo
83 hg ci -mfoobar -d '0 0'
86 hg ci -mfoobar -d '0 0'
84 hg transplant 1:3
87 hg transplant 1:3
85 echo merge > foo
88 echo merge > foo
86 hg transplant --continue
89 hg transplant --continue
90 hg locate
@@ -1,92 +1,98 b''
1 adding r1
1 adding r1
2 adding r2
2 adding r2
3 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 adding b1
4 adding b1
5 adding b2
5 adding b2
6 adding b3
6 adding b3
7 4 b3
7 4 b3
8 3 b2
8 3 b2
9 2 0:17ab29e464c6 b1
9 2 0:17ab29e464c6 b1
10 1 r2
10 1 r2
11 0 r1
11 0 r1
12 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
13 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
14 % rebase b onto r1
14 % rebase b onto r1
15 applying 37a1297eb21b
15 applying 37a1297eb21b
16 37a1297eb21b transplanted to e234d668f844
16 37a1297eb21b transplanted to e234d668f844
17 applying 722f4667af76
17 applying 722f4667af76
18 722f4667af76 transplanted to 539f377d78df
18 722f4667af76 transplanted to 539f377d78df
19 applying a53251cdf717
19 applying a53251cdf717
20 a53251cdf717 transplanted to ffd6818a3975
20 a53251cdf717 transplanted to ffd6818a3975
21 7 b3
21 7 b3
22 6 b2
22 6 b2
23 5 1:d11e3596cc1a b1
23 5 1:d11e3596cc1a b1
24 4 b3
24 4 b3
25 3 b2
25 3 b2
26 2 0:17ab29e464c6 b1
26 2 0:17ab29e464c6 b1
27 1 r2
27 1 r2
28 0 r1
28 0 r1
29 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
30 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
31 % rebase b onto r1, skipping b2
31 % rebase b onto r1, skipping b2
32 applying 37a1297eb21b
32 applying 37a1297eb21b
33 37a1297eb21b transplanted to e234d668f844
33 37a1297eb21b transplanted to e234d668f844
34 applying a53251cdf717
34 applying a53251cdf717
35 a53251cdf717 transplanted to 7275fda4d04f
35 a53251cdf717 transplanted to 7275fda4d04f
36 6 b3
36 6 b3
37 5 1:d11e3596cc1a b1
37 5 1:d11e3596cc1a b1
38 4 b3
38 4 b3
39 3 b2
39 3 b2
40 2 0:17ab29e464c6 b1
40 2 0:17ab29e464c6 b1
41 1 r2
41 1 r2
42 0 r1
42 0 r1
43 % remote transplant
43 % remote transplant
44 requesting all changes
44 requesting all changes
45 adding changesets
45 adding changesets
46 adding manifests
46 adding manifests
47 adding file changes
47 adding file changes
48 added 2 changesets with 2 changes to 2 files
48 added 2 changesets with 2 changes to 2 files
49 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 searching for changes
50 searching for changes
51 applying 37a1297eb21b
51 applying 37a1297eb21b
52 37a1297eb21b transplanted to c19cf0ccb069
52 37a1297eb21b transplanted to c19cf0ccb069
53 applying a53251cdf717
53 applying a53251cdf717
54 a53251cdf717 transplanted to f7fe5bf98525
54 a53251cdf717 transplanted to f7fe5bf98525
55 3 b3
55 3 b3
56 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
56 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
57 2 b1
57 2 b1
58 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
58 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
59 1 r2
59 1 r2
60 0 r1
60 0 r1
61 % skip previous transplants
61 % skip previous transplants
62 searching for changes
62 searching for changes
63 applying 722f4667af76
63 applying 722f4667af76
64 722f4667af76 transplanted to 47156cd86c0b
64 722f4667af76 transplanted to 47156cd86c0b
65 4 b2
65 4 b2
66 3 b3
66 3 b3
67 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
67 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
68 2 b1
68 2 b1
69 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
69 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
70 1 r2
70 1 r2
71 0 r1
71 0 r1
72 % skip local changes transplanted to the source
72 % skip local changes transplanted to the source
73 adding b4
73 adding b4
74 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 searching for changes
75 searching for changes
76 applying 4333daefcb15
76 applying 4333daefcb15
77 4333daefcb15 transplanted to 5f42c04e07cc
77 4333daefcb15 transplanted to 5f42c04e07cc
78 % transplant --continue
78 % transplant --continue
79 adding foo
79 adding foo
80 adding toremove
81 adding added
82 removing toremove
80 adding bar
83 adding bar
81 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
82 applying c029d661401c
85 applying a1e30dd1b8e7
83 foo
86 foo
84 Hunk #1 FAILED at 1.
87 Hunk #1 FAILED at 1.
85 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
88 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
86 patch command failed: exited with status 1
89 patch command failed: exited with status 1
87 abort: Fix up the merge and run hg transplant --continue
90 abort: Fix up the merge and run hg transplant --continue
88 c029d661401c transplanted as 1b09cda4cf1b
91 a1e30dd1b8e7 transplanted as e6d0b5145568
89 applying 67f0722fdc4f
92 applying 1739ac5f6139
90 67f0722fdc4f transplanted to faa04033bb41
93 1739ac5f6139 transplanted to 48f780141a79
91 applying 84c5126bd0d2
94 applying 0282d5fbbe02
92 84c5126bd0d2 transplanted to b35f4019ccc9
95 0282d5fbbe02 transplanted to 821d17b1a3ed
96 added
97 bar
98 foo
General Comments 0
You need to be logged in to leave comments. Login now