Mercurial > public > mercurial-scm > hg
annotate hgext/imerge.py @ 5056:34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Thu, 02 Aug 2007 14:04:51 +0200 |
parents | 56d48aed1f69 |
children | 420e1166a876 |
rev | line source |
---|---|
5044 | 1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com> |
2 # Published under the GNU GPL | |
3 | |
4 ''' | |
5 imerge - interactive merge | |
6 ''' | |
7 | |
8 from mercurial.i18n import _ | |
9 from mercurial.node import * | |
10 from mercurial import commands, cmdutil, hg, merge, util | |
11 import os, tarfile | |
12 | |
13 class InvalidStateFileException(Exception): pass | |
14 | |
15 class ImergeStateFile(object): | |
16 def __init__(self, im): | |
17 self.im = im | |
18 | |
19 def save(self, dest): | |
20 tf = tarfile.open(dest, 'w:gz') | |
21 | |
22 st = os.path.join(self.im.path, 'status') | |
23 tf.add(st, os.path.join('.hg', 'imerge', 'status')) | |
24 | |
25 for f in self.im.resolved: | |
26 abssrc = self.im.repo.wjoin(f) | |
27 tf.add(abssrc, f) | |
28 | |
29 tf.close() | |
30 | |
31 def load(self, source): | |
32 wlock = self.im.repo.wlock() | |
33 lock = self.im.repo.lock() | |
34 | |
35 tf = tarfile.open(source, 'r') | |
36 contents = tf.getnames() | |
37 statusfile = os.path.join('.hg', 'imerge', 'status') | |
38 if statusfile not in contents: | |
39 raise InvalidStateFileException('no status file') | |
40 | |
41 tf.extract(statusfile, self.im.repo.root) | |
42 self.im.load() | |
43 p1 = self.im.parents[0].node() | |
44 p2 = self.im.parents[1].node() | |
45 if self.im.repo.dirstate.parents()[0] != p1: | |
46 hg.clean(self.im.repo, self.im.parents[0].node()) | |
47 self.im.start(p2) | |
5055
56d48aed1f69
imerge: tarfile.extractall is only available in python2.5
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
5054
diff
changeset
|
48 for tarinfo in tf: |
56d48aed1f69
imerge: tarfile.extractall is only available in python2.5
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
5054
diff
changeset
|
49 tf.extract(tarinfo, self.im.repo.root) |
5044 | 50 self.im.load() |
51 | |
52 class Imerge(object): | |
53 def __init__(self, ui, repo): | |
54 self.ui = ui | |
55 self.repo = repo | |
56 | |
57 self.path = repo.join('imerge') | |
58 self.opener = util.opener(self.path) | |
59 | |
60 self.parents = [self.repo.changectx(n) | |
61 for n in self.repo.dirstate.parents()] | |
62 self.conflicts = {} | |
63 self.resolved = [] | |
64 | |
65 def merging(self): | |
66 return self.parents[1].node() != nullid | |
67 | |
68 def load(self): | |
69 # status format. \0-delimited file, fields are | |
70 # p1, p2, conflict count, conflict filenames, resolved filenames | |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
71 # conflict filenames are tuples of localname, remoteorig, remotenew |
5044 | 72 |
73 statusfile = self.opener('status') | |
74 | |
75 status = statusfile.read().split('\0') | |
76 if len(status) < 3: | |
77 raise util.Abort('invalid imerge status file') | |
78 | |
79 try: | |
80 self.parents = [self.repo.changectx(n) for n in status[:2]] | |
81 except LookupError: | |
82 raise util.Abort('merge parent %s not in repository' % short(p)) | |
83 | |
84 status = status[2:] | |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
85 conflicts = int(status.pop(0)) * 3 |
5044 | 86 self.resolved = status[conflicts:] |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
87 for i in xrange(0, conflicts, 3): |
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
88 self.conflicts[status[i]] = (status[i+1], status[i+2]) |
5044 | 89 |
90 def save(self): | |
91 lock = self.repo.lock() | |
92 | |
93 if not os.path.isdir(self.path): | |
94 os.mkdir(self.path) | |
5056
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
95 statusfile = self.opener('status', 'wb') |
5044 | 96 |
97 out = [hex(n.node()) for n in self.parents] | |
98 out.append(str(len(self.conflicts))) | |
5056
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
99 conflicts = self.conflicts.items() |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
100 conflicts.sort() |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
101 for fw, fd_fo in conflicts: |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
102 out.append(fw) |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
103 out.extend(fd_fo) |
5044 | 104 out.extend(self.resolved) |
105 | |
5056
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
106 statusfile.write('\0'.join(out)) |
5044 | 107 |
108 def remaining(self): | |
109 return [f for f in self.conflicts if f not in self.resolved] | |
110 | |
111 def filemerge(self, fn): | |
112 wlock = self.repo.wlock() | |
113 | |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
114 (fd, fo) = self.conflicts[fn] |
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
115 return merge.filemerge(self.repo, fn, fd, fo, self.parents[0], |
5044 | 116 self.parents[1]) |
117 | |
118 def start(self, rev=None): | |
119 _filemerge = merge.filemerge | |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
120 def filemerge(repo, fw, fd, fo, wctx, mctx): |
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
121 self.conflicts[fw] = (fd, fo) |
5044 | 122 |
123 merge.filemerge = filemerge | |
124 commands.merge(self.ui, self.repo, rev=rev) | |
125 merge.filemerge = _filemerge | |
126 | |
127 self.parents = [self.repo.changectx(n) | |
128 for n in self.repo.dirstate.parents()] | |
129 self.save() | |
130 | |
131 def resume(self): | |
132 self.load() | |
133 | |
134 dp = self.repo.dirstate.parents() | |
135 if self.parents[0].node() != dp[0] or self.parents[1].node() != dp[1]: | |
136 raise util.Abort('imerge state does not match working directory') | |
137 | |
138 def status(self): | |
139 self.ui.write('merging %s and %s\n' % \ | |
140 (short(self.parents[0].node()), | |
141 short(self.parents[1].node()))) | |
142 | |
143 if self.resolved: | |
144 self.ui.write('resolved:\n') | |
145 for fn in self.resolved: | |
146 self.ui.write(' %s\n' % fn) | |
147 remaining = [f for f in self.conflicts if f not in self.resolved] | |
148 if remaining: | |
149 self.ui.write('remaining:\n') | |
150 for fn in remaining: | |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
151 (fd, fo) = self.conflicts[fn] |
5044 | 152 if fn == fo: |
153 self.ui.write(' %s\n' % (fn,)) | |
154 else: | |
5054
ec70fd08e16c
Update imerge for new filemerge interface
Brendan Cully <brendan@kublai.com>
parents:
5044
diff
changeset
|
155 self.ui.write(' %s (%s)\n' % (fn, fd)) |
5044 | 156 else: |
157 self.ui.write('all conflicts resolved\n') | |
158 | |
159 def next(self): | |
160 remaining = self.remaining() | |
161 return remaining and remaining[0] | |
162 | |
163 def resolve(self, files): | |
164 resolved = dict.fromkeys(self.resolved) | |
165 for fn in files: | |
166 if fn not in self.conflicts: | |
167 raise util.Abort('%s is not in the merge set' % fn) | |
168 resolved[fn] = True | |
5056
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
169 self.resolved = resolved.keys() |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
170 self.resolved.sort() |
5044 | 171 self.save() |
172 return 0 | |
173 | |
174 def unresolve(self, files): | |
175 resolved = dict.fromkeys(self.resolved) | |
176 for fn in files: | |
177 if fn not in resolved: | |
178 raise util.Abort('%s is not resolved' % fn) | |
179 del resolved[fn] | |
5056
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
180 self.resolved = resolved.keys() |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
181 self.resolved.sort() |
5044 | 182 self.save() |
183 return 0 | |
184 | |
185 def pickle(self, dest): | |
186 '''write current merge state to file to be resumed elsewhere''' | |
187 state = ImergeStateFile(self) | |
188 return state.save(dest) | |
189 | |
190 def unpickle(self, source): | |
191 '''read merge state from file''' | |
192 state = ImergeStateFile(self) | |
193 return state.load(source) | |
194 | |
195 def load(im, source): | |
196 if im.merging(): | |
197 raise util.Abort('there is already a merge in progress ' | |
198 '(update -C <rev> to abort it)' ) | |
199 m, a, r, d = im.repo.status()[:4] | |
200 if m or a or r or d: | |
201 raise util.Abort('working directory has uncommitted changes') | |
202 | |
203 rc = im.unpickle(source) | |
204 if not rc: | |
205 im.status() | |
206 return rc | |
207 | |
208 def merge_(im, filename=None): | |
209 if not filename: | |
210 filename = im.next() | |
211 if not filename: | |
212 im.ui.write('all conflicts resolved\n') | |
213 return 0 | |
214 | |
215 rc = im.filemerge(filename) | |
216 if not rc: | |
217 im.resolve([filename]) | |
218 if not im.next(): | |
219 im.ui.write('all conflicts resolved\n') | |
220 return 0 | |
221 return rc | |
222 | |
223 def next(im): | |
224 n = im.next() | |
225 if n: | |
226 im.ui.write('%s\n' % n) | |
227 else: | |
228 im.ui.write('all conflicts resolved\n') | |
229 return 0 | |
230 | |
231 def resolve(im, *files): | |
232 if not files: | |
233 raise util.Abort('resolve requires at least one filename') | |
234 return im.resolve(files) | |
235 | |
236 def save(im, dest): | |
237 return im.pickle(dest) | |
238 | |
239 def status(im): | |
240 im.status() | |
241 return 0 | |
242 | |
243 def unresolve(im, *files): | |
244 if not files: | |
245 raise util.Abort('unresolve requires at least one filename') | |
246 return im.unresolve(files) | |
247 | |
248 subcmdtable = { | |
249 'load': load, | |
250 'merge': merge_, | |
251 'next': next, | |
252 'resolve': resolve, | |
253 'save': save, | |
254 'status': status, | |
255 'unresolve': unresolve | |
256 } | |
257 | |
258 def dispatch(im, args, opts): | |
259 def complete(s, choices): | |
260 candidates = [] | |
261 for choice in choices: | |
262 if choice.startswith(s): | |
263 candidates.append(choice) | |
264 return candidates | |
265 | |
266 c, args = args[0], args[1:] | |
267 cmd = complete(c, subcmdtable.keys()) | |
268 if not cmd: | |
269 raise cmdutil.UnknownCommand('imerge ' + c) | |
270 if len(cmd) > 1: | |
5056
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
271 cmd.sort() |
34a54cc5df1b
imerge: sorted() is only available in python2.4 and above
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5055
diff
changeset
|
272 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd) |
5044 | 273 cmd = cmd[0] |
274 | |
275 func = subcmdtable[cmd] | |
276 try: | |
277 return func(im, *args) | |
278 except TypeError: | |
279 raise cmdutil.ParseError('imerge', '%s: invalid arguments' % cmd) | |
280 | |
281 def imerge(ui, repo, *args, **opts): | |
282 '''interactive merge | |
283 | |
284 imerge lets you split a merge into pieces. When you start a merge | |
285 with imerge, the names of all files with conflicts are recorded. | |
286 You can then merge any of these files, and if the merge is | |
287 successful, they will be marked as resolved. When all files are | |
288 resolved, the merge is complete. | |
289 | |
290 If no merge is in progress, hg imerge [rev] will merge the working | |
291 directory with rev (defaulting to the other head if the repository | |
292 only has two heads). You may also resume a saved merge with | |
293 hg imerge load <file>. | |
294 | |
295 If a merge is in progress, hg imerge will default to merging the | |
296 next unresolved file. | |
297 | |
298 The following subcommands are available: | |
299 | |
300 status: | |
301 show the current state of the merge | |
302 next: | |
303 show the next unresolved file merge | |
304 merge [<file>]: | |
305 merge <file>. If the file merge is successful, the file will be | |
306 recorded as resolved. If no file is given, the next unresolved | |
307 file will be merged. | |
308 resolve <file>...: | |
309 mark files as successfully merged | |
310 unresolve <file>...: | |
311 mark files as requiring merging. | |
312 save <file>: | |
313 save the state of the merge to a file to be resumed elsewhere | |
314 load <file>: | |
315 load the state of the merge from a file created by save | |
316 ''' | |
317 | |
318 im = Imerge(ui, repo) | |
319 | |
320 if im.merging(): | |
321 im.resume() | |
322 else: | |
323 rev = opts.get('rev') | |
324 if rev and args: | |
325 raise util.Abort('please specify just one revision') | |
326 | |
327 if len(args) == 2 and args[0] == 'load': | |
328 pass | |
329 else: | |
330 if args: | |
331 rev = args[0] | |
332 im.start(rev=rev) | |
333 args = ['status'] | |
334 | |
335 if not args: | |
336 args = ['merge'] | |
337 | |
338 return dispatch(im, args, opts) | |
339 | |
340 cmdtable = { | |
341 '^imerge': | |
342 (imerge, | |
343 [('r', 'rev', '', _('revision to merge'))], 'hg imerge [command]') | |
344 } |