##// END OF EJS Templates
subrepo: don't attempt to share remote sources (issue5793)...
Matt Harbison -
r36705:eed02e19 stable
parent child Browse files
Show More
@@ -1,2150 +1,2156 b''
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 if parentrepo.shared():
985
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
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
990 # work with that.
991 if parentrepo.shared() and hg.islocal(srcurl):
986 self.ui.status(_('sharing subrepo %s from %s\n')
992 self.ui.status(_('sharing subrepo %s from %s\n')
987 % (subrelpath(self), srcurl))
993 % (subrelpath(self), srcurl))
988 shared = hg.share(self._repo._subparent.baseui,
994 shared = hg.share(self._repo._subparent.baseui,
989 other, self._repo.root,
995 other, self._repo.root,
990 update=False, bookmarks=False)
996 update=False, bookmarks=False)
991 self._repo = shared.local()
997 self._repo = shared.local()
992 else:
998 else:
993 self.ui.status(_('cloning subrepo %s from %s\n')
999 self.ui.status(_('cloning subrepo %s from %s\n')
994 % (subrelpath(self), srcurl))
1000 % (subrelpath(self), srcurl))
995 other, cloned = hg.clone(self._repo._subparent.baseui, {},
1001 other, cloned = hg.clone(self._repo._subparent.baseui, {},
996 other, self._repo.root,
1002 other, self._repo.root,
997 update=False)
1003 update=False)
998 self._repo = cloned.local()
1004 self._repo = cloned.local()
999 self._initrepo(parentrepo, source, create=True)
1005 self._initrepo(parentrepo, source, create=True)
1000 self._cachestorehash(srcurl)
1006 self._cachestorehash(srcurl)
1001 else:
1007 else:
1002 self.ui.status(_('pulling subrepo %s from %s\n')
1008 self.ui.status(_('pulling subrepo %s from %s\n')
1003 % (subrelpath(self), srcurl))
1009 % (subrelpath(self), srcurl))
1004 cleansub = self.storeclean(srcurl)
1010 cleansub = self.storeclean(srcurl)
1005 exchange.pull(self._repo, other)
1011 exchange.pull(self._repo, other)
1006 if cleansub:
1012 if cleansub:
1007 # keep the repo clean after pull
1013 # keep the repo clean after pull
1008 self._cachestorehash(srcurl)
1014 self._cachestorehash(srcurl)
1009 return False
1015 return False
1010
1016
1011 @annotatesubrepoerror
1017 @annotatesubrepoerror
1012 def get(self, state, overwrite=False):
1018 def get(self, state, overwrite=False):
1013 inrepo = self._get(state)
1019 inrepo = self._get(state)
1014 source, revision, kind = state
1020 source, revision, kind = state
1015 repo = self._repo
1021 repo = self._repo
1016 repo.ui.debug("getting subrepo %s\n" % self._path)
1022 repo.ui.debug("getting subrepo %s\n" % self._path)
1017 if inrepo:
1023 if inrepo:
1018 urepo = repo.unfiltered()
1024 urepo = repo.unfiltered()
1019 ctx = urepo[revision]
1025 ctx = urepo[revision]
1020 if ctx.hidden():
1026 if ctx.hidden():
1021 urepo.ui.warn(
1027 urepo.ui.warn(
1022 _('revision %s in subrepository "%s" is hidden\n') \
1028 _('revision %s in subrepository "%s" is hidden\n') \
1023 % (revision[0:12], self._path))
1029 % (revision[0:12], self._path))
1024 repo = urepo
1030 repo = urepo
1025 hg.updaterepo(repo, revision, overwrite)
1031 hg.updaterepo(repo, revision, overwrite)
1026
1032
1027 @annotatesubrepoerror
1033 @annotatesubrepoerror
1028 def merge(self, state):
1034 def merge(self, state):
1029 self._get(state)
1035 self._get(state)
1030 cur = self._repo['.']
1036 cur = self._repo['.']
1031 dst = self._repo[state[1]]
1037 dst = self._repo[state[1]]
1032 anc = dst.ancestor(cur)
1038 anc = dst.ancestor(cur)
1033
1039
1034 def mergefunc():
1040 def mergefunc():
1035 if anc == cur and dst.branch() == cur.branch():
1041 if anc == cur and dst.branch() == cur.branch():
1036 self.ui.debug('updating subrepository "%s"\n'
1042 self.ui.debug('updating subrepository "%s"\n'
1037 % subrelpath(self))
1043 % subrelpath(self))
1038 hg.update(self._repo, state[1])
1044 hg.update(self._repo, state[1])
1039 elif anc == dst:
1045 elif anc == dst:
1040 self.ui.debug('skipping subrepository "%s"\n'
1046 self.ui.debug('skipping subrepository "%s"\n'
1041 % subrelpath(self))
1047 % subrelpath(self))
1042 else:
1048 else:
1043 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
1049 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
1044 hg.merge(self._repo, state[1], remind=False)
1050 hg.merge(self._repo, state[1], remind=False)
1045
1051
1046 wctx = self._repo[None]
1052 wctx = self._repo[None]
1047 if self.dirty():
1053 if self.dirty():
1048 if anc != dst:
1054 if anc != dst:
1049 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
1055 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
1050 mergefunc()
1056 mergefunc()
1051 else:
1057 else:
1052 mergefunc()
1058 mergefunc()
1053 else:
1059 else:
1054 mergefunc()
1060 mergefunc()
1055
1061
1056 @annotatesubrepoerror
1062 @annotatesubrepoerror
1057 def push(self, opts):
1063 def push(self, opts):
1058 force = opts.get('force')
1064 force = opts.get('force')
1059 newbranch = opts.get('new_branch')
1065 newbranch = opts.get('new_branch')
1060 ssh = opts.get('ssh')
1066 ssh = opts.get('ssh')
1061
1067
1062 # push subrepos depth-first for coherent ordering
1068 # push subrepos depth-first for coherent ordering
1063 c = self._repo['']
1069 c = self._repo['']
1064 subs = c.substate # only repos that are committed
1070 subs = c.substate # only repos that are committed
1065 for s in sorted(subs):
1071 for s in sorted(subs):
1066 if c.sub(s).push(opts) == 0:
1072 if c.sub(s).push(opts) == 0:
1067 return False
1073 return False
1068
1074
1069 dsturl = _abssource(self._repo, True)
1075 dsturl = _abssource(self._repo, True)
1070 if not force:
1076 if not force:
1071 if self.storeclean(dsturl):
1077 if self.storeclean(dsturl):
1072 self.ui.status(
1078 self.ui.status(
1073 _('no changes made to subrepo %s since last push to %s\n')
1079 _('no changes made to subrepo %s since last push to %s\n')
1074 % (subrelpath(self), dsturl))
1080 % (subrelpath(self), dsturl))
1075 return None
1081 return None
1076 self.ui.status(_('pushing subrepo %s to %s\n') %
1082 self.ui.status(_('pushing subrepo %s to %s\n') %
1077 (subrelpath(self), dsturl))
1083 (subrelpath(self), dsturl))
1078 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
1084 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
1079 res = exchange.push(self._repo, other, force, newbranch=newbranch)
1085 res = exchange.push(self._repo, other, force, newbranch=newbranch)
1080
1086
1081 # the repo is now clean
1087 # the repo is now clean
1082 self._cachestorehash(dsturl)
1088 self._cachestorehash(dsturl)
1083 return res.cgresult
1089 return res.cgresult
1084
1090
1085 @annotatesubrepoerror
1091 @annotatesubrepoerror
1086 def outgoing(self, ui, dest, opts):
1092 def outgoing(self, ui, dest, opts):
1087 if 'rev' in opts or 'branch' in opts:
1093 if 'rev' in opts or 'branch' in opts:
1088 opts = copy.copy(opts)
1094 opts = copy.copy(opts)
1089 opts.pop('rev', None)
1095 opts.pop('rev', None)
1090 opts.pop('branch', None)
1096 opts.pop('branch', None)
1091 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
1097 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
1092
1098
1093 @annotatesubrepoerror
1099 @annotatesubrepoerror
1094 def incoming(self, ui, source, opts):
1100 def incoming(self, ui, source, opts):
1095 if 'rev' in opts or 'branch' in opts:
1101 if 'rev' in opts or 'branch' in opts:
1096 opts = copy.copy(opts)
1102 opts = copy.copy(opts)
1097 opts.pop('rev', None)
1103 opts.pop('rev', None)
1098 opts.pop('branch', None)
1104 opts.pop('branch', None)
1099 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
1105 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
1100
1106
1101 @annotatesubrepoerror
1107 @annotatesubrepoerror
1102 def files(self):
1108 def files(self):
1103 rev = self._state[1]
1109 rev = self._state[1]
1104 ctx = self._repo[rev]
1110 ctx = self._repo[rev]
1105 return ctx.manifest().keys()
1111 return ctx.manifest().keys()
1106
1112
1107 def filedata(self, name, decode):
1113 def filedata(self, name, decode):
1108 rev = self._state[1]
1114 rev = self._state[1]
1109 data = self._repo[rev][name].data()
1115 data = self._repo[rev][name].data()
1110 if decode:
1116 if decode:
1111 data = self._repo.wwritedata(name, data)
1117 data = self._repo.wwritedata(name, data)
1112 return data
1118 return data
1113
1119
1114 def fileflags(self, name):
1120 def fileflags(self, name):
1115 rev = self._state[1]
1121 rev = self._state[1]
1116 ctx = self._repo[rev]
1122 ctx = self._repo[rev]
1117 return ctx.flags(name)
1123 return ctx.flags(name)
1118
1124
1119 @annotatesubrepoerror
1125 @annotatesubrepoerror
1120 def printfiles(self, ui, m, fm, fmt, subrepos):
1126 def printfiles(self, ui, m, fm, fmt, subrepos):
1121 # If the parent context is a workingctx, use the workingctx here for
1127 # If the parent context is a workingctx, use the workingctx here for
1122 # consistency.
1128 # consistency.
1123 if self._ctx.rev() is None:
1129 if self._ctx.rev() is None:
1124 ctx = self._repo[None]
1130 ctx = self._repo[None]
1125 else:
1131 else:
1126 rev = self._state[1]
1132 rev = self._state[1]
1127 ctx = self._repo[rev]
1133 ctx = self._repo[rev]
1128 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1134 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1129
1135
1130 @annotatesubrepoerror
1136 @annotatesubrepoerror
1131 def getfileset(self, expr):
1137 def getfileset(self, expr):
1132 if self._ctx.rev() is None:
1138 if self._ctx.rev() is None:
1133 ctx = self._repo[None]
1139 ctx = self._repo[None]
1134 else:
1140 else:
1135 rev = self._state[1]
1141 rev = self._state[1]
1136 ctx = self._repo[rev]
1142 ctx = self._repo[rev]
1137
1143
1138 files = ctx.getfileset(expr)
1144 files = ctx.getfileset(expr)
1139
1145
1140 for subpath in ctx.substate:
1146 for subpath in ctx.substate:
1141 sub = ctx.sub(subpath)
1147 sub = ctx.sub(subpath)
1142
1148
1143 try:
1149 try:
1144 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1150 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1145 except error.LookupError:
1151 except error.LookupError:
1146 self.ui.status(_("skipping missing subrepository: %s\n")
1152 self.ui.status(_("skipping missing subrepository: %s\n")
1147 % self.wvfs.reljoin(reporelpath(self), subpath))
1153 % self.wvfs.reljoin(reporelpath(self), subpath))
1148 return files
1154 return files
1149
1155
1150 def walk(self, match):
1156 def walk(self, match):
1151 ctx = self._repo[None]
1157 ctx = self._repo[None]
1152 return ctx.walk(match)
1158 return ctx.walk(match)
1153
1159
1154 @annotatesubrepoerror
1160 @annotatesubrepoerror
1155 def forget(self, match, prefix):
1161 def forget(self, match, prefix):
1156 return cmdutil.forget(self.ui, self._repo, match,
1162 return cmdutil.forget(self.ui, self._repo, match,
1157 self.wvfs.reljoin(prefix, self._path), True)
1163 self.wvfs.reljoin(prefix, self._path), True)
1158
1164
1159 @annotatesubrepoerror
1165 @annotatesubrepoerror
1160 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1166 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1161 return cmdutil.remove(self.ui, self._repo, matcher,
1167 return cmdutil.remove(self.ui, self._repo, matcher,
1162 self.wvfs.reljoin(prefix, self._path),
1168 self.wvfs.reljoin(prefix, self._path),
1163 after, force, subrepos)
1169 after, force, subrepos)
1164
1170
1165 @annotatesubrepoerror
1171 @annotatesubrepoerror
1166 def revert(self, substate, *pats, **opts):
1172 def revert(self, substate, *pats, **opts):
1167 # reverting a subrepo is a 2 step process:
1173 # reverting a subrepo is a 2 step process:
1168 # 1. if the no_backup is not set, revert all modified
1174 # 1. if the no_backup is not set, revert all modified
1169 # files inside the subrepo
1175 # files inside the subrepo
1170 # 2. update the subrepo to the revision specified in
1176 # 2. update the subrepo to the revision specified in
1171 # the corresponding substate dictionary
1177 # the corresponding substate dictionary
1172 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1178 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1173 if not opts.get(r'no_backup'):
1179 if not opts.get(r'no_backup'):
1174 # Revert all files on the subrepo, creating backups
1180 # Revert all files on the subrepo, creating backups
1175 # Note that this will not recursively revert subrepos
1181 # Note that this will not recursively revert subrepos
1176 # We could do it if there was a set:subrepos() predicate
1182 # We could do it if there was a set:subrepos() predicate
1177 opts = opts.copy()
1183 opts = opts.copy()
1178 opts[r'date'] = None
1184 opts[r'date'] = None
1179 opts[r'rev'] = substate[1]
1185 opts[r'rev'] = substate[1]
1180
1186
1181 self.filerevert(*pats, **opts)
1187 self.filerevert(*pats, **opts)
1182
1188
1183 # Update the repo to the revision specified in the given substate
1189 # Update the repo to the revision specified in the given substate
1184 if not opts.get(r'dry_run'):
1190 if not opts.get(r'dry_run'):
1185 self.get(substate, overwrite=True)
1191 self.get(substate, overwrite=True)
1186
1192
1187 def filerevert(self, *pats, **opts):
1193 def filerevert(self, *pats, **opts):
1188 ctx = self._repo[opts[r'rev']]
1194 ctx = self._repo[opts[r'rev']]
1189 parents = self._repo.dirstate.parents()
1195 parents = self._repo.dirstate.parents()
1190 if opts.get(r'all'):
1196 if opts.get(r'all'):
1191 pats = ['set:modified()']
1197 pats = ['set:modified()']
1192 else:
1198 else:
1193 pats = []
1199 pats = []
1194 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1200 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1195
1201
1196 def shortid(self, revid):
1202 def shortid(self, revid):
1197 return revid[:12]
1203 return revid[:12]
1198
1204
1199 @annotatesubrepoerror
1205 @annotatesubrepoerror
1200 def unshare(self):
1206 def unshare(self):
1201 # subrepo inherently violates our import layering rules
1207 # subrepo inherently violates our import layering rules
1202 # because it wants to make repo objects from deep inside the stack
1208 # because it wants to make repo objects from deep inside the stack
1203 # so we manually delay the circular imports to not break
1209 # so we manually delay the circular imports to not break
1204 # scripts that don't use our demand-loading
1210 # scripts that don't use our demand-loading
1205 global hg
1211 global hg
1206 from . import hg as h
1212 from . import hg as h
1207 hg = h
1213 hg = h
1208
1214
1209 # Nothing prevents a user from sharing in a repo, and then making that a
1215 # Nothing prevents a user from sharing in a repo, and then making that a
1210 # subrepo. Alternately, the previous unshare attempt may have failed
1216 # subrepo. Alternately, the previous unshare attempt may have failed
1211 # part way through. So recurse whether or not this layer is shared.
1217 # part way through. So recurse whether or not this layer is shared.
1212 if self._repo.shared():
1218 if self._repo.shared():
1213 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
1219 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
1214
1220
1215 hg.unshare(self.ui, self._repo)
1221 hg.unshare(self.ui, self._repo)
1216
1222
1217 def verify(self):
1223 def verify(self):
1218 try:
1224 try:
1219 rev = self._state[1]
1225 rev = self._state[1]
1220 ctx = self._repo.unfiltered()[rev]
1226 ctx = self._repo.unfiltered()[rev]
1221 if ctx.hidden():
1227 if ctx.hidden():
1222 # Since hidden revisions aren't pushed/pulled, it seems worth an
1228 # Since hidden revisions aren't pushed/pulled, it seems worth an
1223 # explicit warning.
1229 # explicit warning.
1224 ui = self._repo.ui
1230 ui = self._repo.ui
1225 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1231 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1226 (self._relpath, node.short(self._ctx.node())))
1232 (self._relpath, node.short(self._ctx.node())))
1227 return 0
1233 return 0
1228 except error.RepoLookupError:
1234 except error.RepoLookupError:
1229 # A missing subrepo revision may be a case of needing to pull it, so
1235 # A missing subrepo revision may be a case of needing to pull it, so
1230 # don't treat this as an error.
1236 # don't treat this as an error.
1231 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1237 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1232 (self._relpath, node.short(self._ctx.node())))
1238 (self._relpath, node.short(self._ctx.node())))
1233 return 0
1239 return 0
1234
1240
1235 @propertycache
1241 @propertycache
1236 def wvfs(self):
1242 def wvfs(self):
1237 """return own wvfs for efficiency and consistency
1243 """return own wvfs for efficiency and consistency
1238 """
1244 """
1239 return self._repo.wvfs
1245 return self._repo.wvfs
1240
1246
1241 @propertycache
1247 @propertycache
1242 def _relpath(self):
1248 def _relpath(self):
1243 """return path to this subrepository as seen from outermost repository
1249 """return path to this subrepository as seen from outermost repository
1244 """
1250 """
1245 # Keep consistent dir separators by avoiding vfs.join(self._path)
1251 # Keep consistent dir separators by avoiding vfs.join(self._path)
1246 return reporelpath(self._repo)
1252 return reporelpath(self._repo)
1247
1253
1248 class svnsubrepo(abstractsubrepo):
1254 class svnsubrepo(abstractsubrepo):
1249 def __init__(self, ctx, path, state, allowcreate):
1255 def __init__(self, ctx, path, state, allowcreate):
1250 super(svnsubrepo, self).__init__(ctx, path)
1256 super(svnsubrepo, self).__init__(ctx, path)
1251 self._state = state
1257 self._state = state
1252 self._exe = util.findexe('svn')
1258 self._exe = util.findexe('svn')
1253 if not self._exe:
1259 if not self._exe:
1254 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1260 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1255 % self._path)
1261 % self._path)
1256
1262
1257 def _svncommand(self, commands, filename='', failok=False):
1263 def _svncommand(self, commands, filename='', failok=False):
1258 cmd = [self._exe]
1264 cmd = [self._exe]
1259 extrakw = {}
1265 extrakw = {}
1260 if not self.ui.interactive():
1266 if not self.ui.interactive():
1261 # Making stdin be a pipe should prevent svn from behaving
1267 # Making stdin be a pipe should prevent svn from behaving
1262 # interactively even if we can't pass --non-interactive.
1268 # interactively even if we can't pass --non-interactive.
1263 extrakw[r'stdin'] = subprocess.PIPE
1269 extrakw[r'stdin'] = subprocess.PIPE
1264 # Starting in svn 1.5 --non-interactive is a global flag
1270 # Starting in svn 1.5 --non-interactive is a global flag
1265 # instead of being per-command, but we need to support 1.4 so
1271 # instead of being per-command, but we need to support 1.4 so
1266 # we have to be intelligent about what commands take
1272 # we have to be intelligent about what commands take
1267 # --non-interactive.
1273 # --non-interactive.
1268 if commands[0] in ('update', 'checkout', 'commit'):
1274 if commands[0] in ('update', 'checkout', 'commit'):
1269 cmd.append('--non-interactive')
1275 cmd.append('--non-interactive')
1270 cmd.extend(commands)
1276 cmd.extend(commands)
1271 if filename is not None:
1277 if filename is not None:
1272 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1278 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1273 self._path, filename)
1279 self._path, filename)
1274 cmd.append(path)
1280 cmd.append(path)
1275 env = dict(encoding.environ)
1281 env = dict(encoding.environ)
1276 # Avoid localized output, preserve current locale for everything else.
1282 # Avoid localized output, preserve current locale for everything else.
1277 lc_all = env.get('LC_ALL')
1283 lc_all = env.get('LC_ALL')
1278 if lc_all:
1284 if lc_all:
1279 env['LANG'] = lc_all
1285 env['LANG'] = lc_all
1280 del env['LC_ALL']
1286 del env['LC_ALL']
1281 env['LC_MESSAGES'] = 'C'
1287 env['LC_MESSAGES'] = 'C'
1282 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1288 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1283 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1289 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1284 universal_newlines=True, env=env, **extrakw)
1290 universal_newlines=True, env=env, **extrakw)
1285 stdout, stderr = p.communicate()
1291 stdout, stderr = p.communicate()
1286 stderr = stderr.strip()
1292 stderr = stderr.strip()
1287 if not failok:
1293 if not failok:
1288 if p.returncode:
1294 if p.returncode:
1289 raise error.Abort(stderr or 'exited with code %d'
1295 raise error.Abort(stderr or 'exited with code %d'
1290 % p.returncode)
1296 % p.returncode)
1291 if stderr:
1297 if stderr:
1292 self.ui.warn(stderr + '\n')
1298 self.ui.warn(stderr + '\n')
1293 return stdout, stderr
1299 return stdout, stderr
1294
1300
1295 @propertycache
1301 @propertycache
1296 def _svnversion(self):
1302 def _svnversion(self):
1297 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1303 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1298 m = re.search(br'^(\d+)\.(\d+)', output)
1304 m = re.search(br'^(\d+)\.(\d+)', output)
1299 if not m:
1305 if not m:
1300 raise error.Abort(_('cannot retrieve svn tool version'))
1306 raise error.Abort(_('cannot retrieve svn tool version'))
1301 return (int(m.group(1)), int(m.group(2)))
1307 return (int(m.group(1)), int(m.group(2)))
1302
1308
1303 def _svnmissing(self):
1309 def _svnmissing(self):
1304 return not self.wvfs.exists('.svn')
1310 return not self.wvfs.exists('.svn')
1305
1311
1306 def _wcrevs(self):
1312 def _wcrevs(self):
1307 # Get the working directory revision as well as the last
1313 # Get the working directory revision as well as the last
1308 # commit revision so we can compare the subrepo state with
1314 # commit revision so we can compare the subrepo state with
1309 # both. We used to store the working directory one.
1315 # both. We used to store the working directory one.
1310 output, err = self._svncommand(['info', '--xml'])
1316 output, err = self._svncommand(['info', '--xml'])
1311 doc = xml.dom.minidom.parseString(output)
1317 doc = xml.dom.minidom.parseString(output)
1312 entries = doc.getElementsByTagName('entry')
1318 entries = doc.getElementsByTagName('entry')
1313 lastrev, rev = '0', '0'
1319 lastrev, rev = '0', '0'
1314 if entries:
1320 if entries:
1315 rev = str(entries[0].getAttribute('revision')) or '0'
1321 rev = str(entries[0].getAttribute('revision')) or '0'
1316 commits = entries[0].getElementsByTagName('commit')
1322 commits = entries[0].getElementsByTagName('commit')
1317 if commits:
1323 if commits:
1318 lastrev = str(commits[0].getAttribute('revision')) or '0'
1324 lastrev = str(commits[0].getAttribute('revision')) or '0'
1319 return (lastrev, rev)
1325 return (lastrev, rev)
1320
1326
1321 def _wcrev(self):
1327 def _wcrev(self):
1322 return self._wcrevs()[0]
1328 return self._wcrevs()[0]
1323
1329
1324 def _wcchanged(self):
1330 def _wcchanged(self):
1325 """Return (changes, extchanges, missing) where changes is True
1331 """Return (changes, extchanges, missing) where changes is True
1326 if the working directory was changed, extchanges is
1332 if the working directory was changed, extchanges is
1327 True if any of these changes concern an external entry and missing
1333 True if any of these changes concern an external entry and missing
1328 is True if any change is a missing entry.
1334 is True if any change is a missing entry.
1329 """
1335 """
1330 output, err = self._svncommand(['status', '--xml'])
1336 output, err = self._svncommand(['status', '--xml'])
1331 externals, changes, missing = [], [], []
1337 externals, changes, missing = [], [], []
1332 doc = xml.dom.minidom.parseString(output)
1338 doc = xml.dom.minidom.parseString(output)
1333 for e in doc.getElementsByTagName('entry'):
1339 for e in doc.getElementsByTagName('entry'):
1334 s = e.getElementsByTagName('wc-status')
1340 s = e.getElementsByTagName('wc-status')
1335 if not s:
1341 if not s:
1336 continue
1342 continue
1337 item = s[0].getAttribute('item')
1343 item = s[0].getAttribute('item')
1338 props = s[0].getAttribute('props')
1344 props = s[0].getAttribute('props')
1339 path = e.getAttribute('path')
1345 path = e.getAttribute('path')
1340 if item == 'external':
1346 if item == 'external':
1341 externals.append(path)
1347 externals.append(path)
1342 elif item == 'missing':
1348 elif item == 'missing':
1343 missing.append(path)
1349 missing.append(path)
1344 if (item not in ('', 'normal', 'unversioned', 'external')
1350 if (item not in ('', 'normal', 'unversioned', 'external')
1345 or props not in ('', 'none', 'normal')):
1351 or props not in ('', 'none', 'normal')):
1346 changes.append(path)
1352 changes.append(path)
1347 for path in changes:
1353 for path in changes:
1348 for ext in externals:
1354 for ext in externals:
1349 if path == ext or path.startswith(ext + pycompat.ossep):
1355 if path == ext or path.startswith(ext + pycompat.ossep):
1350 return True, True, bool(missing)
1356 return True, True, bool(missing)
1351 return bool(changes), False, bool(missing)
1357 return bool(changes), False, bool(missing)
1352
1358
1353 @annotatesubrepoerror
1359 @annotatesubrepoerror
1354 def dirty(self, ignoreupdate=False, missing=False):
1360 def dirty(self, ignoreupdate=False, missing=False):
1355 if self._svnmissing():
1361 if self._svnmissing():
1356 return self._state[1] != ''
1362 return self._state[1] != ''
1357 wcchanged = self._wcchanged()
1363 wcchanged = self._wcchanged()
1358 changed = wcchanged[0] or (missing and wcchanged[2])
1364 changed = wcchanged[0] or (missing and wcchanged[2])
1359 if not changed:
1365 if not changed:
1360 if self._state[1] in self._wcrevs() or ignoreupdate:
1366 if self._state[1] in self._wcrevs() or ignoreupdate:
1361 return False
1367 return False
1362 return True
1368 return True
1363
1369
1364 def basestate(self):
1370 def basestate(self):
1365 lastrev, rev = self._wcrevs()
1371 lastrev, rev = self._wcrevs()
1366 if lastrev != rev:
1372 if lastrev != rev:
1367 # Last committed rev is not the same than rev. We would
1373 # Last committed rev is not the same than rev. We would
1368 # like to take lastrev but we do not know if the subrepo
1374 # like to take lastrev but we do not know if the subrepo
1369 # URL exists at lastrev. Test it and fallback to rev it
1375 # URL exists at lastrev. Test it and fallback to rev it
1370 # is not there.
1376 # is not there.
1371 try:
1377 try:
1372 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1378 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1373 return lastrev
1379 return lastrev
1374 except error.Abort:
1380 except error.Abort:
1375 pass
1381 pass
1376 return rev
1382 return rev
1377
1383
1378 @annotatesubrepoerror
1384 @annotatesubrepoerror
1379 def commit(self, text, user, date):
1385 def commit(self, text, user, date):
1380 # user and date are out of our hands since svn is centralized
1386 # user and date are out of our hands since svn is centralized
1381 changed, extchanged, missing = self._wcchanged()
1387 changed, extchanged, missing = self._wcchanged()
1382 if not changed:
1388 if not changed:
1383 return self.basestate()
1389 return self.basestate()
1384 if extchanged:
1390 if extchanged:
1385 # Do not try to commit externals
1391 # Do not try to commit externals
1386 raise error.Abort(_('cannot commit svn externals'))
1392 raise error.Abort(_('cannot commit svn externals'))
1387 if missing:
1393 if missing:
1388 # svn can commit with missing entries but aborting like hg
1394 # svn can commit with missing entries but aborting like hg
1389 # seems a better approach.
1395 # seems a better approach.
1390 raise error.Abort(_('cannot commit missing svn entries'))
1396 raise error.Abort(_('cannot commit missing svn entries'))
1391 commitinfo, err = self._svncommand(['commit', '-m', text])
1397 commitinfo, err = self._svncommand(['commit', '-m', text])
1392 self.ui.status(commitinfo)
1398 self.ui.status(commitinfo)
1393 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1399 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1394 if not newrev:
1400 if not newrev:
1395 if not commitinfo.strip():
1401 if not commitinfo.strip():
1396 # Sometimes, our definition of "changed" differs from
1402 # Sometimes, our definition of "changed" differs from
1397 # svn one. For instance, svn ignores missing files
1403 # svn one. For instance, svn ignores missing files
1398 # when committing. If there are only missing files, no
1404 # when committing. If there are only missing files, no
1399 # commit is made, no output and no error code.
1405 # commit is made, no output and no error code.
1400 raise error.Abort(_('failed to commit svn changes'))
1406 raise error.Abort(_('failed to commit svn changes'))
1401 raise error.Abort(commitinfo.splitlines()[-1])
1407 raise error.Abort(commitinfo.splitlines()[-1])
1402 newrev = newrev.groups()[0]
1408 newrev = newrev.groups()[0]
1403 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1409 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1404 return newrev
1410 return newrev
1405
1411
1406 @annotatesubrepoerror
1412 @annotatesubrepoerror
1407 def remove(self):
1413 def remove(self):
1408 if self.dirty():
1414 if self.dirty():
1409 self.ui.warn(_('not removing repo %s because '
1415 self.ui.warn(_('not removing repo %s because '
1410 'it has changes.\n') % self._path)
1416 'it has changes.\n') % self._path)
1411 return
1417 return
1412 self.ui.note(_('removing subrepo %s\n') % self._path)
1418 self.ui.note(_('removing subrepo %s\n') % self._path)
1413
1419
1414 self.wvfs.rmtree(forcibly=True)
1420 self.wvfs.rmtree(forcibly=True)
1415 try:
1421 try:
1416 pwvfs = self._ctx.repo().wvfs
1422 pwvfs = self._ctx.repo().wvfs
1417 pwvfs.removedirs(pwvfs.dirname(self._path))
1423 pwvfs.removedirs(pwvfs.dirname(self._path))
1418 except OSError:
1424 except OSError:
1419 pass
1425 pass
1420
1426
1421 @annotatesubrepoerror
1427 @annotatesubrepoerror
1422 def get(self, state, overwrite=False):
1428 def get(self, state, overwrite=False):
1423 if overwrite:
1429 if overwrite:
1424 self._svncommand(['revert', '--recursive'])
1430 self._svncommand(['revert', '--recursive'])
1425 args = ['checkout']
1431 args = ['checkout']
1426 if self._svnversion >= (1, 5):
1432 if self._svnversion >= (1, 5):
1427 args.append('--force')
1433 args.append('--force')
1428 # The revision must be specified at the end of the URL to properly
1434 # The revision must be specified at the end of the URL to properly
1429 # update to a directory which has since been deleted and recreated.
1435 # update to a directory which has since been deleted and recreated.
1430 args.append('%s@%s' % (state[0], state[1]))
1436 args.append('%s@%s' % (state[0], state[1]))
1431
1437
1432 # SEC: check that the ssh url is safe
1438 # SEC: check that the ssh url is safe
1433 util.checksafessh(state[0])
1439 util.checksafessh(state[0])
1434
1440
1435 status, err = self._svncommand(args, failok=True)
1441 status, err = self._svncommand(args, failok=True)
1436 _sanitize(self.ui, self.wvfs, '.svn')
1442 _sanitize(self.ui, self.wvfs, '.svn')
1437 if not re.search('Checked out revision [0-9]+.', status):
1443 if not re.search('Checked out revision [0-9]+.', status):
1438 if ('is already a working copy for a different URL' in err
1444 if ('is already a working copy for a different URL' in err
1439 and (self._wcchanged()[:2] == (False, False))):
1445 and (self._wcchanged()[:2] == (False, False))):
1440 # obstructed but clean working copy, so just blow it away.
1446 # obstructed but clean working copy, so just blow it away.
1441 self.remove()
1447 self.remove()
1442 self.get(state, overwrite=False)
1448 self.get(state, overwrite=False)
1443 return
1449 return
1444 raise error.Abort((status or err).splitlines()[-1])
1450 raise error.Abort((status or err).splitlines()[-1])
1445 self.ui.status(status)
1451 self.ui.status(status)
1446
1452
1447 @annotatesubrepoerror
1453 @annotatesubrepoerror
1448 def merge(self, state):
1454 def merge(self, state):
1449 old = self._state[1]
1455 old = self._state[1]
1450 new = state[1]
1456 new = state[1]
1451 wcrev = self._wcrev()
1457 wcrev = self._wcrev()
1452 if new != wcrev:
1458 if new != wcrev:
1453 dirty = old == wcrev or self._wcchanged()[0]
1459 dirty = old == wcrev or self._wcchanged()[0]
1454 if _updateprompt(self.ui, self, dirty, wcrev, new):
1460 if _updateprompt(self.ui, self, dirty, wcrev, new):
1455 self.get(state, False)
1461 self.get(state, False)
1456
1462
1457 def push(self, opts):
1463 def push(self, opts):
1458 # push is a no-op for SVN
1464 # push is a no-op for SVN
1459 return True
1465 return True
1460
1466
1461 @annotatesubrepoerror
1467 @annotatesubrepoerror
1462 def files(self):
1468 def files(self):
1463 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1469 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1464 doc = xml.dom.minidom.parseString(output)
1470 doc = xml.dom.minidom.parseString(output)
1465 paths = []
1471 paths = []
1466 for e in doc.getElementsByTagName('entry'):
1472 for e in doc.getElementsByTagName('entry'):
1467 kind = str(e.getAttribute('kind'))
1473 kind = str(e.getAttribute('kind'))
1468 if kind != 'file':
1474 if kind != 'file':
1469 continue
1475 continue
1470 name = ''.join(c.data for c
1476 name = ''.join(c.data for c
1471 in e.getElementsByTagName('name')[0].childNodes
1477 in e.getElementsByTagName('name')[0].childNodes
1472 if c.nodeType == c.TEXT_NODE)
1478 if c.nodeType == c.TEXT_NODE)
1473 paths.append(name.encode('utf-8'))
1479 paths.append(name.encode('utf-8'))
1474 return paths
1480 return paths
1475
1481
1476 def filedata(self, name, decode):
1482 def filedata(self, name, decode):
1477 return self._svncommand(['cat'], name)[0]
1483 return self._svncommand(['cat'], name)[0]
1478
1484
1479
1485
1480 class gitsubrepo(abstractsubrepo):
1486 class gitsubrepo(abstractsubrepo):
1481 def __init__(self, ctx, path, state, allowcreate):
1487 def __init__(self, ctx, path, state, allowcreate):
1482 super(gitsubrepo, self).__init__(ctx, path)
1488 super(gitsubrepo, self).__init__(ctx, path)
1483 self._state = state
1489 self._state = state
1484 self._abspath = ctx.repo().wjoin(path)
1490 self._abspath = ctx.repo().wjoin(path)
1485 self._subparent = ctx.repo()
1491 self._subparent = ctx.repo()
1486 self._ensuregit()
1492 self._ensuregit()
1487
1493
1488 def _ensuregit(self):
1494 def _ensuregit(self):
1489 try:
1495 try:
1490 self._gitexecutable = 'git'
1496 self._gitexecutable = 'git'
1491 out, err = self._gitnodir(['--version'])
1497 out, err = self._gitnodir(['--version'])
1492 except OSError as e:
1498 except OSError as e:
1493 genericerror = _("error executing git for subrepo '%s': %s")
1499 genericerror = _("error executing git for subrepo '%s': %s")
1494 notfoundhint = _("check git is installed and in your PATH")
1500 notfoundhint = _("check git is installed and in your PATH")
1495 if e.errno != errno.ENOENT:
1501 if e.errno != errno.ENOENT:
1496 raise error.Abort(genericerror % (
1502 raise error.Abort(genericerror % (
1497 self._path, encoding.strtolocal(e.strerror)))
1503 self._path, encoding.strtolocal(e.strerror)))
1498 elif pycompat.iswindows:
1504 elif pycompat.iswindows:
1499 try:
1505 try:
1500 self._gitexecutable = 'git.cmd'
1506 self._gitexecutable = 'git.cmd'
1501 out, err = self._gitnodir(['--version'])
1507 out, err = self._gitnodir(['--version'])
1502 except OSError as e2:
1508 except OSError as e2:
1503 if e2.errno == errno.ENOENT:
1509 if e2.errno == errno.ENOENT:
1504 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1510 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1505 " for subrepo '%s'") % self._path,
1511 " for subrepo '%s'") % self._path,
1506 hint=notfoundhint)
1512 hint=notfoundhint)
1507 else:
1513 else:
1508 raise error.Abort(genericerror % (self._path,
1514 raise error.Abort(genericerror % (self._path,
1509 encoding.strtolocal(e2.strerror)))
1515 encoding.strtolocal(e2.strerror)))
1510 else:
1516 else:
1511 raise error.Abort(_("couldn't find git for subrepo '%s'")
1517 raise error.Abort(_("couldn't find git for subrepo '%s'")
1512 % self._path, hint=notfoundhint)
1518 % self._path, hint=notfoundhint)
1513 versionstatus = self._checkversion(out)
1519 versionstatus = self._checkversion(out)
1514 if versionstatus == 'unknown':
1520 if versionstatus == 'unknown':
1515 self.ui.warn(_('cannot retrieve git version\n'))
1521 self.ui.warn(_('cannot retrieve git version\n'))
1516 elif versionstatus == 'abort':
1522 elif versionstatus == 'abort':
1517 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1523 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1518 elif versionstatus == 'warning':
1524 elif versionstatus == 'warning':
1519 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1525 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1520
1526
1521 @staticmethod
1527 @staticmethod
1522 def _gitversion(out):
1528 def _gitversion(out):
1523 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1529 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1524 if m:
1530 if m:
1525 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1531 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1526
1532
1527 m = re.search(br'^git version (\d+)\.(\d+)', out)
1533 m = re.search(br'^git version (\d+)\.(\d+)', out)
1528 if m:
1534 if m:
1529 return (int(m.group(1)), int(m.group(2)), 0)
1535 return (int(m.group(1)), int(m.group(2)), 0)
1530
1536
1531 return -1
1537 return -1
1532
1538
1533 @staticmethod
1539 @staticmethod
1534 def _checkversion(out):
1540 def _checkversion(out):
1535 '''ensure git version is new enough
1541 '''ensure git version is new enough
1536
1542
1537 >>> _checkversion = gitsubrepo._checkversion
1543 >>> _checkversion = gitsubrepo._checkversion
1538 >>> _checkversion(b'git version 1.6.0')
1544 >>> _checkversion(b'git version 1.6.0')
1539 'ok'
1545 'ok'
1540 >>> _checkversion(b'git version 1.8.5')
1546 >>> _checkversion(b'git version 1.8.5')
1541 'ok'
1547 'ok'
1542 >>> _checkversion(b'git version 1.4.0')
1548 >>> _checkversion(b'git version 1.4.0')
1543 'abort'
1549 'abort'
1544 >>> _checkversion(b'git version 1.5.0')
1550 >>> _checkversion(b'git version 1.5.0')
1545 'warning'
1551 'warning'
1546 >>> _checkversion(b'git version 1.9-rc0')
1552 >>> _checkversion(b'git version 1.9-rc0')
1547 'ok'
1553 'ok'
1548 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1554 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1549 'ok'
1555 'ok'
1550 >>> _checkversion(b'git version 1.9.0.GIT')
1556 >>> _checkversion(b'git version 1.9.0.GIT')
1551 'ok'
1557 'ok'
1552 >>> _checkversion(b'git version 12345')
1558 >>> _checkversion(b'git version 12345')
1553 'unknown'
1559 'unknown'
1554 >>> _checkversion(b'no')
1560 >>> _checkversion(b'no')
1555 'unknown'
1561 'unknown'
1556 '''
1562 '''
1557 version = gitsubrepo._gitversion(out)
1563 version = gitsubrepo._gitversion(out)
1558 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1564 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1559 # despite the docstring comment. For now, error on 1.4.0, warn on
1565 # despite the docstring comment. For now, error on 1.4.0, warn on
1560 # 1.5.0 but attempt to continue.
1566 # 1.5.0 but attempt to continue.
1561 if version == -1:
1567 if version == -1:
1562 return 'unknown'
1568 return 'unknown'
1563 if version < (1, 5, 0):
1569 if version < (1, 5, 0):
1564 return 'abort'
1570 return 'abort'
1565 elif version < (1, 6, 0):
1571 elif version < (1, 6, 0):
1566 return 'warning'
1572 return 'warning'
1567 return 'ok'
1573 return 'ok'
1568
1574
1569 def _gitcommand(self, commands, env=None, stream=False):
1575 def _gitcommand(self, commands, env=None, stream=False):
1570 return self._gitdir(commands, env=env, stream=stream)[0]
1576 return self._gitdir(commands, env=env, stream=stream)[0]
1571
1577
1572 def _gitdir(self, commands, env=None, stream=False):
1578 def _gitdir(self, commands, env=None, stream=False):
1573 return self._gitnodir(commands, env=env, stream=stream,
1579 return self._gitnodir(commands, env=env, stream=stream,
1574 cwd=self._abspath)
1580 cwd=self._abspath)
1575
1581
1576 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1582 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1577 """Calls the git command
1583 """Calls the git command
1578
1584
1579 The methods tries to call the git command. versions prior to 1.6.0
1585 The methods tries to call the git command. versions prior to 1.6.0
1580 are not supported and very probably fail.
1586 are not supported and very probably fail.
1581 """
1587 """
1582 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1588 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1583 if env is None:
1589 if env is None:
1584 env = encoding.environ.copy()
1590 env = encoding.environ.copy()
1585 # disable localization for Git output (issue5176)
1591 # disable localization for Git output (issue5176)
1586 env['LC_ALL'] = 'C'
1592 env['LC_ALL'] = 'C'
1587 # fix for Git CVE-2015-7545
1593 # fix for Git CVE-2015-7545
1588 if 'GIT_ALLOW_PROTOCOL' not in env:
1594 if 'GIT_ALLOW_PROTOCOL' not in env:
1589 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1595 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1590 # unless ui.quiet is set, print git's stderr,
1596 # unless ui.quiet is set, print git's stderr,
1591 # which is mostly progress and useful info
1597 # which is mostly progress and useful info
1592 errpipe = None
1598 errpipe = None
1593 if self.ui.quiet:
1599 if self.ui.quiet:
1594 errpipe = open(os.devnull, 'w')
1600 errpipe = open(os.devnull, 'w')
1595 if self.ui._colormode and len(commands) and commands[0] == "diff":
1601 if self.ui._colormode and len(commands) and commands[0] == "diff":
1596 # insert the argument in the front,
1602 # insert the argument in the front,
1597 # the end of git diff arguments is used for paths
1603 # the end of git diff arguments is used for paths
1598 commands.insert(1, '--color')
1604 commands.insert(1, '--color')
1599 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1605 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1600 cwd=cwd, env=env, close_fds=util.closefds,
1606 cwd=cwd, env=env, close_fds=util.closefds,
1601 stdout=subprocess.PIPE, stderr=errpipe)
1607 stdout=subprocess.PIPE, stderr=errpipe)
1602 if stream:
1608 if stream:
1603 return p.stdout, None
1609 return p.stdout, None
1604
1610
1605 retdata = p.stdout.read().strip()
1611 retdata = p.stdout.read().strip()
1606 # wait for the child to exit to avoid race condition.
1612 # wait for the child to exit to avoid race condition.
1607 p.wait()
1613 p.wait()
1608
1614
1609 if p.returncode != 0 and p.returncode != 1:
1615 if p.returncode != 0 and p.returncode != 1:
1610 # there are certain error codes that are ok
1616 # there are certain error codes that are ok
1611 command = commands[0]
1617 command = commands[0]
1612 if command in ('cat-file', 'symbolic-ref'):
1618 if command in ('cat-file', 'symbolic-ref'):
1613 return retdata, p.returncode
1619 return retdata, p.returncode
1614 # for all others, abort
1620 # for all others, abort
1615 raise error.Abort(_('git %s error %d in %s') %
1621 raise error.Abort(_('git %s error %d in %s') %
1616 (command, p.returncode, self._relpath))
1622 (command, p.returncode, self._relpath))
1617
1623
1618 return retdata, p.returncode
1624 return retdata, p.returncode
1619
1625
1620 def _gitmissing(self):
1626 def _gitmissing(self):
1621 return not self.wvfs.exists('.git')
1627 return not self.wvfs.exists('.git')
1622
1628
1623 def _gitstate(self):
1629 def _gitstate(self):
1624 return self._gitcommand(['rev-parse', 'HEAD'])
1630 return self._gitcommand(['rev-parse', 'HEAD'])
1625
1631
1626 def _gitcurrentbranch(self):
1632 def _gitcurrentbranch(self):
1627 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1633 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1628 if err:
1634 if err:
1629 current = None
1635 current = None
1630 return current
1636 return current
1631
1637
1632 def _gitremote(self, remote):
1638 def _gitremote(self, remote):
1633 out = self._gitcommand(['remote', 'show', '-n', remote])
1639 out = self._gitcommand(['remote', 'show', '-n', remote])
1634 line = out.split('\n')[1]
1640 line = out.split('\n')[1]
1635 i = line.index('URL: ') + len('URL: ')
1641 i = line.index('URL: ') + len('URL: ')
1636 return line[i:]
1642 return line[i:]
1637
1643
1638 def _githavelocally(self, revision):
1644 def _githavelocally(self, revision):
1639 out, code = self._gitdir(['cat-file', '-e', revision])
1645 out, code = self._gitdir(['cat-file', '-e', revision])
1640 return code == 0
1646 return code == 0
1641
1647
1642 def _gitisancestor(self, r1, r2):
1648 def _gitisancestor(self, r1, r2):
1643 base = self._gitcommand(['merge-base', r1, r2])
1649 base = self._gitcommand(['merge-base', r1, r2])
1644 return base == r1
1650 return base == r1
1645
1651
1646 def _gitisbare(self):
1652 def _gitisbare(self):
1647 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1653 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1648
1654
1649 def _gitupdatestat(self):
1655 def _gitupdatestat(self):
1650 """This must be run before git diff-index.
1656 """This must be run before git diff-index.
1651 diff-index only looks at changes to file stat;
1657 diff-index only looks at changes to file stat;
1652 this command looks at file contents and updates the stat."""
1658 this command looks at file contents and updates the stat."""
1653 self._gitcommand(['update-index', '-q', '--refresh'])
1659 self._gitcommand(['update-index', '-q', '--refresh'])
1654
1660
1655 def _gitbranchmap(self):
1661 def _gitbranchmap(self):
1656 '''returns 2 things:
1662 '''returns 2 things:
1657 a map from git branch to revision
1663 a map from git branch to revision
1658 a map from revision to branches'''
1664 a map from revision to branches'''
1659 branch2rev = {}
1665 branch2rev = {}
1660 rev2branch = {}
1666 rev2branch = {}
1661
1667
1662 out = self._gitcommand(['for-each-ref', '--format',
1668 out = self._gitcommand(['for-each-ref', '--format',
1663 '%(objectname) %(refname)'])
1669 '%(objectname) %(refname)'])
1664 for line in out.split('\n'):
1670 for line in out.split('\n'):
1665 revision, ref = line.split(' ')
1671 revision, ref = line.split(' ')
1666 if (not ref.startswith('refs/heads/') and
1672 if (not ref.startswith('refs/heads/') and
1667 not ref.startswith('refs/remotes/')):
1673 not ref.startswith('refs/remotes/')):
1668 continue
1674 continue
1669 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1675 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1670 continue # ignore remote/HEAD redirects
1676 continue # ignore remote/HEAD redirects
1671 branch2rev[ref] = revision
1677 branch2rev[ref] = revision
1672 rev2branch.setdefault(revision, []).append(ref)
1678 rev2branch.setdefault(revision, []).append(ref)
1673 return branch2rev, rev2branch
1679 return branch2rev, rev2branch
1674
1680
1675 def _gittracking(self, branches):
1681 def _gittracking(self, branches):
1676 'return map of remote branch to local tracking branch'
1682 'return map of remote branch to local tracking branch'
1677 # assumes no more than one local tracking branch for each remote
1683 # assumes no more than one local tracking branch for each remote
1678 tracking = {}
1684 tracking = {}
1679 for b in branches:
1685 for b in branches:
1680 if b.startswith('refs/remotes/'):
1686 if b.startswith('refs/remotes/'):
1681 continue
1687 continue
1682 bname = b.split('/', 2)[2]
1688 bname = b.split('/', 2)[2]
1683 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1689 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1684 if remote:
1690 if remote:
1685 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1691 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1686 tracking['refs/remotes/%s/%s' %
1692 tracking['refs/remotes/%s/%s' %
1687 (remote, ref.split('/', 2)[2])] = b
1693 (remote, ref.split('/', 2)[2])] = b
1688 return tracking
1694 return tracking
1689
1695
1690 def _abssource(self, source):
1696 def _abssource(self, source):
1691 if '://' not in source:
1697 if '://' not in source:
1692 # recognize the scp syntax as an absolute source
1698 # recognize the scp syntax as an absolute source
1693 colon = source.find(':')
1699 colon = source.find(':')
1694 if colon != -1 and '/' not in source[:colon]:
1700 if colon != -1 and '/' not in source[:colon]:
1695 return source
1701 return source
1696 self._subsource = source
1702 self._subsource = source
1697 return _abssource(self)
1703 return _abssource(self)
1698
1704
1699 def _fetch(self, source, revision):
1705 def _fetch(self, source, revision):
1700 if self._gitmissing():
1706 if self._gitmissing():
1701 # SEC: check for safe ssh url
1707 # SEC: check for safe ssh url
1702 util.checksafessh(source)
1708 util.checksafessh(source)
1703
1709
1704 source = self._abssource(source)
1710 source = self._abssource(source)
1705 self.ui.status(_('cloning subrepo %s from %s\n') %
1711 self.ui.status(_('cloning subrepo %s from %s\n') %
1706 (self._relpath, source))
1712 (self._relpath, source))
1707 self._gitnodir(['clone', source, self._abspath])
1713 self._gitnodir(['clone', source, self._abspath])
1708 if self._githavelocally(revision):
1714 if self._githavelocally(revision):
1709 return
1715 return
1710 self.ui.status(_('pulling subrepo %s from %s\n') %
1716 self.ui.status(_('pulling subrepo %s from %s\n') %
1711 (self._relpath, self._gitremote('origin')))
1717 (self._relpath, self._gitremote('origin')))
1712 # try only origin: the originally cloned repo
1718 # try only origin: the originally cloned repo
1713 self._gitcommand(['fetch'])
1719 self._gitcommand(['fetch'])
1714 if not self._githavelocally(revision):
1720 if not self._githavelocally(revision):
1715 raise error.Abort(_('revision %s does not exist in subrepository '
1721 raise error.Abort(_('revision %s does not exist in subrepository '
1716 '"%s"\n') % (revision, self._relpath))
1722 '"%s"\n') % (revision, self._relpath))
1717
1723
1718 @annotatesubrepoerror
1724 @annotatesubrepoerror
1719 def dirty(self, ignoreupdate=False, missing=False):
1725 def dirty(self, ignoreupdate=False, missing=False):
1720 if self._gitmissing():
1726 if self._gitmissing():
1721 return self._state[1] != ''
1727 return self._state[1] != ''
1722 if self._gitisbare():
1728 if self._gitisbare():
1723 return True
1729 return True
1724 if not ignoreupdate and self._state[1] != self._gitstate():
1730 if not ignoreupdate and self._state[1] != self._gitstate():
1725 # different version checked out
1731 # different version checked out
1726 return True
1732 return True
1727 # check for staged changes or modified files; ignore untracked files
1733 # check for staged changes or modified files; ignore untracked files
1728 self._gitupdatestat()
1734 self._gitupdatestat()
1729 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1735 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1730 return code == 1
1736 return code == 1
1731
1737
1732 def basestate(self):
1738 def basestate(self):
1733 return self._gitstate()
1739 return self._gitstate()
1734
1740
1735 @annotatesubrepoerror
1741 @annotatesubrepoerror
1736 def get(self, state, overwrite=False):
1742 def get(self, state, overwrite=False):
1737 source, revision, kind = state
1743 source, revision, kind = state
1738 if not revision:
1744 if not revision:
1739 self.remove()
1745 self.remove()
1740 return
1746 return
1741 self._fetch(source, revision)
1747 self._fetch(source, revision)
1742 # if the repo was set to be bare, unbare it
1748 # if the repo was set to be bare, unbare it
1743 if self._gitisbare():
1749 if self._gitisbare():
1744 self._gitcommand(['config', 'core.bare', 'false'])
1750 self._gitcommand(['config', 'core.bare', 'false'])
1745 if self._gitstate() == revision:
1751 if self._gitstate() == revision:
1746 self._gitcommand(['reset', '--hard', 'HEAD'])
1752 self._gitcommand(['reset', '--hard', 'HEAD'])
1747 return
1753 return
1748 elif self._gitstate() == revision:
1754 elif self._gitstate() == revision:
1749 if overwrite:
1755 if overwrite:
1750 # first reset the index to unmark new files for commit, because
1756 # first reset the index to unmark new files for commit, because
1751 # reset --hard will otherwise throw away files added for commit,
1757 # reset --hard will otherwise throw away files added for commit,
1752 # not just unmark them.
1758 # not just unmark them.
1753 self._gitcommand(['reset', 'HEAD'])
1759 self._gitcommand(['reset', 'HEAD'])
1754 self._gitcommand(['reset', '--hard', 'HEAD'])
1760 self._gitcommand(['reset', '--hard', 'HEAD'])
1755 return
1761 return
1756 branch2rev, rev2branch = self._gitbranchmap()
1762 branch2rev, rev2branch = self._gitbranchmap()
1757
1763
1758 def checkout(args):
1764 def checkout(args):
1759 cmd = ['checkout']
1765 cmd = ['checkout']
1760 if overwrite:
1766 if overwrite:
1761 # first reset the index to unmark new files for commit, because
1767 # first reset the index to unmark new files for commit, because
1762 # the -f option will otherwise throw away files added for
1768 # the -f option will otherwise throw away files added for
1763 # commit, not just unmark them.
1769 # commit, not just unmark them.
1764 self._gitcommand(['reset', 'HEAD'])
1770 self._gitcommand(['reset', 'HEAD'])
1765 cmd.append('-f')
1771 cmd.append('-f')
1766 self._gitcommand(cmd + args)
1772 self._gitcommand(cmd + args)
1767 _sanitize(self.ui, self.wvfs, '.git')
1773 _sanitize(self.ui, self.wvfs, '.git')
1768
1774
1769 def rawcheckout():
1775 def rawcheckout():
1770 # no branch to checkout, check it out with no branch
1776 # no branch to checkout, check it out with no branch
1771 self.ui.warn(_('checking out detached HEAD in '
1777 self.ui.warn(_('checking out detached HEAD in '
1772 'subrepository "%s"\n') % self._relpath)
1778 'subrepository "%s"\n') % self._relpath)
1773 self.ui.warn(_('check out a git branch if you intend '
1779 self.ui.warn(_('check out a git branch if you intend '
1774 'to make changes\n'))
1780 'to make changes\n'))
1775 checkout(['-q', revision])
1781 checkout(['-q', revision])
1776
1782
1777 if revision not in rev2branch:
1783 if revision not in rev2branch:
1778 rawcheckout()
1784 rawcheckout()
1779 return
1785 return
1780 branches = rev2branch[revision]
1786 branches = rev2branch[revision]
1781 firstlocalbranch = None
1787 firstlocalbranch = None
1782 for b in branches:
1788 for b in branches:
1783 if b == 'refs/heads/master':
1789 if b == 'refs/heads/master':
1784 # master trumps all other branches
1790 # master trumps all other branches
1785 checkout(['refs/heads/master'])
1791 checkout(['refs/heads/master'])
1786 return
1792 return
1787 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1793 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1788 firstlocalbranch = b
1794 firstlocalbranch = b
1789 if firstlocalbranch:
1795 if firstlocalbranch:
1790 checkout([firstlocalbranch])
1796 checkout([firstlocalbranch])
1791 return
1797 return
1792
1798
1793 tracking = self._gittracking(branch2rev.keys())
1799 tracking = self._gittracking(branch2rev.keys())
1794 # choose a remote branch already tracked if possible
1800 # choose a remote branch already tracked if possible
1795 remote = branches[0]
1801 remote = branches[0]
1796 if remote not in tracking:
1802 if remote not in tracking:
1797 for b in branches:
1803 for b in branches:
1798 if b in tracking:
1804 if b in tracking:
1799 remote = b
1805 remote = b
1800 break
1806 break
1801
1807
1802 if remote not in tracking:
1808 if remote not in tracking:
1803 # create a new local tracking branch
1809 # create a new local tracking branch
1804 local = remote.split('/', 3)[3]
1810 local = remote.split('/', 3)[3]
1805 checkout(['-b', local, remote])
1811 checkout(['-b', local, remote])
1806 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1812 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1807 # When updating to a tracked remote branch,
1813 # When updating to a tracked remote branch,
1808 # if the local tracking branch is downstream of it,
1814 # if the local tracking branch is downstream of it,
1809 # a normal `git pull` would have performed a "fast-forward merge"
1815 # a normal `git pull` would have performed a "fast-forward merge"
1810 # which is equivalent to updating the local branch to the remote.
1816 # which is equivalent to updating the local branch to the remote.
1811 # Since we are only looking at branching at update, we need to
1817 # Since we are only looking at branching at update, we need to
1812 # detect this situation and perform this action lazily.
1818 # detect this situation and perform this action lazily.
1813 if tracking[remote] != self._gitcurrentbranch():
1819 if tracking[remote] != self._gitcurrentbranch():
1814 checkout([tracking[remote]])
1820 checkout([tracking[remote]])
1815 self._gitcommand(['merge', '--ff', remote])
1821 self._gitcommand(['merge', '--ff', remote])
1816 _sanitize(self.ui, self.wvfs, '.git')
1822 _sanitize(self.ui, self.wvfs, '.git')
1817 else:
1823 else:
1818 # a real merge would be required, just checkout the revision
1824 # a real merge would be required, just checkout the revision
1819 rawcheckout()
1825 rawcheckout()
1820
1826
1821 @annotatesubrepoerror
1827 @annotatesubrepoerror
1822 def commit(self, text, user, date):
1828 def commit(self, text, user, date):
1823 if self._gitmissing():
1829 if self._gitmissing():
1824 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1830 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1825 cmd = ['commit', '-a', '-m', text]
1831 cmd = ['commit', '-a', '-m', text]
1826 env = encoding.environ.copy()
1832 env = encoding.environ.copy()
1827 if user:
1833 if user:
1828 cmd += ['--author', user]
1834 cmd += ['--author', user]
1829 if date:
1835 if date:
1830 # git's date parser silently ignores when seconds < 1e9
1836 # git's date parser silently ignores when seconds < 1e9
1831 # convert to ISO8601
1837 # convert to ISO8601
1832 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1838 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1833 '%Y-%m-%dT%H:%M:%S %1%2')
1839 '%Y-%m-%dT%H:%M:%S %1%2')
1834 self._gitcommand(cmd, env=env)
1840 self._gitcommand(cmd, env=env)
1835 # make sure commit works otherwise HEAD might not exist under certain
1841 # make sure commit works otherwise HEAD might not exist under certain
1836 # circumstances
1842 # circumstances
1837 return self._gitstate()
1843 return self._gitstate()
1838
1844
1839 @annotatesubrepoerror
1845 @annotatesubrepoerror
1840 def merge(self, state):
1846 def merge(self, state):
1841 source, revision, kind = state
1847 source, revision, kind = state
1842 self._fetch(source, revision)
1848 self._fetch(source, revision)
1843 base = self._gitcommand(['merge-base', revision, self._state[1]])
1849 base = self._gitcommand(['merge-base', revision, self._state[1]])
1844 self._gitupdatestat()
1850 self._gitupdatestat()
1845 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1851 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1846
1852
1847 def mergefunc():
1853 def mergefunc():
1848 if base == revision:
1854 if base == revision:
1849 self.get(state) # fast forward merge
1855 self.get(state) # fast forward merge
1850 elif base != self._state[1]:
1856 elif base != self._state[1]:
1851 self._gitcommand(['merge', '--no-commit', revision])
1857 self._gitcommand(['merge', '--no-commit', revision])
1852 _sanitize(self.ui, self.wvfs, '.git')
1858 _sanitize(self.ui, self.wvfs, '.git')
1853
1859
1854 if self.dirty():
1860 if self.dirty():
1855 if self._gitstate() != revision:
1861 if self._gitstate() != revision:
1856 dirty = self._gitstate() == self._state[1] or code != 0
1862 dirty = self._gitstate() == self._state[1] or code != 0
1857 if _updateprompt(self.ui, self, dirty,
1863 if _updateprompt(self.ui, self, dirty,
1858 self._state[1][:7], revision[:7]):
1864 self._state[1][:7], revision[:7]):
1859 mergefunc()
1865 mergefunc()
1860 else:
1866 else:
1861 mergefunc()
1867 mergefunc()
1862
1868
1863 @annotatesubrepoerror
1869 @annotatesubrepoerror
1864 def push(self, opts):
1870 def push(self, opts):
1865 force = opts.get('force')
1871 force = opts.get('force')
1866
1872
1867 if not self._state[1]:
1873 if not self._state[1]:
1868 return True
1874 return True
1869 if self._gitmissing():
1875 if self._gitmissing():
1870 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1876 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1871 # if a branch in origin contains the revision, nothing to do
1877 # if a branch in origin contains the revision, nothing to do
1872 branch2rev, rev2branch = self._gitbranchmap()
1878 branch2rev, rev2branch = self._gitbranchmap()
1873 if self._state[1] in rev2branch:
1879 if self._state[1] in rev2branch:
1874 for b in rev2branch[self._state[1]]:
1880 for b in rev2branch[self._state[1]]:
1875 if b.startswith('refs/remotes/origin/'):
1881 if b.startswith('refs/remotes/origin/'):
1876 return True
1882 return True
1877 for b, revision in branch2rev.iteritems():
1883 for b, revision in branch2rev.iteritems():
1878 if b.startswith('refs/remotes/origin/'):
1884 if b.startswith('refs/remotes/origin/'):
1879 if self._gitisancestor(self._state[1], revision):
1885 if self._gitisancestor(self._state[1], revision):
1880 return True
1886 return True
1881 # otherwise, try to push the currently checked out branch
1887 # otherwise, try to push the currently checked out branch
1882 cmd = ['push']
1888 cmd = ['push']
1883 if force:
1889 if force:
1884 cmd.append('--force')
1890 cmd.append('--force')
1885
1891
1886 current = self._gitcurrentbranch()
1892 current = self._gitcurrentbranch()
1887 if current:
1893 if current:
1888 # determine if the current branch is even useful
1894 # determine if the current branch is even useful
1889 if not self._gitisancestor(self._state[1], current):
1895 if not self._gitisancestor(self._state[1], current):
1890 self.ui.warn(_('unrelated git branch checked out '
1896 self.ui.warn(_('unrelated git branch checked out '
1891 'in subrepository "%s"\n') % self._relpath)
1897 'in subrepository "%s"\n') % self._relpath)
1892 return False
1898 return False
1893 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1899 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1894 (current.split('/', 2)[2], self._relpath))
1900 (current.split('/', 2)[2], self._relpath))
1895 ret = self._gitdir(cmd + ['origin', current])
1901 ret = self._gitdir(cmd + ['origin', current])
1896 return ret[1] == 0
1902 return ret[1] == 0
1897 else:
1903 else:
1898 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1904 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1899 'cannot push revision %s\n') %
1905 'cannot push revision %s\n') %
1900 (self._relpath, self._state[1]))
1906 (self._relpath, self._state[1]))
1901 return False
1907 return False
1902
1908
1903 @annotatesubrepoerror
1909 @annotatesubrepoerror
1904 def add(self, ui, match, prefix, explicitonly, **opts):
1910 def add(self, ui, match, prefix, explicitonly, **opts):
1905 if self._gitmissing():
1911 if self._gitmissing():
1906 return []
1912 return []
1907
1913
1908 (modified, added, removed,
1914 (modified, added, removed,
1909 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1915 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1910 clean=True)
1916 clean=True)
1911
1917
1912 tracked = set()
1918 tracked = set()
1913 # dirstates 'amn' warn, 'r' is added again
1919 # dirstates 'amn' warn, 'r' is added again
1914 for l in (modified, added, deleted, clean):
1920 for l in (modified, added, deleted, clean):
1915 tracked.update(l)
1921 tracked.update(l)
1916
1922
1917 # Unknown files not of interest will be rejected by the matcher
1923 # Unknown files not of interest will be rejected by the matcher
1918 files = unknown
1924 files = unknown
1919 files.extend(match.files())
1925 files.extend(match.files())
1920
1926
1921 rejected = []
1927 rejected = []
1922
1928
1923 files = [f for f in sorted(set(files)) if match(f)]
1929 files = [f for f in sorted(set(files)) if match(f)]
1924 for f in files:
1930 for f in files:
1925 exact = match.exact(f)
1931 exact = match.exact(f)
1926 command = ["add"]
1932 command = ["add"]
1927 if exact:
1933 if exact:
1928 command.append("-f") #should be added, even if ignored
1934 command.append("-f") #should be added, even if ignored
1929 if ui.verbose or not exact:
1935 if ui.verbose or not exact:
1930 ui.status(_('adding %s\n') % match.rel(f))
1936 ui.status(_('adding %s\n') % match.rel(f))
1931
1937
1932 if f in tracked: # hg prints 'adding' even if already tracked
1938 if f in tracked: # hg prints 'adding' even if already tracked
1933 if exact:
1939 if exact:
1934 rejected.append(f)
1940 rejected.append(f)
1935 continue
1941 continue
1936 if not opts.get(r'dry_run'):
1942 if not opts.get(r'dry_run'):
1937 self._gitcommand(command + [f])
1943 self._gitcommand(command + [f])
1938
1944
1939 for f in rejected:
1945 for f in rejected:
1940 ui.warn(_("%s already tracked!\n") % match.abs(f))
1946 ui.warn(_("%s already tracked!\n") % match.abs(f))
1941
1947
1942 return rejected
1948 return rejected
1943
1949
1944 @annotatesubrepoerror
1950 @annotatesubrepoerror
1945 def remove(self):
1951 def remove(self):
1946 if self._gitmissing():
1952 if self._gitmissing():
1947 return
1953 return
1948 if self.dirty():
1954 if self.dirty():
1949 self.ui.warn(_('not removing repo %s because '
1955 self.ui.warn(_('not removing repo %s because '
1950 'it has changes.\n') % self._relpath)
1956 'it has changes.\n') % self._relpath)
1951 return
1957 return
1952 # we can't fully delete the repository as it may contain
1958 # we can't fully delete the repository as it may contain
1953 # local-only history
1959 # local-only history
1954 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1960 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1955 self._gitcommand(['config', 'core.bare', 'true'])
1961 self._gitcommand(['config', 'core.bare', 'true'])
1956 for f, kind in self.wvfs.readdir():
1962 for f, kind in self.wvfs.readdir():
1957 if f == '.git':
1963 if f == '.git':
1958 continue
1964 continue
1959 if kind == stat.S_IFDIR:
1965 if kind == stat.S_IFDIR:
1960 self.wvfs.rmtree(f)
1966 self.wvfs.rmtree(f)
1961 else:
1967 else:
1962 self.wvfs.unlink(f)
1968 self.wvfs.unlink(f)
1963
1969
1964 def archive(self, archiver, prefix, match=None, decode=True):
1970 def archive(self, archiver, prefix, match=None, decode=True):
1965 total = 0
1971 total = 0
1966 source, revision = self._state
1972 source, revision = self._state
1967 if not revision:
1973 if not revision:
1968 return total
1974 return total
1969 self._fetch(source, revision)
1975 self._fetch(source, revision)
1970
1976
1971 # Parse git's native archive command.
1977 # Parse git's native archive command.
1972 # This should be much faster than manually traversing the trees
1978 # This should be much faster than manually traversing the trees
1973 # and objects with many subprocess calls.
1979 # and objects with many subprocess calls.
1974 tarstream = self._gitcommand(['archive', revision], stream=True)
1980 tarstream = self._gitcommand(['archive', revision], stream=True)
1975 tar = tarfile.open(fileobj=tarstream, mode='r|')
1981 tar = tarfile.open(fileobj=tarstream, mode='r|')
1976 relpath = subrelpath(self)
1982 relpath = subrelpath(self)
1977 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1983 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1978 for i, info in enumerate(tar):
1984 for i, info in enumerate(tar):
1979 if info.isdir():
1985 if info.isdir():
1980 continue
1986 continue
1981 if match and not match(info.name):
1987 if match and not match(info.name):
1982 continue
1988 continue
1983 if info.issym():
1989 if info.issym():
1984 data = info.linkname
1990 data = info.linkname
1985 else:
1991 else:
1986 data = tar.extractfile(info).read()
1992 data = tar.extractfile(info).read()
1987 archiver.addfile(prefix + self._path + '/' + info.name,
1993 archiver.addfile(prefix + self._path + '/' + info.name,
1988 info.mode, info.issym(), data)
1994 info.mode, info.issym(), data)
1989 total += 1
1995 total += 1
1990 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1996 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1991 unit=_('files'))
1997 unit=_('files'))
1992 self.ui.progress(_('archiving (%s)') % relpath, None)
1998 self.ui.progress(_('archiving (%s)') % relpath, None)
1993 return total
1999 return total
1994
2000
1995
2001
1996 @annotatesubrepoerror
2002 @annotatesubrepoerror
1997 def cat(self, match, fm, fntemplate, prefix, **opts):
2003 def cat(self, match, fm, fntemplate, prefix, **opts):
1998 rev = self._state[1]
2004 rev = self._state[1]
1999 if match.anypats():
2005 if match.anypats():
2000 return 1 #No support for include/exclude yet
2006 return 1 #No support for include/exclude yet
2001
2007
2002 if not match.files():
2008 if not match.files():
2003 return 1
2009 return 1
2004
2010
2005 # TODO: add support for non-plain formatter (see cmdutil.cat())
2011 # TODO: add support for non-plain formatter (see cmdutil.cat())
2006 for f in match.files():
2012 for f in match.files():
2007 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
2013 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
2008 fp = cmdutil.makefileobj(self._subparent, fntemplate,
2014 fp = cmdutil.makefileobj(self._subparent, fntemplate,
2009 self._ctx.node(),
2015 self._ctx.node(),
2010 pathname=self.wvfs.reljoin(prefix, f))
2016 pathname=self.wvfs.reljoin(prefix, f))
2011 fp.write(output)
2017 fp.write(output)
2012 fp.close()
2018 fp.close()
2013 return 0
2019 return 0
2014
2020
2015
2021
2016 @annotatesubrepoerror
2022 @annotatesubrepoerror
2017 def status(self, rev2, **opts):
2023 def status(self, rev2, **opts):
2018 rev1 = self._state[1]
2024 rev1 = self._state[1]
2019 if self._gitmissing() or not rev1:
2025 if self._gitmissing() or not rev1:
2020 # if the repo is missing, return no results
2026 # if the repo is missing, return no results
2021 return scmutil.status([], [], [], [], [], [], [])
2027 return scmutil.status([], [], [], [], [], [], [])
2022 modified, added, removed = [], [], []
2028 modified, added, removed = [], [], []
2023 self._gitupdatestat()
2029 self._gitupdatestat()
2024 if rev2:
2030 if rev2:
2025 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
2031 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
2026 else:
2032 else:
2027 command = ['diff-index', '--no-renames', rev1]
2033 command = ['diff-index', '--no-renames', rev1]
2028 out = self._gitcommand(command)
2034 out = self._gitcommand(command)
2029 for line in out.split('\n'):
2035 for line in out.split('\n'):
2030 tab = line.find('\t')
2036 tab = line.find('\t')
2031 if tab == -1:
2037 if tab == -1:
2032 continue
2038 continue
2033 status, f = line[tab - 1], line[tab + 1:]
2039 status, f = line[tab - 1], line[tab + 1:]
2034 if status == 'M':
2040 if status == 'M':
2035 modified.append(f)
2041 modified.append(f)
2036 elif status == 'A':
2042 elif status == 'A':
2037 added.append(f)
2043 added.append(f)
2038 elif status == 'D':
2044 elif status == 'D':
2039 removed.append(f)
2045 removed.append(f)
2040
2046
2041 deleted, unknown, ignored, clean = [], [], [], []
2047 deleted, unknown, ignored, clean = [], [], [], []
2042
2048
2043 command = ['status', '--porcelain', '-z']
2049 command = ['status', '--porcelain', '-z']
2044 if opts.get(r'unknown'):
2050 if opts.get(r'unknown'):
2045 command += ['--untracked-files=all']
2051 command += ['--untracked-files=all']
2046 if opts.get(r'ignored'):
2052 if opts.get(r'ignored'):
2047 command += ['--ignored']
2053 command += ['--ignored']
2048 out = self._gitcommand(command)
2054 out = self._gitcommand(command)
2049
2055
2050 changedfiles = set()
2056 changedfiles = set()
2051 changedfiles.update(modified)
2057 changedfiles.update(modified)
2052 changedfiles.update(added)
2058 changedfiles.update(added)
2053 changedfiles.update(removed)
2059 changedfiles.update(removed)
2054 for line in out.split('\0'):
2060 for line in out.split('\0'):
2055 if not line:
2061 if not line:
2056 continue
2062 continue
2057 st = line[0:2]
2063 st = line[0:2]
2058 #moves and copies show 2 files on one line
2064 #moves and copies show 2 files on one line
2059 if line.find('\0') >= 0:
2065 if line.find('\0') >= 0:
2060 filename1, filename2 = line[3:].split('\0')
2066 filename1, filename2 = line[3:].split('\0')
2061 else:
2067 else:
2062 filename1 = line[3:]
2068 filename1 = line[3:]
2063 filename2 = None
2069 filename2 = None
2064
2070
2065 changedfiles.add(filename1)
2071 changedfiles.add(filename1)
2066 if filename2:
2072 if filename2:
2067 changedfiles.add(filename2)
2073 changedfiles.add(filename2)
2068
2074
2069 if st == '??':
2075 if st == '??':
2070 unknown.append(filename1)
2076 unknown.append(filename1)
2071 elif st == '!!':
2077 elif st == '!!':
2072 ignored.append(filename1)
2078 ignored.append(filename1)
2073
2079
2074 if opts.get(r'clean'):
2080 if opts.get(r'clean'):
2075 out = self._gitcommand(['ls-files'])
2081 out = self._gitcommand(['ls-files'])
2076 for f in out.split('\n'):
2082 for f in out.split('\n'):
2077 if not f in changedfiles:
2083 if not f in changedfiles:
2078 clean.append(f)
2084 clean.append(f)
2079
2085
2080 return scmutil.status(modified, added, removed, deleted,
2086 return scmutil.status(modified, added, removed, deleted,
2081 unknown, ignored, clean)
2087 unknown, ignored, clean)
2082
2088
2083 @annotatesubrepoerror
2089 @annotatesubrepoerror
2084 def diff(self, ui, diffopts, node2, match, prefix, **opts):
2090 def diff(self, ui, diffopts, node2, match, prefix, **opts):
2085 node1 = self._state[1]
2091 node1 = self._state[1]
2086 cmd = ['diff', '--no-renames']
2092 cmd = ['diff', '--no-renames']
2087 if opts[r'stat']:
2093 if opts[r'stat']:
2088 cmd.append('--stat')
2094 cmd.append('--stat')
2089 else:
2095 else:
2090 # for Git, this also implies '-p'
2096 # for Git, this also implies '-p'
2091 cmd.append('-U%d' % diffopts.context)
2097 cmd.append('-U%d' % diffopts.context)
2092
2098
2093 gitprefix = self.wvfs.reljoin(prefix, self._path)
2099 gitprefix = self.wvfs.reljoin(prefix, self._path)
2094
2100
2095 if diffopts.noprefix:
2101 if diffopts.noprefix:
2096 cmd.extend(['--src-prefix=%s/' % gitprefix,
2102 cmd.extend(['--src-prefix=%s/' % gitprefix,
2097 '--dst-prefix=%s/' % gitprefix])
2103 '--dst-prefix=%s/' % gitprefix])
2098 else:
2104 else:
2099 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
2105 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
2100 '--dst-prefix=b/%s/' % gitprefix])
2106 '--dst-prefix=b/%s/' % gitprefix])
2101
2107
2102 if diffopts.ignorews:
2108 if diffopts.ignorews:
2103 cmd.append('--ignore-all-space')
2109 cmd.append('--ignore-all-space')
2104 if diffopts.ignorewsamount:
2110 if diffopts.ignorewsamount:
2105 cmd.append('--ignore-space-change')
2111 cmd.append('--ignore-space-change')
2106 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
2112 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
2107 and diffopts.ignoreblanklines:
2113 and diffopts.ignoreblanklines:
2108 cmd.append('--ignore-blank-lines')
2114 cmd.append('--ignore-blank-lines')
2109
2115
2110 cmd.append(node1)
2116 cmd.append(node1)
2111 if node2:
2117 if node2:
2112 cmd.append(node2)
2118 cmd.append(node2)
2113
2119
2114 output = ""
2120 output = ""
2115 if match.always():
2121 if match.always():
2116 output += self._gitcommand(cmd) + '\n'
2122 output += self._gitcommand(cmd) + '\n'
2117 else:
2123 else:
2118 st = self.status(node2)[:3]
2124 st = self.status(node2)[:3]
2119 files = [f for sublist in st for f in sublist]
2125 files = [f for sublist in st for f in sublist]
2120 for f in files:
2126 for f in files:
2121 if match(f):
2127 if match(f):
2122 output += self._gitcommand(cmd + ['--', f]) + '\n'
2128 output += self._gitcommand(cmd + ['--', f]) + '\n'
2123
2129
2124 if output.strip():
2130 if output.strip():
2125 ui.write(output)
2131 ui.write(output)
2126
2132
2127 @annotatesubrepoerror
2133 @annotatesubrepoerror
2128 def revert(self, substate, *pats, **opts):
2134 def revert(self, substate, *pats, **opts):
2129 self.ui.status(_('reverting subrepo %s\n') % substate[0])
2135 self.ui.status(_('reverting subrepo %s\n') % substate[0])
2130 if not opts.get(r'no_backup'):
2136 if not opts.get(r'no_backup'):
2131 status = self.status(None)
2137 status = self.status(None)
2132 names = status.modified
2138 names = status.modified
2133 for name in names:
2139 for name in names:
2134 bakname = scmutil.origpath(self.ui, self._subparent, name)
2140 bakname = scmutil.origpath(self.ui, self._subparent, name)
2135 self.ui.note(_('saving current version of %s as %s\n') %
2141 self.ui.note(_('saving current version of %s as %s\n') %
2136 (name, bakname))
2142 (name, bakname))
2137 self.wvfs.rename(name, bakname)
2143 self.wvfs.rename(name, bakname)
2138
2144
2139 if not opts.get(r'dry_run'):
2145 if not opts.get(r'dry_run'):
2140 self.get(substate, overwrite=True)
2146 self.get(substate, overwrite=True)
2141 return []
2147 return []
2142
2148
2143 def shortid(self, revid):
2149 def shortid(self, revid):
2144 return revid[:7]
2150 return revid[:7]
2145
2151
2146 types = {
2152 types = {
2147 'hg': hgsubrepo,
2153 'hg': hgsubrepo,
2148 'svn': svnsubrepo,
2154 'svn': svnsubrepo,
2149 'git': gitsubrepo,
2155 'git': gitsubrepo,
2150 }
2156 }
@@ -1,665 +1,682 b''
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 BUG: The remote subrepo should be cloned to the local pool, and then shared
296 from there.
296 from there.
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 sharing subrepo foo from http://localhost:$HGPORT/foo
310 cloning subrepo foo from http://localhost:$HGPORT/foo
311 abort: can only share local repositories (in subrepository "foo")
311 requesting all changes
312 [255]
312 adding changesets
313 adding manifests
314 adding file changes
315 added 4 changesets with 7 changes to 3 files
316 new changesets af048e97ade2:65903cebad86
317 cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar
318 requesting all changes
319 adding changesets
320 adding manifests
321 adding file changes
322 added 3 changesets with 3 changes to 1 files
323 new changesets 4904098473f9:31ecbdafd357
324 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
313
325
314 $ cat access.log
326 $ cat access.log
315 * "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
327 * "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
316 * "GET /?cmd=batch HTTP/1.1" 200 - * (glob)
328 * "GET /?cmd=batch HTTP/1.1" 200 - * (glob)
317 * "GET /?cmd=getbundle HTTP/1.1" 200 - * (glob)
329 * "GET /?cmd=getbundle HTTP/1.1" 200 - * (glob)
318 * "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
330 * "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
319 * "GET /foo?cmd=batch HTTP/1.1" 200 - * (glob)
331 * "GET /foo?cmd=batch HTTP/1.1" 200 - * (glob)
320 * "GET /foo?cmd=getbundle HTTP/1.1" 200 - * (glob)
332 * "GET /foo?cmd=getbundle HTTP/1.1" 200 - * (glob)
321 * "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
333 * "GET /foo/bar?cmd=capabilities HTTP/1.1" 200 - (glob)
322 * "GET /foo/bar?cmd=batch HTTP/1.1" 200 - * (glob)
334 * "GET /foo/bar?cmd=batch HTTP/1.1" 200 - * (glob)
323 * "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - * (glob)
335 * "GET /foo/bar?cmd=getbundle HTTP/1.1" 200 - * (glob)
324 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
336 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
325 $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)
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)
326 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
338 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
327 $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)
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)
328 $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)
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)
329 $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)
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)
330 $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)
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)
331 $LOCALIP - - [$LOGDATE$] "GET /foo?cmd=capabilities HTTP/1.1" 200 - (glob)
343 $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)
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)
346 $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)
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)
332
349
333 $ killdaemons.py
350 $ killdaemons.py
334 $ rm hg1.pid error.log access.log
351 $ rm hg1.pid error.log access.log
335 $ cd repo
352 $ cd repo
336 #endif
353 #endif
337
354
338 Enable progress extension for archive tests:
355 Enable progress extension for archive tests:
339
356
340 $ cp $HGRCPATH $HGRCPATH.no-progress
357 $ cp $HGRCPATH $HGRCPATH.no-progress
341 $ cat >> $HGRCPATH <<EOF
358 $ cat >> $HGRCPATH <<EOF
342 > [progress]
359 > [progress]
343 > disable=False
360 > disable=False
344 > assume-tty = 1
361 > assume-tty = 1
345 > delay = 0
362 > delay = 0
346 > # set changedelay really large so we don't see nested topics
363 > # set changedelay really large so we don't see nested topics
347 > changedelay = 30000
364 > changedelay = 30000
348 > format = topic bar number
365 > format = topic bar number
349 > refresh = 0
366 > refresh = 0
350 > width = 60
367 > width = 60
351 > EOF
368 > EOF
352
369
353 Test archiving to a directory tree (the doubled lines in the output
370 Test archiving to a directory tree (the doubled lines in the output
354 only show up in the test output, not in real usage):
371 only show up in the test output, not in real usage):
355
372
356 $ hg archive --subrepos ../archive
373 $ hg archive --subrepos ../archive
357 \r (no-eol) (esc)
374 \r (no-eol) (esc)
358 archiving [ ] 0/3\r (no-eol) (esc)
375 archiving [ ] 0/3\r (no-eol) (esc)
359 archiving [=============> ] 1/3\r (no-eol) (esc)
376 archiving [=============> ] 1/3\r (no-eol) (esc)
360 archiving [===========================> ] 2/3\r (no-eol) (esc)
377 archiving [===========================> ] 2/3\r (no-eol) (esc)
361 archiving [==========================================>] 3/3\r (no-eol) (esc)
378 archiving [==========================================>] 3/3\r (no-eol) (esc)
362 \r (no-eol) (esc)
379 \r (no-eol) (esc)
363 \r (no-eol) (esc)
380 \r (no-eol) (esc)
364 archiving (foo) [ ] 0/3\r (no-eol) (esc)
381 archiving (foo) [ ] 0/3\r (no-eol) (esc)
365 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
382 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
366 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
383 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
367 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
384 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
368 \r (no-eol) (esc)
385 \r (no-eol) (esc)
369 \r (no-eol) (esc)
386 \r (no-eol) (esc)
370 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
387 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
371 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
388 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
372 \r (no-eol) (esc)
389 \r (no-eol) (esc)
373 $ find ../archive | sort
390 $ find ../archive | sort
374 ../archive
391 ../archive
375 ../archive/.hg_archival.txt
392 ../archive/.hg_archival.txt
376 ../archive/.hgsub
393 ../archive/.hgsub
377 ../archive/.hgsubstate
394 ../archive/.hgsubstate
378 ../archive/foo
395 ../archive/foo
379 ../archive/foo/.hgsub
396 ../archive/foo/.hgsub
380 ../archive/foo/.hgsubstate
397 ../archive/foo/.hgsubstate
381 ../archive/foo/bar
398 ../archive/foo/bar
382 ../archive/foo/bar/z.txt
399 ../archive/foo/bar/z.txt
383 ../archive/foo/y.txt
400 ../archive/foo/y.txt
384 ../archive/x.txt
401 ../archive/x.txt
385
402
386 Test archiving to zip file (unzip output is unstable):
403 Test archiving to zip file (unzip output is unstable):
387
404
388 $ hg archive --subrepos --prefix '.' ../archive.zip
405 $ hg archive --subrepos --prefix '.' ../archive.zip
389 \r (no-eol) (esc)
406 \r (no-eol) (esc)
390 archiving [ ] 0/3\r (no-eol) (esc)
407 archiving [ ] 0/3\r (no-eol) (esc)
391 archiving [=============> ] 1/3\r (no-eol) (esc)
408 archiving [=============> ] 1/3\r (no-eol) (esc)
392 archiving [===========================> ] 2/3\r (no-eol) (esc)
409 archiving [===========================> ] 2/3\r (no-eol) (esc)
393 archiving [==========================================>] 3/3\r (no-eol) (esc)
410 archiving [==========================================>] 3/3\r (no-eol) (esc)
394 \r (no-eol) (esc)
411 \r (no-eol) (esc)
395 \r (no-eol) (esc)
412 \r (no-eol) (esc)
396 archiving (foo) [ ] 0/3\r (no-eol) (esc)
413 archiving (foo) [ ] 0/3\r (no-eol) (esc)
397 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
414 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
398 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
415 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
399 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
416 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
400 \r (no-eol) (esc)
417 \r (no-eol) (esc)
401 \r (no-eol) (esc)
418 \r (no-eol) (esc)
402 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
419 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
403 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
420 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
404 \r (no-eol) (esc)
421 \r (no-eol) (esc)
405
422
406 (unzip date formating is unstable, we do not care about it and glob it out)
423 (unzip date formating is unstable, we do not care about it and glob it out)
407
424
408 $ unzip -l ../archive.zip | grep -v -- ----- | egrep -v files$
425 $ unzip -l ../archive.zip | grep -v -- ----- | egrep -v files$
409 Archive: ../archive.zip
426 Archive: ../archive.zip
410 Length [ ]* Date [ ]* Time [ ]* Name (re)
427 Length [ ]* Date [ ]* Time [ ]* Name (re)
411 172 [0-9:\- ]* .hg_archival.txt (re)
428 172 [0-9:\- ]* .hg_archival.txt (re)
412 10 [0-9:\- ]* .hgsub (re)
429 10 [0-9:\- ]* .hgsub (re)
413 45 [0-9:\- ]* .hgsubstate (re)
430 45 [0-9:\- ]* .hgsubstate (re)
414 3 [0-9:\- ]* x.txt (re)
431 3 [0-9:\- ]* x.txt (re)
415 10 [0-9:\- ]* foo/.hgsub (re)
432 10 [0-9:\- ]* foo/.hgsub (re)
416 45 [0-9:\- ]* foo/.hgsubstate (re)
433 45 [0-9:\- ]* foo/.hgsubstate (re)
417 9 [0-9:\- ]* foo/y.txt (re)
434 9 [0-9:\- ]* foo/y.txt (re)
418 9 [0-9:\- ]* foo/bar/z.txt (re)
435 9 [0-9:\- ]* foo/bar/z.txt (re)
419
436
420 Test archiving a revision that references a subrepo that is not yet
437 Test archiving a revision that references a subrepo that is not yet
421 cloned:
438 cloned:
422
439
423 #if hardlink
440 #if hardlink
424 $ hg clone -U . ../empty
441 $ hg clone -U . ../empty
425 \r (no-eol) (esc)
442 \r (no-eol) (esc)
426 linking [ <=> ] 1\r (no-eol) (esc)
443 linking [ <=> ] 1\r (no-eol) (esc)
427 linking [ <=> ] 2\r (no-eol) (esc)
444 linking [ <=> ] 2\r (no-eol) (esc)
428 linking [ <=> ] 3\r (no-eol) (esc)
445 linking [ <=> ] 3\r (no-eol) (esc)
429 linking [ <=> ] 4\r (no-eol) (esc)
446 linking [ <=> ] 4\r (no-eol) (esc)
430 linking [ <=> ] 5\r (no-eol) (esc)
447 linking [ <=> ] 5\r (no-eol) (esc)
431 linking [ <=> ] 6\r (no-eol) (esc)
448 linking [ <=> ] 6\r (no-eol) (esc)
432 linking [ <=> ] 7\r (no-eol) (esc)
449 linking [ <=> ] 7\r (no-eol) (esc)
433 linking [ <=> ] 8\r (no-eol) (esc)
450 linking [ <=> ] 8\r (no-eol) (esc)
434 \r (no-eol) (esc)
451 \r (no-eol) (esc)
435 #else
452 #else
436 $ hg clone -U . ../empty
453 $ hg clone -U . ../empty
437 \r (no-eol) (esc)
454 \r (no-eol) (esc)
438 linking [ <=> ] 1 (no-eol)
455 linking [ <=> ] 1 (no-eol)
439 #endif
456 #endif
440
457
441 $ cd ../empty
458 $ cd ../empty
442 #if hardlink
459 #if hardlink
443 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
460 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
444 \r (no-eol) (esc)
461 \r (no-eol) (esc)
445 archiving [ ] 0/3\r (no-eol) (esc)
462 archiving [ ] 0/3\r (no-eol) (esc)
446 archiving [=============> ] 1/3\r (no-eol) (esc)
463 archiving [=============> ] 1/3\r (no-eol) (esc)
447 archiving [===========================> ] 2/3\r (no-eol) (esc)
464 archiving [===========================> ] 2/3\r (no-eol) (esc)
448 archiving [==========================================>] 3/3\r (no-eol) (esc)
465 archiving [==========================================>] 3/3\r (no-eol) (esc)
449 \r (no-eol) (esc)
466 \r (no-eol) (esc)
450 \r (no-eol) (esc)
467 \r (no-eol) (esc)
451 linking [ <=> ] 1\r (no-eol) (esc)
468 linking [ <=> ] 1\r (no-eol) (esc)
452 linking [ <=> ] 2\r (no-eol) (esc)
469 linking [ <=> ] 2\r (no-eol) (esc)
453 linking [ <=> ] 3\r (no-eol) (esc)
470 linking [ <=> ] 3\r (no-eol) (esc)
454 linking [ <=> ] 4\r (no-eol) (esc)
471 linking [ <=> ] 4\r (no-eol) (esc)
455 linking [ <=> ] 5\r (no-eol) (esc)
472 linking [ <=> ] 5\r (no-eol) (esc)
456 linking [ <=> ] 6\r (no-eol) (esc)
473 linking [ <=> ] 6\r (no-eol) (esc)
457 linking [ <=> ] 7\r (no-eol) (esc)
474 linking [ <=> ] 7\r (no-eol) (esc)
458 linking [ <=> ] 8\r (no-eol) (esc)
475 linking [ <=> ] 8\r (no-eol) (esc)
459 \r (no-eol) (esc)
476 \r (no-eol) (esc)
460 \r (no-eol) (esc)
477 \r (no-eol) (esc)
461 archiving (foo) [ ] 0/3\r (no-eol) (esc)
478 archiving (foo) [ ] 0/3\r (no-eol) (esc)
462 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
479 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
463 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
480 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
464 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
481 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
465 \r (no-eol) (esc)
482 \r (no-eol) (esc)
466 \r (no-eol) (esc)
483 \r (no-eol) (esc)
467 linking [ <=> ] 1\r (no-eol) (esc)
484 linking [ <=> ] 1\r (no-eol) (esc)
468 linking [ <=> ] 2\r (no-eol) (esc)
485 linking [ <=> ] 2\r (no-eol) (esc)
469 linking [ <=> ] 3\r (no-eol) (esc)
486 linking [ <=> ] 3\r (no-eol) (esc)
470 linking [ <=> ] 4\r (no-eol) (esc)
487 linking [ <=> ] 4\r (no-eol) (esc)
471 linking [ <=> ] 5\r (no-eol) (esc)
488 linking [ <=> ] 5\r (no-eol) (esc)
472 linking [ <=> ] 6\r (no-eol) (esc)
489 linking [ <=> ] 6\r (no-eol) (esc)
473 \r (no-eol) (esc)
490 \r (no-eol) (esc)
474 \r (no-eol) (esc)
491 \r (no-eol) (esc)
475 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
492 archiving (foo/bar) [ ] 0/1\r (no-eol) (esc)
476 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
493 archiving (foo/bar) [================================>] 1/1\r (no-eol) (esc)
477 \r (no-eol) (esc)
494 \r (no-eol) (esc)
478 cloning subrepo foo from $TESTTMP/repo/foo
495 cloning subrepo foo from $TESTTMP/repo/foo
479 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
496 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
480 #else
497 #else
481 Note there's a slight output glitch on non-hardlink systems: the last
498 Note there's a slight output glitch on non-hardlink systems: the last
482 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
499 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
483 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
500 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
484 \r (no-eol) (esc)
501 \r (no-eol) (esc)
485 archiving [ ] 0/3\r (no-eol) (esc)
502 archiving [ ] 0/3\r (no-eol) (esc)
486 archiving [=============> ] 1/3\r (no-eol) (esc)
503 archiving [=============> ] 1/3\r (no-eol) (esc)
487 archiving [===========================> ] 2/3\r (no-eol) (esc)
504 archiving [===========================> ] 2/3\r (no-eol) (esc)
488 archiving [==========================================>] 3/3\r (no-eol) (esc)
505 archiving [==========================================>] 3/3\r (no-eol) (esc)
489 \r (no-eol) (esc)
506 \r (no-eol) (esc)
490 \r (no-eol) (esc)
507 \r (no-eol) (esc)
491 linking [ <=> ] 1\r (no-eol) (esc)
508 linking [ <=> ] 1\r (no-eol) (esc)
492 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
509 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
493 #endif
510 #endif
494
511
495 Archive + subrepos uses '/' for all component separators
512 Archive + subrepos uses '/' for all component separators
496
513
497 $ tar -tzf ../archive.tar.gz | sort
514 $ tar -tzf ../archive.tar.gz | sort
498 .hg_archival.txt
515 .hg_archival.txt
499 .hgsub
516 .hgsub
500 .hgsubstate
517 .hgsubstate
501 foo/.hgsub
518 foo/.hgsub
502 foo/.hgsubstate
519 foo/.hgsubstate
503 foo/bar/z.txt
520 foo/bar/z.txt
504 foo/y.txt
521 foo/y.txt
505 x.txt
522 x.txt
506
523
507 The newly cloned subrepos contain no working copy:
524 The newly cloned subrepos contain no working copy:
508
525
509 $ hg -R foo summary
526 $ hg -R foo summary
510 parent: -1:000000000000 (no revision checked out)
527 parent: -1:000000000000 (no revision checked out)
511 branch: default
528 branch: default
512 commit: (clean)
529 commit: (clean)
513 update: 4 new changesets (update)
530 update: 4 new changesets (update)
514
531
515 Sharing a local repo without the locally referenced subrepo (i.e. it was never
532 Sharing a local repo without the locally referenced subrepo (i.e. it was never
516 updated from null), fails the same as a clone operation.
533 updated from null), fails the same as a clone operation.
517
534
518 $ hg --config progress.disable=True clone -U ../empty ../empty2
535 $ hg --config progress.disable=True clone -U ../empty ../empty2
519
536
520 $ hg --config extensions.share= --config progress.disable=True \
537 $ hg --config extensions.share= --config progress.disable=True \
521 > share ../empty2 ../empty_share
538 > share ../empty2 ../empty_share
522 updating working directory
539 updating working directory
523 abort: repository $TESTTMP/empty2/foo not found!
540 abort: repository $TESTTMP/empty2/foo not found!
524 [255]
541 [255]
525
542
526 $ hg --config progress.disable=True clone ../empty2 ../empty_clone
543 $ hg --config progress.disable=True clone ../empty2 ../empty_clone
527 updating to branch default
544 updating to branch default
528 abort: repository $TESTTMP/empty2/foo not found!
545 abort: repository $TESTTMP/empty2/foo not found!
529 [255]
546 [255]
530
547
531 Disable progress extension and cleanup:
548 Disable progress extension and cleanup:
532
549
533 $ mv $HGRCPATH.no-progress $HGRCPATH
550 $ mv $HGRCPATH.no-progress $HGRCPATH
534
551
535 Test archiving when there is a directory in the way for a subrepo
552 Test archiving when there is a directory in the way for a subrepo
536 created by archive:
553 created by archive:
537
554
538 $ hg clone -U . ../almost-empty
555 $ hg clone -U . ../almost-empty
539 $ cd ../almost-empty
556 $ cd ../almost-empty
540 $ mkdir foo
557 $ mkdir foo
541 $ echo f > foo/f
558 $ echo f > foo/f
542 $ hg archive --subrepos -r tip archive
559 $ hg archive --subrepos -r tip archive
543 cloning subrepo foo from $TESTTMP/empty/foo
560 cloning subrepo foo from $TESTTMP/empty/foo
544 abort: destination '$TESTTMP/almost-empty/foo' is not empty (in subrepository "foo")
561 abort: destination '$TESTTMP/almost-empty/foo' is not empty (in subrepository "foo")
545 [255]
562 [255]
546
563
547 Clone and test outgoing:
564 Clone and test outgoing:
548
565
549 $ cd ..
566 $ cd ..
550 $ hg clone repo repo2
567 $ hg clone repo repo2
551 updating to branch default
568 updating to branch default
552 cloning subrepo foo from $TESTTMP/repo/foo
569 cloning subrepo foo from $TESTTMP/repo/foo
553 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
570 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
554 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
555 $ cd repo2
572 $ cd repo2
556 $ hg outgoing -S
573 $ hg outgoing -S
557 comparing with $TESTTMP/repo
574 comparing with $TESTTMP/repo
558 searching for changes
575 searching for changes
559 no changes found
576 no changes found
560 comparing with $TESTTMP/repo/foo
577 comparing with $TESTTMP/repo/foo
561 searching for changes
578 searching for changes
562 no changes found
579 no changes found
563 comparing with $TESTTMP/repo/foo/bar
580 comparing with $TESTTMP/repo/foo/bar
564 searching for changes
581 searching for changes
565 no changes found
582 no changes found
566 [1]
583 [1]
567
584
568 Make nested change:
585 Make nested change:
569
586
570 $ echo y4 >> foo/y.txt
587 $ echo y4 >> foo/y.txt
571 $ hg diff --nodates -S
588 $ hg diff --nodates -S
572 diff -r 65903cebad86 foo/y.txt
589 diff -r 65903cebad86 foo/y.txt
573 --- a/foo/y.txt
590 --- a/foo/y.txt
574 +++ b/foo/y.txt
591 +++ b/foo/y.txt
575 @@ -1,3 +1,4 @@
592 @@ -1,3 +1,4 @@
576 y1
593 y1
577 y2
594 y2
578 y3
595 y3
579 +y4
596 +y4
580 $ hg commit --subrepos -m 3-4-2
597 $ hg commit --subrepos -m 3-4-2
581 committing subrepository foo
598 committing subrepository foo
582 $ hg outgoing -S
599 $ hg outgoing -S
583 comparing with $TESTTMP/repo
600 comparing with $TESTTMP/repo
584 searching for changes
601 searching for changes
585 changeset: 3:2655b8ecc4ee
602 changeset: 3:2655b8ecc4ee
586 tag: tip
603 tag: tip
587 user: test
604 user: test
588 date: Thu Jan 01 00:00:00 1970 +0000
605 date: Thu Jan 01 00:00:00 1970 +0000
589 summary: 3-4-2
606 summary: 3-4-2
590
607
591 comparing with $TESTTMP/repo/foo
608 comparing with $TESTTMP/repo/foo
592 searching for changes
609 searching for changes
593 changeset: 4:e96193d6cb36
610 changeset: 4:e96193d6cb36
594 tag: tip
611 tag: tip
595 user: test
612 user: test
596 date: Thu Jan 01 00:00:00 1970 +0000
613 date: Thu Jan 01 00:00:00 1970 +0000
597 summary: 3-4-2
614 summary: 3-4-2
598
615
599 comparing with $TESTTMP/repo/foo/bar
616 comparing with $TESTTMP/repo/foo/bar
600 searching for changes
617 searching for changes
601 no changes found
618 no changes found
602
619
603
620
604 Switch to original repo and setup default path:
621 Switch to original repo and setup default path:
605
622
606 $ cd ../repo
623 $ cd ../repo
607 $ echo '[paths]' >> .hg/hgrc
624 $ echo '[paths]' >> .hg/hgrc
608 $ echo 'default = ../repo2' >> .hg/hgrc
625 $ echo 'default = ../repo2' >> .hg/hgrc
609
626
610 Test incoming:
627 Test incoming:
611
628
612 $ hg incoming -S
629 $ hg incoming -S
613 comparing with $TESTTMP/repo2
630 comparing with $TESTTMP/repo2
614 searching for changes
631 searching for changes
615 changeset: 3:2655b8ecc4ee
632 changeset: 3:2655b8ecc4ee
616 tag: tip
633 tag: tip
617 user: test
634 user: test
618 date: Thu Jan 01 00:00:00 1970 +0000
635 date: Thu Jan 01 00:00:00 1970 +0000
619 summary: 3-4-2
636 summary: 3-4-2
620
637
621 comparing with $TESTTMP/repo2/foo
638 comparing with $TESTTMP/repo2/foo
622 searching for changes
639 searching for changes
623 changeset: 4:e96193d6cb36
640 changeset: 4:e96193d6cb36
624 tag: tip
641 tag: tip
625 user: test
642 user: test
626 date: Thu Jan 01 00:00:00 1970 +0000
643 date: Thu Jan 01 00:00:00 1970 +0000
627 summary: 3-4-2
644 summary: 3-4-2
628
645
629 comparing with $TESTTMP/repo2/foo/bar
646 comparing with $TESTTMP/repo2/foo/bar
630 searching for changes
647 searching for changes
631 no changes found
648 no changes found
632
649
633 $ hg incoming -S --bundle incoming.hg
650 $ hg incoming -S --bundle incoming.hg
634 abort: cannot combine --bundle and --subrepos
651 abort: cannot combine --bundle and --subrepos
635 [255]
652 [255]
636
653
637 Test missing subrepo:
654 Test missing subrepo:
638
655
639 $ rm -r foo
656 $ rm -r foo
640 $ hg status -S
657 $ hg status -S
641 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
658 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
642
659
643 Issue2619: IndexError: list index out of range on hg add with subrepos
660 Issue2619: IndexError: list index out of range on hg add with subrepos
644 The subrepo must sorts after the explicit filename.
661 The subrepo must sorts after the explicit filename.
645
662
646 $ cd ..
663 $ cd ..
647 $ hg init test
664 $ hg init test
648 $ cd test
665 $ cd test
649 $ hg init x
666 $ hg init x
650 $ echo abc > abc.txt
667 $ echo abc > abc.txt
651 $ hg ci -Am "abc"
668 $ hg ci -Am "abc"
652 adding abc.txt
669 adding abc.txt
653 $ echo "x = x" >> .hgsub
670 $ echo "x = x" >> .hgsub
654 $ hg add .hgsub
671 $ hg add .hgsub
655 $ touch a x/a
672 $ touch a x/a
656 $ hg add a x/a
673 $ hg add a x/a
657
674
658 $ hg ci -Sm "added x"
675 $ hg ci -Sm "added x"
659 committing subrepository x
676 committing subrepository x
660 $ echo abc > x/a
677 $ echo abc > x/a
661 $ hg revert --rev '.^' "set:subrepo('glob:x*')"
678 $ hg revert --rev '.^' "set:subrepo('glob:x*')"
662 abort: subrepository 'x' does not exist in 25ac2c9b3180!
679 abort: subrepository 'x' does not exist in 25ac2c9b3180!
663 [255]
680 [255]
664
681
665 $ cd ..
682 $ cd ..
@@ -1,162 +1,187 b''
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 BUG: Remote subrepos cannot be shared, and pooled repos don't have their
87 Clone pooling works for local clones with a remote subrepo reference.
88 relative subrepos in the relative location stated in .hgsub.
88
89 BUG: subrepos should be shared out of the pool.
89
90
90 $ hg --config extensions.share= --config share.pool=$TESTTMP/pool \
91 $ hg --config extensions.share= --config share.pool=$TESTTMP/pool \
91 > clone absolute_subrepo cloned_from_abs
92 > clone absolute_subrepo cloned_from_abs
92 (sharing from new pooled repository 8d6a2f1e993b34b6557de0042cfe825ae12a8dae)
93 (sharing from new pooled repository 8d6a2f1e993b34b6557de0042cfe825ae12a8dae)
93 requesting all changes
94 requesting all changes
94 adding changesets
95 adding changesets
95 adding manifests
96 adding manifests
96 adding file changes
97 adding file changes
97 added 2 changesets with 3 changes to 3 files
98 added 2 changesets with 3 changes to 3 files
98 new changesets 8d6a2f1e993b:c6d0e6ebd1c9
99 new changesets 8d6a2f1e993b:c6d0e6ebd1c9
99 searching for changes
100 searching for changes
100 no changes found
101 no changes found
101 updating working directory
102 updating working directory
102 sharing subrepo sub from http://localhost:$HGPORT/sub
103 cloning subrepo sub from http://localhost:$HGPORT/sub
103 abort: can only share local repositories (in subrepository "sub")
104 requesting all changes
104 [255]
105 adding changesets
106 adding manifests
107 adding file changes
108 added 1 changesets with 1 changes to 1 files
109 new changesets 863c1745b441
110 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
111
112 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
114 (potentially leaving the shared parent with dangling cset references).
105
115
106 $ hg --config extensions.share= share absolute_subrepo shared_from_abs
116 $ hg --config extensions.share= share absolute_subrepo shared_from_abs
107 updating working directory
117 updating working directory
108 sharing subrepo sub from http://localhost:$HGPORT/sub
118 cloning subrepo sub from http://localhost:$HGPORT/sub
109 abort: can only share local repositories (in subrepository "sub")
119 requesting all changes
110 [255]
120 adding changesets
121 adding manifests
122 adding file changes
123 added 1 changesets with 1 changes to 1 files
124 new changesets 863c1745b441
125 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
111
126
112 $ hg --config extensions.share= share -U absolute_subrepo shared_from_abs2
127 $ hg --config extensions.share= share -U absolute_subrepo shared_from_abs2
113 $ hg -R shared_from_abs2 update -r tip
128 $ hg -R shared_from_abs2 update -r tip
114 sharing subrepo sub from http://localhost:$HGPORT/sub
129 cloning subrepo sub from http://localhost:$HGPORT/sub
115 abort: can only share local repositories (in subrepository "sub")
130 requesting all changes
116 [255]
131 adding changesets
132 adding manifests
133 adding file changes
134 added 1 changesets with 1 changes to 1 files
135 new changesets 863c1745b441
136 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117
137
118 BUG: A repo without its subrepo available locally should be sharable if the
138 A parent repo without its subrepo available locally can be shared if the
119 subrepo is referenced by absolute path.
139 subrepo is referenced by absolute path.
120
140
121 $ hg clone -U absolute_subrepo cloned_null_from_abs
141 $ hg clone -U absolute_subrepo cloned_null_from_abs
122 $ hg --config extensions.share= share cloned_null_from_abs shared_from_null_abs
142 $ hg --config extensions.share= share cloned_null_from_abs shared_from_null_abs
123 updating working directory
143 updating working directory
124 sharing subrepo sub from http://localhost:$HGPORT/sub
144 cloning subrepo sub from http://localhost:$HGPORT/sub
125 abort: can only share local repositories (in subrepository "sub")
145 requesting all changes
126 [255]
146 adding changesets
147 adding manifests
148 adding file changes
149 added 1 changesets with 1 changes to 1 files
150 new changesets 863c1745b441
151 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
127
152
128 $ killdaemons.py
153 $ killdaemons.py
129
154
130 subrepo paths with ssh urls
155 subrepo paths with ssh urls
131
156
132 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/cloned sshclone
157 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/cloned sshclone
133 requesting all changes
158 requesting all changes
134 adding changesets
159 adding changesets
135 adding manifests
160 adding manifests
136 adding file changes
161 adding file changes
137 added 1 changesets with 3 changes to 3 files
162 added 1 changesets with 3 changes to 3 files
138 new changesets fdfeeb3e979e
163 new changesets fdfeeb3e979e
139 updating to branch default
164 updating to branch default
140 cloning subrepo sub from ssh://user@dummy/sub
165 cloning subrepo sub from ssh://user@dummy/sub
141 requesting all changes
166 requesting all changes
142 adding changesets
167 adding changesets
143 adding manifests
168 adding manifests
144 adding file changes
169 adding file changes
145 added 1 changesets with 1 changes to 1 files
170 added 1 changesets with 1 changes to 1 files
146 new changesets 863c1745b441
171 new changesets 863c1745b441
147 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
172 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
148
173
149 $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
174 $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
150 pushing to ssh://user@dummy/$TESTTMP/cloned
175 pushing to ssh://user@dummy/$TESTTMP/cloned
151 pushing subrepo sub to ssh://user@dummy/$TESTTMP/sub
176 pushing subrepo sub to ssh://user@dummy/$TESTTMP/sub
152 searching for changes
177 searching for changes
153 no changes found
178 no changes found
154 searching for changes
179 searching for changes
155 no changes found
180 no changes found
156 [1]
181 [1]
157
182
158 $ cat dummylog
183 $ cat dummylog
159 Got arguments 1:user@dummy 2:hg -R cloned serve --stdio
184 Got arguments 1:user@dummy 2:hg -R cloned serve --stdio
160 Got arguments 1:user@dummy 2:hg -R sub serve --stdio
185 Got arguments 1:user@dummy 2:hg -R sub serve --stdio
161 Got arguments 1:user@dummy 2:hg -R $TESTTMP/cloned serve --stdio
186 Got arguments 1:user@dummy 2:hg -R $TESTTMP/cloned serve --stdio
162 Got arguments 1:user@dummy 2:hg -R $TESTTMP/sub serve --stdio
187 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