##// END OF EJS Templates
Escape '%' when writing to hgrc (issue1199)...
Benoit Boissinot -
r7044:e51c0f41 default
parent child Browse files
Show More
@@ -1,312 +1,313 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 i18n import _
9 from i18n import _
10 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
10 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
11 import errno, lock, os, shutil, util, extensions
11 import errno, lock, os, shutil, util, extensions
12 import merge as _merge
12 import merge as _merge
13 import verify as _verify
13 import verify as _verify
14
14
15 def _local(path):
15 def _local(path):
16 return (os.path.isfile(util.drop_scheme('file', path)) and
16 return (os.path.isfile(util.drop_scheme('file', path)) and
17 bundlerepo or localrepo)
17 bundlerepo or localrepo)
18
18
19 def parseurl(url, revs):
19 def parseurl(url, revs):
20 '''parse url#branch, returning url, branch + revs'''
20 '''parse url#branch, returning url, branch + revs'''
21
21
22 if '#' not in url:
22 if '#' not in url:
23 return url, (revs or None), None
23 return url, (revs or None), None
24
24
25 url, rev = url.split('#', 1)
25 url, rev = url.split('#', 1)
26 return url, revs + [rev], rev
26 return url, revs + [rev], rev
27
27
28 schemes = {
28 schemes = {
29 'bundle': bundlerepo,
29 'bundle': bundlerepo,
30 'file': _local,
30 'file': _local,
31 'http': httprepo,
31 'http': httprepo,
32 'https': httprepo,
32 'https': httprepo,
33 'ssh': sshrepo,
33 'ssh': sshrepo,
34 'static-http': statichttprepo,
34 'static-http': statichttprepo,
35 }
35 }
36
36
37 def _lookup(path):
37 def _lookup(path):
38 scheme = 'file'
38 scheme = 'file'
39 if path:
39 if path:
40 c = path.find(':')
40 c = path.find(':')
41 if c > 0:
41 if c > 0:
42 scheme = path[:c]
42 scheme = path[:c]
43 thing = schemes.get(scheme) or schemes['file']
43 thing = schemes.get(scheme) or schemes['file']
44 try:
44 try:
45 return thing(path)
45 return thing(path)
46 except TypeError:
46 except TypeError:
47 return thing
47 return thing
48
48
49 def islocal(repo):
49 def islocal(repo):
50 '''return true if repo or path is local'''
50 '''return true if repo or path is local'''
51 if isinstance(repo, str):
51 if isinstance(repo, str):
52 try:
52 try:
53 return _lookup(repo).islocal(repo)
53 return _lookup(repo).islocal(repo)
54 except AttributeError:
54 except AttributeError:
55 return False
55 return False
56 return repo.local()
56 return repo.local()
57
57
58 def repository(ui, path='', create=False):
58 def repository(ui, path='', create=False):
59 """return a repository object for the specified path"""
59 """return a repository object for the specified path"""
60 repo = _lookup(path).instance(ui, path, create)
60 repo = _lookup(path).instance(ui, path, create)
61 ui = getattr(repo, "ui", ui)
61 ui = getattr(repo, "ui", ui)
62 for name, module in extensions.extensions():
62 for name, module in extensions.extensions():
63 hook = getattr(module, 'reposetup', None)
63 hook = getattr(module, 'reposetup', None)
64 if hook:
64 if hook:
65 hook(ui, repo)
65 hook(ui, repo)
66 return repo
66 return repo
67
67
68 def defaultdest(source):
68 def defaultdest(source):
69 '''return default destination of clone if none is given'''
69 '''return default destination of clone if none is given'''
70 return os.path.basename(os.path.normpath(source))
70 return os.path.basename(os.path.normpath(source))
71
71
72 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
72 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
73 stream=False):
73 stream=False):
74 """Make a copy of an existing repository.
74 """Make a copy of an existing repository.
75
75
76 Create a copy of an existing repository in a new directory. The
76 Create a copy of an existing repository in a new directory. The
77 source and destination are URLs, as passed to the repository
77 source and destination are URLs, as passed to the repository
78 function. Returns a pair of repository objects, the source and
78 function. Returns a pair of repository objects, the source and
79 newly created destination.
79 newly created destination.
80
80
81 The location of the source is added to the new repository's
81 The location of the source is added to the new repository's
82 .hg/hgrc file, as the default to be used for future pulls and
82 .hg/hgrc file, as the default to be used for future pulls and
83 pushes.
83 pushes.
84
84
85 If an exception is raised, the partly cloned/updated destination
85 If an exception is raised, the partly cloned/updated destination
86 repository will be deleted.
86 repository will be deleted.
87
87
88 Arguments:
88 Arguments:
89
89
90 source: repository object or URL
90 source: repository object or URL
91
91
92 dest: URL of destination repository to create (defaults to base
92 dest: URL of destination repository to create (defaults to base
93 name of source repository)
93 name of source repository)
94
94
95 pull: always pull from source repository, even in local case
95 pull: always pull from source repository, even in local case
96
96
97 stream: stream raw data uncompressed from repository (fast over
97 stream: stream raw data uncompressed from repository (fast over
98 LAN, slow over WAN)
98 LAN, slow over WAN)
99
99
100 rev: revision to clone up to (implies pull=True)
100 rev: revision to clone up to (implies pull=True)
101
101
102 update: update working directory after clone completes, if
102 update: update working directory after clone completes, if
103 destination is local repository
103 destination is local repository
104 """
104 """
105
105
106 if isinstance(source, str):
106 if isinstance(source, str):
107 origsource = ui.expandpath(source)
107 origsource = ui.expandpath(source)
108 source, rev, checkout = parseurl(origsource, rev)
108 source, rev, checkout = parseurl(origsource, rev)
109 src_repo = repository(ui, source)
109 src_repo = repository(ui, source)
110 else:
110 else:
111 src_repo = source
111 src_repo = source
112 origsource = source = src_repo.url()
112 origsource = source = src_repo.url()
113 checkout = None
113 checkout = None
114
114
115 if dest is None:
115 if dest is None:
116 dest = defaultdest(source)
116 dest = defaultdest(source)
117 ui.status(_("destination directory: %s\n") % dest)
117 ui.status(_("destination directory: %s\n") % dest)
118
118
119 def localpath(path):
119 def localpath(path):
120 if path.startswith('file://localhost/'):
120 if path.startswith('file://localhost/'):
121 return path[16:]
121 return path[16:]
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.cancopy() and islocal(dest):
151 if src_repo.cancopy() 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 if not os.path.exists(src):
167 if not os.path.exists(src):
168 # Tolerate empty source repository and optional files
168 # Tolerate empty source repository and optional files
169 return
169 return
170 util.copyfiles(src, dst)
170 util.copyfiles(src, dst)
171
171
172 src_store = os.path.realpath(src_repo.spath)
172 src_store = os.path.realpath(src_repo.spath)
173 if not os.path.exists(dest):
173 if not os.path.exists(dest):
174 os.mkdir(dest)
174 os.mkdir(dest)
175 try:
175 try:
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 except OSError, inst:
178 except OSError, inst:
179 if inst.errno == errno.EEXIST:
179 if inst.errno == errno.EEXIST:
180 dir_cleanup.close()
180 dir_cleanup.close()
181 raise util.Abort(_("destination '%s' already exists")
181 raise util.Abort(_("destination '%s' already exists")
182 % dest)
182 % dest)
183 raise
183 raise
184 if src_repo.spath != src_repo.path:
184 if src_repo.spath != src_repo.path:
185 # XXX racy
185 # XXX racy
186 dummy_changelog = os.path.join(dest_path, "00changelog.i")
186 dummy_changelog = os.path.join(dest_path, "00changelog.i")
187 # copy the dummy changelog
187 # copy the dummy changelog
188 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
188 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
189 dest_store = os.path.join(dest_path, "store")
189 dest_store = os.path.join(dest_path, "store")
190 os.mkdir(dest_store)
190 os.mkdir(dest_store)
191 else:
191 else:
192 dest_store = dest_path
192 dest_store = dest_path
193 # copy the requires file
193 # copy the requires file
194 force_copy(src_repo.join("requires"),
194 force_copy(src_repo.join("requires"),
195 os.path.join(dest_path, "requires"))
195 os.path.join(dest_path, "requires"))
196 # we lock here to avoid premature writing to the target
196 # we lock here to avoid premature writing to the target
197 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
197 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
198
198
199 files = ("data",
199 files = ("data",
200 "00manifest.d", "00manifest.i",
200 "00manifest.d", "00manifest.i",
201 "00changelog.d", "00changelog.i")
201 "00changelog.d", "00changelog.i")
202 for f in files:
202 for f in files:
203 src = os.path.join(src_store, f)
203 src = os.path.join(src_store, f)
204 dst = os.path.join(dest_store, f)
204 dst = os.path.join(dest_store, f)
205 force_copy(src, dst)
205 force_copy(src, dst)
206
206
207 # we need to re-init the repo after manually copying the data
207 # we need to re-init the repo after manually copying the data
208 # into it
208 # into it
209 dest_repo = repository(ui, dest)
209 dest_repo = repository(ui, dest)
210
210
211 else:
211 else:
212 try:
212 try:
213 dest_repo = repository(ui, dest, create=True)
213 dest_repo = repository(ui, dest, create=True)
214 except OSError, inst:
214 except OSError, inst:
215 if inst.errno == errno.EEXIST:
215 if inst.errno == errno.EEXIST:
216 dir_cleanup.close()
216 dir_cleanup.close()
217 raise util.Abort(_("destination '%s' already exists")
217 raise util.Abort(_("destination '%s' already exists")
218 % dest)
218 % dest)
219 raise
219 raise
220
220
221 revs = None
221 revs = None
222 if rev:
222 if rev:
223 if 'lookup' not in src_repo.capabilities:
223 if 'lookup' not in src_repo.capabilities:
224 raise util.Abort(_("src repository does not support revision "
224 raise util.Abort(_("src repository does not support revision "
225 "lookup and so doesn't support clone by "
225 "lookup and so doesn't support clone by "
226 "revision"))
226 "revision"))
227 revs = [src_repo.lookup(r) for r in rev]
227 revs = [src_repo.lookup(r) for r in rev]
228
228
229 if dest_repo.local():
229 if dest_repo.local():
230 dest_repo.clone(src_repo, heads=revs, stream=stream)
230 dest_repo.clone(src_repo, heads=revs, stream=stream)
231 elif src_repo.local():
231 elif src_repo.local():
232 src_repo.push(dest_repo, revs=revs)
232 src_repo.push(dest_repo, revs=revs)
233 else:
233 else:
234 raise util.Abort(_("clone from remote to remote not supported"))
234 raise util.Abort(_("clone from remote to remote not supported"))
235
235
236 if dir_cleanup:
236 if dir_cleanup:
237 dir_cleanup.close()
237 dir_cleanup.close()
238
238
239 if dest_repo.local():
239 if dest_repo.local():
240 fp = dest_repo.opener("hgrc", "w", text=True)
240 fp = dest_repo.opener("hgrc", "w", text=True)
241 fp.write("[paths]\n")
241 fp.write("[paths]\n")
242 fp.write("default = %s\n" % abspath)
242 # percent needs to be escaped for ConfigParser
243 fp.write("default = %s\n" % abspath.replace('%', '%%'))
243 fp.close()
244 fp.close()
244
245
245 if update:
246 if update:
246 dest_repo.ui.status(_("updating working directory\n"))
247 dest_repo.ui.status(_("updating working directory\n"))
247 if not checkout:
248 if not checkout:
248 try:
249 try:
249 checkout = dest_repo.lookup("default")
250 checkout = dest_repo.lookup("default")
250 except:
251 except:
251 checkout = dest_repo.changelog.tip()
252 checkout = dest_repo.changelog.tip()
252 _update(dest_repo, checkout)
253 _update(dest_repo, checkout)
253
254
254 return src_repo, dest_repo
255 return src_repo, dest_repo
255 finally:
256 finally:
256 del src_lock, dest_lock, dir_cleanup
257 del src_lock, dest_lock, dir_cleanup
257
258
258 def _showstats(repo, stats):
259 def _showstats(repo, stats):
259 stats = ((stats[0], _("updated")),
260 stats = ((stats[0], _("updated")),
260 (stats[1], _("merged")),
261 (stats[1], _("merged")),
261 (stats[2], _("removed")),
262 (stats[2], _("removed")),
262 (stats[3], _("unresolved")))
263 (stats[3], _("unresolved")))
263 note = ", ".join([_("%d files %s") % s for s in stats])
264 note = ", ".join([_("%d files %s") % s for s in stats])
264 repo.ui.status("%s\n" % note)
265 repo.ui.status("%s\n" % note)
265
266
266 def _update(repo, node): return update(repo, node)
267 def _update(repo, node): return update(repo, node)
267
268
268 def update(repo, node):
269 def update(repo, node):
269 """update the working directory to node, merging linear changes"""
270 """update the working directory to node, merging linear changes"""
270 pl = repo.parents()
271 pl = repo.parents()
271 stats = _merge.update(repo, node, False, False, None)
272 stats = _merge.update(repo, node, False, False, None)
272 _showstats(repo, stats)
273 _showstats(repo, stats)
273 if stats[3]:
274 if stats[3]:
274 repo.ui.status(_("There are unresolved merges with"
275 repo.ui.status(_("There are unresolved merges with"
275 " locally modified files.\n"))
276 " locally modified files.\n"))
276 if stats[1]:
277 if stats[1]:
277 repo.ui.status(_("You can finish the partial merge using:\n"))
278 repo.ui.status(_("You can finish the partial merge using:\n"))
278 else:
279 else:
279 repo.ui.status(_("You can redo the full merge using:\n"))
280 repo.ui.status(_("You can redo the full merge using:\n"))
280 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
281 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
281 repo.ui.status(_(" hg update %s\n hg update %s\n")
282 repo.ui.status(_(" hg update %s\n hg update %s\n")
282 % (pl[0].rev(), repo.changectx(node).rev()))
283 % (pl[0].rev(), repo.changectx(node).rev()))
283 return stats[3] > 0
284 return stats[3] > 0
284
285
285 def clean(repo, node, show_stats=True):
286 def clean(repo, node, show_stats=True):
286 """forcibly switch the working directory to node, clobbering changes"""
287 """forcibly switch the working directory to node, clobbering changes"""
287 stats = _merge.update(repo, node, False, True, None)
288 stats = _merge.update(repo, node, False, True, None)
288 if show_stats: _showstats(repo, stats)
289 if show_stats: _showstats(repo, stats)
289 return stats[3] > 0
290 return stats[3] > 0
290
291
291 def merge(repo, node, force=None, remind=True):
292 def merge(repo, node, force=None, remind=True):
292 """branch merge with node, resolving changes"""
293 """branch merge with node, resolving changes"""
293 stats = _merge.update(repo, node, True, force, False)
294 stats = _merge.update(repo, node, True, force, False)
294 _showstats(repo, stats)
295 _showstats(repo, stats)
295 if stats[3]:
296 if stats[3]:
296 pl = repo.parents()
297 pl = repo.parents()
297 repo.ui.status(_("There are unresolved merges,"
298 repo.ui.status(_("There are unresolved merges,"
298 " you can redo the full merge using:\n"
299 " you can redo the full merge using:\n"
299 " hg update -C %s\n"
300 " hg update -C %s\n"
300 " hg merge %s\n")
301 " hg merge %s\n")
301 % (pl[0].rev(), pl[1].rev()))
302 % (pl[0].rev(), pl[1].rev()))
302 elif remind:
303 elif remind:
303 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
304 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
304 return stats[3] > 0
305 return stats[3] > 0
305
306
306 def revert(repo, node, choose):
307 def revert(repo, node, choose):
307 """revert changes to revision in node without updating dirstate"""
308 """revert changes to revision in node without updating dirstate"""
308 return _merge.update(repo, node, False, True, choose)[3] > 0
309 return _merge.update(repo, node, False, True, choose)[3] > 0
309
310
310 def verify(repo):
311 def verify(repo):
311 """verify the consistency of a repository"""
312 """verify the consistency of a repository"""
312 return _verify.verify(repo)
313 return _verify.verify(repo)
@@ -1,7 +1,18 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 mkdir t
3 mkdir t
4 cd t
4 cd t
5 hg init
5 hg init
6 echo "invalid" > .hg/hgrc
6 echo "invalid" > .hg/hgrc
7 hg status 2>&1 |sed -e "s:/.*\(/t/.*\):...\1:"
7 hg status 2>&1 |sed -e "s:/.*\(/t/.*\):...\1:"
8
9 #issue 1199, escaping
10
11 cd ..
12 hg init "foo%bar"
13 hg clone "foo%bar" foobar
14 p=`pwd`
15 cd foobar
16 cat .hg/hgrc |sed -e "s:$p:...:"
17 hg paths |sed -e "s:$p:...:"
18 hg showconfig |sed -e "s:$p:...:"
@@ -1,4 +1,16 b''
1 abort: Failed to parse .../t/.hg/hgrc
1 abort: Failed to parse .../t/.hg/hgrc
2 File contains no section headers.
2 File contains no section headers.
3 file: .../t/.hg/hgrc, line: 1
3 file: .../t/.hg/hgrc, line: 1
4 'invalid\n'
4 'invalid\n'
5 updating working directory
6 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
7 [paths]
8 default = .../foo%%bar
9 default = .../foo%bar
10 bundle.mainreporoot=.../foobar
11 defaults.backout=-d "0 0"
12 defaults.commit=-d "0 0"
13 defaults.debugrawcommit=-d "0 0"
14 defaults.tag=-d "0 0"
15 paths.default=.../foo%bar
16 ui.slash=True
General Comments 0
You need to be logged in to leave comments. Login now