##// END OF EJS Templates
subrepo: activate clone pooling to enable sharing with remote URLs...
Matt Harbison -
r36706:fb278041 stable
parent child Browse files
Show More
@@ -1,2156 +1,2170
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import posixpath
14 import posixpath
15 import re
15 import re
16 import stat
16 import stat
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import tarfile
19 import tarfile
20 import xml.dom.minidom
20 import xml.dom.minidom
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 config,
26 config,
27 encoding,
27 encoding,
28 error,
28 error,
29 exchange,
29 exchange,
30 filemerge,
30 filemerge,
31 match as matchmod,
31 match as matchmod,
32 node,
32 node,
33 pathutil,
33 pathutil,
34 phases,
34 phases,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 vfs as vfsmod,
38 vfs as vfsmod,
39 )
39 )
40
40
41 hg = None
41 hg = None
42 propertycache = util.propertycache
42 propertycache = util.propertycache
43
43
44 nullstate = ('', '', 'empty')
44 nullstate = ('', '', 'empty')
45
45
46 def _expandedabspath(path):
46 def _expandedabspath(path):
47 '''
47 '''
48 get a path or url and if it is a path expand it and return an absolute path
48 get a path or url and if it is a path expand it and return an absolute path
49 '''
49 '''
50 expandedpath = util.urllocalpath(util.expandpath(path))
50 expandedpath = util.urllocalpath(util.expandpath(path))
51 u = util.url(expandedpath)
51 u = util.url(expandedpath)
52 if not u.scheme:
52 if not u.scheme:
53 path = util.normpath(os.path.abspath(u.path))
53 path = util.normpath(os.path.abspath(u.path))
54 return path
54 return path
55
55
56 def _getstorehashcachename(remotepath):
56 def _getstorehashcachename(remotepath):
57 '''get a unique filename for the store hash cache of a remote repository'''
57 '''get a unique filename for the store hash cache of a remote repository'''
58 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
58 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
59
59
60 class SubrepoAbort(error.Abort):
60 class SubrepoAbort(error.Abort):
61 """Exception class used to avoid handling a subrepo error more than once"""
61 """Exception class used to avoid handling a subrepo error more than once"""
62 def __init__(self, *args, **kw):
62 def __init__(self, *args, **kw):
63 self.subrepo = kw.pop(r'subrepo', None)
63 self.subrepo = kw.pop(r'subrepo', None)
64 self.cause = kw.pop(r'cause', None)
64 self.cause = kw.pop(r'cause', None)
65 error.Abort.__init__(self, *args, **kw)
65 error.Abort.__init__(self, *args, **kw)
66
66
67 def annotatesubrepoerror(func):
67 def annotatesubrepoerror(func):
68 def decoratedmethod(self, *args, **kargs):
68 def decoratedmethod(self, *args, **kargs):
69 try:
69 try:
70 res = func(self, *args, **kargs)
70 res = func(self, *args, **kargs)
71 except SubrepoAbort as ex:
71 except SubrepoAbort as ex:
72 # This exception has already been handled
72 # This exception has already been handled
73 raise ex
73 raise ex
74 except error.Abort as ex:
74 except error.Abort as ex:
75 subrepo = subrelpath(self)
75 subrepo = subrelpath(self)
76 errormsg = str(ex) + ' ' + _('(in subrepository "%s")') % subrepo
76 errormsg = str(ex) + ' ' + _('(in subrepository "%s")') % subrepo
77 # avoid handling this exception by raising a SubrepoAbort exception
77 # avoid handling this exception by raising a SubrepoAbort exception
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
79 cause=sys.exc_info())
79 cause=sys.exc_info())
80 return res
80 return res
81 return decoratedmethod
81 return decoratedmethod
82
82
83 def state(ctx, ui):
83 def state(ctx, ui):
84 """return a state dict, mapping subrepo paths configured in .hgsub
84 """return a state dict, mapping subrepo paths configured in .hgsub
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
86 (key in types dict))
86 (key in types dict))
87 """
87 """
88 p = config.config()
88 p = config.config()
89 repo = ctx.repo()
89 repo = ctx.repo()
90 def read(f, sections=None, remap=None):
90 def read(f, sections=None, remap=None):
91 if f in ctx:
91 if f in ctx:
92 try:
92 try:
93 data = ctx[f].data()
93 data = ctx[f].data()
94 except IOError as err:
94 except IOError as err:
95 if err.errno != errno.ENOENT:
95 if err.errno != errno.ENOENT:
96 raise
96 raise
97 # handle missing subrepo spec files as removed
97 # handle missing subrepo spec files as removed
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
99 repo.pathto(f))
99 repo.pathto(f))
100 return
100 return
101 p.parse(f, data, sections, remap, read)
101 p.parse(f, data, sections, remap, read)
102 else:
102 else:
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
104 repo.pathto(f))
104 repo.pathto(f))
105 if '.hgsub' in ctx:
105 if '.hgsub' in ctx:
106 read('.hgsub')
106 read('.hgsub')
107
107
108 for path, src in ui.configitems('subpaths'):
108 for path, src in ui.configitems('subpaths'):
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
110
110
111 rev = {}
111 rev = {}
112 if '.hgsubstate' in ctx:
112 if '.hgsubstate' in ctx:
113 try:
113 try:
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
115 l = l.lstrip()
115 l = l.lstrip()
116 if not l:
116 if not l:
117 continue
117 continue
118 try:
118 try:
119 revision, path = l.split(" ", 1)
119 revision, path = l.split(" ", 1)
120 except ValueError:
120 except ValueError:
121 raise error.Abort(_("invalid subrepository revision "
121 raise error.Abort(_("invalid subrepository revision "
122 "specifier in \'%s\' line %d")
122 "specifier in \'%s\' line %d")
123 % (repo.pathto('.hgsubstate'), (i + 1)))
123 % (repo.pathto('.hgsubstate'), (i + 1)))
124 rev[path] = revision
124 rev[path] = revision
125 except IOError as err:
125 except IOError as err:
126 if err.errno != errno.ENOENT:
126 if err.errno != errno.ENOENT:
127 raise
127 raise
128
128
129 def remap(src):
129 def remap(src):
130 for pattern, repl in p.items('subpaths'):
130 for pattern, repl in p.items('subpaths'):
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
132 # does a string decode.
132 # does a string decode.
133 repl = util.escapestr(repl)
133 repl = util.escapestr(repl)
134 # However, we still want to allow back references to go
134 # However, we still want to allow back references to go
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
136 # extra escapes are needed because re.sub string decodes.
136 # extra escapes are needed because re.sub string decodes.
137 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
137 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
138 try:
138 try:
139 src = re.sub(pattern, repl, src, 1)
139 src = re.sub(pattern, repl, src, 1)
140 except re.error as e:
140 except re.error as e:
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
142 % (p.source('subpaths', pattern), e))
142 % (p.source('subpaths', pattern), e))
143 return src
143 return src
144
144
145 state = {}
145 state = {}
146 for path, src in p[''].items():
146 for path, src in p[''].items():
147 kind = 'hg'
147 kind = 'hg'
148 if src.startswith('['):
148 if src.startswith('['):
149 if ']' not in src:
149 if ']' not in src:
150 raise error.Abort(_('missing ] in subrepository source'))
150 raise error.Abort(_('missing ] in subrepository source'))
151 kind, src = src.split(']', 1)
151 kind, src = src.split(']', 1)
152 kind = kind[1:]
152 kind = kind[1:]
153 src = src.lstrip() # strip any extra whitespace after ']'
153 src = src.lstrip() # strip any extra whitespace after ']'
154
154
155 if not util.url(src).isabs():
155 if not util.url(src).isabs():
156 parent = _abssource(repo, abort=False)
156 parent = _abssource(repo, abort=False)
157 if parent:
157 if parent:
158 parent = util.url(parent)
158 parent = util.url(parent)
159 parent.path = posixpath.join(parent.path or '', src)
159 parent.path = posixpath.join(parent.path or '', src)
160 parent.path = posixpath.normpath(parent.path)
160 parent.path = posixpath.normpath(parent.path)
161 joined = str(parent)
161 joined = str(parent)
162 # Remap the full joined path and use it if it changes,
162 # Remap the full joined path and use it if it changes,
163 # else remap the original source.
163 # else remap the original source.
164 remapped = remap(joined)
164 remapped = remap(joined)
165 if remapped == joined:
165 if remapped == joined:
166 src = remap(src)
166 src = remap(src)
167 else:
167 else:
168 src = remapped
168 src = remapped
169
169
170 src = remap(src)
170 src = remap(src)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
172
172
173 return state
173 return state
174
174
175 def writestate(repo, state):
175 def writestate(repo, state):
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
178 if state[s][1] != nullstate[1]]
178 if state[s][1] != nullstate[1]]
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
180
180
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
182 """delegated from merge.applyupdates: merging of .hgsubstate file
182 """delegated from merge.applyupdates: merging of .hgsubstate file
183 in working context, merging context and ancestor context"""
183 in working context, merging context and ancestor context"""
184 if mctx == actx: # backwards?
184 if mctx == actx: # backwards?
185 actx = wctx.p1()
185 actx = wctx.p1()
186 s1 = wctx.substate
186 s1 = wctx.substate
187 s2 = mctx.substate
187 s2 = mctx.substate
188 sa = actx.substate
188 sa = actx.substate
189 sm = {}
189 sm = {}
190
190
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
192
192
193 def debug(s, msg, r=""):
193 def debug(s, msg, r=""):
194 if r:
194 if r:
195 r = "%s:%s:%s" % r
195 r = "%s:%s:%s" % r
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
197
197
198 promptssrc = filemerge.partextras(labels)
198 promptssrc = filemerge.partextras(labels)
199 for s, l in sorted(s1.iteritems()):
199 for s, l in sorted(s1.iteritems()):
200 prompts = None
200 prompts = None
201 a = sa.get(s, nullstate)
201 a = sa.get(s, nullstate)
202 ld = l # local state with possible dirty flag for compares
202 ld = l # local state with possible dirty flag for compares
203 if wctx.sub(s).dirty():
203 if wctx.sub(s).dirty():
204 ld = (l[0], l[1] + "+")
204 ld = (l[0], l[1] + "+")
205 if wctx == actx: # overwrite
205 if wctx == actx: # overwrite
206 a = ld
206 a = ld
207
207
208 prompts = promptssrc.copy()
208 prompts = promptssrc.copy()
209 prompts['s'] = s
209 prompts['s'] = s
210 if s in s2:
210 if s in s2:
211 r = s2[s]
211 r = s2[s]
212 if ld == r or r == a: # no change or local is newer
212 if ld == r or r == a: # no change or local is newer
213 sm[s] = l
213 sm[s] = l
214 continue
214 continue
215 elif ld == a: # other side changed
215 elif ld == a: # other side changed
216 debug(s, "other changed, get", r)
216 debug(s, "other changed, get", r)
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 elif ld[0] != r[0]: # sources differ
219 elif ld[0] != r[0]: # sources differ
220 prompts['lo'] = l[0]
220 prompts['lo'] = l[0]
221 prompts['ro'] = r[0]
221 prompts['ro'] = r[0]
222 if repo.ui.promptchoice(
222 if repo.ui.promptchoice(
223 _(' subrepository sources for %(s)s differ\n'
223 _(' subrepository sources for %(s)s differ\n'
224 'use (l)ocal%(l)s source (%(lo)s)'
224 'use (l)ocal%(l)s source (%(lo)s)'
225 ' or (r)emote%(o)s source (%(ro)s)?'
225 ' or (r)emote%(o)s source (%(ro)s)?'
226 '$$ &Local $$ &Remote') % prompts, 0):
226 '$$ &Local $$ &Remote') % prompts, 0):
227 debug(s, "prompt changed, get", r)
227 debug(s, "prompt changed, get", r)
228 wctx.sub(s).get(r, overwrite)
228 wctx.sub(s).get(r, overwrite)
229 sm[s] = r
229 sm[s] = r
230 elif ld[1] == a[1]: # local side is unchanged
230 elif ld[1] == a[1]: # local side is unchanged
231 debug(s, "other side changed, get", r)
231 debug(s, "other side changed, get", r)
232 wctx.sub(s).get(r, overwrite)
232 wctx.sub(s).get(r, overwrite)
233 sm[s] = r
233 sm[s] = r
234 else:
234 else:
235 debug(s, "both sides changed")
235 debug(s, "both sides changed")
236 srepo = wctx.sub(s)
236 srepo = wctx.sub(s)
237 prompts['sl'] = srepo.shortid(l[1])
237 prompts['sl'] = srepo.shortid(l[1])
238 prompts['sr'] = srepo.shortid(r[1])
238 prompts['sr'] = srepo.shortid(r[1])
239 option = repo.ui.promptchoice(
239 option = repo.ui.promptchoice(
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
241 'remote revision: %(sr)s)\n'
241 'remote revision: %(sr)s)\n'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
243 '$$ &Merge $$ &Local $$ &Remote')
243 '$$ &Merge $$ &Local $$ &Remote')
244 % prompts, 0)
244 % prompts, 0)
245 if option == 0:
245 if option == 0:
246 wctx.sub(s).merge(r)
246 wctx.sub(s).merge(r)
247 sm[s] = l
247 sm[s] = l
248 debug(s, "merge with", r)
248 debug(s, "merge with", r)
249 elif option == 1:
249 elif option == 1:
250 sm[s] = l
250 sm[s] = l
251 debug(s, "keep local subrepo revision", l)
251 debug(s, "keep local subrepo revision", l)
252 else:
252 else:
253 wctx.sub(s).get(r, overwrite)
253 wctx.sub(s).get(r, overwrite)
254 sm[s] = r
254 sm[s] = r
255 debug(s, "get remote subrepo revision", r)
255 debug(s, "get remote subrepo revision", r)
256 elif ld == a: # remote removed, local unchanged
256 elif ld == a: # remote removed, local unchanged
257 debug(s, "remote removed, remove")
257 debug(s, "remote removed, remove")
258 wctx.sub(s).remove()
258 wctx.sub(s).remove()
259 elif a == nullstate: # not present in remote or ancestor
259 elif a == nullstate: # not present in remote or ancestor
260 debug(s, "local added, keep")
260 debug(s, "local added, keep")
261 sm[s] = l
261 sm[s] = l
262 continue
262 continue
263 else:
263 else:
264 if repo.ui.promptchoice(
264 if repo.ui.promptchoice(
265 _(' local%(l)s changed subrepository %(s)s'
265 _(' local%(l)s changed subrepository %(s)s'
266 ' which remote%(o)s removed\n'
266 ' which remote%(o)s removed\n'
267 'use (c)hanged version or (d)elete?'
267 'use (c)hanged version or (d)elete?'
268 '$$ &Changed $$ &Delete') % prompts, 0):
268 '$$ &Changed $$ &Delete') % prompts, 0):
269 debug(s, "prompt remove")
269 debug(s, "prompt remove")
270 wctx.sub(s).remove()
270 wctx.sub(s).remove()
271
271
272 for s, r in sorted(s2.items()):
272 for s, r in sorted(s2.items()):
273 prompts = None
273 prompts = None
274 if s in s1:
274 if s in s1:
275 continue
275 continue
276 elif s not in sa:
276 elif s not in sa:
277 debug(s, "remote added, get", r)
277 debug(s, "remote added, get", r)
278 mctx.sub(s).get(r)
278 mctx.sub(s).get(r)
279 sm[s] = r
279 sm[s] = r
280 elif r != sa[s]:
280 elif r != sa[s]:
281 prompts = promptssrc.copy()
281 prompts = promptssrc.copy()
282 prompts['s'] = s
282 prompts['s'] = s
283 if repo.ui.promptchoice(
283 if repo.ui.promptchoice(
284 _(' remote%(o)s changed subrepository %(s)s'
284 _(' remote%(o)s changed subrepository %(s)s'
285 ' which local%(l)s removed\n'
285 ' which local%(l)s removed\n'
286 'use (c)hanged version or (d)elete?'
286 'use (c)hanged version or (d)elete?'
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
288 debug(s, "prompt recreate", r)
288 debug(s, "prompt recreate", r)
289 mctx.sub(s).get(r)
289 mctx.sub(s).get(r)
290 sm[s] = r
290 sm[s] = r
291
291
292 # record merged .hgsubstate
292 # record merged .hgsubstate
293 writestate(repo, sm)
293 writestate(repo, sm)
294 return sm
294 return sm
295
295
296 def precommit(ui, wctx, status, match, force=False):
296 def precommit(ui, wctx, status, match, force=False):
297 """Calculate .hgsubstate changes that should be applied before committing
297 """Calculate .hgsubstate changes that should be applied before committing
298
298
299 Returns (subs, commitsubs, newstate) where
299 Returns (subs, commitsubs, newstate) where
300 - subs: changed subrepos (including dirty ones)
300 - subs: changed subrepos (including dirty ones)
301 - commitsubs: dirty subrepos which the caller needs to commit recursively
301 - commitsubs: dirty subrepos which the caller needs to commit recursively
302 - newstate: new state dict which the caller must write to .hgsubstate
302 - newstate: new state dict which the caller must write to .hgsubstate
303
303
304 This also updates the given status argument.
304 This also updates the given status argument.
305 """
305 """
306 subs = []
306 subs = []
307 commitsubs = set()
307 commitsubs = set()
308 newstate = wctx.substate.copy()
308 newstate = wctx.substate.copy()
309
309
310 # only manage subrepos and .hgsubstate if .hgsub is present
310 # only manage subrepos and .hgsubstate if .hgsub is present
311 if '.hgsub' in wctx:
311 if '.hgsub' in wctx:
312 # we'll decide whether to track this ourselves, thanks
312 # we'll decide whether to track this ourselves, thanks
313 for c in status.modified, status.added, status.removed:
313 for c in status.modified, status.added, status.removed:
314 if '.hgsubstate' in c:
314 if '.hgsubstate' in c:
315 c.remove('.hgsubstate')
315 c.remove('.hgsubstate')
316
316
317 # compare current state to last committed state
317 # compare current state to last committed state
318 # build new substate based on last committed state
318 # build new substate based on last committed state
319 oldstate = wctx.p1().substate
319 oldstate = wctx.p1().substate
320 for s in sorted(newstate.keys()):
320 for s in sorted(newstate.keys()):
321 if not match(s):
321 if not match(s):
322 # ignore working copy, use old state if present
322 # ignore working copy, use old state if present
323 if s in oldstate:
323 if s in oldstate:
324 newstate[s] = oldstate[s]
324 newstate[s] = oldstate[s]
325 continue
325 continue
326 if not force:
326 if not force:
327 raise error.Abort(
327 raise error.Abort(
328 _("commit with new subrepo %s excluded") % s)
328 _("commit with new subrepo %s excluded") % s)
329 dirtyreason = wctx.sub(s).dirtyreason(True)
329 dirtyreason = wctx.sub(s).dirtyreason(True)
330 if dirtyreason:
330 if dirtyreason:
331 if not ui.configbool('ui', 'commitsubrepos'):
331 if not ui.configbool('ui', 'commitsubrepos'):
332 raise error.Abort(dirtyreason,
332 raise error.Abort(dirtyreason,
333 hint=_("use --subrepos for recursive commit"))
333 hint=_("use --subrepos for recursive commit"))
334 subs.append(s)
334 subs.append(s)
335 commitsubs.add(s)
335 commitsubs.add(s)
336 else:
336 else:
337 bs = wctx.sub(s).basestate()
337 bs = wctx.sub(s).basestate()
338 newstate[s] = (newstate[s][0], bs, newstate[s][2])
338 newstate[s] = (newstate[s][0], bs, newstate[s][2])
339 if oldstate.get(s, (None, None, None))[1] != bs:
339 if oldstate.get(s, (None, None, None))[1] != bs:
340 subs.append(s)
340 subs.append(s)
341
341
342 # check for removed subrepos
342 # check for removed subrepos
343 for p in wctx.parents():
343 for p in wctx.parents():
344 r = [s for s in p.substate if s not in newstate]
344 r = [s for s in p.substate if s not in newstate]
345 subs += [s for s in r if match(s)]
345 subs += [s for s in r if match(s)]
346 if subs:
346 if subs:
347 if (not match('.hgsub') and
347 if (not match('.hgsub') and
348 '.hgsub' in (wctx.modified() + wctx.added())):
348 '.hgsub' in (wctx.modified() + wctx.added())):
349 raise error.Abort(_("can't commit subrepos without .hgsub"))
349 raise error.Abort(_("can't commit subrepos without .hgsub"))
350 status.modified.insert(0, '.hgsubstate')
350 status.modified.insert(0, '.hgsubstate')
351
351
352 elif '.hgsub' in status.removed:
352 elif '.hgsub' in status.removed:
353 # clean up .hgsubstate when .hgsub is removed
353 # clean up .hgsubstate when .hgsub is removed
354 if ('.hgsubstate' in wctx and
354 if ('.hgsubstate' in wctx and
355 '.hgsubstate' not in (status.modified + status.added +
355 '.hgsubstate' not in (status.modified + status.added +
356 status.removed)):
356 status.removed)):
357 status.removed.insert(0, '.hgsubstate')
357 status.removed.insert(0, '.hgsubstate')
358
358
359 return subs, commitsubs, newstate
359 return subs, commitsubs, newstate
360
360
361 def _updateprompt(ui, sub, dirty, local, remote):
361 def _updateprompt(ui, sub, dirty, local, remote):
362 if dirty:
362 if dirty:
363 msg = (_(' subrepository sources for %s differ\n'
363 msg = (_(' subrepository sources for %s differ\n'
364 'use (l)ocal source (%s) or (r)emote source (%s)?'
364 'use (l)ocal source (%s) or (r)emote source (%s)?'
365 '$$ &Local $$ &Remote')
365 '$$ &Local $$ &Remote')
366 % (subrelpath(sub), local, remote))
366 % (subrelpath(sub), local, remote))
367 else:
367 else:
368 msg = (_(' subrepository sources for %s differ (in checked out '
368 msg = (_(' subrepository sources for %s differ (in checked out '
369 'version)\n'
369 'version)\n'
370 'use (l)ocal source (%s) or (r)emote source (%s)?'
370 'use (l)ocal source (%s) or (r)emote source (%s)?'
371 '$$ &Local $$ &Remote')
371 '$$ &Local $$ &Remote')
372 % (subrelpath(sub), local, remote))
372 % (subrelpath(sub), local, remote))
373 return ui.promptchoice(msg, 0)
373 return ui.promptchoice(msg, 0)
374
374
375 def reporelpath(repo):
375 def reporelpath(repo):
376 """return path to this (sub)repo as seen from outermost repo"""
376 """return path to this (sub)repo as seen from outermost repo"""
377 parent = repo
377 parent = repo
378 while util.safehasattr(parent, '_subparent'):
378 while util.safehasattr(parent, '_subparent'):
379 parent = parent._subparent
379 parent = parent._subparent
380 return repo.root[len(pathutil.normasprefix(parent.root)):]
380 return repo.root[len(pathutil.normasprefix(parent.root)):]
381
381
382 def subrelpath(sub):
382 def subrelpath(sub):
383 """return path to this subrepo as seen from outermost repo"""
383 """return path to this subrepo as seen from outermost repo"""
384 return sub._relpath
384 return sub._relpath
385
385
386 def _abssource(repo, push=False, abort=True):
386 def _abssource(repo, push=False, abort=True):
387 """return pull/push path of repo - either based on parent repo .hgsub info
387 """return pull/push path of repo - either based on parent repo .hgsub info
388 or on the top repo config. Abort or return None if no source found."""
388 or on the top repo config. Abort or return None if no source found."""
389 if util.safehasattr(repo, '_subparent'):
389 if util.safehasattr(repo, '_subparent'):
390 source = util.url(repo._subsource)
390 source = util.url(repo._subsource)
391 if source.isabs():
391 if source.isabs():
392 return bytes(source)
392 return bytes(source)
393 source.path = posixpath.normpath(source.path)
393 source.path = posixpath.normpath(source.path)
394 parent = _abssource(repo._subparent, push, abort=False)
394 parent = _abssource(repo._subparent, push, abort=False)
395 if parent:
395 if parent:
396 parent = util.url(util.pconvert(parent))
396 parent = util.url(util.pconvert(parent))
397 parent.path = posixpath.join(parent.path or '', source.path)
397 parent.path = posixpath.join(parent.path or '', source.path)
398 parent.path = posixpath.normpath(parent.path)
398 parent.path = posixpath.normpath(parent.path)
399 return bytes(parent)
399 return bytes(parent)
400 else: # recursion reached top repo
400 else: # recursion reached top repo
401 path = None
401 path = None
402 if util.safehasattr(repo, '_subtoppath'):
402 if util.safehasattr(repo, '_subtoppath'):
403 path = repo._subtoppath
403 path = repo._subtoppath
404 elif push and repo.ui.config('paths', 'default-push'):
404 elif push and repo.ui.config('paths', 'default-push'):
405 path = repo.ui.config('paths', 'default-push')
405 path = repo.ui.config('paths', 'default-push')
406 elif repo.ui.config('paths', 'default'):
406 elif repo.ui.config('paths', 'default'):
407 path = repo.ui.config('paths', 'default')
407 path = repo.ui.config('paths', 'default')
408 elif repo.shared():
408 elif repo.shared():
409 # chop off the .hg component to get the default path form. This has
409 # chop off the .hg component to get the default path form. This has
410 # already run through vfsmod.vfs(..., realpath=True), so it doesn't
410 # already run through vfsmod.vfs(..., realpath=True), so it doesn't
411 # have problems with 'C:'
411 # have problems with 'C:'
412 return os.path.dirname(repo.sharedpath)
412 return os.path.dirname(repo.sharedpath)
413 if path:
413 if path:
414 # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is
414 # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is
415 # as expected: an absolute path to the root of the C: drive. The
415 # as expected: an absolute path to the root of the C: drive. The
416 # latter is a relative path, and works like so:
416 # latter is a relative path, and works like so:
417 #
417 #
418 # C:\>cd C:\some\path
418 # C:\>cd C:\some\path
419 # C:\>D:
419 # C:\>D:
420 # D:\>python -c "import os; print os.path.abspath('C:')"
420 # D:\>python -c "import os; print os.path.abspath('C:')"
421 # C:\some\path
421 # C:\some\path
422 #
422 #
423 # D:\>python -c "import os; print os.path.abspath('C:relative')"
423 # D:\>python -c "import os; print os.path.abspath('C:relative')"
424 # C:\some\path\relative
424 # C:\some\path\relative
425 if util.hasdriveletter(path):
425 if util.hasdriveletter(path):
426 if len(path) == 2 or path[2:3] not in br'\/':
426 if len(path) == 2 or path[2:3] not in br'\/':
427 path = os.path.abspath(path)
427 path = os.path.abspath(path)
428 return path
428 return path
429
429
430 if abort:
430 if abort:
431 raise error.Abort(_("default path for subrepository not found"))
431 raise error.Abort(_("default path for subrepository not found"))
432
432
433 def _sanitize(ui, vfs, ignore):
433 def _sanitize(ui, vfs, ignore):
434 for dirname, dirs, names in vfs.walk():
434 for dirname, dirs, names in vfs.walk():
435 for i, d in enumerate(dirs):
435 for i, d in enumerate(dirs):
436 if d.lower() == ignore:
436 if d.lower() == ignore:
437 del dirs[i]
437 del dirs[i]
438 break
438 break
439 if vfs.basename(dirname).lower() != '.hg':
439 if vfs.basename(dirname).lower() != '.hg':
440 continue
440 continue
441 for f in names:
441 for f in names:
442 if f.lower() == 'hgrc':
442 if f.lower() == 'hgrc':
443 ui.warn(_("warning: removing potentially hostile 'hgrc' "
443 ui.warn(_("warning: removing potentially hostile 'hgrc' "
444 "in '%s'\n") % vfs.join(dirname))
444 "in '%s'\n") % vfs.join(dirname))
445 vfs.unlink(vfs.reljoin(dirname, f))
445 vfs.unlink(vfs.reljoin(dirname, f))
446
446
447 def _auditsubrepopath(repo, path):
447 def _auditsubrepopath(repo, path):
448 # auditor doesn't check if the path itself is a symlink
448 # auditor doesn't check if the path itself is a symlink
449 pathutil.pathauditor(repo.root)(path)
449 pathutil.pathauditor(repo.root)(path)
450 if repo.wvfs.islink(path):
450 if repo.wvfs.islink(path):
451 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
451 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
452
452
453 SUBREPO_ALLOWED_DEFAULTS = {
453 SUBREPO_ALLOWED_DEFAULTS = {
454 'hg': True,
454 'hg': True,
455 'git': False,
455 'git': False,
456 'svn': False,
456 'svn': False,
457 }
457 }
458
458
459 def _checktype(ui, kind):
459 def _checktype(ui, kind):
460 # subrepos.allowed is a master kill switch. If disabled, subrepos are
460 # subrepos.allowed is a master kill switch. If disabled, subrepos are
461 # disabled period.
461 # disabled period.
462 if not ui.configbool('subrepos', 'allowed', True):
462 if not ui.configbool('subrepos', 'allowed', True):
463 raise error.Abort(_('subrepos not enabled'),
463 raise error.Abort(_('subrepos not enabled'),
464 hint=_("see 'hg help config.subrepos' for details"))
464 hint=_("see 'hg help config.subrepos' for details"))
465
465
466 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
466 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
467 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
467 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
468 raise error.Abort(_('%s subrepos not allowed') % kind,
468 raise error.Abort(_('%s subrepos not allowed') % kind,
469 hint=_("see 'hg help config.subrepos' for details"))
469 hint=_("see 'hg help config.subrepos' for details"))
470
470
471 if kind not in types:
471 if kind not in types:
472 raise error.Abort(_('unknown subrepo type %s') % kind)
472 raise error.Abort(_('unknown subrepo type %s') % kind)
473
473
474 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
474 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
475 """return instance of the right subrepo class for subrepo in path"""
475 """return instance of the right subrepo class for subrepo in path"""
476 # subrepo inherently violates our import layering rules
476 # subrepo inherently violates our import layering rules
477 # because it wants to make repo objects from deep inside the stack
477 # because it wants to make repo objects from deep inside the stack
478 # so we manually delay the circular imports to not break
478 # so we manually delay the circular imports to not break
479 # scripts that don't use our demand-loading
479 # scripts that don't use our demand-loading
480 global hg
480 global hg
481 from . import hg as h
481 from . import hg as h
482 hg = h
482 hg = h
483
483
484 repo = ctx.repo()
484 repo = ctx.repo()
485 _auditsubrepopath(repo, path)
485 _auditsubrepopath(repo, path)
486 state = ctx.substate[path]
486 state = ctx.substate[path]
487 _checktype(repo.ui, state[2])
487 _checktype(repo.ui, state[2])
488 if allowwdir:
488 if allowwdir:
489 state = (state[0], ctx.subrev(path), state[2])
489 state = (state[0], ctx.subrev(path), state[2])
490 return types[state[2]](ctx, path, state[:2], allowcreate)
490 return types[state[2]](ctx, path, state[:2], allowcreate)
491
491
492 def nullsubrepo(ctx, path, pctx):
492 def nullsubrepo(ctx, path, pctx):
493 """return an empty subrepo in pctx for the extant subrepo in ctx"""
493 """return an empty subrepo in pctx for the extant subrepo in ctx"""
494 # subrepo inherently violates our import layering rules
494 # subrepo inherently violates our import layering rules
495 # because it wants to make repo objects from deep inside the stack
495 # because it wants to make repo objects from deep inside the stack
496 # so we manually delay the circular imports to not break
496 # so we manually delay the circular imports to not break
497 # scripts that don't use our demand-loading
497 # scripts that don't use our demand-loading
498 global hg
498 global hg
499 from . import hg as h
499 from . import hg as h
500 hg = h
500 hg = h
501
501
502 repo = ctx.repo()
502 repo = ctx.repo()
503 _auditsubrepopath(repo, path)
503 _auditsubrepopath(repo, path)
504 state = ctx.substate[path]
504 state = ctx.substate[path]
505 _checktype(repo.ui, state[2])
505 _checktype(repo.ui, state[2])
506 subrev = ''
506 subrev = ''
507 if state[2] == 'hg':
507 if state[2] == 'hg':
508 subrev = "0" * 40
508 subrev = "0" * 40
509 return types[state[2]](pctx, path, (state[0], subrev), True)
509 return types[state[2]](pctx, path, (state[0], subrev), True)
510
510
511 def newcommitphase(ui, ctx):
511 def newcommitphase(ui, ctx):
512 commitphase = phases.newcommitphase(ui)
512 commitphase = phases.newcommitphase(ui)
513 substate = getattr(ctx, "substate", None)
513 substate = getattr(ctx, "substate", None)
514 if not substate:
514 if not substate:
515 return commitphase
515 return commitphase
516 check = ui.config('phases', 'checksubrepos')
516 check = ui.config('phases', 'checksubrepos')
517 if check not in ('ignore', 'follow', 'abort'):
517 if check not in ('ignore', 'follow', 'abort'):
518 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
518 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
519 % (check))
519 % (check))
520 if check == 'ignore':
520 if check == 'ignore':
521 return commitphase
521 return commitphase
522 maxphase = phases.public
522 maxphase = phases.public
523 maxsub = None
523 maxsub = None
524 for s in sorted(substate):
524 for s in sorted(substate):
525 sub = ctx.sub(s)
525 sub = ctx.sub(s)
526 subphase = sub.phase(substate[s][1])
526 subphase = sub.phase(substate[s][1])
527 if maxphase < subphase:
527 if maxphase < subphase:
528 maxphase = subphase
528 maxphase = subphase
529 maxsub = s
529 maxsub = s
530 if commitphase < maxphase:
530 if commitphase < maxphase:
531 if check == 'abort':
531 if check == 'abort':
532 raise error.Abort(_("can't commit in %s phase"
532 raise error.Abort(_("can't commit in %s phase"
533 " conflicting %s from subrepository %s") %
533 " conflicting %s from subrepository %s") %
534 (phases.phasenames[commitphase],
534 (phases.phasenames[commitphase],
535 phases.phasenames[maxphase], maxsub))
535 phases.phasenames[maxphase], maxsub))
536 ui.warn(_("warning: changes are committed in"
536 ui.warn(_("warning: changes are committed in"
537 " %s phase from subrepository %s\n") %
537 " %s phase from subrepository %s\n") %
538 (phases.phasenames[maxphase], maxsub))
538 (phases.phasenames[maxphase], maxsub))
539 return maxphase
539 return maxphase
540 return commitphase
540 return commitphase
541
541
542 # subrepo classes need to implement the following abstract class:
542 # subrepo classes need to implement the following abstract class:
543
543
544 class abstractsubrepo(object):
544 class abstractsubrepo(object):
545
545
546 def __init__(self, ctx, path):
546 def __init__(self, ctx, path):
547 """Initialize abstractsubrepo part
547 """Initialize abstractsubrepo part
548
548
549 ``ctx`` is the context referring this subrepository in the
549 ``ctx`` is the context referring this subrepository in the
550 parent repository.
550 parent repository.
551
551
552 ``path`` is the path to this subrepository as seen from
552 ``path`` is the path to this subrepository as seen from
553 innermost repository.
553 innermost repository.
554 """
554 """
555 self.ui = ctx.repo().ui
555 self.ui = ctx.repo().ui
556 self._ctx = ctx
556 self._ctx = ctx
557 self._path = path
557 self._path = path
558
558
559 def addwebdirpath(self, serverpath, webconf):
559 def addwebdirpath(self, serverpath, webconf):
560 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
560 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
561
561
562 ``serverpath`` is the path component of the URL for this repo.
562 ``serverpath`` is the path component of the URL for this repo.
563
563
564 ``webconf`` is the dictionary of hgwebdir entries.
564 ``webconf`` is the dictionary of hgwebdir entries.
565 """
565 """
566 pass
566 pass
567
567
568 def storeclean(self, path):
568 def storeclean(self, path):
569 """
569 """
570 returns true if the repository has not changed since it was last
570 returns true if the repository has not changed since it was last
571 cloned from or pushed to a given repository.
571 cloned from or pushed to a given repository.
572 """
572 """
573 return False
573 return False
574
574
575 def dirty(self, ignoreupdate=False, missing=False):
575 def dirty(self, ignoreupdate=False, missing=False):
576 """returns true if the dirstate of the subrepo is dirty or does not
576 """returns true if the dirstate of the subrepo is dirty or does not
577 match current stored state. If ignoreupdate is true, only check
577 match current stored state. If ignoreupdate is true, only check
578 whether the subrepo has uncommitted changes in its dirstate. If missing
578 whether the subrepo has uncommitted changes in its dirstate. If missing
579 is true, check for deleted files.
579 is true, check for deleted files.
580 """
580 """
581 raise NotImplementedError
581 raise NotImplementedError
582
582
583 def dirtyreason(self, ignoreupdate=False, missing=False):
583 def dirtyreason(self, ignoreupdate=False, missing=False):
584 """return reason string if it is ``dirty()``
584 """return reason string if it is ``dirty()``
585
585
586 Returned string should have enough information for the message
586 Returned string should have enough information for the message
587 of exception.
587 of exception.
588
588
589 This returns None, otherwise.
589 This returns None, otherwise.
590 """
590 """
591 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
591 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
592 return _('uncommitted changes in subrepository "%s"'
592 return _('uncommitted changes in subrepository "%s"'
593 ) % subrelpath(self)
593 ) % subrelpath(self)
594
594
595 def bailifchanged(self, ignoreupdate=False, hint=None):
595 def bailifchanged(self, ignoreupdate=False, hint=None):
596 """raise Abort if subrepository is ``dirty()``
596 """raise Abort if subrepository is ``dirty()``
597 """
597 """
598 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
598 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
599 missing=True)
599 missing=True)
600 if dirtyreason:
600 if dirtyreason:
601 raise error.Abort(dirtyreason, hint=hint)
601 raise error.Abort(dirtyreason, hint=hint)
602
602
603 def basestate(self):
603 def basestate(self):
604 """current working directory base state, disregarding .hgsubstate
604 """current working directory base state, disregarding .hgsubstate
605 state and working directory modifications"""
605 state and working directory modifications"""
606 raise NotImplementedError
606 raise NotImplementedError
607
607
608 def checknested(self, path):
608 def checknested(self, path):
609 """check if path is a subrepository within this repository"""
609 """check if path is a subrepository within this repository"""
610 return False
610 return False
611
611
612 def commit(self, text, user, date):
612 def commit(self, text, user, date):
613 """commit the current changes to the subrepo with the given
613 """commit the current changes to the subrepo with the given
614 log message. Use given user and date if possible. Return the
614 log message. Use given user and date if possible. Return the
615 new state of the subrepo.
615 new state of the subrepo.
616 """
616 """
617 raise NotImplementedError
617 raise NotImplementedError
618
618
619 def phase(self, state):
619 def phase(self, state):
620 """returns phase of specified state in the subrepository.
620 """returns phase of specified state in the subrepository.
621 """
621 """
622 return phases.public
622 return phases.public
623
623
624 def remove(self):
624 def remove(self):
625 """remove the subrepo
625 """remove the subrepo
626
626
627 (should verify the dirstate is not dirty first)
627 (should verify the dirstate is not dirty first)
628 """
628 """
629 raise NotImplementedError
629 raise NotImplementedError
630
630
631 def get(self, state, overwrite=False):
631 def get(self, state, overwrite=False):
632 """run whatever commands are needed to put the subrepo into
632 """run whatever commands are needed to put the subrepo into
633 this state
633 this state
634 """
634 """
635 raise NotImplementedError
635 raise NotImplementedError
636
636
637 def merge(self, state):
637 def merge(self, state):
638 """merge currently-saved state with the new state."""
638 """merge currently-saved state with the new state."""
639 raise NotImplementedError
639 raise NotImplementedError
640
640
641 def push(self, opts):
641 def push(self, opts):
642 """perform whatever action is analogous to 'hg push'
642 """perform whatever action is analogous to 'hg push'
643
643
644 This may be a no-op on some systems.
644 This may be a no-op on some systems.
645 """
645 """
646 raise NotImplementedError
646 raise NotImplementedError
647
647
648 def add(self, ui, match, prefix, explicitonly, **opts):
648 def add(self, ui, match, prefix, explicitonly, **opts):
649 return []
649 return []
650
650
651 def addremove(self, matcher, prefix, opts, dry_run, similarity):
651 def addremove(self, matcher, prefix, opts, dry_run, similarity):
652 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
652 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
653 return 1
653 return 1
654
654
655 def cat(self, match, fm, fntemplate, prefix, **opts):
655 def cat(self, match, fm, fntemplate, prefix, **opts):
656 return 1
656 return 1
657
657
658 def status(self, rev2, **opts):
658 def status(self, rev2, **opts):
659 return scmutil.status([], [], [], [], [], [], [])
659 return scmutil.status([], [], [], [], [], [], [])
660
660
661 def diff(self, ui, diffopts, node2, match, prefix, **opts):
661 def diff(self, ui, diffopts, node2, match, prefix, **opts):
662 pass
662 pass
663
663
664 def outgoing(self, ui, dest, opts):
664 def outgoing(self, ui, dest, opts):
665 return 1
665 return 1
666
666
667 def incoming(self, ui, source, opts):
667 def incoming(self, ui, source, opts):
668 return 1
668 return 1
669
669
670 def files(self):
670 def files(self):
671 """return filename iterator"""
671 """return filename iterator"""
672 raise NotImplementedError
672 raise NotImplementedError
673
673
674 def filedata(self, name, decode):
674 def filedata(self, name, decode):
675 """return file data, optionally passed through repo decoders"""
675 """return file data, optionally passed through repo decoders"""
676 raise NotImplementedError
676 raise NotImplementedError
677
677
678 def fileflags(self, name):
678 def fileflags(self, name):
679 """return file flags"""
679 """return file flags"""
680 return ''
680 return ''
681
681
682 def getfileset(self, expr):
682 def getfileset(self, expr):
683 """Resolve the fileset expression for this repo"""
683 """Resolve the fileset expression for this repo"""
684 return set()
684 return set()
685
685
686 def printfiles(self, ui, m, fm, fmt, subrepos):
686 def printfiles(self, ui, m, fm, fmt, subrepos):
687 """handle the files command for this subrepo"""
687 """handle the files command for this subrepo"""
688 return 1
688 return 1
689
689
690 def archive(self, archiver, prefix, match=None, decode=True):
690 def archive(self, archiver, prefix, match=None, decode=True):
691 if match is not None:
691 if match is not None:
692 files = [f for f in self.files() if match(f)]
692 files = [f for f in self.files() if match(f)]
693 else:
693 else:
694 files = self.files()
694 files = self.files()
695 total = len(files)
695 total = len(files)
696 relpath = subrelpath(self)
696 relpath = subrelpath(self)
697 self.ui.progress(_('archiving (%s)') % relpath, 0,
697 self.ui.progress(_('archiving (%s)') % relpath, 0,
698 unit=_('files'), total=total)
698 unit=_('files'), total=total)
699 for i, name in enumerate(files):
699 for i, name in enumerate(files):
700 flags = self.fileflags(name)
700 flags = self.fileflags(name)
701 mode = 'x' in flags and 0o755 or 0o644
701 mode = 'x' in flags and 0o755 or 0o644
702 symlink = 'l' in flags
702 symlink = 'l' in flags
703 archiver.addfile(prefix + self._path + '/' + name,
703 archiver.addfile(prefix + self._path + '/' + name,
704 mode, symlink, self.filedata(name, decode))
704 mode, symlink, self.filedata(name, decode))
705 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
705 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
706 unit=_('files'), total=total)
706 unit=_('files'), total=total)
707 self.ui.progress(_('archiving (%s)') % relpath, None)
707 self.ui.progress(_('archiving (%s)') % relpath, None)
708 return total
708 return total
709
709
710 def walk(self, match):
710 def walk(self, match):
711 '''
711 '''
712 walk recursively through the directory tree, finding all files
712 walk recursively through the directory tree, finding all files
713 matched by the match function
713 matched by the match function
714 '''
714 '''
715
715
716 def forget(self, match, prefix):
716 def forget(self, match, prefix):
717 return ([], [])
717 return ([], [])
718
718
719 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
719 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
720 """remove the matched files from the subrepository and the filesystem,
720 """remove the matched files from the subrepository and the filesystem,
721 possibly by force and/or after the file has been removed from the
721 possibly by force and/or after the file has been removed from the
722 filesystem. Return 0 on success, 1 on any warning.
722 filesystem. Return 0 on success, 1 on any warning.
723 """
723 """
724 warnings.append(_("warning: removefiles not implemented (%s)")
724 warnings.append(_("warning: removefiles not implemented (%s)")
725 % self._path)
725 % self._path)
726 return 1
726 return 1
727
727
728 def revert(self, substate, *pats, **opts):
728 def revert(self, substate, *pats, **opts):
729 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
729 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
730 % (substate[0], substate[2]))
730 % (substate[0], substate[2]))
731 return []
731 return []
732
732
733 def shortid(self, revid):
733 def shortid(self, revid):
734 return revid
734 return revid
735
735
736 def unshare(self):
736 def unshare(self):
737 '''
737 '''
738 convert this repository from shared to normal storage.
738 convert this repository from shared to normal storage.
739 '''
739 '''
740
740
741 def verify(self):
741 def verify(self):
742 '''verify the integrity of the repository. Return 0 on success or
742 '''verify the integrity of the repository. Return 0 on success or
743 warning, 1 on any error.
743 warning, 1 on any error.
744 '''
744 '''
745 return 0
745 return 0
746
746
747 @propertycache
747 @propertycache
748 def wvfs(self):
748 def wvfs(self):
749 """return vfs to access the working directory of this subrepository
749 """return vfs to access the working directory of this subrepository
750 """
750 """
751 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
751 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
752
752
753 @propertycache
753 @propertycache
754 def _relpath(self):
754 def _relpath(self):
755 """return path to this subrepository as seen from outermost repository
755 """return path to this subrepository as seen from outermost repository
756 """
756 """
757 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
757 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
758
758
759 class hgsubrepo(abstractsubrepo):
759 class hgsubrepo(abstractsubrepo):
760 def __init__(self, ctx, path, state, allowcreate):
760 def __init__(self, ctx, path, state, allowcreate):
761 super(hgsubrepo, self).__init__(ctx, path)
761 super(hgsubrepo, self).__init__(ctx, path)
762 self._state = state
762 self._state = state
763 r = ctx.repo()
763 r = ctx.repo()
764 root = r.wjoin(path)
764 root = r.wjoin(path)
765 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
765 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
766 self._repo = hg.repository(r.baseui, root, create=create)
766 self._repo = hg.repository(r.baseui, root, create=create)
767
767
768 # Propagate the parent's --hidden option
768 # Propagate the parent's --hidden option
769 if r is r.unfiltered():
769 if r is r.unfiltered():
770 self._repo = self._repo.unfiltered()
770 self._repo = self._repo.unfiltered()
771
771
772 self.ui = self._repo.ui
772 self.ui = self._repo.ui
773 for s, k in [('ui', 'commitsubrepos')]:
773 for s, k in [('ui', 'commitsubrepos')]:
774 v = r.ui.config(s, k)
774 v = r.ui.config(s, k)
775 if v:
775 if v:
776 self.ui.setconfig(s, k, v, 'subrepo')
776 self.ui.setconfig(s, k, v, 'subrepo')
777 # internal config: ui._usedassubrepo
777 # internal config: ui._usedassubrepo
778 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
778 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
779 self._initrepo(r, state[0], create)
779 self._initrepo(r, state[0], create)
780
780
781 @annotatesubrepoerror
781 @annotatesubrepoerror
782 def addwebdirpath(self, serverpath, webconf):
782 def addwebdirpath(self, serverpath, webconf):
783 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
783 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
784
784
785 def storeclean(self, path):
785 def storeclean(self, path):
786 with self._repo.lock():
786 with self._repo.lock():
787 return self._storeclean(path)
787 return self._storeclean(path)
788
788
789 def _storeclean(self, path):
789 def _storeclean(self, path):
790 clean = True
790 clean = True
791 itercache = self._calcstorehash(path)
791 itercache = self._calcstorehash(path)
792 for filehash in self._readstorehashcache(path):
792 for filehash in self._readstorehashcache(path):
793 if filehash != next(itercache, None):
793 if filehash != next(itercache, None):
794 clean = False
794 clean = False
795 break
795 break
796 if clean:
796 if clean:
797 # if not empty:
797 # if not empty:
798 # the cached and current pull states have a different size
798 # the cached and current pull states have a different size
799 clean = next(itercache, None) is None
799 clean = next(itercache, None) is None
800 return clean
800 return clean
801
801
802 def _calcstorehash(self, remotepath):
802 def _calcstorehash(self, remotepath):
803 '''calculate a unique "store hash"
803 '''calculate a unique "store hash"
804
804
805 This method is used to to detect when there are changes that may
805 This method is used to to detect when there are changes that may
806 require a push to a given remote path.'''
806 require a push to a given remote path.'''
807 # sort the files that will be hashed in increasing (likely) file size
807 # sort the files that will be hashed in increasing (likely) file size
808 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
808 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
809 yield '# %s\n' % _expandedabspath(remotepath)
809 yield '# %s\n' % _expandedabspath(remotepath)
810 vfs = self._repo.vfs
810 vfs = self._repo.vfs
811 for relname in filelist:
811 for relname in filelist:
812 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
812 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
813 yield '%s = %s\n' % (relname, filehash)
813 yield '%s = %s\n' % (relname, filehash)
814
814
815 @propertycache
815 @propertycache
816 def _cachestorehashvfs(self):
816 def _cachestorehashvfs(self):
817 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
817 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
818
818
819 def _readstorehashcache(self, remotepath):
819 def _readstorehashcache(self, remotepath):
820 '''read the store hash cache for a given remote repository'''
820 '''read the store hash cache for a given remote repository'''
821 cachefile = _getstorehashcachename(remotepath)
821 cachefile = _getstorehashcachename(remotepath)
822 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
822 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
823
823
824 def _cachestorehash(self, remotepath):
824 def _cachestorehash(self, remotepath):
825 '''cache the current store hash
825 '''cache the current store hash
826
826
827 Each remote repo requires its own store hash cache, because a subrepo
827 Each remote repo requires its own store hash cache, because a subrepo
828 store may be "clean" versus a given remote repo, but not versus another
828 store may be "clean" versus a given remote repo, but not versus another
829 '''
829 '''
830 cachefile = _getstorehashcachename(remotepath)
830 cachefile = _getstorehashcachename(remotepath)
831 with self._repo.lock():
831 with self._repo.lock():
832 storehash = list(self._calcstorehash(remotepath))
832 storehash = list(self._calcstorehash(remotepath))
833 vfs = self._cachestorehashvfs
833 vfs = self._cachestorehashvfs
834 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
834 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
835
835
836 def _getctx(self):
836 def _getctx(self):
837 '''fetch the context for this subrepo revision, possibly a workingctx
837 '''fetch the context for this subrepo revision, possibly a workingctx
838 '''
838 '''
839 if self._ctx.rev() is None:
839 if self._ctx.rev() is None:
840 return self._repo[None] # workingctx if parent is workingctx
840 return self._repo[None] # workingctx if parent is workingctx
841 else:
841 else:
842 rev = self._state[1]
842 rev = self._state[1]
843 return self._repo[rev]
843 return self._repo[rev]
844
844
845 @annotatesubrepoerror
845 @annotatesubrepoerror
846 def _initrepo(self, parentrepo, source, create):
846 def _initrepo(self, parentrepo, source, create):
847 self._repo._subparent = parentrepo
847 self._repo._subparent = parentrepo
848 self._repo._subsource = source
848 self._repo._subsource = source
849
849
850 if create:
850 if create:
851 lines = ['[paths]\n']
851 lines = ['[paths]\n']
852
852
853 def addpathconfig(key, value):
853 def addpathconfig(key, value):
854 if value:
854 if value:
855 lines.append('%s = %s\n' % (key, value))
855 lines.append('%s = %s\n' % (key, value))
856 self.ui.setconfig('paths', key, value, 'subrepo')
856 self.ui.setconfig('paths', key, value, 'subrepo')
857
857
858 defpath = _abssource(self._repo, abort=False)
858 defpath = _abssource(self._repo, abort=False)
859 defpushpath = _abssource(self._repo, True, abort=False)
859 defpushpath = _abssource(self._repo, True, abort=False)
860 addpathconfig('default', defpath)
860 addpathconfig('default', defpath)
861 if defpath != defpushpath:
861 if defpath != defpushpath:
862 addpathconfig('default-push', defpushpath)
862 addpathconfig('default-push', defpushpath)
863
863
864 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
864 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
865
865
866 @annotatesubrepoerror
866 @annotatesubrepoerror
867 def add(self, ui, match, prefix, explicitonly, **opts):
867 def add(self, ui, match, prefix, explicitonly, **opts):
868 return cmdutil.add(ui, self._repo, match,
868 return cmdutil.add(ui, self._repo, match,
869 self.wvfs.reljoin(prefix, self._path),
869 self.wvfs.reljoin(prefix, self._path),
870 explicitonly, **opts)
870 explicitonly, **opts)
871
871
872 @annotatesubrepoerror
872 @annotatesubrepoerror
873 def addremove(self, m, prefix, opts, dry_run, similarity):
873 def addremove(self, m, prefix, opts, dry_run, similarity):
874 # In the same way as sub directories are processed, once in a subrepo,
874 # In the same way as sub directories are processed, once in a subrepo,
875 # always entry any of its subrepos. Don't corrupt the options that will
875 # always entry any of its subrepos. Don't corrupt the options that will
876 # be used to process sibling subrepos however.
876 # be used to process sibling subrepos however.
877 opts = copy.copy(opts)
877 opts = copy.copy(opts)
878 opts['subrepos'] = True
878 opts['subrepos'] = True
879 return scmutil.addremove(self._repo, m,
879 return scmutil.addremove(self._repo, m,
880 self.wvfs.reljoin(prefix, self._path), opts,
880 self.wvfs.reljoin(prefix, self._path), opts,
881 dry_run, similarity)
881 dry_run, similarity)
882
882
883 @annotatesubrepoerror
883 @annotatesubrepoerror
884 def cat(self, match, fm, fntemplate, prefix, **opts):
884 def cat(self, match, fm, fntemplate, prefix, **opts):
885 rev = self._state[1]
885 rev = self._state[1]
886 ctx = self._repo[rev]
886 ctx = self._repo[rev]
887 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
887 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
888 prefix, **opts)
888 prefix, **opts)
889
889
890 @annotatesubrepoerror
890 @annotatesubrepoerror
891 def status(self, rev2, **opts):
891 def status(self, rev2, **opts):
892 try:
892 try:
893 rev1 = self._state[1]
893 rev1 = self._state[1]
894 ctx1 = self._repo[rev1]
894 ctx1 = self._repo[rev1]
895 ctx2 = self._repo[rev2]
895 ctx2 = self._repo[rev2]
896 return self._repo.status(ctx1, ctx2, **opts)
896 return self._repo.status(ctx1, ctx2, **opts)
897 except error.RepoLookupError as inst:
897 except error.RepoLookupError as inst:
898 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
898 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
899 % (inst, subrelpath(self)))
899 % (inst, subrelpath(self)))
900 return scmutil.status([], [], [], [], [], [], [])
900 return scmutil.status([], [], [], [], [], [], [])
901
901
902 @annotatesubrepoerror
902 @annotatesubrepoerror
903 def diff(self, ui, diffopts, node2, match, prefix, **opts):
903 def diff(self, ui, diffopts, node2, match, prefix, **opts):
904 try:
904 try:
905 node1 = node.bin(self._state[1])
905 node1 = node.bin(self._state[1])
906 # We currently expect node2 to come from substate and be
906 # We currently expect node2 to come from substate and be
907 # in hex format
907 # in hex format
908 if node2 is not None:
908 if node2 is not None:
909 node2 = node.bin(node2)
909 node2 = node.bin(node2)
910 cmdutil.diffordiffstat(ui, self._repo, diffopts,
910 cmdutil.diffordiffstat(ui, self._repo, diffopts,
911 node1, node2, match,
911 node1, node2, match,
912 prefix=posixpath.join(prefix, self._path),
912 prefix=posixpath.join(prefix, self._path),
913 listsubrepos=True, **opts)
913 listsubrepos=True, **opts)
914 except error.RepoLookupError as inst:
914 except error.RepoLookupError as inst:
915 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
915 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
916 % (inst, subrelpath(self)))
916 % (inst, subrelpath(self)))
917
917
918 @annotatesubrepoerror
918 @annotatesubrepoerror
919 def archive(self, archiver, prefix, match=None, decode=True):
919 def archive(self, archiver, prefix, match=None, decode=True):
920 self._get(self._state + ('hg',))
920 self._get(self._state + ('hg',))
921 total = abstractsubrepo.archive(self, archiver, prefix, match)
921 total = abstractsubrepo.archive(self, archiver, prefix, match)
922 rev = self._state[1]
922 rev = self._state[1]
923 ctx = self._repo[rev]
923 ctx = self._repo[rev]
924 for subpath in ctx.substate:
924 for subpath in ctx.substate:
925 s = subrepo(ctx, subpath, True)
925 s = subrepo(ctx, subpath, True)
926 submatch = matchmod.subdirmatcher(subpath, match)
926 submatch = matchmod.subdirmatcher(subpath, match)
927 total += s.archive(archiver, prefix + self._path + '/', submatch,
927 total += s.archive(archiver, prefix + self._path + '/', submatch,
928 decode)
928 decode)
929 return total
929 return total
930
930
931 @annotatesubrepoerror
931 @annotatesubrepoerror
932 def dirty(self, ignoreupdate=False, missing=False):
932 def dirty(self, ignoreupdate=False, missing=False):
933 r = self._state[1]
933 r = self._state[1]
934 if r == '' and not ignoreupdate: # no state recorded
934 if r == '' and not ignoreupdate: # no state recorded
935 return True
935 return True
936 w = self._repo[None]
936 w = self._repo[None]
937 if r != w.p1().hex() and not ignoreupdate:
937 if r != w.p1().hex() and not ignoreupdate:
938 # different version checked out
938 # different version checked out
939 return True
939 return True
940 return w.dirty(missing=missing) # working directory changed
940 return w.dirty(missing=missing) # working directory changed
941
941
942 def basestate(self):
942 def basestate(self):
943 return self._repo['.'].hex()
943 return self._repo['.'].hex()
944
944
945 def checknested(self, path):
945 def checknested(self, path):
946 return self._repo._checknested(self._repo.wjoin(path))
946 return self._repo._checknested(self._repo.wjoin(path))
947
947
948 @annotatesubrepoerror
948 @annotatesubrepoerror
949 def commit(self, text, user, date):
949 def commit(self, text, user, date):
950 # don't bother committing in the subrepo if it's only been
950 # don't bother committing in the subrepo if it's only been
951 # updated
951 # updated
952 if not self.dirty(True):
952 if not self.dirty(True):
953 return self._repo['.'].hex()
953 return self._repo['.'].hex()
954 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
954 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
955 n = self._repo.commit(text, user, date)
955 n = self._repo.commit(text, user, date)
956 if not n:
956 if not n:
957 return self._repo['.'].hex() # different version checked out
957 return self._repo['.'].hex() # different version checked out
958 return node.hex(n)
958 return node.hex(n)
959
959
960 @annotatesubrepoerror
960 @annotatesubrepoerror
961 def phase(self, state):
961 def phase(self, state):
962 return self._repo[state].phase()
962 return self._repo[state].phase()
963
963
964 @annotatesubrepoerror
964 @annotatesubrepoerror
965 def remove(self):
965 def remove(self):
966 # we can't fully delete the repository as it may contain
966 # we can't fully delete the repository as it may contain
967 # local-only history
967 # local-only history
968 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
968 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
969 hg.clean(self._repo, node.nullid, False)
969 hg.clean(self._repo, node.nullid, False)
970
970
971 def _get(self, state):
971 def _get(self, state):
972 source, revision, kind = state
972 source, revision, kind = state
973 parentrepo = self._repo._subparent
973 parentrepo = self._repo._subparent
974
974
975 if revision in self._repo.unfiltered():
975 if revision in self._repo.unfiltered():
976 # Allow shared subrepos tracked at null to setup the sharedpath
976 # Allow shared subrepos tracked at null to setup the sharedpath
977 if len(self._repo) != 0 or not parentrepo.shared():
977 if len(self._repo) != 0 or not parentrepo.shared():
978 return True
978 return True
979 self._repo._subsource = source
979 self._repo._subsource = source
980 srcurl = _abssource(self._repo)
980 srcurl = _abssource(self._repo)
981 other = hg.peer(self._repo, {}, srcurl)
981 other = hg.peer(self._repo, {}, srcurl)
982 if len(self._repo) == 0:
982 if len(self._repo) == 0:
983 # use self._repo.vfs instead of self.wvfs to remove .hg only
983 # use self._repo.vfs instead of self.wvfs to remove .hg only
984 self._repo.vfs.rmtree()
984 self._repo.vfs.rmtree()
985
985
986 # A remote subrepo could be shared if there is a local copy
986 # A remote subrepo could be shared if there is a local copy
987 # relative to the parent's share source. But clone pooling doesn't
987 # relative to the parent's share source. But clone pooling doesn't
988 # assemble the repos in a tree, so that can't be consistently done.
988 # assemble the repos in a tree, so that can't be consistently done.
989 # A simpler option is for the user to configure clone pooling, and
989 # A simpler option is for the user to configure clone pooling, and
990 # work with that.
990 # work with that.
991 if parentrepo.shared() and hg.islocal(srcurl):
991 if parentrepo.shared() and hg.islocal(srcurl):
992 self.ui.status(_('sharing subrepo %s from %s\n')
992 self.ui.status(_('sharing subrepo %s from %s\n')
993 % (subrelpath(self), srcurl))
993 % (subrelpath(self), srcurl))
994 shared = hg.share(self._repo._subparent.baseui,
994 shared = hg.share(self._repo._subparent.baseui,
995 other, self._repo.root,
995 other, self._repo.root,
996 update=False, bookmarks=False)
996 update=False, bookmarks=False)
997 self._repo = shared.local()
997 self._repo = shared.local()
998 else:
998 else:
999 # TODO: find a common place for this and this code in the
1000 # share.py wrap of the clone command.
1001 if parentrepo.shared():
1002 pool = self.ui.config('share', 'pool')
1003 if pool:
1004 pool = util.expandpath(pool)
1005
1006 shareopts = {
1007 'pool': pool,
1008 'mode': self.ui.config('share', 'poolnaming'),
1009 }
1010 else:
1011 shareopts = {}
1012
999 self.ui.status(_('cloning subrepo %s from %s\n')
1013 self.ui.status(_('cloning subrepo %s from %s\n')
1000 % (subrelpath(self), srcurl))
1014 % (subrelpath(self), srcurl))
1001 other, cloned = hg.clone(self._repo._subparent.baseui, {},
1015 other, cloned = hg.clone(self._repo._subparent.baseui, {},
1002 other, self._repo.root,
1016 other, self._repo.root,
1003 update=False)
1017 update=False, shareopts=shareopts)
1004 self._repo = cloned.local()
1018 self._repo = cloned.local()
1005 self._initrepo(parentrepo, source, create=True)
1019 self._initrepo(parentrepo, source, create=True)
1006 self._cachestorehash(srcurl)
1020 self._cachestorehash(srcurl)
1007 else:
1021 else:
1008 self.ui.status(_('pulling subrepo %s from %s\n')
1022 self.ui.status(_('pulling subrepo %s from %s\n')
1009 % (subrelpath(self), srcurl))
1023 % (subrelpath(self), srcurl))
1010 cleansub = self.storeclean(srcurl)
1024 cleansub = self.storeclean(srcurl)
1011 exchange.pull(self._repo, other)
1025 exchange.pull(self._repo, other)
1012 if cleansub:
1026 if cleansub:
1013 # keep the repo clean after pull
1027 # keep the repo clean after pull
1014 self._cachestorehash(srcurl)
1028 self._cachestorehash(srcurl)
1015 return False
1029 return False
1016
1030
1017 @annotatesubrepoerror
1031 @annotatesubrepoerror
1018 def get(self, state, overwrite=False):
1032 def get(self, state, overwrite=False):
1019 inrepo = self._get(state)
1033 inrepo = self._get(state)
1020 source, revision, kind = state
1034 source, revision, kind = state
1021 repo = self._repo
1035 repo = self._repo
1022 repo.ui.debug("getting subrepo %s\n" % self._path)
1036 repo.ui.debug("getting subrepo %s\n" % self._path)
1023 if inrepo:
1037 if inrepo:
1024 urepo = repo.unfiltered()
1038 urepo = repo.unfiltered()
1025 ctx = urepo[revision]
1039 ctx = urepo[revision]
1026 if ctx.hidden():
1040 if ctx.hidden():
1027 urepo.ui.warn(
1041 urepo.ui.warn(
1028 _('revision %s in subrepository "%s" is hidden\n') \
1042 _('revision %s in subrepository "%s" is hidden\n') \
1029 % (revision[0:12], self._path))
1043 % (revision[0:12], self._path))
1030 repo = urepo
1044 repo = urepo
1031 hg.updaterepo(repo, revision, overwrite)
1045 hg.updaterepo(repo, revision, overwrite)
1032
1046
1033 @annotatesubrepoerror
1047 @annotatesubrepoerror
1034 def merge(self, state):
1048 def merge(self, state):
1035 self._get(state)
1049 self._get(state)
1036 cur = self._repo['.']
1050 cur = self._repo['.']
1037 dst = self._repo[state[1]]
1051 dst = self._repo[state[1]]
1038 anc = dst.ancestor(cur)
1052 anc = dst.ancestor(cur)
1039
1053
1040 def mergefunc():
1054 def mergefunc():
1041 if anc == cur and dst.branch() == cur.branch():
1055 if anc == cur and dst.branch() == cur.branch():
1042 self.ui.debug('updating subrepository "%s"\n'
1056 self.ui.debug('updating subrepository "%s"\n'
1043 % subrelpath(self))
1057 % subrelpath(self))
1044 hg.update(self._repo, state[1])
1058 hg.update(self._repo, state[1])
1045 elif anc == dst:
1059 elif anc == dst:
1046 self.ui.debug('skipping subrepository "%s"\n'
1060 self.ui.debug('skipping subrepository "%s"\n'
1047 % subrelpath(self))
1061 % subrelpath(self))
1048 else:
1062 else:
1049 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
1063 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
1050 hg.merge(self._repo, state[1], remind=False)
1064 hg.merge(self._repo, state[1], remind=False)
1051
1065
1052 wctx = self._repo[None]
1066 wctx = self._repo[None]
1053 if self.dirty():
1067 if self.dirty():
1054 if anc != dst:
1068 if anc != dst:
1055 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
1069 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
1056 mergefunc()
1070 mergefunc()
1057 else:
1071 else:
1058 mergefunc()
1072 mergefunc()
1059 else:
1073 else:
1060 mergefunc()
1074 mergefunc()
1061
1075
1062 @annotatesubrepoerror
1076 @annotatesubrepoerror
1063 def push(self, opts):
1077 def push(self, opts):
1064 force = opts.get('force')
1078 force = opts.get('force')
1065 newbranch = opts.get('new_branch')
1079 newbranch = opts.get('new_branch')
1066 ssh = opts.get('ssh')
1080 ssh = opts.get('ssh')
1067
1081
1068 # push subrepos depth-first for coherent ordering
1082 # push subrepos depth-first for coherent ordering
1069 c = self._repo['']
1083 c = self._repo['']
1070 subs = c.substate # only repos that are committed
1084 subs = c.substate # only repos that are committed
1071 for s in sorted(subs):
1085 for s in sorted(subs):
1072 if c.sub(s).push(opts) == 0:
1086 if c.sub(s).push(opts) == 0:
1073 return False
1087 return False
1074
1088
1075 dsturl = _abssource(self._repo, True)
1089 dsturl = _abssource(self._repo, True)
1076 if not force:
1090 if not force:
1077 if self.storeclean(dsturl):
1091 if self.storeclean(dsturl):
1078 self.ui.status(
1092 self.ui.status(
1079 _('no changes made to subrepo %s since last push to %s\n')
1093 _('no changes made to subrepo %s since last push to %s\n')
1080 % (subrelpath(self), dsturl))
1094 % (subrelpath(self), dsturl))
1081 return None
1095 return None
1082 self.ui.status(_('pushing subrepo %s to %s\n') %
1096 self.ui.status(_('pushing subrepo %s to %s\n') %
1083 (subrelpath(self), dsturl))
1097 (subrelpath(self), dsturl))
1084 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
1098 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
1085 res = exchange.push(self._repo, other, force, newbranch=newbranch)
1099 res = exchange.push(self._repo, other, force, newbranch=newbranch)
1086
1100
1087 # the repo is now clean
1101 # the repo is now clean
1088 self._cachestorehash(dsturl)
1102 self._cachestorehash(dsturl)
1089 return res.cgresult
1103 return res.cgresult
1090
1104
1091 @annotatesubrepoerror
1105 @annotatesubrepoerror
1092 def outgoing(self, ui, dest, opts):
1106 def outgoing(self, ui, dest, opts):
1093 if 'rev' in opts or 'branch' in opts:
1107 if 'rev' in opts or 'branch' in opts:
1094 opts = copy.copy(opts)
1108 opts = copy.copy(opts)
1095 opts.pop('rev', None)
1109 opts.pop('rev', None)
1096 opts.pop('branch', None)
1110 opts.pop('branch', None)
1097 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
1111 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
1098
1112
1099 @annotatesubrepoerror
1113 @annotatesubrepoerror
1100 def incoming(self, ui, source, opts):
1114 def incoming(self, ui, source, opts):
1101 if 'rev' in opts or 'branch' in opts:
1115 if 'rev' in opts or 'branch' in opts:
1102 opts = copy.copy(opts)
1116 opts = copy.copy(opts)
1103 opts.pop('rev', None)
1117 opts.pop('rev', None)
1104 opts.pop('branch', None)
1118 opts.pop('branch', None)
1105 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
1119 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
1106
1120
1107 @annotatesubrepoerror
1121 @annotatesubrepoerror
1108 def files(self):
1122 def files(self):
1109 rev = self._state[1]
1123 rev = self._state[1]
1110 ctx = self._repo[rev]
1124 ctx = self._repo[rev]
1111 return ctx.manifest().keys()
1125 return ctx.manifest().keys()
1112
1126
1113 def filedata(self, name, decode):
1127 def filedata(self, name, decode):
1114 rev = self._state[1]
1128 rev = self._state[1]
1115 data = self._repo[rev][name].data()
1129 data = self._repo[rev][name].data()
1116 if decode:
1130 if decode:
1117 data = self._repo.wwritedata(name, data)
1131 data = self._repo.wwritedata(name, data)
1118 return data
1132 return data
1119
1133
1120 def fileflags(self, name):
1134 def fileflags(self, name):
1121 rev = self._state[1]
1135 rev = self._state[1]
1122 ctx = self._repo[rev]
1136 ctx = self._repo[rev]
1123 return ctx.flags(name)
1137 return ctx.flags(name)
1124
1138
1125 @annotatesubrepoerror
1139 @annotatesubrepoerror
1126 def printfiles(self, ui, m, fm, fmt, subrepos):
1140 def printfiles(self, ui, m, fm, fmt, subrepos):
1127 # If the parent context is a workingctx, use the workingctx here for
1141 # If the parent context is a workingctx, use the workingctx here for
1128 # consistency.
1142 # consistency.
1129 if self._ctx.rev() is None:
1143 if self._ctx.rev() is None:
1130 ctx = self._repo[None]
1144 ctx = self._repo[None]
1131 else:
1145 else:
1132 rev = self._state[1]
1146 rev = self._state[1]
1133 ctx = self._repo[rev]
1147 ctx = self._repo[rev]
1134 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1148 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1135
1149
1136 @annotatesubrepoerror
1150 @annotatesubrepoerror
1137 def getfileset(self, expr):
1151 def getfileset(self, expr):
1138 if self._ctx.rev() is None:
1152 if self._ctx.rev() is None:
1139 ctx = self._repo[None]
1153 ctx = self._repo[None]
1140 else:
1154 else:
1141 rev = self._state[1]
1155 rev = self._state[1]
1142 ctx = self._repo[rev]
1156 ctx = self._repo[rev]
1143
1157
1144 files = ctx.getfileset(expr)
1158 files = ctx.getfileset(expr)
1145
1159
1146 for subpath in ctx.substate:
1160 for subpath in ctx.substate:
1147 sub = ctx.sub(subpath)
1161 sub = ctx.sub(subpath)
1148
1162
1149 try:
1163 try:
1150 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1164 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1151 except error.LookupError:
1165 except error.LookupError:
1152 self.ui.status(_("skipping missing subrepository: %s\n")
1166 self.ui.status(_("skipping missing subrepository: %s\n")
1153 % self.wvfs.reljoin(reporelpath(self), subpath))
1167 % self.wvfs.reljoin(reporelpath(self), subpath))
1154 return files
1168 return files
1155
1169
1156 def walk(self, match):
1170 def walk(self, match):
1157 ctx = self._repo[None]
1171 ctx = self._repo[None]
1158 return ctx.walk(match)
1172 return ctx.walk(match)
1159
1173
1160 @annotatesubrepoerror
1174 @annotatesubrepoerror
1161 def forget(self, match, prefix):
1175 def forget(self, match, prefix):
1162 return cmdutil.forget(self.ui, self._repo, match,
1176 return cmdutil.forget(self.ui, self._repo, match,
1163 self.wvfs.reljoin(prefix, self._path), True)
1177 self.wvfs.reljoin(prefix, self._path), True)
1164
1178
1165 @annotatesubrepoerror
1179 @annotatesubrepoerror
1166 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1180 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1167 return cmdutil.remove(self.ui, self._repo, matcher,
1181 return cmdutil.remove(self.ui, self._repo, matcher,
1168 self.wvfs.reljoin(prefix, self._path),
1182 self.wvfs.reljoin(prefix, self._path),
1169 after, force, subrepos)
1183 after, force, subrepos)
1170
1184
1171 @annotatesubrepoerror
1185 @annotatesubrepoerror
1172 def revert(self, substate, *pats, **opts):
1186 def revert(self, substate, *pats, **opts):
1173 # reverting a subrepo is a 2 step process:
1187 # reverting a subrepo is a 2 step process:
1174 # 1. if the no_backup is not set, revert all modified
1188 # 1. if the no_backup is not set, revert all modified
1175 # files inside the subrepo
1189 # files inside the subrepo
1176 # 2. update the subrepo to the revision specified in
1190 # 2. update the subrepo to the revision specified in
1177 # the corresponding substate dictionary
1191 # the corresponding substate dictionary
1178 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1192 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1179 if not opts.get(r'no_backup'):
1193 if not opts.get(r'no_backup'):
1180 # Revert all files on the subrepo, creating backups
1194 # Revert all files on the subrepo, creating backups
1181 # Note that this will not recursively revert subrepos
1195 # Note that this will not recursively revert subrepos
1182 # We could do it if there was a set:subrepos() predicate
1196 # We could do it if there was a set:subrepos() predicate
1183 opts = opts.copy()
1197 opts = opts.copy()
1184 opts[r'date'] = None
1198 opts[r'date'] = None
1185 opts[r'rev'] = substate[1]
1199 opts[r'rev'] = substate[1]
1186
1200
1187 self.filerevert(*pats, **opts)
1201 self.filerevert(*pats, **opts)
1188
1202
1189 # Update the repo to the revision specified in the given substate
1203 # Update the repo to the revision specified in the given substate
1190 if not opts.get(r'dry_run'):
1204 if not opts.get(r'dry_run'):
1191 self.get(substate, overwrite=True)
1205 self.get(substate, overwrite=True)
1192
1206
1193 def filerevert(self, *pats, **opts):
1207 def filerevert(self, *pats, **opts):
1194 ctx = self._repo[opts[r'rev']]
1208 ctx = self._repo[opts[r'rev']]
1195 parents = self._repo.dirstate.parents()
1209 parents = self._repo.dirstate.parents()
1196 if opts.get(r'all'):
1210 if opts.get(r'all'):
1197 pats = ['set:modified()']
1211 pats = ['set:modified()']
1198 else:
1212 else:
1199 pats = []
1213 pats = []
1200 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1214 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1201
1215
1202 def shortid(self, revid):
1216 def shortid(self, revid):
1203 return revid[:12]
1217 return revid[:12]
1204
1218
1205 @annotatesubrepoerror
1219 @annotatesubrepoerror
1206 def unshare(self):
1220 def unshare(self):
1207 # subrepo inherently violates our import layering rules
1221 # subrepo inherently violates our import layering rules
1208 # because it wants to make repo objects from deep inside the stack
1222 # because it wants to make repo objects from deep inside the stack
1209 # so we manually delay the circular imports to not break
1223 # so we manually delay the circular imports to not break
1210 # scripts that don't use our demand-loading
1224 # scripts that don't use our demand-loading
1211 global hg
1225 global hg
1212 from . import hg as h
1226 from . import hg as h
1213 hg = h
1227 hg = h
1214
1228
1215 # Nothing prevents a user from sharing in a repo, and then making that a
1229 # Nothing prevents a user from sharing in a repo, and then making that a
1216 # subrepo. Alternately, the previous unshare attempt may have failed
1230 # subrepo. Alternately, the previous unshare attempt may have failed
1217 # part way through. So recurse whether or not this layer is shared.
1231 # part way through. So recurse whether or not this layer is shared.
1218 if self._repo.shared():
1232 if self._repo.shared():
1219 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
1233 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
1220
1234
1221 hg.unshare(self.ui, self._repo)
1235 hg.unshare(self.ui, self._repo)
1222
1236
1223 def verify(self):
1237 def verify(self):
1224 try:
1238 try:
1225 rev = self._state[1]
1239 rev = self._state[1]
1226 ctx = self._repo.unfiltered()[rev]
1240 ctx = self._repo.unfiltered()[rev]
1227 if ctx.hidden():
1241 if ctx.hidden():
1228 # Since hidden revisions aren't pushed/pulled, it seems worth an
1242 # Since hidden revisions aren't pushed/pulled, it seems worth an
1229 # explicit warning.
1243 # explicit warning.
1230 ui = self._repo.ui
1244 ui = self._repo.ui
1231 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1245 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1232 (self._relpath, node.short(self._ctx.node())))
1246 (self._relpath, node.short(self._ctx.node())))
1233 return 0
1247 return 0
1234 except error.RepoLookupError:
1248 except error.RepoLookupError:
1235 # A missing subrepo revision may be a case of needing to pull it, so
1249 # A missing subrepo revision may be a case of needing to pull it, so
1236 # don't treat this as an error.
1250 # don't treat this as an error.
1237 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1251 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1238 (self._relpath, node.short(self._ctx.node())))
1252 (self._relpath, node.short(self._ctx.node())))
1239 return 0
1253 return 0
1240
1254
1241 @propertycache
1255 @propertycache
1242 def wvfs(self):
1256 def wvfs(self):
1243 """return own wvfs for efficiency and consistency
1257 """return own wvfs for efficiency and consistency
1244 """
1258 """
1245 return self._repo.wvfs
1259 return self._repo.wvfs
1246
1260
1247 @propertycache
1261 @propertycache
1248 def _relpath(self):
1262 def _relpath(self):
1249 """return path to this subrepository as seen from outermost repository
1263 """return path to this subrepository as seen from outermost repository
1250 """
1264 """
1251 # Keep consistent dir separators by avoiding vfs.join(self._path)
1265 # Keep consistent dir separators by avoiding vfs.join(self._path)
1252 return reporelpath(self._repo)
1266 return reporelpath(self._repo)
1253
1267
1254 class svnsubrepo(abstractsubrepo):
1268 class svnsubrepo(abstractsubrepo):
1255 def __init__(self, ctx, path, state, allowcreate):
1269 def __init__(self, ctx, path, state, allowcreate):
1256 super(svnsubrepo, self).__init__(ctx, path)
1270 super(svnsubrepo, self).__init__(ctx, path)
1257 self._state = state
1271 self._state = state
1258 self._exe = util.findexe('svn')
1272 self._exe = util.findexe('svn')
1259 if not self._exe:
1273 if not self._exe:
1260 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1274 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1261 % self._path)
1275 % self._path)
1262
1276
1263 def _svncommand(self, commands, filename='', failok=False):
1277 def _svncommand(self, commands, filename='', failok=False):
1264 cmd = [self._exe]
1278 cmd = [self._exe]
1265 extrakw = {}
1279 extrakw = {}
1266 if not self.ui.interactive():
1280 if not self.ui.interactive():
1267 # Making stdin be a pipe should prevent svn from behaving
1281 # Making stdin be a pipe should prevent svn from behaving
1268 # interactively even if we can't pass --non-interactive.
1282 # interactively even if we can't pass --non-interactive.
1269 extrakw[r'stdin'] = subprocess.PIPE
1283 extrakw[r'stdin'] = subprocess.PIPE
1270 # Starting in svn 1.5 --non-interactive is a global flag
1284 # Starting in svn 1.5 --non-interactive is a global flag
1271 # instead of being per-command, but we need to support 1.4 so
1285 # instead of being per-command, but we need to support 1.4 so
1272 # we have to be intelligent about what commands take
1286 # we have to be intelligent about what commands take
1273 # --non-interactive.
1287 # --non-interactive.
1274 if commands[0] in ('update', 'checkout', 'commit'):
1288 if commands[0] in ('update', 'checkout', 'commit'):
1275 cmd.append('--non-interactive')
1289 cmd.append('--non-interactive')
1276 cmd.extend(commands)
1290 cmd.extend(commands)
1277 if filename is not None:
1291 if filename is not None:
1278 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1292 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1279 self._path, filename)
1293 self._path, filename)
1280 cmd.append(path)
1294 cmd.append(path)
1281 env = dict(encoding.environ)
1295 env = dict(encoding.environ)
1282 # Avoid localized output, preserve current locale for everything else.
1296 # Avoid localized output, preserve current locale for everything else.
1283 lc_all = env.get('LC_ALL')
1297 lc_all = env.get('LC_ALL')
1284 if lc_all:
1298 if lc_all:
1285 env['LANG'] = lc_all
1299 env['LANG'] = lc_all
1286 del env['LC_ALL']
1300 del env['LC_ALL']
1287 env['LC_MESSAGES'] = 'C'
1301 env['LC_MESSAGES'] = 'C'
1288 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1302 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1289 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1303 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1290 universal_newlines=True, env=env, **extrakw)
1304 universal_newlines=True, env=env, **extrakw)
1291 stdout, stderr = p.communicate()
1305 stdout, stderr = p.communicate()
1292 stderr = stderr.strip()
1306 stderr = stderr.strip()
1293 if not failok:
1307 if not failok:
1294 if p.returncode:
1308 if p.returncode:
1295 raise error.Abort(stderr or 'exited with code %d'
1309 raise error.Abort(stderr or 'exited with code %d'
1296 % p.returncode)
1310 % p.returncode)
1297 if stderr:
1311 if stderr:
1298 self.ui.warn(stderr + '\n')
1312 self.ui.warn(stderr + '\n')
1299 return stdout, stderr
1313 return stdout, stderr
1300
1314
1301 @propertycache
1315 @propertycache
1302 def _svnversion(self):
1316 def _svnversion(self):
1303 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1317 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1304 m = re.search(br'^(\d+)\.(\d+)', output)
1318 m = re.search(br'^(\d+)\.(\d+)', output)
1305 if not m:
1319 if not m:
1306 raise error.Abort(_('cannot retrieve svn tool version'))
1320 raise error.Abort(_('cannot retrieve svn tool version'))
1307 return (int(m.group(1)), int(m.group(2)))
1321 return (int(m.group(1)), int(m.group(2)))
1308
1322
1309 def _svnmissing(self):
1323 def _svnmissing(self):
1310 return not self.wvfs.exists('.svn')
1324 return not self.wvfs.exists('.svn')
1311
1325
1312 def _wcrevs(self):
1326 def _wcrevs(self):
1313 # Get the working directory revision as well as the last
1327 # Get the working directory revision as well as the last
1314 # commit revision so we can compare the subrepo state with
1328 # commit revision so we can compare the subrepo state with
1315 # both. We used to store the working directory one.
1329 # both. We used to store the working directory one.
1316 output, err = self._svncommand(['info', '--xml'])
1330 output, err = self._svncommand(['info', '--xml'])
1317 doc = xml.dom.minidom.parseString(output)
1331 doc = xml.dom.minidom.parseString(output)
1318 entries = doc.getElementsByTagName('entry')
1332 entries = doc.getElementsByTagName('entry')
1319 lastrev, rev = '0', '0'
1333 lastrev, rev = '0', '0'
1320 if entries:
1334 if entries:
1321 rev = str(entries[0].getAttribute('revision')) or '0'
1335 rev = str(entries[0].getAttribute('revision')) or '0'
1322 commits = entries[0].getElementsByTagName('commit')
1336 commits = entries[0].getElementsByTagName('commit')
1323 if commits:
1337 if commits:
1324 lastrev = str(commits[0].getAttribute('revision')) or '0'
1338 lastrev = str(commits[0].getAttribute('revision')) or '0'
1325 return (lastrev, rev)
1339 return (lastrev, rev)
1326
1340
1327 def _wcrev(self):
1341 def _wcrev(self):
1328 return self._wcrevs()[0]
1342 return self._wcrevs()[0]
1329
1343
1330 def _wcchanged(self):
1344 def _wcchanged(self):
1331 """Return (changes, extchanges, missing) where changes is True
1345 """Return (changes, extchanges, missing) where changes is True
1332 if the working directory was changed, extchanges is
1346 if the working directory was changed, extchanges is
1333 True if any of these changes concern an external entry and missing
1347 True if any of these changes concern an external entry and missing
1334 is True if any change is a missing entry.
1348 is True if any change is a missing entry.
1335 """
1349 """
1336 output, err = self._svncommand(['status', '--xml'])
1350 output, err = self._svncommand(['status', '--xml'])
1337 externals, changes, missing = [], [], []
1351 externals, changes, missing = [], [], []
1338 doc = xml.dom.minidom.parseString(output)
1352 doc = xml.dom.minidom.parseString(output)
1339 for e in doc.getElementsByTagName('entry'):
1353 for e in doc.getElementsByTagName('entry'):
1340 s = e.getElementsByTagName('wc-status')
1354 s = e.getElementsByTagName('wc-status')
1341 if not s:
1355 if not s:
1342 continue
1356 continue
1343 item = s[0].getAttribute('item')
1357 item = s[0].getAttribute('item')
1344 props = s[0].getAttribute('props')
1358 props = s[0].getAttribute('props')
1345 path = e.getAttribute('path')
1359 path = e.getAttribute('path')
1346 if item == 'external':
1360 if item == 'external':
1347 externals.append(path)
1361 externals.append(path)
1348 elif item == 'missing':
1362 elif item == 'missing':
1349 missing.append(path)
1363 missing.append(path)
1350 if (item not in ('', 'normal', 'unversioned', 'external')
1364 if (item not in ('', 'normal', 'unversioned', 'external')
1351 or props not in ('', 'none', 'normal')):
1365 or props not in ('', 'none', 'normal')):
1352 changes.append(path)
1366 changes.append(path)
1353 for path in changes:
1367 for path in changes:
1354 for ext in externals:
1368 for ext in externals:
1355 if path == ext or path.startswith(ext + pycompat.ossep):
1369 if path == ext or path.startswith(ext + pycompat.ossep):
1356 return True, True, bool(missing)
1370 return True, True, bool(missing)
1357 return bool(changes), False, bool(missing)
1371 return bool(changes), False, bool(missing)
1358
1372
1359 @annotatesubrepoerror
1373 @annotatesubrepoerror
1360 def dirty(self, ignoreupdate=False, missing=False):
1374 def dirty(self, ignoreupdate=False, missing=False):
1361 if self._svnmissing():
1375 if self._svnmissing():
1362 return self._state[1] != ''
1376 return self._state[1] != ''
1363 wcchanged = self._wcchanged()
1377 wcchanged = self._wcchanged()
1364 changed = wcchanged[0] or (missing and wcchanged[2])
1378 changed = wcchanged[0] or (missing and wcchanged[2])
1365 if not changed:
1379 if not changed:
1366 if self._state[1] in self._wcrevs() or ignoreupdate:
1380 if self._state[1] in self._wcrevs() or ignoreupdate:
1367 return False
1381 return False
1368 return True
1382 return True
1369
1383
1370 def basestate(self):
1384 def basestate(self):
1371 lastrev, rev = self._wcrevs()
1385 lastrev, rev = self._wcrevs()
1372 if lastrev != rev:
1386 if lastrev != rev:
1373 # Last committed rev is not the same than rev. We would
1387 # Last committed rev is not the same than rev. We would
1374 # like to take lastrev but we do not know if the subrepo
1388 # like to take lastrev but we do not know if the subrepo
1375 # URL exists at lastrev. Test it and fallback to rev it
1389 # URL exists at lastrev. Test it and fallback to rev it
1376 # is not there.
1390 # is not there.
1377 try:
1391 try:
1378 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1392 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1379 return lastrev
1393 return lastrev
1380 except error.Abort:
1394 except error.Abort:
1381 pass
1395 pass
1382 return rev
1396 return rev
1383
1397
1384 @annotatesubrepoerror
1398 @annotatesubrepoerror
1385 def commit(self, text, user, date):
1399 def commit(self, text, user, date):
1386 # user and date are out of our hands since svn is centralized
1400 # user and date are out of our hands since svn is centralized
1387 changed, extchanged, missing = self._wcchanged()
1401 changed, extchanged, missing = self._wcchanged()
1388 if not changed:
1402 if not changed:
1389 return self.basestate()
1403 return self.basestate()
1390 if extchanged:
1404 if extchanged:
1391 # Do not try to commit externals
1405 # Do not try to commit externals
1392 raise error.Abort(_('cannot commit svn externals'))
1406 raise error.Abort(_('cannot commit svn externals'))
1393 if missing:
1407 if missing:
1394 # svn can commit with missing entries but aborting like hg
1408 # svn can commit with missing entries but aborting like hg
1395 # seems a better approach.
1409 # seems a better approach.
1396 raise error.Abort(_('cannot commit missing svn entries'))
1410 raise error.Abort(_('cannot commit missing svn entries'))
1397 commitinfo, err = self._svncommand(['commit', '-m', text])
1411 commitinfo, err = self._svncommand(['commit', '-m', text])
1398 self.ui.status(commitinfo)
1412 self.ui.status(commitinfo)
1399 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1413 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1400 if not newrev:
1414 if not newrev:
1401 if not commitinfo.strip():
1415 if not commitinfo.strip():
1402 # Sometimes, our definition of "changed" differs from
1416 # Sometimes, our definition of "changed" differs from
1403 # svn one. For instance, svn ignores missing files
1417 # svn one. For instance, svn ignores missing files
1404 # when committing. If there are only missing files, no
1418 # when committing. If there are only missing files, no
1405 # commit is made, no output and no error code.
1419 # commit is made, no output and no error code.
1406 raise error.Abort(_('failed to commit svn changes'))
1420 raise error.Abort(_('failed to commit svn changes'))
1407 raise error.Abort(commitinfo.splitlines()[-1])
1421 raise error.Abort(commitinfo.splitlines()[-1])
1408 newrev = newrev.groups()[0]
1422 newrev = newrev.groups()[0]
1409 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1423 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1410 return newrev
1424 return newrev
1411
1425
1412 @annotatesubrepoerror
1426 @annotatesubrepoerror
1413 def remove(self):
1427 def remove(self):
1414 if self.dirty():
1428 if self.dirty():
1415 self.ui.warn(_('not removing repo %s because '
1429 self.ui.warn(_('not removing repo %s because '
1416 'it has changes.\n') % self._path)
1430 'it has changes.\n') % self._path)
1417 return
1431 return
1418 self.ui.note(_('removing subrepo %s\n') % self._path)
1432 self.ui.note(_('removing subrepo %s\n') % self._path)
1419
1433
1420 self.wvfs.rmtree(forcibly=True)
1434 self.wvfs.rmtree(forcibly=True)
1421 try:
1435 try:
1422 pwvfs = self._ctx.repo().wvfs
1436 pwvfs = self._ctx.repo().wvfs
1423 pwvfs.removedirs(pwvfs.dirname(self._path))
1437 pwvfs.removedirs(pwvfs.dirname(self._path))
1424 except OSError:
1438 except OSError:
1425 pass
1439 pass
1426
1440
1427 @annotatesubrepoerror
1441 @annotatesubrepoerror
1428 def get(self, state, overwrite=False):
1442 def get(self, state, overwrite=False):
1429 if overwrite:
1443 if overwrite:
1430 self._svncommand(['revert', '--recursive'])
1444 self._svncommand(['revert', '--recursive'])
1431 args = ['checkout']
1445 args = ['checkout']
1432 if self._svnversion >= (1, 5):
1446 if self._svnversion >= (1, 5):
1433 args.append('--force')
1447 args.append('--force')
1434 # The revision must be specified at the end of the URL to properly
1448 # The revision must be specified at the end of the URL to properly
1435 # update to a directory which has since been deleted and recreated.
1449 # update to a directory which has since been deleted and recreated.
1436 args.append('%s@%s' % (state[0], state[1]))
1450 args.append('%s@%s' % (state[0], state[1]))
1437
1451
1438 # SEC: check that the ssh url is safe
1452 # SEC: check that the ssh url is safe
1439 util.checksafessh(state[0])
1453 util.checksafessh(state[0])
1440
1454
1441 status, err = self._svncommand(args, failok=True)
1455 status, err = self._svncommand(args, failok=True)
1442 _sanitize(self.ui, self.wvfs, '.svn')
1456 _sanitize(self.ui, self.wvfs, '.svn')
1443 if not re.search('Checked out revision [0-9]+.', status):
1457 if not re.search('Checked out revision [0-9]+.', status):
1444 if ('is already a working copy for a different URL' in err
1458 if ('is already a working copy for a different URL' in err
1445 and (self._wcchanged()[:2] == (False, False))):
1459 and (self._wcchanged()[:2] == (False, False))):
1446 # obstructed but clean working copy, so just blow it away.
1460 # obstructed but clean working copy, so just blow it away.
1447 self.remove()
1461 self.remove()
1448 self.get(state, overwrite=False)
1462 self.get(state, overwrite=False)
1449 return
1463 return
1450 raise error.Abort((status or err).splitlines()[-1])
1464 raise error.Abort((status or err).splitlines()[-1])
1451 self.ui.status(status)
1465 self.ui.status(status)
1452
1466
1453 @annotatesubrepoerror
1467 @annotatesubrepoerror
1454 def merge(self, state):
1468 def merge(self, state):
1455 old = self._state[1]
1469 old = self._state[1]
1456 new = state[1]
1470 new = state[1]
1457 wcrev = self._wcrev()
1471 wcrev = self._wcrev()
1458 if new != wcrev:
1472 if new != wcrev:
1459 dirty = old == wcrev or self._wcchanged()[0]
1473 dirty = old == wcrev or self._wcchanged()[0]
1460 if _updateprompt(self.ui, self, dirty, wcrev, new):
1474 if _updateprompt(self.ui, self, dirty, wcrev, new):
1461 self.get(state, False)
1475 self.get(state, False)
1462
1476
1463 def push(self, opts):
1477 def push(self, opts):
1464 # push is a no-op for SVN
1478 # push is a no-op for SVN
1465 return True
1479 return True
1466
1480
1467 @annotatesubrepoerror
1481 @annotatesubrepoerror
1468 def files(self):
1482 def files(self):
1469 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1483 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1470 doc = xml.dom.minidom.parseString(output)
1484 doc = xml.dom.minidom.parseString(output)
1471 paths = []
1485 paths = []
1472 for e in doc.getElementsByTagName('entry'):
1486 for e in doc.getElementsByTagName('entry'):
1473 kind = str(e.getAttribute('kind'))
1487 kind = str(e.getAttribute('kind'))
1474 if kind != 'file':
1488 if kind != 'file':
1475 continue
1489 continue
1476 name = ''.join(c.data for c
1490 name = ''.join(c.data for c
1477 in e.getElementsByTagName('name')[0].childNodes
1491 in e.getElementsByTagName('name')[0].childNodes
1478 if c.nodeType == c.TEXT_NODE)
1492 if c.nodeType == c.TEXT_NODE)
1479 paths.append(name.encode('utf-8'))
1493 paths.append(name.encode('utf-8'))
1480 return paths
1494 return paths
1481
1495
1482 def filedata(self, name, decode):
1496 def filedata(self, name, decode):
1483 return self._svncommand(['cat'], name)[0]
1497 return self._svncommand(['cat'], name)[0]
1484
1498
1485
1499
1486 class gitsubrepo(abstractsubrepo):
1500 class gitsubrepo(abstractsubrepo):
1487 def __init__(self, ctx, path, state, allowcreate):
1501 def __init__(self, ctx, path, state, allowcreate):
1488 super(gitsubrepo, self).__init__(ctx, path)
1502 super(gitsubrepo, self).__init__(ctx, path)
1489 self._state = state
1503 self._state = state
1490 self._abspath = ctx.repo().wjoin(path)
1504 self._abspath = ctx.repo().wjoin(path)
1491 self._subparent = ctx.repo()
1505 self._subparent = ctx.repo()
1492 self._ensuregit()
1506 self._ensuregit()
1493
1507
1494 def _ensuregit(self):
1508 def _ensuregit(self):
1495 try:
1509 try:
1496 self._gitexecutable = 'git'
1510 self._gitexecutable = 'git'
1497 out, err = self._gitnodir(['--version'])
1511 out, err = self._gitnodir(['--version'])
1498 except OSError as e:
1512 except OSError as e:
1499 genericerror = _("error executing git for subrepo '%s': %s")
1513 genericerror = _("error executing git for subrepo '%s': %s")
1500 notfoundhint = _("check git is installed and in your PATH")
1514 notfoundhint = _("check git is installed and in your PATH")
1501 if e.errno != errno.ENOENT:
1515 if e.errno != errno.ENOENT:
1502 raise error.Abort(genericerror % (
1516 raise error.Abort(genericerror % (
1503 self._path, encoding.strtolocal(e.strerror)))
1517 self._path, encoding.strtolocal(e.strerror)))
1504 elif pycompat.iswindows:
1518 elif pycompat.iswindows:
1505 try:
1519 try:
1506 self._gitexecutable = 'git.cmd'
1520 self._gitexecutable = 'git.cmd'
1507 out, err = self._gitnodir(['--version'])
1521 out, err = self._gitnodir(['--version'])
1508 except OSError as e2:
1522 except OSError as e2:
1509 if e2.errno == errno.ENOENT:
1523 if e2.errno == errno.ENOENT:
1510 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1524 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1511 " for subrepo '%s'") % self._path,
1525 " for subrepo '%s'") % self._path,
1512 hint=notfoundhint)
1526 hint=notfoundhint)
1513 else:
1527 else:
1514 raise error.Abort(genericerror % (self._path,
1528 raise error.Abort(genericerror % (self._path,
1515 encoding.strtolocal(e2.strerror)))
1529 encoding.strtolocal(e2.strerror)))
1516 else:
1530 else:
1517 raise error.Abort(_("couldn't find git for subrepo '%s'")
1531 raise error.Abort(_("couldn't find git for subrepo '%s'")
1518 % self._path, hint=notfoundhint)
1532 % self._path, hint=notfoundhint)
1519 versionstatus = self._checkversion(out)
1533 versionstatus = self._checkversion(out)
1520 if versionstatus == 'unknown':
1534 if versionstatus == 'unknown':
1521 self.ui.warn(_('cannot retrieve git version\n'))
1535 self.ui.warn(_('cannot retrieve git version\n'))
1522 elif versionstatus == 'abort':
1536 elif versionstatus == 'abort':
1523 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1537 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1524 elif versionstatus == 'warning':
1538 elif versionstatus == 'warning':
1525 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1539 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1526
1540
1527 @staticmethod
1541 @staticmethod
1528 def _gitversion(out):
1542 def _gitversion(out):
1529 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1543 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1530 if m:
1544 if m:
1531 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1545 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1532
1546
1533 m = re.search(br'^git version (\d+)\.(\d+)', out)
1547 m = re.search(br'^git version (\d+)\.(\d+)', out)
1534 if m:
1548 if m:
1535 return (int(m.group(1)), int(m.group(2)), 0)
1549 return (int(m.group(1)), int(m.group(2)), 0)
1536
1550
1537 return -1
1551 return -1
1538
1552
1539 @staticmethod
1553 @staticmethod
1540 def _checkversion(out):
1554 def _checkversion(out):
1541 '''ensure git version is new enough
1555 '''ensure git version is new enough
1542
1556
1543 >>> _checkversion = gitsubrepo._checkversion
1557 >>> _checkversion = gitsubrepo._checkversion
1544 >>> _checkversion(b'git version 1.6.0')
1558 >>> _checkversion(b'git version 1.6.0')
1545 'ok'
1559 'ok'
1546 >>> _checkversion(b'git version 1.8.5')
1560 >>> _checkversion(b'git version 1.8.5')
1547 'ok'
1561 'ok'
1548 >>> _checkversion(b'git version 1.4.0')
1562 >>> _checkversion(b'git version 1.4.0')
1549 'abort'
1563 'abort'
1550 >>> _checkversion(b'git version 1.5.0')
1564 >>> _checkversion(b'git version 1.5.0')
1551 'warning'
1565 'warning'
1552 >>> _checkversion(b'git version 1.9-rc0')
1566 >>> _checkversion(b'git version 1.9-rc0')
1553 'ok'
1567 'ok'
1554 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1568 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1555 'ok'
1569 'ok'
1556 >>> _checkversion(b'git version 1.9.0.GIT')
1570 >>> _checkversion(b'git version 1.9.0.GIT')
1557 'ok'
1571 'ok'
1558 >>> _checkversion(b'git version 12345')
1572 >>> _checkversion(b'git version 12345')
1559 'unknown'
1573 'unknown'
1560 >>> _checkversion(b'no')
1574 >>> _checkversion(b'no')
1561 'unknown'
1575 'unknown'
1562 '''
1576 '''
1563 version = gitsubrepo._gitversion(out)
1577 version = gitsubrepo._gitversion(out)
1564 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1578 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1565 # despite the docstring comment. For now, error on 1.4.0, warn on
1579 # despite the docstring comment. For now, error on 1.4.0, warn on
1566 # 1.5.0 but attempt to continue.
1580 # 1.5.0 but attempt to continue.
1567 if version == -1:
1581 if version == -1:
1568 return 'unknown'
1582 return 'unknown'
1569 if version < (1, 5, 0):
1583 if version < (1, 5, 0):
1570 return 'abort'
1584 return 'abort'
1571 elif version < (1, 6, 0):
1585 elif version < (1, 6, 0):
1572 return 'warning'
1586 return 'warning'
1573 return 'ok'
1587 return 'ok'
1574
1588
1575 def _gitcommand(self, commands, env=None, stream=False):
1589 def _gitcommand(self, commands, env=None, stream=False):
1576 return self._gitdir(commands, env=env, stream=stream)[0]
1590 return self._gitdir(commands, env=env, stream=stream)[0]
1577
1591
1578 def _gitdir(self, commands, env=None, stream=False):
1592 def _gitdir(self, commands, env=None, stream=False):
1579 return self._gitnodir(commands, env=env, stream=stream,
1593 return self._gitnodir(commands, env=env, stream=stream,
1580 cwd=self._abspath)
1594 cwd=self._abspath)
1581
1595
1582 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1596 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1583 """Calls the git command
1597 """Calls the git command
1584
1598
1585 The methods tries to call the git command. versions prior to 1.6.0
1599 The methods tries to call the git command. versions prior to 1.6.0
1586 are not supported and very probably fail.
1600 are not supported and very probably fail.
1587 """
1601 """
1588 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1602 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1589 if env is None:
1603 if env is None:
1590 env = encoding.environ.copy()
1604 env = encoding.environ.copy()
1591 # disable localization for Git output (issue5176)
1605 # disable localization for Git output (issue5176)
1592 env['LC_ALL'] = 'C'
1606 env['LC_ALL'] = 'C'
1593 # fix for Git CVE-2015-7545
1607 # fix for Git CVE-2015-7545
1594 if 'GIT_ALLOW_PROTOCOL' not in env:
1608 if 'GIT_ALLOW_PROTOCOL' not in env:
1595 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1609 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1596 # unless ui.quiet is set, print git's stderr,
1610 # unless ui.quiet is set, print git's stderr,
1597 # which is mostly progress and useful info
1611 # which is mostly progress and useful info
1598 errpipe = None
1612 errpipe = None
1599 if self.ui.quiet:
1613 if self.ui.quiet:
1600 errpipe = open(os.devnull, 'w')
1614 errpipe = open(os.devnull, 'w')
1601 if self.ui._colormode and len(commands) and commands[0] == "diff":
1615 if self.ui._colormode and len(commands) and commands[0] == "diff":
1602 # insert the argument in the front,
1616 # insert the argument in the front,
1603 # the end of git diff arguments is used for paths
1617 # the end of git diff arguments is used for paths
1604 commands.insert(1, '--color')
1618 commands.insert(1, '--color')
1605 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1619 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1606 cwd=cwd, env=env, close_fds=util.closefds,
1620 cwd=cwd, env=env, close_fds=util.closefds,
1607 stdout=subprocess.PIPE, stderr=errpipe)
1621 stdout=subprocess.PIPE, stderr=errpipe)
1608 if stream:
1622 if stream:
1609 return p.stdout, None
1623 return p.stdout, None
1610
1624
1611 retdata = p.stdout.read().strip()
1625 retdata = p.stdout.read().strip()
1612 # wait for the child to exit to avoid race condition.
1626 # wait for the child to exit to avoid race condition.
1613 p.wait()
1627 p.wait()
1614
1628
1615 if p.returncode != 0 and p.returncode != 1:
1629 if p.returncode != 0 and p.returncode != 1:
1616 # there are certain error codes that are ok
1630 # there are certain error codes that are ok
1617 command = commands[0]
1631 command = commands[0]
1618 if command in ('cat-file', 'symbolic-ref'):
1632 if command in ('cat-file', 'symbolic-ref'):
1619 return retdata, p.returncode
1633 return retdata, p.returncode
1620 # for all others, abort
1634 # for all others, abort
1621 raise error.Abort(_('git %s error %d in %s') %
1635 raise error.Abort(_('git %s error %d in %s') %
1622 (command, p.returncode, self._relpath))
1636 (command, p.returncode, self._relpath))
1623
1637
1624 return retdata, p.returncode
1638 return retdata, p.returncode
1625
1639
1626 def _gitmissing(self):
1640 def _gitmissing(self):
1627 return not self.wvfs.exists('.git')
1641 return not self.wvfs.exists('.git')
1628
1642
1629 def _gitstate(self):
1643 def _gitstate(self):
1630 return self._gitcommand(['rev-parse', 'HEAD'])
1644 return self._gitcommand(['rev-parse', 'HEAD'])
1631
1645
1632 def _gitcurrentbranch(self):
1646 def _gitcurrentbranch(self):
1633 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1647 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1634 if err:
1648 if err:
1635 current = None
1649 current = None
1636 return current
1650 return current
1637
1651
1638 def _gitremote(self, remote):
1652 def _gitremote(self, remote):
1639 out = self._gitcommand(['remote', 'show', '-n', remote])
1653 out = self._gitcommand(['remote', 'show', '-n', remote])
1640 line = out.split('\n')[1]
1654 line = out.split('\n')[1]
1641 i = line.index('URL: ') + len('URL: ')
1655 i = line.index('URL: ') + len('URL: ')
1642 return line[i:]
1656 return line[i:]
1643
1657
1644 def _githavelocally(self, revision):
1658 def _githavelocally(self, revision):
1645 out, code = self._gitdir(['cat-file', '-e', revision])
1659 out, code = self._gitdir(['cat-file', '-e', revision])
1646 return code == 0
1660 return code == 0
1647
1661
1648 def _gitisancestor(self, r1, r2):
1662 def _gitisancestor(self, r1, r2):
1649 base = self._gitcommand(['merge-base', r1, r2])
1663 base = self._gitcommand(['merge-base', r1, r2])
1650 return base == r1
1664 return base == r1
1651
1665
1652 def _gitisbare(self):
1666 def _gitisbare(self):
1653 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1667 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1654
1668
1655 def _gitupdatestat(self):
1669 def _gitupdatestat(self):
1656 """This must be run before git diff-index.
1670 """This must be run before git diff-index.
1657 diff-index only looks at changes to file stat;
1671 diff-index only looks at changes to file stat;
1658 this command looks at file contents and updates the stat."""
1672 this command looks at file contents and updates the stat."""
1659 self._gitcommand(['update-index', '-q', '--refresh'])
1673 self._gitcommand(['update-index', '-q', '--refresh'])
1660
1674
1661 def _gitbranchmap(self):
1675 def _gitbranchmap(self):
1662 '''returns 2 things:
1676 '''returns 2 things:
1663 a map from git branch to revision
1677 a map from git branch to revision
1664 a map from revision to branches'''
1678 a map from revision to branches'''
1665 branch2rev = {}
1679 branch2rev = {}
1666 rev2branch = {}
1680 rev2branch = {}
1667
1681
1668 out = self._gitcommand(['for-each-ref', '--format',
1682 out = self._gitcommand(['for-each-ref', '--format',
1669 '%(objectname) %(refname)'])
1683 '%(objectname) %(refname)'])
1670 for line in out.split('\n'):
1684 for line in out.split('\n'):
1671 revision, ref = line.split(' ')
1685 revision, ref = line.split(' ')
1672 if (not ref.startswith('refs/heads/') and
1686 if (not ref.startswith('refs/heads/') and
1673 not ref.startswith('refs/remotes/')):
1687 not ref.startswith('refs/remotes/')):
1674 continue
1688 continue
1675 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1689 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1676 continue # ignore remote/HEAD redirects
1690 continue # ignore remote/HEAD redirects
1677 branch2rev[ref] = revision
1691 branch2rev[ref] = revision
1678 rev2branch.setdefault(revision, []).append(ref)
1692 rev2branch.setdefault(revision, []).append(ref)
1679 return branch2rev, rev2branch
1693 return branch2rev, rev2branch
1680
1694
1681 def _gittracking(self, branches):
1695 def _gittracking(self, branches):
1682 'return map of remote branch to local tracking branch'
1696 'return map of remote branch to local tracking branch'
1683 # assumes no more than one local tracking branch for each remote
1697 # assumes no more than one local tracking branch for each remote
1684 tracking = {}
1698 tracking = {}
1685 for b in branches:
1699 for b in branches:
1686 if b.startswith('refs/remotes/'):
1700 if b.startswith('refs/remotes/'):
1687 continue
1701 continue
1688 bname = b.split('/', 2)[2]
1702 bname = b.split('/', 2)[2]
1689 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1703 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1690 if remote:
1704 if remote:
1691 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1705 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1692 tracking['refs/remotes/%s/%s' %
1706 tracking['refs/remotes/%s/%s' %
1693 (remote, ref.split('/', 2)[2])] = b
1707 (remote, ref.split('/', 2)[2])] = b
1694 return tracking
1708 return tracking
1695
1709
1696 def _abssource(self, source):
1710 def _abssource(self, source):
1697 if '://' not in source:
1711 if '://' not in source:
1698 # recognize the scp syntax as an absolute source
1712 # recognize the scp syntax as an absolute source
1699 colon = source.find(':')
1713 colon = source.find(':')
1700 if colon != -1 and '/' not in source[:colon]:
1714 if colon != -1 and '/' not in source[:colon]:
1701 return source
1715 return source
1702 self._subsource = source
1716 self._subsource = source
1703 return _abssource(self)
1717 return _abssource(self)
1704
1718
1705 def _fetch(self, source, revision):
1719 def _fetch(self, source, revision):
1706 if self._gitmissing():
1720 if self._gitmissing():
1707 # SEC: check for safe ssh url
1721 # SEC: check for safe ssh url
1708 util.checksafessh(source)
1722 util.checksafessh(source)
1709
1723
1710 source = self._abssource(source)
1724 source = self._abssource(source)
1711 self.ui.status(_('cloning subrepo %s from %s\n') %
1725 self.ui.status(_('cloning subrepo %s from %s\n') %
1712 (self._relpath, source))
1726 (self._relpath, source))
1713 self._gitnodir(['clone', source, self._abspath])
1727 self._gitnodir(['clone', source, self._abspath])
1714 if self._githavelocally(revision):
1728 if self._githavelocally(revision):
1715 return
1729 return
1716 self.ui.status(_('pulling subrepo %s from %s\n') %
1730 self.ui.status(_('pulling subrepo %s from %s\n') %
1717 (self._relpath, self._gitremote('origin')))
1731 (self._relpath, self._gitremote('origin')))
1718 # try only origin: the originally cloned repo
1732 # try only origin: the originally cloned repo
1719 self._gitcommand(['fetch'])
1733 self._gitcommand(['fetch'])
1720 if not self._githavelocally(revision):
1734 if not self._githavelocally(revision):
1721 raise error.Abort(_('revision %s does not exist in subrepository '
1735 raise error.Abort(_('revision %s does not exist in subrepository '
1722 '"%s"\n') % (revision, self._relpath))
1736 '"%s"\n') % (revision, self._relpath))
1723
1737
1724 @annotatesubrepoerror
1738 @annotatesubrepoerror
1725 def dirty(self, ignoreupdate=False, missing=False):
1739 def dirty(self, ignoreupdate=False, missing=False):
1726 if self._gitmissing():
1740 if self._gitmissing():
1727 return self._state[1] != ''
1741 return self._state[1] != ''
1728 if self._gitisbare():
1742 if self._gitisbare():
1729 return True
1743 return True
1730 if not ignoreupdate and self._state[1] != self._gitstate():
1744 if not ignoreupdate and self._state[1] != self._gitstate():
1731 # different version checked out
1745 # different version checked out
1732 return True
1746 return True
1733 # check for staged changes or modified files; ignore untracked files
1747 # check for staged changes or modified files; ignore untracked files
1734 self._gitupdatestat()
1748 self._gitupdatestat()
1735 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1749 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1736 return code == 1
1750 return code == 1
1737
1751
1738 def basestate(self):
1752 def basestate(self):
1739 return self._gitstate()
1753 return self._gitstate()
1740
1754
1741 @annotatesubrepoerror
1755 @annotatesubrepoerror
1742 def get(self, state, overwrite=False):
1756 def get(self, state, overwrite=False):
1743 source, revision, kind = state
1757 source, revision, kind = state
1744 if not revision:
1758 if not revision:
1745 self.remove()
1759 self.remove()
1746 return
1760 return
1747 self._fetch(source, revision)
1761 self._fetch(source, revision)
1748 # if the repo was set to be bare, unbare it
1762 # if the repo was set to be bare, unbare it
1749 if self._gitisbare():
1763 if self._gitisbare():
1750 self._gitcommand(['config', 'core.bare', 'false'])
1764 self._gitcommand(['config', 'core.bare', 'false'])
1751 if self._gitstate() == revision:
1765 if self._gitstate() == revision:
1752 self._gitcommand(['reset', '--hard', 'HEAD'])
1766 self._gitcommand(['reset', '--hard', 'HEAD'])
1753 return
1767 return
1754 elif self._gitstate() == revision:
1768 elif self._gitstate() == revision:
1755 if overwrite:
1769 if overwrite:
1756 # first reset the index to unmark new files for commit, because
1770 # first reset the index to unmark new files for commit, because
1757 # reset --hard will otherwise throw away files added for commit,
1771 # reset --hard will otherwise throw away files added for commit,
1758 # not just unmark them.
1772 # not just unmark them.
1759 self._gitcommand(['reset', 'HEAD'])
1773 self._gitcommand(['reset', 'HEAD'])
1760 self._gitcommand(['reset', '--hard', 'HEAD'])
1774 self._gitcommand(['reset', '--hard', 'HEAD'])
1761 return
1775 return
1762 branch2rev, rev2branch = self._gitbranchmap()
1776 branch2rev, rev2branch = self._gitbranchmap()
1763
1777
1764 def checkout(args):
1778 def checkout(args):
1765 cmd = ['checkout']
1779 cmd = ['checkout']
1766 if overwrite:
1780 if overwrite:
1767 # first reset the index to unmark new files for commit, because
1781 # first reset the index to unmark new files for commit, because
1768 # the -f option will otherwise throw away files added for
1782 # the -f option will otherwise throw away files added for
1769 # commit, not just unmark them.
1783 # commit, not just unmark them.
1770 self._gitcommand(['reset', 'HEAD'])
1784 self._gitcommand(['reset', 'HEAD'])
1771 cmd.append('-f')
1785 cmd.append('-f')
1772 self._gitcommand(cmd + args)
1786 self._gitcommand(cmd + args)
1773 _sanitize(self.ui, self.wvfs, '.git')
1787 _sanitize(self.ui, self.wvfs, '.git')
1774
1788
1775 def rawcheckout():
1789 def rawcheckout():
1776 # no branch to checkout, check it out with no branch
1790 # no branch to checkout, check it out with no branch
1777 self.ui.warn(_('checking out detached HEAD in '
1791 self.ui.warn(_('checking out detached HEAD in '
1778 'subrepository "%s"\n') % self._relpath)
1792 'subrepository "%s"\n') % self._relpath)
1779 self.ui.warn(_('check out a git branch if you intend '
1793 self.ui.warn(_('check out a git branch if you intend '
1780 'to make changes\n'))
1794 'to make changes\n'))
1781 checkout(['-q', revision])
1795 checkout(['-q', revision])
1782
1796
1783 if revision not in rev2branch:
1797 if revision not in rev2branch:
1784 rawcheckout()
1798 rawcheckout()
1785 return
1799 return
1786 branches = rev2branch[revision]
1800 branches = rev2branch[revision]
1787 firstlocalbranch = None
1801 firstlocalbranch = None
1788 for b in branches:
1802 for b in branches:
1789 if b == 'refs/heads/master':
1803 if b == 'refs/heads/master':
1790 # master trumps all other branches
1804 # master trumps all other branches
1791 checkout(['refs/heads/master'])
1805 checkout(['refs/heads/master'])
1792 return
1806 return
1793 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1807 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1794 firstlocalbranch = b
1808 firstlocalbranch = b
1795 if firstlocalbranch:
1809 if firstlocalbranch:
1796 checkout([firstlocalbranch])
1810 checkout([firstlocalbranch])
1797 return
1811 return
1798
1812
1799 tracking = self._gittracking(branch2rev.keys())
1813 tracking = self._gittracking(branch2rev.keys())
1800 # choose a remote branch already tracked if possible
1814 # choose a remote branch already tracked if possible
1801 remote = branches[0]
1815 remote = branches[0]
1802 if remote not in tracking:
1816 if remote not in tracking:
1803 for b in branches:
1817 for b in branches:
1804 if b in tracking:
1818 if b in tracking:
1805 remote = b
1819 remote = b
1806 break
1820 break
1807
1821
1808 if remote not in tracking:
1822 if remote not in tracking:
1809 # create a new local tracking branch
1823 # create a new local tracking branch
1810 local = remote.split('/', 3)[3]
1824 local = remote.split('/', 3)[3]
1811 checkout(['-b', local, remote])
1825 checkout(['-b', local, remote])
1812 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1826 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1813 # When updating to a tracked remote branch,
1827 # When updating to a tracked remote branch,
1814 # if the local tracking branch is downstream of it,
1828 # if the local tracking branch is downstream of it,
1815 # a normal `git pull` would have performed a "fast-forward merge"
1829 # a normal `git pull` would have performed a "fast-forward merge"
1816 # which is equivalent to updating the local branch to the remote.
1830 # which is equivalent to updating the local branch to the remote.
1817 # Since we are only looking at branching at update, we need to
1831 # Since we are only looking at branching at update, we need to
1818 # detect this situation and perform this action lazily.
1832 # detect this situation and perform this action lazily.
1819 if tracking[remote] != self._gitcurrentbranch():
1833 if tracking[remote] != self._gitcurrentbranch():
1820 checkout([tracking[remote]])
1834 checkout([tracking[remote]])
1821 self._gitcommand(['merge', '--ff', remote])
1835 self._gitcommand(['merge', '--ff', remote])
1822 _sanitize(self.ui, self.wvfs, '.git')
1836 _sanitize(self.ui, self.wvfs, '.git')
1823 else:
1837 else:
1824 # a real merge would be required, just checkout the revision
1838 # a real merge would be required, just checkout the revision
1825 rawcheckout()
1839 rawcheckout()
1826
1840
1827 @annotatesubrepoerror
1841 @annotatesubrepoerror
1828 def commit(self, text, user, date):
1842 def commit(self, text, user, date):
1829 if self._gitmissing():
1843 if self._gitmissing():
1830 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1844 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1831 cmd = ['commit', '-a', '-m', text]
1845 cmd = ['commit', '-a', '-m', text]
1832 env = encoding.environ.copy()
1846 env = encoding.environ.copy()
1833 if user:
1847 if user:
1834 cmd += ['--author', user]
1848 cmd += ['--author', user]
1835 if date:
1849 if date:
1836 # git's date parser silently ignores when seconds < 1e9
1850 # git's date parser silently ignores when seconds < 1e9
1837 # convert to ISO8601
1851 # convert to ISO8601
1838 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1852 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1839 '%Y-%m-%dT%H:%M:%S %1%2')
1853 '%Y-%m-%dT%H:%M:%S %1%2')
1840 self._gitcommand(cmd, env=env)
1854 self._gitcommand(cmd, env=env)
1841 # make sure commit works otherwise HEAD might not exist under certain
1855 # make sure commit works otherwise HEAD might not exist under certain
1842 # circumstances
1856 # circumstances
1843 return self._gitstate()
1857 return self._gitstate()
1844
1858
1845 @annotatesubrepoerror
1859 @annotatesubrepoerror
1846 def merge(self, state):
1860 def merge(self, state):
1847 source, revision, kind = state
1861 source, revision, kind = state
1848 self._fetch(source, revision)
1862 self._fetch(source, revision)
1849 base = self._gitcommand(['merge-base', revision, self._state[1]])
1863 base = self._gitcommand(['merge-base', revision, self._state[1]])
1850 self._gitupdatestat()
1864 self._gitupdatestat()
1851 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1865 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1852
1866
1853 def mergefunc():
1867 def mergefunc():
1854 if base == revision:
1868 if base == revision:
1855 self.get(state) # fast forward merge
1869 self.get(state) # fast forward merge
1856 elif base != self._state[1]:
1870 elif base != self._state[1]:
1857 self._gitcommand(['merge', '--no-commit', revision])
1871 self._gitcommand(['merge', '--no-commit', revision])
1858 _sanitize(self.ui, self.wvfs, '.git')
1872 _sanitize(self.ui, self.wvfs, '.git')
1859
1873
1860 if self.dirty():
1874 if self.dirty():
1861 if self._gitstate() != revision:
1875 if self._gitstate() != revision:
1862 dirty = self._gitstate() == self._state[1] or code != 0
1876 dirty = self._gitstate() == self._state[1] or code != 0
1863 if _updateprompt(self.ui, self, dirty,
1877 if _updateprompt(self.ui, self, dirty,
1864 self._state[1][:7], revision[:7]):
1878 self._state[1][:7], revision[:7]):
1865 mergefunc()
1879 mergefunc()
1866 else:
1880 else:
1867 mergefunc()
1881 mergefunc()
1868
1882
1869 @annotatesubrepoerror
1883 @annotatesubrepoerror
1870 def push(self, opts):
1884 def push(self, opts):
1871 force = opts.get('force')
1885 force = opts.get('force')
1872
1886
1873 if not self._state[1]:
1887 if not self._state[1]:
1874 return True
1888 return True
1875 if self._gitmissing():
1889 if self._gitmissing():
1876 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1890 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1877 # if a branch in origin contains the revision, nothing to do
1891 # if a branch in origin contains the revision, nothing to do
1878 branch2rev, rev2branch = self._gitbranchmap()
1892 branch2rev, rev2branch = self._gitbranchmap()
1879 if self._state[1] in rev2branch:
1893 if self._state[1] in rev2branch:
1880 for b in rev2branch[self._state[1]]:
1894 for b in rev2branch[self._state[1]]:
1881 if b.startswith('refs/remotes/origin/'):
1895 if b.startswith('refs/remotes/origin/'):
1882 return True
1896 return True
1883 for b, revision in branch2rev.iteritems():
1897 for b, revision in branch2rev.iteritems():
1884 if b.startswith('refs/remotes/origin/'):
1898 if b.startswith('refs/remotes/origin/'):
1885 if self._gitisancestor(self._state[1], revision):
1899 if self._gitisancestor(self._state[1], revision):
1886 return True
1900 return True
1887 # otherwise, try to push the currently checked out branch
1901 # otherwise, try to push the currently checked out branch
1888 cmd = ['push']
1902 cmd = ['push']
1889 if force:
1903 if force:
1890 cmd.append('--force')
1904 cmd.append('--force')
1891
1905
1892 current = self._gitcurrentbranch()
1906 current = self._gitcurrentbranch()
1893 if current:
1907 if current:
1894 # determine if the current branch is even useful
1908 # determine if the current branch is even useful
1895 if not self._gitisancestor(self._state[1], current):
1909 if not self._gitisancestor(self._state[1], current):
1896 self.ui.warn(_('unrelated git branch checked out '
1910 self.ui.warn(_('unrelated git branch checked out '
1897 'in subrepository "%s"\n') % self._relpath)
1911 'in subrepository "%s"\n') % self._relpath)
1898 return False
1912 return False
1899 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1913 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1900 (current.split('/', 2)[2], self._relpath))
1914 (current.split('/', 2)[2], self._relpath))
1901 ret = self._gitdir(cmd + ['origin', current])
1915 ret = self._gitdir(cmd + ['origin', current])
1902 return ret[1] == 0
1916 return ret[1] == 0
1903 else:
1917 else:
1904 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1918 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1905 'cannot push revision %s\n') %
1919 'cannot push revision %s\n') %
1906 (self._relpath, self._state[1]))
1920 (self._relpath, self._state[1]))
1907 return False
1921 return False
1908
1922
1909 @annotatesubrepoerror
1923 @annotatesubrepoerror
1910 def add(self, ui, match, prefix, explicitonly, **opts):
1924 def add(self, ui, match, prefix, explicitonly, **opts):
1911 if self._gitmissing():
1925 if self._gitmissing():
1912 return []
1926 return []
1913
1927
1914 (modified, added, removed,
1928 (modified, added, removed,
1915 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1929 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1916 clean=True)
1930 clean=True)
1917
1931
1918 tracked = set()
1932 tracked = set()
1919 # dirstates 'amn' warn, 'r' is added again
1933 # dirstates 'amn' warn, 'r' is added again
1920 for l in (modified, added, deleted, clean):
1934 for l in (modified, added, deleted, clean):
1921 tracked.update(l)
1935 tracked.update(l)
1922
1936
1923 # Unknown files not of interest will be rejected by the matcher
1937 # Unknown files not of interest will be rejected by the matcher
1924 files = unknown
1938 files = unknown
1925 files.extend(match.files())
1939 files.extend(match.files())
1926
1940
1927 rejected = []
1941 rejected = []
1928
1942
1929 files = [f for f in sorted(set(files)) if match(f)]
1943 files = [f for f in sorted(set(files)) if match(f)]
1930 for f in files:
1944 for f in files:
1931 exact = match.exact(f)
1945 exact = match.exact(f)
1932 command = ["add"]
1946 command = ["add"]
1933 if exact:
1947 if exact:
1934 command.append("-f") #should be added, even if ignored
1948 command.append("-f") #should be added, even if ignored
1935 if ui.verbose or not exact:
1949 if ui.verbose or not exact:
1936 ui.status(_('adding %s\n') % match.rel(f))
1950 ui.status(_('adding %s\n') % match.rel(f))
1937
1951
1938 if f in tracked: # hg prints 'adding' even if already tracked
1952 if f in tracked: # hg prints 'adding' even if already tracked
1939 if exact:
1953 if exact:
1940 rejected.append(f)
1954 rejected.append(f)
1941 continue
1955 continue
1942 if not opts.get(r'dry_run'):
1956 if not opts.get(r'dry_run'):
1943 self._gitcommand(command + [f])
1957 self._gitcommand(command + [f])
1944
1958
1945 for f in rejected:
1959 for f in rejected:
1946 ui.warn(_("%s already tracked!\n") % match.abs(f))
1960 ui.warn(_("%s already tracked!\n") % match.abs(f))
1947
1961
1948 return rejected
1962 return rejected
1949
1963
1950 @annotatesubrepoerror
1964 @annotatesubrepoerror
1951 def remove(self):
1965 def remove(self):
1952 if self._gitmissing():
1966 if self._gitmissing():
1953 return
1967 return
1954 if self.dirty():
1968 if self.dirty():
1955 self.ui.warn(_('not removing repo %s because '
1969 self.ui.warn(_('not removing repo %s because '
1956 'it has changes.\n') % self._relpath)
1970 'it has changes.\n') % self._relpath)
1957 return
1971 return
1958 # we can't fully delete the repository as it may contain
1972 # we can't fully delete the repository as it may contain
1959 # local-only history
1973 # local-only history
1960 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1974 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1961 self._gitcommand(['config', 'core.bare', 'true'])
1975 self._gitcommand(['config', 'core.bare', 'true'])
1962 for f, kind in self.wvfs.readdir():
1976 for f, kind in self.wvfs.readdir():
1963 if f == '.git':
1977 if f == '.git':
1964 continue
1978 continue
1965 if kind == stat.S_IFDIR:
1979 if kind == stat.S_IFDIR:
1966 self.wvfs.rmtree(f)
1980 self.wvfs.rmtree(f)
1967 else:
1981 else:
1968 self.wvfs.unlink(f)
1982 self.wvfs.unlink(f)
1969
1983
1970 def archive(self, archiver, prefix, match=None, decode=True):
1984 def archive(self, archiver, prefix, match=None, decode=True):
1971 total = 0
1985 total = 0
1972 source, revision = self._state
1986 source, revision = self._state
1973 if not revision:
1987 if not revision:
1974 return total
1988 return total
1975 self._fetch(source, revision)
1989 self._fetch(source, revision)
1976
1990
1977 # Parse git's native archive command.
1991 # Parse git's native archive command.
1978 # This should be much faster than manually traversing the trees
1992 # This should be much faster than manually traversing the trees
1979 # and objects with many subprocess calls.
1993 # and objects with many subprocess calls.
1980 tarstream = self._gitcommand(['archive', revision], stream=True)
1994 tarstream = self._gitcommand(['archive', revision], stream=True)
1981 tar = tarfile.open(fileobj=tarstream, mode='r|')
1995 tar = tarfile.open(fileobj=tarstream, mode='r|')
1982 relpath = subrelpath(self)
1996 relpath = subrelpath(self)
1983 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1997 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1984 for i, info in enumerate(tar):
1998 for i, info in enumerate(tar):
1985 if info.isdir():
1999 if info.isdir():
1986 continue
2000 continue
1987 if match and not match(info.name):
2001 if match and not match(info.name):
1988 continue
2002 continue
1989 if info.issym():
2003 if info.issym():
1990 data = info.linkname
2004 data = info.linkname
1991 else:
2005 else:
1992 data = tar.extractfile(info).read()
2006 data = tar.extractfile(info).read()
1993 archiver.addfile(prefix + self._path + '/' + info.name,
2007 archiver.addfile(prefix + self._path + '/' + info.name,
1994 info.mode, info.issym(), data)
2008 info.mode, info.issym(), data)
1995 total += 1
2009 total += 1
1996 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
2010 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1997 unit=_('files'))
2011 unit=_('files'))
1998 self.ui.progress(_('archiving (%s)') % relpath, None)
2012 self.ui.progress(_('archiving (%s)') % relpath, None)
1999 return total
2013 return total
2000
2014
2001
2015
2002 @annotatesubrepoerror
2016 @annotatesubrepoerror
2003 def cat(self, match, fm, fntemplate, prefix, **opts):
2017 def cat(self, match, fm, fntemplate, prefix, **opts):
2004 rev = self._state[1]
2018 rev = self._state[1]
2005 if match.anypats():
2019 if match.anypats():
2006 return 1 #No support for include/exclude yet
2020 return 1 #No support for include/exclude yet
2007
2021
2008 if not match.files():
2022 if not match.files():
2009 return 1
2023 return 1
2010
2024
2011 # TODO: add support for non-plain formatter (see cmdutil.cat())
2025 # TODO: add support for non-plain formatter (see cmdutil.cat())
2012 for f in match.files():
2026 for f in match.files():
2013 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
2027 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
2014 fp = cmdutil.makefileobj(self._subparent, fntemplate,
2028 fp = cmdutil.makefileobj(self._subparent, fntemplate,
2015 self._ctx.node(),
2029 self._ctx.node(),
2016 pathname=self.wvfs.reljoin(prefix, f))
2030 pathname=self.wvfs.reljoin(prefix, f))
2017 fp.write(output)
2031 fp.write(output)
2018 fp.close()
2032 fp.close()
2019 return 0
2033 return 0
2020
2034
2021
2035
2022 @annotatesubrepoerror
2036 @annotatesubrepoerror
2023 def status(self, rev2, **opts):
2037 def status(self, rev2, **opts):
2024 rev1 = self._state[1]
2038 rev1 = self._state[1]
2025 if self._gitmissing() or not rev1:
2039 if self._gitmissing() or not rev1:
2026 # if the repo is missing, return no results
2040 # if the repo is missing, return no results
2027 return scmutil.status([], [], [], [], [], [], [])
2041 return scmutil.status([], [], [], [], [], [], [])
2028 modified, added, removed = [], [], []
2042 modified, added, removed = [], [], []
2029 self._gitupdatestat()
2043 self._gitupdatestat()
2030 if rev2:
2044 if rev2:
2031 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
2045 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
2032 else:
2046 else:
2033 command = ['diff-index', '--no-renames', rev1]
2047 command = ['diff-index', '--no-renames', rev1]
2034 out = self._gitcommand(command)
2048 out = self._gitcommand(command)
2035 for line in out.split('\n'):
2049 for line in out.split('\n'):
2036 tab = line.find('\t')
2050 tab = line.find('\t')
2037 if tab == -1:
2051 if tab == -1:
2038 continue
2052 continue
2039 status, f = line[tab - 1], line[tab + 1:]
2053 status, f = line[tab - 1], line[tab + 1:]
2040 if status == 'M':
2054 if status == 'M':
2041 modified.append(f)
2055 modified.append(f)
2042 elif status == 'A':
2056 elif status == 'A':
2043 added.append(f)
2057 added.append(f)
2044 elif status == 'D':
2058 elif status == 'D':
2045 removed.append(f)
2059 removed.append(f)
2046
2060
2047 deleted, unknown, ignored, clean = [], [], [], []
2061 deleted, unknown, ignored, clean = [], [], [], []
2048
2062
2049 command = ['status', '--porcelain', '-z']
2063 command = ['status', '--porcelain', '-z']
2050 if opts.get(r'unknown'):
2064 if opts.get(r'unknown'):
2051 command += ['--untracked-files=all']
2065 command += ['--untracked-files=all']
2052 if opts.get(r'ignored'):
2066 if opts.get(r'ignored'):
2053 command += ['--ignored']
2067 command += ['--ignored']
2054 out = self._gitcommand(command)
2068 out = self._gitcommand(command)
2055
2069
2056 changedfiles = set()
2070 changedfiles = set()
2057 changedfiles.update(modified)
2071 changedfiles.update(modified)
2058 changedfiles.update(added)
2072 changedfiles.update(added)
2059 changedfiles.update(removed)
2073 changedfiles.update(removed)
2060 for line in out.split('\0'):
2074 for line in out.split('\0'):
2061 if not line:
2075 if not line:
2062 continue
2076 continue
2063 st = line[0:2]
2077 st = line[0:2]
2064 #moves and copies show 2 files on one line
2078 #moves and copies show 2 files on one line
2065 if line.find('\0') >= 0:
2079 if line.find('\0') >= 0:
2066 filename1, filename2 = line[3:].split('\0')
2080 filename1, filename2 = line[3:].split('\0')
2067 else:
2081 else:
2068 filename1 = line[3:]
2082 filename1 = line[3:]
2069 filename2 = None
2083 filename2 = None
2070
2084
2071 changedfiles.add(filename1)
2085 changedfiles.add(filename1)
2072 if filename2:
2086 if filename2:
2073 changedfiles.add(filename2)
2087 changedfiles.add(filename2)
2074
2088
2075 if st == '??':
2089 if st == '??':
2076 unknown.append(filename1)
2090 unknown.append(filename1)
2077 elif st == '!!':
2091 elif st == '!!':
2078 ignored.append(filename1)
2092 ignored.append(filename1)
2079
2093
2080 if opts.get(r'clean'):
2094 if opts.get(r'clean'):
2081 out = self._gitcommand(['ls-files'])
2095 out = self._gitcommand(['ls-files'])
2082 for f in out.split('\n'):
2096 for f in out.split('\n'):
2083 if not f in changedfiles:
2097 if not f in changedfiles:
2084 clean.append(f)
2098 clean.append(f)
2085
2099
2086 return scmutil.status(modified, added, removed, deleted,
2100 return scmutil.status(modified, added, removed, deleted,
2087 unknown, ignored, clean)
2101 unknown, ignored, clean)
2088
2102
2089 @annotatesubrepoerror
2103 @annotatesubrepoerror
2090 def diff(self, ui, diffopts, node2, match, prefix, **opts):
2104 def diff(self, ui, diffopts, node2, match, prefix, **opts):
2091 node1 = self._state[1]
2105 node1 = self._state[1]
2092 cmd = ['diff', '--no-renames']
2106 cmd = ['diff', '--no-renames']
2093 if opts[r'stat']:
2107 if opts[r'stat']:
2094 cmd.append('--stat')
2108 cmd.append('--stat')
2095 else:
2109 else:
2096 # for Git, this also implies '-p'
2110 # for Git, this also implies '-p'
2097 cmd.append('-U%d' % diffopts.context)
2111 cmd.append('-U%d' % diffopts.context)
2098
2112
2099 gitprefix = self.wvfs.reljoin(prefix, self._path)
2113 gitprefix = self.wvfs.reljoin(prefix, self._path)
2100
2114
2101 if diffopts.noprefix:
2115 if diffopts.noprefix:
2102 cmd.extend(['--src-prefix=%s/' % gitprefix,
2116 cmd.extend(['--src-prefix=%s/' % gitprefix,
2103 '--dst-prefix=%s/' % gitprefix])
2117 '--dst-prefix=%s/' % gitprefix])
2104 else:
2118 else:
2105 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
2119 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
2106 '--dst-prefix=b/%s/' % gitprefix])
2120 '--dst-prefix=b/%s/' % gitprefix])
2107
2121
2108 if diffopts.ignorews:
2122 if diffopts.ignorews:
2109 cmd.append('--ignore-all-space')
2123 cmd.append('--ignore-all-space')
2110 if diffopts.ignorewsamount:
2124 if diffopts.ignorewsamount:
2111 cmd.append('--ignore-space-change')
2125 cmd.append('--ignore-space-change')
2112 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
2126 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
2113 and diffopts.ignoreblanklines:
2127 and diffopts.ignoreblanklines:
2114 cmd.append('--ignore-blank-lines')
2128 cmd.append('--ignore-blank-lines')
2115
2129
2116 cmd.append(node1)
2130 cmd.append(node1)
2117 if node2:
2131 if node2:
2118 cmd.append(node2)
2132 cmd.append(node2)
2119
2133
2120 output = ""
2134 output = ""
2121 if match.always():
2135 if match.always():
2122 output += self._gitcommand(cmd) + '\n'
2136 output += self._gitcommand(cmd) + '\n'
2123 else:
2137 else:
2124 st = self.status(node2)[:3]
2138 st = self.status(node2)[:3]
2125 files = [f for sublist in st for f in sublist]
2139 files = [f for sublist in st for f in sublist]
2126 for f in files:
2140 for f in files:
2127 if match(f):
2141 if match(f):
2128 output += self._gitcommand(cmd + ['--', f]) + '\n'
2142 output += self._gitcommand(cmd + ['--', f]) + '\n'
2129
2143
2130 if output.strip():
2144 if output.strip():
2131 ui.write(output)
2145 ui.write(output)
2132
2146
2133 @annotatesubrepoerror
2147 @annotatesubrepoerror
2134 def revert(self, substate, *pats, **opts):
2148 def revert(self, substate, *pats, **opts):
2135 self.ui.status(_('reverting subrepo %s\n') % substate[0])
2149 self.ui.status(_('reverting subrepo %s\n') % substate[0])
2136 if not opts.get(r'no_backup'):
2150 if not opts.get(r'no_backup'):
2137 status = self.status(None)
2151 status = self.status(None)
2138 names = status.modified
2152 names = status.modified
2139 for name in names:
2153 for name in names:
2140 bakname = scmutil.origpath(self.ui, self._subparent, name)
2154 bakname = scmutil.origpath(self.ui, self._subparent, name)
2141 self.ui.note(_('saving current version of %s as %s\n') %
2155 self.ui.note(_('saving current version of %s as %s\n') %
2142 (name, bakname))
2156 (name, bakname))
2143 self.wvfs.rename(name, bakname)
2157 self.wvfs.rename(name, bakname)
2144
2158
2145 if not opts.get(r'dry_run'):
2159 if not opts.get(r'dry_run'):
2146 self.get(substate, overwrite=True)
2160 self.get(substate, overwrite=True)
2147 return []
2161 return []
2148
2162
2149 def shortid(self, revid):
2163 def shortid(self, revid):
2150 return revid[:7]
2164 return revid[:7]
2151
2165
2152 types = {
2166 types = {
2153 'hg': hgsubrepo,
2167 'hg': hgsubrepo,
2154 'svn': svnsubrepo,
2168 'svn': svnsubrepo,
2155 'git': gitsubrepo,
2169 'git': gitsubrepo,
2156 }
2170 }
@@ -1,682 +1,696
1 Create test repository:
1 Create test repository:
2
2
3 $ hg init repo
3 $ hg init repo
4 $ cd repo
4 $ cd repo
5 $ echo x1 > x.txt
5 $ echo x1 > x.txt
6
6
7 $ hg init foo
7 $ hg init foo
8 $ cd foo
8 $ cd foo
9 $ echo y1 > y.txt
9 $ echo y1 > y.txt
10
10
11 $ hg init bar
11 $ hg init bar
12 $ cd bar
12 $ cd bar
13 $ echo z1 > z.txt
13 $ echo z1 > z.txt
14
14
15 $ cd ..
15 $ cd ..
16 $ echo 'bar = bar' > .hgsub
16 $ echo 'bar = bar' > .hgsub
17
17
18 $ cd ..
18 $ cd ..
19 $ echo 'foo = foo' > .hgsub
19 $ echo 'foo = foo' > .hgsub
20
20
21 Add files --- .hgsub files must go first to trigger subrepos:
21 Add files --- .hgsub files must go first to trigger subrepos:
22
22
23 $ hg add -S .hgsub
23 $ hg add -S .hgsub
24 $ hg add -S foo/.hgsub
24 $ hg add -S foo/.hgsub
25 $ hg add -S foo/bar
25 $ hg add -S foo/bar
26 adding foo/bar/z.txt
26 adding foo/bar/z.txt
27 $ hg add -S
27 $ hg add -S
28 adding x.txt
28 adding x.txt
29 adding foo/y.txt
29 adding foo/y.txt
30
30
31 Test recursive status without committing anything:
31 Test recursive status without committing anything:
32
32
33 $ hg status -S
33 $ hg status -S
34 A .hgsub
34 A .hgsub
35 A foo/.hgsub
35 A foo/.hgsub
36 A foo/bar/z.txt
36 A foo/bar/z.txt
37 A foo/y.txt
37 A foo/y.txt
38 A x.txt
38 A x.txt
39
39
40 Test recursive diff without committing anything:
40 Test recursive diff without committing anything:
41
41
42 $ hg diff --nodates -S foo
42 $ hg diff --nodates -S foo
43 diff -r 000000000000 foo/.hgsub
43 diff -r 000000000000 foo/.hgsub
44 --- /dev/null
44 --- /dev/null
45 +++ b/foo/.hgsub
45 +++ b/foo/.hgsub
46 @@ -0,0 +1,1 @@
46 @@ -0,0 +1,1 @@
47 +bar = bar
47 +bar = bar
48 diff -r 000000000000 foo/y.txt
48 diff -r 000000000000 foo/y.txt
49 --- /dev/null
49 --- /dev/null
50 +++ b/foo/y.txt
50 +++ b/foo/y.txt
51 @@ -0,0 +1,1 @@
51 @@ -0,0 +1,1 @@
52 +y1
52 +y1
53 diff -r 000000000000 foo/bar/z.txt
53 diff -r 000000000000 foo/bar/z.txt
54 --- /dev/null
54 --- /dev/null
55 +++ b/foo/bar/z.txt
55 +++ b/foo/bar/z.txt
56 @@ -0,0 +1,1 @@
56 @@ -0,0 +1,1 @@
57 +z1
57 +z1
58
58
59 Commits:
59 Commits:
60
60
61 $ hg commit -m fails
61 $ hg commit -m fails
62 abort: uncommitted changes in subrepository "foo"
62 abort: uncommitted changes in subrepository "foo"
63 (use --subrepos for recursive commit)
63 (use --subrepos for recursive commit)
64 [255]
64 [255]
65
65
66 The --subrepos flag overwrite the config setting:
66 The --subrepos flag overwrite the config setting:
67
67
68 $ hg commit -m 0-0-0 --config ui.commitsubrepos=No --subrepos
68 $ hg commit -m 0-0-0 --config ui.commitsubrepos=No --subrepos
69 committing subrepository foo
69 committing subrepository foo
70 committing subrepository foo/bar
70 committing subrepository foo/bar
71
71
72 $ cd foo
72 $ cd foo
73 $ echo y2 >> y.txt
73 $ echo y2 >> y.txt
74 $ hg commit -m 0-1-0
74 $ hg commit -m 0-1-0
75
75
76 $ cd bar
76 $ cd bar
77 $ echo z2 >> z.txt
77 $ echo z2 >> z.txt
78 $ hg commit -m 0-1-1
78 $ hg commit -m 0-1-1
79
79
80 $ cd ..
80 $ cd ..
81 $ hg commit -m 0-2-1
81 $ hg commit -m 0-2-1
82
82
83 $ cd ..
83 $ cd ..
84 $ hg commit -m 1-2-1
84 $ hg commit -m 1-2-1
85
85
86 Change working directory:
86 Change working directory:
87
87
88 $ echo y3 >> foo/y.txt
88 $ echo y3 >> foo/y.txt
89 $ echo z3 >> foo/bar/z.txt
89 $ echo z3 >> foo/bar/z.txt
90 $ hg status -S
90 $ hg status -S
91 M foo/bar/z.txt
91 M foo/bar/z.txt
92 M foo/y.txt
92 M foo/y.txt
93 $ hg diff --nodates -S
93 $ hg diff --nodates -S
94 diff -r d254738c5f5e foo/y.txt
94 diff -r d254738c5f5e foo/y.txt
95 --- a/foo/y.txt
95 --- a/foo/y.txt
96 +++ b/foo/y.txt
96 +++ b/foo/y.txt
97 @@ -1,2 +1,3 @@
97 @@ -1,2 +1,3 @@
98 y1
98 y1
99 y2
99 y2
100 +y3
100 +y3
101 diff -r 9647f22de499 foo/bar/z.txt
101 diff -r 9647f22de499 foo/bar/z.txt
102 --- a/foo/bar/z.txt
102 --- a/foo/bar/z.txt
103 +++ b/foo/bar/z.txt
103 +++ b/foo/bar/z.txt
104 @@ -1,2 +1,3 @@
104 @@ -1,2 +1,3 @@
105 z1
105 z1
106 z2
106 z2
107 +z3
107 +z3
108
108
109 Status call crossing repository boundaries:
109 Status call crossing repository boundaries:
110
110
111 $ hg status -S foo/bar/z.txt
111 $ hg status -S foo/bar/z.txt
112 M foo/bar/z.txt
112 M foo/bar/z.txt
113 $ hg status -S -I 'foo/?.txt'
113 $ hg status -S -I 'foo/?.txt'
114 M foo/y.txt
114 M foo/y.txt
115 $ hg status -S -I '**/?.txt'
115 $ hg status -S -I '**/?.txt'
116 M foo/bar/z.txt
116 M foo/bar/z.txt
117 M foo/y.txt
117 M foo/y.txt
118 $ hg diff --nodates -S -I '**/?.txt'
118 $ hg diff --nodates -S -I '**/?.txt'
119 diff -r d254738c5f5e foo/y.txt
119 diff -r d254738c5f5e foo/y.txt
120 --- a/foo/y.txt
120 --- a/foo/y.txt
121 +++ b/foo/y.txt
121 +++ b/foo/y.txt
122 @@ -1,2 +1,3 @@
122 @@ -1,2 +1,3 @@
123 y1
123 y1
124 y2
124 y2
125 +y3
125 +y3
126 diff -r 9647f22de499 foo/bar/z.txt
126 diff -r 9647f22de499 foo/bar/z.txt
127 --- a/foo/bar/z.txt
127 --- a/foo/bar/z.txt
128 +++ b/foo/bar/z.txt
128 +++ b/foo/bar/z.txt
129 @@ -1,2 +1,3 @@
129 @@ -1,2 +1,3 @@
130 z1
130 z1
131 z2
131 z2
132 +z3
132 +z3
133
133
134 Status from within a subdirectory:
134 Status from within a subdirectory:
135
135
136 $ mkdir dir
136 $ mkdir dir
137 $ cd dir
137 $ cd dir
138 $ echo a1 > a.txt
138 $ echo a1 > a.txt
139 $ hg status -S
139 $ hg status -S
140 M foo/bar/z.txt
140 M foo/bar/z.txt
141 M foo/y.txt
141 M foo/y.txt
142 ? dir/a.txt
142 ? dir/a.txt
143 $ hg diff --nodates -S
143 $ hg diff --nodates -S
144 diff -r d254738c5f5e foo/y.txt
144 diff -r d254738c5f5e foo/y.txt
145 --- a/foo/y.txt
145 --- a/foo/y.txt
146 +++ b/foo/y.txt
146 +++ b/foo/y.txt
147 @@ -1,2 +1,3 @@
147 @@ -1,2 +1,3 @@
148 y1
148 y1
149 y2
149 y2
150 +y3
150 +y3
151 diff -r 9647f22de499 foo/bar/z.txt
151 diff -r 9647f22de499 foo/bar/z.txt
152 --- a/foo/bar/z.txt
152 --- a/foo/bar/z.txt
153 +++ b/foo/bar/z.txt
153 +++ b/foo/bar/z.txt
154 @@ -1,2 +1,3 @@
154 @@ -1,2 +1,3 @@
155 z1
155 z1
156 z2
156 z2
157 +z3
157 +z3
158
158
159 Status with relative path:
159 Status with relative path:
160
160
161 $ hg status -S ..
161 $ hg status -S ..
162 M ../foo/bar/z.txt
162 M ../foo/bar/z.txt
163 M ../foo/y.txt
163 M ../foo/y.txt
164 ? a.txt
164 ? a.txt
165
165
166 XXX: filtering lfilesrepo.status() in 3.3-rc causes these files to be listed as
166 XXX: filtering lfilesrepo.status() in 3.3-rc causes these files to be listed as
167 added instead of modified.
167 added instead of modified.
168 $ hg status -S .. --config extensions.largefiles=
168 $ hg status -S .. --config extensions.largefiles=
169 M ../foo/bar/z.txt
169 M ../foo/bar/z.txt
170 M ../foo/y.txt
170 M ../foo/y.txt
171 ? a.txt
171 ? a.txt
172
172
173 $ hg diff --nodates -S ..
173 $ hg diff --nodates -S ..
174 diff -r d254738c5f5e foo/y.txt
174 diff -r d254738c5f5e foo/y.txt
175 --- a/foo/y.txt
175 --- a/foo/y.txt
176 +++ b/foo/y.txt
176 +++ b/foo/y.txt
177 @@ -1,2 +1,3 @@
177 @@ -1,2 +1,3 @@
178 y1
178 y1
179 y2
179 y2
180 +y3
180 +y3
181 diff -r 9647f22de499 foo/bar/z.txt
181 diff -r 9647f22de499 foo/bar/z.txt
182 --- a/foo/bar/z.txt
182 --- a/foo/bar/z.txt
183 +++ b/foo/bar/z.txt
183 +++ b/foo/bar/z.txt
184 @@ -1,2 +1,3 @@
184 @@ -1,2 +1,3 @@
185 z1
185 z1
186 z2
186 z2
187 +z3
187 +z3
188 $ cd ..
188 $ cd ..
189
189
190 Cleanup and final commit:
190 Cleanup and final commit:
191
191
192 $ rm -r dir
192 $ rm -r dir
193 $ hg commit --subrepos -m 2-3-2
193 $ hg commit --subrepos -m 2-3-2
194 committing subrepository foo
194 committing subrepository foo
195 committing subrepository foo/bar
195 committing subrepository foo/bar
196
196
197 Test explicit path commands within subrepos: add/forget
197 Test explicit path commands within subrepos: add/forget
198 $ echo z1 > foo/bar/z2.txt
198 $ echo z1 > foo/bar/z2.txt
199 $ hg status -S
199 $ hg status -S
200 ? foo/bar/z2.txt
200 ? foo/bar/z2.txt
201 $ hg add foo/bar/z2.txt
201 $ hg add foo/bar/z2.txt
202 $ hg status -S
202 $ hg status -S
203 A foo/bar/z2.txt
203 A foo/bar/z2.txt
204 $ hg forget foo/bar/z2.txt
204 $ hg forget foo/bar/z2.txt
205 $ hg status -S
205 $ hg status -S
206 ? foo/bar/z2.txt
206 ? foo/bar/z2.txt
207 $ hg forget foo/bar/z2.txt
207 $ hg forget foo/bar/z2.txt
208 not removing foo/bar/z2.txt: file is already untracked
208 not removing foo/bar/z2.txt: file is already untracked
209 [1]
209 [1]
210 $ hg status -S
210 $ hg status -S
211 ? foo/bar/z2.txt
211 ? foo/bar/z2.txt
212 $ rm foo/bar/z2.txt
212 $ rm foo/bar/z2.txt
213
213
214 Log with the relationships between repo and its subrepo:
214 Log with the relationships between repo and its subrepo:
215
215
216 $ hg log --template '{rev}:{node|short} {desc}\n'
216 $ hg log --template '{rev}:{node|short} {desc}\n'
217 2:1326fa26d0c0 2-3-2
217 2:1326fa26d0c0 2-3-2
218 1:4b3c9ff4f66b 1-2-1
218 1:4b3c9ff4f66b 1-2-1
219 0:23376cbba0d8 0-0-0
219 0:23376cbba0d8 0-0-0
220
220
221 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
221 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
222 3:65903cebad86 2-3-2
222 3:65903cebad86 2-3-2
223 2:d254738c5f5e 0-2-1
223 2:d254738c5f5e 0-2-1
224 1:8629ce7dcc39 0-1-0
224 1:8629ce7dcc39 0-1-0
225 0:af048e97ade2 0-0-0
225 0:af048e97ade2 0-0-0
226
226
227 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
227 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
228 2:31ecbdafd357 2-3-2
228 2:31ecbdafd357 2-3-2
229 1:9647f22de499 0-1-1
229 1:9647f22de499 0-1-1
230 0:4904098473f9 0-0-0
230 0:4904098473f9 0-0-0
231
231
232 Status between revisions:
232 Status between revisions:
233
233
234 $ hg status -S
234 $ hg status -S
235 $ hg status -S --rev 0:1
235 $ hg status -S --rev 0:1
236 M .hgsubstate
236 M .hgsubstate
237 M foo/.hgsubstate
237 M foo/.hgsubstate
238 M foo/bar/z.txt
238 M foo/bar/z.txt
239 M foo/y.txt
239 M foo/y.txt
240 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
240 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
241 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
241 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
242 --- a/foo/y.txt
242 --- a/foo/y.txt
243 +++ b/foo/y.txt
243 +++ b/foo/y.txt
244 @@ -1,1 +1,2 @@
244 @@ -1,1 +1,2 @@
245 y1
245 y1
246 +y2
246 +y2
247 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
247 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
248 --- a/foo/bar/z.txt
248 --- a/foo/bar/z.txt
249 +++ b/foo/bar/z.txt
249 +++ b/foo/bar/z.txt
250 @@ -1,1 +1,2 @@
250 @@ -1,1 +1,2 @@
251 z1
251 z1
252 +z2
252 +z2
253
253
254 #if serve
254 #if serve
255 $ cd ..
255 $ cd ..
256 $ hg serve -R repo --debug -S -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log
256 $ hg serve -R repo --debug -S -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log
257 adding = $TESTTMP/repo
257 adding = $TESTTMP/repo
258 adding foo = $TESTTMP/repo/foo
258 adding foo = $TESTTMP/repo/foo
259 adding foo/bar = $TESTTMP/repo/foo/bar
259 adding foo/bar = $TESTTMP/repo/foo/bar
260 listening at http://*:$HGPORT/ (bound to *:$HGPORT) (glob) (?)
260 listening at http://*:$HGPORT/ (bound to *:$HGPORT) (glob) (?)
261 adding = $TESTTMP/repo (?)
261 adding = $TESTTMP/repo (?)
262 adding foo = $TESTTMP/repo/foo (?)
262 adding foo = $TESTTMP/repo/foo (?)
263 adding foo/bar = $TESTTMP/repo/foo/bar (?)
263 adding foo/bar = $TESTTMP/repo/foo/bar (?)
264 $ cat hg1.pid >> $DAEMON_PIDS
264 $ cat hg1.pid >> $DAEMON_PIDS
265
265
266 $ hg clone http://localhost:$HGPORT clone --config progress.disable=True
266 $ hg clone http://localhost:$HGPORT clone --config progress.disable=True
267 requesting all changes
267 requesting all changes
268 adding changesets
268 adding changesets
269 adding manifests
269 adding manifests
270 adding file changes
270 adding file changes
271 added 3 changesets with 5 changes to 3 files
271 added 3 changesets with 5 changes to 3 files
272 new changesets 23376cbba0d8:1326fa26d0c0
272 new changesets 23376cbba0d8:1326fa26d0c0
273 updating to branch default
273 updating to branch default
274 cloning subrepo foo from http://localhost:$HGPORT/foo
274 cloning subrepo foo from http://localhost:$HGPORT/foo
275 requesting all changes
275 requesting all changes
276 adding changesets
276 adding changesets
277 adding manifests
277 adding manifests
278 adding file changes
278 adding file changes
279 added 4 changesets with 7 changes to 3 files
279 added 4 changesets with 7 changes to 3 files
280 new changesets af048e97ade2:65903cebad86
280 new changesets af048e97ade2:65903cebad86
281 cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar
281 cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar
282 requesting all changes
282 requesting all changes
283 adding changesets
283 adding changesets
284 adding manifests
284 adding manifests
285 adding file changes
285 adding file changes
286 added 3 changesets with 3 changes to 1 files
286 added 3 changesets with 3 changes to 1 files
287 new changesets 4904098473f9:31ecbdafd357
287 new changesets 4904098473f9:31ecbdafd357
288 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
289
289
290 $ cat clone/foo/bar/z.txt
290 $ cat clone/foo/bar/z.txt
291 z1
291 z1
292 z2
292 z2
293 z3
293 z3
294
294
295 BUG: The remote subrepo should be cloned to the local pool, and then shared
295 Clone pooling from a remote URL will share the top level repo and the subrepos,
296 from there.
296 even if they are referenced by remote URL.
297
297
298 $ hg --config extensions.share= --config share.pool=$TESTTMP/pool \
298 $ hg --config extensions.share= --config share.pool=$TESTTMP/pool \
299 > clone http://localhost:$HGPORT shared
299 > clone http://localhost:$HGPORT shared
300 (sharing from new pooled repository 23376cbba0d87c15906bb3652584927c140907bf)
300 (sharing from new pooled repository 23376cbba0d87c15906bb3652584927c140907bf)
301 requesting all changes
301 requesting all changes
302 adding changesets
302 adding changesets
303 adding manifests
303 adding manifests
304 adding file changes
304 adding file changes
305 added 3 changesets with 5 changes to 3 files
305 added 3 changesets with 5 changes to 3 files
306 new changesets 23376cbba0d8:1326fa26d0c0
306 new changesets 23376cbba0d8:1326fa26d0c0
307 searching for changes
307 searching for changes
308 no changes found
308 no changes found
309 updating working directory
309 updating working directory
310 cloning subrepo foo from http://localhost:$HGPORT/foo
310 cloning subrepo foo from http://localhost:$HGPORT/foo
311 (sharing from new pooled repository af048e97ade2e236f754f05d07013e586af0f8bf)
311 requesting all changes
312 requesting all changes
312 adding changesets
313 adding changesets
313 adding manifests
314 adding manifests
314 adding file changes
315 adding file changes
315 added 4 changesets with 7 changes to 3 files
316 added 4 changesets with 7 changes to 3 files
316 new changesets af048e97ade2:65903cebad86
317 new changesets af048e97ade2:65903cebad86
318 searching for changes
319 no changes found
317 cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar
320 cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar
321 (sharing from new pooled repository 4904098473f96c900fec436dad267edd4da59fad)
318 requesting all changes
322 requesting all changes
319 adding changesets
323 adding changesets
320 adding manifests
324 adding manifests
321 adding file changes
325 adding file changes
322 added 3 changesets with 3 changes to 1 files
326 added 3 changesets with 3 changes to 1 files
323 new changesets 4904098473f9:31ecbdafd357
327 new changesets 4904098473f9:31ecbdafd357
328 searching for changes
329 no changes found
324 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
330 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
325
331
326 $ cat access.log
332 $ cat access.log
327 * "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
333 * "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
328 * "GET /?cmd=batch HTTP/1.1" 200 - * (glob)
334 * "GET /?cmd=batch HTTP/1.1" 200 - * (glob)
329 * "GET /?cmd=getbundle HTTP/1.1" 200 - * (glob)
335 * "GET /?cmd=getbundle HTTP/1.1" 200 - * (glob)
330 * "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
336 * "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
331 * "GET /foo?cmd=batch HTTP/1.1" 200 - * (glob)
337 * "GET /foo?cmd=batch HTTP/1.1" 200 - * (glob)
332 * "GET /foo?cmd=getbundle HTTP/1.1" 200 - * (glob)
338 * "GET /foo?cmd=getbundle HTTP/1.1" 200 - * (glob)
333 * "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
339 * "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
334 * "GET /foo/bar?cmd=batch HTTP/1.1" 200 - * (glob)
340 * "GET /foo/bar?cmd=batch HTTP/1.1" 200 - * (glob)
335 * "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - * (glob)
341 * "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - * (glob)
336 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
342 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
337 $LOCALIP - - [$LOGDATE$] "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=0 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
343 $LOCALIP - - [$LOGDATE$] "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=0 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
338 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
344 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
339 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
345 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
340 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=1326fa26d0c00d2146c63b56bb6a45149d7325ac&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
346 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=1326fa26d0c00d2146c63b56bb6a45149d7325ac&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
341 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D1326fa26d0c00d2146c63b56bb6a45149d7325ac x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
347 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D1326fa26d0c00d2146c63b56bb6a45149d7325ac x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
342 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=1326fa26d0c00d2146c63b56bb6a45149d7325ac&heads=1326fa26d0c00d2146c63b56bb6a45149d7325ac&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
348 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=1326fa26d0c00d2146c63b56bb6a45149d7325ac&heads=1326fa26d0c00d2146c63b56bb6a45149d7325ac&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
343 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
349 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
350 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=0 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
351 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
344 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
352 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
345 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=65903cebad86f1a84bd4f1134f62fa7dcb7a1c98&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
353 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=65903cebad86f1a84bd4f1134f62fa7dcb7a1c98&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
354 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D65903cebad86f1a84bd4f1134f62fa7dcb7a1c98 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
355 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=65903cebad86f1a84bd4f1134f62fa7dcb7a1c98&heads=65903cebad86f1a84bd4f1134f62fa7dcb7a1c98&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
356 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
357 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=0 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
346 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
358 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
347 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
359 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
348 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=31ecbdafd357f54b281c9bd1d681bb90de219e22&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
360 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=31ecbdafd357f54b281c9bd1d681bb90de219e22&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
361 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D31ecbdafd357f54b281c9bd1d681bb90de219e22 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
362 $LOCALIP - - [$LOGDATE$] "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=31ecbdafd357f54b281c9bd1d681bb90de219e22&heads=31ecbdafd357f54b281c9bd1d681bb90de219e22&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
349
363
350 $ killdaemons.py
364 $ killdaemons.py
351 $ rm hg1.pid error.log access.log
365 $ rm hg1.pid error.log access.log
352 $ cd repo
366 $ cd repo
353 #endif
367 #endif
354
368
355 Enable progress extension for archive tests:
369 Enable progress extension for archive tests:
356
370
357 $ cp $HGRCPATH $HGRCPATH.no-progress
371 $ cp $HGRCPATH $HGRCPATH.no-progress
358 $ cat >> $HGRCPATH <<EOF
372 $ cat >> $HGRCPATH <<EOF
359 > [progress]
373 > [progress]
360 > disable=False
374 > disable=False
361 > assume-tty = 1
375 > assume-tty = 1
362 > delay = 0
376 > delay = 0
363 > # set changedelay really large so we don't see nested topics
377 > # set changedelay really large so we don't see nested topics
364 > changedelay = 30000
378 > changedelay = 30000
365 > format = topic bar number
379 > format = topic bar number
366 > refresh = 0
380 > refresh = 0
367 > width = 60
381 > width = 60
368 > EOF
382 > EOF
369
383
370 Test archiving to a directory tree (the doubled lines in the output
384 Test archiving to a directory tree (the doubled lines in the output
371 only show up in the test output, not in real usage):
385 only show up in the test output, not in real usage):
372
386
373 $ hg archive --subrepos ../archive
387 $ hg archive --subrepos ../archive
374 \r (no-eol) (esc)
388 \r (no-eol) (esc)
375 archiving [ ] 0/3\r (no-eol) (esc)
389 archiving [ ] 0/3\r (no-eol) (esc)
376 archiving [=============> ] 1/3\r (no-eol) (esc)
390 archiving [=============> ] 1/3\r (no-eol) (esc)
377 archiving [===========================> ] 2/3\r (no-eol) (esc)
391 archiving [===========================> ] 2/3\r (no-eol) (esc)
378 archiving [==========================================>] 3/3\r (no-eol) (esc)
392 archiving [==========================================>] 3/3\r (no-eol) (esc)
379 \r (no-eol) (esc)
393 \r (no-eol) (esc)
380 \r (no-eol) (esc)
394 \r (no-eol) (esc)
381 archiving (foo) [ ] 0/3\r (no-eol) (esc)
395 archiving (foo) [ ] 0/3\r (no-eol) (esc)
382 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
396 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
383 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
397 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
384 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
398 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
385 \r (no-eol) (esc)
399 \r (no-eol) (esc)
386 \r (no-eol) (esc)
400 \r (no-eol) (esc)
387 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
401 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
388 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
402 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
389 \r (no-eol) (esc)
403 \r (no-eol) (esc)
390 $ find ../archive | sort
404 $ find ../archive | sort
391 ../archive
405 ../archive
392 ../archive/.hg_archival.txt
406 ../archive/.hg_archival.txt
393 ../archive/.hgsub
407 ../archive/.hgsub
394 ../archive/.hgsubstate
408 ../archive/.hgsubstate
395 ../archive/foo
409 ../archive/foo
396 ../archive/foo/.hgsub
410 ../archive/foo/.hgsub
397 ../archive/foo/.hgsubstate
411 ../archive/foo/.hgsubstate
398 ../archive/foo/bar
412 ../archive/foo/bar
399 ../archive/foo/bar/z.txt
413 ../archive/foo/bar/z.txt
400 ../archive/foo/y.txt
414 ../archive/foo/y.txt
401 ../archive/x.txt
415 ../archive/x.txt
402
416
403 Test archiving to zip file (unzip output is unstable):
417 Test archiving to zip file (unzip output is unstable):
404
418
405 $ hg archive --subrepos --prefix '.' ../archive.zip
419 $ hg archive --subrepos --prefix '.' ../archive.zip
406 \r (no-eol) (esc)
420 \r (no-eol) (esc)
407 archiving [ ] 0/3\r (no-eol) (esc)
421 archiving [ ] 0/3\r (no-eol) (esc)
408 archiving [=============> ] 1/3\r (no-eol) (esc)
422 archiving [=============> ] 1/3\r (no-eol) (esc)
409 archiving [===========================> ] 2/3\r (no-eol) (esc)
423 archiving [===========================> ] 2/3\r (no-eol) (esc)
410 archiving [==========================================>] 3/3\r (no-eol) (esc)
424 archiving [==========================================>] 3/3\r (no-eol) (esc)
411 \r (no-eol) (esc)
425 \r (no-eol) (esc)
412 \r (no-eol) (esc)
426 \r (no-eol) (esc)
413 archiving (foo) [ ] 0/3\r (no-eol) (esc)
427 archiving (foo) [ ] 0/3\r (no-eol) (esc)
414 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
428 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
415 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
429 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
416 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
430 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
417 \r (no-eol) (esc)
431 \r (no-eol) (esc)
418 \r (no-eol) (esc)
432 \r (no-eol) (esc)
419 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
433 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
420 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
434 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
421 \r (no-eol) (esc)
435 \r (no-eol) (esc)
422
436
423 (unzip date formating is unstable, we do not care about it and glob it out)
437 (unzip date formating is unstable, we do not care about it and glob it out)
424
438
425 $ unzip -l ../archive.zip | grep -v -- ----- | egrep -v files$
439 $ unzip -l ../archive.zip | grep -v -- ----- | egrep -v files$
426 Archive: ../archive.zip
440 Archive: ../archive.zip
427 Length [ ]* Date [ ]* Time [ ]* Name (re)
441 Length [ ]* Date [ ]* Time [ ]* Name (re)
428 172 [0-9:\- ]* .hg_archival.txt (re)
442 172 [0-9:\- ]* .hg_archival.txt (re)
429 10 [0-9:\- ]* .hgsub (re)
443 10 [0-9:\- ]* .hgsub (re)
430 45 [0-9:\- ]* .hgsubstate (re)
444 45 [0-9:\- ]* .hgsubstate (re)
431 3 [0-9:\- ]* x.txt (re)
445 3 [0-9:\- ]* x.txt (re)
432 10 [0-9:\- ]* foo/.hgsub (re)
446 10 [0-9:\- ]* foo/.hgsub (re)
433 45 [0-9:\- ]* foo/.hgsubstate (re)
447 45 [0-9:\- ]* foo/.hgsubstate (re)
434 9 [0-9:\- ]* foo/y.txt (re)
448 9 [0-9:\- ]* foo/y.txt (re)
435 9 [0-9:\- ]* foo/bar/z.txt (re)
449 9 [0-9:\- ]* foo/bar/z.txt (re)
436
450
437 Test archiving a revision that references a subrepo that is not yet
451 Test archiving a revision that references a subrepo that is not yet
438 cloned:
452 cloned:
439
453
440 #if hardlink
454 #if hardlink
441 $ hg clone -U . ../empty
455 $ hg clone -U . ../empty
442 \r (no-eol) (esc)
456 \r (no-eol) (esc)
443 linking [ <=> ] 1\r (no-eol) (esc)
457 linking [ <=> ] 1\r (no-eol) (esc)
444 linking [ <=> ] 2\r (no-eol) (esc)
458 linking [ <=> ] 2\r (no-eol) (esc)
445 linking [ <=> ] 3\r (no-eol) (esc)
459 linking [ <=> ] 3\r (no-eol) (esc)
446 linking [ <=> ] 4\r (no-eol) (esc)
460 linking [ <=> ] 4\r (no-eol) (esc)
447 linking [ <=> ] 5\r (no-eol) (esc)
461 linking [ <=> ] 5\r (no-eol) (esc)
448 linking [ <=> ] 6\r (no-eol) (esc)
462 linking [ <=> ] 6\r (no-eol) (esc)
449 linking [ <=> ] 7\r (no-eol) (esc)
463 linking [ <=> ] 7\r (no-eol) (esc)
450 linking [ <=> ] 8\r (no-eol) (esc)
464 linking [ <=> ] 8\r (no-eol) (esc)
451 \r (no-eol) (esc)
465 \r (no-eol) (esc)
452 #else
466 #else
453 $ hg clone -U . ../empty
467 $ hg clone -U . ../empty
454 \r (no-eol) (esc)
468 \r (no-eol) (esc)
455 linking [ <=> ] 1 (no-eol)
469 linking [ <=> ] 1 (no-eol)
456 #endif
470 #endif
457
471
458 $ cd ../empty
472 $ cd ../empty
459 #if hardlink
473 #if hardlink
460 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
474 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
461 \r (no-eol) (esc)
475 \r (no-eol) (esc)
462 archiving [ ] 0/3\r (no-eol) (esc)
476 archiving [ ] 0/3\r (no-eol) (esc)
463 archiving [=============> ] 1/3\r (no-eol) (esc)
477 archiving [=============> ] 1/3\r (no-eol) (esc)
464 archiving [===========================> ] 2/3\r (no-eol) (esc)
478 archiving [===========================> ] 2/3\r (no-eol) (esc)
465 archiving [==========================================>] 3/3\r (no-eol) (esc)
479 archiving [==========================================>] 3/3\r (no-eol) (esc)
466 \r (no-eol) (esc)
480 \r (no-eol) (esc)
467 \r (no-eol) (esc)
481 \r (no-eol) (esc)
468 linking [ <=> ] 1\r (no-eol) (esc)
482 linking [ <=> ] 1\r (no-eol) (esc)
469 linking [ <=> ] 2\r (no-eol) (esc)
483 linking [ <=> ] 2\r (no-eol) (esc)
470 linking [ <=> ] 3\r (no-eol) (esc)
484 linking [ <=> ] 3\r (no-eol) (esc)
471 linking [ <=> ] 4\r (no-eol) (esc)
485 linking [ <=> ] 4\r (no-eol) (esc)
472 linking [ <=> ] 5\r (no-eol) (esc)
486 linking [ <=> ] 5\r (no-eol) (esc)
473 linking [ <=> ] 6\r (no-eol) (esc)
487 linking [ <=> ] 6\r (no-eol) (esc)
474 linking [ <=> ] 7\r (no-eol) (esc)
488 linking [ <=> ] 7\r (no-eol) (esc)
475 linking [ <=> ] 8\r (no-eol) (esc)
489 linking [ <=> ] 8\r (no-eol) (esc)
476 \r (no-eol) (esc)
490 \r (no-eol) (esc)
477 \r (no-eol) (esc)
491 \r (no-eol) (esc)
478 archiving (foo) [ ] 0/3\r (no-eol) (esc)
492 archiving (foo) [ ] 0/3\r (no-eol) (esc)
479 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
493 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
480 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
494 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
481 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
495 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
482 \r (no-eol) (esc)
496 \r (no-eol) (esc)
483 \r (no-eol) (esc)
497 \r (no-eol) (esc)
484 linking [ <=> ] 1\r (no-eol) (esc)
498 linking [ <=> ] 1\r (no-eol) (esc)
485 linking [ <=> ] 2\r (no-eol) (esc)
499 linking [ <=> ] 2\r (no-eol) (esc)
486 linking [ <=> ] 3\r (no-eol) (esc)
500 linking [ <=> ] 3\r (no-eol) (esc)
487 linking [ <=> ] 4\r (no-eol) (esc)
501 linking [ <=> ] 4\r (no-eol) (esc)
488 linking [ <=> ] 5\r (no-eol) (esc)
502 linking [ <=> ] 5\r (no-eol) (esc)
489 linking [ <=> ] 6\r (no-eol) (esc)
503 linking [ <=> ] 6\r (no-eol) (esc)
490 \r (no-eol) (esc)
504 \r (no-eol) (esc)
491 \r (no-eol) (esc)
505 \r (no-eol) (esc)
492 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
506 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
493 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
507 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
494 \r (no-eol) (esc)
508 \r (no-eol) (esc)
495 cloning subrepo foo from $TESTTMP/repo/foo
509 cloning subrepo foo from $TESTTMP/repo/foo
496 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
510 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
497 #else
511 #else
498 Note there's a slight output glitch on non-hardlink systems: the last
512 Note there's a slight output glitch on non-hardlink systems: the last
499 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
513 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
500 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
514 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
501 \r (no-eol) (esc)
515 \r (no-eol) (esc)
502 archiving [ ] 0/3\r (no-eol) (esc)
516 archiving [ ] 0/3\r (no-eol) (esc)
503 archiving [=============> ] 1/3\r (no-eol) (esc)
517 archiving [=============> ] 1/3\r (no-eol) (esc)
504 archiving [===========================> ] 2/3\r (no-eol) (esc)
518 archiving [===========================> ] 2/3\r (no-eol) (esc)
505 archiving [==========================================>] 3/3\r (no-eol) (esc)
519 archiving [==========================================>] 3/3\r (no-eol) (esc)
506 \r (no-eol) (esc)
520 \r (no-eol) (esc)
507 \r (no-eol) (esc)
521 \r (no-eol) (esc)
508 linking [ <=> ] 1\r (no-eol) (esc)
522 linking [ <=> ] 1\r (no-eol) (esc)
509 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
523 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
510 #endif
524 #endif
511
525
512 Archive + subrepos uses '/' for all component separators
526 Archive + subrepos uses '/' for all component separators
513
527
514 $ tar -tzf ../archive.tar.gz | sort
528 $ tar -tzf ../archive.tar.gz | sort
515 .hg_archival.txt
529 .hg_archival.txt
516 .hgsub
530 .hgsub
517 .hgsubstate
531 .hgsubstate
518 foo/.hgsub
532 foo/.hgsub
519 foo/.hgsubstate
533 foo/.hgsubstate
520 foo/bar/z.txt
534 foo/bar/z.txt
521 foo/y.txt
535 foo/y.txt
522 x.txt
536 x.txt
523
537
524 The newly cloned subrepos contain no working copy:
538 The newly cloned subrepos contain no working copy:
525
539
526 $ hg -R foo summary
540 $ hg -R foo summary
527 parent: -1:000000000000 (no revision checked out)
541 parent: -1:000000000000 (no revision checked out)
528 branch: default
542 branch: default
529 commit: (clean)
543 commit: (clean)
530 update: 4 new changesets (update)
544 update: 4 new changesets (update)
531
545
532 Sharing a local repo without the locally referenced subrepo (i.e. it was never
546 Sharing a local repo without the locally referenced subrepo (i.e. it was never
533 updated from null), fails the same as a clone operation.
547 updated from null), fails the same as a clone operation.
534
548
535 $ hg --config progress.disable=True clone -U ../empty ../empty2
549 $ hg --config progress.disable=True clone -U ../empty ../empty2
536
550
537 $ hg --config extensions.share= --config progress.disable=True \
551 $ hg --config extensions.share= --config progress.disable=True \
538 > share ../empty2 ../empty_share
552 > share ../empty2 ../empty_share
539 updating working directory
553 updating working directory
540 abort: repository $TESTTMP/empty2/foo not found!
554 abort: repository $TESTTMP/empty2/foo not found!
541 [255]
555 [255]
542
556
543 $ hg --config progress.disable=True clone ../empty2 ../empty_clone
557 $ hg --config progress.disable=True clone ../empty2 ../empty_clone
544 updating to branch default
558 updating to branch default
545 abort: repository $TESTTMP/empty2/foo not found!
559 abort: repository $TESTTMP/empty2/foo not found!
546 [255]
560 [255]
547
561
548 Disable progress extension and cleanup:
562 Disable progress extension and cleanup:
549
563
550 $ mv $HGRCPATH.no-progress $HGRCPATH
564 $ mv $HGRCPATH.no-progress $HGRCPATH
551
565
552 Test archiving when there is a directory in the way for a subrepo
566 Test archiving when there is a directory in the way for a subrepo
553 created by archive:
567 created by archive:
554
568
555 $ hg clone -U . ../almost-empty
569 $ hg clone -U . ../almost-empty
556 $ cd ../almost-empty
570 $ cd ../almost-empty
557 $ mkdir foo
571 $ mkdir foo
558 $ echo f > foo/f
572 $ echo f > foo/f
559 $ hg archive --subrepos -r tip archive
573 $ hg archive --subrepos -r tip archive
560 cloning subrepo foo from $TESTTMP/empty/foo
574 cloning subrepo foo from $TESTTMP/empty/foo
561 abort: destination '$TESTTMP/almost-empty/foo' is not empty (in subrepository "foo")
575 abort: destination '$TESTTMP/almost-empty/foo' is not empty (in subrepository "foo")
562 [255]
576 [255]
563
577
564 Clone and test outgoing:
578 Clone and test outgoing:
565
579
566 $ cd ..
580 $ cd ..
567 $ hg clone repo repo2
581 $ hg clone repo repo2
568 updating to branch default
582 updating to branch default
569 cloning subrepo foo from $TESTTMP/repo/foo
583 cloning subrepo foo from $TESTTMP/repo/foo
570 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
584 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
571 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
585 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
572 $ cd repo2
586 $ cd repo2
573 $ hg outgoing -S
587 $ hg outgoing -S
574 comparing with $TESTTMP/repo
588 comparing with $TESTTMP/repo
575 searching for changes
589 searching for changes
576 no changes found
590 no changes found
577 comparing with $TESTTMP/repo/foo
591 comparing with $TESTTMP/repo/foo
578 searching for changes
592 searching for changes
579 no changes found
593 no changes found
580 comparing with $TESTTMP/repo/foo/bar
594 comparing with $TESTTMP/repo/foo/bar
581 searching for changes
595 searching for changes
582 no changes found
596 no changes found
583 [1]
597 [1]
584
598
585 Make nested change:
599 Make nested change:
586
600
587 $ echo y4 >> foo/y.txt
601 $ echo y4 >> foo/y.txt
588 $ hg diff --nodates -S
602 $ hg diff --nodates -S
589 diff -r 65903cebad86 foo/y.txt
603 diff -r 65903cebad86 foo/y.txt
590 --- a/foo/y.txt
604 --- a/foo/y.txt
591 +++ b/foo/y.txt
605 +++ b/foo/y.txt
592 @@ -1,3 +1,4 @@
606 @@ -1,3 +1,4 @@
593 y1
607 y1
594 y2
608 y2
595 y3
609 y3
596 +y4
610 +y4
597 $ hg commit --subrepos -m 3-4-2
611 $ hg commit --subrepos -m 3-4-2
598 committing subrepository foo
612 committing subrepository foo
599 $ hg outgoing -S
613 $ hg outgoing -S
600 comparing with $TESTTMP/repo
614 comparing with $TESTTMP/repo
601 searching for changes
615 searching for changes
602 changeset: 3:2655b8ecc4ee
616 changeset: 3:2655b8ecc4ee
603 tag: tip
617 tag: tip
604 user: test
618 user: test
605 date: Thu Jan 01 00:00:00 1970 +0000
619 date: Thu Jan 01 00:00:00 1970 +0000
606 summary: 3-4-2
620 summary: 3-4-2
607
621
608 comparing with $TESTTMP/repo/foo
622 comparing with $TESTTMP/repo/foo
609 searching for changes
623 searching for changes
610 changeset: 4:e96193d6cb36
624 changeset: 4:e96193d6cb36
611 tag: tip
625 tag: tip
612 user: test
626 user: test
613 date: Thu Jan 01 00:00:00 1970 +0000
627 date: Thu Jan 01 00:00:00 1970 +0000
614 summary: 3-4-2
628 summary: 3-4-2
615
629
616 comparing with $TESTTMP/repo/foo/bar
630 comparing with $TESTTMP/repo/foo/bar
617 searching for changes
631 searching for changes
618 no changes found
632 no changes found
619
633
620
634
621 Switch to original repo and setup default path:
635 Switch to original repo and setup default path:
622
636
623 $ cd ../repo
637 $ cd ../repo
624 $ echo '[paths]' >> .hg/hgrc
638 $ echo '[paths]' >> .hg/hgrc
625 $ echo 'default = ../repo2' >> .hg/hgrc
639 $ echo 'default = ../repo2' >> .hg/hgrc
626
640
627 Test incoming:
641 Test incoming:
628
642
629 $ hg incoming -S
643 $ hg incoming -S
630 comparing with $TESTTMP/repo2
644 comparing with $TESTTMP/repo2
631 searching for changes
645 searching for changes
632 changeset: 3:2655b8ecc4ee
646 changeset: 3:2655b8ecc4ee
633 tag: tip
647 tag: tip
634 user: test
648 user: test
635 date: Thu Jan 01 00:00:00 1970 +0000
649 date: Thu Jan 01 00:00:00 1970 +0000
636 summary: 3-4-2
650 summary: 3-4-2
637
651
638 comparing with $TESTTMP/repo2/foo
652 comparing with $TESTTMP/repo2/foo
639 searching for changes
653 searching for changes
640 changeset: 4:e96193d6cb36
654 changeset: 4:e96193d6cb36
641 tag: tip
655 tag: tip
642 user: test
656 user: test
643 date: Thu Jan 01 00:00:00 1970 +0000
657 date: Thu Jan 01 00:00:00 1970 +0000
644 summary: 3-4-2
658 summary: 3-4-2
645
659
646 comparing with $TESTTMP/repo2/foo/bar
660 comparing with $TESTTMP/repo2/foo/bar
647 searching for changes
661 searching for changes
648 no changes found
662 no changes found
649
663
650 $ hg incoming -S --bundle incoming.hg
664 $ hg incoming -S --bundle incoming.hg
651 abort: cannot combine --bundle and --subrepos
665 abort: cannot combine --bundle and --subrepos
652 [255]
666 [255]
653
667
654 Test missing subrepo:
668 Test missing subrepo:
655
669
656 $ rm -r foo
670 $ rm -r foo
657 $ hg status -S
671 $ hg status -S
658 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
672 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
659
673
660 Issue2619: IndexError: list index out of range on hg add with subrepos
674 Issue2619: IndexError: list index out of range on hg add with subrepos
661 The subrepo must sorts after the explicit filename.
675 The subrepo must sorts after the explicit filename.
662
676
663 $ cd ..
677 $ cd ..
664 $ hg init test
678 $ hg init test
665 $ cd test
679 $ cd test
666 $ hg init x
680 $ hg init x
667 $ echo abc > abc.txt
681 $ echo abc > abc.txt
668 $ hg ci -Am "abc"
682 $ hg ci -Am "abc"
669 adding abc.txt
683 adding abc.txt
670 $ echo "x = x" >> .hgsub
684 $ echo "x = x" >> .hgsub
671 $ hg add .hgsub
685 $ hg add .hgsub
672 $ touch a x/a
686 $ touch a x/a
673 $ hg add a x/a
687 $ hg add a x/a
674
688
675 $ hg ci -Sm "added x"
689 $ hg ci -Sm "added x"
676 committing subrepository x
690 committing subrepository x
677 $ echo abc > x/a
691 $ echo abc > x/a
678 $ hg revert --rev '.^' "set:subrepo('glob:x*')"
692 $ hg revert --rev '.^' "set:subrepo('glob:x*')"
679 abort: subrepository 'x' does not exist in 25ac2c9b3180!
693 abort: subrepository 'x' does not exist in 25ac2c9b3180!
680 [255]
694 [255]
681
695
682 $ cd ..
696 $ cd ..
@@ -1,187 +1,190
1 #require killdaemons
1 #require killdaemons
2
2
3 Preparing the subrepository 'sub'
3 Preparing the subrepository 'sub'
4
4
5 $ hg init sub
5 $ hg init sub
6 $ echo sub > sub/sub
6 $ echo sub > sub/sub
7 $ hg add -R sub
7 $ hg add -R sub
8 adding sub/sub
8 adding sub/sub
9 $ hg commit -R sub -m "sub import"
9 $ hg commit -R sub -m "sub import"
10
10
11 Preparing the 'main' repo which depends on the subrepo 'sub'
11 Preparing the 'main' repo which depends on the subrepo 'sub'
12
12
13 $ hg init main
13 $ hg init main
14 $ echo main > main/main
14 $ echo main > main/main
15 $ echo "sub = ../sub" > main/.hgsub
15 $ echo "sub = ../sub" > main/.hgsub
16 $ hg clone sub main/sub
16 $ hg clone sub main/sub
17 updating to branch default
17 updating to branch default
18 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
18 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
19 $ hg add -R main
19 $ hg add -R main
20 adding main/.hgsub
20 adding main/.hgsub
21 adding main/main
21 adding main/main
22 $ hg commit -R main -m "main import"
22 $ hg commit -R main -m "main import"
23
23
24 Cleaning both repositories, just as a clone -U
24 Cleaning both repositories, just as a clone -U
25
25
26 $ hg up -C -R sub null
26 $ hg up -C -R sub null
27 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
27 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
28 $ hg up -C -R main null
28 $ hg up -C -R main null
29 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
29 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
30 $ rm -rf main/sub
30 $ rm -rf main/sub
31
31
32 hide outer repo
32 hide outer repo
33 $ hg init
33 $ hg init
34
34
35 Serving them both using hgweb
35 Serving them both using hgweb
36
36
37 $ printf '[paths]\n/main = main\nsub = sub\n' > webdir.conf
37 $ printf '[paths]\n/main = main\nsub = sub\n' > webdir.conf
38 $ hg serve --webdir-conf webdir.conf -a localhost -p $HGPORT \
38 $ hg serve --webdir-conf webdir.conf -a localhost -p $HGPORT \
39 > -A /dev/null -E /dev/null --pid-file hg.pid -d
39 > -A /dev/null -E /dev/null --pid-file hg.pid -d
40 $ cat hg.pid >> $DAEMON_PIDS
40 $ cat hg.pid >> $DAEMON_PIDS
41
41
42 Clone main from hgweb
42 Clone main from hgweb
43
43
44 $ hg clone "http://localhost:$HGPORT/main" cloned
44 $ hg clone "http://localhost:$HGPORT/main" cloned
45 requesting all changes
45 requesting all changes
46 adding changesets
46 adding changesets
47 adding manifests
47 adding manifests
48 adding file changes
48 adding file changes
49 added 1 changesets with 3 changes to 3 files
49 added 1 changesets with 3 changes to 3 files
50 new changesets fdfeeb3e979e
50 new changesets fdfeeb3e979e
51 updating to branch default
51 updating to branch default
52 cloning subrepo sub from http://localhost:$HGPORT/sub
52 cloning subrepo sub from http://localhost:$HGPORT/sub
53 requesting all changes
53 requesting all changes
54 adding changesets
54 adding changesets
55 adding manifests
55 adding manifests
56 adding file changes
56 adding file changes
57 added 1 changesets with 1 changes to 1 files
57 added 1 changesets with 1 changes to 1 files
58 new changesets 863c1745b441
58 new changesets 863c1745b441
59 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
60
60
61 Checking cloned repo ids
61 Checking cloned repo ids
62
62
63 $ hg id -R cloned
63 $ hg id -R cloned
64 fdfeeb3e979e tip
64 fdfeeb3e979e tip
65 $ hg id -R cloned/sub
65 $ hg id -R cloned/sub
66 863c1745b441 tip
66 863c1745b441 tip
67
67
68 subrepo debug for 'main' clone
68 subrepo debug for 'main' clone
69
69
70 $ hg debugsub -R cloned
70 $ hg debugsub -R cloned
71 path sub
71 path sub
72 source ../sub
72 source ../sub
73 revision 863c1745b441bd97a8c4a096e87793073f4fb215
73 revision 863c1745b441bd97a8c4a096e87793073f4fb215
74
74
75 Test sharing with a remote URL reference
75 Test sharing with a remote URL reference
76
76
77 $ hg init absolute_subrepo
77 $ hg init absolute_subrepo
78 $ cd absolute_subrepo
78 $ cd absolute_subrepo
79 $ echo foo > foo.txt
79 $ echo foo > foo.txt
80 $ hg ci -Am 'initial commit'
80 $ hg ci -Am 'initial commit'
81 adding foo.txt
81 adding foo.txt
82 $ echo "sub = http://localhost:$HGPORT/sub" > .hgsub
82 $ echo "sub = http://localhost:$HGPORT/sub" > .hgsub
83 $ hg ci -Am 'add absolute subrepo'
83 $ hg ci -Am 'add absolute subrepo'
84 adding .hgsub
84 adding .hgsub
85 $ cd ..
85 $ cd ..
86
86
87 Clone pooling works for local clones with a remote subrepo reference.
87 Clone pooling works for local clones with a remote subrepo reference. The
88
88 subrepo is cloned to the pool and shared from there, so that all clones will
89 BUG: subrepos should be shared out of the pool.
89 share the same subrepo.
90
90
91 $ hg --config extensions.share= --config share.pool=$TESTTMP/pool \
91 $ hg --config extensions.share= --config share.pool=$TESTTMP/pool \
92 > clone absolute_subrepo cloned_from_abs
92 > clone absolute_subrepo cloned_from_abs
93 (sharing from new pooled repository 8d6a2f1e993b34b6557de0042cfe825ae12a8dae)
93 (sharing from new pooled repository 8d6a2f1e993b34b6557de0042cfe825ae12a8dae)
94 requesting all changes
94 requesting all changes
95 adding changesets
95 adding changesets
96 adding manifests
96 adding manifests
97 adding file changes
97 adding file changes
98 added 2 changesets with 3 changes to 3 files
98 added 2 changesets with 3 changes to 3 files
99 new changesets 8d6a2f1e993b:c6d0e6ebd1c9
99 new changesets 8d6a2f1e993b:c6d0e6ebd1c9
100 searching for changes
100 searching for changes
101 no changes found
101 no changes found
102 updating working directory
102 updating working directory
103 cloning subrepo sub from http://localhost:$HGPORT/sub
103 cloning subrepo sub from http://localhost:$HGPORT/sub
104 (sharing from new pooled repository 863c1745b441bd97a8c4a096e87793073f4fb215)
104 requesting all changes
105 requesting all changes
105 adding changesets
106 adding changesets
106 adding manifests
107 adding manifests
107 adding file changes
108 adding file changes
108 added 1 changesets with 1 changes to 1 files
109 added 1 changesets with 1 changes to 1 files
109 new changesets 863c1745b441
110 new changesets 863c1745b441
111 searching for changes
112 no changes found
110 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
111
114
112 Vanilla sharing with a subrepo remote path reference will clone the subrepo.
115 Vanilla sharing with a subrepo remote path reference will clone the subrepo.
113 Each share of these top level repos will end up with independent subrepo copies
116 Each share of these top level repos will end up with independent subrepo copies
114 (potentially leaving the shared parent with dangling cset references).
117 (potentially leaving the shared parent with dangling cset references).
115
118
116 $ hg --config extensions.share= share absolute_subrepo shared_from_abs
119 $ hg --config extensions.share= share absolute_subrepo shared_from_abs
117 updating working directory
120 updating working directory
118 cloning subrepo sub from http://localhost:$HGPORT/sub
121 cloning subrepo sub from http://localhost:$HGPORT/sub
119 requesting all changes
122 requesting all changes
120 adding changesets
123 adding changesets
121 adding manifests
124 adding manifests
122 adding file changes
125 adding file changes
123 added 1 changesets with 1 changes to 1 files
126 added 1 changesets with 1 changes to 1 files
124 new changesets 863c1745b441
127 new changesets 863c1745b441
125 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
126
129
127 $ hg --config extensions.share= share -U absolute_subrepo shared_from_abs2
130 $ hg --config extensions.share= share -U absolute_subrepo shared_from_abs2
128 $ hg -R shared_from_abs2 update -r tip
131 $ hg -R shared_from_abs2 update -r tip
129 cloning subrepo sub from http://localhost:$HGPORT/sub
132 cloning subrepo sub from http://localhost:$HGPORT/sub
130 requesting all changes
133 requesting all changes
131 adding changesets
134 adding changesets
132 adding manifests
135 adding manifests
133 adding file changes
136 adding file changes
134 added 1 changesets with 1 changes to 1 files
137 added 1 changesets with 1 changes to 1 files
135 new changesets 863c1745b441
138 new changesets 863c1745b441
136 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
137
140
138 A parent repo without its subrepo available locally can be shared if the
141 A parent repo without its subrepo available locally can be shared if the
139 subrepo is referenced by absolute path.
142 subrepo is referenced by absolute path.
140
143
141 $ hg clone -U absolute_subrepo cloned_null_from_abs
144 $ hg clone -U absolute_subrepo cloned_null_from_abs
142 $ hg --config extensions.share= share cloned_null_from_abs shared_from_null_abs
145 $ hg --config extensions.share= share cloned_null_from_abs shared_from_null_abs
143 updating working directory
146 updating working directory
144 cloning subrepo sub from http://localhost:$HGPORT/sub
147 cloning subrepo sub from http://localhost:$HGPORT/sub
145 requesting all changes
148 requesting all changes
146 adding changesets
149 adding changesets
147 adding manifests
150 adding manifests
148 adding file changes
151 adding file changes
149 added 1 changesets with 1 changes to 1 files
152 added 1 changesets with 1 changes to 1 files
150 new changesets 863c1745b441
153 new changesets 863c1745b441
151 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
152
155
153 $ killdaemons.py
156 $ killdaemons.py
154
157
155 subrepo paths with ssh urls
158 subrepo paths with ssh urls
156
159
157 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/cloned sshclone
160 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/cloned sshclone
158 requesting all changes
161 requesting all changes
159 adding changesets
162 adding changesets
160 adding manifests
163 adding manifests
161 adding file changes
164 adding file changes
162 added 1 changesets with 3 changes to 3 files
165 added 1 changesets with 3 changes to 3 files
163 new changesets fdfeeb3e979e
166 new changesets fdfeeb3e979e
164 updating to branch default
167 updating to branch default
165 cloning subrepo sub from ssh://user@dummy/sub
168 cloning subrepo sub from ssh://user@dummy/sub
166 requesting all changes
169 requesting all changes
167 adding changesets
170 adding changesets
168 adding manifests
171 adding manifests
169 adding file changes
172 adding file changes
170 added 1 changesets with 1 changes to 1 files
173 added 1 changesets with 1 changes to 1 files
171 new changesets 863c1745b441
174 new changesets 863c1745b441
172 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
173
176
174 $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
177 $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
175 pushing to ssh://user@dummy/$TESTTMP/cloned
178 pushing to ssh://user@dummy/$TESTTMP/cloned
176 pushing subrepo sub to ssh://user@dummy/$TESTTMP/sub
179 pushing subrepo sub to ssh://user@dummy/$TESTTMP/sub
177 searching for changes
180 searching for changes
178 no changes found
181 no changes found
179 searching for changes
182 searching for changes
180 no changes found
183 no changes found
181 [1]
184 [1]
182
185
183 $ cat dummylog
186 $ cat dummylog
184 Got arguments 1:user@dummy 2:hg -R cloned serve --stdio
187 Got arguments 1:user@dummy 2:hg -R cloned serve --stdio
185 Got arguments 1:user@dummy 2:hg -R sub serve --stdio
188 Got arguments 1:user@dummy 2:hg -R sub serve --stdio
186 Got arguments 1:user@dummy 2:hg -R $TESTTMP/cloned serve --stdio
189 Got arguments 1:user@dummy 2:hg -R $TESTTMP/cloned serve --stdio
187 Got arguments 1:user@dummy 2:hg -R $TESTTMP/sub serve --stdio
190 Got arguments 1:user@dummy 2:hg -R $TESTTMP/sub serve --stdio
General Comments 0
You need to be logged in to leave comments. Login now