##// END OF EJS Templates
allow clone into existing but empty directories
Steve Borho -
r7927:a218ba5f default
parent child Browse files
Show More
@@ -1,289 +1,292 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from i18n import _
10 10 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
11 11 import errno, lock, os, shutil, util, extensions, error
12 12 import merge as _merge
13 13 import verify as _verify
14 14
15 15 def _local(path):
16 16 return (os.path.isfile(util.drop_scheme('file', path)) and
17 17 bundlerepo or localrepo)
18 18
19 19 def parseurl(url, revs=[]):
20 20 '''parse url#branch, returning url, branch + revs'''
21 21
22 22 if '#' not in url:
23 23 return url, (revs or None), revs and revs[-1] or None
24 24
25 25 url, branch = url.split('#', 1)
26 26 checkout = revs and revs[-1] or branch
27 27 return url, revs + [branch], checkout
28 28
29 29 schemes = {
30 30 'bundle': bundlerepo,
31 31 'file': _local,
32 32 'http': httprepo,
33 33 'https': httprepo,
34 34 'ssh': sshrepo,
35 35 'static-http': statichttprepo,
36 36 }
37 37
38 38 def _lookup(path):
39 39 scheme = 'file'
40 40 if path:
41 41 c = path.find(':')
42 42 if c > 0:
43 43 scheme = path[:c]
44 44 thing = schemes.get(scheme) or schemes['file']
45 45 try:
46 46 return thing(path)
47 47 except TypeError:
48 48 return thing
49 49
50 50 def islocal(repo):
51 51 '''return true if repo or path is local'''
52 52 if isinstance(repo, str):
53 53 try:
54 54 return _lookup(repo).islocal(repo)
55 55 except AttributeError:
56 56 return False
57 57 return repo.local()
58 58
59 59 def repository(ui, path='', create=False):
60 60 """return a repository object for the specified path"""
61 61 repo = _lookup(path).instance(ui, path, create)
62 62 ui = getattr(repo, "ui", ui)
63 63 for name, module in extensions.extensions():
64 64 hook = getattr(module, 'reposetup', None)
65 65 if hook:
66 66 hook(ui, repo)
67 67 return repo
68 68
69 69 def defaultdest(source):
70 70 '''return default destination of clone if none is given'''
71 71 return os.path.basename(os.path.normpath(source))
72 72
73 73 def localpath(path):
74 74 if path.startswith('file://localhost/'):
75 75 return path[16:]
76 76 if path.startswith('file://'):
77 77 return path[7:]
78 78 if path.startswith('file:'):
79 79 return path[5:]
80 80 return path
81 81
82 82 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
83 83 stream=False):
84 84 """Make a copy of an existing repository.
85 85
86 86 Create a copy of an existing repository in a new directory. The
87 87 source and destination are URLs, as passed to the repository
88 88 function. Returns a pair of repository objects, the source and
89 89 newly created destination.
90 90
91 91 The location of the source is added to the new repository's
92 92 .hg/hgrc file, as the default to be used for future pulls and
93 93 pushes.
94 94
95 95 If an exception is raised, the partly cloned/updated destination
96 96 repository will be deleted.
97 97
98 98 Arguments:
99 99
100 100 source: repository object or URL
101 101
102 102 dest: URL of destination repository to create (defaults to base
103 103 name of source repository)
104 104
105 105 pull: always pull from source repository, even in local case
106 106
107 107 stream: stream raw data uncompressed from repository (fast over
108 108 LAN, slow over WAN)
109 109
110 110 rev: revision to clone up to (implies pull=True)
111 111
112 112 update: update working directory after clone completes, if
113 113 destination is local repository (True means update to default rev,
114 114 anything else is treated as a revision)
115 115 """
116 116
117 117 if isinstance(source, str):
118 118 origsource = ui.expandpath(source)
119 119 source, rev, checkout = parseurl(origsource, rev)
120 120 src_repo = repository(ui, source)
121 121 else:
122 122 src_repo = source
123 123 origsource = source = src_repo.url()
124 124 checkout = rev and rev[-1] or None
125 125
126 126 if dest is None:
127 127 dest = defaultdest(source)
128 128 ui.status(_("destination directory: %s\n") % dest)
129 129
130 130 dest = localpath(dest)
131 131 source = localpath(source)
132 132
133 133 if os.path.exists(dest):
134 raise util.Abort(_("destination '%s' already exists") % dest)
134 if not os.path.isdir(dest):
135 raise util.Abort(_("destination '%s' already exists") % dest)
136 elif os.listdir(dest):
137 raise util.Abort(_("destination '%s' is not empty") % dest)
135 138
136 139 class DirCleanup(object):
137 140 def __init__(self, dir_):
138 141 self.rmtree = shutil.rmtree
139 142 self.dir_ = dir_
140 143 def close(self):
141 144 self.dir_ = None
142 145 def __del__(self):
143 146 if self.dir_:
144 147 self.rmtree(self.dir_, True)
145 148
146 149 src_lock = dest_lock = dir_cleanup = None
147 150 try:
148 151 if islocal(dest):
149 152 dir_cleanup = DirCleanup(dest)
150 153
151 154 abspath = origsource
152 155 copy = False
153 156 if src_repo.cancopy() and islocal(dest):
154 157 abspath = os.path.abspath(util.drop_scheme('file', origsource))
155 158 copy = not pull and not rev
156 159
157 160 if copy:
158 161 try:
159 162 # we use a lock here because if we race with commit, we
160 163 # can end up with extra data in the cloned revlogs that's
161 164 # not pointed to by changesets, thus causing verify to
162 165 # fail
163 166 src_lock = src_repo.lock()
164 167 except error.LockError:
165 168 copy = False
166 169
167 170 if copy:
168 171 if not os.path.exists(dest):
169 172 os.mkdir(dest)
170 173 try:
171 174 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
172 175 os.mkdir(dest_path)
173 176 except OSError, inst:
174 177 if inst.errno == errno.EEXIST:
175 178 dir_cleanup.close()
176 179 raise util.Abort(_("destination '%s' already exists")
177 180 % dest)
178 181 raise
179 182
180 183 for f in src_repo.store.copylist():
181 184 src = os.path.join(src_repo.path, f)
182 185 dst = os.path.join(dest_path, f)
183 186 dstbase = os.path.dirname(dst)
184 187 if dstbase and not os.path.exists(dstbase):
185 188 os.mkdir(dstbase)
186 189 if os.path.exists(src):
187 190 if dst.endswith('data'):
188 191 # lock to avoid premature writing to the target
189 192 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
190 193 util.copyfiles(src, dst)
191 194
192 195 # we need to re-init the repo after manually copying the data
193 196 # into it
194 197 dest_repo = repository(ui, dest)
195 198
196 199 else:
197 200 try:
198 201 dest_repo = repository(ui, dest, create=True)
199 202 except OSError, inst:
200 203 if inst.errno == errno.EEXIST:
201 204 dir_cleanup.close()
202 205 raise util.Abort(_("destination '%s' already exists")
203 206 % dest)
204 207 raise
205 208
206 209 revs = None
207 210 if rev:
208 211 if 'lookup' not in src_repo.capabilities:
209 212 raise util.Abort(_("src repository does not support revision "
210 213 "lookup and so doesn't support clone by "
211 214 "revision"))
212 215 revs = [src_repo.lookup(r) for r in rev]
213 216
214 217 if dest_repo.local():
215 218 dest_repo.clone(src_repo, heads=revs, stream=stream)
216 219 elif src_repo.local():
217 220 src_repo.push(dest_repo, revs=revs)
218 221 else:
219 222 raise util.Abort(_("clone from remote to remote not supported"))
220 223
221 224 if dir_cleanup:
222 225 dir_cleanup.close()
223 226
224 227 if dest_repo.local():
225 228 fp = dest_repo.opener("hgrc", "w", text=True)
226 229 fp.write("[paths]\n")
227 230 # percent needs to be escaped for ConfigParser
228 231 fp.write("default = %s\n" % abspath.replace('%', '%%'))
229 232 fp.close()
230 233
231 234 if update:
232 235 dest_repo.ui.status(_("updating working directory\n"))
233 236 if update is not True:
234 237 checkout = update
235 238 for test in (checkout, 'default', 'tip'):
236 239 try:
237 240 uprev = dest_repo.lookup(test)
238 241 break
239 242 except:
240 243 continue
241 244 _update(dest_repo, uprev)
242 245
243 246 return src_repo, dest_repo
244 247 finally:
245 248 del src_lock, dest_lock, dir_cleanup
246 249
247 250 def _showstats(repo, stats):
248 251 stats = ((stats[0], _("updated")),
249 252 (stats[1], _("merged")),
250 253 (stats[2], _("removed")),
251 254 (stats[3], _("unresolved")))
252 255 note = ", ".join([_("%d files %s") % s for s in stats])
253 256 repo.ui.status("%s\n" % note)
254 257
255 258 def update(repo, node):
256 259 """update the working directory to node, merging linear changes"""
257 260 stats = _merge.update(repo, node, False, False, None)
258 261 _showstats(repo, stats)
259 262 if stats[3]:
260 263 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
261 264 return stats[3] > 0
262 265
263 266 # naming conflict in clone()
264 267 _update = update
265 268
266 269 def clean(repo, node, show_stats=True):
267 270 """forcibly switch the working directory to node, clobbering changes"""
268 271 stats = _merge.update(repo, node, False, True, None)
269 272 if show_stats: _showstats(repo, stats)
270 273 return stats[3] > 0
271 274
272 275 def merge(repo, node, force=None, remind=True):
273 276 """branch merge with node, resolving changes"""
274 277 stats = _merge.update(repo, node, True, force, False)
275 278 _showstats(repo, stats)
276 279 if stats[3]:
277 280 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
278 281 "or 'hg up --clean' to abandon\n"))
279 282 elif remind:
280 283 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
281 284 return stats[3] > 0
282 285
283 286 def revert(repo, node, choose):
284 287 """revert changes to revision in node without updating dirstate"""
285 288 return _merge.update(repo, node, False, True, choose)[3] > 0
286 289
287 290 def verify(repo):
288 291 """verify the consistency of a repository"""
289 292 return _verify.verify(repo)
@@ -1,63 +1,70 b''
1 1 #!/bin/sh
2 2
3 3 echo
4 4 echo % prepare repo a
5 5 mkdir a
6 6 cd a
7 7 hg init
8 8 echo a > a
9 9 hg add a
10 10 hg commit -m test -d '0 0'
11 11 echo first line > b
12 12 hg add b
13 13 # create a non-inlined filelog
14 14 python -c 'for x in range(10000): print x' >> data1
15 15 for j in 0 1 2 3 4 5 6 7 8 9; do
16 16 cat data1 >> b
17 17 hg commit -m test -d '0 0'
18 18 done
19 19 echo % "list files in store/data (should show a 'b.d')"
20 20 for i in .hg/store/data/*; do
21 21 echo $i
22 22 done
23 23
24 24 echo
25 25 echo % default operation
26 26 hg clone . ../b
27 27 cd ../b
28 28 cat a
29 29 hg verify
30 30
31 31 echo
32 32 echo % no update
33 33 hg clone -U . ../c
34 34 cd ../c
35 35 cat a 2>/dev/null || echo "a not present"
36 36 hg verify
37 37
38 38 echo
39 39 echo % default destination
40 40 mkdir ../d
41 41 cd ../d
42 42 hg clone ../a
43 43 cd a
44 44 hg cat a
45 45
46 46 echo
47 47 echo % "check that we drop the file:// from the path before"
48 48 echo % "writing the .hgrc"
49 49 cd ../..
50 50 hg clone file://a e
51 51 grep 'file:' e/.hg/hgrc
52 52
53 53 echo
54 54 echo % check that path aliases are expanded
55 55 hg clone -q -U --config 'paths.foobar=a#0' foobar f
56 56 hg -R f showconfig paths.default | sed -e 's,.*/,,'
57 57
58 58 echo
59 59 echo % use --pull
60 60 hg clone --pull a g
61 61 hg -R g verify
62 62
63 echo
64 echo % clone to '.'
65 mkdir h
66 cd h
67 hg clone ../a .
68 cd ..
69
63 70 exit 0
@@ -1,46 +1,52 b''
1 1 #!/bin/sh
2 2
3 3 # No local source
4 4 hg clone a b
5 5 echo $?
6 6
7 7 # No remote source
8 8 hg clone http://127.0.0.1:3121/a b
9 9 echo $?
10 10 rm -rf b # work around bug with http clone
11 11
12 12 # Inaccessible source
13 13 mkdir a
14 14 chmod 000 a
15 15 hg clone a b
16 16 echo $?
17 17
18 18 # Inaccessible destination
19 19 mkdir b
20 20 cd b
21 21 hg init
22 22 hg clone . ../a
23 23 echo $?
24 24 cd ..
25 25 chmod 700 a
26 26 rm -r a b
27 27
28 28 # Source of wrong type
29 29 if "$TESTDIR/hghave" -q fifo; then
30 30 mkfifo a
31 31 hg clone a b
32 32 echo $?
33 33 rm a
34 34 else
35 35 echo "abort: repository a not found!"
36 36 echo 255
37 37 fi
38 38
39 39 # Default destination, same directory
40 40 mkdir q
41 41 cd q
42 42 hg init
43 43 cd ..
44 44 hg clone q
45 45
46 # destination directory not empty
47 mkdir a
48 echo stuff > a/a
49 hg clone q a
50 echo $?
51
46 52 true
@@ -1,12 +1,14 b''
1 1 abort: repository a not found!
2 2 255
3 3 abort: error: Connection refused
4 4 255
5 5 abort: repository a not found!
6 6 255
7 abort: destination '../a' already exists
7 abort: Permission denied: ../a
8 8 255
9 9 abort: repository a not found!
10 10 255
11 11 destination directory: q
12 abort: destination 'q' already exists
12 abort: destination 'q' is not empty
13 abort: destination 'a' is not empty
14 255
@@ -1,52 +1,56 b''
1 1
2 2 % prepare repo a
3 3 % list files in store/data (should show a 'b.d')
4 4 .hg/store/data/a.i
5 5 .hg/store/data/b.d
6 6 .hg/store/data/b.i
7 7
8 8 % default operation
9 9 updating working directory
10 10 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 11 a
12 12 checking changesets
13 13 checking manifests
14 14 crosschecking files in changesets and manifests
15 15 checking files
16 16 2 files, 11 changesets, 11 total revisions
17 17
18 18 % no update
19 19 a not present
20 20 checking changesets
21 21 checking manifests
22 22 crosschecking files in changesets and manifests
23 23 checking files
24 24 2 files, 11 changesets, 11 total revisions
25 25
26 26 % default destination
27 27 destination directory: a
28 28 updating working directory
29 29 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 a
31 31
32 32 % check that we drop the file:// from the path before
33 33 % writing the .hgrc
34 34 updating working directory
35 35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36
37 37 % check that path aliases are expanded
38 38 a#0
39 39
40 40 % use --pull
41 41 requesting all changes
42 42 adding changesets
43 43 adding manifests
44 44 adding file changes
45 45 added 11 changesets with 11 changes to 2 files
46 46 updating working directory
47 47 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 48 checking changesets
49 49 checking manifests
50 50 crosschecking files in changesets and manifests
51 51 checking files
52 52 2 files, 11 changesets, 11 total revisions
53
54 % clone to .
55 updating working directory
56 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
General Comments 0
You need to be logged in to leave comments. Login now