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