##// END OF EJS Templates
Add instructions how to redo/finish failed merge with local working directory....
Thomas Arendsen Hein -
r3869:ad6f34c8 default
parent child Browse files
Show More
@@ -0,0 +1,50 b''
1 #!/bin/sh
2
3 hg init
4
5 echo "# revision 0"
6 echo "unchanged" > unchanged
7 echo "remove me" > remove
8 echo "copy me" > copy
9 echo "move me" > move
10 for i in 1 2 3 4 5 6 7 8 9; do
11 echo "merge ok $i" >> zzz1_merge_ok
12 done
13 echo "merge bad" > zzz2_merge_bad
14 hg ci -Am "revision 0" -d "1000000 0"
15
16 echo "# revision 1"
17 hg rm remove
18 hg mv move moved
19 hg cp copy copied
20 echo "added" > added
21 hg add added
22 echo "new first line" > zzz1_merge_ok
23 hg cat zzz1_merge_ok >> zzz1_merge_ok
24 echo "new last line" >> zzz2_merge_bad
25 hg ci -m "revision 1" -d "1000000 0"
26
27 echo "# local changes to revision 0"
28 hg co 0
29 echo "new last line" >> zzz1_merge_ok
30 echo "another last line" >> zzz2_merge_bad
31 hg diff --nodates | grep "^[+-][^<>]"
32 hg st
33
34 echo "# local merge with bad merge tool"
35 HGMERGE=false hg co
36 hg co 0
37 hg diff --nodates | grep "^[+-][^<>]"
38 hg st
39
40 echo "# local merge with conflicts"
41 HGMERGE=merge hg co
42 hg co 0
43 hg diff --nodates | grep "^[+-][^<>]"
44 hg st
45
46 echo "# local merge without conflicts"
47 hg revert zzz2_merge_bad
48 HGMERGE=merge hg co
49 hg diff --nodates | grep "^[+-][^<>]"
50 hg st
@@ -0,0 +1,67 b''
1 # revision 0
2 adding copy
3 adding move
4 adding remove
5 adding unchanged
6 adding zzz1_merge_ok
7 adding zzz2_merge_bad
8 # revision 1
9 # local changes to revision 0
10 4 files updated, 0 files merged, 3 files removed, 0 files unresolved
11 --- a/zzz1_merge_ok
12 +++ b/zzz1_merge_ok
13 +new last line
14 --- a/zzz2_merge_bad
15 +++ b/zzz2_merge_bad
16 +another last line
17 M zzz1_merge_ok
18 M zzz2_merge_bad
19 # local merge with bad merge tool
20 merging zzz1_merge_ok
21 merging zzz1_merge_ok failed!
22 merging zzz2_merge_bad
23 merging zzz2_merge_bad failed!
24 3 files updated, 0 files merged, 2 files removed, 2 files unresolved
25 There are unresolved merges with locally modified files.
26 You can redo the full merge using:
27 hg update 0
28 hg update 1
29 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
30 --- a/zzz1_merge_ok
31 +++ b/zzz1_merge_ok
32 +new last line
33 --- a/zzz2_merge_bad
34 +++ b/zzz2_merge_bad
35 +another last line
36 M zzz1_merge_ok
37 M zzz2_merge_bad
38 # local merge with conflicts
39 merge: warning: conflicts during merge
40 merging zzz1_merge_ok
41 merging zzz2_merge_bad
42 merging zzz2_merge_bad failed!
43 3 files updated, 1 files merged, 2 files removed, 1 files unresolved
44 There are unresolved merges with locally modified files.
45 You can finish the partial merge using:
46 hg update 0
47 hg update 1
48 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
49 --- a/zzz1_merge_ok
50 +++ b/zzz1_merge_ok
51 +new first line
52 +new last line
53 --- a/zzz2_merge_bad
54 +++ b/zzz2_merge_bad
55 +another last line
56 +=======
57 +new last line
58 M zzz1_merge_ok
59 M zzz2_merge_bad
60 # local merge without conflicts
61 merging zzz1_merge_ok
62 4 files updated, 1 files merged, 2 files removed, 0 files unresolved
63 --- a/zzz1_merge_ok
64 +++ b/zzz1_merge_ok
65 +new last line
66 M zzz1_merge_ok
67 ? zzz2_merge_bad.orig
@@ -1,271 +1,279 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from node import *
9 from node import *
10 from repo import *
10 from repo import *
11 from demandload import *
11 from demandload import *
12 from i18n import gettext as _
12 from i18n import gettext as _
13 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
13 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
14 demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
14 demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
15
15
16 def _local(path):
16 def _local(path):
17 return (os.path.isfile(util.drop_scheme('file', path)) and
17 return (os.path.isfile(util.drop_scheme('file', path)) and
18 bundlerepo or localrepo)
18 bundlerepo or localrepo)
19
19
20 schemes = {
20 schemes = {
21 'bundle': bundlerepo,
21 'bundle': bundlerepo,
22 'file': _local,
22 'file': _local,
23 'hg': httprepo,
23 'hg': httprepo,
24 'http': httprepo,
24 'http': httprepo,
25 'https': httprepo,
25 'https': httprepo,
26 'old-http': statichttprepo,
26 'old-http': statichttprepo,
27 'ssh': sshrepo,
27 'ssh': sshrepo,
28 'static-http': statichttprepo,
28 'static-http': statichttprepo,
29 }
29 }
30
30
31 def _lookup(path):
31 def _lookup(path):
32 scheme = 'file'
32 scheme = 'file'
33 if path:
33 if path:
34 c = path.find(':')
34 c = path.find(':')
35 if c > 0:
35 if c > 0:
36 scheme = path[:c]
36 scheme = path[:c]
37 thing = schemes.get(scheme) or schemes['file']
37 thing = schemes.get(scheme) or schemes['file']
38 try:
38 try:
39 return thing(path)
39 return thing(path)
40 except TypeError:
40 except TypeError:
41 return thing
41 return thing
42
42
43 def islocal(repo):
43 def islocal(repo):
44 '''return true if repo or path is local'''
44 '''return true if repo or path is local'''
45 if isinstance(repo, str):
45 if isinstance(repo, str):
46 try:
46 try:
47 return _lookup(repo).islocal(repo)
47 return _lookup(repo).islocal(repo)
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50 return repo.local()
50 return repo.local()
51
51
52 repo_setup_hooks = []
52 repo_setup_hooks = []
53
53
54 def repository(ui, path='', create=False):
54 def repository(ui, path='', create=False):
55 """return a repository object for the specified path"""
55 """return a repository object for the specified path"""
56 repo = _lookup(path).instance(ui, path, create)
56 repo = _lookup(path).instance(ui, path, create)
57 for hook in repo_setup_hooks:
57 for hook in repo_setup_hooks:
58 hook(ui, repo)
58 hook(ui, repo)
59 return repo
59 return repo
60
60
61 def defaultdest(source):
61 def defaultdest(source):
62 '''return default destination of clone if none is given'''
62 '''return default destination of clone if none is given'''
63 return os.path.basename(os.path.normpath(source))
63 return os.path.basename(os.path.normpath(source))
64
64
65 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
65 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
66 stream=False):
66 stream=False):
67 """Make a copy of an existing repository.
67 """Make a copy of an existing repository.
68
68
69 Create a copy of an existing repository in a new directory. The
69 Create a copy of an existing repository in a new directory. The
70 source and destination are URLs, as passed to the repository
70 source and destination are URLs, as passed to the repository
71 function. Returns a pair of repository objects, the source and
71 function. Returns a pair of repository objects, the source and
72 newly created destination.
72 newly created destination.
73
73
74 The location of the source is added to the new repository's
74 The location of the source is added to the new repository's
75 .hg/hgrc file, as the default to be used for future pulls and
75 .hg/hgrc file, as the default to be used for future pulls and
76 pushes.
76 pushes.
77
77
78 If an exception is raised, the partly cloned/updated destination
78 If an exception is raised, the partly cloned/updated destination
79 repository will be deleted.
79 repository will be deleted.
80
80
81 Arguments:
81 Arguments:
82
82
83 source: repository object or URL
83 source: repository object or URL
84
84
85 dest: URL of destination repository to create (defaults to base
85 dest: URL of destination repository to create (defaults to base
86 name of source repository)
86 name of source repository)
87
87
88 pull: always pull from source repository, even in local case
88 pull: always pull from source repository, even in local case
89
89
90 stream: stream raw data uncompressed from repository (fast over
90 stream: stream raw data uncompressed from repository (fast over
91 LAN, slow over WAN)
91 LAN, slow over WAN)
92
92
93 rev: revision to clone up to (implies pull=True)
93 rev: revision to clone up to (implies pull=True)
94
94
95 update: update working directory after clone completes, if
95 update: update working directory after clone completes, if
96 destination is local repository
96 destination is local repository
97 """
97 """
98 if isinstance(source, str):
98 if isinstance(source, str):
99 src_repo = repository(ui, source)
99 src_repo = repository(ui, source)
100 else:
100 else:
101 src_repo = source
101 src_repo = source
102 source = src_repo.url()
102 source = src_repo.url()
103
103
104 if dest is None:
104 if dest is None:
105 dest = defaultdest(source)
105 dest = defaultdest(source)
106 ui.status(_("destination directory: %s\n") % dest)
106 ui.status(_("destination directory: %s\n") % dest)
107
107
108 def localpath(path):
108 def localpath(path):
109 if path.startswith('file://'):
109 if path.startswith('file://'):
110 return path[7:]
110 return path[7:]
111 if path.startswith('file:'):
111 if path.startswith('file:'):
112 return path[5:]
112 return path[5:]
113 return path
113 return path
114
114
115 dest = localpath(dest)
115 dest = localpath(dest)
116 source = localpath(source)
116 source = localpath(source)
117
117
118 if os.path.exists(dest):
118 if os.path.exists(dest):
119 raise util.Abort(_("destination '%s' already exists") % dest)
119 raise util.Abort(_("destination '%s' already exists") % dest)
120
120
121 class DirCleanup(object):
121 class DirCleanup(object):
122 def __init__(self, dir_):
122 def __init__(self, dir_):
123 self.rmtree = shutil.rmtree
123 self.rmtree = shutil.rmtree
124 self.dir_ = dir_
124 self.dir_ = dir_
125 def close(self):
125 def close(self):
126 self.dir_ = None
126 self.dir_ = None
127 def __del__(self):
127 def __del__(self):
128 if self.dir_:
128 if self.dir_:
129 self.rmtree(self.dir_, True)
129 self.rmtree(self.dir_, True)
130
130
131 dir_cleanup = None
131 dir_cleanup = None
132 if islocal(dest):
132 if islocal(dest):
133 dir_cleanup = DirCleanup(dest)
133 dir_cleanup = DirCleanup(dest)
134
134
135 abspath = source
135 abspath = source
136 copy = False
136 copy = False
137 if src_repo.local() and islocal(dest):
137 if src_repo.local() and islocal(dest):
138 abspath = os.path.abspath(source)
138 abspath = os.path.abspath(source)
139 copy = not pull and not rev
139 copy = not pull and not rev
140
140
141 src_lock, dest_lock = None, None
141 src_lock, dest_lock = None, None
142 if copy:
142 if copy:
143 try:
143 try:
144 # we use a lock here because if we race with commit, we
144 # we use a lock here because if we race with commit, we
145 # can end up with extra data in the cloned revlogs that's
145 # can end up with extra data in the cloned revlogs that's
146 # not pointed to by changesets, thus causing verify to
146 # not pointed to by changesets, thus causing verify to
147 # fail
147 # fail
148 src_lock = src_repo.lock()
148 src_lock = src_repo.lock()
149 except lock.LockException:
149 except lock.LockException:
150 copy = False
150 copy = False
151
151
152 if copy:
152 if copy:
153 def force_copy(src, dst):
153 def force_copy(src, dst):
154 try:
154 try:
155 util.copyfiles(src, dst)
155 util.copyfiles(src, dst)
156 except OSError, inst:
156 except OSError, inst:
157 if inst.errno != errno.ENOENT:
157 if inst.errno != errno.ENOENT:
158 raise
158 raise
159
159
160 src_store = os.path.realpath(src_repo.spath)
160 src_store = os.path.realpath(src_repo.spath)
161 if not os.path.exists(dest):
161 if not os.path.exists(dest):
162 os.mkdir(dest)
162 os.mkdir(dest)
163 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
163 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
164 os.mkdir(dest_path)
164 os.mkdir(dest_path)
165 if src_repo.spath != src_repo.path:
165 if src_repo.spath != src_repo.path:
166 dest_store = os.path.join(dest_path, "store")
166 dest_store = os.path.join(dest_path, "store")
167 os.mkdir(dest_store)
167 os.mkdir(dest_store)
168 else:
168 else:
169 dest_store = dest_path
169 dest_store = dest_path
170 # copy the requires file
170 # copy the requires file
171 force_copy(src_repo.join("requires"),
171 force_copy(src_repo.join("requires"),
172 os.path.join(dest_path, "requires"))
172 os.path.join(dest_path, "requires"))
173 # we lock here to avoid premature writing to the target
173 # we lock here to avoid premature writing to the target
174 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
174 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
175
175
176 files = ("data",
176 files = ("data",
177 "00manifest.d", "00manifest.i",
177 "00manifest.d", "00manifest.i",
178 "00changelog.d", "00changelog.i")
178 "00changelog.d", "00changelog.i")
179 for f in files:
179 for f in files:
180 src = os.path.join(src_store, f)
180 src = os.path.join(src_store, f)
181 dst = os.path.join(dest_store, f)
181 dst = os.path.join(dest_store, f)
182 force_copy(src, dst)
182 force_copy(src, dst)
183
183
184 # we need to re-init the repo after manually copying the data
184 # we need to re-init the repo after manually copying the data
185 # into it
185 # into it
186 dest_repo = repository(ui, dest)
186 dest_repo = repository(ui, dest)
187
187
188 else:
188 else:
189 dest_repo = repository(ui, dest, create=True)
189 dest_repo = repository(ui, dest, create=True)
190
190
191 revs = None
191 revs = None
192 if rev:
192 if rev:
193 if 'lookup' not in src_repo.capabilities:
193 if 'lookup' not in src_repo.capabilities:
194 raise util.Abort(_("src repository does not support revision "
194 raise util.Abort(_("src repository does not support revision "
195 "lookup and so doesn't support clone by "
195 "lookup and so doesn't support clone by "
196 "revision"))
196 "revision"))
197 revs = [src_repo.lookup(r) for r in rev]
197 revs = [src_repo.lookup(r) for r in rev]
198
198
199 if dest_repo.local():
199 if dest_repo.local():
200 dest_repo.clone(src_repo, heads=revs, stream=stream)
200 dest_repo.clone(src_repo, heads=revs, stream=stream)
201 elif src_repo.local():
201 elif src_repo.local():
202 src_repo.push(dest_repo, revs=revs)
202 src_repo.push(dest_repo, revs=revs)
203 else:
203 else:
204 raise util.Abort(_("clone from remote to remote not supported"))
204 raise util.Abort(_("clone from remote to remote not supported"))
205
205
206 if src_lock:
206 if src_lock:
207 src_lock.release()
207 src_lock.release()
208
208
209 if dest_repo.local():
209 if dest_repo.local():
210 fp = dest_repo.opener("hgrc", "w", text=True)
210 fp = dest_repo.opener("hgrc", "w", text=True)
211 fp.write("[paths]\n")
211 fp.write("[paths]\n")
212 fp.write("default = %s\n" % abspath)
212 fp.write("default = %s\n" % abspath)
213 fp.close()
213 fp.close()
214
214
215 if dest_lock:
215 if dest_lock:
216 dest_lock.release()
216 dest_lock.release()
217
217
218 if update:
218 if update:
219 _update(dest_repo, dest_repo.changelog.tip())
219 _update(dest_repo, dest_repo.changelog.tip())
220 if dir_cleanup:
220 if dir_cleanup:
221 dir_cleanup.close()
221 dir_cleanup.close()
222
222
223 return src_repo, dest_repo
223 return src_repo, dest_repo
224
224
225 def _showstats(repo, stats):
225 def _showstats(repo, stats):
226 stats = ((stats[0], _("updated")),
226 stats = ((stats[0], _("updated")),
227 (stats[1], _("merged")),
227 (stats[1], _("merged")),
228 (stats[2], _("removed")),
228 (stats[2], _("removed")),
229 (stats[3], _("unresolved")))
229 (stats[3], _("unresolved")))
230 note = ", ".join([_("%d files %s") % s for s in stats])
230 note = ", ".join([_("%d files %s") % s for s in stats])
231 repo.ui.status("%s\n" % note)
231 repo.ui.status("%s\n" % note)
232
232
233 def _update(repo, node): return update(repo, node)
233 def _update(repo, node): return update(repo, node)
234
234
235 def update(repo, node):
235 def update(repo, node):
236 """update the working directory to node, merging linear changes"""
236 """update the working directory to node, merging linear changes"""
237 pl = repo.parents()
237 stats = _merge.update(repo, node, False, False, None, None)
238 stats = _merge.update(repo, node, False, False, None, None)
238 _showstats(repo, stats)
239 _showstats(repo, stats)
239 if stats[3]:
240 if stats[3]:
240 repo.ui.status(_("There are unresolved merges with"
241 repo.ui.status(_("There are unresolved merges with"
241 " locally modified files.\n"))
242 " locally modified files.\n"))
243 if stats[1]:
244 repo.ui.status(_("You can finish the partial merge using:\n"))
245 else:
246 repo.ui.status(_("You can redo the full merge using:\n"))
247 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
248 repo.ui.status(_(" hg update %s\n hg update %s\n")
249 % (pl[0].rev(), repo.changectx(node).rev()))
242 return stats[3]
250 return stats[3]
243
251
244 def clean(repo, node, wlock=None, show_stats=True):
252 def clean(repo, node, wlock=None, show_stats=True):
245 """forcibly switch the working directory to node, clobbering changes"""
253 """forcibly switch the working directory to node, clobbering changes"""
246 stats = _merge.update(repo, node, False, True, None, wlock)
254 stats = _merge.update(repo, node, False, True, None, wlock)
247 if show_stats: _showstats(repo, stats)
255 if show_stats: _showstats(repo, stats)
248 return stats[3]
256 return stats[3]
249
257
250 def merge(repo, node, force=None, remind=True, wlock=None):
258 def merge(repo, node, force=None, remind=True, wlock=None):
251 """branch merge with node, resolving changes"""
259 """branch merge with node, resolving changes"""
252 stats = _merge.update(repo, node, True, force, False, wlock)
260 stats = _merge.update(repo, node, True, force, False, wlock)
253 _showstats(repo, stats)
261 _showstats(repo, stats)
254 if stats[3]:
262 if stats[3]:
255 pl = repo.parents()
263 pl = repo.parents()
256 repo.ui.status(_("There are unresolved merges,"
264 repo.ui.status(_("There are unresolved merges,"
257 " you can redo the full merge using:\n"
265 " you can redo the full merge using:\n"
258 " hg update -C %s\n"
266 " hg update -C %s\n"
259 " hg merge %s\n")
267 " hg merge %s\n")
260 % (pl[0].rev(), pl[1].rev()))
268 % (pl[0].rev(), pl[1].rev()))
261 elif remind:
269 elif remind:
262 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
270 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
263 return stats[3]
271 return stats[3]
264
272
265 def revert(repo, node, choose, wlock):
273 def revert(repo, node, choose, wlock):
266 """revert changes to revision in node without updating dirstate"""
274 """revert changes to revision in node without updating dirstate"""
267 return _merge.update(repo, node, False, True, choose, wlock)[3]
275 return _merge.update(repo, node, False, True, choose, wlock)[3]
268
276
269 def verify(repo):
277 def verify(repo):
270 """verify the consistency of a repository"""
278 """verify the consistency of a repository"""
271 return _verify.verify(repo)
279 return _verify.verify(repo)
@@ -1,34 +1,37 b''
1 1:f248da0d4c3e
1 1:f248da0d4c3e
2 0:9eca13a34789
2 0:9eca13a34789
3 f248da0d4c3e tip
3 f248da0d4c3e tip
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 9eca13a34789
5 9eca13a34789
6 9eca13a34789+
6 9eca13a34789+
7 reverting file1
7 reverting file1
8 9eca13a34789
8 9eca13a34789
9 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
9 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 f248da0d4c3e tip
10 f248da0d4c3e tip
11 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 merge: warning: conflicts during merge
12 merge: warning: conflicts during merge
13 merging file1
13 merging file1
14 merging file1 failed!
14 merging file1 failed!
15 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
15 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
16 There are unresolved merges with locally modified files.
16 There are unresolved merges with locally modified files.
17 You can redo the full merge using:
18 hg update 0
19 hg update 1
17 diff -r f248da0d4c3e file1
20 diff -r f248da0d4c3e file1
18 --- a/file1
21 --- a/file1
19 +++ b/file1
22 +++ b/file1
20 @@ -1,3 +1,7 @@ added file1
23 @@ -1,3 +1,7 @@ added file1
21 added file1
24 added file1
22 another line of text
25 another line of text
23 +<<<<<<<
26 +<<<<<<<
24 +changed file1 different
27 +changed file1 different
25 +=======
28 +=======
26 changed file1
29 changed file1
27 +>>>>>>>
30 +>>>>>>>
28 M file1
31 M file1
29 f248da0d4c3e+ tip
32 f248da0d4c3e+ tip
30 reverting file1
33 reverting file1
31 f248da0d4c3e tip
34 f248da0d4c3e tip
32 f248da0d4c3e tip
35 f248da0d4c3e tip
33 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 f248da0d4c3e tip
37 f248da0d4c3e tip
General Comments 0
You need to be logged in to leave comments. Login now