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