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