##// END OF EJS Templates
imerge: replace "merge" with "internal:merge" when non-interactive
Patrick Mezard -
r6362:bc3dbdb1 default
parent child Browse files
Show More
@@ -1,407 +1,407 b''
1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
2 # Published under the GNU GPL
2 # Published under the GNU GPL
3
3
4 '''
4 '''
5 imerge - interactive merge
5 imerge - interactive merge
6 '''
6 '''
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import hex, short
9 from mercurial.node import hex, short
10 from mercurial import commands, cmdutil, dispatch, fancyopts
10 from mercurial import commands, cmdutil, dispatch, fancyopts
11 from mercurial import hg, filemerge, util, revlog
11 from mercurial import hg, filemerge, util, revlog
12 import os, tarfile
12 import os, tarfile
13
13
14 class InvalidStateFileException(Exception): pass
14 class InvalidStateFileException(Exception): pass
15
15
16 class ImergeStateFile(object):
16 class ImergeStateFile(object):
17 def __init__(self, im):
17 def __init__(self, im):
18 self.im = im
18 self.im = im
19
19
20 def save(self, dest):
20 def save(self, dest):
21 tf = tarfile.open(dest, 'w:gz')
21 tf = tarfile.open(dest, 'w:gz')
22
22
23 st = os.path.join(self.im.path, 'status')
23 st = os.path.join(self.im.path, 'status')
24 tf.add(st, os.path.join('.hg', 'imerge', 'status'))
24 tf.add(st, os.path.join('.hg', 'imerge', 'status'))
25
25
26 for f in self.im.resolved:
26 for f in self.im.resolved:
27 (fd, fo) = self.im.conflicts[f]
27 (fd, fo) = self.im.conflicts[f]
28 abssrc = self.im.repo.wjoin(fd)
28 abssrc = self.im.repo.wjoin(fd)
29 tf.add(abssrc, fd)
29 tf.add(abssrc, fd)
30
30
31 tf.close()
31 tf.close()
32
32
33 def load(self, source):
33 def load(self, source):
34 wlock = self.im.repo.wlock()
34 wlock = self.im.repo.wlock()
35 lock = self.im.repo.lock()
35 lock = self.im.repo.lock()
36
36
37 tf = tarfile.open(source, 'r')
37 tf = tarfile.open(source, 'r')
38 contents = tf.getnames()
38 contents = tf.getnames()
39 # tarfile normalizes path separators to '/'
39 # tarfile normalizes path separators to '/'
40 statusfile = '.hg/imerge/status'
40 statusfile = '.hg/imerge/status'
41 if statusfile not in contents:
41 if statusfile not in contents:
42 raise InvalidStateFileException('no status file')
42 raise InvalidStateFileException('no status file')
43
43
44 tf.extract(statusfile, self.im.repo.root)
44 tf.extract(statusfile, self.im.repo.root)
45 p1, p2 = self.im.load()
45 p1, p2 = self.im.load()
46 if self.im.repo.dirstate.parents()[0] != p1.node():
46 if self.im.repo.dirstate.parents()[0] != p1.node():
47 hg.clean(self.im.repo, p1.node())
47 hg.clean(self.im.repo, p1.node())
48 self.im.start(p2.node())
48 self.im.start(p2.node())
49 for tarinfo in tf:
49 for tarinfo in tf:
50 tf.extract(tarinfo, self.im.repo.root)
50 tf.extract(tarinfo, self.im.repo.root)
51 self.im.load()
51 self.im.load()
52
52
53 class Imerge(object):
53 class Imerge(object):
54 def __init__(self, ui, repo):
54 def __init__(self, ui, repo):
55 self.ui = ui
55 self.ui = ui
56 self.repo = repo
56 self.repo = repo
57
57
58 self.path = repo.join('imerge')
58 self.path = repo.join('imerge')
59 self.opener = util.opener(self.path)
59 self.opener = util.opener(self.path)
60
60
61 self.wctx = self.repo.workingctx()
61 self.wctx = self.repo.workingctx()
62 self.conflicts = {}
62 self.conflicts = {}
63 self.resolved = []
63 self.resolved = []
64
64
65 def merging(self):
65 def merging(self):
66 return len(self.wctx.parents()) > 1
66 return len(self.wctx.parents()) > 1
67
67
68 def load(self):
68 def load(self):
69 # status format. \0-delimited file, fields are
69 # status format. \0-delimited file, fields are
70 # p1, p2, conflict count, conflict filenames, resolved filenames
70 # p1, p2, conflict count, conflict filenames, resolved filenames
71 # conflict filenames are tuples of localname, remoteorig, remotenew
71 # conflict filenames are tuples of localname, remoteorig, remotenew
72
72
73 statusfile = self.opener('status')
73 statusfile = self.opener('status')
74
74
75 status = statusfile.read().split('\0')
75 status = statusfile.read().split('\0')
76 if len(status) < 3:
76 if len(status) < 3:
77 raise util.Abort('invalid imerge status file')
77 raise util.Abort('invalid imerge status file')
78
78
79 try:
79 try:
80 parents = [self.repo.changectx(n) for n in status[:2]]
80 parents = [self.repo.changectx(n) for n in status[:2]]
81 except revlog.LookupError, e:
81 except revlog.LookupError, e:
82 raise util.Abort(_('merge parent %s not in repository') %
82 raise util.Abort(_('merge parent %s not in repository') %
83 short(e.name))
83 short(e.name))
84
84
85 status = status[2:]
85 status = status[2:]
86 conflicts = int(status.pop(0)) * 3
86 conflicts = int(status.pop(0)) * 3
87 self.resolved = status[conflicts:]
87 self.resolved = status[conflicts:]
88 for i in xrange(0, conflicts, 3):
88 for i in xrange(0, conflicts, 3):
89 self.conflicts[status[i]] = (status[i+1], status[i+2])
89 self.conflicts[status[i]] = (status[i+1], status[i+2])
90
90
91 return parents
91 return parents
92
92
93 def save(self):
93 def save(self):
94 lock = self.repo.lock()
94 lock = self.repo.lock()
95
95
96 if not os.path.isdir(self.path):
96 if not os.path.isdir(self.path):
97 os.mkdir(self.path)
97 os.mkdir(self.path)
98 statusfile = self.opener('status', 'wb')
98 statusfile = self.opener('status', 'wb')
99
99
100 out = [hex(n.node()) for n in self.wctx.parents()]
100 out = [hex(n.node()) for n in self.wctx.parents()]
101 out.append(str(len(self.conflicts)))
101 out.append(str(len(self.conflicts)))
102 conflicts = self.conflicts.items()
102 conflicts = self.conflicts.items()
103 conflicts.sort()
103 conflicts.sort()
104 for fw, fd_fo in conflicts:
104 for fw, fd_fo in conflicts:
105 out.append(fw)
105 out.append(fw)
106 out.extend(fd_fo)
106 out.extend(fd_fo)
107 out.extend(self.resolved)
107 out.extend(self.resolved)
108
108
109 statusfile.write('\0'.join(out))
109 statusfile.write('\0'.join(out))
110
110
111 def remaining(self):
111 def remaining(self):
112 return [f for f in self.conflicts if f not in self.resolved]
112 return [f for f in self.conflicts if f not in self.resolved]
113
113
114 def filemerge(self, fn, interactive=True):
114 def filemerge(self, fn, interactive=True):
115 wlock = self.repo.wlock()
115 wlock = self.repo.wlock()
116
116
117 (fd, fo) = self.conflicts[fn]
117 (fd, fo) = self.conflicts[fn]
118 p1, p2 = self.wctx.parents()
118 p1, p2 = self.wctx.parents()
119
119
120 # this could be greatly improved
120 # this could be greatly improved
121 realmerge = os.environ.get('HGMERGE')
121 realmerge = os.environ.get('HGMERGE')
122 if not interactive:
122 if not interactive:
123 os.environ['HGMERGE'] = 'merge'
123 os.environ['HGMERGE'] = 'internal:merge'
124
124
125 # The filemerge ancestor algorithm does not work if self.wctx
125 # The filemerge ancestor algorithm does not work if self.wctx
126 # already has two parents (in normal merge it doesn't yet). But
126 # already has two parents (in normal merge it doesn't yet). But
127 # this is very dirty.
127 # this is very dirty.
128 self.wctx._parents.pop()
128 self.wctx._parents.pop()
129 try:
129 try:
130 # TODO: we should probably revert the file if merge fails
130 # TODO: we should probably revert the file if merge fails
131 return filemerge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
131 return filemerge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
132 finally:
132 finally:
133 self.wctx._parents.append(p2)
133 self.wctx._parents.append(p2)
134 if realmerge:
134 if realmerge:
135 os.environ['HGMERGE'] = realmerge
135 os.environ['HGMERGE'] = realmerge
136 elif not interactive:
136 elif not interactive:
137 del os.environ['HGMERGE']
137 del os.environ['HGMERGE']
138
138
139 def start(self, rev=None):
139 def start(self, rev=None):
140 _filemerge = filemerge.filemerge
140 _filemerge = filemerge.filemerge
141 def filemerge_(repo, fw, fd, fo, wctx, mctx):
141 def filemerge_(repo, fw, fd, fo, wctx, mctx):
142 self.conflicts[fw] = (fd, fo)
142 self.conflicts[fw] = (fd, fo)
143
143
144 filemerge.filemerge = filemerge_
144 filemerge.filemerge = filemerge_
145 commands.merge(self.ui, self.repo, rev=rev)
145 commands.merge(self.ui, self.repo, rev=rev)
146 filemerge.filemerge = _filemerge
146 filemerge.filemerge = _filemerge
147
147
148 self.wctx = self.repo.workingctx()
148 self.wctx = self.repo.workingctx()
149 self.save()
149 self.save()
150
150
151 def resume(self):
151 def resume(self):
152 self.load()
152 self.load()
153
153
154 dp = self.repo.dirstate.parents()
154 dp = self.repo.dirstate.parents()
155 p1, p2 = self.wctx.parents()
155 p1, p2 = self.wctx.parents()
156 if p1.node() != dp[0] or p2.node() != dp[1]:
156 if p1.node() != dp[0] or p2.node() != dp[1]:
157 raise util.Abort('imerge state does not match working directory')
157 raise util.Abort('imerge state does not match working directory')
158
158
159 def next(self):
159 def next(self):
160 remaining = self.remaining()
160 remaining = self.remaining()
161 return remaining and remaining[0]
161 return remaining and remaining[0]
162
162
163 def resolve(self, files):
163 def resolve(self, files):
164 resolved = dict.fromkeys(self.resolved)
164 resolved = dict.fromkeys(self.resolved)
165 for fn in files:
165 for fn in files:
166 if fn not in self.conflicts:
166 if fn not in self.conflicts:
167 raise util.Abort('%s is not in the merge set' % fn)
167 raise util.Abort('%s is not in the merge set' % fn)
168 resolved[fn] = True
168 resolved[fn] = True
169 self.resolved = resolved.keys()
169 self.resolved = resolved.keys()
170 self.resolved.sort()
170 self.resolved.sort()
171 self.save()
171 self.save()
172 return 0
172 return 0
173
173
174 def unresolve(self, files):
174 def unresolve(self, files):
175 resolved = dict.fromkeys(self.resolved)
175 resolved = dict.fromkeys(self.resolved)
176 for fn in files:
176 for fn in files:
177 if fn not in resolved:
177 if fn not in resolved:
178 raise util.Abort('%s is not resolved' % fn)
178 raise util.Abort('%s is not resolved' % fn)
179 del resolved[fn]
179 del resolved[fn]
180 self.resolved = resolved.keys()
180 self.resolved = resolved.keys()
181 self.resolved.sort()
181 self.resolved.sort()
182 self.save()
182 self.save()
183 return 0
183 return 0
184
184
185 def pickle(self, dest):
185 def pickle(self, dest):
186 '''write current merge state to file to be resumed elsewhere'''
186 '''write current merge state to file to be resumed elsewhere'''
187 state = ImergeStateFile(self)
187 state = ImergeStateFile(self)
188 return state.save(dest)
188 return state.save(dest)
189
189
190 def unpickle(self, source):
190 def unpickle(self, source):
191 '''read merge state from file'''
191 '''read merge state from file'''
192 state = ImergeStateFile(self)
192 state = ImergeStateFile(self)
193 return state.load(source)
193 return state.load(source)
194
194
195 def load(im, source):
195 def load(im, source):
196 if im.merging():
196 if im.merging():
197 raise util.Abort('there is already a merge in progress '
197 raise util.Abort('there is already a merge in progress '
198 '(update -C <rev> to abort it)' )
198 '(update -C <rev> to abort it)' )
199 m, a, r, d = im.repo.status()[:4]
199 m, a, r, d = im.repo.status()[:4]
200 if m or a or r or d:
200 if m or a or r or d:
201 raise util.Abort('working directory has uncommitted changes')
201 raise util.Abort('working directory has uncommitted changes')
202
202
203 rc = im.unpickle(source)
203 rc = im.unpickle(source)
204 if not rc:
204 if not rc:
205 status(im)
205 status(im)
206 return rc
206 return rc
207
207
208 def merge_(im, filename=None, auto=False):
208 def merge_(im, filename=None, auto=False):
209 success = True
209 success = True
210 if auto and not filename:
210 if auto and not filename:
211 for fn in im.remaining():
211 for fn in im.remaining():
212 rc = im.filemerge(fn, interactive=False)
212 rc = im.filemerge(fn, interactive=False)
213 if rc:
213 if rc:
214 success = False
214 success = False
215 else:
215 else:
216 im.resolve([fn])
216 im.resolve([fn])
217 if success:
217 if success:
218 im.ui.write('all conflicts resolved\n')
218 im.ui.write('all conflicts resolved\n')
219 else:
219 else:
220 status(im)
220 status(im)
221 return 0
221 return 0
222
222
223 if not filename:
223 if not filename:
224 filename = im.next()
224 filename = im.next()
225 if not filename:
225 if not filename:
226 im.ui.write('all conflicts resolved\n')
226 im.ui.write('all conflicts resolved\n')
227 return 0
227 return 0
228
228
229 rc = im.filemerge(filename, interactive=not auto)
229 rc = im.filemerge(filename, interactive=not auto)
230 if not rc:
230 if not rc:
231 im.resolve([filename])
231 im.resolve([filename])
232 if not im.next():
232 if not im.next():
233 im.ui.write('all conflicts resolved\n')
233 im.ui.write('all conflicts resolved\n')
234 return rc
234 return rc
235
235
236 def next(im):
236 def next(im):
237 n = im.next()
237 n = im.next()
238 if n:
238 if n:
239 im.ui.write('%s\n' % n)
239 im.ui.write('%s\n' % n)
240 else:
240 else:
241 im.ui.write('all conflicts resolved\n')
241 im.ui.write('all conflicts resolved\n')
242 return 0
242 return 0
243
243
244 def resolve(im, *files):
244 def resolve(im, *files):
245 if not files:
245 if not files:
246 raise util.Abort('resolve requires at least one filename')
246 raise util.Abort('resolve requires at least one filename')
247 return im.resolve(files)
247 return im.resolve(files)
248
248
249 def save(im, dest):
249 def save(im, dest):
250 return im.pickle(dest)
250 return im.pickle(dest)
251
251
252 def status(im, **opts):
252 def status(im, **opts):
253 if not opts.get('resolved') and not opts.get('unresolved'):
253 if not opts.get('resolved') and not opts.get('unresolved'):
254 opts['resolved'] = True
254 opts['resolved'] = True
255 opts['unresolved'] = True
255 opts['unresolved'] = True
256
256
257 if im.ui.verbose:
257 if im.ui.verbose:
258 p1, p2 = [short(p.node()) for p in im.wctx.parents()]
258 p1, p2 = [short(p.node()) for p in im.wctx.parents()]
259 im.ui.note(_('merging %s and %s\n') % (p1, p2))
259 im.ui.note(_('merging %s and %s\n') % (p1, p2))
260
260
261 conflicts = im.conflicts.keys()
261 conflicts = im.conflicts.keys()
262 conflicts.sort()
262 conflicts.sort()
263 remaining = dict.fromkeys(im.remaining())
263 remaining = dict.fromkeys(im.remaining())
264 st = []
264 st = []
265 for fn in conflicts:
265 for fn in conflicts:
266 if opts.get('no_status'):
266 if opts.get('no_status'):
267 mode = ''
267 mode = ''
268 elif fn in remaining:
268 elif fn in remaining:
269 mode = 'U '
269 mode = 'U '
270 else:
270 else:
271 mode = 'R '
271 mode = 'R '
272 if ((opts.get('resolved') and fn not in remaining)
272 if ((opts.get('resolved') and fn not in remaining)
273 or (opts.get('unresolved') and fn in remaining)):
273 or (opts.get('unresolved') and fn in remaining)):
274 st.append((mode, fn))
274 st.append((mode, fn))
275 st.sort()
275 st.sort()
276 for (mode, fn) in st:
276 for (mode, fn) in st:
277 if im.ui.verbose:
277 if im.ui.verbose:
278 fo, fd = im.conflicts[fn]
278 fo, fd = im.conflicts[fn]
279 if fd != fn:
279 if fd != fn:
280 fn = '%s (%s)' % (fn, fd)
280 fn = '%s (%s)' % (fn, fd)
281 im.ui.write('%s%s\n' % (mode, fn))
281 im.ui.write('%s%s\n' % (mode, fn))
282 if opts.get('unresolved') and not remaining:
282 if opts.get('unresolved') and not remaining:
283 im.ui.write(_('all conflicts resolved\n'))
283 im.ui.write(_('all conflicts resolved\n'))
284
284
285 return 0
285 return 0
286
286
287 def unresolve(im, *files):
287 def unresolve(im, *files):
288 if not files:
288 if not files:
289 raise util.Abort('unresolve requires at least one filename')
289 raise util.Abort('unresolve requires at least one filename')
290 return im.unresolve(files)
290 return im.unresolve(files)
291
291
292 subcmdtable = {
292 subcmdtable = {
293 'load': (load, []),
293 'load': (load, []),
294 'merge':
294 'merge':
295 (merge_,
295 (merge_,
296 [('a', 'auto', None, _('automatically resolve if possible'))]),
296 [('a', 'auto', None, _('automatically resolve if possible'))]),
297 'next': (next, []),
297 'next': (next, []),
298 'resolve': (resolve, []),
298 'resolve': (resolve, []),
299 'save': (save, []),
299 'save': (save, []),
300 'status':
300 'status':
301 (status,
301 (status,
302 [('n', 'no-status', None, _('hide status prefix')),
302 [('n', 'no-status', None, _('hide status prefix')),
303 ('', 'resolved', None, _('only show resolved conflicts')),
303 ('', 'resolved', None, _('only show resolved conflicts')),
304 ('', 'unresolved', None, _('only show unresolved conflicts'))]),
304 ('', 'unresolved', None, _('only show unresolved conflicts'))]),
305 'unresolve': (unresolve, [])
305 'unresolve': (unresolve, [])
306 }
306 }
307
307
308 def dispatch_(im, args, opts):
308 def dispatch_(im, args, opts):
309 def complete(s, choices):
309 def complete(s, choices):
310 candidates = []
310 candidates = []
311 for choice in choices:
311 for choice in choices:
312 if choice.startswith(s):
312 if choice.startswith(s):
313 candidates.append(choice)
313 candidates.append(choice)
314 return candidates
314 return candidates
315
315
316 c, args = args[0], list(args[1:])
316 c, args = args[0], list(args[1:])
317 cmd = complete(c, subcmdtable.keys())
317 cmd = complete(c, subcmdtable.keys())
318 if not cmd:
318 if not cmd:
319 raise cmdutil.UnknownCommand('imerge ' + c)
319 raise cmdutil.UnknownCommand('imerge ' + c)
320 if len(cmd) > 1:
320 if len(cmd) > 1:
321 cmd.sort()
321 cmd.sort()
322 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
322 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
323 cmd = cmd[0]
323 cmd = cmd[0]
324
324
325 func, optlist = subcmdtable[cmd]
325 func, optlist = subcmdtable[cmd]
326 opts = {}
326 opts = {}
327 try:
327 try:
328 args = fancyopts.fancyopts(args, optlist, opts)
328 args = fancyopts.fancyopts(args, optlist, opts)
329 return func(im, *args, **opts)
329 return func(im, *args, **opts)
330 except fancyopts.getopt.GetoptError, inst:
330 except fancyopts.getopt.GetoptError, inst:
331 raise dispatch.ParseError('imerge', '%s: %s' % (cmd, inst))
331 raise dispatch.ParseError('imerge', '%s: %s' % (cmd, inst))
332 except TypeError:
332 except TypeError:
333 raise dispatch.ParseError('imerge', _('%s: invalid arguments') % cmd)
333 raise dispatch.ParseError('imerge', _('%s: invalid arguments') % cmd)
334
334
335 def imerge(ui, repo, *args, **opts):
335 def imerge(ui, repo, *args, **opts):
336 '''interactive merge
336 '''interactive merge
337
337
338 imerge lets you split a merge into pieces. When you start a merge
338 imerge lets you split a merge into pieces. When you start a merge
339 with imerge, the names of all files with conflicts are recorded.
339 with imerge, the names of all files with conflicts are recorded.
340 You can then merge any of these files, and if the merge is
340 You can then merge any of these files, and if the merge is
341 successful, they will be marked as resolved. When all files are
341 successful, they will be marked as resolved. When all files are
342 resolved, the merge is complete.
342 resolved, the merge is complete.
343
343
344 If no merge is in progress, hg imerge [rev] will merge the working
344 If no merge is in progress, hg imerge [rev] will merge the working
345 directory with rev (defaulting to the other head if the repository
345 directory with rev (defaulting to the other head if the repository
346 only has two heads). You may also resume a saved merge with
346 only has two heads). You may also resume a saved merge with
347 hg imerge load <file>.
347 hg imerge load <file>.
348
348
349 If a merge is in progress, hg imerge will default to merging the
349 If a merge is in progress, hg imerge will default to merging the
350 next unresolved file.
350 next unresolved file.
351
351
352 The following subcommands are available:
352 The following subcommands are available:
353
353
354 status:
354 status:
355 show the current state of the merge
355 show the current state of the merge
356 options:
356 options:
357 -n --no-status: do not print the status prefix
357 -n --no-status: do not print the status prefix
358 --resolved: only print resolved conflicts
358 --resolved: only print resolved conflicts
359 --unresolved: only print unresolved conflicts
359 --unresolved: only print unresolved conflicts
360 next:
360 next:
361 show the next unresolved file merge
361 show the next unresolved file merge
362 merge [<file>]:
362 merge [<file>]:
363 merge <file>. If the file merge is successful, the file will be
363 merge <file>. If the file merge is successful, the file will be
364 recorded as resolved. If no file is given, the next unresolved
364 recorded as resolved. If no file is given, the next unresolved
365 file will be merged.
365 file will be merged.
366 resolve <file>...:
366 resolve <file>...:
367 mark files as successfully merged
367 mark files as successfully merged
368 unresolve <file>...:
368 unresolve <file>...:
369 mark files as requiring merging.
369 mark files as requiring merging.
370 save <file>:
370 save <file>:
371 save the state of the merge to a file to be resumed elsewhere
371 save the state of the merge to a file to be resumed elsewhere
372 load <file>:
372 load <file>:
373 load the state of the merge from a file created by save
373 load the state of the merge from a file created by save
374 '''
374 '''
375
375
376 im = Imerge(ui, repo)
376 im = Imerge(ui, repo)
377
377
378 if im.merging():
378 if im.merging():
379 im.resume()
379 im.resume()
380 else:
380 else:
381 rev = opts.get('rev')
381 rev = opts.get('rev')
382 if rev and args:
382 if rev and args:
383 raise util.Abort('please specify just one revision')
383 raise util.Abort('please specify just one revision')
384
384
385 if len(args) == 2 and args[0] == 'load':
385 if len(args) == 2 and args[0] == 'load':
386 pass
386 pass
387 else:
387 else:
388 if args:
388 if args:
389 rev = args[0]
389 rev = args[0]
390 im.start(rev=rev)
390 im.start(rev=rev)
391 if opts.get('auto'):
391 if opts.get('auto'):
392 args = ['merge', '--auto']
392 args = ['merge', '--auto']
393 else:
393 else:
394 args = ['status']
394 args = ['status']
395
395
396 if not args:
396 if not args:
397 args = ['merge']
397 args = ['merge']
398
398
399 return dispatch_(im, args, opts)
399 return dispatch_(im, args, opts)
400
400
401 cmdtable = {
401 cmdtable = {
402 '^imerge':
402 '^imerge':
403 (imerge,
403 (imerge,
404 [('r', 'rev', '', _('revision to merge')),
404 [('r', 'rev', '', _('revision to merge')),
405 ('a', 'auto', None, _('automatically merge where possible'))],
405 ('a', 'auto', None, _('automatically merge where possible'))],
406 'hg imerge [command]')
406 'hg imerge [command]')
407 }
407 }
@@ -1,70 +1,75 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "imerge=" >> $HGRCPATH
4 echo "imerge=" >> $HGRCPATH
5 HGMERGE=true
5 HGMERGE=true
6 export HGMERGE
6 export HGMERGE
7
7
8 hg init base
8 hg init base
9 cd base
9 cd base
10
10
11 echo foo > foo
11 echo foo > foo
12 echo bar > bar
12 echo bar > bar
13 hg ci -Am0 -d '0 0'
13 hg ci -Am0 -d '0 0'
14
14
15 hg mv foo foo2
15 hg mv foo foo2
16 echo foo >> foo2
16 echo foo >> foo2
17 hg ci -m1 -d '1 0'
17 hg ci -m1 -d '1 0'
18
18
19 hg up -C 0
19 hg up -C 0
20 echo bar >> foo
20 echo bar >> foo
21 echo bar >> bar
21 echo bar >> bar
22 hg ci -m2 -d '2 0'
22 hg ci -m2 -d '2 0'
23
23
24 echo % start imerge
24 echo % start imerge
25 hg imerge
25 hg imerge
26
26
27 cat foo2
27 cat foo2
28 cat bar
28 cat bar
29
29
30 echo % status -v
30 echo % status -v
31 hg -v imerge st
31 hg -v imerge st
32
32
33 echo % next
33 echo % next
34 hg imerge next
34 hg imerge next
35
35
36 echo % merge next
36 echo % merge next
37 hg --traceback imerge
37 hg --traceback imerge
38
38
39 echo % unresolve
39 echo % unresolve
40 hg imerge unres foo
40 hg imerge unres foo
41
41
42 echo % merge foo
42 echo % merge foo
43 hg imerge merge foo
43 hg imerge merge foo
44
44
45 echo % save
45 echo % save
46 echo foo > foo2
46 echo foo > foo2
47 hg imerge save ../savedmerge
47 hg imerge save ../savedmerge
48
48
49 echo % merge auto
50 hg up -C 1
51 hg --traceback imerge --auto
52 cat foo2
53
49 echo % load
54 echo % load
50 hg up -C 0
55 hg up -C 0
51 hg imerge --traceback load ../savedmerge
56 hg imerge --traceback load ../savedmerge
52 cat foo2
57 cat foo2
53
58
54 hg ci -m'merged' -d '3 0'
59 hg ci -m'merged' -d '3 0'
55 hg tip -v
60 hg tip -v
56
61
57 echo % nothing to merge -- tip
62 echo % nothing to merge -- tip
58 hg imerge
63 hg imerge
59
64
60 hg up 0
65 hg up 0
61 echo % nothing to merge
66 echo % nothing to merge
62 hg imerge
67 hg imerge
63
68
64 cd ..
69 cd ..
65 hg -q clone -r 0 base clone
70 hg -q clone -r 0 base clone
66 cd clone
71 cd clone
67 echo % load unknown parent
72 echo % load unknown parent
68 hg imerge load ../savedmerge
73 hg imerge load ../savedmerge
69
74
70 exit 0
75 exit 0
@@ -1,51 +1,65 b''
1 adding bar
1 adding bar
2 adding foo
2 adding foo
3 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
3 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 created new head
4 created new head
5 % start imerge
5 % start imerge
6 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
6 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7 (branch merge, don't forget to commit)
7 (branch merge, don't forget to commit)
8 U foo
8 U foo
9 foo
9 foo
10 bar
10 bar
11 bar
11 bar
12 bar
12 bar
13 % status -v
13 % status -v
14 merging e6da46716401 and 30d266f502e7
14 merging e6da46716401 and 30d266f502e7
15 U foo (foo2)
15 U foo (foo2)
16 % next
16 % next
17 foo
17 foo
18 % merge next
18 % merge next
19 merging foo and foo2
19 merging foo and foo2
20 all conflicts resolved
20 all conflicts resolved
21 % unresolve
21 % unresolve
22 % merge foo
22 % merge foo
23 merging foo and foo2
23 merging foo and foo2
24 all conflicts resolved
24 all conflicts resolved
25 % save
25 % save
26 % merge auto
27 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 (branch merge, don't forget to commit)
30 merging foo2 and foo
31 warning: conflicts during merge.
32 merging foo2 failed!
33 U foo2
34 foo
35 <<<<<<< local
36 foo
37 =======
38 bar
39 >>>>>>> other
26 % load
40 % load
27 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
41 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
28 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 (branch merge, don't forget to commit)
44 (branch merge, don't forget to commit)
31 R foo
45 R foo
32 all conflicts resolved
46 all conflicts resolved
33 foo
47 foo
34 changeset: 3:fa9a6defdcaf
48 changeset: 3:fa9a6defdcaf
35 tag: tip
49 tag: tip
36 parent: 2:e6da46716401
50 parent: 2:e6da46716401
37 parent: 1:30d266f502e7
51 parent: 1:30d266f502e7
38 user: test
52 user: test
39 date: Thu Jan 01 00:00:03 1970 +0000
53 date: Thu Jan 01 00:00:03 1970 +0000
40 files: foo foo2
54 files: foo foo2
41 description:
55 description:
42 merged
56 merged
43
57
44
58
45 % nothing to merge -- tip
59 % nothing to merge -- tip
46 abort: there is nothing to merge
60 abort: there is nothing to merge
47 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
61 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 % nothing to merge
62 % nothing to merge
49 abort: there is nothing to merge - use "hg update" instead
63 abort: there is nothing to merge - use "hg update" instead
50 % load unknown parent
64 % load unknown parent
51 abort: merge parent e6da46716401 not in repository
65 abort: merge parent e6da46716401 not in repository
General Comments 0
You need to be logged in to leave comments. Login now