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