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