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