Mercurial > public > mercurial-scm > hg-stable
annotate hgext/transplant.py @ 4531:b51a8138292a
Avoid extra filelogs entries.
Right now, there are some situations in which localrepo.filecommit can
create filelog entries even though they're not needed. For example:
- permissions for a file have changed;
- qrefresh can create a filelog entry identical to its parent (see the
added test);
- convert-repo creates extra filelog entries in every merge where the
first parent has added files (for example, changeset ebebe9577a1a of
the kernel repo added extra filelog entries to files in the
arch/blackfin directory, even though the merge should only touch the
drivers/ata directory). This makes "hg log file" in a converted repo
less useful than it could be, since it may mention many merges that
don't actually touch that specific file.
They all come from the same basic problem: localrepo.commit (through
filecommit) creates new filelog entries for all files passed to it
(except for some cases during a merge).
Patch and test case provided by Benoit.
This should fix issue351.
author | Alexis S. L. Carvalho <alexis@cecm.usp.br> |
---|---|
date | Sat, 09 Jun 2007 01:04:28 -0300 |
parents | 96d8a56d4ef9 |
children | 63b9d2deed48 |
rev | line source |
---|---|
3714 | 1 # Patch transplanting extension for Mercurial |
2 # | |
3 # Copyright 2006 Brendan Cully <brendan@kublai.com> | |
4 # | |
5 # This software may be used and distributed according to the terms | |
6 # of the GNU General Public License, incorporated herein by reference. | |
7 | |
3891 | 8 from mercurial.i18n import _ |
3877
abaee83ce0a6
Replace demandload with new demandimport
Matt Mackall <mpm@selenic.com>
parents:
3831
diff
changeset
|
9 import os, tempfile |
4049
863465381028
transplant: update to current writebundle API
Brendan Cully <brendan@kublai.com>
parents:
4035
diff
changeset
|
10 from mercurial import bundlerepo, changegroup, cmdutil, commands, hg, merge |
863465381028
transplant: update to current writebundle API
Brendan Cully <brendan@kublai.com>
parents:
4035
diff
changeset
|
11 from mercurial import patch, revlog, util |
3714 | 12 |
13 '''patch transplanting tool | |
14 | |
15 This extension allows you to transplant patches from another branch. | |
16 | |
17 Transplanted patches are recorded in .hg/transplant/transplants, as a map | |
18 from a changeset hash to its hash in the source repository. | |
19 ''' | |
20 | |
21 class transplantentry: | |
22 def __init__(self, lnode, rnode): | |
23 self.lnode = lnode | |
24 self.rnode = rnode | |
25 | |
26 class transplants: | |
27 def __init__(self, path=None, transplantfile=None, opener=None): | |
28 self.path = path | |
29 self.transplantfile = transplantfile | |
30 self.opener = opener | |
31 | |
32 if not opener: | |
33 self.opener = util.opener(self.path) | |
34 self.transplants = [] | |
35 self.dirty = False | |
36 self.read() | |
37 | |
38 def read(self): | |
39 abspath = os.path.join(self.path, self.transplantfile) | |
40 if self.transplantfile and os.path.exists(abspath): | |
41 for line in self.opener(self.transplantfile).read().splitlines(): | |
42 lnode, rnode = map(revlog.bin, line.split(':')) | |
43 self.transplants.append(transplantentry(lnode, rnode)) | |
44 | |
45 def write(self): | |
46 if self.dirty and self.transplantfile: | |
47 if not os.path.isdir(self.path): | |
48 os.mkdir(self.path) | |
49 fp = self.opener(self.transplantfile, 'w') | |
50 for c in self.transplants: | |
51 l, r = map(revlog.hex, (c.lnode, c.rnode)) | |
52 fp.write(l + ':' + r + '\n') | |
53 fp.close() | |
54 self.dirty = False | |
55 | |
56 def get(self, rnode): | |
57 return [t for t in self.transplants if t.rnode == rnode] | |
58 | |
59 def set(self, lnode, rnode): | |
60 self.transplants.append(transplantentry(lnode, rnode)) | |
61 self.dirty = True | |
62 | |
63 def remove(self, transplant): | |
64 del self.transplants[self.transplants.index(transplant)] | |
65 self.dirty = True | |
66 | |
67 class transplanter: | |
68 def __init__(self, ui, repo): | |
69 self.ui = ui | |
70 self.path = repo.join('transplant') | |
71 self.opener = util.opener(self.path) | |
72 self.transplants = transplants(self.path, 'transplants', opener=self.opener) | |
73 | |
74 def applied(self, repo, node, parent): | |
75 '''returns True if a node is already an ancestor of parent | |
76 or has already been transplanted''' | |
77 if hasnode(repo, node): | |
78 if node in repo.changelog.reachable(parent, stop=node): | |
79 return True | |
80 for t in self.transplants.get(node): | |
81 # it might have been stripped | |
82 if not hasnode(repo, t.lnode): | |
83 self.transplants.remove(t) | |
84 return False | |
85 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode): | |
86 return True | |
87 return False | |
88 | |
89 def apply(self, repo, source, revmap, merges, opts={}): | |
90 '''apply the revisions in revmap one by one in revision order''' | |
91 revs = revmap.keys() | |
92 revs.sort() | |
93 | |
94 p1, p2 = repo.dirstate.parents() | |
95 pulls = [] | |
96 diffopts = patch.diffopts(self.ui, opts) | |
97 diffopts.git = True | |
98 | |
99 lock = repo.lock() | |
100 wlock = repo.wlock() | |
101 try: | |
102 for rev in revs: | |
103 node = revmap[rev] | |
104 revstr = '%s:%s' % (rev, revlog.short(node)) | |
105 | |
106 if self.applied(repo, node, p1): | |
107 self.ui.warn(_('skipping already applied revision %s\n') % | |
108 revstr) | |
109 continue | |
110 | |
111 parents = source.changelog.parents(node) | |
112 if not opts.get('filter'): | |
113 # If the changeset parent is the same as the wdir's parent, | |
114 # just pull it. | |
115 if parents[0] == p1: | |
116 pulls.append(node) | |
117 p1 = node | |
118 continue | |
119 if pulls: | |
120 if source != repo: | |
121 repo.pull(source, heads=pulls, lock=lock) | |
4035
c8f26bd24e86
Update transplant's merge.update call
Brendan Cully <brendan@kublai.com>
parents:
3991
diff
changeset
|
122 merge.update(repo, pulls[-1], False, False, None, |
c8f26bd24e86
Update transplant's merge.update call
Brendan Cully <brendan@kublai.com>
parents:
3991
diff
changeset
|
123 wlock=wlock) |
3714 | 124 p1, p2 = repo.dirstate.parents() |
125 pulls = [] | |
126 | |
127 domerge = False | |
128 if node in merges: | |
129 # pulling all the merge revs at once would mean we couldn't | |
130 # transplant after the latest even if transplants before them | |
131 # fail. | |
132 domerge = True | |
133 if not hasnode(repo, node): | |
134 repo.pull(source, heads=[node], lock=lock) | |
135 | |
136 if parents[1] != revlog.nullid: | |
137 self.ui.note(_('skipping merge changeset %s:%s\n') | |
138 % (rev, revlog.short(node))) | |
139 patchfile = None | |
140 else: | |
141 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-') | |
142 fp = os.fdopen(fd, 'w') | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
143 patch.diff(source, parents[0], node, fp=fp, opts=diffopts) |
3714 | 144 fp.close() |
145 | |
146 del revmap[rev] | |
147 if patchfile or domerge: | |
148 try: | |
149 n = self.applyone(repo, node, source.changelog.read(node), | |
150 patchfile, merge=domerge, | |
151 log=opts.get('log'), | |
152 filter=opts.get('filter'), | |
153 lock=lock, wlock=wlock) | |
4251
e76e52145c3d
transplant: fix ignoring empty changesets (eg after filter)
Brendan Cully <brendan@kublai.com>
parents:
4072
diff
changeset
|
154 if n and domerge: |
3714 | 155 self.ui.status(_('%s merged at %s\n') % (revstr, |
156 revlog.short(n))) | |
4251
e76e52145c3d
transplant: fix ignoring empty changesets (eg after filter)
Brendan Cully <brendan@kublai.com>
parents:
4072
diff
changeset
|
157 elif n: |
3714 | 158 self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node), |
159 revlog.short(n))) | |
160 finally: | |
161 if patchfile: | |
162 os.unlink(patchfile) | |
163 if pulls: | |
164 repo.pull(source, heads=pulls, lock=lock) | |
4035
c8f26bd24e86
Update transplant's merge.update call
Brendan Cully <brendan@kublai.com>
parents:
3991
diff
changeset
|
165 merge.update(repo, pulls[-1], False, False, None, wlock=wlock) |
3714 | 166 finally: |
167 self.saveseries(revmap, merges) | |
168 self.transplants.write() | |
169 | |
170 def filter(self, filter, changelog, patchfile): | |
171 '''arbitrarily rewrite changeset before applying it''' | |
172 | |
3752
f902f409cd81
transplant: "filtering %s\n"
Brendan Cully <brendan@kublai.com>
parents:
3726
diff
changeset
|
173 self.ui.status('filtering %s\n' % patchfile) |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
174 user, date, msg = (changelog[1], changelog[2], changelog[4]) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
175 |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
176 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-') |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
177 fp = os.fdopen(fd, 'w') |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
178 fp.write("# HG changeset patch\n") |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
179 fp.write("# User %s\n" % user) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
180 fp.write("# Date %d %d\n" % date) |
3831
2db191165e9a
transplant: don't add extra newlines to changelog entry in filter
Brendan Cully <brendan@kublai.com>
parents:
3759
diff
changeset
|
181 fp.write(changelog[4]) |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
182 fp.close() |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
183 |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
184 try: |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
185 util.system('%s %s %s' % (filter, util.shellquote(headerfile), |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
186 util.shellquote(patchfile)), |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
187 environ={'HGUSER': changelog[1]}, |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
188 onerr=util.Abort, errprefix=_('filter failed')) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
189 user, date, msg = self.parselog(file(headerfile))[1:4] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
190 finally: |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
191 os.unlink(headerfile) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
192 |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
193 return (user, date, msg) |
3714 | 194 |
195 def applyone(self, repo, node, cl, patchfile, merge=False, log=False, | |
196 filter=None, lock=None, wlock=None): | |
197 '''apply the patch in patchfile to the repository as a transplant''' | |
198 (manifest, user, (time, timezone), files, message) = cl[:5] | |
199 date = "%d %d" % (time, timezone) | |
200 extra = {'transplant_source': node} | |
201 if filter: | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
202 (user, date, message) = self.filter(filter, cl, patchfile) |
3714 | 203 |
204 if log: | |
205 message += '\n(transplanted from %s)' % revlog.hex(node) | |
206 | |
207 self.ui.status(_('applying %s\n') % revlog.short(node)) | |
208 self.ui.note('%s %s\n%s\n' % (user, date, message)) | |
209 | |
210 if not patchfile and not merge: | |
211 raise util.Abort(_('can only omit patchfile if merging')) | |
212 if patchfile: | |
213 try: | |
214 files = {} | |
3726
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
215 try: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
216 fuzz = patch.patch(patchfile, self.ui, cwd=repo.root, |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
217 files=files) |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
218 if not files: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
219 self.ui.warn(_('%s: empty changeset') % revlog.hex(node)) |
4251
e76e52145c3d
transplant: fix ignoring empty changesets (eg after filter)
Brendan Cully <brendan@kublai.com>
parents:
4072
diff
changeset
|
220 return None |
3726
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
221 finally: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
222 files = patch.updatedir(self.ui, repo, files, wlock=wlock) |
3714 | 223 except Exception, inst: |
224 if filter: | |
225 os.unlink(patchfile) | |
3757
faed44bab17b
transplant: clobber old series when transplant fails
Brendan Cully <brendan@kublai.com>
parents:
3752
diff
changeset
|
226 seriespath = os.path.join(self.path, 'series') |
faed44bab17b
transplant: clobber old series when transplant fails
Brendan Cully <brendan@kublai.com>
parents:
3752
diff
changeset
|
227 if os.path.exists(seriespath): |
faed44bab17b
transplant: clobber old series when transplant fails
Brendan Cully <brendan@kublai.com>
parents:
3752
diff
changeset
|
228 os.unlink(seriespath) |
3714 | 229 p1 = repo.dirstate.parents()[0] |
230 p2 = node | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
231 self.log(user, date, message, p1, p2, merge=merge) |
3714 | 232 self.ui.write(str(inst) + '\n') |
233 raise util.Abort(_('Fix up the merge and run hg transplant --continue')) | |
234 else: | |
235 files = None | |
236 if merge: | |
237 p1, p2 = repo.dirstate.parents() | |
238 repo.dirstate.setparents(p1, node) | |
239 | |
240 n = repo.commit(files, message, user, date, lock=lock, wlock=wlock, | |
241 extra=extra) | |
242 if not merge: | |
243 self.transplants.set(n, node) | |
244 | |
245 return n | |
246 | |
247 def resume(self, repo, source, opts=None): | |
248 '''recover last transaction and apply remaining changesets''' | |
249 if os.path.exists(os.path.join(self.path, 'journal')): | |
250 n, node = self.recover(repo) | |
3724
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
251 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node), |
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
252 revlog.short(n))) |
3714 | 253 seriespath = os.path.join(self.path, 'series') |
254 if not os.path.exists(seriespath): | |
3758
889f7e74a0d9
transplant: log source node when recovering too.
Brendan Cully <brendan@kublai.com>
parents:
3757
diff
changeset
|
255 self.transplants.write() |
3714 | 256 return |
257 nodes, merges = self.readseries() | |
258 revmap = {} | |
259 for n in nodes: | |
260 revmap[source.changelog.rev(n)] = n | |
261 os.unlink(seriespath) | |
262 | |
263 self.apply(repo, source, revmap, merges, opts) | |
264 | |
265 def recover(self, repo): | |
266 '''commit working directory using journal metadata''' | |
267 node, user, date, message, parents = self.readlog() | |
268 merge = len(parents) == 2 | |
269 | |
270 if not user or not date or not message or not parents[0]: | |
271 raise util.Abort(_('transplant log file is corrupt')) | |
272 | |
3758
889f7e74a0d9
transplant: log source node when recovering too.
Brendan Cully <brendan@kublai.com>
parents:
3757
diff
changeset
|
273 extra = {'transplant_source': node} |
3714 | 274 wlock = repo.wlock() |
275 p1, p2 = repo.dirstate.parents() | |
276 if p1 != parents[0]: | |
277 raise util.Abort(_('working dir not at transplant parent %s') % | |
278 revlog.hex(parents[0])) | |
279 if merge: | |
280 repo.dirstate.setparents(p1, parents[1]) | |
3758
889f7e74a0d9
transplant: log source node when recovering too.
Brendan Cully <brendan@kublai.com>
parents:
3757
diff
changeset
|
281 n = repo.commit(None, message, user, date, wlock=wlock, extra=extra) |
3714 | 282 if not n: |
283 raise util.Abort(_('commit failed')) | |
284 if not merge: | |
285 self.transplants.set(n, node) | |
286 self.unlog() | |
287 | |
288 return n, node | |
289 | |
290 def readseries(self): | |
291 nodes = [] | |
292 merges = [] | |
293 cur = nodes | |
294 for line in self.opener('series').read().splitlines(): | |
295 if line.startswith('# Merges'): | |
296 cur = merges | |
297 continue | |
298 cur.append(revlog.bin(line)) | |
299 | |
300 return (nodes, merges) | |
301 | |
302 def saveseries(self, revmap, merges): | |
303 if not revmap: | |
304 return | |
305 | |
306 if not os.path.isdir(self.path): | |
307 os.mkdir(self.path) | |
308 series = self.opener('series', 'w') | |
309 revs = revmap.keys() | |
310 revs.sort() | |
311 for rev in revs: | |
312 series.write(revlog.hex(revmap[rev]) + '\n') | |
313 if merges: | |
314 series.write('# Merges\n') | |
315 for m in merges: | |
316 series.write(revlog.hex(m) + '\n') | |
317 series.close() | |
318 | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
319 def parselog(self, fp): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
320 parents = [] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
321 message = [] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
322 node = revlog.nullid |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
323 inmsg = False |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
324 for line in fp.read().splitlines(): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
325 if inmsg: |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
326 message.append(line) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
327 elif line.startswith('# User '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
328 user = line[7:] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
329 elif line.startswith('# Date '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
330 date = line[7:] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
331 elif line.startswith('# Node ID '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
332 node = revlog.bin(line[10:]) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
333 elif line.startswith('# Parent '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
334 parents.append(revlog.bin(line[9:])) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
335 elif not line.startswith('#'): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
336 inmsg = True |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
337 message.append(line) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
338 return (node, user, date, '\n'.join(message), parents) |
4516
96d8a56d4ef9
Removed trailing whitespace and tabs from python files
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4251
diff
changeset
|
339 |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
340 def log(self, user, date, message, p1, p2, merge=False): |
3714 | 341 '''journal changelog metadata for later recover''' |
342 | |
343 if not os.path.isdir(self.path): | |
344 os.mkdir(self.path) | |
345 fp = self.opener('journal', 'w') | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
346 fp.write('# User %s\n' % user) |
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
347 fp.write('# Date %s\n' % date) |
3714 | 348 fp.write('# Node ID %s\n' % revlog.hex(p2)) |
349 fp.write('# Parent ' + revlog.hex(p1) + '\n') | |
350 if merge: | |
351 fp.write('# Parent ' + revlog.hex(p2) + '\n') | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
352 fp.write(message.rstrip() + '\n') |
3714 | 353 fp.close() |
354 | |
355 def readlog(self): | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
356 return self.parselog(self.opener('journal')) |
3714 | 357 |
358 def unlog(self): | |
359 '''remove changelog journal''' | |
360 absdst = os.path.join(self.path, 'journal') | |
361 if os.path.exists(absdst): | |
362 os.unlink(absdst) | |
363 | |
364 def transplantfilter(self, repo, source, root): | |
365 def matchfn(node): | |
366 if self.applied(repo, node, root): | |
367 return False | |
368 if source.changelog.parents(node)[1] != revlog.nullid: | |
369 return False | |
370 extra = source.changelog.read(node)[5] | |
371 cnode = extra.get('transplant_source') | |
372 if cnode and self.applied(repo, cnode, root): | |
373 return False | |
374 return True | |
375 | |
376 return matchfn | |
377 | |
378 def hasnode(repo, node): | |
379 try: | |
380 return repo.changelog.rev(node) != None | |
381 except revlog.RevlogError: | |
382 return False | |
383 | |
384 def browserevs(ui, repo, nodes, opts): | |
385 '''interactively transplant changesets''' | |
386 def browsehelp(ui): | |
387 ui.write('y: transplant this changeset\n' | |
388 'n: skip this changeset\n' | |
389 'm: merge at this changeset\n' | |
390 'p: show patch\n' | |
391 'c: commit selected changesets\n' | |
392 'q: cancel transplant\n' | |
393 '?: show this help\n') | |
394 | |
3723
c828fca6f38a
transplant: show_changeset moved to cmdutil
Brendan Cully <brendan@kublai.com>
parents:
3714
diff
changeset
|
395 displayer = cmdutil.show_changeset(ui, repo, opts) |
3714 | 396 transplants = [] |
397 merges = [] | |
398 for node in nodes: | |
399 displayer.show(changenode=node) | |
400 action = None | |
401 while not action: | |
402 action = ui.prompt(_('apply changeset? [ynmpcq?]:')) | |
403 if action == '?': | |
404 browsehelp(ui) | |
405 action = None | |
406 elif action == 'p': | |
407 parent = repo.changelog.parents(node)[0] | |
408 patch.diff(repo, parent, node) | |
409 action = None | |
410 elif action not in ('y', 'n', 'm', 'c', 'q'): | |
411 ui.write('no such option\n') | |
412 action = None | |
413 if action == 'y': | |
414 transplants.append(node) | |
415 elif action == 'm': | |
416 merges.append(node) | |
417 elif action == 'c': | |
418 break | |
419 elif action == 'q': | |
420 transplants = () | |
421 merges = () | |
422 break | |
423 return (transplants, merges) | |
424 | |
425 def transplant(ui, repo, *revs, **opts): | |
426 '''transplant changesets from another branch | |
427 | |
428 Selected changesets will be applied on top of the current working | |
429 directory with the log of the original changeset. If --log is | |
430 specified, log messages will have a comment appended of the form: | |
431 | |
432 (transplanted from CHANGESETHASH) | |
433 | |
434 You can rewrite the changelog message with the --filter option. | |
435 Its argument will be invoked with the current changelog message | |
436 as $1 and the patch as $2. | |
437 | |
438 If --source is specified, selects changesets from the named | |
439 repository. If --branch is specified, selects changesets from the | |
440 branch holding the named revision, up to that revision. If --all | |
441 is specified, all changesets on the branch will be transplanted, | |
442 otherwise you will be prompted to select the changesets you want. | |
443 | |
444 hg transplant --branch REVISION --all will rebase the selected branch | |
445 (up to the named revision) onto your current working directory. | |
446 | |
447 You can optionally mark selected transplanted changesets as | |
448 merge changesets. You will not be prompted to transplant any | |
449 ancestors of a merged transplant, and you can merge descendants | |
450 of them normally instead of transplanting them. | |
451 | |
452 If no merges or revisions are provided, hg transplant will start | |
453 an interactive changeset browser. | |
454 | |
455 If a changeset application fails, you can fix the merge by hand and | |
456 then resume where you left off by calling hg transplant --continue. | |
457 ''' | |
458 def getoneitem(opts, item, errmsg): | |
459 val = opts.get(item) | |
460 if val: | |
461 if len(val) > 1: | |
462 raise util.Abort(errmsg) | |
463 else: | |
464 return val[0] | |
465 | |
466 def getremotechanges(repo, url): | |
467 sourcerepo = ui.expandpath(url) | |
468 source = hg.repository(ui, sourcerepo) | |
469 incoming = repo.findincoming(source, force=True) | |
470 if not incoming: | |
471 return (source, None, None) | |
472 | |
473 bundle = None | |
474 if not source.local(): | |
475 cg = source.changegroup(incoming, 'incoming') | |
4049
863465381028
transplant: update to current writebundle API
Brendan Cully <brendan@kublai.com>
parents:
4035
diff
changeset
|
476 bundle = changegroup.writebundle(cg, None, 'HG10UN') |
3714 | 477 source = bundlerepo.bundlerepository(ui, repo.root, bundle) |
478 | |
479 return (source, incoming, bundle) | |
480 | |
481 def incwalk(repo, incoming, branches, match=util.always): | |
482 if not branches: | |
483 branches=None | |
484 for node in repo.changelog.nodesbetween(incoming, branches)[0]: | |
485 if match(node): | |
486 yield node | |
487 | |
488 def transplantwalk(repo, root, branches, match=util.always): | |
489 if not branches: | |
490 branches = repo.heads() | |
491 ancestors = [] | |
492 for branch in branches: | |
493 ancestors.append(repo.changelog.ancestor(root, branch)) | |
494 for node in repo.changelog.nodesbetween(ancestors, branches)[0]: | |
495 if match(node): | |
496 yield node | |
497 | |
498 def checkopts(opts, revs): | |
499 if opts.get('continue'): | |
500 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')): | |
501 raise util.Abort(_('--continue is incompatible with branch, all or merge')) | |
502 return | |
503 if not (opts.get('source') or revs or | |
504 opts.get('merge') or opts.get('branch')): | |
505 raise util.Abort(_('no source URL, branch tag or revision list provided')) | |
506 if opts.get('all'): | |
507 if not opts.get('branch'): | |
508 raise util.Abort(_('--all requires a branch revision')) | |
509 if revs: | |
510 raise util.Abort(_('--all is incompatible with a revision list')) | |
511 | |
512 checkopts(opts, revs) | |
513 | |
514 if not opts.get('log'): | |
515 opts['log'] = ui.config('transplant', 'log') | |
516 if not opts.get('filter'): | |
517 opts['filter'] = ui.config('transplant', 'filter') | |
518 | |
519 tp = transplanter(ui, repo) | |
520 | |
521 p1, p2 = repo.dirstate.parents() | |
522 if p1 == revlog.nullid: | |
523 raise util.Abort(_('no revision checked out')) | |
524 if not opts.get('continue'): | |
525 if p2 != revlog.nullid: | |
526 raise util.Abort(_('outstanding uncommitted merges')) | |
527 m, a, r, d = repo.status()[:4] | |
528 if m or a or r or d: | |
529 raise util.Abort(_('outstanding local changes')) | |
530 | |
531 bundle = None | |
532 source = opts.get('source') | |
533 if source: | |
534 (source, incoming, bundle) = getremotechanges(repo, source) | |
535 else: | |
536 source = repo | |
537 | |
538 try: | |
539 if opts.get('continue'): | |
3724
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
540 tp.resume(repo, source, opts) |
3714 | 541 return |
542 | |
543 tf=tp.transplantfilter(repo, source, p1) | |
544 if opts.get('prune'): | |
545 prune = [source.lookup(r) | |
546 for r in cmdutil.revrange(source, opts.get('prune'))] | |
547 matchfn = lambda x: tf(x) and x not in prune | |
548 else: | |
549 matchfn = tf | |
550 branches = map(source.lookup, opts.get('branch', ())) | |
551 merges = map(source.lookup, opts.get('merge', ())) | |
552 revmap = {} | |
553 if revs: | |
554 for r in cmdutil.revrange(source, revs): | |
555 revmap[int(r)] = source.lookup(r) | |
556 elif opts.get('all') or not merges: | |
557 if source != repo: | |
558 alltransplants = incwalk(source, incoming, branches, match=matchfn) | |
559 else: | |
560 alltransplants = transplantwalk(source, p1, branches, match=matchfn) | |
561 if opts.get('all'): | |
562 revs = alltransplants | |
563 else: | |
564 revs, newmerges = browserevs(ui, source, alltransplants, opts) | |
565 merges.extend(newmerges) | |
566 for r in revs: | |
567 revmap[source.changelog.rev(r)] = r | |
568 for r in merges: | |
569 revmap[source.changelog.rev(r)] = r | |
570 | |
571 revs = revmap.keys() | |
572 revs.sort() | |
573 pulls = [] | |
574 | |
575 tp.apply(repo, source, revmap, merges, opts) | |
576 finally: | |
577 if bundle: | |
4072
e916bc0dfdd6
transplant: remote bundle source was not closed before deleting the fetched bundle.
Patrick Mezard <pmezard@gmail.com>
parents:
4049
diff
changeset
|
578 source.close() |
3714 | 579 os.unlink(bundle) |
580 | |
581 cmdtable = { | |
582 "transplant": | |
583 (transplant, | |
584 [('s', 'source', '', _('pull patches from REPOSITORY')), | |
585 ('b', 'branch', [], _('pull patches from branch BRANCH')), | |
586 ('a', 'all', None, _('pull all changesets up to BRANCH')), | |
587 ('p', 'prune', [], _('skip over REV')), | |
588 ('m', 'merge', [], _('merge at REV')), | |
589 ('', 'log', None, _('append transplant info to log message')), | |
590 ('c', 'continue', None, _('continue last transplant session after repair')), | |
591 ('', 'filter', '', _('filter changesets through FILTER'))], | |
3991
da3dc89f1e9a
Corrected synopsis for transplant.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3891
diff
changeset
|
592 _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] [-m REV] [REV]...')) |
3714 | 593 } |