##// END OF EJS Templates
clone: make things work when source is a repo object
Alexis S. L. Carvalho -
r6089:28054773 default
parent child Browse files
Show More
@@ -1,298 +1,298 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 = ui.expandpath(source)
109 source, rev, checkout = parseurl(origsource, rev)
110
111 if isinstance(source, str):
108 if isinstance(source, str):
109 origsource = ui.expandpath(source)
110 source, rev, checkout = parseurl(origsource, rev)
112 src_repo = repository(ui, source)
111 src_repo = repository(ui, source)
113 else:
112 else:
114 src_repo = source
113 src_repo = source
115 source = src_repo.url()
114 origsource = source = src_repo.url()
115 checkout = None
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 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
176 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
177 os.mkdir(dest_path)
177 os.mkdir(dest_path)
178 if src_repo.spath != src_repo.path:
178 if src_repo.spath != src_repo.path:
179 # XXX racy
179 # XXX racy
180 dummy_changelog = os.path.join(dest_path, "00changelog.i")
180 dummy_changelog = os.path.join(dest_path, "00changelog.i")
181 # copy the dummy changelog
181 # copy the dummy changelog
182 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
182 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
183 dest_store = os.path.join(dest_path, "store")
183 dest_store = os.path.join(dest_path, "store")
184 os.mkdir(dest_store)
184 os.mkdir(dest_store)
185 else:
185 else:
186 dest_store = dest_path
186 dest_store = dest_path
187 # copy the requires file
187 # copy the requires file
188 force_copy(src_repo.join("requires"),
188 force_copy(src_repo.join("requires"),
189 os.path.join(dest_path, "requires"))
189 os.path.join(dest_path, "requires"))
190 # we lock here to avoid premature writing to the target
190 # we lock here to avoid premature writing to the target
191 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
191 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
192
192
193 files = ("data",
193 files = ("data",
194 "00manifest.d", "00manifest.i",
194 "00manifest.d", "00manifest.i",
195 "00changelog.d", "00changelog.i")
195 "00changelog.d", "00changelog.i")
196 for f in files:
196 for f in files:
197 src = os.path.join(src_store, f)
197 src = os.path.join(src_store, f)
198 dst = os.path.join(dest_store, f)
198 dst = os.path.join(dest_store, f)
199 force_copy(src, dst)
199 force_copy(src, dst)
200
200
201 # we need to re-init the repo after manually copying the data
201 # we need to re-init the repo after manually copying the data
202 # into it
202 # into it
203 dest_repo = repository(ui, dest)
203 dest_repo = repository(ui, dest)
204
204
205 else:
205 else:
206 dest_repo = repository(ui, dest, create=True)
206 dest_repo = repository(ui, dest, create=True)
207
207
208 revs = None
208 revs = None
209 if rev:
209 if rev:
210 if 'lookup' not in src_repo.capabilities:
210 if 'lookup' not in src_repo.capabilities:
211 raise util.Abort(_("src repository does not support revision "
211 raise util.Abort(_("src repository does not support revision "
212 "lookup and so doesn't support clone by "
212 "lookup and so doesn't support clone by "
213 "revision"))
213 "revision"))
214 revs = [src_repo.lookup(r) for r in rev]
214 revs = [src_repo.lookup(r) for r in rev]
215
215
216 if dest_repo.local():
216 if dest_repo.local():
217 dest_repo.clone(src_repo, heads=revs, stream=stream)
217 dest_repo.clone(src_repo, heads=revs, stream=stream)
218 elif src_repo.local():
218 elif src_repo.local():
219 src_repo.push(dest_repo, revs=revs)
219 src_repo.push(dest_repo, revs=revs)
220 else:
220 else:
221 raise util.Abort(_("clone from remote to remote not supported"))
221 raise util.Abort(_("clone from remote to remote not supported"))
222
222
223 if dir_cleanup:
223 if dir_cleanup:
224 dir_cleanup.close()
224 dir_cleanup.close()
225
225
226 if dest_repo.local():
226 if dest_repo.local():
227 fp = dest_repo.opener("hgrc", "w", text=True)
227 fp = dest_repo.opener("hgrc", "w", text=True)
228 fp.write("[paths]\n")
228 fp.write("[paths]\n")
229 fp.write("default = %s\n" % abspath)
229 fp.write("default = %s\n" % abspath)
230 fp.close()
230 fp.close()
231
231
232 if update:
232 if update:
233 if not checkout:
233 if not checkout:
234 try:
234 try:
235 checkout = dest_repo.lookup("default")
235 checkout = dest_repo.lookup("default")
236 except:
236 except:
237 checkout = dest_repo.changelog.tip()
237 checkout = dest_repo.changelog.tip()
238 _update(dest_repo, checkout)
238 _update(dest_repo, checkout)
239
239
240 return src_repo, dest_repo
240 return src_repo, dest_repo
241 finally:
241 finally:
242 del src_lock, dest_lock, dir_cleanup
242 del src_lock, dest_lock, dir_cleanup
243
243
244 def _showstats(repo, stats):
244 def _showstats(repo, stats):
245 stats = ((stats[0], _("updated")),
245 stats = ((stats[0], _("updated")),
246 (stats[1], _("merged")),
246 (stats[1], _("merged")),
247 (stats[2], _("removed")),
247 (stats[2], _("removed")),
248 (stats[3], _("unresolved")))
248 (stats[3], _("unresolved")))
249 note = ", ".join([_("%d files %s") % s for s in stats])
249 note = ", ".join([_("%d files %s") % s for s in stats])
250 repo.ui.status("%s\n" % note)
250 repo.ui.status("%s\n" % note)
251
251
252 def _update(repo, node): return update(repo, node)
252 def _update(repo, node): return update(repo, node)
253
253
254 def update(repo, node):
254 def update(repo, node):
255 """update the working directory to node, merging linear changes"""
255 """update the working directory to node, merging linear changes"""
256 pl = repo.parents()
256 pl = repo.parents()
257 stats = _merge.update(repo, node, False, False, None)
257 stats = _merge.update(repo, node, False, False, None)
258 _showstats(repo, stats)
258 _showstats(repo, stats)
259 if stats[3]:
259 if stats[3]:
260 repo.ui.status(_("There are unresolved merges with"
260 repo.ui.status(_("There are unresolved merges with"
261 " locally modified files.\n"))
261 " locally modified files.\n"))
262 if stats[1]:
262 if stats[1]:
263 repo.ui.status(_("You can finish the partial merge using:\n"))
263 repo.ui.status(_("You can finish the partial merge using:\n"))
264 else:
264 else:
265 repo.ui.status(_("You can redo the full merge using:\n"))
265 repo.ui.status(_("You can redo the full merge using:\n"))
266 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
266 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
267 repo.ui.status(_(" hg update %s\n hg update %s\n")
267 repo.ui.status(_(" hg update %s\n hg update %s\n")
268 % (pl[0].rev(), repo.changectx(node).rev()))
268 % (pl[0].rev(), repo.changectx(node).rev()))
269 return stats[3]
269 return stats[3]
270
270
271 def clean(repo, node, show_stats=True):
271 def clean(repo, node, show_stats=True):
272 """forcibly switch the working directory to node, clobbering changes"""
272 """forcibly switch the working directory to node, clobbering changes"""
273 stats = _merge.update(repo, node, False, True, None)
273 stats = _merge.update(repo, node, False, True, None)
274 if show_stats: _showstats(repo, stats)
274 if show_stats: _showstats(repo, stats)
275 return stats[3]
275 return stats[3]
276
276
277 def merge(repo, node, force=None, remind=True):
277 def merge(repo, node, force=None, remind=True):
278 """branch merge with node, resolving changes"""
278 """branch merge with node, resolving changes"""
279 stats = _merge.update(repo, node, True, force, False)
279 stats = _merge.update(repo, node, True, force, False)
280 _showstats(repo, stats)
280 _showstats(repo, stats)
281 if stats[3]:
281 if stats[3]:
282 pl = repo.parents()
282 pl = repo.parents()
283 repo.ui.status(_("There are unresolved merges,"
283 repo.ui.status(_("There are unresolved merges,"
284 " you can redo the full merge using:\n"
284 " you can redo the full merge using:\n"
285 " hg update -C %s\n"
285 " hg update -C %s\n"
286 " hg merge %s\n")
286 " hg merge %s\n")
287 % (pl[0].rev(), pl[1].rev()))
287 % (pl[0].rev(), pl[1].rev()))
288 elif remind:
288 elif remind:
289 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
289 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
290 return stats[3]
290 return stats[3]
291
291
292 def revert(repo, node, choose):
292 def revert(repo, node, choose):
293 """revert changes to revision in node without updating dirstate"""
293 """revert changes to revision in node without updating dirstate"""
294 return _merge.update(repo, node, False, True, choose)[3]
294 return _merge.update(repo, node, False, True, choose)[3]
295
295
296 def verify(repo):
296 def verify(repo):
297 """verify the consistency of a repository"""
297 """verify the consistency of a repository"""
298 return _verify.verify(repo)
298 return _verify.verify(repo)
General Comments 0
You need to be logged in to leave comments. Login now