##// END OF EJS Templates
verify: check the subrepository references in .hgsubstate...
Matt Harbison -
r25591:f1d46075 default
parent child Browse files
Show More
@@ -1,698 +1,719
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 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import nullid
11 from node import nullid
12
12
13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
15 import cmdutil, discovery, repoview, exchange
15 import cmdutil, discovery, repoview, exchange
16 import ui as uimod
16 import ui as uimod
17 import merge as mergemod
17 import merge as mergemod
18 import verify as verifymod
18 import verify as verifymod
19 import errno, os, shutil
19 import errno, os, shutil
20
20
21 def _local(path):
21 def _local(path):
22 path = util.expandpath(util.urllocalpath(path))
22 path = util.expandpath(util.urllocalpath(path))
23 return (os.path.isfile(path) and bundlerepo or localrepo)
23 return (os.path.isfile(path) and bundlerepo or localrepo)
24
24
25 def addbranchrevs(lrepo, other, branches, revs):
25 def addbranchrevs(lrepo, other, branches, revs):
26 peer = other.peer() # a courtesy to callers using a localrepo for other
26 peer = other.peer() # a courtesy to callers using a localrepo for other
27 hashbranch, branches = branches
27 hashbranch, branches = branches
28 if not hashbranch and not branches:
28 if not hashbranch and not branches:
29 x = revs or None
29 x = revs or None
30 if util.safehasattr(revs, 'first'):
30 if util.safehasattr(revs, 'first'):
31 y = revs.first()
31 y = revs.first()
32 elif revs:
32 elif revs:
33 y = revs[0]
33 y = revs[0]
34 else:
34 else:
35 y = None
35 y = None
36 return x, y
36 return x, y
37 if revs:
37 if revs:
38 revs = list(revs)
38 revs = list(revs)
39 else:
39 else:
40 revs = []
40 revs = []
41
41
42 if not peer.capable('branchmap'):
42 if not peer.capable('branchmap'):
43 if branches:
43 if branches:
44 raise util.Abort(_("remote branch lookup not supported"))
44 raise util.Abort(_("remote branch lookup not supported"))
45 revs.append(hashbranch)
45 revs.append(hashbranch)
46 return revs, revs[0]
46 return revs, revs[0]
47 branchmap = peer.branchmap()
47 branchmap = peer.branchmap()
48
48
49 def primary(branch):
49 def primary(branch):
50 if branch == '.':
50 if branch == '.':
51 if not lrepo:
51 if not lrepo:
52 raise util.Abort(_("dirstate branch not accessible"))
52 raise util.Abort(_("dirstate branch not accessible"))
53 branch = lrepo.dirstate.branch()
53 branch = lrepo.dirstate.branch()
54 if branch in branchmap:
54 if branch in branchmap:
55 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
55 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
56 return True
56 return True
57 else:
57 else:
58 return False
58 return False
59
59
60 for branch in branches:
60 for branch in branches:
61 if not primary(branch):
61 if not primary(branch):
62 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
62 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
63 if hashbranch:
63 if hashbranch:
64 if not primary(hashbranch):
64 if not primary(hashbranch):
65 revs.append(hashbranch)
65 revs.append(hashbranch)
66 return revs, revs[0]
66 return revs, revs[0]
67
67
68 def parseurl(path, branches=None):
68 def parseurl(path, branches=None):
69 '''parse url#branch, returning (url, (branch, branches))'''
69 '''parse url#branch, returning (url, (branch, branches))'''
70
70
71 u = util.url(path)
71 u = util.url(path)
72 branch = None
72 branch = None
73 if u.fragment:
73 if u.fragment:
74 branch = u.fragment
74 branch = u.fragment
75 u.fragment = None
75 u.fragment = None
76 return str(u), (branch, branches or [])
76 return str(u), (branch, branches or [])
77
77
78 schemes = {
78 schemes = {
79 'bundle': bundlerepo,
79 'bundle': bundlerepo,
80 'union': unionrepo,
80 'union': unionrepo,
81 'file': _local,
81 'file': _local,
82 'http': httppeer,
82 'http': httppeer,
83 'https': httppeer,
83 'https': httppeer,
84 'ssh': sshpeer,
84 'ssh': sshpeer,
85 'static-http': statichttprepo,
85 'static-http': statichttprepo,
86 }
86 }
87
87
88 def _peerlookup(path):
88 def _peerlookup(path):
89 u = util.url(path)
89 u = util.url(path)
90 scheme = u.scheme or 'file'
90 scheme = u.scheme or 'file'
91 thing = schemes.get(scheme) or schemes['file']
91 thing = schemes.get(scheme) or schemes['file']
92 try:
92 try:
93 return thing(path)
93 return thing(path)
94 except TypeError:
94 except TypeError:
95 # we can't test callable(thing) because 'thing' can be an unloaded
95 # we can't test callable(thing) because 'thing' can be an unloaded
96 # module that implements __call__
96 # module that implements __call__
97 if not util.safehasattr(thing, 'instance'):
97 if not util.safehasattr(thing, 'instance'):
98 raise
98 raise
99 return thing
99 return thing
100
100
101 def islocal(repo):
101 def islocal(repo):
102 '''return true if repo (or path pointing to repo) is local'''
102 '''return true if repo (or path pointing to repo) is local'''
103 if isinstance(repo, str):
103 if isinstance(repo, str):
104 try:
104 try:
105 return _peerlookup(repo).islocal(repo)
105 return _peerlookup(repo).islocal(repo)
106 except AttributeError:
106 except AttributeError:
107 return False
107 return False
108 return repo.local()
108 return repo.local()
109
109
110 def openpath(ui, path):
110 def openpath(ui, path):
111 '''open path with open if local, url.open if remote'''
111 '''open path with open if local, url.open if remote'''
112 pathurl = util.url(path, parsequery=False, parsefragment=False)
112 pathurl = util.url(path, parsequery=False, parsefragment=False)
113 if pathurl.islocal():
113 if pathurl.islocal():
114 return util.posixfile(pathurl.localpath(), 'rb')
114 return util.posixfile(pathurl.localpath(), 'rb')
115 else:
115 else:
116 return url.open(ui, path)
116 return url.open(ui, path)
117
117
118 # a list of (ui, repo) functions called for wire peer initialization
118 # a list of (ui, repo) functions called for wire peer initialization
119 wirepeersetupfuncs = []
119 wirepeersetupfuncs = []
120
120
121 def _peerorrepo(ui, path, create=False):
121 def _peerorrepo(ui, path, create=False):
122 """return a repository object for the specified path"""
122 """return a repository object for the specified path"""
123 obj = _peerlookup(path).instance(ui, path, create)
123 obj = _peerlookup(path).instance(ui, path, create)
124 ui = getattr(obj, "ui", ui)
124 ui = getattr(obj, "ui", ui)
125 for name, module in extensions.extensions(ui):
125 for name, module in extensions.extensions(ui):
126 hook = getattr(module, 'reposetup', None)
126 hook = getattr(module, 'reposetup', None)
127 if hook:
127 if hook:
128 hook(ui, obj)
128 hook(ui, obj)
129 if not obj.local():
129 if not obj.local():
130 for f in wirepeersetupfuncs:
130 for f in wirepeersetupfuncs:
131 f(ui, obj)
131 f(ui, obj)
132 return obj
132 return obj
133
133
134 def repository(ui, path='', create=False):
134 def repository(ui, path='', create=False):
135 """return a repository object for the specified path"""
135 """return a repository object for the specified path"""
136 peer = _peerorrepo(ui, path, create)
136 peer = _peerorrepo(ui, path, create)
137 repo = peer.local()
137 repo = peer.local()
138 if not repo:
138 if not repo:
139 raise util.Abort(_("repository '%s' is not local") %
139 raise util.Abort(_("repository '%s' is not local") %
140 (path or peer.url()))
140 (path or peer.url()))
141 return repo.filtered('visible')
141 return repo.filtered('visible')
142
142
143 def peer(uiorrepo, opts, path, create=False):
143 def peer(uiorrepo, opts, path, create=False):
144 '''return a repository peer for the specified path'''
144 '''return a repository peer for the specified path'''
145 rui = remoteui(uiorrepo, opts)
145 rui = remoteui(uiorrepo, opts)
146 return _peerorrepo(rui, path, create).peer()
146 return _peerorrepo(rui, path, create).peer()
147
147
148 def defaultdest(source):
148 def defaultdest(source):
149 '''return default destination of clone if none is given
149 '''return default destination of clone if none is given
150
150
151 >>> defaultdest('foo')
151 >>> defaultdest('foo')
152 'foo'
152 'foo'
153 >>> defaultdest('/foo/bar')
153 >>> defaultdest('/foo/bar')
154 'bar'
154 'bar'
155 >>> defaultdest('/')
155 >>> defaultdest('/')
156 ''
156 ''
157 >>> defaultdest('')
157 >>> defaultdest('')
158 ''
158 ''
159 >>> defaultdest('http://example.org/')
159 >>> defaultdest('http://example.org/')
160 ''
160 ''
161 >>> defaultdest('http://example.org/foo/')
161 >>> defaultdest('http://example.org/foo/')
162 'foo'
162 'foo'
163 '''
163 '''
164 path = util.url(source).path
164 path = util.url(source).path
165 if not path:
165 if not path:
166 return ''
166 return ''
167 return os.path.basename(os.path.normpath(path))
167 return os.path.basename(os.path.normpath(path))
168
168
169 def share(ui, source, dest=None, update=True, bookmarks=True):
169 def share(ui, source, dest=None, update=True, bookmarks=True):
170 '''create a shared repository'''
170 '''create a shared repository'''
171
171
172 if not islocal(source):
172 if not islocal(source):
173 raise util.Abort(_('can only share local repositories'))
173 raise util.Abort(_('can only share local repositories'))
174
174
175 if not dest:
175 if not dest:
176 dest = defaultdest(source)
176 dest = defaultdest(source)
177 else:
177 else:
178 dest = ui.expandpath(dest)
178 dest = ui.expandpath(dest)
179
179
180 if isinstance(source, str):
180 if isinstance(source, str):
181 origsource = ui.expandpath(source)
181 origsource = ui.expandpath(source)
182 source, branches = parseurl(origsource)
182 source, branches = parseurl(origsource)
183 srcrepo = repository(ui, source)
183 srcrepo = repository(ui, source)
184 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
184 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
185 else:
185 else:
186 srcrepo = source.local()
186 srcrepo = source.local()
187 origsource = source = srcrepo.url()
187 origsource = source = srcrepo.url()
188 checkout = None
188 checkout = None
189
189
190 sharedpath = srcrepo.sharedpath # if our source is already sharing
190 sharedpath = srcrepo.sharedpath # if our source is already sharing
191
191
192 destwvfs = scmutil.vfs(dest, realpath=True)
192 destwvfs = scmutil.vfs(dest, realpath=True)
193 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
193 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
194
194
195 if destvfs.lexists():
195 if destvfs.lexists():
196 raise util.Abort(_('destination already exists'))
196 raise util.Abort(_('destination already exists'))
197
197
198 if not destwvfs.isdir():
198 if not destwvfs.isdir():
199 destwvfs.mkdir()
199 destwvfs.mkdir()
200 destvfs.makedir()
200 destvfs.makedir()
201
201
202 requirements = ''
202 requirements = ''
203 try:
203 try:
204 requirements = srcrepo.vfs.read('requires')
204 requirements = srcrepo.vfs.read('requires')
205 except IOError, inst:
205 except IOError, inst:
206 if inst.errno != errno.ENOENT:
206 if inst.errno != errno.ENOENT:
207 raise
207 raise
208
208
209 requirements += 'shared\n'
209 requirements += 'shared\n'
210 destvfs.write('requires', requirements)
210 destvfs.write('requires', requirements)
211 destvfs.write('sharedpath', sharedpath)
211 destvfs.write('sharedpath', sharedpath)
212
212
213 r = repository(ui, destwvfs.base)
213 r = repository(ui, destwvfs.base)
214
214
215 default = srcrepo.ui.config('paths', 'default')
215 default = srcrepo.ui.config('paths', 'default')
216 if default:
216 if default:
217 fp = r.vfs("hgrc", "w", text=True)
217 fp = r.vfs("hgrc", "w", text=True)
218 fp.write("[paths]\n")
218 fp.write("[paths]\n")
219 fp.write("default = %s\n" % default)
219 fp.write("default = %s\n" % default)
220 fp.close()
220 fp.close()
221
221
222 if update:
222 if update:
223 r.ui.status(_("updating working directory\n"))
223 r.ui.status(_("updating working directory\n"))
224 if update is not True:
224 if update is not True:
225 checkout = update
225 checkout = update
226 for test in (checkout, 'default', 'tip'):
226 for test in (checkout, 'default', 'tip'):
227 if test is None:
227 if test is None:
228 continue
228 continue
229 try:
229 try:
230 uprev = r.lookup(test)
230 uprev = r.lookup(test)
231 break
231 break
232 except error.RepoLookupError:
232 except error.RepoLookupError:
233 continue
233 continue
234 _update(r, uprev)
234 _update(r, uprev)
235
235
236 if bookmarks:
236 if bookmarks:
237 fp = r.vfs('shared', 'w')
237 fp = r.vfs('shared', 'w')
238 fp.write('bookmarks\n')
238 fp.write('bookmarks\n')
239 fp.close()
239 fp.close()
240
240
241 def copystore(ui, srcrepo, destpath):
241 def copystore(ui, srcrepo, destpath):
242 '''copy files from store of srcrepo in destpath
242 '''copy files from store of srcrepo in destpath
243
243
244 returns destlock
244 returns destlock
245 '''
245 '''
246 destlock = None
246 destlock = None
247 try:
247 try:
248 hardlink = None
248 hardlink = None
249 num = 0
249 num = 0
250 closetopic = [None]
250 closetopic = [None]
251 def prog(topic, pos):
251 def prog(topic, pos):
252 if pos is None:
252 if pos is None:
253 closetopic[0] = topic
253 closetopic[0] = topic
254 else:
254 else:
255 ui.progress(topic, pos + num)
255 ui.progress(topic, pos + num)
256 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
256 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
257 srcvfs = scmutil.vfs(srcrepo.sharedpath)
257 srcvfs = scmutil.vfs(srcrepo.sharedpath)
258 dstvfs = scmutil.vfs(destpath)
258 dstvfs = scmutil.vfs(destpath)
259 for f in srcrepo.store.copylist():
259 for f in srcrepo.store.copylist():
260 if srcpublishing and f.endswith('phaseroots'):
260 if srcpublishing and f.endswith('phaseroots'):
261 continue
261 continue
262 dstbase = os.path.dirname(f)
262 dstbase = os.path.dirname(f)
263 if dstbase and not dstvfs.exists(dstbase):
263 if dstbase and not dstvfs.exists(dstbase):
264 dstvfs.mkdir(dstbase)
264 dstvfs.mkdir(dstbase)
265 if srcvfs.exists(f):
265 if srcvfs.exists(f):
266 if f.endswith('data'):
266 if f.endswith('data'):
267 # 'dstbase' may be empty (e.g. revlog format 0)
267 # 'dstbase' may be empty (e.g. revlog format 0)
268 lockfile = os.path.join(dstbase, "lock")
268 lockfile = os.path.join(dstbase, "lock")
269 # lock to avoid premature writing to the target
269 # lock to avoid premature writing to the target
270 destlock = lock.lock(dstvfs, lockfile)
270 destlock = lock.lock(dstvfs, lockfile)
271 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
271 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
272 hardlink, progress=prog)
272 hardlink, progress=prog)
273 num += n
273 num += n
274 if hardlink:
274 if hardlink:
275 ui.debug("linked %d files\n" % num)
275 ui.debug("linked %d files\n" % num)
276 if closetopic[0]:
276 if closetopic[0]:
277 ui.progress(closetopic[0], None)
277 ui.progress(closetopic[0], None)
278 else:
278 else:
279 ui.debug("copied %d files\n" % num)
279 ui.debug("copied %d files\n" % num)
280 if closetopic[0]:
280 if closetopic[0]:
281 ui.progress(closetopic[0], None)
281 ui.progress(closetopic[0], None)
282 return destlock
282 return destlock
283 except: # re-raises
283 except: # re-raises
284 release(destlock)
284 release(destlock)
285 raise
285 raise
286
286
287 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
287 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
288 update=True, stream=False, branch=None):
288 update=True, stream=False, branch=None):
289 """Make a copy of an existing repository.
289 """Make a copy of an existing repository.
290
290
291 Create a copy of an existing repository in a new directory. The
291 Create a copy of an existing repository in a new directory. The
292 source and destination are URLs, as passed to the repository
292 source and destination are URLs, as passed to the repository
293 function. Returns a pair of repository peers, the source and
293 function. Returns a pair of repository peers, the source and
294 newly created destination.
294 newly created destination.
295
295
296 The location of the source is added to the new repository's
296 The location of the source is added to the new repository's
297 .hg/hgrc file, as the default to be used for future pulls and
297 .hg/hgrc file, as the default to be used for future pulls and
298 pushes.
298 pushes.
299
299
300 If an exception is raised, the partly cloned/updated destination
300 If an exception is raised, the partly cloned/updated destination
301 repository will be deleted.
301 repository will be deleted.
302
302
303 Arguments:
303 Arguments:
304
304
305 source: repository object or URL
305 source: repository object or URL
306
306
307 dest: URL of destination repository to create (defaults to base
307 dest: URL of destination repository to create (defaults to base
308 name of source repository)
308 name of source repository)
309
309
310 pull: always pull from source repository, even in local case or if the
310 pull: always pull from source repository, even in local case or if the
311 server prefers streaming
311 server prefers streaming
312
312
313 stream: stream raw data uncompressed from repository (fast over
313 stream: stream raw data uncompressed from repository (fast over
314 LAN, slow over WAN)
314 LAN, slow over WAN)
315
315
316 rev: revision to clone up to (implies pull=True)
316 rev: revision to clone up to (implies pull=True)
317
317
318 update: update working directory after clone completes, if
318 update: update working directory after clone completes, if
319 destination is local repository (True means update to default rev,
319 destination is local repository (True means update to default rev,
320 anything else is treated as a revision)
320 anything else is treated as a revision)
321
321
322 branch: branches to clone
322 branch: branches to clone
323 """
323 """
324
324
325 if isinstance(source, str):
325 if isinstance(source, str):
326 origsource = ui.expandpath(source)
326 origsource = ui.expandpath(source)
327 source, branch = parseurl(origsource, branch)
327 source, branch = parseurl(origsource, branch)
328 srcpeer = peer(ui, peeropts, source)
328 srcpeer = peer(ui, peeropts, source)
329 else:
329 else:
330 srcpeer = source.peer() # in case we were called with a localrepo
330 srcpeer = source.peer() # in case we were called with a localrepo
331 branch = (None, branch or [])
331 branch = (None, branch or [])
332 origsource = source = srcpeer.url()
332 origsource = source = srcpeer.url()
333 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
333 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
334
334
335 if dest is None:
335 if dest is None:
336 dest = defaultdest(source)
336 dest = defaultdest(source)
337 if dest:
337 if dest:
338 ui.status(_("destination directory: %s\n") % dest)
338 ui.status(_("destination directory: %s\n") % dest)
339 else:
339 else:
340 dest = ui.expandpath(dest)
340 dest = ui.expandpath(dest)
341
341
342 dest = util.urllocalpath(dest)
342 dest = util.urllocalpath(dest)
343 source = util.urllocalpath(source)
343 source = util.urllocalpath(source)
344
344
345 if not dest:
345 if not dest:
346 raise util.Abort(_("empty destination path is not valid"))
346 raise util.Abort(_("empty destination path is not valid"))
347
347
348 destvfs = scmutil.vfs(dest, expandpath=True)
348 destvfs = scmutil.vfs(dest, expandpath=True)
349 if destvfs.lexists():
349 if destvfs.lexists():
350 if not destvfs.isdir():
350 if not destvfs.isdir():
351 raise util.Abort(_("destination '%s' already exists") % dest)
351 raise util.Abort(_("destination '%s' already exists") % dest)
352 elif destvfs.listdir():
352 elif destvfs.listdir():
353 raise util.Abort(_("destination '%s' is not empty") % dest)
353 raise util.Abort(_("destination '%s' is not empty") % dest)
354
354
355 srclock = destlock = cleandir = None
355 srclock = destlock = cleandir = None
356 srcrepo = srcpeer.local()
356 srcrepo = srcpeer.local()
357 try:
357 try:
358 abspath = origsource
358 abspath = origsource
359 if islocal(origsource):
359 if islocal(origsource):
360 abspath = os.path.abspath(util.urllocalpath(origsource))
360 abspath = os.path.abspath(util.urllocalpath(origsource))
361
361
362 if islocal(dest):
362 if islocal(dest):
363 cleandir = dest
363 cleandir = dest
364
364
365 copy = False
365 copy = False
366 if (srcrepo and srcrepo.cancopy() and islocal(dest)
366 if (srcrepo and srcrepo.cancopy() and islocal(dest)
367 and not phases.hassecret(srcrepo)):
367 and not phases.hassecret(srcrepo)):
368 copy = not pull and not rev
368 copy = not pull and not rev
369
369
370 if copy:
370 if copy:
371 try:
371 try:
372 # we use a lock here because if we race with commit, we
372 # we use a lock here because if we race with commit, we
373 # can end up with extra data in the cloned revlogs that's
373 # can end up with extra data in the cloned revlogs that's
374 # not pointed to by changesets, thus causing verify to
374 # not pointed to by changesets, thus causing verify to
375 # fail
375 # fail
376 srclock = srcrepo.lock(wait=False)
376 srclock = srcrepo.lock(wait=False)
377 except error.LockError:
377 except error.LockError:
378 copy = False
378 copy = False
379
379
380 if copy:
380 if copy:
381 srcrepo.hook('preoutgoing', throw=True, source='clone')
381 srcrepo.hook('preoutgoing', throw=True, source='clone')
382 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
382 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
383 if not os.path.exists(dest):
383 if not os.path.exists(dest):
384 os.mkdir(dest)
384 os.mkdir(dest)
385 else:
385 else:
386 # only clean up directories we create ourselves
386 # only clean up directories we create ourselves
387 cleandir = hgdir
387 cleandir = hgdir
388 try:
388 try:
389 destpath = hgdir
389 destpath = hgdir
390 util.makedir(destpath, notindexed=True)
390 util.makedir(destpath, notindexed=True)
391 except OSError, inst:
391 except OSError, inst:
392 if inst.errno == errno.EEXIST:
392 if inst.errno == errno.EEXIST:
393 cleandir = None
393 cleandir = None
394 raise util.Abort(_("destination '%s' already exists")
394 raise util.Abort(_("destination '%s' already exists")
395 % dest)
395 % dest)
396 raise
396 raise
397
397
398 destlock = copystore(ui, srcrepo, destpath)
398 destlock = copystore(ui, srcrepo, destpath)
399 # copy bookmarks over
399 # copy bookmarks over
400 srcbookmarks = srcrepo.join('bookmarks')
400 srcbookmarks = srcrepo.join('bookmarks')
401 dstbookmarks = os.path.join(destpath, 'bookmarks')
401 dstbookmarks = os.path.join(destpath, 'bookmarks')
402 if os.path.exists(srcbookmarks):
402 if os.path.exists(srcbookmarks):
403 util.copyfile(srcbookmarks, dstbookmarks)
403 util.copyfile(srcbookmarks, dstbookmarks)
404
404
405 # Recomputing branch cache might be slow on big repos,
405 # Recomputing branch cache might be slow on big repos,
406 # so just copy it
406 # so just copy it
407 def copybranchcache(fname):
407 def copybranchcache(fname):
408 srcbranchcache = srcrepo.join('cache/%s' % fname)
408 srcbranchcache = srcrepo.join('cache/%s' % fname)
409 dstbranchcache = os.path.join(dstcachedir, fname)
409 dstbranchcache = os.path.join(dstcachedir, fname)
410 if os.path.exists(srcbranchcache):
410 if os.path.exists(srcbranchcache):
411 if not os.path.exists(dstcachedir):
411 if not os.path.exists(dstcachedir):
412 os.mkdir(dstcachedir)
412 os.mkdir(dstcachedir)
413 util.copyfile(srcbranchcache, dstbranchcache)
413 util.copyfile(srcbranchcache, dstbranchcache)
414
414
415 dstcachedir = os.path.join(destpath, 'cache')
415 dstcachedir = os.path.join(destpath, 'cache')
416 # In local clones we're copying all nodes, not just served
416 # In local clones we're copying all nodes, not just served
417 # ones. Therefore copy all branch caches over.
417 # ones. Therefore copy all branch caches over.
418 copybranchcache('branch2')
418 copybranchcache('branch2')
419 for cachename in repoview.filtertable:
419 for cachename in repoview.filtertable:
420 copybranchcache('branch2-%s' % cachename)
420 copybranchcache('branch2-%s' % cachename)
421
421
422 # we need to re-init the repo after manually copying the data
422 # we need to re-init the repo after manually copying the data
423 # into it
423 # into it
424 destpeer = peer(srcrepo, peeropts, dest)
424 destpeer = peer(srcrepo, peeropts, dest)
425 srcrepo.hook('outgoing', source='clone',
425 srcrepo.hook('outgoing', source='clone',
426 node=node.hex(node.nullid))
426 node=node.hex(node.nullid))
427 else:
427 else:
428 try:
428 try:
429 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
429 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
430 # only pass ui when no srcrepo
430 # only pass ui when no srcrepo
431 except OSError, inst:
431 except OSError, inst:
432 if inst.errno == errno.EEXIST:
432 if inst.errno == errno.EEXIST:
433 cleandir = None
433 cleandir = None
434 raise util.Abort(_("destination '%s' already exists")
434 raise util.Abort(_("destination '%s' already exists")
435 % dest)
435 % dest)
436 raise
436 raise
437
437
438 revs = None
438 revs = None
439 if rev:
439 if rev:
440 if not srcpeer.capable('lookup'):
440 if not srcpeer.capable('lookup'):
441 raise util.Abort(_("src repository does not support "
441 raise util.Abort(_("src repository does not support "
442 "revision lookup and so doesn't "
442 "revision lookup and so doesn't "
443 "support clone by revision"))
443 "support clone by revision"))
444 revs = [srcpeer.lookup(r) for r in rev]
444 revs = [srcpeer.lookup(r) for r in rev]
445 checkout = revs[0]
445 checkout = revs[0]
446 if destpeer.local():
446 if destpeer.local():
447 if not stream:
447 if not stream:
448 if pull:
448 if pull:
449 stream = False
449 stream = False
450 else:
450 else:
451 stream = None
451 stream = None
452 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
452 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
453 elif srcrepo:
453 elif srcrepo:
454 exchange.push(srcrepo, destpeer, revs=revs,
454 exchange.push(srcrepo, destpeer, revs=revs,
455 bookmarks=srcrepo._bookmarks.keys())
455 bookmarks=srcrepo._bookmarks.keys())
456 else:
456 else:
457 raise util.Abort(_("clone from remote to remote not supported"))
457 raise util.Abort(_("clone from remote to remote not supported"))
458
458
459 cleandir = None
459 cleandir = None
460
460
461 destrepo = destpeer.local()
461 destrepo = destpeer.local()
462 if destrepo:
462 if destrepo:
463 template = uimod.samplehgrcs['cloned']
463 template = uimod.samplehgrcs['cloned']
464 fp = destrepo.vfs("hgrc", "w", text=True)
464 fp = destrepo.vfs("hgrc", "w", text=True)
465 u = util.url(abspath)
465 u = util.url(abspath)
466 u.passwd = None
466 u.passwd = None
467 defaulturl = str(u)
467 defaulturl = str(u)
468 fp.write(template % defaulturl)
468 fp.write(template % defaulturl)
469 fp.close()
469 fp.close()
470
470
471 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
471 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
472
472
473 if update:
473 if update:
474 if update is not True:
474 if update is not True:
475 checkout = srcpeer.lookup(update)
475 checkout = srcpeer.lookup(update)
476 uprev = None
476 uprev = None
477 status = None
477 status = None
478 if checkout is not None:
478 if checkout is not None:
479 try:
479 try:
480 uprev = destrepo.lookup(checkout)
480 uprev = destrepo.lookup(checkout)
481 except error.RepoLookupError:
481 except error.RepoLookupError:
482 pass
482 pass
483 if uprev is None:
483 if uprev is None:
484 try:
484 try:
485 uprev = destrepo._bookmarks['@']
485 uprev = destrepo._bookmarks['@']
486 update = '@'
486 update = '@'
487 bn = destrepo[uprev].branch()
487 bn = destrepo[uprev].branch()
488 if bn == 'default':
488 if bn == 'default':
489 status = _("updating to bookmark @\n")
489 status = _("updating to bookmark @\n")
490 else:
490 else:
491 status = (_("updating to bookmark @ on branch %s\n")
491 status = (_("updating to bookmark @ on branch %s\n")
492 % bn)
492 % bn)
493 except KeyError:
493 except KeyError:
494 try:
494 try:
495 uprev = destrepo.branchtip('default')
495 uprev = destrepo.branchtip('default')
496 except error.RepoLookupError:
496 except error.RepoLookupError:
497 uprev = destrepo.lookup('tip')
497 uprev = destrepo.lookup('tip')
498 if not status:
498 if not status:
499 bn = destrepo[uprev].branch()
499 bn = destrepo[uprev].branch()
500 status = _("updating to branch %s\n") % bn
500 status = _("updating to branch %s\n") % bn
501 destrepo.ui.status(status)
501 destrepo.ui.status(status)
502 _update(destrepo, uprev)
502 _update(destrepo, uprev)
503 if update in destrepo._bookmarks:
503 if update in destrepo._bookmarks:
504 bookmarks.activate(destrepo, update)
504 bookmarks.activate(destrepo, update)
505 finally:
505 finally:
506 release(srclock, destlock)
506 release(srclock, destlock)
507 if cleandir is not None:
507 if cleandir is not None:
508 shutil.rmtree(cleandir, True)
508 shutil.rmtree(cleandir, True)
509 if srcpeer is not None:
509 if srcpeer is not None:
510 srcpeer.close()
510 srcpeer.close()
511 return srcpeer, destpeer
511 return srcpeer, destpeer
512
512
513 def _showstats(repo, stats):
513 def _showstats(repo, stats):
514 repo.ui.status(_("%d files updated, %d files merged, "
514 repo.ui.status(_("%d files updated, %d files merged, "
515 "%d files removed, %d files unresolved\n") % stats)
515 "%d files removed, %d files unresolved\n") % stats)
516
516
517 def updaterepo(repo, node, overwrite):
517 def updaterepo(repo, node, overwrite):
518 """Update the working directory to node.
518 """Update the working directory to node.
519
519
520 When overwrite is set, changes are clobbered, merged else
520 When overwrite is set, changes are clobbered, merged else
521
521
522 returns stats (see pydoc mercurial.merge.applyupdates)"""
522 returns stats (see pydoc mercurial.merge.applyupdates)"""
523 return mergemod.update(repo, node, False, overwrite, None,
523 return mergemod.update(repo, node, False, overwrite, None,
524 labels=['working copy', 'destination'])
524 labels=['working copy', 'destination'])
525
525
526 def update(repo, node):
526 def update(repo, node):
527 """update the working directory to node, merging linear changes"""
527 """update the working directory to node, merging linear changes"""
528 stats = updaterepo(repo, node, False)
528 stats = updaterepo(repo, node, False)
529 _showstats(repo, stats)
529 _showstats(repo, stats)
530 if stats[3]:
530 if stats[3]:
531 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
531 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
532 return stats[3] > 0
532 return stats[3] > 0
533
533
534 # naming conflict in clone()
534 # naming conflict in clone()
535 _update = update
535 _update = update
536
536
537 def clean(repo, node, show_stats=True):
537 def clean(repo, node, show_stats=True):
538 """forcibly switch the working directory to node, clobbering changes"""
538 """forcibly switch the working directory to node, clobbering changes"""
539 stats = updaterepo(repo, node, True)
539 stats = updaterepo(repo, node, True)
540 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
540 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
541 if show_stats:
541 if show_stats:
542 _showstats(repo, stats)
542 _showstats(repo, stats)
543 return stats[3] > 0
543 return stats[3] > 0
544
544
545 def merge(repo, node, force=None, remind=True):
545 def merge(repo, node, force=None, remind=True):
546 """Branch merge with node, resolving changes. Return true if any
546 """Branch merge with node, resolving changes. Return true if any
547 unresolved conflicts."""
547 unresolved conflicts."""
548 stats = mergemod.update(repo, node, True, force, False)
548 stats = mergemod.update(repo, node, True, force, False)
549 _showstats(repo, stats)
549 _showstats(repo, stats)
550 if stats[3]:
550 if stats[3]:
551 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
551 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
552 "or 'hg update -C .' to abandon\n"))
552 "or 'hg update -C .' to abandon\n"))
553 elif remind:
553 elif remind:
554 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
554 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
555 return stats[3] > 0
555 return stats[3] > 0
556
556
557 def _incoming(displaychlist, subreporecurse, ui, repo, source,
557 def _incoming(displaychlist, subreporecurse, ui, repo, source,
558 opts, buffered=False):
558 opts, buffered=False):
559 """
559 """
560 Helper for incoming / gincoming.
560 Helper for incoming / gincoming.
561 displaychlist gets called with
561 displaychlist gets called with
562 (remoterepo, incomingchangesetlist, displayer) parameters,
562 (remoterepo, incomingchangesetlist, displayer) parameters,
563 and is supposed to contain only code that can't be unified.
563 and is supposed to contain only code that can't be unified.
564 """
564 """
565 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
565 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
566 other = peer(repo, opts, source)
566 other = peer(repo, opts, source)
567 ui.status(_('comparing with %s\n') % util.hidepassword(source))
567 ui.status(_('comparing with %s\n') % util.hidepassword(source))
568 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
568 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
569
569
570 if revs:
570 if revs:
571 revs = [other.lookup(rev) for rev in revs]
571 revs = [other.lookup(rev) for rev in revs]
572 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
572 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
573 revs, opts["bundle"], opts["force"])
573 revs, opts["bundle"], opts["force"])
574 try:
574 try:
575 if not chlist:
575 if not chlist:
576 ui.status(_("no changes found\n"))
576 ui.status(_("no changes found\n"))
577 return subreporecurse()
577 return subreporecurse()
578
578
579 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
579 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
580 displaychlist(other, chlist, displayer)
580 displaychlist(other, chlist, displayer)
581 displayer.close()
581 displayer.close()
582 finally:
582 finally:
583 cleanupfn()
583 cleanupfn()
584 subreporecurse()
584 subreporecurse()
585 return 0 # exit code is zero since we found incoming changes
585 return 0 # exit code is zero since we found incoming changes
586
586
587 def incoming(ui, repo, source, opts):
587 def incoming(ui, repo, source, opts):
588 def subreporecurse():
588 def subreporecurse():
589 ret = 1
589 ret = 1
590 if opts.get('subrepos'):
590 if opts.get('subrepos'):
591 ctx = repo[None]
591 ctx = repo[None]
592 for subpath in sorted(ctx.substate):
592 for subpath in sorted(ctx.substate):
593 sub = ctx.sub(subpath)
593 sub = ctx.sub(subpath)
594 ret = min(ret, sub.incoming(ui, source, opts))
594 ret = min(ret, sub.incoming(ui, source, opts))
595 return ret
595 return ret
596
596
597 def display(other, chlist, displayer):
597 def display(other, chlist, displayer):
598 limit = cmdutil.loglimit(opts)
598 limit = cmdutil.loglimit(opts)
599 if opts.get('newest_first'):
599 if opts.get('newest_first'):
600 chlist.reverse()
600 chlist.reverse()
601 count = 0
601 count = 0
602 for n in chlist:
602 for n in chlist:
603 if limit is not None and count >= limit:
603 if limit is not None and count >= limit:
604 break
604 break
605 parents = [p for p in other.changelog.parents(n) if p != nullid]
605 parents = [p for p in other.changelog.parents(n) if p != nullid]
606 if opts.get('no_merges') and len(parents) == 2:
606 if opts.get('no_merges') and len(parents) == 2:
607 continue
607 continue
608 count += 1
608 count += 1
609 displayer.show(other[n])
609 displayer.show(other[n])
610 return _incoming(display, subreporecurse, ui, repo, source, opts)
610 return _incoming(display, subreporecurse, ui, repo, source, opts)
611
611
612 def _outgoing(ui, repo, dest, opts):
612 def _outgoing(ui, repo, dest, opts):
613 dest = ui.expandpath(dest or 'default-push', dest or 'default')
613 dest = ui.expandpath(dest or 'default-push', dest or 'default')
614 dest, branches = parseurl(dest, opts.get('branch'))
614 dest, branches = parseurl(dest, opts.get('branch'))
615 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
615 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
616 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
616 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
617 if revs:
617 if revs:
618 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
618 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
619
619
620 other = peer(repo, opts, dest)
620 other = peer(repo, opts, dest)
621 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
621 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
622 force=opts.get('force'))
622 force=opts.get('force'))
623 o = outgoing.missing
623 o = outgoing.missing
624 if not o:
624 if not o:
625 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
625 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
626 return o, other
626 return o, other
627
627
628 def outgoing(ui, repo, dest, opts):
628 def outgoing(ui, repo, dest, opts):
629 def recurse():
629 def recurse():
630 ret = 1
630 ret = 1
631 if opts.get('subrepos'):
631 if opts.get('subrepos'):
632 ctx = repo[None]
632 ctx = repo[None]
633 for subpath in sorted(ctx.substate):
633 for subpath in sorted(ctx.substate):
634 sub = ctx.sub(subpath)
634 sub = ctx.sub(subpath)
635 ret = min(ret, sub.outgoing(ui, dest, opts))
635 ret = min(ret, sub.outgoing(ui, dest, opts))
636 return ret
636 return ret
637
637
638 limit = cmdutil.loglimit(opts)
638 limit = cmdutil.loglimit(opts)
639 o, other = _outgoing(ui, repo, dest, opts)
639 o, other = _outgoing(ui, repo, dest, opts)
640 if not o:
640 if not o:
641 cmdutil.outgoinghooks(ui, repo, other, opts, o)
641 cmdutil.outgoinghooks(ui, repo, other, opts, o)
642 return recurse()
642 return recurse()
643
643
644 if opts.get('newest_first'):
644 if opts.get('newest_first'):
645 o.reverse()
645 o.reverse()
646 displayer = cmdutil.show_changeset(ui, repo, opts)
646 displayer = cmdutil.show_changeset(ui, repo, opts)
647 count = 0
647 count = 0
648 for n in o:
648 for n in o:
649 if limit is not None and count >= limit:
649 if limit is not None and count >= limit:
650 break
650 break
651 parents = [p for p in repo.changelog.parents(n) if p != nullid]
651 parents = [p for p in repo.changelog.parents(n) if p != nullid]
652 if opts.get('no_merges') and len(parents) == 2:
652 if opts.get('no_merges') and len(parents) == 2:
653 continue
653 continue
654 count += 1
654 count += 1
655 displayer.show(repo[n])
655 displayer.show(repo[n])
656 displayer.close()
656 displayer.close()
657 cmdutil.outgoinghooks(ui, repo, other, opts, o)
657 cmdutil.outgoinghooks(ui, repo, other, opts, o)
658 recurse()
658 recurse()
659 return 0 # exit code is zero since we found outgoing changes
659 return 0 # exit code is zero since we found outgoing changes
660
660
661 def revert(repo, node, choose):
661 def revert(repo, node, choose):
662 """revert changes to revision in node without updating dirstate"""
662 """revert changes to revision in node without updating dirstate"""
663 return mergemod.update(repo, node, False, True, choose)[3] > 0
663 return mergemod.update(repo, node, False, True, choose)[3] > 0
664
664
665 def verify(repo):
665 def verify(repo):
666 """verify the consistency of a repository"""
666 """verify the consistency of a repository"""
667 return verifymod.verify(repo)
667 ret = verifymod.verify(repo)
668
669 # Broken subrepo references in hidden csets don't seem worth worrying about,
670 # since they can't be pushed/pulled, and --hidden can be used if they are a
671 # concern.
672
673 # pathto() is needed for -R case
674 revs = repo.revs("filelog(%s)",
675 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
676
677 if revs:
678 repo.ui.status(_('checking subrepo links\n'))
679 for rev in revs:
680 ctx = repo[rev]
681 try:
682 for subpath in ctx.substate:
683 ret = ctx.sub(subpath).verify() or ret
684 except Exception:
685 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
686 node.short(ctx.node()))
687
688 return ret
668
689
669 def remoteui(src, opts):
690 def remoteui(src, opts):
670 'build a remote ui from ui or repo and opts'
691 'build a remote ui from ui or repo and opts'
671 if util.safehasattr(src, 'baseui'): # looks like a repository
692 if util.safehasattr(src, 'baseui'): # looks like a repository
672 dst = src.baseui.copy() # drop repo-specific config
693 dst = src.baseui.copy() # drop repo-specific config
673 src = src.ui # copy target options from repo
694 src = src.ui # copy target options from repo
674 else: # assume it's a global ui object
695 else: # assume it's a global ui object
675 dst = src.copy() # keep all global options
696 dst = src.copy() # keep all global options
676
697
677 # copy ssh-specific options
698 # copy ssh-specific options
678 for o in 'ssh', 'remotecmd':
699 for o in 'ssh', 'remotecmd':
679 v = opts.get(o) or src.config('ui', o)
700 v = opts.get(o) or src.config('ui', o)
680 if v:
701 if v:
681 dst.setconfig("ui", o, v, 'copied')
702 dst.setconfig("ui", o, v, 'copied')
682
703
683 # copy bundle-specific options
704 # copy bundle-specific options
684 r = src.config('bundle', 'mainreporoot')
705 r = src.config('bundle', 'mainreporoot')
685 if r:
706 if r:
686 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
707 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
687
708
688 # copy selected local settings to the remote ui
709 # copy selected local settings to the remote ui
689 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
710 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
690 for key, val in src.configitems(sect):
711 for key, val in src.configitems(sect):
691 dst.setconfig(sect, key, val, 'copied')
712 dst.setconfig(sect, key, val, 'copied')
692 v = src.config('web', 'cacerts')
713 v = src.config('web', 'cacerts')
693 if v == '!':
714 if v == '!':
694 dst.setconfig('web', 'cacerts', v, 'copied')
715 dst.setconfig('web', 'cacerts', v, 'copied')
695 elif v:
716 elif v:
696 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
717 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
697
718
698 return dst
719 return dst
@@ -1,1884 +1,1908
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import copy
8 import copy
9 import errno, os, re, posixpath, sys
9 import errno, os, re, posixpath, sys
10 import xml.dom.minidom
10 import xml.dom.minidom
11 import stat, subprocess, tarfile
11 import stat, subprocess, tarfile
12 from i18n import _
12 from i18n import _
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 import phases
14 import phases
15 import pathutil
15 import pathutil
16 import exchange
16 import exchange
17 hg = None
17 hg = None
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 nullstate = ('', '', 'empty')
20 nullstate = ('', '', 'empty')
21
21
22 def _expandedabspath(path):
22 def _expandedabspath(path):
23 '''
23 '''
24 get a path or url and if it is a path expand it and return an absolute path
24 get a path or url and if it is a path expand it and return an absolute path
25 '''
25 '''
26 expandedpath = util.urllocalpath(util.expandpath(path))
26 expandedpath = util.urllocalpath(util.expandpath(path))
27 u = util.url(expandedpath)
27 u = util.url(expandedpath)
28 if not u.scheme:
28 if not u.scheme:
29 path = util.normpath(os.path.abspath(u.path))
29 path = util.normpath(os.path.abspath(u.path))
30 return path
30 return path
31
31
32 def _getstorehashcachename(remotepath):
32 def _getstorehashcachename(remotepath):
33 '''get a unique filename for the store hash cache of a remote repository'''
33 '''get a unique filename for the store hash cache of a remote repository'''
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35
35
36 class SubrepoAbort(error.Abort):
36 class SubrepoAbort(error.Abort):
37 """Exception class used to avoid handling a subrepo error more than once"""
37 """Exception class used to avoid handling a subrepo error more than once"""
38 def __init__(self, *args, **kw):
38 def __init__(self, *args, **kw):
39 error.Abort.__init__(self, *args, **kw)
39 error.Abort.__init__(self, *args, **kw)
40 self.subrepo = kw.get('subrepo')
40 self.subrepo = kw.get('subrepo')
41 self.cause = kw.get('cause')
41 self.cause = kw.get('cause')
42
42
43 def annotatesubrepoerror(func):
43 def annotatesubrepoerror(func):
44 def decoratedmethod(self, *args, **kargs):
44 def decoratedmethod(self, *args, **kargs):
45 try:
45 try:
46 res = func(self, *args, **kargs)
46 res = func(self, *args, **kargs)
47 except SubrepoAbort, ex:
47 except SubrepoAbort, ex:
48 # This exception has already been handled
48 # This exception has already been handled
49 raise ex
49 raise ex
50 except error.Abort, ex:
50 except error.Abort, ex:
51 subrepo = subrelpath(self)
51 subrepo = subrelpath(self)
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 # avoid handling this exception by raising a SubrepoAbort exception
53 # avoid handling this exception by raising a SubrepoAbort exception
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 cause=sys.exc_info())
55 cause=sys.exc_info())
56 return res
56 return res
57 return decoratedmethod
57 return decoratedmethod
58
58
59 def state(ctx, ui):
59 def state(ctx, ui):
60 """return a state dict, mapping subrepo paths configured in .hgsub
60 """return a state dict, mapping subrepo paths configured in .hgsub
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 (key in types dict))
62 (key in types dict))
63 """
63 """
64 p = config.config()
64 p = config.config()
65 def read(f, sections=None, remap=None):
65 def read(f, sections=None, remap=None):
66 if f in ctx:
66 if f in ctx:
67 try:
67 try:
68 data = ctx[f].data()
68 data = ctx[f].data()
69 except IOError, err:
69 except IOError, err:
70 if err.errno != errno.ENOENT:
70 if err.errno != errno.ENOENT:
71 raise
71 raise
72 # handle missing subrepo spec files as removed
72 # handle missing subrepo spec files as removed
73 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
73 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
74 util.pathto(ctx.repo().root, ctx.repo().getcwd(), f))
74 util.pathto(ctx.repo().root, ctx.repo().getcwd(), f))
75 return
75 return
76 p.parse(f, data, sections, remap, read)
76 p.parse(f, data, sections, remap, read)
77 else:
77 else:
78 repo = ctx.repo()
78 repo = ctx.repo()
79 raise util.Abort(_("subrepo spec file \'%s\' not found") %
79 raise util.Abort(_("subrepo spec file \'%s\' not found") %
80 util.pathto(repo.root, repo.getcwd(), f))
80 util.pathto(repo.root, repo.getcwd(), f))
81
81
82 if '.hgsub' in ctx:
82 if '.hgsub' in ctx:
83 read('.hgsub')
83 read('.hgsub')
84
84
85 for path, src in ui.configitems('subpaths'):
85 for path, src in ui.configitems('subpaths'):
86 p.set('subpaths', path, src, ui.configsource('subpaths', path))
86 p.set('subpaths', path, src, ui.configsource('subpaths', path))
87
87
88 rev = {}
88 rev = {}
89 if '.hgsubstate' in ctx:
89 if '.hgsubstate' in ctx:
90 try:
90 try:
91 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
91 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
92 l = l.lstrip()
92 l = l.lstrip()
93 if not l:
93 if not l:
94 continue
94 continue
95 try:
95 try:
96 revision, path = l.split(" ", 1)
96 revision, path = l.split(" ", 1)
97 except ValueError:
97 except ValueError:
98 repo = ctx.repo()
98 repo = ctx.repo()
99 raise util.Abort(_("invalid subrepository revision "
99 raise util.Abort(_("invalid subrepository revision "
100 "specifier in \'%s\' line %d")
100 "specifier in \'%s\' line %d")
101 % (util.pathto(repo.root, repo.getcwd(),
101 % (util.pathto(repo.root, repo.getcwd(),
102 '.hgsubstate'), (i + 1)))
102 '.hgsubstate'), (i + 1)))
103 rev[path] = revision
103 rev[path] = revision
104 except IOError, err:
104 except IOError, err:
105 if err.errno != errno.ENOENT:
105 if err.errno != errno.ENOENT:
106 raise
106 raise
107
107
108 def remap(src):
108 def remap(src):
109 for pattern, repl in p.items('subpaths'):
109 for pattern, repl in p.items('subpaths'):
110 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
110 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
111 # does a string decode.
111 # does a string decode.
112 repl = repl.encode('string-escape')
112 repl = repl.encode('string-escape')
113 # However, we still want to allow back references to go
113 # However, we still want to allow back references to go
114 # through unharmed, so we turn r'\\1' into r'\1'. Again,
114 # through unharmed, so we turn r'\\1' into r'\1'. Again,
115 # extra escapes are needed because re.sub string decodes.
115 # extra escapes are needed because re.sub string decodes.
116 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
116 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
117 try:
117 try:
118 src = re.sub(pattern, repl, src, 1)
118 src = re.sub(pattern, repl, src, 1)
119 except re.error, e:
119 except re.error, e:
120 raise util.Abort(_("bad subrepository pattern in %s: %s")
120 raise util.Abort(_("bad subrepository pattern in %s: %s")
121 % (p.source('subpaths', pattern), e))
121 % (p.source('subpaths', pattern), e))
122 return src
122 return src
123
123
124 state = {}
124 state = {}
125 for path, src in p[''].items():
125 for path, src in p[''].items():
126 kind = 'hg'
126 kind = 'hg'
127 if src.startswith('['):
127 if src.startswith('['):
128 if ']' not in src:
128 if ']' not in src:
129 raise util.Abort(_('missing ] in subrepo source'))
129 raise util.Abort(_('missing ] in subrepo source'))
130 kind, src = src.split(']', 1)
130 kind, src = src.split(']', 1)
131 kind = kind[1:]
131 kind = kind[1:]
132 src = src.lstrip() # strip any extra whitespace after ']'
132 src = src.lstrip() # strip any extra whitespace after ']'
133
133
134 if not util.url(src).isabs():
134 if not util.url(src).isabs():
135 parent = _abssource(ctx.repo(), abort=False)
135 parent = _abssource(ctx.repo(), abort=False)
136 if parent:
136 if parent:
137 parent = util.url(parent)
137 parent = util.url(parent)
138 parent.path = posixpath.join(parent.path or '', src)
138 parent.path = posixpath.join(parent.path or '', src)
139 parent.path = posixpath.normpath(parent.path)
139 parent.path = posixpath.normpath(parent.path)
140 joined = str(parent)
140 joined = str(parent)
141 # Remap the full joined path and use it if it changes,
141 # Remap the full joined path and use it if it changes,
142 # else remap the original source.
142 # else remap the original source.
143 remapped = remap(joined)
143 remapped = remap(joined)
144 if remapped == joined:
144 if remapped == joined:
145 src = remap(src)
145 src = remap(src)
146 else:
146 else:
147 src = remapped
147 src = remapped
148
148
149 src = remap(src)
149 src = remap(src)
150 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
150 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
151
151
152 return state
152 return state
153
153
154 def writestate(repo, state):
154 def writestate(repo, state):
155 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
155 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
156 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
156 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
157 if state[s][1] != nullstate[1]]
157 if state[s][1] != nullstate[1]]
158 repo.wwrite('.hgsubstate', ''.join(lines), '')
158 repo.wwrite('.hgsubstate', ''.join(lines), '')
159
159
160 def submerge(repo, wctx, mctx, actx, overwrite):
160 def submerge(repo, wctx, mctx, actx, overwrite):
161 """delegated from merge.applyupdates: merging of .hgsubstate file
161 """delegated from merge.applyupdates: merging of .hgsubstate file
162 in working context, merging context and ancestor context"""
162 in working context, merging context and ancestor context"""
163 if mctx == actx: # backwards?
163 if mctx == actx: # backwards?
164 actx = wctx.p1()
164 actx = wctx.p1()
165 s1 = wctx.substate
165 s1 = wctx.substate
166 s2 = mctx.substate
166 s2 = mctx.substate
167 sa = actx.substate
167 sa = actx.substate
168 sm = {}
168 sm = {}
169
169
170 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
170 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
171
171
172 def debug(s, msg, r=""):
172 def debug(s, msg, r=""):
173 if r:
173 if r:
174 r = "%s:%s:%s" % r
174 r = "%s:%s:%s" % r
175 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
175 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
176
176
177 for s, l in sorted(s1.iteritems()):
177 for s, l in sorted(s1.iteritems()):
178 a = sa.get(s, nullstate)
178 a = sa.get(s, nullstate)
179 ld = l # local state with possible dirty flag for compares
179 ld = l # local state with possible dirty flag for compares
180 if wctx.sub(s).dirty():
180 if wctx.sub(s).dirty():
181 ld = (l[0], l[1] + "+")
181 ld = (l[0], l[1] + "+")
182 if wctx == actx: # overwrite
182 if wctx == actx: # overwrite
183 a = ld
183 a = ld
184
184
185 if s in s2:
185 if s in s2:
186 r = s2[s]
186 r = s2[s]
187 if ld == r or r == a: # no change or local is newer
187 if ld == r or r == a: # no change or local is newer
188 sm[s] = l
188 sm[s] = l
189 continue
189 continue
190 elif ld == a: # other side changed
190 elif ld == a: # other side changed
191 debug(s, "other changed, get", r)
191 debug(s, "other changed, get", r)
192 wctx.sub(s).get(r, overwrite)
192 wctx.sub(s).get(r, overwrite)
193 sm[s] = r
193 sm[s] = r
194 elif ld[0] != r[0]: # sources differ
194 elif ld[0] != r[0]: # sources differ
195 if repo.ui.promptchoice(
195 if repo.ui.promptchoice(
196 _(' subrepository sources for %s differ\n'
196 _(' subrepository sources for %s differ\n'
197 'use (l)ocal source (%s) or (r)emote source (%s)?'
197 'use (l)ocal source (%s) or (r)emote source (%s)?'
198 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
198 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
199 debug(s, "prompt changed, get", r)
199 debug(s, "prompt changed, get", r)
200 wctx.sub(s).get(r, overwrite)
200 wctx.sub(s).get(r, overwrite)
201 sm[s] = r
201 sm[s] = r
202 elif ld[1] == a[1]: # local side is unchanged
202 elif ld[1] == a[1]: # local side is unchanged
203 debug(s, "other side changed, get", r)
203 debug(s, "other side changed, get", r)
204 wctx.sub(s).get(r, overwrite)
204 wctx.sub(s).get(r, overwrite)
205 sm[s] = r
205 sm[s] = r
206 else:
206 else:
207 debug(s, "both sides changed")
207 debug(s, "both sides changed")
208 srepo = wctx.sub(s)
208 srepo = wctx.sub(s)
209 option = repo.ui.promptchoice(
209 option = repo.ui.promptchoice(
210 _(' subrepository %s diverged (local revision: %s, '
210 _(' subrepository %s diverged (local revision: %s, '
211 'remote revision: %s)\n'
211 'remote revision: %s)\n'
212 '(M)erge, keep (l)ocal or keep (r)emote?'
212 '(M)erge, keep (l)ocal or keep (r)emote?'
213 '$$ &Merge $$ &Local $$ &Remote')
213 '$$ &Merge $$ &Local $$ &Remote')
214 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
214 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
215 if option == 0:
215 if option == 0:
216 wctx.sub(s).merge(r)
216 wctx.sub(s).merge(r)
217 sm[s] = l
217 sm[s] = l
218 debug(s, "merge with", r)
218 debug(s, "merge with", r)
219 elif option == 1:
219 elif option == 1:
220 sm[s] = l
220 sm[s] = l
221 debug(s, "keep local subrepo revision", l)
221 debug(s, "keep local subrepo revision", l)
222 else:
222 else:
223 wctx.sub(s).get(r, overwrite)
223 wctx.sub(s).get(r, overwrite)
224 sm[s] = r
224 sm[s] = r
225 debug(s, "get remote subrepo revision", r)
225 debug(s, "get remote subrepo revision", r)
226 elif ld == a: # remote removed, local unchanged
226 elif ld == a: # remote removed, local unchanged
227 debug(s, "remote removed, remove")
227 debug(s, "remote removed, remove")
228 wctx.sub(s).remove()
228 wctx.sub(s).remove()
229 elif a == nullstate: # not present in remote or ancestor
229 elif a == nullstate: # not present in remote or ancestor
230 debug(s, "local added, keep")
230 debug(s, "local added, keep")
231 sm[s] = l
231 sm[s] = l
232 continue
232 continue
233 else:
233 else:
234 if repo.ui.promptchoice(
234 if repo.ui.promptchoice(
235 _(' local changed subrepository %s which remote removed\n'
235 _(' local changed subrepository %s which remote removed\n'
236 'use (c)hanged version or (d)elete?'
236 'use (c)hanged version or (d)elete?'
237 '$$ &Changed $$ &Delete') % s, 0):
237 '$$ &Changed $$ &Delete') % s, 0):
238 debug(s, "prompt remove")
238 debug(s, "prompt remove")
239 wctx.sub(s).remove()
239 wctx.sub(s).remove()
240
240
241 for s, r in sorted(s2.items()):
241 for s, r in sorted(s2.items()):
242 if s in s1:
242 if s in s1:
243 continue
243 continue
244 elif s not in sa:
244 elif s not in sa:
245 debug(s, "remote added, get", r)
245 debug(s, "remote added, get", r)
246 mctx.sub(s).get(r)
246 mctx.sub(s).get(r)
247 sm[s] = r
247 sm[s] = r
248 elif r != sa[s]:
248 elif r != sa[s]:
249 if repo.ui.promptchoice(
249 if repo.ui.promptchoice(
250 _(' remote changed subrepository %s which local removed\n'
250 _(' remote changed subrepository %s which local removed\n'
251 'use (c)hanged version or (d)elete?'
251 'use (c)hanged version or (d)elete?'
252 '$$ &Changed $$ &Delete') % s, 0) == 0:
252 '$$ &Changed $$ &Delete') % s, 0) == 0:
253 debug(s, "prompt recreate", r)
253 debug(s, "prompt recreate", r)
254 mctx.sub(s).get(r)
254 mctx.sub(s).get(r)
255 sm[s] = r
255 sm[s] = r
256
256
257 # record merged .hgsubstate
257 # record merged .hgsubstate
258 writestate(repo, sm)
258 writestate(repo, sm)
259 return sm
259 return sm
260
260
261 def _updateprompt(ui, sub, dirty, local, remote):
261 def _updateprompt(ui, sub, dirty, local, remote):
262 if dirty:
262 if dirty:
263 msg = (_(' subrepository sources for %s differ\n'
263 msg = (_(' subrepository sources for %s differ\n'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
265 '$$ &Local $$ &Remote')
265 '$$ &Local $$ &Remote')
266 % (subrelpath(sub), local, remote))
266 % (subrelpath(sub), local, remote))
267 else:
267 else:
268 msg = (_(' subrepository sources for %s differ (in checked out '
268 msg = (_(' subrepository sources for %s differ (in checked out '
269 'version)\n'
269 'version)\n'
270 'use (l)ocal source (%s) or (r)emote source (%s)?'
270 'use (l)ocal source (%s) or (r)emote source (%s)?'
271 '$$ &Local $$ &Remote')
271 '$$ &Local $$ &Remote')
272 % (subrelpath(sub), local, remote))
272 % (subrelpath(sub), local, remote))
273 return ui.promptchoice(msg, 0)
273 return ui.promptchoice(msg, 0)
274
274
275 def reporelpath(repo):
275 def reporelpath(repo):
276 """return path to this (sub)repo as seen from outermost repo"""
276 """return path to this (sub)repo as seen from outermost repo"""
277 parent = repo
277 parent = repo
278 while util.safehasattr(parent, '_subparent'):
278 while util.safehasattr(parent, '_subparent'):
279 parent = parent._subparent
279 parent = parent._subparent
280 return repo.root[len(pathutil.normasprefix(parent.root)):]
280 return repo.root[len(pathutil.normasprefix(parent.root)):]
281
281
282 def subrelpath(sub):
282 def subrelpath(sub):
283 """return path to this subrepo as seen from outermost repo"""
283 """return path to this subrepo as seen from outermost repo"""
284 return sub._relpath
284 return sub._relpath
285
285
286 def _abssource(repo, push=False, abort=True):
286 def _abssource(repo, push=False, abort=True):
287 """return pull/push path of repo - either based on parent repo .hgsub info
287 """return pull/push path of repo - either based on parent repo .hgsub info
288 or on the top repo config. Abort or return None if no source found."""
288 or on the top repo config. Abort or return None if no source found."""
289 if util.safehasattr(repo, '_subparent'):
289 if util.safehasattr(repo, '_subparent'):
290 source = util.url(repo._subsource)
290 source = util.url(repo._subsource)
291 if source.isabs():
291 if source.isabs():
292 return str(source)
292 return str(source)
293 source.path = posixpath.normpath(source.path)
293 source.path = posixpath.normpath(source.path)
294 parent = _abssource(repo._subparent, push, abort=False)
294 parent = _abssource(repo._subparent, push, abort=False)
295 if parent:
295 if parent:
296 parent = util.url(util.pconvert(parent))
296 parent = util.url(util.pconvert(parent))
297 parent.path = posixpath.join(parent.path or '', source.path)
297 parent.path = posixpath.join(parent.path or '', source.path)
298 parent.path = posixpath.normpath(parent.path)
298 parent.path = posixpath.normpath(parent.path)
299 return str(parent)
299 return str(parent)
300 else: # recursion reached top repo
300 else: # recursion reached top repo
301 if util.safehasattr(repo, '_subtoppath'):
301 if util.safehasattr(repo, '_subtoppath'):
302 return repo._subtoppath
302 return repo._subtoppath
303 if push and repo.ui.config('paths', 'default-push'):
303 if push and repo.ui.config('paths', 'default-push'):
304 return repo.ui.config('paths', 'default-push')
304 return repo.ui.config('paths', 'default-push')
305 if repo.ui.config('paths', 'default'):
305 if repo.ui.config('paths', 'default'):
306 return repo.ui.config('paths', 'default')
306 return repo.ui.config('paths', 'default')
307 if repo.shared():
307 if repo.shared():
308 # chop off the .hg component to get the default path form
308 # chop off the .hg component to get the default path form
309 return os.path.dirname(repo.sharedpath)
309 return os.path.dirname(repo.sharedpath)
310 if abort:
310 if abort:
311 raise util.Abort(_("default path for subrepository not found"))
311 raise util.Abort(_("default path for subrepository not found"))
312
312
313 def _sanitize(ui, vfs, ignore):
313 def _sanitize(ui, vfs, ignore):
314 for dirname, dirs, names in vfs.walk():
314 for dirname, dirs, names in vfs.walk():
315 for i, d in enumerate(dirs):
315 for i, d in enumerate(dirs):
316 if d.lower() == ignore:
316 if d.lower() == ignore:
317 del dirs[i]
317 del dirs[i]
318 break
318 break
319 if os.path.basename(dirname).lower() != '.hg':
319 if os.path.basename(dirname).lower() != '.hg':
320 continue
320 continue
321 for f in names:
321 for f in names:
322 if f.lower() == 'hgrc':
322 if f.lower() == 'hgrc':
323 ui.warn(_("warning: removing potentially hostile 'hgrc' "
323 ui.warn(_("warning: removing potentially hostile 'hgrc' "
324 "in '%s'\n") % vfs.join(dirname))
324 "in '%s'\n") % vfs.join(dirname))
325 vfs.unlink(vfs.reljoin(dirname, f))
325 vfs.unlink(vfs.reljoin(dirname, f))
326
326
327 def subrepo(ctx, path):
327 def subrepo(ctx, path):
328 """return instance of the right subrepo class for subrepo in path"""
328 """return instance of the right subrepo class for subrepo in path"""
329 # subrepo inherently violates our import layering rules
329 # subrepo inherently violates our import layering rules
330 # because it wants to make repo objects from deep inside the stack
330 # because it wants to make repo objects from deep inside the stack
331 # so we manually delay the circular imports to not break
331 # so we manually delay the circular imports to not break
332 # scripts that don't use our demand-loading
332 # scripts that don't use our demand-loading
333 global hg
333 global hg
334 import hg as h
334 import hg as h
335 hg = h
335 hg = h
336
336
337 pathutil.pathauditor(ctx.repo().root)(path)
337 pathutil.pathauditor(ctx.repo().root)(path)
338 state = ctx.substate[path]
338 state = ctx.substate[path]
339 if state[2] not in types:
339 if state[2] not in types:
340 raise util.Abort(_('unknown subrepo type %s') % state[2])
340 raise util.Abort(_('unknown subrepo type %s') % state[2])
341 return types[state[2]](ctx, path, state[:2])
341 return types[state[2]](ctx, path, state[:2])
342
342
343 def nullsubrepo(ctx, path, pctx):
343 def nullsubrepo(ctx, path, pctx):
344 """return an empty subrepo in pctx for the extant subrepo in ctx"""
344 """return an empty subrepo in pctx for the extant subrepo in ctx"""
345 # subrepo inherently violates our import layering rules
345 # subrepo inherently violates our import layering rules
346 # because it wants to make repo objects from deep inside the stack
346 # because it wants to make repo objects from deep inside the stack
347 # so we manually delay the circular imports to not break
347 # so we manually delay the circular imports to not break
348 # scripts that don't use our demand-loading
348 # scripts that don't use our demand-loading
349 global hg
349 global hg
350 import hg as h
350 import hg as h
351 hg = h
351 hg = h
352
352
353 pathutil.pathauditor(ctx.repo().root)(path)
353 pathutil.pathauditor(ctx.repo().root)(path)
354 state = ctx.substate[path]
354 state = ctx.substate[path]
355 if state[2] not in types:
355 if state[2] not in types:
356 raise util.Abort(_('unknown subrepo type %s') % state[2])
356 raise util.Abort(_('unknown subrepo type %s') % state[2])
357 subrev = ''
357 subrev = ''
358 if state[2] == 'hg':
358 if state[2] == 'hg':
359 subrev = "0" * 40
359 subrev = "0" * 40
360 return types[state[2]](pctx, path, (state[0], subrev))
360 return types[state[2]](pctx, path, (state[0], subrev))
361
361
362 def newcommitphase(ui, ctx):
362 def newcommitphase(ui, ctx):
363 commitphase = phases.newcommitphase(ui)
363 commitphase = phases.newcommitphase(ui)
364 substate = getattr(ctx, "substate", None)
364 substate = getattr(ctx, "substate", None)
365 if not substate:
365 if not substate:
366 return commitphase
366 return commitphase
367 check = ui.config('phases', 'checksubrepos', 'follow')
367 check = ui.config('phases', 'checksubrepos', 'follow')
368 if check not in ('ignore', 'follow', 'abort'):
368 if check not in ('ignore', 'follow', 'abort'):
369 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
369 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
370 % (check))
370 % (check))
371 if check == 'ignore':
371 if check == 'ignore':
372 return commitphase
372 return commitphase
373 maxphase = phases.public
373 maxphase = phases.public
374 maxsub = None
374 maxsub = None
375 for s in sorted(substate):
375 for s in sorted(substate):
376 sub = ctx.sub(s)
376 sub = ctx.sub(s)
377 subphase = sub.phase(substate[s][1])
377 subphase = sub.phase(substate[s][1])
378 if maxphase < subphase:
378 if maxphase < subphase:
379 maxphase = subphase
379 maxphase = subphase
380 maxsub = s
380 maxsub = s
381 if commitphase < maxphase:
381 if commitphase < maxphase:
382 if check == 'abort':
382 if check == 'abort':
383 raise util.Abort(_("can't commit in %s phase"
383 raise util.Abort(_("can't commit in %s phase"
384 " conflicting %s from subrepository %s") %
384 " conflicting %s from subrepository %s") %
385 (phases.phasenames[commitphase],
385 (phases.phasenames[commitphase],
386 phases.phasenames[maxphase], maxsub))
386 phases.phasenames[maxphase], maxsub))
387 ui.warn(_("warning: changes are committed in"
387 ui.warn(_("warning: changes are committed in"
388 " %s phase from subrepository %s\n") %
388 " %s phase from subrepository %s\n") %
389 (phases.phasenames[maxphase], maxsub))
389 (phases.phasenames[maxphase], maxsub))
390 return maxphase
390 return maxphase
391 return commitphase
391 return commitphase
392
392
393 # subrepo classes need to implement the following abstract class:
393 # subrepo classes need to implement the following abstract class:
394
394
395 class abstractsubrepo(object):
395 class abstractsubrepo(object):
396
396
397 def __init__(self, ctx, path):
397 def __init__(self, ctx, path):
398 """Initialize abstractsubrepo part
398 """Initialize abstractsubrepo part
399
399
400 ``ctx`` is the context referring this subrepository in the
400 ``ctx`` is the context referring this subrepository in the
401 parent repository.
401 parent repository.
402
402
403 ``path`` is the path to this subrepositiry as seen from
403 ``path`` is the path to this subrepositiry as seen from
404 innermost repository.
404 innermost repository.
405 """
405 """
406 self.ui = ctx.repo().ui
406 self.ui = ctx.repo().ui
407 self._ctx = ctx
407 self._ctx = ctx
408 self._path = path
408 self._path = path
409
409
410 def storeclean(self, path):
410 def storeclean(self, path):
411 """
411 """
412 returns true if the repository has not changed since it was last
412 returns true if the repository has not changed since it was last
413 cloned from or pushed to a given repository.
413 cloned from or pushed to a given repository.
414 """
414 """
415 return False
415 return False
416
416
417 def dirty(self, ignoreupdate=False):
417 def dirty(self, ignoreupdate=False):
418 """returns true if the dirstate of the subrepo is dirty or does not
418 """returns true if the dirstate of the subrepo is dirty or does not
419 match current stored state. If ignoreupdate is true, only check
419 match current stored state. If ignoreupdate is true, only check
420 whether the subrepo has uncommitted changes in its dirstate.
420 whether the subrepo has uncommitted changes in its dirstate.
421 """
421 """
422 raise NotImplementedError
422 raise NotImplementedError
423
423
424 def dirtyreason(self, ignoreupdate=False):
424 def dirtyreason(self, ignoreupdate=False):
425 """return reason string if it is ``dirty()``
425 """return reason string if it is ``dirty()``
426
426
427 Returned string should have enough information for the message
427 Returned string should have enough information for the message
428 of exception.
428 of exception.
429
429
430 This returns None, otherwise.
430 This returns None, otherwise.
431 """
431 """
432 if self.dirty(ignoreupdate=ignoreupdate):
432 if self.dirty(ignoreupdate=ignoreupdate):
433 return _("uncommitted changes in subrepository '%s'"
433 return _("uncommitted changes in subrepository '%s'"
434 ) % subrelpath(self)
434 ) % subrelpath(self)
435
435
436 def bailifchanged(self, ignoreupdate=False):
436 def bailifchanged(self, ignoreupdate=False):
437 """raise Abort if subrepository is ``dirty()``
437 """raise Abort if subrepository is ``dirty()``
438 """
438 """
439 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
439 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
440 if dirtyreason:
440 if dirtyreason:
441 raise util.Abort(dirtyreason)
441 raise util.Abort(dirtyreason)
442
442
443 def basestate(self):
443 def basestate(self):
444 """current working directory base state, disregarding .hgsubstate
444 """current working directory base state, disregarding .hgsubstate
445 state and working directory modifications"""
445 state and working directory modifications"""
446 raise NotImplementedError
446 raise NotImplementedError
447
447
448 def checknested(self, path):
448 def checknested(self, path):
449 """check if path is a subrepository within this repository"""
449 """check if path is a subrepository within this repository"""
450 return False
450 return False
451
451
452 def commit(self, text, user, date):
452 def commit(self, text, user, date):
453 """commit the current changes to the subrepo with the given
453 """commit the current changes to the subrepo with the given
454 log message. Use given user and date if possible. Return the
454 log message. Use given user and date if possible. Return the
455 new state of the subrepo.
455 new state of the subrepo.
456 """
456 """
457 raise NotImplementedError
457 raise NotImplementedError
458
458
459 def phase(self, state):
459 def phase(self, state):
460 """returns phase of specified state in the subrepository.
460 """returns phase of specified state in the subrepository.
461 """
461 """
462 return phases.public
462 return phases.public
463
463
464 def remove(self):
464 def remove(self):
465 """remove the subrepo
465 """remove the subrepo
466
466
467 (should verify the dirstate is not dirty first)
467 (should verify the dirstate is not dirty first)
468 """
468 """
469 raise NotImplementedError
469 raise NotImplementedError
470
470
471 def get(self, state, overwrite=False):
471 def get(self, state, overwrite=False):
472 """run whatever commands are needed to put the subrepo into
472 """run whatever commands are needed to put the subrepo into
473 this state
473 this state
474 """
474 """
475 raise NotImplementedError
475 raise NotImplementedError
476
476
477 def merge(self, state):
477 def merge(self, state):
478 """merge currently-saved state with the new state."""
478 """merge currently-saved state with the new state."""
479 raise NotImplementedError
479 raise NotImplementedError
480
480
481 def push(self, opts):
481 def push(self, opts):
482 """perform whatever action is analogous to 'hg push'
482 """perform whatever action is analogous to 'hg push'
483
483
484 This may be a no-op on some systems.
484 This may be a no-op on some systems.
485 """
485 """
486 raise NotImplementedError
486 raise NotImplementedError
487
487
488 def add(self, ui, match, prefix, explicitonly, **opts):
488 def add(self, ui, match, prefix, explicitonly, **opts):
489 return []
489 return []
490
490
491 def addremove(self, matcher, prefix, opts, dry_run, similarity):
491 def addremove(self, matcher, prefix, opts, dry_run, similarity):
492 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
492 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
493 return 1
493 return 1
494
494
495 def cat(self, match, prefix, **opts):
495 def cat(self, match, prefix, **opts):
496 return 1
496 return 1
497
497
498 def status(self, rev2, **opts):
498 def status(self, rev2, **opts):
499 return scmutil.status([], [], [], [], [], [], [])
499 return scmutil.status([], [], [], [], [], [], [])
500
500
501 def diff(self, ui, diffopts, node2, match, prefix, **opts):
501 def diff(self, ui, diffopts, node2, match, prefix, **opts):
502 pass
502 pass
503
503
504 def outgoing(self, ui, dest, opts):
504 def outgoing(self, ui, dest, opts):
505 return 1
505 return 1
506
506
507 def incoming(self, ui, source, opts):
507 def incoming(self, ui, source, opts):
508 return 1
508 return 1
509
509
510 def files(self):
510 def files(self):
511 """return filename iterator"""
511 """return filename iterator"""
512 raise NotImplementedError
512 raise NotImplementedError
513
513
514 def filedata(self, name):
514 def filedata(self, name):
515 """return file data"""
515 """return file data"""
516 raise NotImplementedError
516 raise NotImplementedError
517
517
518 def fileflags(self, name):
518 def fileflags(self, name):
519 """return file flags"""
519 """return file flags"""
520 return ''
520 return ''
521
521
522 def getfileset(self, expr):
522 def getfileset(self, expr):
523 """Resolve the fileset expression for this repo"""
523 """Resolve the fileset expression for this repo"""
524 return set()
524 return set()
525
525
526 def printfiles(self, ui, m, fm, fmt, subrepos):
526 def printfiles(self, ui, m, fm, fmt, subrepos):
527 """handle the files command for this subrepo"""
527 """handle the files command for this subrepo"""
528 return 1
528 return 1
529
529
530 def archive(self, archiver, prefix, match=None):
530 def archive(self, archiver, prefix, match=None):
531 if match is not None:
531 if match is not None:
532 files = [f for f in self.files() if match(f)]
532 files = [f for f in self.files() if match(f)]
533 else:
533 else:
534 files = self.files()
534 files = self.files()
535 total = len(files)
535 total = len(files)
536 relpath = subrelpath(self)
536 relpath = subrelpath(self)
537 self.ui.progress(_('archiving (%s)') % relpath, 0,
537 self.ui.progress(_('archiving (%s)') % relpath, 0,
538 unit=_('files'), total=total)
538 unit=_('files'), total=total)
539 for i, name in enumerate(files):
539 for i, name in enumerate(files):
540 flags = self.fileflags(name)
540 flags = self.fileflags(name)
541 mode = 'x' in flags and 0755 or 0644
541 mode = 'x' in flags and 0755 or 0644
542 symlink = 'l' in flags
542 symlink = 'l' in flags
543 archiver.addfile(prefix + self._path + '/' + name,
543 archiver.addfile(prefix + self._path + '/' + name,
544 mode, symlink, self.filedata(name))
544 mode, symlink, self.filedata(name))
545 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
545 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
546 unit=_('files'), total=total)
546 unit=_('files'), total=total)
547 self.ui.progress(_('archiving (%s)') % relpath, None)
547 self.ui.progress(_('archiving (%s)') % relpath, None)
548 return total
548 return total
549
549
550 def walk(self, match):
550 def walk(self, match):
551 '''
551 '''
552 walk recursively through the directory tree, finding all files
552 walk recursively through the directory tree, finding all files
553 matched by the match function
553 matched by the match function
554 '''
554 '''
555 pass
555 pass
556
556
557 def forget(self, match, prefix):
557 def forget(self, match, prefix):
558 return ([], [])
558 return ([], [])
559
559
560 def removefiles(self, matcher, prefix, after, force, subrepos):
560 def removefiles(self, matcher, prefix, after, force, subrepos):
561 """remove the matched files from the subrepository and the filesystem,
561 """remove the matched files from the subrepository and the filesystem,
562 possibly by force and/or after the file has been removed from the
562 possibly by force and/or after the file has been removed from the
563 filesystem. Return 0 on success, 1 on any warning.
563 filesystem. Return 0 on success, 1 on any warning.
564 """
564 """
565 return 1
565 return 1
566
566
567 def revert(self, substate, *pats, **opts):
567 def revert(self, substate, *pats, **opts):
568 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
568 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
569 % (substate[0], substate[2]))
569 % (substate[0], substate[2]))
570 return []
570 return []
571
571
572 def shortid(self, revid):
572 def shortid(self, revid):
573 return revid
573 return revid
574
574
575 def verify(self):
576 '''verify the integrity of the repository. Return 0 on success or
577 warning, 1 on any error.
578 '''
579 return 0
580
575 @propertycache
581 @propertycache
576 def wvfs(self):
582 def wvfs(self):
577 """return vfs to access the working directory of this subrepository
583 """return vfs to access the working directory of this subrepository
578 """
584 """
579 return scmutil.vfs(self._ctx.repo().wvfs.join(self._path))
585 return scmutil.vfs(self._ctx.repo().wvfs.join(self._path))
580
586
581 @propertycache
587 @propertycache
582 def _relpath(self):
588 def _relpath(self):
583 """return path to this subrepository as seen from outermost repository
589 """return path to this subrepository as seen from outermost repository
584 """
590 """
585 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
591 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
586
592
587 class hgsubrepo(abstractsubrepo):
593 class hgsubrepo(abstractsubrepo):
588 def __init__(self, ctx, path, state):
594 def __init__(self, ctx, path, state):
589 super(hgsubrepo, self).__init__(ctx, path)
595 super(hgsubrepo, self).__init__(ctx, path)
590 self._state = state
596 self._state = state
591 r = ctx.repo()
597 r = ctx.repo()
592 root = r.wjoin(path)
598 root = r.wjoin(path)
593 create = not r.wvfs.exists('%s/.hg' % path)
599 create = not r.wvfs.exists('%s/.hg' % path)
594 self._repo = hg.repository(r.baseui, root, create=create)
600 self._repo = hg.repository(r.baseui, root, create=create)
595
601
596 # Propagate the parent's --hidden option
602 # Propagate the parent's --hidden option
597 if r is r.unfiltered():
603 if r is r.unfiltered():
598 self._repo = self._repo.unfiltered()
604 self._repo = self._repo.unfiltered()
599
605
600 self.ui = self._repo.ui
606 self.ui = self._repo.ui
601 for s, k in [('ui', 'commitsubrepos')]:
607 for s, k in [('ui', 'commitsubrepos')]:
602 v = r.ui.config(s, k)
608 v = r.ui.config(s, k)
603 if v:
609 if v:
604 self.ui.setconfig(s, k, v, 'subrepo')
610 self.ui.setconfig(s, k, v, 'subrepo')
605 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
611 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
606 self._initrepo(r, state[0], create)
612 self._initrepo(r, state[0], create)
607
613
608 def storeclean(self, path):
614 def storeclean(self, path):
609 lock = self._repo.lock()
615 lock = self._repo.lock()
610 try:
616 try:
611 return self._storeclean(path)
617 return self._storeclean(path)
612 finally:
618 finally:
613 lock.release()
619 lock.release()
614
620
615 def _storeclean(self, path):
621 def _storeclean(self, path):
616 clean = True
622 clean = True
617 itercache = self._calcstorehash(path)
623 itercache = self._calcstorehash(path)
618 for filehash in self._readstorehashcache(path):
624 for filehash in self._readstorehashcache(path):
619 if filehash != next(itercache, None):
625 if filehash != next(itercache, None):
620 clean = False
626 clean = False
621 break
627 break
622 if clean:
628 if clean:
623 # if not empty:
629 # if not empty:
624 # the cached and current pull states have a different size
630 # the cached and current pull states have a different size
625 clean = next(itercache, None) is None
631 clean = next(itercache, None) is None
626 return clean
632 return clean
627
633
628 def _calcstorehash(self, remotepath):
634 def _calcstorehash(self, remotepath):
629 '''calculate a unique "store hash"
635 '''calculate a unique "store hash"
630
636
631 This method is used to to detect when there are changes that may
637 This method is used to to detect when there are changes that may
632 require a push to a given remote path.'''
638 require a push to a given remote path.'''
633 # sort the files that will be hashed in increasing (likely) file size
639 # sort the files that will be hashed in increasing (likely) file size
634 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
640 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
635 yield '# %s\n' % _expandedabspath(remotepath)
641 yield '# %s\n' % _expandedabspath(remotepath)
636 vfs = self._repo.vfs
642 vfs = self._repo.vfs
637 for relname in filelist:
643 for relname in filelist:
638 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
644 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
639 yield '%s = %s\n' % (relname, filehash)
645 yield '%s = %s\n' % (relname, filehash)
640
646
641 @propertycache
647 @propertycache
642 def _cachestorehashvfs(self):
648 def _cachestorehashvfs(self):
643 return scmutil.vfs(self._repo.join('cache/storehash'))
649 return scmutil.vfs(self._repo.join('cache/storehash'))
644
650
645 def _readstorehashcache(self, remotepath):
651 def _readstorehashcache(self, remotepath):
646 '''read the store hash cache for a given remote repository'''
652 '''read the store hash cache for a given remote repository'''
647 cachefile = _getstorehashcachename(remotepath)
653 cachefile = _getstorehashcachename(remotepath)
648 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
654 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
649
655
650 def _cachestorehash(self, remotepath):
656 def _cachestorehash(self, remotepath):
651 '''cache the current store hash
657 '''cache the current store hash
652
658
653 Each remote repo requires its own store hash cache, because a subrepo
659 Each remote repo requires its own store hash cache, because a subrepo
654 store may be "clean" versus a given remote repo, but not versus another
660 store may be "clean" versus a given remote repo, but not versus another
655 '''
661 '''
656 cachefile = _getstorehashcachename(remotepath)
662 cachefile = _getstorehashcachename(remotepath)
657 lock = self._repo.lock()
663 lock = self._repo.lock()
658 try:
664 try:
659 storehash = list(self._calcstorehash(remotepath))
665 storehash = list(self._calcstorehash(remotepath))
660 vfs = self._cachestorehashvfs
666 vfs = self._cachestorehashvfs
661 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
667 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
662 finally:
668 finally:
663 lock.release()
669 lock.release()
664
670
665 def _getctx(self):
671 def _getctx(self):
666 '''fetch the context for this subrepo revision, possibly a workingctx
672 '''fetch the context for this subrepo revision, possibly a workingctx
667 '''
673 '''
668 if self._ctx.rev() is None:
674 if self._ctx.rev() is None:
669 return self._repo[None] # workingctx if parent is workingctx
675 return self._repo[None] # workingctx if parent is workingctx
670 else:
676 else:
671 rev = self._state[1]
677 rev = self._state[1]
672 return self._repo[rev]
678 return self._repo[rev]
673
679
674 @annotatesubrepoerror
680 @annotatesubrepoerror
675 def _initrepo(self, parentrepo, source, create):
681 def _initrepo(self, parentrepo, source, create):
676 self._repo._subparent = parentrepo
682 self._repo._subparent = parentrepo
677 self._repo._subsource = source
683 self._repo._subsource = source
678
684
679 if create:
685 if create:
680 lines = ['[paths]\n']
686 lines = ['[paths]\n']
681
687
682 def addpathconfig(key, value):
688 def addpathconfig(key, value):
683 if value:
689 if value:
684 lines.append('%s = %s\n' % (key, value))
690 lines.append('%s = %s\n' % (key, value))
685 self.ui.setconfig('paths', key, value, 'subrepo')
691 self.ui.setconfig('paths', key, value, 'subrepo')
686
692
687 defpath = _abssource(self._repo, abort=False)
693 defpath = _abssource(self._repo, abort=False)
688 defpushpath = _abssource(self._repo, True, abort=False)
694 defpushpath = _abssource(self._repo, True, abort=False)
689 addpathconfig('default', defpath)
695 addpathconfig('default', defpath)
690 if defpath != defpushpath:
696 if defpath != defpushpath:
691 addpathconfig('default-push', defpushpath)
697 addpathconfig('default-push', defpushpath)
692
698
693 fp = self._repo.vfs("hgrc", "w", text=True)
699 fp = self._repo.vfs("hgrc", "w", text=True)
694 try:
700 try:
695 fp.write(''.join(lines))
701 fp.write(''.join(lines))
696 finally:
702 finally:
697 fp.close()
703 fp.close()
698
704
699 @annotatesubrepoerror
705 @annotatesubrepoerror
700 def add(self, ui, match, prefix, explicitonly, **opts):
706 def add(self, ui, match, prefix, explicitonly, **opts):
701 return cmdutil.add(ui, self._repo, match,
707 return cmdutil.add(ui, self._repo, match,
702 self.wvfs.reljoin(prefix, self._path),
708 self.wvfs.reljoin(prefix, self._path),
703 explicitonly, **opts)
709 explicitonly, **opts)
704
710
705 @annotatesubrepoerror
711 @annotatesubrepoerror
706 def addremove(self, m, prefix, opts, dry_run, similarity):
712 def addremove(self, m, prefix, opts, dry_run, similarity):
707 # In the same way as sub directories are processed, once in a subrepo,
713 # In the same way as sub directories are processed, once in a subrepo,
708 # always entry any of its subrepos. Don't corrupt the options that will
714 # always entry any of its subrepos. Don't corrupt the options that will
709 # be used to process sibling subrepos however.
715 # be used to process sibling subrepos however.
710 opts = copy.copy(opts)
716 opts = copy.copy(opts)
711 opts['subrepos'] = True
717 opts['subrepos'] = True
712 return scmutil.addremove(self._repo, m,
718 return scmutil.addremove(self._repo, m,
713 self.wvfs.reljoin(prefix, self._path), opts,
719 self.wvfs.reljoin(prefix, self._path), opts,
714 dry_run, similarity)
720 dry_run, similarity)
715
721
716 @annotatesubrepoerror
722 @annotatesubrepoerror
717 def cat(self, match, prefix, **opts):
723 def cat(self, match, prefix, **opts):
718 rev = self._state[1]
724 rev = self._state[1]
719 ctx = self._repo[rev]
725 ctx = self._repo[rev]
720 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
726 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
721
727
722 @annotatesubrepoerror
728 @annotatesubrepoerror
723 def status(self, rev2, **opts):
729 def status(self, rev2, **opts):
724 try:
730 try:
725 rev1 = self._state[1]
731 rev1 = self._state[1]
726 ctx1 = self._repo[rev1]
732 ctx1 = self._repo[rev1]
727 ctx2 = self._repo[rev2]
733 ctx2 = self._repo[rev2]
728 return self._repo.status(ctx1, ctx2, **opts)
734 return self._repo.status(ctx1, ctx2, **opts)
729 except error.RepoLookupError, inst:
735 except error.RepoLookupError, inst:
730 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
736 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
731 % (inst, subrelpath(self)))
737 % (inst, subrelpath(self)))
732 return scmutil.status([], [], [], [], [], [], [])
738 return scmutil.status([], [], [], [], [], [], [])
733
739
734 @annotatesubrepoerror
740 @annotatesubrepoerror
735 def diff(self, ui, diffopts, node2, match, prefix, **opts):
741 def diff(self, ui, diffopts, node2, match, prefix, **opts):
736 try:
742 try:
737 node1 = node.bin(self._state[1])
743 node1 = node.bin(self._state[1])
738 # We currently expect node2 to come from substate and be
744 # We currently expect node2 to come from substate and be
739 # in hex format
745 # in hex format
740 if node2 is not None:
746 if node2 is not None:
741 node2 = node.bin(node2)
747 node2 = node.bin(node2)
742 cmdutil.diffordiffstat(ui, self._repo, diffopts,
748 cmdutil.diffordiffstat(ui, self._repo, diffopts,
743 node1, node2, match,
749 node1, node2, match,
744 prefix=posixpath.join(prefix, self._path),
750 prefix=posixpath.join(prefix, self._path),
745 listsubrepos=True, **opts)
751 listsubrepos=True, **opts)
746 except error.RepoLookupError, inst:
752 except error.RepoLookupError, inst:
747 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
753 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
748 % (inst, subrelpath(self)))
754 % (inst, subrelpath(self)))
749
755
750 @annotatesubrepoerror
756 @annotatesubrepoerror
751 def archive(self, archiver, prefix, match=None):
757 def archive(self, archiver, prefix, match=None):
752 self._get(self._state + ('hg',))
758 self._get(self._state + ('hg',))
753 total = abstractsubrepo.archive(self, archiver, prefix, match)
759 total = abstractsubrepo.archive(self, archiver, prefix, match)
754 rev = self._state[1]
760 rev = self._state[1]
755 ctx = self._repo[rev]
761 ctx = self._repo[rev]
756 for subpath in ctx.substate:
762 for subpath in ctx.substate:
757 s = subrepo(ctx, subpath)
763 s = subrepo(ctx, subpath)
758 submatch = matchmod.narrowmatcher(subpath, match)
764 submatch = matchmod.narrowmatcher(subpath, match)
759 total += s.archive(archiver, prefix + self._path + '/', submatch)
765 total += s.archive(archiver, prefix + self._path + '/', submatch)
760 return total
766 return total
761
767
762 @annotatesubrepoerror
768 @annotatesubrepoerror
763 def dirty(self, ignoreupdate=False):
769 def dirty(self, ignoreupdate=False):
764 r = self._state[1]
770 r = self._state[1]
765 if r == '' and not ignoreupdate: # no state recorded
771 if r == '' and not ignoreupdate: # no state recorded
766 return True
772 return True
767 w = self._repo[None]
773 w = self._repo[None]
768 if r != w.p1().hex() and not ignoreupdate:
774 if r != w.p1().hex() and not ignoreupdate:
769 # different version checked out
775 # different version checked out
770 return True
776 return True
771 return w.dirty() # working directory changed
777 return w.dirty() # working directory changed
772
778
773 def basestate(self):
779 def basestate(self):
774 return self._repo['.'].hex()
780 return self._repo['.'].hex()
775
781
776 def checknested(self, path):
782 def checknested(self, path):
777 return self._repo._checknested(self._repo.wjoin(path))
783 return self._repo._checknested(self._repo.wjoin(path))
778
784
779 @annotatesubrepoerror
785 @annotatesubrepoerror
780 def commit(self, text, user, date):
786 def commit(self, text, user, date):
781 # don't bother committing in the subrepo if it's only been
787 # don't bother committing in the subrepo if it's only been
782 # updated
788 # updated
783 if not self.dirty(True):
789 if not self.dirty(True):
784 return self._repo['.'].hex()
790 return self._repo['.'].hex()
785 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
791 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
786 n = self._repo.commit(text, user, date)
792 n = self._repo.commit(text, user, date)
787 if not n:
793 if not n:
788 return self._repo['.'].hex() # different version checked out
794 return self._repo['.'].hex() # different version checked out
789 return node.hex(n)
795 return node.hex(n)
790
796
791 @annotatesubrepoerror
797 @annotatesubrepoerror
792 def phase(self, state):
798 def phase(self, state):
793 return self._repo[state].phase()
799 return self._repo[state].phase()
794
800
795 @annotatesubrepoerror
801 @annotatesubrepoerror
796 def remove(self):
802 def remove(self):
797 # we can't fully delete the repository as it may contain
803 # we can't fully delete the repository as it may contain
798 # local-only history
804 # local-only history
799 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
805 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
800 hg.clean(self._repo, node.nullid, False)
806 hg.clean(self._repo, node.nullid, False)
801
807
802 def _get(self, state):
808 def _get(self, state):
803 source, revision, kind = state
809 source, revision, kind = state
804 if revision in self._repo.unfiltered():
810 if revision in self._repo.unfiltered():
805 return True
811 return True
806 self._repo._subsource = source
812 self._repo._subsource = source
807 srcurl = _abssource(self._repo)
813 srcurl = _abssource(self._repo)
808 other = hg.peer(self._repo, {}, srcurl)
814 other = hg.peer(self._repo, {}, srcurl)
809 if len(self._repo) == 0:
815 if len(self._repo) == 0:
810 self.ui.status(_('cloning subrepo %s from %s\n')
816 self.ui.status(_('cloning subrepo %s from %s\n')
811 % (subrelpath(self), srcurl))
817 % (subrelpath(self), srcurl))
812 parentrepo = self._repo._subparent
818 parentrepo = self._repo._subparent
813 # use self._repo.vfs instead of self.wvfs to remove .hg only
819 # use self._repo.vfs instead of self.wvfs to remove .hg only
814 self._repo.vfs.rmtree()
820 self._repo.vfs.rmtree()
815 other, cloned = hg.clone(self._repo._subparent.baseui, {},
821 other, cloned = hg.clone(self._repo._subparent.baseui, {},
816 other, self._repo.root,
822 other, self._repo.root,
817 update=False)
823 update=False)
818 self._repo = cloned.local()
824 self._repo = cloned.local()
819 self._initrepo(parentrepo, source, create=True)
825 self._initrepo(parentrepo, source, create=True)
820 self._cachestorehash(srcurl)
826 self._cachestorehash(srcurl)
821 else:
827 else:
822 self.ui.status(_('pulling subrepo %s from %s\n')
828 self.ui.status(_('pulling subrepo %s from %s\n')
823 % (subrelpath(self), srcurl))
829 % (subrelpath(self), srcurl))
824 cleansub = self.storeclean(srcurl)
830 cleansub = self.storeclean(srcurl)
825 exchange.pull(self._repo, other)
831 exchange.pull(self._repo, other)
826 if cleansub:
832 if cleansub:
827 # keep the repo clean after pull
833 # keep the repo clean after pull
828 self._cachestorehash(srcurl)
834 self._cachestorehash(srcurl)
829 return False
835 return False
830
836
831 @annotatesubrepoerror
837 @annotatesubrepoerror
832 def get(self, state, overwrite=False):
838 def get(self, state, overwrite=False):
833 inrepo = self._get(state)
839 inrepo = self._get(state)
834 source, revision, kind = state
840 source, revision, kind = state
835 repo = self._repo
841 repo = self._repo
836 repo.ui.debug("getting subrepo %s\n" % self._path)
842 repo.ui.debug("getting subrepo %s\n" % self._path)
837 if inrepo:
843 if inrepo:
838 urepo = repo.unfiltered()
844 urepo = repo.unfiltered()
839 ctx = urepo[revision]
845 ctx = urepo[revision]
840 if ctx.hidden():
846 if ctx.hidden():
841 urepo.ui.warn(
847 urepo.ui.warn(
842 _('revision %s in subrepo %s is hidden\n') \
848 _('revision %s in subrepo %s is hidden\n') \
843 % (revision[0:12], self._path))
849 % (revision[0:12], self._path))
844 repo = urepo
850 repo = urepo
845 hg.updaterepo(repo, revision, overwrite)
851 hg.updaterepo(repo, revision, overwrite)
846
852
847 @annotatesubrepoerror
853 @annotatesubrepoerror
848 def merge(self, state):
854 def merge(self, state):
849 self._get(state)
855 self._get(state)
850 cur = self._repo['.']
856 cur = self._repo['.']
851 dst = self._repo[state[1]]
857 dst = self._repo[state[1]]
852 anc = dst.ancestor(cur)
858 anc = dst.ancestor(cur)
853
859
854 def mergefunc():
860 def mergefunc():
855 if anc == cur and dst.branch() == cur.branch():
861 if anc == cur and dst.branch() == cur.branch():
856 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
862 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
857 hg.update(self._repo, state[1])
863 hg.update(self._repo, state[1])
858 elif anc == dst:
864 elif anc == dst:
859 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
865 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
860 else:
866 else:
861 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
867 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
862 hg.merge(self._repo, state[1], remind=False)
868 hg.merge(self._repo, state[1], remind=False)
863
869
864 wctx = self._repo[None]
870 wctx = self._repo[None]
865 if self.dirty():
871 if self.dirty():
866 if anc != dst:
872 if anc != dst:
867 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
873 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
868 mergefunc()
874 mergefunc()
869 else:
875 else:
870 mergefunc()
876 mergefunc()
871 else:
877 else:
872 mergefunc()
878 mergefunc()
873
879
874 @annotatesubrepoerror
880 @annotatesubrepoerror
875 def push(self, opts):
881 def push(self, opts):
876 force = opts.get('force')
882 force = opts.get('force')
877 newbranch = opts.get('new_branch')
883 newbranch = opts.get('new_branch')
878 ssh = opts.get('ssh')
884 ssh = opts.get('ssh')
879
885
880 # push subrepos depth-first for coherent ordering
886 # push subrepos depth-first for coherent ordering
881 c = self._repo['']
887 c = self._repo['']
882 subs = c.substate # only repos that are committed
888 subs = c.substate # only repos that are committed
883 for s in sorted(subs):
889 for s in sorted(subs):
884 if c.sub(s).push(opts) == 0:
890 if c.sub(s).push(opts) == 0:
885 return False
891 return False
886
892
887 dsturl = _abssource(self._repo, True)
893 dsturl = _abssource(self._repo, True)
888 if not force:
894 if not force:
889 if self.storeclean(dsturl):
895 if self.storeclean(dsturl):
890 self.ui.status(
896 self.ui.status(
891 _('no changes made to subrepo %s since last push to %s\n')
897 _('no changes made to subrepo %s since last push to %s\n')
892 % (subrelpath(self), dsturl))
898 % (subrelpath(self), dsturl))
893 return None
899 return None
894 self.ui.status(_('pushing subrepo %s to %s\n') %
900 self.ui.status(_('pushing subrepo %s to %s\n') %
895 (subrelpath(self), dsturl))
901 (subrelpath(self), dsturl))
896 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
902 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
897 res = exchange.push(self._repo, other, force, newbranch=newbranch)
903 res = exchange.push(self._repo, other, force, newbranch=newbranch)
898
904
899 # the repo is now clean
905 # the repo is now clean
900 self._cachestorehash(dsturl)
906 self._cachestorehash(dsturl)
901 return res.cgresult
907 return res.cgresult
902
908
903 @annotatesubrepoerror
909 @annotatesubrepoerror
904 def outgoing(self, ui, dest, opts):
910 def outgoing(self, ui, dest, opts):
905 if 'rev' in opts or 'branch' in opts:
911 if 'rev' in opts or 'branch' in opts:
906 opts = copy.copy(opts)
912 opts = copy.copy(opts)
907 opts.pop('rev', None)
913 opts.pop('rev', None)
908 opts.pop('branch', None)
914 opts.pop('branch', None)
909 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
915 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
910
916
911 @annotatesubrepoerror
917 @annotatesubrepoerror
912 def incoming(self, ui, source, opts):
918 def incoming(self, ui, source, opts):
913 if 'rev' in opts or 'branch' in opts:
919 if 'rev' in opts or 'branch' in opts:
914 opts = copy.copy(opts)
920 opts = copy.copy(opts)
915 opts.pop('rev', None)
921 opts.pop('rev', None)
916 opts.pop('branch', None)
922 opts.pop('branch', None)
917 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
923 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
918
924
919 @annotatesubrepoerror
925 @annotatesubrepoerror
920 def files(self):
926 def files(self):
921 rev = self._state[1]
927 rev = self._state[1]
922 ctx = self._repo[rev]
928 ctx = self._repo[rev]
923 return ctx.manifest().keys()
929 return ctx.manifest().keys()
924
930
925 def filedata(self, name):
931 def filedata(self, name):
926 rev = self._state[1]
932 rev = self._state[1]
927 return self._repo[rev][name].data()
933 return self._repo[rev][name].data()
928
934
929 def fileflags(self, name):
935 def fileflags(self, name):
930 rev = self._state[1]
936 rev = self._state[1]
931 ctx = self._repo[rev]
937 ctx = self._repo[rev]
932 return ctx.flags(name)
938 return ctx.flags(name)
933
939
934 @annotatesubrepoerror
940 @annotatesubrepoerror
935 def printfiles(self, ui, m, fm, fmt, subrepos):
941 def printfiles(self, ui, m, fm, fmt, subrepos):
936 # If the parent context is a workingctx, use the workingctx here for
942 # If the parent context is a workingctx, use the workingctx here for
937 # consistency.
943 # consistency.
938 if self._ctx.rev() is None:
944 if self._ctx.rev() is None:
939 ctx = self._repo[None]
945 ctx = self._repo[None]
940 else:
946 else:
941 rev = self._state[1]
947 rev = self._state[1]
942 ctx = self._repo[rev]
948 ctx = self._repo[rev]
943 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
949 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
944
950
945 @annotatesubrepoerror
951 @annotatesubrepoerror
946 def getfileset(self, expr):
952 def getfileset(self, expr):
947 if self._ctx.rev() is None:
953 if self._ctx.rev() is None:
948 ctx = self._repo[None]
954 ctx = self._repo[None]
949 else:
955 else:
950 rev = self._state[1]
956 rev = self._state[1]
951 ctx = self._repo[rev]
957 ctx = self._repo[rev]
952
958
953 files = ctx.getfileset(expr)
959 files = ctx.getfileset(expr)
954
960
955 for subpath in ctx.substate:
961 for subpath in ctx.substate:
956 sub = ctx.sub(subpath)
962 sub = ctx.sub(subpath)
957
963
958 try:
964 try:
959 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
965 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
960 except error.LookupError:
966 except error.LookupError:
961 self.ui.status(_("skipping missing subrepository: %s\n")
967 self.ui.status(_("skipping missing subrepository: %s\n")
962 % self.wvfs.reljoin(reporelpath(self), subpath))
968 % self.wvfs.reljoin(reporelpath(self), subpath))
963 return files
969 return files
964
970
965 def walk(self, match):
971 def walk(self, match):
966 ctx = self._repo[None]
972 ctx = self._repo[None]
967 return ctx.walk(match)
973 return ctx.walk(match)
968
974
969 @annotatesubrepoerror
975 @annotatesubrepoerror
970 def forget(self, match, prefix):
976 def forget(self, match, prefix):
971 return cmdutil.forget(self.ui, self._repo, match,
977 return cmdutil.forget(self.ui, self._repo, match,
972 self.wvfs.reljoin(prefix, self._path), True)
978 self.wvfs.reljoin(prefix, self._path), True)
973
979
974 @annotatesubrepoerror
980 @annotatesubrepoerror
975 def removefiles(self, matcher, prefix, after, force, subrepos):
981 def removefiles(self, matcher, prefix, after, force, subrepos):
976 return cmdutil.remove(self.ui, self._repo, matcher,
982 return cmdutil.remove(self.ui, self._repo, matcher,
977 self.wvfs.reljoin(prefix, self._path),
983 self.wvfs.reljoin(prefix, self._path),
978 after, force, subrepos)
984 after, force, subrepos)
979
985
980 @annotatesubrepoerror
986 @annotatesubrepoerror
981 def revert(self, substate, *pats, **opts):
987 def revert(self, substate, *pats, **opts):
982 # reverting a subrepo is a 2 step process:
988 # reverting a subrepo is a 2 step process:
983 # 1. if the no_backup is not set, revert all modified
989 # 1. if the no_backup is not set, revert all modified
984 # files inside the subrepo
990 # files inside the subrepo
985 # 2. update the subrepo to the revision specified in
991 # 2. update the subrepo to the revision specified in
986 # the corresponding substate dictionary
992 # the corresponding substate dictionary
987 self.ui.status(_('reverting subrepo %s\n') % substate[0])
993 self.ui.status(_('reverting subrepo %s\n') % substate[0])
988 if not opts.get('no_backup'):
994 if not opts.get('no_backup'):
989 # Revert all files on the subrepo, creating backups
995 # Revert all files on the subrepo, creating backups
990 # Note that this will not recursively revert subrepos
996 # Note that this will not recursively revert subrepos
991 # We could do it if there was a set:subrepos() predicate
997 # We could do it if there was a set:subrepos() predicate
992 opts = opts.copy()
998 opts = opts.copy()
993 opts['date'] = None
999 opts['date'] = None
994 opts['rev'] = substate[1]
1000 opts['rev'] = substate[1]
995
1001
996 self.filerevert(*pats, **opts)
1002 self.filerevert(*pats, **opts)
997
1003
998 # Update the repo to the revision specified in the given substate
1004 # Update the repo to the revision specified in the given substate
999 if not opts.get('dry_run'):
1005 if not opts.get('dry_run'):
1000 self.get(substate, overwrite=True)
1006 self.get(substate, overwrite=True)
1001
1007
1002 def filerevert(self, *pats, **opts):
1008 def filerevert(self, *pats, **opts):
1003 ctx = self._repo[opts['rev']]
1009 ctx = self._repo[opts['rev']]
1004 parents = self._repo.dirstate.parents()
1010 parents = self._repo.dirstate.parents()
1005 if opts.get('all'):
1011 if opts.get('all'):
1006 pats = ['set:modified()']
1012 pats = ['set:modified()']
1007 else:
1013 else:
1008 pats = []
1014 pats = []
1009 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1015 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1010
1016
1011 def shortid(self, revid):
1017 def shortid(self, revid):
1012 return revid[:12]
1018 return revid[:12]
1013
1019
1020 def verify(self):
1021 try:
1022 rev = self._state[1]
1023 ctx = self._repo.unfiltered()[rev]
1024 if ctx.hidden():
1025 # Since hidden revisions aren't pushed/pulled, it seems worth an
1026 # explicit warning.
1027 ui = self._repo.ui
1028 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1029 (self._relpath, node.short(self._ctx.node())))
1030 return 0
1031 except error.RepoLookupError:
1032 # A missing subrepo revision may be a case of needing to pull it, so
1033 # don't treat this as an error.
1034 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1035 (self._relpath, node.short(self._ctx.node())))
1036 return 0
1037
1014 @propertycache
1038 @propertycache
1015 def wvfs(self):
1039 def wvfs(self):
1016 """return own wvfs for efficiency and consitency
1040 """return own wvfs for efficiency and consitency
1017 """
1041 """
1018 return self._repo.wvfs
1042 return self._repo.wvfs
1019
1043
1020 @propertycache
1044 @propertycache
1021 def _relpath(self):
1045 def _relpath(self):
1022 """return path to this subrepository as seen from outermost repository
1046 """return path to this subrepository as seen from outermost repository
1023 """
1047 """
1024 # Keep consistent dir separators by avoiding vfs.join(self._path)
1048 # Keep consistent dir separators by avoiding vfs.join(self._path)
1025 return reporelpath(self._repo)
1049 return reporelpath(self._repo)
1026
1050
1027 class svnsubrepo(abstractsubrepo):
1051 class svnsubrepo(abstractsubrepo):
1028 def __init__(self, ctx, path, state):
1052 def __init__(self, ctx, path, state):
1029 super(svnsubrepo, self).__init__(ctx, path)
1053 super(svnsubrepo, self).__init__(ctx, path)
1030 self._state = state
1054 self._state = state
1031 self._exe = util.findexe('svn')
1055 self._exe = util.findexe('svn')
1032 if not self._exe:
1056 if not self._exe:
1033 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
1057 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
1034 % self._path)
1058 % self._path)
1035
1059
1036 def _svncommand(self, commands, filename='', failok=False):
1060 def _svncommand(self, commands, filename='', failok=False):
1037 cmd = [self._exe]
1061 cmd = [self._exe]
1038 extrakw = {}
1062 extrakw = {}
1039 if not self.ui.interactive():
1063 if not self.ui.interactive():
1040 # Making stdin be a pipe should prevent svn from behaving
1064 # Making stdin be a pipe should prevent svn from behaving
1041 # interactively even if we can't pass --non-interactive.
1065 # interactively even if we can't pass --non-interactive.
1042 extrakw['stdin'] = subprocess.PIPE
1066 extrakw['stdin'] = subprocess.PIPE
1043 # Starting in svn 1.5 --non-interactive is a global flag
1067 # Starting in svn 1.5 --non-interactive is a global flag
1044 # instead of being per-command, but we need to support 1.4 so
1068 # instead of being per-command, but we need to support 1.4 so
1045 # we have to be intelligent about what commands take
1069 # we have to be intelligent about what commands take
1046 # --non-interactive.
1070 # --non-interactive.
1047 if commands[0] in ('update', 'checkout', 'commit'):
1071 if commands[0] in ('update', 'checkout', 'commit'):
1048 cmd.append('--non-interactive')
1072 cmd.append('--non-interactive')
1049 cmd.extend(commands)
1073 cmd.extend(commands)
1050 if filename is not None:
1074 if filename is not None:
1051 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1075 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1052 self._path, filename)
1076 self._path, filename)
1053 cmd.append(path)
1077 cmd.append(path)
1054 env = dict(os.environ)
1078 env = dict(os.environ)
1055 # Avoid localized output, preserve current locale for everything else.
1079 # Avoid localized output, preserve current locale for everything else.
1056 lc_all = env.get('LC_ALL')
1080 lc_all = env.get('LC_ALL')
1057 if lc_all:
1081 if lc_all:
1058 env['LANG'] = lc_all
1082 env['LANG'] = lc_all
1059 del env['LC_ALL']
1083 del env['LC_ALL']
1060 env['LC_MESSAGES'] = 'C'
1084 env['LC_MESSAGES'] = 'C'
1061 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1085 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1062 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1086 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1063 universal_newlines=True, env=env, **extrakw)
1087 universal_newlines=True, env=env, **extrakw)
1064 stdout, stderr = p.communicate()
1088 stdout, stderr = p.communicate()
1065 stderr = stderr.strip()
1089 stderr = stderr.strip()
1066 if not failok:
1090 if not failok:
1067 if p.returncode:
1091 if p.returncode:
1068 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
1092 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
1069 if stderr:
1093 if stderr:
1070 self.ui.warn(stderr + '\n')
1094 self.ui.warn(stderr + '\n')
1071 return stdout, stderr
1095 return stdout, stderr
1072
1096
1073 @propertycache
1097 @propertycache
1074 def _svnversion(self):
1098 def _svnversion(self):
1075 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1099 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1076 m = re.search(r'^(\d+)\.(\d+)', output)
1100 m = re.search(r'^(\d+)\.(\d+)', output)
1077 if not m:
1101 if not m:
1078 raise util.Abort(_('cannot retrieve svn tool version'))
1102 raise util.Abort(_('cannot retrieve svn tool version'))
1079 return (int(m.group(1)), int(m.group(2)))
1103 return (int(m.group(1)), int(m.group(2)))
1080
1104
1081 def _wcrevs(self):
1105 def _wcrevs(self):
1082 # Get the working directory revision as well as the last
1106 # Get the working directory revision as well as the last
1083 # commit revision so we can compare the subrepo state with
1107 # commit revision so we can compare the subrepo state with
1084 # both. We used to store the working directory one.
1108 # both. We used to store the working directory one.
1085 output, err = self._svncommand(['info', '--xml'])
1109 output, err = self._svncommand(['info', '--xml'])
1086 doc = xml.dom.minidom.parseString(output)
1110 doc = xml.dom.minidom.parseString(output)
1087 entries = doc.getElementsByTagName('entry')
1111 entries = doc.getElementsByTagName('entry')
1088 lastrev, rev = '0', '0'
1112 lastrev, rev = '0', '0'
1089 if entries:
1113 if entries:
1090 rev = str(entries[0].getAttribute('revision')) or '0'
1114 rev = str(entries[0].getAttribute('revision')) or '0'
1091 commits = entries[0].getElementsByTagName('commit')
1115 commits = entries[0].getElementsByTagName('commit')
1092 if commits:
1116 if commits:
1093 lastrev = str(commits[0].getAttribute('revision')) or '0'
1117 lastrev = str(commits[0].getAttribute('revision')) or '0'
1094 return (lastrev, rev)
1118 return (lastrev, rev)
1095
1119
1096 def _wcrev(self):
1120 def _wcrev(self):
1097 return self._wcrevs()[0]
1121 return self._wcrevs()[0]
1098
1122
1099 def _wcchanged(self):
1123 def _wcchanged(self):
1100 """Return (changes, extchanges, missing) where changes is True
1124 """Return (changes, extchanges, missing) where changes is True
1101 if the working directory was changed, extchanges is
1125 if the working directory was changed, extchanges is
1102 True if any of these changes concern an external entry and missing
1126 True if any of these changes concern an external entry and missing
1103 is True if any change is a missing entry.
1127 is True if any change is a missing entry.
1104 """
1128 """
1105 output, err = self._svncommand(['status', '--xml'])
1129 output, err = self._svncommand(['status', '--xml'])
1106 externals, changes, missing = [], [], []
1130 externals, changes, missing = [], [], []
1107 doc = xml.dom.minidom.parseString(output)
1131 doc = xml.dom.minidom.parseString(output)
1108 for e in doc.getElementsByTagName('entry'):
1132 for e in doc.getElementsByTagName('entry'):
1109 s = e.getElementsByTagName('wc-status')
1133 s = e.getElementsByTagName('wc-status')
1110 if not s:
1134 if not s:
1111 continue
1135 continue
1112 item = s[0].getAttribute('item')
1136 item = s[0].getAttribute('item')
1113 props = s[0].getAttribute('props')
1137 props = s[0].getAttribute('props')
1114 path = e.getAttribute('path')
1138 path = e.getAttribute('path')
1115 if item == 'external':
1139 if item == 'external':
1116 externals.append(path)
1140 externals.append(path)
1117 elif item == 'missing':
1141 elif item == 'missing':
1118 missing.append(path)
1142 missing.append(path)
1119 if (item not in ('', 'normal', 'unversioned', 'external')
1143 if (item not in ('', 'normal', 'unversioned', 'external')
1120 or props not in ('', 'none', 'normal')):
1144 or props not in ('', 'none', 'normal')):
1121 changes.append(path)
1145 changes.append(path)
1122 for path in changes:
1146 for path in changes:
1123 for ext in externals:
1147 for ext in externals:
1124 if path == ext or path.startswith(ext + os.sep):
1148 if path == ext or path.startswith(ext + os.sep):
1125 return True, True, bool(missing)
1149 return True, True, bool(missing)
1126 return bool(changes), False, bool(missing)
1150 return bool(changes), False, bool(missing)
1127
1151
1128 def dirty(self, ignoreupdate=False):
1152 def dirty(self, ignoreupdate=False):
1129 if not self._wcchanged()[0]:
1153 if not self._wcchanged()[0]:
1130 if self._state[1] in self._wcrevs() or ignoreupdate:
1154 if self._state[1] in self._wcrevs() or ignoreupdate:
1131 return False
1155 return False
1132 return True
1156 return True
1133
1157
1134 def basestate(self):
1158 def basestate(self):
1135 lastrev, rev = self._wcrevs()
1159 lastrev, rev = self._wcrevs()
1136 if lastrev != rev:
1160 if lastrev != rev:
1137 # Last committed rev is not the same than rev. We would
1161 # Last committed rev is not the same than rev. We would
1138 # like to take lastrev but we do not know if the subrepo
1162 # like to take lastrev but we do not know if the subrepo
1139 # URL exists at lastrev. Test it and fallback to rev it
1163 # URL exists at lastrev. Test it and fallback to rev it
1140 # is not there.
1164 # is not there.
1141 try:
1165 try:
1142 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1166 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1143 return lastrev
1167 return lastrev
1144 except error.Abort:
1168 except error.Abort:
1145 pass
1169 pass
1146 return rev
1170 return rev
1147
1171
1148 @annotatesubrepoerror
1172 @annotatesubrepoerror
1149 def commit(self, text, user, date):
1173 def commit(self, text, user, date):
1150 # user and date are out of our hands since svn is centralized
1174 # user and date are out of our hands since svn is centralized
1151 changed, extchanged, missing = self._wcchanged()
1175 changed, extchanged, missing = self._wcchanged()
1152 if not changed:
1176 if not changed:
1153 return self.basestate()
1177 return self.basestate()
1154 if extchanged:
1178 if extchanged:
1155 # Do not try to commit externals
1179 # Do not try to commit externals
1156 raise util.Abort(_('cannot commit svn externals'))
1180 raise util.Abort(_('cannot commit svn externals'))
1157 if missing:
1181 if missing:
1158 # svn can commit with missing entries but aborting like hg
1182 # svn can commit with missing entries but aborting like hg
1159 # seems a better approach.
1183 # seems a better approach.
1160 raise util.Abort(_('cannot commit missing svn entries'))
1184 raise util.Abort(_('cannot commit missing svn entries'))
1161 commitinfo, err = self._svncommand(['commit', '-m', text])
1185 commitinfo, err = self._svncommand(['commit', '-m', text])
1162 self.ui.status(commitinfo)
1186 self.ui.status(commitinfo)
1163 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1187 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1164 if not newrev:
1188 if not newrev:
1165 if not commitinfo.strip():
1189 if not commitinfo.strip():
1166 # Sometimes, our definition of "changed" differs from
1190 # Sometimes, our definition of "changed" differs from
1167 # svn one. For instance, svn ignores missing files
1191 # svn one. For instance, svn ignores missing files
1168 # when committing. If there are only missing files, no
1192 # when committing. If there are only missing files, no
1169 # commit is made, no output and no error code.
1193 # commit is made, no output and no error code.
1170 raise util.Abort(_('failed to commit svn changes'))
1194 raise util.Abort(_('failed to commit svn changes'))
1171 raise util.Abort(commitinfo.splitlines()[-1])
1195 raise util.Abort(commitinfo.splitlines()[-1])
1172 newrev = newrev.groups()[0]
1196 newrev = newrev.groups()[0]
1173 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1197 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1174 return newrev
1198 return newrev
1175
1199
1176 @annotatesubrepoerror
1200 @annotatesubrepoerror
1177 def remove(self):
1201 def remove(self):
1178 if self.dirty():
1202 if self.dirty():
1179 self.ui.warn(_('not removing repo %s because '
1203 self.ui.warn(_('not removing repo %s because '
1180 'it has changes.\n') % self._path)
1204 'it has changes.\n') % self._path)
1181 return
1205 return
1182 self.ui.note(_('removing subrepo %s\n') % self._path)
1206 self.ui.note(_('removing subrepo %s\n') % self._path)
1183
1207
1184 self.wvfs.rmtree(forcibly=True)
1208 self.wvfs.rmtree(forcibly=True)
1185 try:
1209 try:
1186 self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path))
1210 self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path))
1187 except OSError:
1211 except OSError:
1188 pass
1212 pass
1189
1213
1190 @annotatesubrepoerror
1214 @annotatesubrepoerror
1191 def get(self, state, overwrite=False):
1215 def get(self, state, overwrite=False):
1192 if overwrite:
1216 if overwrite:
1193 self._svncommand(['revert', '--recursive'])
1217 self._svncommand(['revert', '--recursive'])
1194 args = ['checkout']
1218 args = ['checkout']
1195 if self._svnversion >= (1, 5):
1219 if self._svnversion >= (1, 5):
1196 args.append('--force')
1220 args.append('--force')
1197 # The revision must be specified at the end of the URL to properly
1221 # The revision must be specified at the end of the URL to properly
1198 # update to a directory which has since been deleted and recreated.
1222 # update to a directory which has since been deleted and recreated.
1199 args.append('%s@%s' % (state[0], state[1]))
1223 args.append('%s@%s' % (state[0], state[1]))
1200 status, err = self._svncommand(args, failok=True)
1224 status, err = self._svncommand(args, failok=True)
1201 _sanitize(self.ui, self.wvfs, '.svn')
1225 _sanitize(self.ui, self.wvfs, '.svn')
1202 if not re.search('Checked out revision [0-9]+.', status):
1226 if not re.search('Checked out revision [0-9]+.', status):
1203 if ('is already a working copy for a different URL' in err
1227 if ('is already a working copy for a different URL' in err
1204 and (self._wcchanged()[:2] == (False, False))):
1228 and (self._wcchanged()[:2] == (False, False))):
1205 # obstructed but clean working copy, so just blow it away.
1229 # obstructed but clean working copy, so just blow it away.
1206 self.remove()
1230 self.remove()
1207 self.get(state, overwrite=False)
1231 self.get(state, overwrite=False)
1208 return
1232 return
1209 raise util.Abort((status or err).splitlines()[-1])
1233 raise util.Abort((status or err).splitlines()[-1])
1210 self.ui.status(status)
1234 self.ui.status(status)
1211
1235
1212 @annotatesubrepoerror
1236 @annotatesubrepoerror
1213 def merge(self, state):
1237 def merge(self, state):
1214 old = self._state[1]
1238 old = self._state[1]
1215 new = state[1]
1239 new = state[1]
1216 wcrev = self._wcrev()
1240 wcrev = self._wcrev()
1217 if new != wcrev:
1241 if new != wcrev:
1218 dirty = old == wcrev or self._wcchanged()[0]
1242 dirty = old == wcrev or self._wcchanged()[0]
1219 if _updateprompt(self.ui, self, dirty, wcrev, new):
1243 if _updateprompt(self.ui, self, dirty, wcrev, new):
1220 self.get(state, False)
1244 self.get(state, False)
1221
1245
1222 def push(self, opts):
1246 def push(self, opts):
1223 # push is a no-op for SVN
1247 # push is a no-op for SVN
1224 return True
1248 return True
1225
1249
1226 @annotatesubrepoerror
1250 @annotatesubrepoerror
1227 def files(self):
1251 def files(self):
1228 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1252 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1229 doc = xml.dom.minidom.parseString(output)
1253 doc = xml.dom.minidom.parseString(output)
1230 paths = []
1254 paths = []
1231 for e in doc.getElementsByTagName('entry'):
1255 for e in doc.getElementsByTagName('entry'):
1232 kind = str(e.getAttribute('kind'))
1256 kind = str(e.getAttribute('kind'))
1233 if kind != 'file':
1257 if kind != 'file':
1234 continue
1258 continue
1235 name = ''.join(c.data for c
1259 name = ''.join(c.data for c
1236 in e.getElementsByTagName('name')[0].childNodes
1260 in e.getElementsByTagName('name')[0].childNodes
1237 if c.nodeType == c.TEXT_NODE)
1261 if c.nodeType == c.TEXT_NODE)
1238 paths.append(name.encode('utf-8'))
1262 paths.append(name.encode('utf-8'))
1239 return paths
1263 return paths
1240
1264
1241 def filedata(self, name):
1265 def filedata(self, name):
1242 return self._svncommand(['cat'], name)[0]
1266 return self._svncommand(['cat'], name)[0]
1243
1267
1244
1268
1245 class gitsubrepo(abstractsubrepo):
1269 class gitsubrepo(abstractsubrepo):
1246 def __init__(self, ctx, path, state):
1270 def __init__(self, ctx, path, state):
1247 super(gitsubrepo, self).__init__(ctx, path)
1271 super(gitsubrepo, self).__init__(ctx, path)
1248 self._state = state
1272 self._state = state
1249 self._abspath = ctx.repo().wjoin(path)
1273 self._abspath = ctx.repo().wjoin(path)
1250 self._subparent = ctx.repo()
1274 self._subparent = ctx.repo()
1251 self._ensuregit()
1275 self._ensuregit()
1252
1276
1253 def _ensuregit(self):
1277 def _ensuregit(self):
1254 try:
1278 try:
1255 self._gitexecutable = 'git'
1279 self._gitexecutable = 'git'
1256 out, err = self._gitnodir(['--version'])
1280 out, err = self._gitnodir(['--version'])
1257 except OSError, e:
1281 except OSError, e:
1258 if e.errno != 2 or os.name != 'nt':
1282 if e.errno != 2 or os.name != 'nt':
1259 raise
1283 raise
1260 self._gitexecutable = 'git.cmd'
1284 self._gitexecutable = 'git.cmd'
1261 out, err = self._gitnodir(['--version'])
1285 out, err = self._gitnodir(['--version'])
1262 versionstatus = self._checkversion(out)
1286 versionstatus = self._checkversion(out)
1263 if versionstatus == 'unknown':
1287 if versionstatus == 'unknown':
1264 self.ui.warn(_('cannot retrieve git version\n'))
1288 self.ui.warn(_('cannot retrieve git version\n'))
1265 elif versionstatus == 'abort':
1289 elif versionstatus == 'abort':
1266 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1290 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1267 elif versionstatus == 'warning':
1291 elif versionstatus == 'warning':
1268 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1292 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1269
1293
1270 @staticmethod
1294 @staticmethod
1271 def _gitversion(out):
1295 def _gitversion(out):
1272 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1296 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1273 if m:
1297 if m:
1274 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1298 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1275
1299
1276 m = re.search(r'^git version (\d+)\.(\d+)', out)
1300 m = re.search(r'^git version (\d+)\.(\d+)', out)
1277 if m:
1301 if m:
1278 return (int(m.group(1)), int(m.group(2)), 0)
1302 return (int(m.group(1)), int(m.group(2)), 0)
1279
1303
1280 return -1
1304 return -1
1281
1305
1282 @staticmethod
1306 @staticmethod
1283 def _checkversion(out):
1307 def _checkversion(out):
1284 '''ensure git version is new enough
1308 '''ensure git version is new enough
1285
1309
1286 >>> _checkversion = gitsubrepo._checkversion
1310 >>> _checkversion = gitsubrepo._checkversion
1287 >>> _checkversion('git version 1.6.0')
1311 >>> _checkversion('git version 1.6.0')
1288 'ok'
1312 'ok'
1289 >>> _checkversion('git version 1.8.5')
1313 >>> _checkversion('git version 1.8.5')
1290 'ok'
1314 'ok'
1291 >>> _checkversion('git version 1.4.0')
1315 >>> _checkversion('git version 1.4.0')
1292 'abort'
1316 'abort'
1293 >>> _checkversion('git version 1.5.0')
1317 >>> _checkversion('git version 1.5.0')
1294 'warning'
1318 'warning'
1295 >>> _checkversion('git version 1.9-rc0')
1319 >>> _checkversion('git version 1.9-rc0')
1296 'ok'
1320 'ok'
1297 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1321 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1298 'ok'
1322 'ok'
1299 >>> _checkversion('git version 1.9.0.GIT')
1323 >>> _checkversion('git version 1.9.0.GIT')
1300 'ok'
1324 'ok'
1301 >>> _checkversion('git version 12345')
1325 >>> _checkversion('git version 12345')
1302 'unknown'
1326 'unknown'
1303 >>> _checkversion('no')
1327 >>> _checkversion('no')
1304 'unknown'
1328 'unknown'
1305 '''
1329 '''
1306 version = gitsubrepo._gitversion(out)
1330 version = gitsubrepo._gitversion(out)
1307 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1331 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1308 # despite the docstring comment. For now, error on 1.4.0, warn on
1332 # despite the docstring comment. For now, error on 1.4.0, warn on
1309 # 1.5.0 but attempt to continue.
1333 # 1.5.0 but attempt to continue.
1310 if version == -1:
1334 if version == -1:
1311 return 'unknown'
1335 return 'unknown'
1312 if version < (1, 5, 0):
1336 if version < (1, 5, 0):
1313 return 'abort'
1337 return 'abort'
1314 elif version < (1, 6, 0):
1338 elif version < (1, 6, 0):
1315 return 'warning'
1339 return 'warning'
1316 return 'ok'
1340 return 'ok'
1317
1341
1318 def _gitcommand(self, commands, env=None, stream=False):
1342 def _gitcommand(self, commands, env=None, stream=False):
1319 return self._gitdir(commands, env=env, stream=stream)[0]
1343 return self._gitdir(commands, env=env, stream=stream)[0]
1320
1344
1321 def _gitdir(self, commands, env=None, stream=False):
1345 def _gitdir(self, commands, env=None, stream=False):
1322 return self._gitnodir(commands, env=env, stream=stream,
1346 return self._gitnodir(commands, env=env, stream=stream,
1323 cwd=self._abspath)
1347 cwd=self._abspath)
1324
1348
1325 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1349 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1326 """Calls the git command
1350 """Calls the git command
1327
1351
1328 The methods tries to call the git command. versions prior to 1.6.0
1352 The methods tries to call the git command. versions prior to 1.6.0
1329 are not supported and very probably fail.
1353 are not supported and very probably fail.
1330 """
1354 """
1331 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1355 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1332 # unless ui.quiet is set, print git's stderr,
1356 # unless ui.quiet is set, print git's stderr,
1333 # which is mostly progress and useful info
1357 # which is mostly progress and useful info
1334 errpipe = None
1358 errpipe = None
1335 if self.ui.quiet:
1359 if self.ui.quiet:
1336 errpipe = open(os.devnull, 'w')
1360 errpipe = open(os.devnull, 'w')
1337 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1361 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1338 cwd=cwd, env=env, close_fds=util.closefds,
1362 cwd=cwd, env=env, close_fds=util.closefds,
1339 stdout=subprocess.PIPE, stderr=errpipe)
1363 stdout=subprocess.PIPE, stderr=errpipe)
1340 if stream:
1364 if stream:
1341 return p.stdout, None
1365 return p.stdout, None
1342
1366
1343 retdata = p.stdout.read().strip()
1367 retdata = p.stdout.read().strip()
1344 # wait for the child to exit to avoid race condition.
1368 # wait for the child to exit to avoid race condition.
1345 p.wait()
1369 p.wait()
1346
1370
1347 if p.returncode != 0 and p.returncode != 1:
1371 if p.returncode != 0 and p.returncode != 1:
1348 # there are certain error codes that are ok
1372 # there are certain error codes that are ok
1349 command = commands[0]
1373 command = commands[0]
1350 if command in ('cat-file', 'symbolic-ref'):
1374 if command in ('cat-file', 'symbolic-ref'):
1351 return retdata, p.returncode
1375 return retdata, p.returncode
1352 # for all others, abort
1376 # for all others, abort
1353 raise util.Abort('git %s error %d in %s' %
1377 raise util.Abort('git %s error %d in %s' %
1354 (command, p.returncode, self._relpath))
1378 (command, p.returncode, self._relpath))
1355
1379
1356 return retdata, p.returncode
1380 return retdata, p.returncode
1357
1381
1358 def _gitmissing(self):
1382 def _gitmissing(self):
1359 return not self.wvfs.exists('.git')
1383 return not self.wvfs.exists('.git')
1360
1384
1361 def _gitstate(self):
1385 def _gitstate(self):
1362 return self._gitcommand(['rev-parse', 'HEAD'])
1386 return self._gitcommand(['rev-parse', 'HEAD'])
1363
1387
1364 def _gitcurrentbranch(self):
1388 def _gitcurrentbranch(self):
1365 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1389 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1366 if err:
1390 if err:
1367 current = None
1391 current = None
1368 return current
1392 return current
1369
1393
1370 def _gitremote(self, remote):
1394 def _gitremote(self, remote):
1371 out = self._gitcommand(['remote', 'show', '-n', remote])
1395 out = self._gitcommand(['remote', 'show', '-n', remote])
1372 line = out.split('\n')[1]
1396 line = out.split('\n')[1]
1373 i = line.index('URL: ') + len('URL: ')
1397 i = line.index('URL: ') + len('URL: ')
1374 return line[i:]
1398 return line[i:]
1375
1399
1376 def _githavelocally(self, revision):
1400 def _githavelocally(self, revision):
1377 out, code = self._gitdir(['cat-file', '-e', revision])
1401 out, code = self._gitdir(['cat-file', '-e', revision])
1378 return code == 0
1402 return code == 0
1379
1403
1380 def _gitisancestor(self, r1, r2):
1404 def _gitisancestor(self, r1, r2):
1381 base = self._gitcommand(['merge-base', r1, r2])
1405 base = self._gitcommand(['merge-base', r1, r2])
1382 return base == r1
1406 return base == r1
1383
1407
1384 def _gitisbare(self):
1408 def _gitisbare(self):
1385 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1409 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1386
1410
1387 def _gitupdatestat(self):
1411 def _gitupdatestat(self):
1388 """This must be run before git diff-index.
1412 """This must be run before git diff-index.
1389 diff-index only looks at changes to file stat;
1413 diff-index only looks at changes to file stat;
1390 this command looks at file contents and updates the stat."""
1414 this command looks at file contents and updates the stat."""
1391 self._gitcommand(['update-index', '-q', '--refresh'])
1415 self._gitcommand(['update-index', '-q', '--refresh'])
1392
1416
1393 def _gitbranchmap(self):
1417 def _gitbranchmap(self):
1394 '''returns 2 things:
1418 '''returns 2 things:
1395 a map from git branch to revision
1419 a map from git branch to revision
1396 a map from revision to branches'''
1420 a map from revision to branches'''
1397 branch2rev = {}
1421 branch2rev = {}
1398 rev2branch = {}
1422 rev2branch = {}
1399
1423
1400 out = self._gitcommand(['for-each-ref', '--format',
1424 out = self._gitcommand(['for-each-ref', '--format',
1401 '%(objectname) %(refname)'])
1425 '%(objectname) %(refname)'])
1402 for line in out.split('\n'):
1426 for line in out.split('\n'):
1403 revision, ref = line.split(' ')
1427 revision, ref = line.split(' ')
1404 if (not ref.startswith('refs/heads/') and
1428 if (not ref.startswith('refs/heads/') and
1405 not ref.startswith('refs/remotes/')):
1429 not ref.startswith('refs/remotes/')):
1406 continue
1430 continue
1407 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1431 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1408 continue # ignore remote/HEAD redirects
1432 continue # ignore remote/HEAD redirects
1409 branch2rev[ref] = revision
1433 branch2rev[ref] = revision
1410 rev2branch.setdefault(revision, []).append(ref)
1434 rev2branch.setdefault(revision, []).append(ref)
1411 return branch2rev, rev2branch
1435 return branch2rev, rev2branch
1412
1436
1413 def _gittracking(self, branches):
1437 def _gittracking(self, branches):
1414 'return map of remote branch to local tracking branch'
1438 'return map of remote branch to local tracking branch'
1415 # assumes no more than one local tracking branch for each remote
1439 # assumes no more than one local tracking branch for each remote
1416 tracking = {}
1440 tracking = {}
1417 for b in branches:
1441 for b in branches:
1418 if b.startswith('refs/remotes/'):
1442 if b.startswith('refs/remotes/'):
1419 continue
1443 continue
1420 bname = b.split('/', 2)[2]
1444 bname = b.split('/', 2)[2]
1421 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1445 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1422 if remote:
1446 if remote:
1423 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1447 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1424 tracking['refs/remotes/%s/%s' %
1448 tracking['refs/remotes/%s/%s' %
1425 (remote, ref.split('/', 2)[2])] = b
1449 (remote, ref.split('/', 2)[2])] = b
1426 return tracking
1450 return tracking
1427
1451
1428 def _abssource(self, source):
1452 def _abssource(self, source):
1429 if '://' not in source:
1453 if '://' not in source:
1430 # recognize the scp syntax as an absolute source
1454 # recognize the scp syntax as an absolute source
1431 colon = source.find(':')
1455 colon = source.find(':')
1432 if colon != -1 and '/' not in source[:colon]:
1456 if colon != -1 and '/' not in source[:colon]:
1433 return source
1457 return source
1434 self._subsource = source
1458 self._subsource = source
1435 return _abssource(self)
1459 return _abssource(self)
1436
1460
1437 def _fetch(self, source, revision):
1461 def _fetch(self, source, revision):
1438 if self._gitmissing():
1462 if self._gitmissing():
1439 source = self._abssource(source)
1463 source = self._abssource(source)
1440 self.ui.status(_('cloning subrepo %s from %s\n') %
1464 self.ui.status(_('cloning subrepo %s from %s\n') %
1441 (self._relpath, source))
1465 (self._relpath, source))
1442 self._gitnodir(['clone', source, self._abspath])
1466 self._gitnodir(['clone', source, self._abspath])
1443 if self._githavelocally(revision):
1467 if self._githavelocally(revision):
1444 return
1468 return
1445 self.ui.status(_('pulling subrepo %s from %s\n') %
1469 self.ui.status(_('pulling subrepo %s from %s\n') %
1446 (self._relpath, self._gitremote('origin')))
1470 (self._relpath, self._gitremote('origin')))
1447 # try only origin: the originally cloned repo
1471 # try only origin: the originally cloned repo
1448 self._gitcommand(['fetch'])
1472 self._gitcommand(['fetch'])
1449 if not self._githavelocally(revision):
1473 if not self._githavelocally(revision):
1450 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1474 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1451 (revision, self._relpath))
1475 (revision, self._relpath))
1452
1476
1453 @annotatesubrepoerror
1477 @annotatesubrepoerror
1454 def dirty(self, ignoreupdate=False):
1478 def dirty(self, ignoreupdate=False):
1455 if self._gitmissing():
1479 if self._gitmissing():
1456 return self._state[1] != ''
1480 return self._state[1] != ''
1457 if self._gitisbare():
1481 if self._gitisbare():
1458 return True
1482 return True
1459 if not ignoreupdate and self._state[1] != self._gitstate():
1483 if not ignoreupdate and self._state[1] != self._gitstate():
1460 # different version checked out
1484 # different version checked out
1461 return True
1485 return True
1462 # check for staged changes or modified files; ignore untracked files
1486 # check for staged changes or modified files; ignore untracked files
1463 self._gitupdatestat()
1487 self._gitupdatestat()
1464 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1488 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1465 return code == 1
1489 return code == 1
1466
1490
1467 def basestate(self):
1491 def basestate(self):
1468 return self._gitstate()
1492 return self._gitstate()
1469
1493
1470 @annotatesubrepoerror
1494 @annotatesubrepoerror
1471 def get(self, state, overwrite=False):
1495 def get(self, state, overwrite=False):
1472 source, revision, kind = state
1496 source, revision, kind = state
1473 if not revision:
1497 if not revision:
1474 self.remove()
1498 self.remove()
1475 return
1499 return
1476 self._fetch(source, revision)
1500 self._fetch(source, revision)
1477 # if the repo was set to be bare, unbare it
1501 # if the repo was set to be bare, unbare it
1478 if self._gitisbare():
1502 if self._gitisbare():
1479 self._gitcommand(['config', 'core.bare', 'false'])
1503 self._gitcommand(['config', 'core.bare', 'false'])
1480 if self._gitstate() == revision:
1504 if self._gitstate() == revision:
1481 self._gitcommand(['reset', '--hard', 'HEAD'])
1505 self._gitcommand(['reset', '--hard', 'HEAD'])
1482 return
1506 return
1483 elif self._gitstate() == revision:
1507 elif self._gitstate() == revision:
1484 if overwrite:
1508 if overwrite:
1485 # first reset the index to unmark new files for commit, because
1509 # first reset the index to unmark new files for commit, because
1486 # reset --hard will otherwise throw away files added for commit,
1510 # reset --hard will otherwise throw away files added for commit,
1487 # not just unmark them.
1511 # not just unmark them.
1488 self._gitcommand(['reset', 'HEAD'])
1512 self._gitcommand(['reset', 'HEAD'])
1489 self._gitcommand(['reset', '--hard', 'HEAD'])
1513 self._gitcommand(['reset', '--hard', 'HEAD'])
1490 return
1514 return
1491 branch2rev, rev2branch = self._gitbranchmap()
1515 branch2rev, rev2branch = self._gitbranchmap()
1492
1516
1493 def checkout(args):
1517 def checkout(args):
1494 cmd = ['checkout']
1518 cmd = ['checkout']
1495 if overwrite:
1519 if overwrite:
1496 # first reset the index to unmark new files for commit, because
1520 # first reset the index to unmark new files for commit, because
1497 # the -f option will otherwise throw away files added for
1521 # the -f option will otherwise throw away files added for
1498 # commit, not just unmark them.
1522 # commit, not just unmark them.
1499 self._gitcommand(['reset', 'HEAD'])
1523 self._gitcommand(['reset', 'HEAD'])
1500 cmd.append('-f')
1524 cmd.append('-f')
1501 self._gitcommand(cmd + args)
1525 self._gitcommand(cmd + args)
1502 _sanitize(self.ui, self.wvfs, '.git')
1526 _sanitize(self.ui, self.wvfs, '.git')
1503
1527
1504 def rawcheckout():
1528 def rawcheckout():
1505 # no branch to checkout, check it out with no branch
1529 # no branch to checkout, check it out with no branch
1506 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1530 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1507 self._relpath)
1531 self._relpath)
1508 self.ui.warn(_('check out a git branch if you intend '
1532 self.ui.warn(_('check out a git branch if you intend '
1509 'to make changes\n'))
1533 'to make changes\n'))
1510 checkout(['-q', revision])
1534 checkout(['-q', revision])
1511
1535
1512 if revision not in rev2branch:
1536 if revision not in rev2branch:
1513 rawcheckout()
1537 rawcheckout()
1514 return
1538 return
1515 branches = rev2branch[revision]
1539 branches = rev2branch[revision]
1516 firstlocalbranch = None
1540 firstlocalbranch = None
1517 for b in branches:
1541 for b in branches:
1518 if b == 'refs/heads/master':
1542 if b == 'refs/heads/master':
1519 # master trumps all other branches
1543 # master trumps all other branches
1520 checkout(['refs/heads/master'])
1544 checkout(['refs/heads/master'])
1521 return
1545 return
1522 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1546 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1523 firstlocalbranch = b
1547 firstlocalbranch = b
1524 if firstlocalbranch:
1548 if firstlocalbranch:
1525 checkout([firstlocalbranch])
1549 checkout([firstlocalbranch])
1526 return
1550 return
1527
1551
1528 tracking = self._gittracking(branch2rev.keys())
1552 tracking = self._gittracking(branch2rev.keys())
1529 # choose a remote branch already tracked if possible
1553 # choose a remote branch already tracked if possible
1530 remote = branches[0]
1554 remote = branches[0]
1531 if remote not in tracking:
1555 if remote not in tracking:
1532 for b in branches:
1556 for b in branches:
1533 if b in tracking:
1557 if b in tracking:
1534 remote = b
1558 remote = b
1535 break
1559 break
1536
1560
1537 if remote not in tracking:
1561 if remote not in tracking:
1538 # create a new local tracking branch
1562 # create a new local tracking branch
1539 local = remote.split('/', 3)[3]
1563 local = remote.split('/', 3)[3]
1540 checkout(['-b', local, remote])
1564 checkout(['-b', local, remote])
1541 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1565 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1542 # When updating to a tracked remote branch,
1566 # When updating to a tracked remote branch,
1543 # if the local tracking branch is downstream of it,
1567 # if the local tracking branch is downstream of it,
1544 # a normal `git pull` would have performed a "fast-forward merge"
1568 # a normal `git pull` would have performed a "fast-forward merge"
1545 # which is equivalent to updating the local branch to the remote.
1569 # which is equivalent to updating the local branch to the remote.
1546 # Since we are only looking at branching at update, we need to
1570 # Since we are only looking at branching at update, we need to
1547 # detect this situation and perform this action lazily.
1571 # detect this situation and perform this action lazily.
1548 if tracking[remote] != self._gitcurrentbranch():
1572 if tracking[remote] != self._gitcurrentbranch():
1549 checkout([tracking[remote]])
1573 checkout([tracking[remote]])
1550 self._gitcommand(['merge', '--ff', remote])
1574 self._gitcommand(['merge', '--ff', remote])
1551 _sanitize(self.ui, self.wvfs, '.git')
1575 _sanitize(self.ui, self.wvfs, '.git')
1552 else:
1576 else:
1553 # a real merge would be required, just checkout the revision
1577 # a real merge would be required, just checkout the revision
1554 rawcheckout()
1578 rawcheckout()
1555
1579
1556 @annotatesubrepoerror
1580 @annotatesubrepoerror
1557 def commit(self, text, user, date):
1581 def commit(self, text, user, date):
1558 if self._gitmissing():
1582 if self._gitmissing():
1559 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1583 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1560 cmd = ['commit', '-a', '-m', text]
1584 cmd = ['commit', '-a', '-m', text]
1561 env = os.environ.copy()
1585 env = os.environ.copy()
1562 if user:
1586 if user:
1563 cmd += ['--author', user]
1587 cmd += ['--author', user]
1564 if date:
1588 if date:
1565 # git's date parser silently ignores when seconds < 1e9
1589 # git's date parser silently ignores when seconds < 1e9
1566 # convert to ISO8601
1590 # convert to ISO8601
1567 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1591 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1568 '%Y-%m-%dT%H:%M:%S %1%2')
1592 '%Y-%m-%dT%H:%M:%S %1%2')
1569 self._gitcommand(cmd, env=env)
1593 self._gitcommand(cmd, env=env)
1570 # make sure commit works otherwise HEAD might not exist under certain
1594 # make sure commit works otherwise HEAD might not exist under certain
1571 # circumstances
1595 # circumstances
1572 return self._gitstate()
1596 return self._gitstate()
1573
1597
1574 @annotatesubrepoerror
1598 @annotatesubrepoerror
1575 def merge(self, state):
1599 def merge(self, state):
1576 source, revision, kind = state
1600 source, revision, kind = state
1577 self._fetch(source, revision)
1601 self._fetch(source, revision)
1578 base = self._gitcommand(['merge-base', revision, self._state[1]])
1602 base = self._gitcommand(['merge-base', revision, self._state[1]])
1579 self._gitupdatestat()
1603 self._gitupdatestat()
1580 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1604 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1581
1605
1582 def mergefunc():
1606 def mergefunc():
1583 if base == revision:
1607 if base == revision:
1584 self.get(state) # fast forward merge
1608 self.get(state) # fast forward merge
1585 elif base != self._state[1]:
1609 elif base != self._state[1]:
1586 self._gitcommand(['merge', '--no-commit', revision])
1610 self._gitcommand(['merge', '--no-commit', revision])
1587 _sanitize(self.ui, self.wvfs, '.git')
1611 _sanitize(self.ui, self.wvfs, '.git')
1588
1612
1589 if self.dirty():
1613 if self.dirty():
1590 if self._gitstate() != revision:
1614 if self._gitstate() != revision:
1591 dirty = self._gitstate() == self._state[1] or code != 0
1615 dirty = self._gitstate() == self._state[1] or code != 0
1592 if _updateprompt(self.ui, self, dirty,
1616 if _updateprompt(self.ui, self, dirty,
1593 self._state[1][:7], revision[:7]):
1617 self._state[1][:7], revision[:7]):
1594 mergefunc()
1618 mergefunc()
1595 else:
1619 else:
1596 mergefunc()
1620 mergefunc()
1597
1621
1598 @annotatesubrepoerror
1622 @annotatesubrepoerror
1599 def push(self, opts):
1623 def push(self, opts):
1600 force = opts.get('force')
1624 force = opts.get('force')
1601
1625
1602 if not self._state[1]:
1626 if not self._state[1]:
1603 return True
1627 return True
1604 if self._gitmissing():
1628 if self._gitmissing():
1605 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1629 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1606 # if a branch in origin contains the revision, nothing to do
1630 # if a branch in origin contains the revision, nothing to do
1607 branch2rev, rev2branch = self._gitbranchmap()
1631 branch2rev, rev2branch = self._gitbranchmap()
1608 if self._state[1] in rev2branch:
1632 if self._state[1] in rev2branch:
1609 for b in rev2branch[self._state[1]]:
1633 for b in rev2branch[self._state[1]]:
1610 if b.startswith('refs/remotes/origin/'):
1634 if b.startswith('refs/remotes/origin/'):
1611 return True
1635 return True
1612 for b, revision in branch2rev.iteritems():
1636 for b, revision in branch2rev.iteritems():
1613 if b.startswith('refs/remotes/origin/'):
1637 if b.startswith('refs/remotes/origin/'):
1614 if self._gitisancestor(self._state[1], revision):
1638 if self._gitisancestor(self._state[1], revision):
1615 return True
1639 return True
1616 # otherwise, try to push the currently checked out branch
1640 # otherwise, try to push the currently checked out branch
1617 cmd = ['push']
1641 cmd = ['push']
1618 if force:
1642 if force:
1619 cmd.append('--force')
1643 cmd.append('--force')
1620
1644
1621 current = self._gitcurrentbranch()
1645 current = self._gitcurrentbranch()
1622 if current:
1646 if current:
1623 # determine if the current branch is even useful
1647 # determine if the current branch is even useful
1624 if not self._gitisancestor(self._state[1], current):
1648 if not self._gitisancestor(self._state[1], current):
1625 self.ui.warn(_('unrelated git branch checked out '
1649 self.ui.warn(_('unrelated git branch checked out '
1626 'in subrepo %s\n') % self._relpath)
1650 'in subrepo %s\n') % self._relpath)
1627 return False
1651 return False
1628 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1652 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1629 (current.split('/', 2)[2], self._relpath))
1653 (current.split('/', 2)[2], self._relpath))
1630 ret = self._gitdir(cmd + ['origin', current])
1654 ret = self._gitdir(cmd + ['origin', current])
1631 return ret[1] == 0
1655 return ret[1] == 0
1632 else:
1656 else:
1633 self.ui.warn(_('no branch checked out in subrepo %s\n'
1657 self.ui.warn(_('no branch checked out in subrepo %s\n'
1634 'cannot push revision %s\n') %
1658 'cannot push revision %s\n') %
1635 (self._relpath, self._state[1]))
1659 (self._relpath, self._state[1]))
1636 return False
1660 return False
1637
1661
1638 @annotatesubrepoerror
1662 @annotatesubrepoerror
1639 def add(self, ui, match, prefix, explicitonly, **opts):
1663 def add(self, ui, match, prefix, explicitonly, **opts):
1640 if self._gitmissing():
1664 if self._gitmissing():
1641 return []
1665 return []
1642
1666
1643 (modified, added, removed,
1667 (modified, added, removed,
1644 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1668 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1645 clean=True)
1669 clean=True)
1646
1670
1647 tracked = set()
1671 tracked = set()
1648 # dirstates 'amn' warn, 'r' is added again
1672 # dirstates 'amn' warn, 'r' is added again
1649 for l in (modified, added, deleted, clean):
1673 for l in (modified, added, deleted, clean):
1650 tracked.update(l)
1674 tracked.update(l)
1651
1675
1652 # Unknown files not of interest will be rejected by the matcher
1676 # Unknown files not of interest will be rejected by the matcher
1653 files = unknown
1677 files = unknown
1654 files.extend(match.files())
1678 files.extend(match.files())
1655
1679
1656 rejected = []
1680 rejected = []
1657
1681
1658 files = [f for f in sorted(set(files)) if match(f)]
1682 files = [f for f in sorted(set(files)) if match(f)]
1659 for f in files:
1683 for f in files:
1660 exact = match.exact(f)
1684 exact = match.exact(f)
1661 command = ["add"]
1685 command = ["add"]
1662 if exact:
1686 if exact:
1663 command.append("-f") #should be added, even if ignored
1687 command.append("-f") #should be added, even if ignored
1664 if ui.verbose or not exact:
1688 if ui.verbose or not exact:
1665 ui.status(_('adding %s\n') % match.rel(f))
1689 ui.status(_('adding %s\n') % match.rel(f))
1666
1690
1667 if f in tracked: # hg prints 'adding' even if already tracked
1691 if f in tracked: # hg prints 'adding' even if already tracked
1668 if exact:
1692 if exact:
1669 rejected.append(f)
1693 rejected.append(f)
1670 continue
1694 continue
1671 if not opts.get('dry_run'):
1695 if not opts.get('dry_run'):
1672 self._gitcommand(command + [f])
1696 self._gitcommand(command + [f])
1673
1697
1674 for f in rejected:
1698 for f in rejected:
1675 ui.warn(_("%s already tracked!\n") % match.abs(f))
1699 ui.warn(_("%s already tracked!\n") % match.abs(f))
1676
1700
1677 return rejected
1701 return rejected
1678
1702
1679 @annotatesubrepoerror
1703 @annotatesubrepoerror
1680 def remove(self):
1704 def remove(self):
1681 if self._gitmissing():
1705 if self._gitmissing():
1682 return
1706 return
1683 if self.dirty():
1707 if self.dirty():
1684 self.ui.warn(_('not removing repo %s because '
1708 self.ui.warn(_('not removing repo %s because '
1685 'it has changes.\n') % self._relpath)
1709 'it has changes.\n') % self._relpath)
1686 return
1710 return
1687 # we can't fully delete the repository as it may contain
1711 # we can't fully delete the repository as it may contain
1688 # local-only history
1712 # local-only history
1689 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1713 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1690 self._gitcommand(['config', 'core.bare', 'true'])
1714 self._gitcommand(['config', 'core.bare', 'true'])
1691 for f, kind in self.wvfs.readdir():
1715 for f, kind in self.wvfs.readdir():
1692 if f == '.git':
1716 if f == '.git':
1693 continue
1717 continue
1694 if kind == stat.S_IFDIR:
1718 if kind == stat.S_IFDIR:
1695 self.wvfs.rmtree(f)
1719 self.wvfs.rmtree(f)
1696 else:
1720 else:
1697 self.wvfs.unlink(f)
1721 self.wvfs.unlink(f)
1698
1722
1699 def archive(self, archiver, prefix, match=None):
1723 def archive(self, archiver, prefix, match=None):
1700 total = 0
1724 total = 0
1701 source, revision = self._state
1725 source, revision = self._state
1702 if not revision:
1726 if not revision:
1703 return total
1727 return total
1704 self._fetch(source, revision)
1728 self._fetch(source, revision)
1705
1729
1706 # Parse git's native archive command.
1730 # Parse git's native archive command.
1707 # This should be much faster than manually traversing the trees
1731 # This should be much faster than manually traversing the trees
1708 # and objects with many subprocess calls.
1732 # and objects with many subprocess calls.
1709 tarstream = self._gitcommand(['archive', revision], stream=True)
1733 tarstream = self._gitcommand(['archive', revision], stream=True)
1710 tar = tarfile.open(fileobj=tarstream, mode='r|')
1734 tar = tarfile.open(fileobj=tarstream, mode='r|')
1711 relpath = subrelpath(self)
1735 relpath = subrelpath(self)
1712 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1736 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1713 for i, info in enumerate(tar):
1737 for i, info in enumerate(tar):
1714 if info.isdir():
1738 if info.isdir():
1715 continue
1739 continue
1716 if match and not match(info.name):
1740 if match and not match(info.name):
1717 continue
1741 continue
1718 if info.issym():
1742 if info.issym():
1719 data = info.linkname
1743 data = info.linkname
1720 else:
1744 else:
1721 data = tar.extractfile(info).read()
1745 data = tar.extractfile(info).read()
1722 archiver.addfile(prefix + self._path + '/' + info.name,
1746 archiver.addfile(prefix + self._path + '/' + info.name,
1723 info.mode, info.issym(), data)
1747 info.mode, info.issym(), data)
1724 total += 1
1748 total += 1
1725 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1749 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1726 unit=_('files'))
1750 unit=_('files'))
1727 self.ui.progress(_('archiving (%s)') % relpath, None)
1751 self.ui.progress(_('archiving (%s)') % relpath, None)
1728 return total
1752 return total
1729
1753
1730
1754
1731 @annotatesubrepoerror
1755 @annotatesubrepoerror
1732 def cat(self, match, prefix, **opts):
1756 def cat(self, match, prefix, **opts):
1733 rev = self._state[1]
1757 rev = self._state[1]
1734 if match.anypats():
1758 if match.anypats():
1735 return 1 #No support for include/exclude yet
1759 return 1 #No support for include/exclude yet
1736
1760
1737 if not match.files():
1761 if not match.files():
1738 return 1
1762 return 1
1739
1763
1740 for f in match.files():
1764 for f in match.files():
1741 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1765 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1742 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1766 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1743 self._ctx.node(),
1767 self._ctx.node(),
1744 pathname=self.wvfs.reljoin(prefix, f))
1768 pathname=self.wvfs.reljoin(prefix, f))
1745 fp.write(output)
1769 fp.write(output)
1746 fp.close()
1770 fp.close()
1747 return 0
1771 return 0
1748
1772
1749
1773
1750 @annotatesubrepoerror
1774 @annotatesubrepoerror
1751 def status(self, rev2, **opts):
1775 def status(self, rev2, **opts):
1752 rev1 = self._state[1]
1776 rev1 = self._state[1]
1753 if self._gitmissing() or not rev1:
1777 if self._gitmissing() or not rev1:
1754 # if the repo is missing, return no results
1778 # if the repo is missing, return no results
1755 return scmutil.status([], [], [], [], [], [], [])
1779 return scmutil.status([], [], [], [], [], [], [])
1756 modified, added, removed = [], [], []
1780 modified, added, removed = [], [], []
1757 self._gitupdatestat()
1781 self._gitupdatestat()
1758 if rev2:
1782 if rev2:
1759 command = ['diff-tree', '-r', rev1, rev2]
1783 command = ['diff-tree', '-r', rev1, rev2]
1760 else:
1784 else:
1761 command = ['diff-index', rev1]
1785 command = ['diff-index', rev1]
1762 out = self._gitcommand(command)
1786 out = self._gitcommand(command)
1763 for line in out.split('\n'):
1787 for line in out.split('\n'):
1764 tab = line.find('\t')
1788 tab = line.find('\t')
1765 if tab == -1:
1789 if tab == -1:
1766 continue
1790 continue
1767 status, f = line[tab - 1], line[tab + 1:]
1791 status, f = line[tab - 1], line[tab + 1:]
1768 if status == 'M':
1792 if status == 'M':
1769 modified.append(f)
1793 modified.append(f)
1770 elif status == 'A':
1794 elif status == 'A':
1771 added.append(f)
1795 added.append(f)
1772 elif status == 'D':
1796 elif status == 'D':
1773 removed.append(f)
1797 removed.append(f)
1774
1798
1775 deleted, unknown, ignored, clean = [], [], [], []
1799 deleted, unknown, ignored, clean = [], [], [], []
1776
1800
1777 command = ['status', '--porcelain', '-z']
1801 command = ['status', '--porcelain', '-z']
1778 if opts.get('unknown'):
1802 if opts.get('unknown'):
1779 command += ['--untracked-files=all']
1803 command += ['--untracked-files=all']
1780 if opts.get('ignored'):
1804 if opts.get('ignored'):
1781 command += ['--ignored']
1805 command += ['--ignored']
1782 out = self._gitcommand(command)
1806 out = self._gitcommand(command)
1783
1807
1784 changedfiles = set()
1808 changedfiles = set()
1785 changedfiles.update(modified)
1809 changedfiles.update(modified)
1786 changedfiles.update(added)
1810 changedfiles.update(added)
1787 changedfiles.update(removed)
1811 changedfiles.update(removed)
1788 for line in out.split('\0'):
1812 for line in out.split('\0'):
1789 if not line:
1813 if not line:
1790 continue
1814 continue
1791 st = line[0:2]
1815 st = line[0:2]
1792 #moves and copies show 2 files on one line
1816 #moves and copies show 2 files on one line
1793 if line.find('\0') >= 0:
1817 if line.find('\0') >= 0:
1794 filename1, filename2 = line[3:].split('\0')
1818 filename1, filename2 = line[3:].split('\0')
1795 else:
1819 else:
1796 filename1 = line[3:]
1820 filename1 = line[3:]
1797 filename2 = None
1821 filename2 = None
1798
1822
1799 changedfiles.add(filename1)
1823 changedfiles.add(filename1)
1800 if filename2:
1824 if filename2:
1801 changedfiles.add(filename2)
1825 changedfiles.add(filename2)
1802
1826
1803 if st == '??':
1827 if st == '??':
1804 unknown.append(filename1)
1828 unknown.append(filename1)
1805 elif st == '!!':
1829 elif st == '!!':
1806 ignored.append(filename1)
1830 ignored.append(filename1)
1807
1831
1808 if opts.get('clean'):
1832 if opts.get('clean'):
1809 out = self._gitcommand(['ls-files'])
1833 out = self._gitcommand(['ls-files'])
1810 for f in out.split('\n'):
1834 for f in out.split('\n'):
1811 if not f in changedfiles:
1835 if not f in changedfiles:
1812 clean.append(f)
1836 clean.append(f)
1813
1837
1814 return scmutil.status(modified, added, removed, deleted,
1838 return scmutil.status(modified, added, removed, deleted,
1815 unknown, ignored, clean)
1839 unknown, ignored, clean)
1816
1840
1817 @annotatesubrepoerror
1841 @annotatesubrepoerror
1818 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1842 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1819 node1 = self._state[1]
1843 node1 = self._state[1]
1820 cmd = ['diff']
1844 cmd = ['diff']
1821 if opts['stat']:
1845 if opts['stat']:
1822 cmd.append('--stat')
1846 cmd.append('--stat')
1823 else:
1847 else:
1824 # for Git, this also implies '-p'
1848 # for Git, this also implies '-p'
1825 cmd.append('-U%d' % diffopts.context)
1849 cmd.append('-U%d' % diffopts.context)
1826
1850
1827 gitprefix = self.wvfs.reljoin(prefix, self._path)
1851 gitprefix = self.wvfs.reljoin(prefix, self._path)
1828
1852
1829 if diffopts.noprefix:
1853 if diffopts.noprefix:
1830 cmd.extend(['--src-prefix=%s/' % gitprefix,
1854 cmd.extend(['--src-prefix=%s/' % gitprefix,
1831 '--dst-prefix=%s/' % gitprefix])
1855 '--dst-prefix=%s/' % gitprefix])
1832 else:
1856 else:
1833 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1857 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1834 '--dst-prefix=b/%s/' % gitprefix])
1858 '--dst-prefix=b/%s/' % gitprefix])
1835
1859
1836 if diffopts.ignorews:
1860 if diffopts.ignorews:
1837 cmd.append('--ignore-all-space')
1861 cmd.append('--ignore-all-space')
1838 if diffopts.ignorewsamount:
1862 if diffopts.ignorewsamount:
1839 cmd.append('--ignore-space-change')
1863 cmd.append('--ignore-space-change')
1840 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1864 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1841 and diffopts.ignoreblanklines:
1865 and diffopts.ignoreblanklines:
1842 cmd.append('--ignore-blank-lines')
1866 cmd.append('--ignore-blank-lines')
1843
1867
1844 cmd.append(node1)
1868 cmd.append(node1)
1845 if node2:
1869 if node2:
1846 cmd.append(node2)
1870 cmd.append(node2)
1847
1871
1848 output = ""
1872 output = ""
1849 if match.always():
1873 if match.always():
1850 output += self._gitcommand(cmd) + '\n'
1874 output += self._gitcommand(cmd) + '\n'
1851 else:
1875 else:
1852 st = self.status(node2)[:3]
1876 st = self.status(node2)[:3]
1853 files = [f for sublist in st for f in sublist]
1877 files = [f for sublist in st for f in sublist]
1854 for f in files:
1878 for f in files:
1855 if match(f):
1879 if match(f):
1856 output += self._gitcommand(cmd + ['--', f]) + '\n'
1880 output += self._gitcommand(cmd + ['--', f]) + '\n'
1857
1881
1858 if output.strip():
1882 if output.strip():
1859 ui.write(output)
1883 ui.write(output)
1860
1884
1861 @annotatesubrepoerror
1885 @annotatesubrepoerror
1862 def revert(self, substate, *pats, **opts):
1886 def revert(self, substate, *pats, **opts):
1863 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1887 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1864 if not opts.get('no_backup'):
1888 if not opts.get('no_backup'):
1865 status = self.status(None)
1889 status = self.status(None)
1866 names = status.modified
1890 names = status.modified
1867 for name in names:
1891 for name in names:
1868 bakname = "%s.orig" % name
1892 bakname = "%s.orig" % name
1869 self.ui.note(_('saving current version of %s as %s\n') %
1893 self.ui.note(_('saving current version of %s as %s\n') %
1870 (name, bakname))
1894 (name, bakname))
1871 self.wvfs.rename(name, bakname)
1895 self.wvfs.rename(name, bakname)
1872
1896
1873 if not opts.get('dry_run'):
1897 if not opts.get('dry_run'):
1874 self.get(substate, overwrite=True)
1898 self.get(substate, overwrite=True)
1875 return []
1899 return []
1876
1900
1877 def shortid(self, revid):
1901 def shortid(self, revid):
1878 return revid[:7]
1902 return revid[:7]
1879
1903
1880 types = {
1904 types = {
1881 'hg': hgsubrepo,
1905 'hg': hgsubrepo,
1882 'svn': svnsubrepo,
1906 'svn': svnsubrepo,
1883 'git': gitsubrepo,
1907 'git': gitsubrepo,
1884 }
1908 }
@@ -1,162 +1,163
1 #require killdaemons
1 #require killdaemons
2
2
3 #if windows
3 #if windows
4 $ hg clone http://localhost:$HGPORT/ copy
4 $ hg clone http://localhost:$HGPORT/ copy
5 abort: * (glob)
5 abort: * (glob)
6 [255]
6 [255]
7 #else
7 #else
8 $ hg clone http://localhost:$HGPORT/ copy
8 $ hg clone http://localhost:$HGPORT/ copy
9 abort: error: Connection refused
9 abort: error: Connection refused
10 [255]
10 [255]
11 #endif
11 #endif
12 $ test -d copy
12 $ test -d copy
13 [1]
13 [1]
14
14
15 This server doesn't do range requests so it's basically only good for
15 This server doesn't do range requests so it's basically only good for
16 one pull
16 one pull
17
17
18 $ python "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
18 $ python "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
19 $ cat dumb.pid >> $DAEMON_PIDS
19 $ cat dumb.pid >> $DAEMON_PIDS
20 $ hg init remote
20 $ hg init remote
21 $ cd remote
21 $ cd remote
22 $ echo foo > bar
22 $ echo foo > bar
23 $ echo c2 > '.dotfile with spaces'
23 $ echo c2 > '.dotfile with spaces'
24 $ hg add
24 $ hg add
25 adding .dotfile with spaces
25 adding .dotfile with spaces
26 adding bar
26 adding bar
27 $ hg commit -m"test"
27 $ hg commit -m"test"
28 $ hg tip
28 $ hg tip
29 changeset: 0:02770d679fb8
29 changeset: 0:02770d679fb8
30 tag: tip
30 tag: tip
31 user: test
31 user: test
32 date: Thu Jan 01 00:00:00 1970 +0000
32 date: Thu Jan 01 00:00:00 1970 +0000
33 summary: test
33 summary: test
34
34
35 $ cd ..
35 $ cd ..
36 $ hg clone static-http://localhost:$HGPORT/remote local
36 $ hg clone static-http://localhost:$HGPORT/remote local
37 requesting all changes
37 requesting all changes
38 adding changesets
38 adding changesets
39 adding manifests
39 adding manifests
40 adding file changes
40 adding file changes
41 added 1 changesets with 2 changes to 2 files
41 added 1 changesets with 2 changes to 2 files
42 updating to branch default
42 updating to branch default
43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 $ cd local
44 $ cd local
45 $ hg verify
45 $ hg verify
46 checking changesets
46 checking changesets
47 checking manifests
47 checking manifests
48 crosschecking files in changesets and manifests
48 crosschecking files in changesets and manifests
49 checking files
49 checking files
50 2 files, 1 changesets, 2 total revisions
50 2 files, 1 changesets, 2 total revisions
51 $ cat bar
51 $ cat bar
52 foo
52 foo
53 $ cd ../remote
53 $ cd ../remote
54 $ echo baz > quux
54 $ echo baz > quux
55 $ hg commit -A -mtest2
55 $ hg commit -A -mtest2
56 adding quux
56 adding quux
57
57
58 check for HTTP opener failures when cachefile does not exist
58 check for HTTP opener failures when cachefile does not exist
59
59
60 $ rm .hg/cache/*
60 $ rm .hg/cache/*
61 $ cd ../local
61 $ cd ../local
62 $ echo '[hooks]' >> .hg/hgrc
62 $ echo '[hooks]' >> .hg/hgrc
63 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
63 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
64 $ hg pull
64 $ hg pull
65 pulling from static-http://localhost:$HGPORT/remote
65 pulling from static-http://localhost:$HGPORT/remote
66 searching for changes
66 searching for changes
67 adding changesets
67 adding changesets
68 adding manifests
68 adding manifests
69 adding file changes
69 adding file changes
70 added 1 changesets with 1 changes to 1 files
70 added 1 changesets with 1 changes to 1 files
71 changegroup hook: HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT/remote (glob)
71 changegroup hook: HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT/remote (glob)
72 (run 'hg update' to get a working copy)
72 (run 'hg update' to get a working copy)
73
73
74 trying to push
74 trying to push
75
75
76 $ hg update
76 $ hg update
77 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 $ echo more foo >> bar
78 $ echo more foo >> bar
79 $ hg commit -m"test"
79 $ hg commit -m"test"
80 $ hg push
80 $ hg push
81 pushing to static-http://localhost:$HGPORT/remote
81 pushing to static-http://localhost:$HGPORT/remote
82 abort: destination does not support push
82 abort: destination does not support push
83 [255]
83 [255]
84
84
85 trying clone -r
85 trying clone -r
86
86
87 $ cd ..
87 $ cd ..
88 $ hg clone -r doesnotexist static-http://localhost:$HGPORT/remote local0
88 $ hg clone -r doesnotexist static-http://localhost:$HGPORT/remote local0
89 abort: unknown revision 'doesnotexist'!
89 abort: unknown revision 'doesnotexist'!
90 [255]
90 [255]
91 $ hg clone -r 0 static-http://localhost:$HGPORT/remote local0
91 $ hg clone -r 0 static-http://localhost:$HGPORT/remote local0
92 adding changesets
92 adding changesets
93 adding manifests
93 adding manifests
94 adding file changes
94 adding file changes
95 added 1 changesets with 2 changes to 2 files
95 added 1 changesets with 2 changes to 2 files
96 updating to branch default
96 updating to branch default
97 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
98
98
99 test with "/" URI (issue747) and subrepo
99 test with "/" URI (issue747) and subrepo
100
100
101 $ hg init
101 $ hg init
102 $ hg init sub
102 $ hg init sub
103 $ touch sub/test
103 $ touch sub/test
104 $ hg -R sub commit -A -m "test"
104 $ hg -R sub commit -A -m "test"
105 adding test
105 adding test
106 $ hg -R sub tag not-empty
106 $ hg -R sub tag not-empty
107 $ echo sub=sub > .hgsub
107 $ echo sub=sub > .hgsub
108 $ echo a > a
108 $ echo a > a
109 $ hg add a .hgsub
109 $ hg add a .hgsub
110 $ hg -q ci -ma
110 $ hg -q ci -ma
111 $ hg clone static-http://localhost:$HGPORT/ local2
111 $ hg clone static-http://localhost:$HGPORT/ local2
112 requesting all changes
112 requesting all changes
113 adding changesets
113 adding changesets
114 adding manifests
114 adding manifests
115 adding file changes
115 adding file changes
116 added 1 changesets with 3 changes to 3 files
116 added 1 changesets with 3 changes to 3 files
117 updating to branch default
117 updating to branch default
118 cloning subrepo sub from static-http://localhost:$HGPORT/sub
118 cloning subrepo sub from static-http://localhost:$HGPORT/sub
119 requesting all changes
119 requesting all changes
120 adding changesets
120 adding changesets
121 adding manifests
121 adding manifests
122 adding file changes
122 adding file changes
123 added 2 changesets with 2 changes to 2 files
123 added 2 changesets with 2 changes to 2 files
124 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
124 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 $ cd local2
125 $ cd local2
126 $ hg verify
126 $ hg verify
127 checking changesets
127 checking changesets
128 checking manifests
128 checking manifests
129 crosschecking files in changesets and manifests
129 crosschecking files in changesets and manifests
130 checking files
130 checking files
131 3 files, 1 changesets, 3 total revisions
131 3 files, 1 changesets, 3 total revisions
132 checking subrepo links
132 $ cat a
133 $ cat a
133 a
134 a
134 $ hg paths
135 $ hg paths
135 default = static-http://localhost:$HGPORT/
136 default = static-http://localhost:$HGPORT/
136
137
137 test with empty repo (issue965)
138 test with empty repo (issue965)
138
139
139 $ cd ..
140 $ cd ..
140 $ hg init remotempty
141 $ hg init remotempty
141 $ hg clone static-http://localhost:$HGPORT/remotempty local3
142 $ hg clone static-http://localhost:$HGPORT/remotempty local3
142 no changes found
143 no changes found
143 updating to branch default
144 updating to branch default
144 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
145 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
145 $ cd local3
146 $ cd local3
146 $ hg verify
147 $ hg verify
147 checking changesets
148 checking changesets
148 checking manifests
149 checking manifests
149 crosschecking files in changesets and manifests
150 crosschecking files in changesets and manifests
150 checking files
151 checking files
151 0 files, 0 changesets, 0 total revisions
152 0 files, 0 changesets, 0 total revisions
152 $ hg paths
153 $ hg paths
153 default = static-http://localhost:$HGPORT/remotempty
154 default = static-http://localhost:$HGPORT/remotempty
154
155
155 test with non-repo
156 test with non-repo
156
157
157 $ cd ..
158 $ cd ..
158 $ mkdir notarepo
159 $ mkdir notarepo
159 $ hg clone static-http://localhost:$HGPORT/notarepo local3
160 $ hg clone static-http://localhost:$HGPORT/notarepo local3
160 abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository!
161 abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository!
161 [255]
162 [255]
162 $ killdaemons.py
163 $ killdaemons.py
@@ -1,109 +1,124
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ hg init subrepo
3 $ hg init subrepo
4 $ echo a > subrepo/a
4 $ echo a > subrepo/a
5 $ hg -R subrepo ci -Am adda
5 $ hg -R subrepo ci -Am adda
6 adding a
6 adding a
7 $ echo 'subrepo = subrepo' > .hgsub
7 $ echo 'subrepo = subrepo' > .hgsub
8 $ hg ci -Am addsubrepo
8 $ hg ci -Am addsubrepo
9 adding .hgsub
9 adding .hgsub
10 $ echo b > subrepo/b
10 $ echo b > subrepo/b
11 $ hg -R subrepo ci -Am addb
11 $ hg -R subrepo ci -Am addb
12 adding b
12 adding b
13 $ hg ci -m updatedsub
13 $ hg ci -m updatedsub
14
14
15 ignore blanklines in .hgsubstate
15 ignore blanklines in .hgsubstate
16
16
17 >>> file('.hgsubstate', 'wb').write('\n\n \t \n \n')
17 >>> file('.hgsubstate', 'wb').write('\n\n \t \n \n')
18 $ hg st --subrepos
18 $ hg st --subrepos
19 M .hgsubstate
19 M .hgsubstate
20 $ hg revert -qC .hgsubstate
20 $ hg revert -qC .hgsubstate
21
21
22 abort more gracefully on .hgsubstate parsing error
22 abort more gracefully on .hgsubstate parsing error
23
23
24 $ cp .hgsubstate .hgsubstate.old
24 $ cp .hgsubstate .hgsubstate.old
25 >>> file('.hgsubstate', 'wb').write('\ninvalid')
25 >>> file('.hgsubstate', 'wb').write('\ninvalid')
26 $ hg st --subrepos
26 $ hg st --subrepos
27 abort: invalid subrepository revision specifier in '.hgsubstate' line 2
27 abort: invalid subrepository revision specifier in '.hgsubstate' line 2
28 [255]
28 [255]
29 $ mv .hgsubstate.old .hgsubstate
29 $ mv .hgsubstate.old .hgsubstate
30
30
31 delete .hgsub and revert it
31 delete .hgsub and revert it
32
32
33 $ rm .hgsub
33 $ rm .hgsub
34 $ hg revert .hgsub
34 $ hg revert .hgsub
35 warning: subrepo spec file '.hgsub' not found
35 warning: subrepo spec file '.hgsub' not found
36 warning: subrepo spec file '.hgsub' not found
36 warning: subrepo spec file '.hgsub' not found
37 warning: subrepo spec file '.hgsub' not found
37 warning: subrepo spec file '.hgsub' not found
38
38
39 delete .hgsubstate and revert it
39 delete .hgsubstate and revert it
40
40
41 $ rm .hgsubstate
41 $ rm .hgsubstate
42 $ hg revert .hgsubstate
42 $ hg revert .hgsubstate
43
43
44 delete .hgsub and update
44 delete .hgsub and update
45
45
46 $ rm .hgsub
46 $ rm .hgsub
47 $ hg up 0
47 $ hg up 0
48 warning: subrepo spec file '.hgsub' not found
48 warning: subrepo spec file '.hgsub' not found
49 warning: subrepo spec file '.hgsub' not found
49 warning: subrepo spec file '.hgsub' not found
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 $ hg st
51 $ hg st
52 warning: subrepo spec file '.hgsub' not found
52 warning: subrepo spec file '.hgsub' not found
53 ! .hgsub
53 ! .hgsub
54 $ ls subrepo
54 $ ls subrepo
55 a
55 a
56
56
57 delete .hgsubstate and update
57 delete .hgsubstate and update
58
58
59 $ hg up -C
59 $ hg up -C
60 warning: subrepo spec file '.hgsub' not found
60 warning: subrepo spec file '.hgsub' not found
61 warning: subrepo spec file '.hgsub' not found
61 warning: subrepo spec file '.hgsub' not found
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ rm .hgsubstate
63 $ rm .hgsubstate
64 $ hg up 0
64 $ hg up 0
65 remote changed .hgsubstate which local deleted
65 remote changed .hgsubstate which local deleted
66 use (c)hanged version or leave (d)eleted? c
66 use (c)hanged version or leave (d)eleted? c
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 $ hg st
68 $ hg st
69 $ ls subrepo
69 $ ls subrepo
70 a
70 a
71
71
72 Enable obsolete
72 Enable obsolete
73
73
74 $ cat >> $HGRCPATH << EOF
74 $ cat >> $HGRCPATH << EOF
75 > [ui]
75 > [ui]
76 > logtemplate= {rev}:{node|short} {desc|firstline}
76 > logtemplate= {rev}:{node|short} {desc|firstline}
77 > [phases]
77 > [phases]
78 > publish=False
78 > publish=False
79 > [experimental]
79 > [experimental]
80 > evolution=createmarkers
80 > evolution=createmarkers
81 > EOF
81 > EOF
82
82
83 check that we can update parent repo with missing (amended) subrepo revision
83 check that we can update parent repo with missing (amended) subrepo revision
84
84
85 $ hg up --repository subrepo -r tip
85 $ hg up --repository subrepo -r tip
86 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 $ hg ci -m "updated subrepo to tip"
87 $ hg ci -m "updated subrepo to tip"
88 created new head
88 created new head
89 $ cd subrepo
89 $ cd subrepo
90 $ hg update -r tip
90 $ hg update -r tip
91 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 $ echo foo > a
92 $ echo foo > a
93 $ hg commit --amend -m "addb (amended)"
93 $ hg commit --amend -m "addb (amended)"
94 $ cd ..
94 $ cd ..
95 $ hg update --clean .
95 $ hg update --clean .
96 revision 102a90ea7b4a in subrepo subrepo is hidden
96 revision 102a90ea7b4a in subrepo subrepo is hidden
97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
98
98
99 check that --hidden is propagated to the subrepo
99 check that --hidden is propagated to the subrepo
100
100
101 $ hg -R subrepo up tip
101 $ hg -R subrepo up tip
102 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 $ hg ci -m 'commit with amended subrepo'
103 $ hg ci -m 'commit with amended subrepo'
104 $ echo bar > subrepo/a
104 $ echo bar > subrepo/a
105 $ hg -R subrepo ci --amend -m "amend a (again)"
105 $ hg -R subrepo ci --amend -m "amend a (again)"
106 $ hg --hidden cat subrepo/a
106 $ hg --hidden cat subrepo/a
107 foo
107 foo
108
108
109 verify will warn if locked-in subrepo revisions are hidden or missing
110
111 $ hg ci -m "amended subrepo (again)"
112 $ hg --config extensions.strip= --hidden strip -R subrepo -qr 'tip'
113 $ hg verify
114 checking changesets
115 checking manifests
116 crosschecking files in changesets and manifests
117 checking files
118 2 files, 5 changesets, 5 total revisions
119 checking subrepo links
120 subrepo 'subrepo' is hidden in revision a66de08943b6
121 subrepo 'subrepo' is hidden in revision 674d05939c1e
122 subrepo 'subrepo' not found in revision a7d05d9055a4
123
109 $ cd ..
124 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now