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