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