##// END OF EJS Templates
share: allow dest to default to the basename of source
Matt Mackall -
r8807:8bf6eb68 default
parent child Browse files
Show More
@@ -1,31 +1,31
1 1 # Mercurial extension to provide the 'hg share' command
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 import os
9 9 from mercurial.i18n import _
10 10 from mercurial import hg, commands
11 11
12 def share(ui, source, dest, noupdate=False):
12 def share(ui, source, dest=None, noupdate=False):
13 13 """create a new shared repository (experimental)
14 14
15 15 Initialize a new repository and working directory that shares its
16 16 history with another repository.
17 17
18 18 NOTE: actions that change history such as rollback or moving the
19 19 source may confuse sharers.
20 20 """
21 21
22 22 return hg.share(ui, source, dest, not noupdate)
23 23
24 24 cmdtable = {
25 25 "share":
26 26 (share,
27 27 [('U', 'noupdate', None, _('do not create a working copy'))],
28 _('[-U] SOURCE DEST')),
28 _('[-U] SOURCE [DEST]')),
29 29 }
30 30
31 31 commands.norepo += " share"
@@ -1,357 +1,360
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 of the
7 7 # GNU General Public License version 2, incorporated herein by reference.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
12 12 import lock, util, extensions, error
13 13 import merge as _merge
14 14 import verify as _verify
15 15 import errno, os, shutil
16 16
17 17 def _local(path):
18 18 return (os.path.isfile(util.drop_scheme('file', path)) and
19 19 bundlerepo or localrepo)
20 20
21 21 def parseurl(url, revs=[]):
22 22 '''parse url#branch, returning url, branch + revs'''
23 23
24 24 if '#' not in url:
25 25 return url, (revs or None), revs and revs[-1] or None
26 26
27 27 url, branch = url.split('#', 1)
28 28 checkout = revs and revs[-1] or branch
29 29 return url, (revs or []) + [branch], checkout
30 30
31 31 schemes = {
32 32 'bundle': bundlerepo,
33 33 'file': _local,
34 34 'http': httprepo,
35 35 'https': httprepo,
36 36 'ssh': sshrepo,
37 37 'static-http': statichttprepo,
38 38 }
39 39
40 40 def _lookup(path):
41 41 scheme = 'file'
42 42 if path:
43 43 c = path.find(':')
44 44 if c > 0:
45 45 scheme = path[:c]
46 46 thing = schemes.get(scheme) or schemes['file']
47 47 try:
48 48 return thing(path)
49 49 except TypeError:
50 50 return thing
51 51
52 52 def islocal(repo):
53 53 '''return true if repo or path is local'''
54 54 if isinstance(repo, str):
55 55 try:
56 56 return _lookup(repo).islocal(repo)
57 57 except AttributeError:
58 58 return False
59 59 return repo.local()
60 60
61 61 def repository(ui, path='', create=False):
62 62 """return a repository object for the specified path"""
63 63 repo = _lookup(path).instance(ui, path, create)
64 64 ui = getattr(repo, "ui", ui)
65 65 for name, module in extensions.extensions():
66 66 hook = getattr(module, 'reposetup', None)
67 67 if hook:
68 68 hook(ui, repo)
69 69 return repo
70 70
71 71 def defaultdest(source):
72 72 '''return default destination of clone if none is given'''
73 73 return os.path.basename(os.path.normpath(source))
74 74
75 75 def localpath(path):
76 76 if path.startswith('file://localhost/'):
77 77 return path[16:]
78 78 if path.startswith('file://'):
79 79 return path[7:]
80 80 if path.startswith('file:'):
81 81 return path[5:]
82 82 return path
83 83
84 def share(ui, source, dest, update=True):
84 def share(ui, source, dest=None, update=True):
85 85 '''create a shared repository'''
86 86
87 87 if not islocal(source):
88 88 raise util.Abort(_('can only share local repositories'))
89 89
90 if not dest:
91 dest = os.path.basename(source)
92
90 93 if isinstance(source, str):
91 94 origsource = ui.expandpath(source)
92 95 source, rev, checkout = parseurl(origsource, '')
93 96 srcrepo = repository(ui, source)
94 97 else:
95 98 srcrepo = source
96 99 origsource = source = srcrepo.url()
97 100 checkout = None
98 101
99 102 sharedpath = srcrepo.sharedpath # if our source is already sharing
100 103
101 104 root = os.path.realpath(dest)
102 105 roothg = os.path.join(root, '.hg')
103 106
104 107 if os.path.exists(roothg):
105 108 raise util.Abort(_('destination already exists'))
106 109
107 110 if not os.path.isdir(root):
108 111 os.mkdir(root)
109 112 os.mkdir(roothg)
110 113
111 114 requirements = ''
112 115 try:
113 116 requirements = srcrepo.opener('requires').read()
114 117 except IOError, inst:
115 118 if inst.errno != errno.ENOENT:
116 119 raise
117 120
118 121 requirements += 'shared\n'
119 122 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
120 123 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
121 124
122 125 default = srcrepo.ui.config('paths', 'default')
123 126 if default:
124 127 f = file(os.path.join(roothg, 'hgrc'), 'w')
125 128 f.write('[paths]\ndefault = %s\n' % default)
126 129 f.close()
127 130
128 131 r = repository(ui, root)
129 132
130 133 if update:
131 134 r.ui.status(_("updating working directory\n"))
132 135 if update is not True:
133 136 checkout = update
134 137 for test in (checkout, 'default', 'tip'):
135 138 try:
136 139 uprev = r.lookup(test)
137 140 break
138 141 except:
139 142 continue
140 143 _update(r, uprev)
141 144
142 145 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
143 146 stream=False):
144 147 """Make a copy of an existing repository.
145 148
146 149 Create a copy of an existing repository in a new directory. The
147 150 source and destination are URLs, as passed to the repository
148 151 function. Returns a pair of repository objects, the source and
149 152 newly created destination.
150 153
151 154 The location of the source is added to the new repository's
152 155 .hg/hgrc file, as the default to be used for future pulls and
153 156 pushes.
154 157
155 158 If an exception is raised, the partly cloned/updated destination
156 159 repository will be deleted.
157 160
158 161 Arguments:
159 162
160 163 source: repository object or URL
161 164
162 165 dest: URL of destination repository to create (defaults to base
163 166 name of source repository)
164 167
165 168 pull: always pull from source repository, even in local case
166 169
167 170 stream: stream raw data uncompressed from repository (fast over
168 171 LAN, slow over WAN)
169 172
170 173 rev: revision to clone up to (implies pull=True)
171 174
172 175 update: update working directory after clone completes, if
173 176 destination is local repository (True means update to default rev,
174 177 anything else is treated as a revision)
175 178 """
176 179
177 180 if isinstance(source, str):
178 181 origsource = ui.expandpath(source)
179 182 source, rev, checkout = parseurl(origsource, rev)
180 183 src_repo = repository(ui, source)
181 184 else:
182 185 src_repo = source
183 186 origsource = source = src_repo.url()
184 187 checkout = rev and rev[-1] or None
185 188
186 189 if dest is None:
187 190 dest = defaultdest(source)
188 191 ui.status(_("destination directory: %s\n") % dest)
189 192
190 193 dest = localpath(dest)
191 194 source = localpath(source)
192 195
193 196 if os.path.exists(dest):
194 197 if not os.path.isdir(dest):
195 198 raise util.Abort(_("destination '%s' already exists") % dest)
196 199 elif os.listdir(dest):
197 200 raise util.Abort(_("destination '%s' is not empty") % dest)
198 201
199 202 class DirCleanup(object):
200 203 def __init__(self, dir_):
201 204 self.rmtree = shutil.rmtree
202 205 self.dir_ = dir_
203 206 def close(self):
204 207 self.dir_ = None
205 208 def cleanup(self):
206 209 if self.dir_:
207 210 self.rmtree(self.dir_, True)
208 211
209 212 src_lock = dest_lock = dir_cleanup = None
210 213 try:
211 214 if islocal(dest):
212 215 dir_cleanup = DirCleanup(dest)
213 216
214 217 abspath = origsource
215 218 copy = False
216 219 if src_repo.cancopy() and islocal(dest):
217 220 abspath = os.path.abspath(util.drop_scheme('file', origsource))
218 221 copy = not pull and not rev
219 222
220 223 if copy:
221 224 try:
222 225 # we use a lock here because if we race with commit, we
223 226 # can end up with extra data in the cloned revlogs that's
224 227 # not pointed to by changesets, thus causing verify to
225 228 # fail
226 229 src_lock = src_repo.lock(wait=False)
227 230 except error.LockError:
228 231 copy = False
229 232
230 233 if copy:
231 234 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
232 235 if not os.path.exists(dest):
233 236 os.mkdir(dest)
234 237 else:
235 238 # only clean up directories we create ourselves
236 239 dir_cleanup.dir_ = hgdir
237 240 try:
238 241 dest_path = hgdir
239 242 os.mkdir(dest_path)
240 243 except OSError, inst:
241 244 if inst.errno == errno.EEXIST:
242 245 dir_cleanup.close()
243 246 raise util.Abort(_("destination '%s' already exists")
244 247 % dest)
245 248 raise
246 249
247 250 for f in src_repo.store.copylist():
248 251 src = os.path.join(src_repo.path, f)
249 252 dst = os.path.join(dest_path, f)
250 253 dstbase = os.path.dirname(dst)
251 254 if dstbase and not os.path.exists(dstbase):
252 255 os.mkdir(dstbase)
253 256 if os.path.exists(src):
254 257 if dst.endswith('data'):
255 258 # lock to avoid premature writing to the target
256 259 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
257 260 util.copyfiles(src, dst)
258 261
259 262 # we need to re-init the repo after manually copying the data
260 263 # into it
261 264 dest_repo = repository(ui, dest)
262 265
263 266 else:
264 267 try:
265 268 dest_repo = repository(ui, dest, create=True)
266 269 except OSError, inst:
267 270 if inst.errno == errno.EEXIST:
268 271 dir_cleanup.close()
269 272 raise util.Abort(_("destination '%s' already exists")
270 273 % dest)
271 274 raise
272 275
273 276 revs = None
274 277 if rev:
275 278 if 'lookup' not in src_repo.capabilities:
276 279 raise util.Abort(_("src repository does not support revision "
277 280 "lookup and so doesn't support clone by "
278 281 "revision"))
279 282 revs = [src_repo.lookup(r) for r in rev]
280 283 checkout = revs[0]
281 284 if dest_repo.local():
282 285 dest_repo.clone(src_repo, heads=revs, stream=stream)
283 286 elif src_repo.local():
284 287 src_repo.push(dest_repo, revs=revs)
285 288 else:
286 289 raise util.Abort(_("clone from remote to remote not supported"))
287 290
288 291 if dir_cleanup:
289 292 dir_cleanup.close()
290 293
291 294 if dest_repo.local():
292 295 fp = dest_repo.opener("hgrc", "w", text=True)
293 296 fp.write("[paths]\n")
294 297 fp.write("default = %s\n" % abspath)
295 298 fp.close()
296 299
297 300 if update:
298 301 dest_repo.ui.status(_("updating working directory\n"))
299 302 if update is not True:
300 303 checkout = update
301 304 for test in (checkout, 'default', 'tip'):
302 305 try:
303 306 uprev = dest_repo.lookup(test)
304 307 break
305 308 except:
306 309 continue
307 310 _update(dest_repo, uprev)
308 311
309 312 return src_repo, dest_repo
310 313 finally:
311 314 release(src_lock, dest_lock)
312 315 if dir_cleanup is not None:
313 316 dir_cleanup.cleanup()
314 317
315 318 def _showstats(repo, stats):
316 319 stats = ((stats[0], _("updated")),
317 320 (stats[1], _("merged")),
318 321 (stats[2], _("removed")),
319 322 (stats[3], _("unresolved")))
320 323 note = ", ".join([_("%d files %s") % s for s in stats])
321 324 repo.ui.status("%s\n" % note)
322 325
323 326 def update(repo, node):
324 327 """update the working directory to node, merging linear changes"""
325 328 stats = _merge.update(repo, node, False, False, None)
326 329 _showstats(repo, stats)
327 330 if stats[3]:
328 331 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
329 332 return stats[3] > 0
330 333
331 334 # naming conflict in clone()
332 335 _update = update
333 336
334 337 def clean(repo, node, show_stats=True):
335 338 """forcibly switch the working directory to node, clobbering changes"""
336 339 stats = _merge.update(repo, node, False, True, None)
337 340 if show_stats: _showstats(repo, stats)
338 341 return stats[3] > 0
339 342
340 343 def merge(repo, node, force=None, remind=True):
341 344 """branch merge with node, resolving changes"""
342 345 stats = _merge.update(repo, node, True, force, False)
343 346 _showstats(repo, stats)
344 347 if stats[3]:
345 348 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
346 349 "or 'hg up --clean' to abandon\n"))
347 350 elif remind:
348 351 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
349 352 return stats[3] > 0
350 353
351 354 def revert(repo, node, choose):
352 355 """revert changes to revision in node without updating dirstate"""
353 356 return _merge.update(repo, node, False, True, choose)[3] > 0
354 357
355 358 def verify(repo):
356 359 """verify the consistency of a repository"""
357 360 return _verify.verify(repo)
General Comments 0
You need to be logged in to leave comments. Login now