##// END OF EJS Templates
merge: make return codes more sensible...
Matt Mackall -
r5635:0c608a8d default
parent child Browse files
Show More
@@ -1,312 +1,312 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 i18n import _
11 from i18n import _
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 import errno, lock, os, shutil, util, extensions
13 import errno, lock, os, shutil, util, extensions
14 import merge as _merge
14 import merge as _merge
15 import verify as _verify
15 import verify as _verify
16
16
17 def _local(path):
17 def _local(path):
18 return (os.path.isfile(util.drop_scheme('file', path)) and
18 return (os.path.isfile(util.drop_scheme('file', path)) and
19 bundlerepo or localrepo)
19 bundlerepo or localrepo)
20
20
21 def parseurl(url, revs):
21 def parseurl(url, revs):
22 '''parse url#branch, returning url, branch + revs'''
22 '''parse url#branch, returning url, branch + revs'''
23
23
24 if '#' not in url:
24 if '#' not in url:
25 return url, (revs or None), None
25 return url, (revs or None), None
26
26
27 url, rev = url.split('#', 1)
27 url, rev = url.split('#', 1)
28 return url, revs + [rev], rev
28 return url, revs + [rev], rev
29
29
30 schemes = {
30 schemes = {
31 'bundle': bundlerepo,
31 'bundle': bundlerepo,
32 'file': _local,
32 'file': _local,
33 'http': httprepo,
33 'http': httprepo,
34 'https': httprepo,
34 'https': httprepo,
35 'ssh': sshrepo,
35 'ssh': sshrepo,
36 'static-http': statichttprepo,
36 'static-http': statichttprepo,
37 }
37 }
38
38
39 def _lookup(path):
39 def _lookup(path):
40 scheme = 'file'
40 scheme = 'file'
41 if path:
41 if path:
42 c = path.find(':')
42 c = path.find(':')
43 if c > 0:
43 if c > 0:
44 scheme = path[:c]
44 scheme = path[:c]
45 thing = schemes.get(scheme) or schemes['file']
45 thing = schemes.get(scheme) or schemes['file']
46 try:
46 try:
47 return thing(path)
47 return thing(path)
48 except TypeError:
48 except TypeError:
49 return thing
49 return thing
50
50
51 def islocal(repo):
51 def islocal(repo):
52 '''return true if repo or path is local'''
52 '''return true if repo or path is local'''
53 if isinstance(repo, str):
53 if isinstance(repo, str):
54 try:
54 try:
55 return _lookup(repo).islocal(repo)
55 return _lookup(repo).islocal(repo)
56 except AttributeError:
56 except AttributeError:
57 return False
57 return False
58 return repo.local()
58 return repo.local()
59
59
60 def repository(ui, path='', create=False):
60 def repository(ui, path='', create=False):
61 """return a repository object for the specified path"""
61 """return a repository object for the specified path"""
62 repo = _lookup(path).instance(ui, path, create)
62 repo = _lookup(path).instance(ui, path, create)
63 ui = getattr(repo, "ui", ui)
63 ui = getattr(repo, "ui", ui)
64 for name, module in extensions.extensions():
64 for name, module in extensions.extensions():
65 hook = getattr(module, 'reposetup', None)
65 hook = getattr(module, 'reposetup', None)
66 if hook:
66 if hook:
67 hook(ui, repo)
67 hook(ui, repo)
68 return repo
68 return repo
69
69
70 def defaultdest(source):
70 def defaultdest(source):
71 '''return default destination of clone if none is given'''
71 '''return default destination of clone if none is given'''
72 return os.path.basename(os.path.normpath(source))
72 return os.path.basename(os.path.normpath(source))
73
73
74 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
74 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
75 stream=False):
75 stream=False):
76 """Make a copy of an existing repository.
76 """Make a copy of an existing repository.
77
77
78 Create a copy of an existing repository in a new directory. The
78 Create a copy of an existing repository in a new directory. The
79 source and destination are URLs, as passed to the repository
79 source and destination are URLs, as passed to the repository
80 function. Returns a pair of repository objects, the source and
80 function. Returns a pair of repository objects, the source and
81 newly created destination.
81 newly created destination.
82
82
83 The location of the source is added to the new repository's
83 The location of the source is added to the new repository's
84 .hg/hgrc file, as the default to be used for future pulls and
84 .hg/hgrc file, as the default to be used for future pulls and
85 pushes.
85 pushes.
86
86
87 If an exception is raised, the partly cloned/updated destination
87 If an exception is raised, the partly cloned/updated destination
88 repository will be deleted.
88 repository will be deleted.
89
89
90 Arguments:
90 Arguments:
91
91
92 source: repository object or URL
92 source: repository object or URL
93
93
94 dest: URL of destination repository to create (defaults to base
94 dest: URL of destination repository to create (defaults to base
95 name of source repository)
95 name of source repository)
96
96
97 pull: always pull from source repository, even in local case
97 pull: always pull from source repository, even in local case
98
98
99 stream: stream raw data uncompressed from repository (fast over
99 stream: stream raw data uncompressed from repository (fast over
100 LAN, slow over WAN)
100 LAN, slow over WAN)
101
101
102 rev: revision to clone up to (implies pull=True)
102 rev: revision to clone up to (implies pull=True)
103
103
104 update: update working directory after clone completes, if
104 update: update working directory after clone completes, if
105 destination is local repository
105 destination is local repository
106 """
106 """
107
107
108 origsource = source
108 origsource = source
109 source, rev, checkout = parseurl(ui.expandpath(source), rev)
109 source, rev, checkout = parseurl(ui.expandpath(source), rev)
110
110
111 if isinstance(source, str):
111 if isinstance(source, str):
112 src_repo = repository(ui, source)
112 src_repo = repository(ui, source)
113 else:
113 else:
114 src_repo = source
114 src_repo = source
115 source = src_repo.url()
115 source = src_repo.url()
116
116
117 if dest is None:
117 if dest is None:
118 dest = defaultdest(source)
118 dest = defaultdest(source)
119 ui.status(_("destination directory: %s\n") % dest)
119 ui.status(_("destination directory: %s\n") % dest)
120
120
121 def localpath(path):
121 def localpath(path):
122 if path.startswith('file://'):
122 if path.startswith('file://'):
123 return path[7:]
123 return path[7:]
124 if path.startswith('file:'):
124 if path.startswith('file:'):
125 return path[5:]
125 return path[5:]
126 return path
126 return path
127
127
128 dest = localpath(dest)
128 dest = localpath(dest)
129 source = localpath(source)
129 source = localpath(source)
130
130
131 if os.path.exists(dest):
131 if os.path.exists(dest):
132 raise util.Abort(_("destination '%s' already exists") % dest)
132 raise util.Abort(_("destination '%s' already exists") % dest)
133
133
134 class DirCleanup(object):
134 class DirCleanup(object):
135 def __init__(self, dir_):
135 def __init__(self, dir_):
136 self.rmtree = shutil.rmtree
136 self.rmtree = shutil.rmtree
137 self.dir_ = dir_
137 self.dir_ = dir_
138 def close(self):
138 def close(self):
139 self.dir_ = None
139 self.dir_ = None
140 def __del__(self):
140 def __del__(self):
141 if self.dir_:
141 if self.dir_:
142 self.rmtree(self.dir_, True)
142 self.rmtree(self.dir_, True)
143
143
144 src_lock = dest_lock = dir_cleanup = None
144 src_lock = dest_lock = dir_cleanup = None
145 try:
145 try:
146 if islocal(dest):
146 if islocal(dest):
147 dir_cleanup = DirCleanup(dest)
147 dir_cleanup = DirCleanup(dest)
148
148
149 abspath = origsource
149 abspath = origsource
150 copy = False
150 copy = False
151 if src_repo.local() and islocal(dest):
151 if src_repo.local() and islocal(dest):
152 abspath = os.path.abspath(util.drop_scheme('file', origsource))
152 abspath = os.path.abspath(util.drop_scheme('file', origsource))
153 copy = not pull and not rev
153 copy = not pull and not rev
154
154
155 if copy:
155 if copy:
156 try:
156 try:
157 # we use a lock here because if we race with commit, we
157 # we use a lock here because if we race with commit, we
158 # can end up with extra data in the cloned revlogs that's
158 # can end up with extra data in the cloned revlogs that's
159 # not pointed to by changesets, thus causing verify to
159 # not pointed to by changesets, thus causing verify to
160 # fail
160 # fail
161 src_lock = src_repo.lock()
161 src_lock = src_repo.lock()
162 except lock.LockException:
162 except lock.LockException:
163 copy = False
163 copy = False
164
164
165 if copy:
165 if copy:
166 def force_copy(src, dst):
166 def force_copy(src, dst):
167 try:
167 try:
168 util.copyfiles(src, dst)
168 util.copyfiles(src, dst)
169 except OSError, inst:
169 except OSError, inst:
170 if inst.errno != errno.ENOENT:
170 if inst.errno != errno.ENOENT:
171 raise
171 raise
172
172
173 src_store = os.path.realpath(src_repo.spath)
173 src_store = os.path.realpath(src_repo.spath)
174 if not os.path.exists(dest):
174 if not os.path.exists(dest):
175 os.mkdir(dest)
175 os.mkdir(dest)
176 try:
176 try:
177 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
177 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
178 os.mkdir(dest_path)
178 os.mkdir(dest_path)
179 except OSError, inst:
179 except OSError, inst:
180 if inst.errno == errno.EEXIST:
180 if inst.errno == errno.EEXIST:
181 dir_cleanup.close()
181 dir_cleanup.close()
182 raise util.Abort(_("destination '%s' already exists")
182 raise util.Abort(_("destination '%s' already exists")
183 % dest)
183 % dest)
184 raise
184 raise
185 if src_repo.spath != src_repo.path:
185 if src_repo.spath != src_repo.path:
186 # XXX racy
186 # XXX racy
187 dummy_changelog = os.path.join(dest_path, "00changelog.i")
187 dummy_changelog = os.path.join(dest_path, "00changelog.i")
188 # copy the dummy changelog
188 # copy the dummy changelog
189 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
189 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
190 dest_store = os.path.join(dest_path, "store")
190 dest_store = os.path.join(dest_path, "store")
191 os.mkdir(dest_store)
191 os.mkdir(dest_store)
192 else:
192 else:
193 dest_store = dest_path
193 dest_store = dest_path
194 # copy the requires file
194 # copy the requires file
195 force_copy(src_repo.join("requires"),
195 force_copy(src_repo.join("requires"),
196 os.path.join(dest_path, "requires"))
196 os.path.join(dest_path, "requires"))
197 # we lock here to avoid premature writing to the target
197 # we lock here to avoid premature writing to the target
198 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
198 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
199
199
200 files = ("data",
200 files = ("data",
201 "00manifest.d", "00manifest.i",
201 "00manifest.d", "00manifest.i",
202 "00changelog.d", "00changelog.i")
202 "00changelog.d", "00changelog.i")
203 for f in files:
203 for f in files:
204 src = os.path.join(src_store, f)
204 src = os.path.join(src_store, f)
205 dst = os.path.join(dest_store, f)
205 dst = os.path.join(dest_store, f)
206 force_copy(src, dst)
206 force_copy(src, dst)
207
207
208 # we need to re-init the repo after manually copying the data
208 # we need to re-init the repo after manually copying the data
209 # into it
209 # into it
210 dest_repo = repository(ui, dest)
210 dest_repo = repository(ui, dest)
211
211
212 else:
212 else:
213 try:
213 try:
214 dest_repo = repository(ui, dest, create=True)
214 dest_repo = repository(ui, dest, create=True)
215 except OSError, inst:
215 except OSError, inst:
216 if inst.errno == errno.EEXIST:
216 if inst.errno == errno.EEXIST:
217 dir_cleanup.close()
217 dir_cleanup.close()
218 raise util.Abort(_("destination '%s' already exists")
218 raise util.Abort(_("destination '%s' already exists")
219 % dest)
219 % dest)
220 raise
220 raise
221
221
222 revs = None
222 revs = None
223 if rev:
223 if rev:
224 if 'lookup' not in src_repo.capabilities:
224 if 'lookup' not in src_repo.capabilities:
225 raise util.Abort(_("src repository does not support revision "
225 raise util.Abort(_("src repository does not support revision "
226 "lookup and so doesn't support clone by "
226 "lookup and so doesn't support clone by "
227 "revision"))
227 "revision"))
228 revs = [src_repo.lookup(r) for r in rev]
228 revs = [src_repo.lookup(r) for r in rev]
229
229
230 if dest_repo.local():
230 if dest_repo.local():
231 dest_repo.clone(src_repo, heads=revs, stream=stream)
231 dest_repo.clone(src_repo, heads=revs, stream=stream)
232 elif src_repo.local():
232 elif src_repo.local():
233 src_repo.push(dest_repo, revs=revs)
233 src_repo.push(dest_repo, revs=revs)
234 else:
234 else:
235 raise util.Abort(_("clone from remote to remote not supported"))
235 raise util.Abort(_("clone from remote to remote not supported"))
236
236
237 if dir_cleanup:
237 if dir_cleanup:
238 dir_cleanup.close()
238 dir_cleanup.close()
239
239
240 if dest_repo.local():
240 if dest_repo.local():
241 fp = dest_repo.opener("hgrc", "w", text=True)
241 fp = dest_repo.opener("hgrc", "w", text=True)
242 fp.write("[paths]\n")
242 fp.write("[paths]\n")
243 fp.write("default = %s\n" % abspath)
243 fp.write("default = %s\n" % abspath)
244 fp.close()
244 fp.close()
245
245
246 if update:
246 if update:
247 if not checkout:
247 if not checkout:
248 try:
248 try:
249 checkout = dest_repo.lookup("default")
249 checkout = dest_repo.lookup("default")
250 except:
250 except:
251 checkout = dest_repo.changelog.tip()
251 checkout = dest_repo.changelog.tip()
252 _update(dest_repo, checkout)
252 _update(dest_repo, checkout)
253
253
254 return src_repo, dest_repo
254 return src_repo, dest_repo
255 finally:
255 finally:
256 del src_lock, dest_lock, dir_cleanup
256 del src_lock, dest_lock, dir_cleanup
257
257
258 def _showstats(repo, stats):
258 def _showstats(repo, stats):
259 stats = ((stats[0], _("updated")),
259 stats = ((stats[0], _("updated")),
260 (stats[1], _("merged")),
260 (stats[1], _("merged")),
261 (stats[2], _("removed")),
261 (stats[2], _("removed")),
262 (stats[3], _("unresolved")))
262 (stats[3], _("unresolved")))
263 note = ", ".join([_("%d files %s") % s for s in stats])
263 note = ", ".join([_("%d files %s") % s for s in stats])
264 repo.ui.status("%s\n" % note)
264 repo.ui.status("%s\n" % note)
265
265
266 def _update(repo, node): return update(repo, node)
266 def _update(repo, node): return update(repo, node)
267
267
268 def update(repo, node):
268 def update(repo, node):
269 """update the working directory to node, merging linear changes"""
269 """update the working directory to node, merging linear changes"""
270 pl = repo.parents()
270 pl = repo.parents()
271 stats = _merge.update(repo, node, False, False, None)
271 stats = _merge.update(repo, node, False, False, None)
272 _showstats(repo, stats)
272 _showstats(repo, stats)
273 if stats[3]:
273 if stats[3]:
274 repo.ui.status(_("There are unresolved merges with"
274 repo.ui.status(_("There are unresolved merges with"
275 " locally modified files.\n"))
275 " locally modified files.\n"))
276 if stats[1]:
276 if stats[1]:
277 repo.ui.status(_("You can finish the partial merge using:\n"))
277 repo.ui.status(_("You can finish the partial merge using:\n"))
278 else:
278 else:
279 repo.ui.status(_("You can redo the full merge using:\n"))
279 repo.ui.status(_("You can redo the full merge using:\n"))
280 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
280 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
281 repo.ui.status(_(" hg update %s\n hg update %s\n")
281 repo.ui.status(_(" hg update %s\n hg update %s\n")
282 % (pl[0].rev(), repo.changectx(node).rev()))
282 % (pl[0].rev(), repo.changectx(node).rev()))
283 return stats[3]
283 return stats[3] > 0
284
284
285 def clean(repo, node, show_stats=True):
285 def clean(repo, node, show_stats=True):
286 """forcibly switch the working directory to node, clobbering changes"""
286 """forcibly switch the working directory to node, clobbering changes"""
287 stats = _merge.update(repo, node, False, True, None)
287 stats = _merge.update(repo, node, False, True, None)
288 if show_stats: _showstats(repo, stats)
288 if show_stats: _showstats(repo, stats)
289 return stats[3]
289 return stats[3] > 0
290
290
291 def merge(repo, node, force=None, remind=True):
291 def merge(repo, node, force=None, remind=True):
292 """branch merge with node, resolving changes"""
292 """branch merge with node, resolving changes"""
293 stats = _merge.update(repo, node, True, force, False)
293 stats = _merge.update(repo, node, True, force, False)
294 _showstats(repo, stats)
294 _showstats(repo, stats)
295 if stats[3]:
295 if stats[3]:
296 pl = repo.parents()
296 pl = repo.parents()
297 repo.ui.status(_("There are unresolved merges,"
297 repo.ui.status(_("There are unresolved merges,"
298 " you can redo the full merge using:\n"
298 " you can redo the full merge using:\n"
299 " hg update -C %s\n"
299 " hg update -C %s\n"
300 " hg merge %s\n")
300 " hg merge %s\n")
301 % (pl[0].rev(), pl[1].rev()))
301 % (pl[0].rev(), pl[1].rev()))
302 elif remind:
302 elif remind:
303 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
303 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
304 return stats[3]
304 return stats[3] > 0
305
305
306 def revert(repo, node, choose):
306 def revert(repo, node, choose):
307 """revert changes to revision in node without updating dirstate"""
307 """revert changes to revision in node without updating dirstate"""
308 return _merge.update(repo, node, False, True, choose)[3]
308 return _merge.update(repo, node, False, True, choose)[3] > 0
309
309
310 def verify(repo):
310 def verify(repo):
311 """verify the consistency of a repository"""
311 """verify the consistency of a repository"""
312 return _verify.verify(repo)
312 return _verify.verify(repo)
General Comments 0
You need to be logged in to leave comments. Login now