Mercurial > hg
annotate hgext/transplant.py @ 7329:fd4bf5269733
Do not abort with inotify extension enabled, but not supported by the system.
And remove the "native support is required" message which is generated at an
inappropriate location and is printed more than once when using 'hg status'.
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 07 Nov 2008 13:02:04 +0100 |
parents | b6f5490effbf |
children | 87158be081b8 |
rev | line source |
---|---|
3714 | 1 # Patch transplanting extension for Mercurial |
2 # | |
4635
63b9d2deed48
Updated copyright notices and add "and others" to "hg version"
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4516
diff
changeset
|
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com> |
3714 | 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 |
6212 | 10 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge |
4049
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''' | |
6762 | 91 revs = util.sort(revmap) |
3714 | 92 p1, p2 = repo.dirstate.parents() |
93 pulls = [] | |
94 diffopts = patch.diffopts(self.ui, opts) | |
95 diffopts.git = True | |
96 | |
4915
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
97 lock = wlock = None |
3714 | 98 try: |
4915
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
99 wlock = repo.wlock() |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
100 lock = repo.lock() |
3714 | 101 for rev in revs: |
102 node = revmap[rev] | |
103 revstr = '%s:%s' % (rev, revlog.short(node)) | |
104 | |
105 if self.applied(repo, node, p1): | |
106 self.ui.warn(_('skipping already applied revision %s\n') % | |
107 revstr) | |
108 continue | |
109 | |
110 parents = source.changelog.parents(node) | |
111 if not opts.get('filter'): | |
112 # If the changeset parent is the same as the wdir's parent, | |
113 # just pull it. | |
114 if parents[0] == p1: | |
115 pulls.append(node) | |
116 p1 = node | |
117 continue | |
118 if pulls: | |
119 if source != repo: | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
120 repo.pull(source, heads=pulls) |
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
121 merge.update(repo, pulls[-1], False, False, None) |
3714 | 122 p1, p2 = repo.dirstate.parents() |
123 pulls = [] | |
124 | |
125 domerge = False | |
126 if node in merges: | |
127 # pulling all the merge revs at once would mean we couldn't | |
128 # transplant after the latest even if transplants before them | |
129 # fail. | |
130 domerge = True | |
131 if not hasnode(repo, node): | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
132 repo.pull(source, heads=[node]) |
3714 | 133 |
134 if parents[1] != revlog.nullid: | |
135 self.ui.note(_('skipping merge changeset %s:%s\n') | |
136 % (rev, revlog.short(node))) | |
137 patchfile = None | |
138 else: | |
139 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-') | |
140 fp = os.fdopen(fd, 'w') | |
7308
b6f5490effbf
patch: turn patch.diff() into a generator
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
7280
diff
changeset
|
141 gen = patch.diff(source, parents[0], node, opts=diffopts) |
b6f5490effbf
patch: turn patch.diff() into a generator
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
7280
diff
changeset
|
142 for chunk in gen: |
b6f5490effbf
patch: turn patch.diff() into a generator
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
7280
diff
changeset
|
143 fp.write(chunk) |
3714 | 144 fp.close() |
145 | |
146 del revmap[rev] | |
147 if patchfile or domerge: | |
148 try: | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
149 n = self.applyone(repo, node, |
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
150 source.changelog.read(node), |
3714 | 151 patchfile, merge=domerge, |
152 log=opts.get('log'), | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
153 filter=opts.get('filter')) |
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: | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
164 repo.pull(source, heads=pulls) |
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
165 merge.update(repo, pulls[-1], False, False, None) |
3714 | 166 finally: |
167 self.saveseries(revmap, merges) | |
168 self.transplants.write() | |
4915
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
169 del lock, wlock |
3714 | 170 |
171 def filter(self, filter, changelog, patchfile): | |
172 '''arbitrarily rewrite changeset before applying it''' | |
173 | |
6966
057ced2b8543
i18n: mark strings for translation in transplant extension
Martin Geisler <mg@daimi.au.dk>
parents:
6762
diff
changeset
|
174 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
|
175 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
|
176 |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
177 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
|
178 fp = os.fdopen(fd, 'w') |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
179 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
|
180 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
|
181 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
|
182 fp.write(changelog[4]) |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
183 fp.close() |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
184 |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
185 try: |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
186 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
|
187 util.shellquote(patchfile)), |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
188 environ={'HGUSER': changelog[1]}, |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
189 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
|
190 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
|
191 finally: |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
192 os.unlink(headerfile) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
193 |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
194 return (user, date, msg) |
3714 | 195 |
196 def applyone(self, repo, node, cl, patchfile, merge=False, log=False, | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
197 filter=None): |
3714 | 198 '''apply the patch in patchfile to the repository as a transplant''' |
199 (manifest, user, (time, timezone), files, message) = cl[:5] | |
200 date = "%d %d" % (time, timezone) | |
201 extra = {'transplant_source': node} | |
202 if filter: | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
203 (user, date, message) = self.filter(filter, cl, patchfile) |
3714 | 204 |
205 if log: | |
206 message += '\n(transplanted from %s)' % revlog.hex(node) | |
207 | |
208 self.ui.status(_('applying %s\n') % revlog.short(node)) | |
209 self.ui.note('%s %s\n%s\n' % (user, date, message)) | |
210 | |
211 if not patchfile and not merge: | |
212 raise util.Abort(_('can only omit patchfile if merging')) | |
213 if patchfile: | |
214 try: | |
215 files = {} | |
3726
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
216 try: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
217 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
|
218 files=files) |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
219 if not files: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
220 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
|
221 return None |
3726
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
222 finally: |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
223 files = patch.updatedir(self.ui, repo, files) |
3714 | 224 except Exception, inst: |
225 if filter: | |
226 os.unlink(patchfile) | |
3757
faed44bab17b
transplant: clobber old series when transplant fails
Brendan Cully <brendan@kublai.com>
parents:
3752
diff
changeset
|
227 seriespath = os.path.join(self.path, 'series') |
faed44bab17b
transplant: clobber old series when transplant fails
Brendan Cully <brendan@kublai.com>
parents:
3752
diff
changeset
|
228 if os.path.exists(seriespath): |
faed44bab17b
transplant: clobber old series when transplant fails
Brendan Cully <brendan@kublai.com>
parents:
3752
diff
changeset
|
229 os.unlink(seriespath) |
3714 | 230 p1 = repo.dirstate.parents()[0] |
231 p2 = node | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
232 self.log(user, date, message, p1, p2, merge=merge) |
3714 | 233 self.ui.write(str(inst) + '\n') |
234 raise util.Abort(_('Fix up the merge and run hg transplant --continue')) | |
235 else: | |
236 files = None | |
237 if merge: | |
238 p1, p2 = repo.dirstate.parents() | |
239 repo.dirstate.setparents(p1, node) | |
240 | |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
241 n = repo.commit(files, message, user, date, extra=extra) |
3714 | 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() |
4915
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
275 try: |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
276 p1, p2 = repo.dirstate.parents() |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
277 if p1 != parents[0]: |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
278 raise util.Abort( |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
279 _('working dir not at transplant parent %s') % |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
280 revlog.hex(parents[0])) |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
281 if merge: |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
282 repo.dirstate.setparents(p1, parents[1]) |
4917
126f527b3ba3
Make repo locks recursive, eliminate all passing of lock/wlock
Matt Mackall <mpm@selenic.com>
parents:
4915
diff
changeset
|
283 n = repo.commit(None, message, user, date, extra=extra) |
4915
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
284 if not n: |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
285 raise util.Abort(_('commit failed')) |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
286 if not merge: |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
287 self.transplants.set(n, node) |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
288 self.unlog() |
3714 | 289 |
4915
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
290 return n, node |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
291 finally: |
97b734fb9c6f
Use try/finally pattern to cleanup locks and transactions
Matt Mackall <mpm@selenic.com>
parents:
4680
diff
changeset
|
292 del wlock |
3714 | 293 |
294 def readseries(self): | |
295 nodes = [] | |
296 merges = [] | |
297 cur = nodes | |
298 for line in self.opener('series').read().splitlines(): | |
299 if line.startswith('# Merges'): | |
300 cur = merges | |
301 continue | |
302 cur.append(revlog.bin(line)) | |
303 | |
304 return (nodes, merges) | |
305 | |
306 def saveseries(self, revmap, merges): | |
307 if not revmap: | |
308 return | |
309 | |
310 if not os.path.isdir(self.path): | |
311 os.mkdir(self.path) | |
312 series = self.opener('series', 'w') | |
6762 | 313 for rev in util.sort(revmap): |
3714 | 314 series.write(revlog.hex(revmap[rev]) + '\n') |
315 if merges: | |
316 series.write('# Merges\n') | |
317 for m in merges: | |
318 series.write(revlog.hex(m) + '\n') | |
319 series.close() | |
320 | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
321 def parselog(self, fp): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
322 parents = [] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
323 message = [] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
324 node = revlog.nullid |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
325 inmsg = False |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
326 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
|
327 if inmsg: |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
328 message.append(line) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
329 elif line.startswith('# User '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
330 user = 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('# Date '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
332 date = line[7:] |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
333 elif line.startswith('# Node ID '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
334 node = revlog.bin(line[10:]) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
335 elif line.startswith('# Parent '): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
336 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
|
337 elif not line.startswith('#'): |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
338 inmsg = True |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
339 message.append(line) |
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
340 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
|
341 |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
342 def log(self, user, date, message, p1, p2, merge=False): |
3714 | 343 '''journal changelog metadata for later recover''' |
344 | |
345 if not os.path.isdir(self.path): | |
346 os.mkdir(self.path) | |
347 fp = self.opener('journal', 'w') | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
348 fp.write('# User %s\n' % user) |
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
349 fp.write('# Date %s\n' % date) |
3714 | 350 fp.write('# Node ID %s\n' % revlog.hex(p2)) |
351 fp.write('# Parent ' + revlog.hex(p1) + '\n') | |
352 if merge: | |
353 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
|
354 fp.write(message.rstrip() + '\n') |
3714 | 355 fp.close() |
356 | |
357 def readlog(self): | |
3759
e96f97ca0358
transplant: split filter args into changelog entry and patch
Brendan Cully <brendan@kublai.com>
parents:
3758
diff
changeset
|
358 return self.parselog(self.opener('journal')) |
3714 | 359 |
360 def unlog(self): | |
361 '''remove changelog journal''' | |
362 absdst = os.path.join(self.path, 'journal') | |
363 if os.path.exists(absdst): | |
364 os.unlink(absdst) | |
365 | |
366 def transplantfilter(self, repo, source, root): | |
367 def matchfn(node): | |
368 if self.applied(repo, node, root): | |
369 return False | |
370 if source.changelog.parents(node)[1] != revlog.nullid: | |
371 return False | |
372 extra = source.changelog.read(node)[5] | |
373 cnode = extra.get('transplant_source') | |
374 if cnode and self.applied(repo, cnode, root): | |
375 return False | |
376 return True | |
377 | |
378 return matchfn | |
379 | |
380 def hasnode(repo, node): | |
381 try: | |
382 return repo.changelog.rev(node) != None | |
383 except revlog.RevlogError: | |
384 return False | |
385 | |
386 def browserevs(ui, repo, nodes, opts): | |
387 '''interactively transplant changesets''' | |
388 def browsehelp(ui): | |
389 ui.write('y: transplant this changeset\n' | |
390 'n: skip this changeset\n' | |
391 'm: merge at this changeset\n' | |
392 'p: show patch\n' | |
393 'c: commit selected changesets\n' | |
394 'q: cancel transplant\n' | |
395 '?: show this help\n') | |
396 | |
3723
c828fca6f38a
transplant: show_changeset moved to cmdutil
Brendan Cully <brendan@kublai.com>
parents:
3714
diff
changeset
|
397 displayer = cmdutil.show_changeset(ui, repo, opts) |
3714 | 398 transplants = [] |
399 merges = [] | |
400 for node in nodes: | |
401 displayer.show(changenode=node) | |
402 action = None | |
403 while not action: | |
404 action = ui.prompt(_('apply changeset? [ynmpcq?]:')) | |
405 if action == '?': | |
406 browsehelp(ui) | |
407 action = None | |
408 elif action == 'p': | |
409 parent = repo.changelog.parents(node)[0] | |
7308
b6f5490effbf
patch: turn patch.diff() into a generator
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
7280
diff
changeset
|
410 for chunk in patch.diff(repo, parent, node): |
b6f5490effbf
patch: turn patch.diff() into a generator
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
7280
diff
changeset
|
411 repo.ui.write(chunk) |
3714 | 412 action = None |
413 elif action not in ('y', 'n', 'm', 'c', 'q'): | |
414 ui.write('no such option\n') | |
415 action = None | |
416 if action == 'y': | |
417 transplants.append(node) | |
418 elif action == 'm': | |
419 merges.append(node) | |
420 elif action == 'c': | |
421 break | |
422 elif action == 'q': | |
423 transplants = () | |
424 merges = () | |
425 break | |
426 return (transplants, merges) | |
427 | |
428 def transplant(ui, repo, *revs, **opts): | |
429 '''transplant changesets from another branch | |
430 | |
431 Selected changesets will be applied on top of the current working | |
432 directory with the log of the original changeset. If --log is | |
433 specified, log messages will have a comment appended of the form: | |
434 | |
435 (transplanted from CHANGESETHASH) | |
436 | |
437 You can rewrite the changelog message with the --filter option. | |
438 Its argument will be invoked with the current changelog message | |
439 as $1 and the patch as $2. | |
440 | |
441 If --source is specified, selects changesets from the named | |
442 repository. If --branch is specified, selects changesets from the | |
443 branch holding the named revision, up to that revision. If --all | |
444 is specified, all changesets on the branch will be transplanted, | |
445 otherwise you will be prompted to select the changesets you want. | |
446 | |
447 hg transplant --branch REVISION --all will rebase the selected branch | |
448 (up to the named revision) onto your current working directory. | |
449 | |
450 You can optionally mark selected transplanted changesets as | |
451 merge changesets. You will not be prompted to transplant any | |
452 ancestors of a merged transplant, and you can merge descendants | |
453 of them normally instead of transplanting them. | |
454 | |
455 If no merges or revisions are provided, hg transplant will start | |
456 an interactive changeset browser. | |
457 | |
458 If a changeset application fails, you can fix the merge by hand and | |
459 then resume where you left off by calling hg transplant --continue. | |
460 ''' | |
461 def getremotechanges(repo, url): | |
462 sourcerepo = ui.expandpath(url) | |
463 source = hg.repository(ui, sourcerepo) | |
464 incoming = repo.findincoming(source, force=True) | |
465 if not incoming: | |
466 return (source, None, None) | |
467 | |
468 bundle = None | |
469 if not source.local(): | |
470 cg = source.changegroup(incoming, 'incoming') | |
4049
863465381028
transplant: update to current writebundle API
Brendan Cully <brendan@kublai.com>
parents:
4035
diff
changeset
|
471 bundle = changegroup.writebundle(cg, None, 'HG10UN') |
3714 | 472 source = bundlerepo.bundlerepository(ui, repo.root, bundle) |
473 | |
474 return (source, incoming, bundle) | |
475 | |
476 def incwalk(repo, incoming, branches, match=util.always): | |
477 if not branches: | |
478 branches=None | |
479 for node in repo.changelog.nodesbetween(incoming, branches)[0]: | |
480 if match(node): | |
481 yield node | |
482 | |
483 def transplantwalk(repo, root, branches, match=util.always): | |
484 if not branches: | |
485 branches = repo.heads() | |
486 ancestors = [] | |
487 for branch in branches: | |
488 ancestors.append(repo.changelog.ancestor(root, branch)) | |
489 for node in repo.changelog.nodesbetween(ancestors, branches)[0]: | |
490 if match(node): | |
491 yield node | |
492 | |
493 def checkopts(opts, revs): | |
494 if opts.get('continue'): | |
495 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')): | |
496 raise util.Abort(_('--continue is incompatible with branch, all or merge')) | |
497 return | |
498 if not (opts.get('source') or revs or | |
499 opts.get('merge') or opts.get('branch')): | |
500 raise util.Abort(_('no source URL, branch tag or revision list provided')) | |
501 if opts.get('all'): | |
502 if not opts.get('branch'): | |
503 raise util.Abort(_('--all requires a branch revision')) | |
504 if revs: | |
505 raise util.Abort(_('--all is incompatible with a revision list')) | |
506 | |
507 checkopts(opts, revs) | |
508 | |
509 if not opts.get('log'): | |
510 opts['log'] = ui.config('transplant', 'log') | |
511 if not opts.get('filter'): | |
512 opts['filter'] = ui.config('transplant', 'filter') | |
513 | |
514 tp = transplanter(ui, repo) | |
515 | |
516 p1, p2 = repo.dirstate.parents() | |
517 if p1 == revlog.nullid: | |
518 raise util.Abort(_('no revision checked out')) | |
519 if not opts.get('continue'): | |
520 if p2 != revlog.nullid: | |
521 raise util.Abort(_('outstanding uncommitted merges')) | |
522 m, a, r, d = repo.status()[:4] | |
523 if m or a or r or d: | |
524 raise util.Abort(_('outstanding local changes')) | |
525 | |
526 bundle = None | |
527 source = opts.get('source') | |
528 if source: | |
529 (source, incoming, bundle) = getremotechanges(repo, source) | |
530 else: | |
531 source = repo | |
532 | |
533 try: | |
534 if opts.get('continue'): | |
3724
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
535 tp.resume(repo, source, opts) |
3714 | 536 return |
537 | |
538 tf=tp.transplantfilter(repo, source, p1) | |
539 if opts.get('prune'): | |
540 prune = [source.lookup(r) | |
541 for r in cmdutil.revrange(source, opts.get('prune'))] | |
542 matchfn = lambda x: tf(x) and x not in prune | |
543 else: | |
544 matchfn = tf | |
545 branches = map(source.lookup, opts.get('branch', ())) | |
546 merges = map(source.lookup, opts.get('merge', ())) | |
547 revmap = {} | |
548 if revs: | |
549 for r in cmdutil.revrange(source, revs): | |
550 revmap[int(r)] = source.lookup(r) | |
551 elif opts.get('all') or not merges: | |
552 if source != repo: | |
553 alltransplants = incwalk(source, incoming, branches, match=matchfn) | |
554 else: | |
555 alltransplants = transplantwalk(source, p1, branches, match=matchfn) | |
556 if opts.get('all'): | |
557 revs = alltransplants | |
558 else: | |
559 revs, newmerges = browserevs(ui, source, alltransplants, opts) | |
560 merges.extend(newmerges) | |
561 for r in revs: | |
562 revmap[source.changelog.rev(r)] = r | |
563 for r in merges: | |
564 revmap[source.changelog.rev(r)] = r | |
565 | |
566 tp.apply(repo, source, revmap, merges, opts) | |
567 finally: | |
568 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
|
569 source.close() |
3714 | 570 os.unlink(bundle) |
571 | |
572 cmdtable = { | |
573 "transplant": | |
574 (transplant, | |
575 [('s', 'source', '', _('pull patches from REPOSITORY')), | |
576 ('b', 'branch', [], _('pull patches from branch BRANCH')), | |
577 ('a', 'all', None, _('pull all changesets up to BRANCH')), | |
578 ('p', 'prune', [], _('skip over REV')), | |
579 ('m', 'merge', [], _('merge at REV')), | |
580 ('', 'log', None, _('append transplant info to log message')), | |
581 ('c', 'continue', None, _('continue last transplant session after repair')), | |
582 ('', 'filter', '', _('filter changesets through FILTER'))], | |
3991
da3dc89f1e9a
Corrected synopsis for transplant.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3891
diff
changeset
|
583 _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] [-m REV] [REV]...')) |
3714 | 584 } |