##// END OF EJS Templates
subrepo: fix status check on SVN subrepos (issue2445)
Matt Mackall -
r12798:3d6ba8c2 stable
parent child Browse files
Show More
@@ -1,580 +1,580 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
9 from i18n import _
9 from i18n import _
10 import config, util, node, error, cmdutil
10 import config, util, node, error, cmdutil
11 hg = None
11 hg = None
12
12
13 nullstate = ('', '', 'empty')
13 nullstate = ('', '', 'empty')
14
14
15 def state(ctx, ui):
15 def state(ctx, ui):
16 """return a state dict, mapping subrepo paths configured in .hgsub
16 """return a state dict, mapping subrepo paths configured in .hgsub
17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 (key in types dict))
18 (key in types dict))
19 """
19 """
20 p = config.config()
20 p = config.config()
21 def read(f, sections=None, remap=None):
21 def read(f, sections=None, remap=None):
22 if f in ctx:
22 if f in ctx:
23 p.parse(f, ctx[f].data(), sections, remap, read)
23 p.parse(f, ctx[f].data(), sections, remap, read)
24 else:
24 else:
25 raise util.Abort(_("subrepo spec file %s not found") % f)
25 raise util.Abort(_("subrepo spec file %s not found") % f)
26
26
27 if '.hgsub' in ctx:
27 if '.hgsub' in ctx:
28 read('.hgsub')
28 read('.hgsub')
29
29
30 for path, src in ui.configitems('subpaths'):
30 for path, src in ui.configitems('subpaths'):
31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
32
32
33 rev = {}
33 rev = {}
34 if '.hgsubstate' in ctx:
34 if '.hgsubstate' in ctx:
35 try:
35 try:
36 for l in ctx['.hgsubstate'].data().splitlines():
36 for l in ctx['.hgsubstate'].data().splitlines():
37 revision, path = l.split(" ", 1)
37 revision, path = l.split(" ", 1)
38 rev[path] = revision
38 rev[path] = revision
39 except IOError, err:
39 except IOError, err:
40 if err.errno != errno.ENOENT:
40 if err.errno != errno.ENOENT:
41 raise
41 raise
42
42
43 state = {}
43 state = {}
44 for path, src in p[''].items():
44 for path, src in p[''].items():
45 kind = 'hg'
45 kind = 'hg'
46 if src.startswith('['):
46 if src.startswith('['):
47 if ']' not in src:
47 if ']' not in src:
48 raise util.Abort(_('missing ] in subrepo source'))
48 raise util.Abort(_('missing ] in subrepo source'))
49 kind, src = src.split(']', 1)
49 kind, src = src.split(']', 1)
50 kind = kind[1:]
50 kind = kind[1:]
51
51
52 for pattern, repl in p.items('subpaths'):
52 for pattern, repl in p.items('subpaths'):
53 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
53 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
54 # does a string decode.
54 # does a string decode.
55 repl = repl.encode('string-escape')
55 repl = repl.encode('string-escape')
56 # However, we still want to allow back references to go
56 # However, we still want to allow back references to go
57 # through unharmed, so we turn r'\\1' into r'\1'. Again,
57 # through unharmed, so we turn r'\\1' into r'\1'. Again,
58 # extra escapes are needed because re.sub string decodes.
58 # extra escapes are needed because re.sub string decodes.
59 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
59 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
60 try:
60 try:
61 src = re.sub(pattern, repl, src, 1)
61 src = re.sub(pattern, repl, src, 1)
62 except re.error, e:
62 except re.error, e:
63 raise util.Abort(_("bad subrepository pattern in %s: %s")
63 raise util.Abort(_("bad subrepository pattern in %s: %s")
64 % (p.source('subpaths', pattern), e))
64 % (p.source('subpaths', pattern), e))
65
65
66 state[path] = (src.strip(), rev.get(path, ''), kind)
66 state[path] = (src.strip(), rev.get(path, ''), kind)
67
67
68 return state
68 return state
69
69
70 def writestate(repo, state):
70 def writestate(repo, state):
71 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
71 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
72 repo.wwrite('.hgsubstate',
72 repo.wwrite('.hgsubstate',
73 ''.join(['%s %s\n' % (state[s][1], s)
73 ''.join(['%s %s\n' % (state[s][1], s)
74 for s in sorted(state)]), '')
74 for s in sorted(state)]), '')
75
75
76 def submerge(repo, wctx, mctx, actx):
76 def submerge(repo, wctx, mctx, actx):
77 """delegated from merge.applyupdates: merging of .hgsubstate file
77 """delegated from merge.applyupdates: merging of .hgsubstate file
78 in working context, merging context and ancestor context"""
78 in working context, merging context and ancestor context"""
79 if mctx == actx: # backwards?
79 if mctx == actx: # backwards?
80 actx = wctx.p1()
80 actx = wctx.p1()
81 s1 = wctx.substate
81 s1 = wctx.substate
82 s2 = mctx.substate
82 s2 = mctx.substate
83 sa = actx.substate
83 sa = actx.substate
84 sm = {}
84 sm = {}
85
85
86 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
86 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
87
87
88 def debug(s, msg, r=""):
88 def debug(s, msg, r=""):
89 if r:
89 if r:
90 r = "%s:%s:%s" % r
90 r = "%s:%s:%s" % r
91 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
91 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
92
92
93 for s, l in s1.items():
93 for s, l in s1.items():
94 a = sa.get(s, nullstate)
94 a = sa.get(s, nullstate)
95 ld = l # local state with possible dirty flag for compares
95 ld = l # local state with possible dirty flag for compares
96 if wctx.sub(s).dirty():
96 if wctx.sub(s).dirty():
97 ld = (l[0], l[1] + "+")
97 ld = (l[0], l[1] + "+")
98 if wctx == actx: # overwrite
98 if wctx == actx: # overwrite
99 a = ld
99 a = ld
100
100
101 if s in s2:
101 if s in s2:
102 r = s2[s]
102 r = s2[s]
103 if ld == r or r == a: # no change or local is newer
103 if ld == r or r == a: # no change or local is newer
104 sm[s] = l
104 sm[s] = l
105 continue
105 continue
106 elif ld == a: # other side changed
106 elif ld == a: # other side changed
107 debug(s, "other changed, get", r)
107 debug(s, "other changed, get", r)
108 wctx.sub(s).get(r)
108 wctx.sub(s).get(r)
109 sm[s] = r
109 sm[s] = r
110 elif ld[0] != r[0]: # sources differ
110 elif ld[0] != r[0]: # sources differ
111 if repo.ui.promptchoice(
111 if repo.ui.promptchoice(
112 _(' subrepository sources for %s differ\n'
112 _(' subrepository sources for %s differ\n'
113 'use (l)ocal source (%s) or (r)emote source (%s)?')
113 'use (l)ocal source (%s) or (r)emote source (%s)?')
114 % (s, l[0], r[0]),
114 % (s, l[0], r[0]),
115 (_('&Local'), _('&Remote')), 0):
115 (_('&Local'), _('&Remote')), 0):
116 debug(s, "prompt changed, get", r)
116 debug(s, "prompt changed, get", r)
117 wctx.sub(s).get(r)
117 wctx.sub(s).get(r)
118 sm[s] = r
118 sm[s] = r
119 elif ld[1] == a[1]: # local side is unchanged
119 elif ld[1] == a[1]: # local side is unchanged
120 debug(s, "other side changed, get", r)
120 debug(s, "other side changed, get", r)
121 wctx.sub(s).get(r)
121 wctx.sub(s).get(r)
122 sm[s] = r
122 sm[s] = r
123 else:
123 else:
124 debug(s, "both sides changed, merge with", r)
124 debug(s, "both sides changed, merge with", r)
125 wctx.sub(s).merge(r)
125 wctx.sub(s).merge(r)
126 sm[s] = l
126 sm[s] = l
127 elif ld == a: # remote removed, local unchanged
127 elif ld == a: # remote removed, local unchanged
128 debug(s, "remote removed, remove")
128 debug(s, "remote removed, remove")
129 wctx.sub(s).remove()
129 wctx.sub(s).remove()
130 else:
130 else:
131 if repo.ui.promptchoice(
131 if repo.ui.promptchoice(
132 _(' local changed subrepository %s which remote removed\n'
132 _(' local changed subrepository %s which remote removed\n'
133 'use (c)hanged version or (d)elete?') % s,
133 'use (c)hanged version or (d)elete?') % s,
134 (_('&Changed'), _('&Delete')), 0):
134 (_('&Changed'), _('&Delete')), 0):
135 debug(s, "prompt remove")
135 debug(s, "prompt remove")
136 wctx.sub(s).remove()
136 wctx.sub(s).remove()
137
137
138 for s, r in s2.items():
138 for s, r in s2.items():
139 if s in s1:
139 if s in s1:
140 continue
140 continue
141 elif s not in sa:
141 elif s not in sa:
142 debug(s, "remote added, get", r)
142 debug(s, "remote added, get", r)
143 mctx.sub(s).get(r)
143 mctx.sub(s).get(r)
144 sm[s] = r
144 sm[s] = r
145 elif r != sa[s]:
145 elif r != sa[s]:
146 if repo.ui.promptchoice(
146 if repo.ui.promptchoice(
147 _(' remote changed subrepository %s which local removed\n'
147 _(' remote changed subrepository %s which local removed\n'
148 'use (c)hanged version or (d)elete?') % s,
148 'use (c)hanged version or (d)elete?') % s,
149 (_('&Changed'), _('&Delete')), 0) == 0:
149 (_('&Changed'), _('&Delete')), 0) == 0:
150 debug(s, "prompt recreate", r)
150 debug(s, "prompt recreate", r)
151 wctx.sub(s).get(r)
151 wctx.sub(s).get(r)
152 sm[s] = r
152 sm[s] = r
153
153
154 # record merged .hgsubstate
154 # record merged .hgsubstate
155 writestate(repo, sm)
155 writestate(repo, sm)
156
156
157 def reporelpath(repo):
157 def reporelpath(repo):
158 """return path to this (sub)repo as seen from outermost repo"""
158 """return path to this (sub)repo as seen from outermost repo"""
159 parent = repo
159 parent = repo
160 while hasattr(parent, '_subparent'):
160 while hasattr(parent, '_subparent'):
161 parent = parent._subparent
161 parent = parent._subparent
162 return repo.root[len(parent.root)+1:]
162 return repo.root[len(parent.root)+1:]
163
163
164 def subrelpath(sub):
164 def subrelpath(sub):
165 """return path to this subrepo as seen from outermost repo"""
165 """return path to this subrepo as seen from outermost repo"""
166 if not hasattr(sub, '_repo'):
166 if not hasattr(sub, '_repo'):
167 return sub._path
167 return sub._path
168 return reporelpath(sub._repo)
168 return reporelpath(sub._repo)
169
169
170 def _abssource(repo, push=False, abort=True):
170 def _abssource(repo, push=False, abort=True):
171 """return pull/push path of repo - either based on parent repo .hgsub info
171 """return pull/push path of repo - either based on parent repo .hgsub info
172 or on the top repo config. Abort or return None if no source found."""
172 or on the top repo config. Abort or return None if no source found."""
173 if hasattr(repo, '_subparent'):
173 if hasattr(repo, '_subparent'):
174 source = repo._subsource
174 source = repo._subsource
175 if source.startswith('/') or '://' in source:
175 if source.startswith('/') or '://' in source:
176 return source
176 return source
177 parent = _abssource(repo._subparent, push, abort=False)
177 parent = _abssource(repo._subparent, push, abort=False)
178 if parent:
178 if parent:
179 if '://' in parent:
179 if '://' in parent:
180 if parent[-1] == '/':
180 if parent[-1] == '/':
181 parent = parent[:-1]
181 parent = parent[:-1]
182 r = urlparse.urlparse(parent + '/' + source)
182 r = urlparse.urlparse(parent + '/' + source)
183 r = urlparse.urlunparse((r[0], r[1],
183 r = urlparse.urlunparse((r[0], r[1],
184 posixpath.normpath(r[2]),
184 posixpath.normpath(r[2]),
185 r[3], r[4], r[5]))
185 r[3], r[4], r[5]))
186 return r
186 return r
187 else: # plain file system path
187 else: # plain file system path
188 return posixpath.normpath(os.path.join(parent, repo._subsource))
188 return posixpath.normpath(os.path.join(parent, repo._subsource))
189 else: # recursion reached top repo
189 else: # recursion reached top repo
190 if push and repo.ui.config('paths', 'default-push'):
190 if push and repo.ui.config('paths', 'default-push'):
191 return repo.ui.config('paths', 'default-push')
191 return repo.ui.config('paths', 'default-push')
192 if repo.ui.config('paths', 'default'):
192 if repo.ui.config('paths', 'default'):
193 return repo.ui.config('paths', 'default')
193 return repo.ui.config('paths', 'default')
194 if abort:
194 if abort:
195 raise util.Abort(_("default path for subrepository %s not found") %
195 raise util.Abort(_("default path for subrepository %s not found") %
196 reporelpath(repo))
196 reporelpath(repo))
197
197
198 def itersubrepos(ctx1, ctx2):
198 def itersubrepos(ctx1, ctx2):
199 """find subrepos in ctx1 or ctx2"""
199 """find subrepos in ctx1 or ctx2"""
200 # Create a (subpath, ctx) mapping where we prefer subpaths from
200 # Create a (subpath, ctx) mapping where we prefer subpaths from
201 # ctx1. The subpaths from ctx2 are important when the .hgsub file
201 # ctx1. The subpaths from ctx2 are important when the .hgsub file
202 # has been modified (in ctx2) but not yet committed (in ctx1).
202 # has been modified (in ctx2) but not yet committed (in ctx1).
203 subpaths = dict.fromkeys(ctx2.substate, ctx2)
203 subpaths = dict.fromkeys(ctx2.substate, ctx2)
204 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
204 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
205 for subpath, ctx in sorted(subpaths.iteritems()):
205 for subpath, ctx in sorted(subpaths.iteritems()):
206 yield subpath, ctx.sub(subpath)
206 yield subpath, ctx.sub(subpath)
207
207
208 def subrepo(ctx, path):
208 def subrepo(ctx, path):
209 """return instance of the right subrepo class for subrepo in path"""
209 """return instance of the right subrepo class for subrepo in path"""
210 # subrepo inherently violates our import layering rules
210 # subrepo inherently violates our import layering rules
211 # because it wants to make repo objects from deep inside the stack
211 # because it wants to make repo objects from deep inside the stack
212 # so we manually delay the circular imports to not break
212 # so we manually delay the circular imports to not break
213 # scripts that don't use our demand-loading
213 # scripts that don't use our demand-loading
214 global hg
214 global hg
215 import hg as h
215 import hg as h
216 hg = h
216 hg = h
217
217
218 util.path_auditor(ctx._repo.root)(path)
218 util.path_auditor(ctx._repo.root)(path)
219 state = ctx.substate.get(path, nullstate)
219 state = ctx.substate.get(path, nullstate)
220 if state[2] not in types:
220 if state[2] not in types:
221 raise util.Abort(_('unknown subrepo type %s') % state[2])
221 raise util.Abort(_('unknown subrepo type %s') % state[2])
222 return types[state[2]](ctx, path, state[:2])
222 return types[state[2]](ctx, path, state[:2])
223
223
224 # subrepo classes need to implement the following abstract class:
224 # subrepo classes need to implement the following abstract class:
225
225
226 class abstractsubrepo(object):
226 class abstractsubrepo(object):
227
227
228 def dirty(self):
228 def dirty(self):
229 """returns true if the dirstate of the subrepo does not match
229 """returns true if the dirstate of the subrepo does not match
230 current stored state
230 current stored state
231 """
231 """
232 raise NotImplementedError
232 raise NotImplementedError
233
233
234 def checknested(self, path):
234 def checknested(self, path):
235 """check if path is a subrepository within this repository"""
235 """check if path is a subrepository within this repository"""
236 return False
236 return False
237
237
238 def commit(self, text, user, date):
238 def commit(self, text, user, date):
239 """commit the current changes to the subrepo with the given
239 """commit the current changes to the subrepo with the given
240 log message. Use given user and date if possible. Return the
240 log message. Use given user and date if possible. Return the
241 new state of the subrepo.
241 new state of the subrepo.
242 """
242 """
243 raise NotImplementedError
243 raise NotImplementedError
244
244
245 def remove(self):
245 def remove(self):
246 """remove the subrepo
246 """remove the subrepo
247
247
248 (should verify the dirstate is not dirty first)
248 (should verify the dirstate is not dirty first)
249 """
249 """
250 raise NotImplementedError
250 raise NotImplementedError
251
251
252 def get(self, state):
252 def get(self, state):
253 """run whatever commands are needed to put the subrepo into
253 """run whatever commands are needed to put the subrepo into
254 this state
254 this state
255 """
255 """
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258 def merge(self, state):
258 def merge(self, state):
259 """merge currently-saved state with the new state."""
259 """merge currently-saved state with the new state."""
260 raise NotImplementedError
260 raise NotImplementedError
261
261
262 def push(self, force):
262 def push(self, force):
263 """perform whatever action is analogous to 'hg push'
263 """perform whatever action is analogous to 'hg push'
264
264
265 This may be a no-op on some systems.
265 This may be a no-op on some systems.
266 """
266 """
267 raise NotImplementedError
267 raise NotImplementedError
268
268
269 def add(self, ui, match, dryrun, prefix):
269 def add(self, ui, match, dryrun, prefix):
270 return []
270 return []
271
271
272 def status(self, rev2, **opts):
272 def status(self, rev2, **opts):
273 return [], [], [], [], [], [], []
273 return [], [], [], [], [], [], []
274
274
275 def diff(self, diffopts, node2, match, prefix, **opts):
275 def diff(self, diffopts, node2, match, prefix, **opts):
276 pass
276 pass
277
277
278 def outgoing(self, ui, dest, opts):
278 def outgoing(self, ui, dest, opts):
279 return 1
279 return 1
280
280
281 def incoming(self, ui, source, opts):
281 def incoming(self, ui, source, opts):
282 return 1
282 return 1
283
283
284 def files(self):
284 def files(self):
285 """return filename iterator"""
285 """return filename iterator"""
286 raise NotImplementedError
286 raise NotImplementedError
287
287
288 def filedata(self, name):
288 def filedata(self, name):
289 """return file data"""
289 """return file data"""
290 raise NotImplementedError
290 raise NotImplementedError
291
291
292 def fileflags(self, name):
292 def fileflags(self, name):
293 """return file flags"""
293 """return file flags"""
294 return ''
294 return ''
295
295
296 def archive(self, archiver, prefix):
296 def archive(self, archiver, prefix):
297 for name in self.files():
297 for name in self.files():
298 flags = self.fileflags(name)
298 flags = self.fileflags(name)
299 mode = 'x' in flags and 0755 or 0644
299 mode = 'x' in flags and 0755 or 0644
300 symlink = 'l' in flags
300 symlink = 'l' in flags
301 archiver.addfile(os.path.join(prefix, self._path, name),
301 archiver.addfile(os.path.join(prefix, self._path, name),
302 mode, symlink, self.filedata(name))
302 mode, symlink, self.filedata(name))
303
303
304
304
305 class hgsubrepo(abstractsubrepo):
305 class hgsubrepo(abstractsubrepo):
306 def __init__(self, ctx, path, state):
306 def __init__(self, ctx, path, state):
307 self._path = path
307 self._path = path
308 self._state = state
308 self._state = state
309 r = ctx._repo
309 r = ctx._repo
310 root = r.wjoin(path)
310 root = r.wjoin(path)
311 create = False
311 create = False
312 if not os.path.exists(os.path.join(root, '.hg')):
312 if not os.path.exists(os.path.join(root, '.hg')):
313 create = True
313 create = True
314 util.makedirs(root)
314 util.makedirs(root)
315 self._repo = hg.repository(r.ui, root, create=create)
315 self._repo = hg.repository(r.ui, root, create=create)
316 self._repo._subparent = r
316 self._repo._subparent = r
317 self._repo._subsource = state[0]
317 self._repo._subsource = state[0]
318
318
319 if create:
319 if create:
320 fp = self._repo.opener("hgrc", "w", text=True)
320 fp = self._repo.opener("hgrc", "w", text=True)
321 fp.write('[paths]\n')
321 fp.write('[paths]\n')
322
322
323 def addpathconfig(key, value):
323 def addpathconfig(key, value):
324 if value:
324 if value:
325 fp.write('%s = %s\n' % (key, value))
325 fp.write('%s = %s\n' % (key, value))
326 self._repo.ui.setconfig('paths', key, value)
326 self._repo.ui.setconfig('paths', key, value)
327
327
328 defpath = _abssource(self._repo, abort=False)
328 defpath = _abssource(self._repo, abort=False)
329 defpushpath = _abssource(self._repo, True, abort=False)
329 defpushpath = _abssource(self._repo, True, abort=False)
330 addpathconfig('default', defpath)
330 addpathconfig('default', defpath)
331 if defpath != defpushpath:
331 if defpath != defpushpath:
332 addpathconfig('default-push', defpushpath)
332 addpathconfig('default-push', defpushpath)
333 fp.close()
333 fp.close()
334
334
335 def add(self, ui, match, dryrun, prefix):
335 def add(self, ui, match, dryrun, prefix):
336 return cmdutil.add(ui, self._repo, match, dryrun, True,
336 return cmdutil.add(ui, self._repo, match, dryrun, True,
337 os.path.join(prefix, self._path))
337 os.path.join(prefix, self._path))
338
338
339 def status(self, rev2, **opts):
339 def status(self, rev2, **opts):
340 try:
340 try:
341 rev1 = self._state[1]
341 rev1 = self._state[1]
342 ctx1 = self._repo[rev1]
342 ctx1 = self._repo[rev1]
343 ctx2 = self._repo[rev2]
343 ctx2 = self._repo[rev2]
344 return self._repo.status(ctx1, ctx2, **opts)
344 return self._repo.status(ctx1, ctx2, **opts)
345 except error.RepoLookupError, inst:
345 except error.RepoLookupError, inst:
346 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
346 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
347 % (inst, subrelpath(self)))
347 % (inst, subrelpath(self)))
348 return [], [], [], [], [], [], []
348 return [], [], [], [], [], [], []
349
349
350 def diff(self, diffopts, node2, match, prefix, **opts):
350 def diff(self, diffopts, node2, match, prefix, **opts):
351 try:
351 try:
352 node1 = node.bin(self._state[1])
352 node1 = node.bin(self._state[1])
353 # We currently expect node2 to come from substate and be
353 # We currently expect node2 to come from substate and be
354 # in hex format
354 # in hex format
355 if node2 is not None:
355 if node2 is not None:
356 node2 = node.bin(node2)
356 node2 = node.bin(node2)
357 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
357 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
358 node1, node2, match,
358 node1, node2, match,
359 prefix=os.path.join(prefix, self._path),
359 prefix=os.path.join(prefix, self._path),
360 listsubrepos=True, **opts)
360 listsubrepos=True, **opts)
361 except error.RepoLookupError, inst:
361 except error.RepoLookupError, inst:
362 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
362 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
363 % (inst, subrelpath(self)))
363 % (inst, subrelpath(self)))
364
364
365 def archive(self, archiver, prefix):
365 def archive(self, archiver, prefix):
366 abstractsubrepo.archive(self, archiver, prefix)
366 abstractsubrepo.archive(self, archiver, prefix)
367
367
368 rev = self._state[1]
368 rev = self._state[1]
369 ctx = self._repo[rev]
369 ctx = self._repo[rev]
370 for subpath in ctx.substate:
370 for subpath in ctx.substate:
371 s = subrepo(ctx, subpath)
371 s = subrepo(ctx, subpath)
372 s.archive(archiver, os.path.join(prefix, self._path))
372 s.archive(archiver, os.path.join(prefix, self._path))
373
373
374 def dirty(self):
374 def dirty(self):
375 r = self._state[1]
375 r = self._state[1]
376 if r == '':
376 if r == '':
377 return True
377 return True
378 w = self._repo[None]
378 w = self._repo[None]
379 if w.p1() != self._repo[r]: # version checked out change
379 if w.p1() != self._repo[r]: # version checked out change
380 return True
380 return True
381 return w.dirty() # working directory changed
381 return w.dirty() # working directory changed
382
382
383 def checknested(self, path):
383 def checknested(self, path):
384 return self._repo._checknested(self._repo.wjoin(path))
384 return self._repo._checknested(self._repo.wjoin(path))
385
385
386 def commit(self, text, user, date):
386 def commit(self, text, user, date):
387 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
387 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
388 n = self._repo.commit(text, user, date)
388 n = self._repo.commit(text, user, date)
389 if not n:
389 if not n:
390 return self._repo['.'].hex() # different version checked out
390 return self._repo['.'].hex() # different version checked out
391 return node.hex(n)
391 return node.hex(n)
392
392
393 def remove(self):
393 def remove(self):
394 # we can't fully delete the repository as it may contain
394 # we can't fully delete the repository as it may contain
395 # local-only history
395 # local-only history
396 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
396 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
397 hg.clean(self._repo, node.nullid, False)
397 hg.clean(self._repo, node.nullid, False)
398
398
399 def _get(self, state):
399 def _get(self, state):
400 source, revision, kind = state
400 source, revision, kind = state
401 try:
401 try:
402 self._repo.lookup(revision)
402 self._repo.lookup(revision)
403 except error.RepoError:
403 except error.RepoError:
404 self._repo._subsource = source
404 self._repo._subsource = source
405 srcurl = _abssource(self._repo)
405 srcurl = _abssource(self._repo)
406 self._repo.ui.status(_('pulling subrepo %s from %s\n')
406 self._repo.ui.status(_('pulling subrepo %s from %s\n')
407 % (subrelpath(self), srcurl))
407 % (subrelpath(self), srcurl))
408 other = hg.repository(self._repo.ui, srcurl)
408 other = hg.repository(self._repo.ui, srcurl)
409 self._repo.pull(other)
409 self._repo.pull(other)
410
410
411 def get(self, state):
411 def get(self, state):
412 self._get(state)
412 self._get(state)
413 source, revision, kind = state
413 source, revision, kind = state
414 self._repo.ui.debug("getting subrepo %s\n" % self._path)
414 self._repo.ui.debug("getting subrepo %s\n" % self._path)
415 hg.clean(self._repo, revision, False)
415 hg.clean(self._repo, revision, False)
416
416
417 def merge(self, state):
417 def merge(self, state):
418 self._get(state)
418 self._get(state)
419 cur = self._repo['.']
419 cur = self._repo['.']
420 dst = self._repo[state[1]]
420 dst = self._repo[state[1]]
421 anc = dst.ancestor(cur)
421 anc = dst.ancestor(cur)
422 if anc == cur:
422 if anc == cur:
423 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
423 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
424 hg.update(self._repo, state[1])
424 hg.update(self._repo, state[1])
425 elif anc == dst:
425 elif anc == dst:
426 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
426 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
427 else:
427 else:
428 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
428 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
429 hg.merge(self._repo, state[1], remind=False)
429 hg.merge(self._repo, state[1], remind=False)
430
430
431 def push(self, force):
431 def push(self, force):
432 # push subrepos depth-first for coherent ordering
432 # push subrepos depth-first for coherent ordering
433 c = self._repo['']
433 c = self._repo['']
434 subs = c.substate # only repos that are committed
434 subs = c.substate # only repos that are committed
435 for s in sorted(subs):
435 for s in sorted(subs):
436 if not c.sub(s).push(force):
436 if not c.sub(s).push(force):
437 return False
437 return False
438
438
439 dsturl = _abssource(self._repo, True)
439 dsturl = _abssource(self._repo, True)
440 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
440 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
441 (subrelpath(self), dsturl))
441 (subrelpath(self), dsturl))
442 other = hg.repository(self._repo.ui, dsturl)
442 other = hg.repository(self._repo.ui, dsturl)
443 return self._repo.push(other, force)
443 return self._repo.push(other, force)
444
444
445 def outgoing(self, ui, dest, opts):
445 def outgoing(self, ui, dest, opts):
446 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
446 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
447
447
448 def incoming(self, ui, source, opts):
448 def incoming(self, ui, source, opts):
449 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
449 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
450
450
451 def files(self):
451 def files(self):
452 rev = self._state[1]
452 rev = self._state[1]
453 ctx = self._repo[rev]
453 ctx = self._repo[rev]
454 return ctx.manifest()
454 return ctx.manifest()
455
455
456 def filedata(self, name):
456 def filedata(self, name):
457 rev = self._state[1]
457 rev = self._state[1]
458 return self._repo[rev][name].data()
458 return self._repo[rev][name].data()
459
459
460 def fileflags(self, name):
460 def fileflags(self, name):
461 rev = self._state[1]
461 rev = self._state[1]
462 ctx = self._repo[rev]
462 ctx = self._repo[rev]
463 return ctx.flags(name)
463 return ctx.flags(name)
464
464
465
465
466 class svnsubrepo(abstractsubrepo):
466 class svnsubrepo(abstractsubrepo):
467 def __init__(self, ctx, path, state):
467 def __init__(self, ctx, path, state):
468 self._path = path
468 self._path = path
469 self._state = state
469 self._state = state
470 self._ctx = ctx
470 self._ctx = ctx
471 self._ui = ctx._repo.ui
471 self._ui = ctx._repo.ui
472
472
473 def _svncommand(self, commands, filename=''):
473 def _svncommand(self, commands, filename=''):
474 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
474 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
475 cmd = ['svn'] + commands + [path]
475 cmd = ['svn'] + commands + [path]
476 cmd = [util.shellquote(arg) for arg in cmd]
476 cmd = [util.shellquote(arg) for arg in cmd]
477 cmd = util.quotecommand(' '.join(cmd))
477 cmd = util.quotecommand(' '.join(cmd))
478 env = dict(os.environ)
478 env = dict(os.environ)
479 # Avoid localized output, preserve current locale for everything else.
479 # Avoid localized output, preserve current locale for everything else.
480 env['LC_MESSAGES'] = 'C'
480 env['LC_MESSAGES'] = 'C'
481 write, read, err = util.popen3(cmd, env=env, newlines=True)
481 write, read, err = util.popen3(cmd, env=env, newlines=True)
482 retdata = read.read()
482 retdata = read.read()
483 err = err.read().strip()
483 err = err.read().strip()
484 if err:
484 if err:
485 raise util.Abort(err)
485 raise util.Abort(err)
486 return retdata
486 return retdata
487
487
488 def _wcrev(self):
488 def _wcrev(self):
489 output = self._svncommand(['info', '--xml'])
489 output = self._svncommand(['info', '--xml'])
490 doc = xml.dom.minidom.parseString(output)
490 doc = xml.dom.minidom.parseString(output)
491 entries = doc.getElementsByTagName('entry')
491 entries = doc.getElementsByTagName('entry')
492 if not entries:
492 if not entries:
493 return 0
493 return 0
494 return int(entries[0].getAttribute('revision') or 0)
494 return str(entries[0].getAttribute('revision')) or '0'
495
495
496 def _wcchanged(self):
496 def _wcchanged(self):
497 """Return (changes, extchanges) where changes is True
497 """Return (changes, extchanges) where changes is True
498 if the working directory was changed, and extchanges is
498 if the working directory was changed, and extchanges is
499 True if any of these changes concern an external entry.
499 True if any of these changes concern an external entry.
500 """
500 """
501 output = self._svncommand(['status', '--xml'])
501 output = self._svncommand(['status', '--xml'])
502 externals, changes = [], []
502 externals, changes = [], []
503 doc = xml.dom.minidom.parseString(output)
503 doc = xml.dom.minidom.parseString(output)
504 for e in doc.getElementsByTagName('entry'):
504 for e in doc.getElementsByTagName('entry'):
505 s = e.getElementsByTagName('wc-status')
505 s = e.getElementsByTagName('wc-status')
506 if not s:
506 if not s:
507 continue
507 continue
508 item = s[0].getAttribute('item')
508 item = s[0].getAttribute('item')
509 props = s[0].getAttribute('props')
509 props = s[0].getAttribute('props')
510 path = e.getAttribute('path')
510 path = e.getAttribute('path')
511 if item == 'external':
511 if item == 'external':
512 externals.append(path)
512 externals.append(path)
513 if (item not in ('', 'normal', 'unversioned', 'external')
513 if (item not in ('', 'normal', 'unversioned', 'external')
514 or props not in ('', 'none')):
514 or props not in ('', 'none')):
515 changes.append(path)
515 changes.append(path)
516 for path in changes:
516 for path in changes:
517 for ext in externals:
517 for ext in externals:
518 if path == ext or path.startswith(ext + os.sep):
518 if path == ext or path.startswith(ext + os.sep):
519 return True, True
519 return True, True
520 return bool(changes), False
520 return bool(changes), False
521
521
522 def dirty(self):
522 def dirty(self):
523 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
523 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
524 return False
524 return False
525 return True
525 return True
526
526
527 def commit(self, text, user, date):
527 def commit(self, text, user, date):
528 # user and date are out of our hands since svn is centralized
528 # user and date are out of our hands since svn is centralized
529 changed, extchanged = self._wcchanged()
529 changed, extchanged = self._wcchanged()
530 if not changed:
530 if not changed:
531 return self._wcrev()
531 return self._wcrev()
532 if extchanged:
532 if extchanged:
533 # Do not try to commit externals
533 # Do not try to commit externals
534 raise util.Abort(_('cannot commit svn externals'))
534 raise util.Abort(_('cannot commit svn externals'))
535 commitinfo = self._svncommand(['commit', '-m', text])
535 commitinfo = self._svncommand(['commit', '-m', text])
536 self._ui.status(commitinfo)
536 self._ui.status(commitinfo)
537 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
537 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
538 if not newrev:
538 if not newrev:
539 raise util.Abort(commitinfo.splitlines()[-1])
539 raise util.Abort(commitinfo.splitlines()[-1])
540 newrev = newrev.groups()[0]
540 newrev = newrev.groups()[0]
541 self._ui.status(self._svncommand(['update', '-r', newrev]))
541 self._ui.status(self._svncommand(['update', '-r', newrev]))
542 return newrev
542 return newrev
543
543
544 def remove(self):
544 def remove(self):
545 if self.dirty():
545 if self.dirty():
546 self._ui.warn(_('not removing repo %s because '
546 self._ui.warn(_('not removing repo %s because '
547 'it has changes.\n' % self._path))
547 'it has changes.\n' % self._path))
548 return
548 return
549 self._ui.note(_('removing subrepo %s\n') % self._path)
549 self._ui.note(_('removing subrepo %s\n') % self._path)
550 shutil.rmtree(self._ctx.repo.join(self._path))
550 shutil.rmtree(self._ctx.repo.join(self._path))
551
551
552 def get(self, state):
552 def get(self, state):
553 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
553 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
554 if not re.search('Checked out revision [0-9]+.', status):
554 if not re.search('Checked out revision [0-9]+.', status):
555 raise util.Abort(status.splitlines()[-1])
555 raise util.Abort(status.splitlines()[-1])
556 self._ui.status(status)
556 self._ui.status(status)
557
557
558 def merge(self, state):
558 def merge(self, state):
559 old = int(self._state[1])
559 old = int(self._state[1])
560 new = int(state[1])
560 new = int(state[1])
561 if new > old:
561 if new > old:
562 self.get(state)
562 self.get(state)
563
563
564 def push(self, force):
564 def push(self, force):
565 # push is a no-op for SVN
565 # push is a no-op for SVN
566 return True
566 return True
567
567
568 def files(self):
568 def files(self):
569 output = self._svncommand(['list'])
569 output = self._svncommand(['list'])
570 # This works because svn forbids \n in filenames.
570 # This works because svn forbids \n in filenames.
571 return output.splitlines()
571 return output.splitlines()
572
572
573 def filedata(self, name):
573 def filedata(self, name):
574 return self._svncommand(['cat'], name)
574 return self._svncommand(['cat'], name)
575
575
576
576
577 types = {
577 types = {
578 'hg': hgsubrepo,
578 'hg': hgsubrepo,
579 'svn': svnsubrepo,
579 'svn': svnsubrepo,
580 }
580 }
@@ -1,201 +1,219 b''
1 $ "$TESTDIR/hghave" svn || exit 80
1 $ "$TESTDIR/hghave" svn || exit 80
2
2
3 $ fix_path()
3 $ fix_path()
4 > {
4 > {
5 > tr '\\' /
5 > tr '\\' /
6 > }
6 > }
7
7
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
9 don't. Handle that.
9 don't. Handle that.
10
10
11 $ escapedwd=`pwd | fix_path`
11 $ escapedwd=`pwd | fix_path`
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
14
14
15 create subversion repo
15 create subversion repo
16
16
17 $ SVNREPO="file://$escapedwd/svn-repo"
17 $ SVNREPO="file://$escapedwd/svn-repo"
18 $ WCROOT="`pwd`/svn-wc"
18 $ WCROOT="`pwd`/svn-wc"
19 $ svnadmin create svn-repo
19 $ svnadmin create svn-repo
20 $ svn co "$SVNREPO" svn-wc
20 $ svn co "$SVNREPO" svn-wc
21 Checked out revision 0.
21 Checked out revision 0.
22 $ cd svn-wc
22 $ cd svn-wc
23 $ mkdir src
23 $ mkdir src
24 $ echo alpha > src/alpha
24 $ echo alpha > src/alpha
25 $ svn add src
25 $ svn add src
26 A src
26 A src
27 A src/alpha
27 A src/alpha
28 $ mkdir externals
28 $ mkdir externals
29 $ echo other > externals/other
29 $ echo other > externals/other
30 $ svn add externals
30 $ svn add externals
31 A externals
31 A externals
32 A externals/other
32 A externals/other
33 $ svn ci -m 'Add alpha'
33 $ svn ci -m 'Add alpha'
34 Adding externals
34 Adding externals
35 Adding externals/other
35 Adding externals/other
36 Adding src
36 Adding src
37 Adding src/alpha
37 Adding src/alpha
38 Transmitting file data ..
38 Transmitting file data ..
39 Committed revision 1.
39 Committed revision 1.
40 $ svn up
40 $ svn up
41 At revision 1.
41 At revision 1.
42 $ echo "externals -r1 $SVNREPO/externals" > extdef
42 $ echo "externals -r1 $SVNREPO/externals" > extdef
43 $ svn propset -F extdef svn:externals src
43 $ svn propset -F extdef svn:externals src
44 property 'svn:externals' set on 'src'
44 property 'svn:externals' set on 'src'
45 $ svn ci -m 'Setting externals'
45 $ svn ci -m 'Setting externals'
46 Sending src
46 Sending src
47
47
48 Committed revision 2.
48 Committed revision 2.
49 $ cd ..
49 $ cd ..
50
50
51 create hg repo
51 create hg repo
52
52
53 $ mkdir sub
53 $ mkdir sub
54 $ cd sub
54 $ cd sub
55 $ hg init t
55 $ hg init t
56 $ cd t
56 $ cd t
57
57
58 first revision, no sub
58 first revision, no sub
59
59
60 $ echo a > a
60 $ echo a > a
61 $ hg ci -Am0
61 $ hg ci -Am0
62 adding a
62 adding a
63
63
64 add first svn sub with leading whitespaces
64 add first svn sub with leading whitespaces
65
65
66 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
66 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
67 $ svn co --quiet "$SVNREPO"/src s
67 $ svn co --quiet "$SVNREPO"/src s
68 $ hg add .hgsub
68 $ hg add .hgsub
69 $ hg ci -m1
69 $ hg ci -m1
70 committing subrepository s
70 committing subrepository s
71
71
72 make sure we avoid empty commits (issue2445)
73
74 $ hg sum
75 parent: 1:* tip (glob)
76 1
77 branch: default
78 commit: (clean)
79 update: (current)
80 $ hg ci -moops
81 nothing changed
82 [1]
83
72 debugsub
84 debugsub
73
85
74 $ hg debugsub
86 $ hg debugsub
75 path s
87 path s
76 source file://*/svn-repo/src (glob)
88 source file://*/svn-repo/src (glob)
77 revision 2
89 revision 2
78
90
79 change file in svn and hg, commit
91 change file in svn and hg, commit
80
92
81 $ echo a >> a
93 $ echo a >> a
82 $ echo alpha >> s/alpha
94 $ echo alpha >> s/alpha
95 $ hg sum
96 parent: 1:* tip (glob)
97 1
98 branch: default
99 commit: 1 modified, 1 subrepos
100 update: (current)
83 $ hg commit -m 'Message!'
101 $ hg commit -m 'Message!'
84 committing subrepository s
102 committing subrepository s
85 Sending*s/alpha (glob)
103 Sending*s/alpha (glob)
86 Transmitting file data .
104 Transmitting file data .
87 Committed revision 3.
105 Committed revision 3.
88
106
89 Fetching external item into '$TESTTMP/sub/t/s/externals'
107 Fetching external item into '$TESTTMP/sub/t/s/externals'
90 External at revision 1.
108 External at revision 1.
91
109
92 At revision 3.
110 At revision 3.
93 $ hg debugsub
111 $ hg debugsub
94 path s
112 path s
95 source file://*/svn-repo/src (glob)
113 source file://*/svn-repo/src (glob)
96 revision 3
114 revision 3
97
115
98 $ echo a > s/a
116 $ echo a > s/a
99
117
100 should be empty despite change to s/a
118 should be empty despite change to s/a
101
119
102 $ hg st
120 $ hg st
103
121
104 add a commit from svn
122 add a commit from svn
105
123
106 $ cd "$WCROOT"/src
124 $ cd "$WCROOT"/src
107 $ svn up
125 $ svn up
108 U alpha
126 U alpha
109
127
110 Fetching external item into 'externals'
128 Fetching external item into 'externals'
111 A externals/other
129 A externals/other
112 Updated external to revision 1.
130 Updated external to revision 1.
113
131
114 Updated to revision 3.
132 Updated to revision 3.
115 $ echo xyz >> alpha
133 $ echo xyz >> alpha
116 $ svn propset svn:mime-type 'text/xml' alpha
134 $ svn propset svn:mime-type 'text/xml' alpha
117 property 'svn:mime-type' set on 'alpha'
135 property 'svn:mime-type' set on 'alpha'
118 $ svn ci -m 'amend a from svn'
136 $ svn ci -m 'amend a from svn'
119 Sending src/alpha
137 Sending src/alpha
120 Transmitting file data .
138 Transmitting file data .
121 Committed revision 4.
139 Committed revision 4.
122 $ cd ../../sub/t
140 $ cd ../../sub/t
123
141
124 this commit from hg will fail
142 this commit from hg will fail
125
143
126 $ echo zzz >> s/alpha
144 $ echo zzz >> s/alpha
127 $ hg ci -m 'amend alpha from hg'
145 $ hg ci -m 'amend alpha from hg'
128 committing subrepository s
146 committing subrepository s
129 abort: svn: Commit failed (details follow):
147 abort: svn: Commit failed (details follow):
130 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
148 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
131 [255]
149 [255]
132 $ svn revert -q s/alpha
150 $ svn revert -q s/alpha
133
151
134 this commit fails because of meta changes
152 this commit fails because of meta changes
135
153
136 $ svn propset svn:mime-type 'text/html' s/alpha
154 $ svn propset svn:mime-type 'text/html' s/alpha
137 property 'svn:mime-type' set on 's/alpha'
155 property 'svn:mime-type' set on 's/alpha'
138 $ hg ci -m 'amend alpha from hg'
156 $ hg ci -m 'amend alpha from hg'
139 committing subrepository s
157 committing subrepository s
140 abort: svn: Commit failed (details follow):
158 abort: svn: Commit failed (details follow):
141 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
159 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
142 [255]
160 [255]
143 $ svn revert -q s/alpha
161 $ svn revert -q s/alpha
144
162
145 this commit fails because of externals changes
163 this commit fails because of externals changes
146
164
147 $ echo zzz > s/externals/other
165 $ echo zzz > s/externals/other
148 $ hg ci -m 'amend externals from hg'
166 $ hg ci -m 'amend externals from hg'
149 committing subrepository s
167 committing subrepository s
150 abort: cannot commit svn externals
168 abort: cannot commit svn externals
151 [255]
169 [255]
152 $ hg diff --subrepos -r 1:2 | grep -v diff
170 $ hg diff --subrepos -r 1:2 | grep -v diff
153 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
171 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
154 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
172 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
155 @@ -1,1 +1,1 @@
173 @@ -1,1 +1,1 @@
156 -2 s
174 -2 s
157 +3 s
175 +3 s
158 --- a/a Thu Jan 01 00:00:00 1970 +0000
176 --- a/a Thu Jan 01 00:00:00 1970 +0000
159 +++ b/a Thu Jan 01 00:00:00 1970 +0000
177 +++ b/a Thu Jan 01 00:00:00 1970 +0000
160 @@ -1,1 +1,2 @@
178 @@ -1,1 +1,2 @@
161 a
179 a
162 +a
180 +a
163 $ svn revert -q s/externals/other
181 $ svn revert -q s/externals/other
164
182
165 this commit fails because of externals meta changes
183 this commit fails because of externals meta changes
166
184
167 $ svn propset svn:mime-type 'text/html' s/externals/other
185 $ svn propset svn:mime-type 'text/html' s/externals/other
168 property 'svn:mime-type' set on 's/externals/other'
186 property 'svn:mime-type' set on 's/externals/other'
169 $ hg ci -m 'amend externals from hg'
187 $ hg ci -m 'amend externals from hg'
170 committing subrepository s
188 committing subrepository s
171 abort: cannot commit svn externals
189 abort: cannot commit svn externals
172 [255]
190 [255]
173 $ svn revert -q s/externals/other
191 $ svn revert -q s/externals/other
174
192
175 clone
193 clone
176
194
177 $ cd ..
195 $ cd ..
178 $ hg clone t tc | fix_path
196 $ hg clone t tc | fix_path
179 updating to branch default
197 updating to branch default
180 A tc/s/alpha
198 A tc/s/alpha
181 U tc/s
199 U tc/s
182
200
183 Fetching external item into 'tc/s/externals'
201 Fetching external item into 'tc/s/externals'
184 A tc/s/externals/other
202 A tc/s/externals/other
185 Checked out external at revision 1.
203 Checked out external at revision 1.
186
204
187 Checked out revision 3.
205 Checked out revision 3.
188 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 $ cd tc
207 $ cd tc
190
208
191 debugsub in clone
209 debugsub in clone
192
210
193 $ hg debugsub
211 $ hg debugsub
194 path s
212 path s
195 source file://*/svn-repo/src (glob)
213 source file://*/svn-repo/src (glob)
196 revision 3
214 revision 3
197
215
198 verify subrepo is contained within the repo directory
216 verify subrepo is contained within the repo directory
199
217
200 $ python -c "import os.path; print os.path.exists('s')"
218 $ python -c "import os.path; print os.path.exists('s')"
201 True
219 True
General Comments 0
You need to be logged in to leave comments. Login now