##// END OF EJS Templates
subrepo: handle svn externals and meta changes (issue1982)...
Patrick Mezard -
r10273:e898bc78 default
parent child Browse files
Show More
@@ -1,342 +1,360 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 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
8 import errno, os, re, xml.dom.minidom
9 from i18n import _
9 from i18n import _
10 import config, util, node, error
10 import config, util, node, error
11 hg = None
11 hg = None
12
12
13 nullstate = ('', '', 'empty')
13 nullstate = ('', '', 'empty')
14
14
15 def state(ctx):
15 def state(ctx):
16 p = config.config()
16 p = config.config()
17 def read(f, sections=None, remap=None):
17 def read(f, sections=None, remap=None):
18 if f in ctx:
18 if f in ctx:
19 p.parse(f, ctx[f].data(), sections, remap, read)
19 p.parse(f, ctx[f].data(), sections, remap, read)
20 else:
20 else:
21 raise util.Abort(_("subrepo spec file %s not found") % f)
21 raise util.Abort(_("subrepo spec file %s not found") % f)
22
22
23 if '.hgsub' in ctx:
23 if '.hgsub' in ctx:
24 read('.hgsub')
24 read('.hgsub')
25
25
26 rev = {}
26 rev = {}
27 if '.hgsubstate' in ctx:
27 if '.hgsubstate' in ctx:
28 try:
28 try:
29 for l in ctx['.hgsubstate'].data().splitlines():
29 for l in ctx['.hgsubstate'].data().splitlines():
30 revision, path = l.split(" ", 1)
30 revision, path = l.split(" ", 1)
31 rev[path] = revision
31 rev[path] = revision
32 except IOError, err:
32 except IOError, err:
33 if err.errno != errno.ENOENT:
33 if err.errno != errno.ENOENT:
34 raise
34 raise
35
35
36 state = {}
36 state = {}
37 for path, src in p[''].items():
37 for path, src in p[''].items():
38 kind = 'hg'
38 kind = 'hg'
39 if src.startswith('['):
39 if src.startswith('['):
40 if ']' not in src:
40 if ']' not in src:
41 raise util.Abort(_('missing ] in subrepo source'))
41 raise util.Abort(_('missing ] in subrepo source'))
42 kind, src = src.split(']', 1)
42 kind, src = src.split(']', 1)
43 kind = kind[1:]
43 kind = kind[1:]
44 state[path] = (src, rev.get(path, ''), kind)
44 state[path] = (src, rev.get(path, ''), kind)
45
45
46 return state
46 return state
47
47
48 def writestate(repo, state):
48 def writestate(repo, state):
49 repo.wwrite('.hgsubstate',
49 repo.wwrite('.hgsubstate',
50 ''.join(['%s %s\n' % (state[s][1], s)
50 ''.join(['%s %s\n' % (state[s][1], s)
51 for s in sorted(state)]), '')
51 for s in sorted(state)]), '')
52
52
53 def submerge(repo, wctx, mctx, actx):
53 def submerge(repo, wctx, mctx, actx):
54 # working context, merging context, ancestor context
54 # working context, merging context, ancestor context
55 if mctx == actx: # backwards?
55 if mctx == actx: # backwards?
56 actx = wctx.p1()
56 actx = wctx.p1()
57 s1 = wctx.substate
57 s1 = wctx.substate
58 s2 = mctx.substate
58 s2 = mctx.substate
59 sa = actx.substate
59 sa = actx.substate
60 sm = {}
60 sm = {}
61
61
62 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
62 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
63
63
64 def debug(s, msg, r=""):
64 def debug(s, msg, r=""):
65 if r:
65 if r:
66 r = "%s:%s:%s" % r
66 r = "%s:%s:%s" % r
67 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
67 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
68
68
69 for s, l in s1.items():
69 for s, l in s1.items():
70 if wctx != actx and wctx.sub(s).dirty():
70 if wctx != actx and wctx.sub(s).dirty():
71 l = (l[0], l[1] + "+")
71 l = (l[0], l[1] + "+")
72 a = sa.get(s, nullstate)
72 a = sa.get(s, nullstate)
73 if s in s2:
73 if s in s2:
74 r = s2[s]
74 r = s2[s]
75 if l == r or r == a: # no change or local is newer
75 if l == r or r == a: # no change or local is newer
76 sm[s] = l
76 sm[s] = l
77 continue
77 continue
78 elif l == a: # other side changed
78 elif l == a: # other side changed
79 debug(s, "other changed, get", r)
79 debug(s, "other changed, get", r)
80 wctx.sub(s).get(r)
80 wctx.sub(s).get(r)
81 sm[s] = r
81 sm[s] = r
82 elif l[0] != r[0]: # sources differ
82 elif l[0] != r[0]: # sources differ
83 if repo.ui.promptchoice(
83 if repo.ui.promptchoice(
84 _(' subrepository sources for %s differ\n'
84 _(' subrepository sources for %s differ\n'
85 'use (l)ocal source (%s) or (r)emote source (%s)?')
85 'use (l)ocal source (%s) or (r)emote source (%s)?')
86 % (s, l[0], r[0]),
86 % (s, l[0], r[0]),
87 (_('&Local'), _('&Remote')), 0):
87 (_('&Local'), _('&Remote')), 0):
88 debug(s, "prompt changed, get", r)
88 debug(s, "prompt changed, get", r)
89 wctx.sub(s).get(r)
89 wctx.sub(s).get(r)
90 sm[s] = r
90 sm[s] = r
91 elif l[1] == a[1]: # local side is unchanged
91 elif l[1] == a[1]: # local side is unchanged
92 debug(s, "other side changed, get", r)
92 debug(s, "other side changed, get", r)
93 wctx.sub(s).get(r)
93 wctx.sub(s).get(r)
94 sm[s] = r
94 sm[s] = r
95 else:
95 else:
96 debug(s, "both sides changed, merge with", r)
96 debug(s, "both sides changed, merge with", r)
97 wctx.sub(s).merge(r)
97 wctx.sub(s).merge(r)
98 sm[s] = l
98 sm[s] = l
99 elif l == a: # remote removed, local unchanged
99 elif l == a: # remote removed, local unchanged
100 debug(s, "remote removed, remove")
100 debug(s, "remote removed, remove")
101 wctx.sub(s).remove()
101 wctx.sub(s).remove()
102 else:
102 else:
103 if repo.ui.promptchoice(
103 if repo.ui.promptchoice(
104 _(' local changed subrepository %s which remote removed\n'
104 _(' local changed subrepository %s which remote removed\n'
105 'use (c)hanged version or (d)elete?') % s,
105 'use (c)hanged version or (d)elete?') % s,
106 (_('&Changed'), _('&Delete')), 0):
106 (_('&Changed'), _('&Delete')), 0):
107 debug(s, "prompt remove")
107 debug(s, "prompt remove")
108 wctx.sub(s).remove()
108 wctx.sub(s).remove()
109
109
110 for s, r in s2.items():
110 for s, r in s2.items():
111 if s in s1:
111 if s in s1:
112 continue
112 continue
113 elif s not in sa:
113 elif s not in sa:
114 debug(s, "remote added, get", r)
114 debug(s, "remote added, get", r)
115 mctx.sub(s).get(r)
115 mctx.sub(s).get(r)
116 sm[s] = r
116 sm[s] = r
117 elif r != sa[s]:
117 elif r != sa[s]:
118 if repo.ui.promptchoice(
118 if repo.ui.promptchoice(
119 _(' remote changed subrepository %s which local removed\n'
119 _(' remote changed subrepository %s which local removed\n'
120 'use (c)hanged version or (d)elete?') % s,
120 'use (c)hanged version or (d)elete?') % s,
121 (_('&Changed'), _('&Delete')), 0) == 0:
121 (_('&Changed'), _('&Delete')), 0) == 0:
122 debug(s, "prompt recreate", r)
122 debug(s, "prompt recreate", r)
123 wctx.sub(s).get(r)
123 wctx.sub(s).get(r)
124 sm[s] = r
124 sm[s] = r
125
125
126 # record merged .hgsubstate
126 # record merged .hgsubstate
127 writestate(repo, sm)
127 writestate(repo, sm)
128
128
129 def _abssource(repo, push=False):
129 def _abssource(repo, push=False):
130 if hasattr(repo, '_subparent'):
130 if hasattr(repo, '_subparent'):
131 source = repo._subsource
131 source = repo._subsource
132 if source.startswith('/') or '://' in source:
132 if source.startswith('/') or '://' in source:
133 return source
133 return source
134 parent = _abssource(repo._subparent)
134 parent = _abssource(repo._subparent)
135 if '://' in parent:
135 if '://' in parent:
136 if parent[-1] == '/':
136 if parent[-1] == '/':
137 parent = parent[:-1]
137 parent = parent[:-1]
138 return parent + '/' + source
138 return parent + '/' + source
139 return os.path.join(parent, repo._subsource)
139 return os.path.join(parent, repo._subsource)
140 if push and repo.ui.config('paths', 'default-push'):
140 if push and repo.ui.config('paths', 'default-push'):
141 return repo.ui.config('paths', 'default-push', repo.root)
141 return repo.ui.config('paths', 'default-push', repo.root)
142 return repo.ui.config('paths', 'default', repo.root)
142 return repo.ui.config('paths', 'default', repo.root)
143
143
144 def subrepo(ctx, path):
144 def subrepo(ctx, path):
145 # subrepo inherently violates our import layering rules
145 # subrepo inherently violates our import layering rules
146 # because it wants to make repo objects from deep inside the stack
146 # because it wants to make repo objects from deep inside the stack
147 # so we manually delay the circular imports to not break
147 # so we manually delay the circular imports to not break
148 # scripts that don't use our demand-loading
148 # scripts that don't use our demand-loading
149 global hg
149 global hg
150 import hg as h
150 import hg as h
151 hg = h
151 hg = h
152
152
153 util.path_auditor(ctx._repo.root)(path)
153 util.path_auditor(ctx._repo.root)(path)
154 state = ctx.substate.get(path, nullstate)
154 state = ctx.substate.get(path, nullstate)
155 if state[2] not in types:
155 if state[2] not in types:
156 raise util.Abort(_('unknown subrepo type %s') % t)
156 raise util.Abort(_('unknown subrepo type %s') % t)
157 return types[state[2]](ctx, path, state[:2])
157 return types[state[2]](ctx, path, state[:2])
158
158
159 # subrepo classes need to implement the following methods:
159 # subrepo classes need to implement the following methods:
160 # __init__(self, ctx, path, state)
160 # __init__(self, ctx, path, state)
161 # dirty(self): returns true if the dirstate of the subrepo
161 # dirty(self): returns true if the dirstate of the subrepo
162 # does not match current stored state
162 # does not match current stored state
163 # commit(self, text, user, date): commit the current changes
163 # commit(self, text, user, date): commit the current changes
164 # to the subrepo with the given log message. Use given
164 # to the subrepo with the given log message. Use given
165 # user and date if possible. Return the new state of the subrepo.
165 # user and date if possible. Return the new state of the subrepo.
166 # remove(self): remove the subrepo (should verify the dirstate
166 # remove(self): remove the subrepo (should verify the dirstate
167 # is not dirty first)
167 # is not dirty first)
168 # get(self, state): run whatever commands are needed to put the
168 # get(self, state): run whatever commands are needed to put the
169 # subrepo into this state
169 # subrepo into this state
170 # merge(self, state): merge currently-saved state with the new state.
170 # merge(self, state): merge currently-saved state with the new state.
171 # push(self, force): perform whatever action is analagous to 'hg push'
171 # push(self, force): perform whatever action is analagous to 'hg push'
172 # This may be a no-op on some systems.
172 # This may be a no-op on some systems.
173
173
174 class hgsubrepo(object):
174 class hgsubrepo(object):
175 def __init__(self, ctx, path, state):
175 def __init__(self, ctx, path, state):
176 self._path = path
176 self._path = path
177 self._state = state
177 self._state = state
178 r = ctx._repo
178 r = ctx._repo
179 root = r.wjoin(path)
179 root = r.wjoin(path)
180 if os.path.exists(os.path.join(root, '.hg')):
180 if os.path.exists(os.path.join(root, '.hg')):
181 self._repo = hg.repository(r.ui, root)
181 self._repo = hg.repository(r.ui, root)
182 else:
182 else:
183 util.makedirs(root)
183 util.makedirs(root)
184 self._repo = hg.repository(r.ui, root, create=True)
184 self._repo = hg.repository(r.ui, root, create=True)
185 f = file(os.path.join(root, '.hg', 'hgrc'), 'w')
185 f = file(os.path.join(root, '.hg', 'hgrc'), 'w')
186 f.write('[paths]\ndefault = %s\n' % state[0])
186 f.write('[paths]\ndefault = %s\n' % state[0])
187 f.close()
187 f.close()
188 self._repo._subparent = r
188 self._repo._subparent = r
189 self._repo._subsource = state[0]
189 self._repo._subsource = state[0]
190
190
191 def dirty(self):
191 def dirty(self):
192 r = self._state[1]
192 r = self._state[1]
193 if r == '':
193 if r == '':
194 return True
194 return True
195 w = self._repo[None]
195 w = self._repo[None]
196 if w.p1() != self._repo[r]: # version checked out changed
196 if w.p1() != self._repo[r]: # version checked out changed
197 return True
197 return True
198 return w.dirty() # working directory changed
198 return w.dirty() # working directory changed
199
199
200 def commit(self, text, user, date):
200 def commit(self, text, user, date):
201 self._repo.ui.debug("committing subrepo %s\n" % self._path)
201 self._repo.ui.debug("committing subrepo %s\n" % self._path)
202 n = self._repo.commit(text, user, date)
202 n = self._repo.commit(text, user, date)
203 if not n:
203 if not n:
204 return self._repo['.'].hex() # different version checked out
204 return self._repo['.'].hex() # different version checked out
205 return node.hex(n)
205 return node.hex(n)
206
206
207 def remove(self):
207 def remove(self):
208 # we can't fully delete the repository as it may contain
208 # we can't fully delete the repository as it may contain
209 # local-only history
209 # local-only history
210 self._repo.ui.note(_('removing subrepo %s\n') % self._path)
210 self._repo.ui.note(_('removing subrepo %s\n') % self._path)
211 hg.clean(self._repo, node.nullid, False)
211 hg.clean(self._repo, node.nullid, False)
212
212
213 def _get(self, state):
213 def _get(self, state):
214 source, revision, kind = state
214 source, revision, kind = state
215 try:
215 try:
216 self._repo.lookup(revision)
216 self._repo.lookup(revision)
217 except error.RepoError:
217 except error.RepoError:
218 self._repo._subsource = source
218 self._repo._subsource = source
219 self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
219 self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
220 srcurl = _abssource(self._repo)
220 srcurl = _abssource(self._repo)
221 other = hg.repository(self._repo.ui, srcurl)
221 other = hg.repository(self._repo.ui, srcurl)
222 self._repo.pull(other)
222 self._repo.pull(other)
223
223
224 def get(self, state):
224 def get(self, state):
225 self._get(state)
225 self._get(state)
226 source, revision, kind = state
226 source, revision, kind = state
227 self._repo.ui.debug("getting subrepo %s\n" % self._path)
227 self._repo.ui.debug("getting subrepo %s\n" % self._path)
228 hg.clean(self._repo, revision, False)
228 hg.clean(self._repo, revision, False)
229
229
230 def merge(self, state):
230 def merge(self, state):
231 self._get(state)
231 self._get(state)
232 cur = self._repo['.']
232 cur = self._repo['.']
233 dst = self._repo[state[1]]
233 dst = self._repo[state[1]]
234 anc = dst.ancestor(cur)
234 anc = dst.ancestor(cur)
235 if anc == cur:
235 if anc == cur:
236 self._repo.ui.debug("updating subrepo %s\n" % self._path)
236 self._repo.ui.debug("updating subrepo %s\n" % self._path)
237 hg.update(self._repo, state[1])
237 hg.update(self._repo, state[1])
238 elif anc == dst:
238 elif anc == dst:
239 self._repo.ui.debug("skipping subrepo %s\n" % self._path)
239 self._repo.ui.debug("skipping subrepo %s\n" % self._path)
240 else:
240 else:
241 self._repo.ui.debug("merging subrepo %s\n" % self._path)
241 self._repo.ui.debug("merging subrepo %s\n" % self._path)
242 hg.merge(self._repo, state[1], remind=False)
242 hg.merge(self._repo, state[1], remind=False)
243
243
244 def push(self, force):
244 def push(self, force):
245 # push subrepos depth-first for coherent ordering
245 # push subrepos depth-first for coherent ordering
246 c = self._repo['']
246 c = self._repo['']
247 subs = c.substate # only repos that are committed
247 subs = c.substate # only repos that are committed
248 for s in sorted(subs):
248 for s in sorted(subs):
249 c.sub(s).push(force)
249 c.sub(s).push(force)
250
250
251 self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
251 self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
252 dsturl = _abssource(self._repo, True)
252 dsturl = _abssource(self._repo, True)
253 other = hg.repository(self._repo.ui, dsturl)
253 other = hg.repository(self._repo.ui, dsturl)
254 self._repo.push(other, force)
254 self._repo.push(other, force)
255
255
256 class svnsubrepo(object):
256 class svnsubrepo(object):
257 def __init__(self, ctx, path, state):
257 def __init__(self, ctx, path, state):
258 self._path = path
258 self._path = path
259 self._state = state
259 self._state = state
260 self._ctx = ctx
260 self._ctx = ctx
261 self._ui = ctx._repo.ui
261 self._ui = ctx._repo.ui
262
262
263 def _svncommand(self, commands):
263 def _svncommand(self, commands):
264 cmd = ['svn'] + commands + [self._path]
264 cmd = ['svn'] + commands + [self._path]
265 cmd = [util.shellquote(arg) for arg in cmd]
265 cmd = [util.shellquote(arg) for arg in cmd]
266 cmd = util.quotecommand(' '.join(cmd))
266 cmd = util.quotecommand(' '.join(cmd))
267 env = dict(os.environ)
267 env = dict(os.environ)
268 # Avoid localized output, preserve current locale for everything else.
268 # Avoid localized output, preserve current locale for everything else.
269 env['LC_MESSAGES'] = 'C'
269 env['LC_MESSAGES'] = 'C'
270 write, read, err = util.popen3(cmd, env=env, newlines=True)
270 write, read, err = util.popen3(cmd, env=env, newlines=True)
271 retdata = read.read()
271 retdata = read.read()
272 err = err.read().strip()
272 err = err.read().strip()
273 if err:
273 if err:
274 raise util.Abort(err)
274 raise util.Abort(err)
275 return retdata
275 return retdata
276
276
277 def _wcrev(self):
277 def _wcrev(self):
278 output = self._svncommand(['info', '--xml'])
278 output = self._svncommand(['info', '--xml'])
279 doc = xml.dom.minidom.parseString(output)
279 doc = xml.dom.minidom.parseString(output)
280 entries = doc.getElementsByTagName('entry')
280 entries = doc.getElementsByTagName('entry')
281 if not entries:
281 if not entries:
282 return 0
282 return 0
283 return int(entries[0].getAttribute('revision') or 0)
283 return int(entries[0].getAttribute('revision') or 0)
284
284
285 def _wcclean(self):
285 def _wcchanged(self):
286 """Return (changes, extchanges) where changes is True
287 if the working directory was changed, and extchanges is
288 True if any of these changes concern an external entry.
289 """
286 output = self._svncommand(['status', '--xml'])
290 output = self._svncommand(['status', '--xml'])
291 externals, changes = [], []
287 doc = xml.dom.minidom.parseString(output)
292 doc = xml.dom.minidom.parseString(output)
288 for s in doc.getElementsByTagName('wc-status'):
293 for e in doc.getElementsByTagName('entry'):
289 st = s.getAttribute('item')
294 s = e.getElementsByTagName('wc-status')
290 if st and st != 'unversioned':
295 if not s:
291 return False
296 continue
292 props = s.getAttribute('props')
297 item = s[0].getAttribute('item')
293 if props and props != 'none':
298 props = s[0].getAttribute('props')
294 return False
299 path = e.getAttribute('path')
295 return True
300 if item == 'external':
301 externals.append(path)
302 if (item not in ('', 'normal', 'unversioned', 'external')
303 or props not in ('', 'none')):
304 changes.append(path)
305 for path in changes:
306 for ext in externals:
307 if path == ext or path.startswith(ext + os.sep):
308 return True, True
309 return bool(changes), False
296
310
297 def dirty(self):
311 def dirty(self):
298 if self._wcrev() == self._state[1] and self._wcclean():
312 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
299 return False
313 return False
300 return True
314 return True
301
315
302 def commit(self, text, user, date):
316 def commit(self, text, user, date):
303 # user and date are out of our hands since svn is centralized
317 # user and date are out of our hands since svn is centralized
304 if self._wcclean():
318 changed, extchanged = self._wcchanged()
319 if not changed:
305 return self._wcrev()
320 return self._wcrev()
321 if extchanged:
322 # Do not try to commit externals
323 raise util.Abort(_('cannot commit svn externals'))
306 commitinfo = self._svncommand(['commit', '-m', text])
324 commitinfo = self._svncommand(['commit', '-m', text])
307 self._ui.status(commitinfo)
325 self._ui.status(commitinfo)
308 newrev = re.search('Committed revision ([\d]+).', commitinfo)
326 newrev = re.search('Committed revision ([\d]+).', commitinfo)
309 if not newrev:
327 if not newrev:
310 raise util.Abort(commitinfo.splitlines()[-1])
328 raise util.Abort(commitinfo.splitlines()[-1])
311 newrev = newrev.groups()[0]
329 newrev = newrev.groups()[0]
312 self._ui.status(self._svncommand(['update', '-r', newrev]))
330 self._ui.status(self._svncommand(['update', '-r', newrev]))
313 return newrev
331 return newrev
314
332
315 def remove(self):
333 def remove(self):
316 if self.dirty():
334 if self.dirty():
317 self._repo.ui.warn(_('not removing repo %s because '
335 self._repo.ui.warn(_('not removing repo %s because '
318 'it has changes.\n' % self._path))
336 'it has changes.\n' % self._path))
319 return
337 return
320 self._repo.ui.note('removing subrepo %s\n' % self._path)
338 self._repo.ui.note('removing subrepo %s\n' % self._path)
321 shutil.rmtree(self._ctx.repo.join(self._path))
339 shutil.rmtree(self._ctx.repo.join(self._path))
322
340
323 def get(self, state):
341 def get(self, state):
324 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
342 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
325 if not re.search('Checked out revision [\d]+.', status):
343 if not re.search('Checked out revision [\d]+.', status):
326 raise util.Abort(status.splitlines()[-1])
344 raise util.Abort(status.splitlines()[-1])
327 self._ui.status(status)
345 self._ui.status(status)
328
346
329 def merge(self, state):
347 def merge(self, state):
330 old = int(self._state[1])
348 old = int(self._state[1])
331 new = int(state[1])
349 new = int(state[1])
332 if new > old:
350 if new > old:
333 self.get(state)
351 self.get(state)
334
352
335 def push(self, force):
353 def push(self, force):
336 # nothing for svn
354 # nothing for svn
337 pass
355 pass
338
356
339 types = {
357 types = {
340 'hg': hgsubrepo,
358 'hg': hgsubrepo,
341 'svn': svnsubrepo,
359 'svn': svnsubrepo,
342 }
360 }
@@ -1,78 +1,106 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" svn || exit 80
3 "$TESTDIR/hghave" svn || exit 80
4
4
5 fix_path()
5 fix_path()
6 {
6 {
7 tr '\\' /
7 tr '\\' /
8 }
8 }
9
9
10 escapedwd=`pwd | fix_path`
10 escapedwd=`pwd | fix_path`
11 # SVN wants all paths to start with a slash. Unfortunately,
11 # SVN wants all paths to start with a slash. Unfortunately,
12 # Windows ones don't. Handle that.
12 # Windows ones don't. Handle that.
13 expr $escapedwd : "\/" > /dev/null
13 expr $escapedwd : "\/" > /dev/null
14 if [ $? -ne 0 ]; then
14 if [ $? -ne 0 ]; then
15 escapedwd='/'$escapedwd
15 escapedwd='/'$escapedwd
16 fi
16 fi
17 filterpath="sed s|$escapedwd|/root|"
17 filterpath="sed s|$escapedwd|/root|"
18
18
19 echo % create subversion repo
19 echo % create subversion repo
20
20
21 SVNREPO="file://$escapedwd/svn-repo"
21 SVNREPO="file://$escapedwd/svn-repo"
22 WCROOT="`pwd`/svn-wc"
22 WCROOT="`pwd`/svn-wc"
23 svnadmin create svn-repo
23 svnadmin create svn-repo
24 svn co $SVNREPO svn-wc
24 svn co $SVNREPO svn-wc
25 cd svn-wc
25 cd svn-wc
26 echo alpha > alpha
26 mkdir src
27 svn add alpha
27 echo alpha > src/alpha
28 svn add src
29 mkdir externals
30 echo other > externals/other
31 svn add externals
28 svn ci -m 'Add alpha'
32 svn ci -m 'Add alpha'
33 svn up
34 cat > extdef <<EOF
35 externals -r1 $SVNREPO/externals
36 EOF
37 svn propset -F extdef svn:externals src
38 svn ci -m 'Setting externals'
29 cd ..
39 cd ..
30
40
31 echo % create hg repo
41 echo % create hg repo
32 mkdir sub
42 mkdir sub
33 cd sub
43 cd sub
34 hg init t
44 hg init t
35 cd t
45 cd t
36
46
37 echo % first revision, no sub
47 echo % first revision, no sub
38 echo a > a
48 echo a > a
39 hg ci -Am0
49 hg ci -Am0
40
50
41 echo % add first svn sub
51 echo % add first svn sub
42 echo "s = [svn]$SVNREPO" >> .hgsub
52 echo "s = [svn]$SVNREPO/src" >> .hgsub
43 svn co --quiet $SVNREPO s
53 svn co --quiet $SVNREPO/src s
44 hg add .hgsub
54 hg add .hgsub
45 hg ci -m1
55 hg ci -m1
46 echo % debugsub
56 echo % debugsub
47 hg debugsub | $filterpath
57 hg debugsub | $filterpath
48
58
49 echo
59 echo
50 echo % change file in svn and hg, commit
60 echo % change file in svn and hg, commit
51 echo a >> a
61 echo a >> a
52 echo alpha >> s/alpha
62 echo alpha >> s/alpha
53 hg commit -m 'Message!'
63 hg commit -m 'Message!'
54 hg debugsub | $filterpath
64 hg debugsub | $filterpath
55
65
56 echo
66 echo
57 echo a > s/a
67 echo a > s/a
58 echo % should be empty despite change to s/a
68 echo % should be empty despite change to s/a
59 hg st
69 hg st
60
70
61 echo
71 echo
62 echo % add a commit from svn
72 echo % add a commit from svn
63 cd "$WCROOT"
73 cd "$WCROOT"/src
64 svn up
74 svn up
65 echo xyz >> alpha
75 echo xyz >> alpha
76 svn propset svn:mime-type 'text/xml' alpha
66 svn ci -m 'amend a from svn'
77 svn ci -m 'amend a from svn'
67 cd ../sub/t
78 cd ../../sub/t
79
68 echo % this commit from hg will fail
80 echo % this commit from hg will fail
69 echo zzz >> s/alpha
81 echo zzz >> s/alpha
70 hg ci -m 'amend alpha from hg'
82 hg ci -m 'amend alpha from hg'
83 svn revert -q s/alpha
84
85 echo % this commit fails because of meta changes
86 svn propset svn:mime-type 'text/html' s/alpha
87 hg ci -m 'amend alpha from hg'
88 svn revert -q s/alpha
89
90 echo % this commit fails because of externals changes
91 echo zzz > s/externals/other
92 hg ci -m 'amend externals from hg'
93 svn revert -q s/externals/other
94
95 echo % this commit fails because of externals meta changes
96 svn propset svn:mime-type 'text/html' s/externals/other
97 hg ci -m 'amend externals from hg'
98 svn revert -q s/externals/other
71
99
72 echo
100 echo
73 echo % clone
101 echo % clone
74 cd ..
102 cd ..
75 hg clone t tc | fix_path
103 hg clone t tc | fix_path
76 cd tc
104 cd tc
77 echo % debugsub in clone
105 echo % debugsub in clone
78 hg debugsub | $filterpath
106 hg debugsub | $filterpath
@@ -1,48 +1,87 b''
1 % create subversion repo
1 % create subversion repo
2 Checked out revision 0.
2 Checked out revision 0.
3 A alpha
3 A src
4 Adding alpha
4 A src/alpha
5 Transmitting file data .
5 A externals
6 A externals/other
7 Adding externals
8 Adding externals/other
9 Adding src
10 Adding src/alpha
11 Transmitting file data ..
6 Committed revision 1.
12 Committed revision 1.
13 At revision 1.
14 property 'svn:externals' set on 'src'
15 Sending src
16
17 Committed revision 2.
7 % create hg repo
18 % create hg repo
8 % first revision, no sub
19 % first revision, no sub
9 adding a
20 adding a
10 % add first svn sub
21 % add first svn sub
11 committing subrepository s
22 committing subrepository s
12 % debugsub
23 % debugsub
13 path s
24 path s
14 source file:///root/svn-repo
25 source file:///root/svn-repo/src
15 revision 1
26 revision 2
16
27
17 % change file in svn and hg, commit
28 % change file in svn and hg, commit
18 committing subrepository s
29 committing subrepository s
19 Sending s/alpha
30 Sending s/alpha
20 Transmitting file data .
31 Transmitting file data .
21 Committed revision 2.
32 Committed revision 3.
22 At revision 2.
33
34 Fetching external item into 's/externals'
35 External at revision 1.
36
37 At revision 3.
23 path s
38 path s
24 source file:///root/svn-repo
39 source file:///root/svn-repo/src
25 revision 2
40 revision 3
26
41
27 % should be empty despite change to s/a
42 % should be empty despite change to s/a
28
43
29 % add a commit from svn
44 % add a commit from svn
30 U alpha
45 U alpha
31 Updated to revision 2.
46
32 Sending alpha
47 Fetching external item into 'externals'
48 A externals/other
49 Updated external to revision 1.
50
51 Updated to revision 3.
52 property 'svn:mime-type' set on 'alpha'
53 Sending src/alpha
33 Transmitting file data .
54 Transmitting file data .
34 Committed revision 3.
55 Committed revision 4.
35 % this commit from hg will fail
56 % this commit from hg will fail
36 committing subrepository s
57 committing subrepository s
37 abort: svn: Commit failed (details follow):
58 abort: svn: Commit failed (details follow):
38 svn: File '/alpha' is out of date
59 svn: File '/src/alpha' is out of date
60 % this commit fails because of meta changes
61 property 'svn:mime-type' set on 's/alpha'
62 committing subrepository s
63 abort: svn: Commit failed (details follow):
64 svn: File '/src/alpha' is out of date
65 % this commit fails because of externals changes
66 committing subrepository s
67 abort: cannot commit svn externals
68 % this commit fails because of externals meta changes
69 property 'svn:mime-type' set on 's/externals/other'
70 committing subrepository s
71 abort: cannot commit svn externals
39
72
40 % clone
73 % clone
41 updating to branch default
74 updating to branch default
42 A s/alpha
75 A s/alpha
43 Checked out revision 2.
76 U s
77
78 Fetching external item into 's/externals'
79 A s/externals/other
80 Checked out external at revision 1.
81
82 Checked out revision 3.
44 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 % debugsub in clone
84 % debugsub in clone
46 path s
85 path s
47 source file:///root/svn-repo
86 source file:///root/svn-repo/src
48 revision 2
87 revision 3
General Comments 0
You need to be logged in to leave comments. Login now