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