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