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