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