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