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