##// END OF EJS Templates
subrepos: handle backslashes in subpaths
Martin Geisler -
r11961:f3075ffa default
parent child Browse files
Show More
@@ -1,440 +1,447 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
9 from i18n import _
9 from i18n import _
10 import config, util, node, error
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, ui):
15 def state(ctx, ui):
16 """return a state dict, mapping subrepo paths configured in .hgsub
16 """return a state dict, mapping subrepo paths configured in .hgsub
17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 (key in types dict))
18 (key in types dict))
19 """
19 """
20 p = config.config()
20 p = config.config()
21 def read(f, sections=None, remap=None):
21 def read(f, sections=None, remap=None):
22 if f in ctx:
22 if f in ctx:
23 p.parse(f, ctx[f].data(), sections, remap, read)
23 p.parse(f, ctx[f].data(), sections, remap, read)
24 else:
24 else:
25 raise util.Abort(_("subrepo spec file %s not found") % f)
25 raise util.Abort(_("subrepo spec file %s not found") % f)
26
26
27 if '.hgsub' in ctx:
27 if '.hgsub' in ctx:
28 read('.hgsub')
28 read('.hgsub')
29
29
30 for path, src in ui.configitems('subpaths'):
30 for path, src in ui.configitems('subpaths'):
31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
32
32
33 rev = {}
33 rev = {}
34 if '.hgsubstate' in ctx:
34 if '.hgsubstate' in ctx:
35 try:
35 try:
36 for l in ctx['.hgsubstate'].data().splitlines():
36 for l in ctx['.hgsubstate'].data().splitlines():
37 revision, path = l.split(" ", 1)
37 revision, path = l.split(" ", 1)
38 rev[path] = revision
38 rev[path] = revision
39 except IOError, err:
39 except IOError, err:
40 if err.errno != errno.ENOENT:
40 if err.errno != errno.ENOENT:
41 raise
41 raise
42
42
43 state = {}
43 state = {}
44 for path, src in p[''].items():
44 for path, src in p[''].items():
45 kind = 'hg'
45 kind = 'hg'
46 if src.startswith('['):
46 if src.startswith('['):
47 if ']' not in src:
47 if ']' not in src:
48 raise util.Abort(_('missing ] in subrepo source'))
48 raise util.Abort(_('missing ] in subrepo source'))
49 kind, src = src.split(']', 1)
49 kind, src = src.split(']', 1)
50 kind = kind[1:]
50 kind = kind[1:]
51
51
52 for pattern, repl in p.items('subpaths'):
52 for pattern, repl in p.items('subpaths'):
53 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
54 # does a string decode.
55 repl = repl.encode('string-escape')
56 # However, we still want to allow back references to go
57 # through unharmed, so we turn r'\\1' into r'\1'. Again,
58 # extra escapes are needed because re.sub string decodes.
59 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
53 try:
60 try:
54 src = re.sub(pattern, repl, src, 1)
61 src = re.sub(pattern, repl, src, 1)
55 except re.error, e:
62 except re.error, e:
56 raise util.Abort(_("bad subrepository pattern in %s: %s")
63 raise util.Abort(_("bad subrepository pattern in %s: %s")
57 % (p.source('subpaths', pattern), e))
64 % (p.source('subpaths', pattern), e))
58
65
59 state[path] = (src.strip(), rev.get(path, ''), kind)
66 state[path] = (src.strip(), rev.get(path, ''), kind)
60
67
61 return state
68 return state
62
69
63 def writestate(repo, state):
70 def writestate(repo, state):
64 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
71 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
65 repo.wwrite('.hgsubstate',
72 repo.wwrite('.hgsubstate',
66 ''.join(['%s %s\n' % (state[s][1], s)
73 ''.join(['%s %s\n' % (state[s][1], s)
67 for s in sorted(state)]), '')
74 for s in sorted(state)]), '')
68
75
69 def submerge(repo, wctx, mctx, actx):
76 def submerge(repo, wctx, mctx, actx):
70 """delegated from merge.applyupdates: merging of .hgsubstate file
77 """delegated from merge.applyupdates: merging of .hgsubstate file
71 in working context, merging context and ancestor context"""
78 in working context, merging context and ancestor context"""
72 if mctx == actx: # backwards?
79 if mctx == actx: # backwards?
73 actx = wctx.p1()
80 actx = wctx.p1()
74 s1 = wctx.substate
81 s1 = wctx.substate
75 s2 = mctx.substate
82 s2 = mctx.substate
76 sa = actx.substate
83 sa = actx.substate
77 sm = {}
84 sm = {}
78
85
79 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
86 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
80
87
81 def debug(s, msg, r=""):
88 def debug(s, msg, r=""):
82 if r:
89 if r:
83 r = "%s:%s:%s" % r
90 r = "%s:%s:%s" % r
84 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
91 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
85
92
86 for s, l in s1.items():
93 for s, l in s1.items():
87 a = sa.get(s, nullstate)
94 a = sa.get(s, nullstate)
88 ld = l # local state with possible dirty flag for compares
95 ld = l # local state with possible dirty flag for compares
89 if wctx.sub(s).dirty():
96 if wctx.sub(s).dirty():
90 ld = (l[0], l[1] + "+")
97 ld = (l[0], l[1] + "+")
91 if wctx == actx: # overwrite
98 if wctx == actx: # overwrite
92 a = ld
99 a = ld
93
100
94 if s in s2:
101 if s in s2:
95 r = s2[s]
102 r = s2[s]
96 if ld == r or r == a: # no change or local is newer
103 if ld == r or r == a: # no change or local is newer
97 sm[s] = l
104 sm[s] = l
98 continue
105 continue
99 elif ld == a: # other side changed
106 elif ld == a: # other side changed
100 debug(s, "other changed, get", r)
107 debug(s, "other changed, get", r)
101 wctx.sub(s).get(r)
108 wctx.sub(s).get(r)
102 sm[s] = r
109 sm[s] = r
103 elif ld[0] != r[0]: # sources differ
110 elif ld[0] != r[0]: # sources differ
104 if repo.ui.promptchoice(
111 if repo.ui.promptchoice(
105 _(' subrepository sources for %s differ\n'
112 _(' subrepository sources for %s differ\n'
106 'use (l)ocal source (%s) or (r)emote source (%s)?')
113 'use (l)ocal source (%s) or (r)emote source (%s)?')
107 % (s, l[0], r[0]),
114 % (s, l[0], r[0]),
108 (_('&Local'), _('&Remote')), 0):
115 (_('&Local'), _('&Remote')), 0):
109 debug(s, "prompt changed, get", r)
116 debug(s, "prompt changed, get", r)
110 wctx.sub(s).get(r)
117 wctx.sub(s).get(r)
111 sm[s] = r
118 sm[s] = r
112 elif ld[1] == a[1]: # local side is unchanged
119 elif ld[1] == a[1]: # local side is unchanged
113 debug(s, "other side changed, get", r)
120 debug(s, "other side changed, get", r)
114 wctx.sub(s).get(r)
121 wctx.sub(s).get(r)
115 sm[s] = r
122 sm[s] = r
116 else:
123 else:
117 debug(s, "both sides changed, merge with", r)
124 debug(s, "both sides changed, merge with", r)
118 wctx.sub(s).merge(r)
125 wctx.sub(s).merge(r)
119 sm[s] = l
126 sm[s] = l
120 elif ld == a: # remote removed, local unchanged
127 elif ld == a: # remote removed, local unchanged
121 debug(s, "remote removed, remove")
128 debug(s, "remote removed, remove")
122 wctx.sub(s).remove()
129 wctx.sub(s).remove()
123 else:
130 else:
124 if repo.ui.promptchoice(
131 if repo.ui.promptchoice(
125 _(' local changed subrepository %s which remote removed\n'
132 _(' local changed subrepository %s which remote removed\n'
126 'use (c)hanged version or (d)elete?') % s,
133 'use (c)hanged version or (d)elete?') % s,
127 (_('&Changed'), _('&Delete')), 0):
134 (_('&Changed'), _('&Delete')), 0):
128 debug(s, "prompt remove")
135 debug(s, "prompt remove")
129 wctx.sub(s).remove()
136 wctx.sub(s).remove()
130
137
131 for s, r in s2.items():
138 for s, r in s2.items():
132 if s in s1:
139 if s in s1:
133 continue
140 continue
134 elif s not in sa:
141 elif s not in sa:
135 debug(s, "remote added, get", r)
142 debug(s, "remote added, get", r)
136 mctx.sub(s).get(r)
143 mctx.sub(s).get(r)
137 sm[s] = r
144 sm[s] = r
138 elif r != sa[s]:
145 elif r != sa[s]:
139 if repo.ui.promptchoice(
146 if repo.ui.promptchoice(
140 _(' remote changed subrepository %s which local removed\n'
147 _(' remote changed subrepository %s which local removed\n'
141 'use (c)hanged version or (d)elete?') % s,
148 'use (c)hanged version or (d)elete?') % s,
142 (_('&Changed'), _('&Delete')), 0) == 0:
149 (_('&Changed'), _('&Delete')), 0) == 0:
143 debug(s, "prompt recreate", r)
150 debug(s, "prompt recreate", r)
144 wctx.sub(s).get(r)
151 wctx.sub(s).get(r)
145 sm[s] = r
152 sm[s] = r
146
153
147 # record merged .hgsubstate
154 # record merged .hgsubstate
148 writestate(repo, sm)
155 writestate(repo, sm)
149
156
150 def relpath(sub):
157 def relpath(sub):
151 """return path to this subrepo as seen from outermost repo"""
158 """return path to this subrepo as seen from outermost repo"""
152 if not hasattr(sub, '_repo'):
159 if not hasattr(sub, '_repo'):
153 return sub._path
160 return sub._path
154 parent = sub._repo
161 parent = sub._repo
155 while hasattr(parent, '_subparent'):
162 while hasattr(parent, '_subparent'):
156 parent = parent._subparent
163 parent = parent._subparent
157 return sub._repo.root[len(parent.root)+1:]
164 return sub._repo.root[len(parent.root)+1:]
158
165
159 def _abssource(repo, push=False):
166 def _abssource(repo, push=False):
160 """return pull/push path of repo - either based on parent repo
167 """return pull/push path of repo - either based on parent repo
161 .hgsub info or on the subrepos own config"""
168 .hgsub info or on the subrepos own config"""
162 if hasattr(repo, '_subparent'):
169 if hasattr(repo, '_subparent'):
163 source = repo._subsource
170 source = repo._subsource
164 if source.startswith('/') or '://' in source:
171 if source.startswith('/') or '://' in source:
165 return source
172 return source
166 parent = _abssource(repo._subparent, push)
173 parent = _abssource(repo._subparent, push)
167 if '://' in parent:
174 if '://' in parent:
168 if parent[-1] == '/':
175 if parent[-1] == '/':
169 parent = parent[:-1]
176 parent = parent[:-1]
170 r = urlparse.urlparse(parent + '/' + source)
177 r = urlparse.urlparse(parent + '/' + source)
171 r = urlparse.urlunparse((r[0], r[1],
178 r = urlparse.urlunparse((r[0], r[1],
172 posixpath.normpath(r[2]),
179 posixpath.normpath(r[2]),
173 r[3], r[4], r[5]))
180 r[3], r[4], r[5]))
174 return r
181 return r
175 return posixpath.normpath(os.path.join(parent, repo._subsource))
182 return posixpath.normpath(os.path.join(parent, repo._subsource))
176 if push and repo.ui.config('paths', 'default-push'):
183 if push and repo.ui.config('paths', 'default-push'):
177 return repo.ui.config('paths', 'default-push', repo.root)
184 return repo.ui.config('paths', 'default-push', repo.root)
178 return repo.ui.config('paths', 'default', repo.root)
185 return repo.ui.config('paths', 'default', repo.root)
179
186
180 def subrepo(ctx, path):
187 def subrepo(ctx, path):
181 """return instance of the right subrepo class for subrepo in path"""
188 """return instance of the right subrepo class for subrepo in path"""
182 # subrepo inherently violates our import layering rules
189 # subrepo inherently violates our import layering rules
183 # because it wants to make repo objects from deep inside the stack
190 # because it wants to make repo objects from deep inside the stack
184 # so we manually delay the circular imports to not break
191 # so we manually delay the circular imports to not break
185 # scripts that don't use our demand-loading
192 # scripts that don't use our demand-loading
186 global hg
193 global hg
187 import hg as h
194 import hg as h
188 hg = h
195 hg = h
189
196
190 util.path_auditor(ctx._repo.root)(path)
197 util.path_auditor(ctx._repo.root)(path)
191 state = ctx.substate.get(path, nullstate)
198 state = ctx.substate.get(path, nullstate)
192 if state[2] not in types:
199 if state[2] not in types:
193 raise util.Abort(_('unknown subrepo type %s') % state[2])
200 raise util.Abort(_('unknown subrepo type %s') % state[2])
194 return types[state[2]](ctx, path, state[:2])
201 return types[state[2]](ctx, path, state[:2])
195
202
196 # subrepo classes need to implement the following abstract class:
203 # subrepo classes need to implement the following abstract class:
197
204
198 class abstractsubrepo(object):
205 class abstractsubrepo(object):
199
206
200 def dirty(self):
207 def dirty(self):
201 """returns true if the dirstate of the subrepo does not match
208 """returns true if the dirstate of the subrepo does not match
202 current stored state
209 current stored state
203 """
210 """
204 raise NotImplementedError
211 raise NotImplementedError
205
212
206 def commit(self, text, user, date):
213 def commit(self, text, user, date):
207 """commit the current changes to the subrepo with the given
214 """commit the current changes to the subrepo with the given
208 log message. Use given user and date if possible. Return the
215 log message. Use given user and date if possible. Return the
209 new state of the subrepo.
216 new state of the subrepo.
210 """
217 """
211 raise NotImplementedError
218 raise NotImplementedError
212
219
213 def remove(self):
220 def remove(self):
214 """remove the subrepo
221 """remove the subrepo
215
222
216 (should verify the dirstate is not dirty first)
223 (should verify the dirstate is not dirty first)
217 """
224 """
218 raise NotImplementedError
225 raise NotImplementedError
219
226
220 def get(self, state):
227 def get(self, state):
221 """run whatever commands are needed to put the subrepo into
228 """run whatever commands are needed to put the subrepo into
222 this state
229 this state
223 """
230 """
224 raise NotImplementedError
231 raise NotImplementedError
225
232
226 def merge(self, state):
233 def merge(self, state):
227 """merge currently-saved state with the new state."""
234 """merge currently-saved state with the new state."""
228 raise NotImplementedError
235 raise NotImplementedError
229
236
230 def push(self, force):
237 def push(self, force):
231 """perform whatever action is analogous to 'hg push'
238 """perform whatever action is analogous to 'hg push'
232
239
233 This may be a no-op on some systems.
240 This may be a no-op on some systems.
234 """
241 """
235 raise NotImplementedError
242 raise NotImplementedError
236
243
237
244
238 class hgsubrepo(abstractsubrepo):
245 class hgsubrepo(abstractsubrepo):
239 def __init__(self, ctx, path, state):
246 def __init__(self, ctx, path, state):
240 self._path = path
247 self._path = path
241 self._state = state
248 self._state = state
242 r = ctx._repo
249 r = ctx._repo
243 root = r.wjoin(path)
250 root = r.wjoin(path)
244 create = False
251 create = False
245 if not os.path.exists(os.path.join(root, '.hg')):
252 if not os.path.exists(os.path.join(root, '.hg')):
246 create = True
253 create = True
247 util.makedirs(root)
254 util.makedirs(root)
248 self._repo = hg.repository(r.ui, root, create=create)
255 self._repo = hg.repository(r.ui, root, create=create)
249 self._repo._subparent = r
256 self._repo._subparent = r
250 self._repo._subsource = state[0]
257 self._repo._subsource = state[0]
251
258
252 if create:
259 if create:
253 fp = self._repo.opener("hgrc", "w", text=True)
260 fp = self._repo.opener("hgrc", "w", text=True)
254 fp.write('[paths]\n')
261 fp.write('[paths]\n')
255
262
256 def addpathconfig(key, value):
263 def addpathconfig(key, value):
257 fp.write('%s = %s\n' % (key, value))
264 fp.write('%s = %s\n' % (key, value))
258 self._repo.ui.setconfig('paths', key, value)
265 self._repo.ui.setconfig('paths', key, value)
259
266
260 defpath = _abssource(self._repo)
267 defpath = _abssource(self._repo)
261 defpushpath = _abssource(self._repo, True)
268 defpushpath = _abssource(self._repo, True)
262 addpathconfig('default', defpath)
269 addpathconfig('default', defpath)
263 if defpath != defpushpath:
270 if defpath != defpushpath:
264 addpathconfig('default-push', defpushpath)
271 addpathconfig('default-push', defpushpath)
265 fp.close()
272 fp.close()
266
273
267 def dirty(self):
274 def dirty(self):
268 r = self._state[1]
275 r = self._state[1]
269 if r == '':
276 if r == '':
270 return True
277 return True
271 w = self._repo[None]
278 w = self._repo[None]
272 if w.p1() != self._repo[r]: # version checked out change
279 if w.p1() != self._repo[r]: # version checked out change
273 return True
280 return True
274 return w.dirty() # working directory changed
281 return w.dirty() # working directory changed
275
282
276 def commit(self, text, user, date):
283 def commit(self, text, user, date):
277 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
284 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
278 n = self._repo.commit(text, user, date)
285 n = self._repo.commit(text, user, date)
279 if not n:
286 if not n:
280 return self._repo['.'].hex() # different version checked out
287 return self._repo['.'].hex() # different version checked out
281 return node.hex(n)
288 return node.hex(n)
282
289
283 def remove(self):
290 def remove(self):
284 # we can't fully delete the repository as it may contain
291 # we can't fully delete the repository as it may contain
285 # local-only history
292 # local-only history
286 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
293 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
287 hg.clean(self._repo, node.nullid, False)
294 hg.clean(self._repo, node.nullid, False)
288
295
289 def _get(self, state):
296 def _get(self, state):
290 source, revision, kind = state
297 source, revision, kind = state
291 try:
298 try:
292 self._repo.lookup(revision)
299 self._repo.lookup(revision)
293 except error.RepoError:
300 except error.RepoError:
294 self._repo._subsource = source
301 self._repo._subsource = source
295 srcurl = _abssource(self._repo)
302 srcurl = _abssource(self._repo)
296 self._repo.ui.status(_('pulling subrepo %s from %s\n')
303 self._repo.ui.status(_('pulling subrepo %s from %s\n')
297 % (relpath(self), srcurl))
304 % (relpath(self), srcurl))
298 other = hg.repository(self._repo.ui, srcurl)
305 other = hg.repository(self._repo.ui, srcurl)
299 self._repo.pull(other)
306 self._repo.pull(other)
300
307
301 def get(self, state):
308 def get(self, state):
302 self._get(state)
309 self._get(state)
303 source, revision, kind = state
310 source, revision, kind = state
304 self._repo.ui.debug("getting subrepo %s\n" % self._path)
311 self._repo.ui.debug("getting subrepo %s\n" % self._path)
305 hg.clean(self._repo, revision, False)
312 hg.clean(self._repo, revision, False)
306
313
307 def merge(self, state):
314 def merge(self, state):
308 self._get(state)
315 self._get(state)
309 cur = self._repo['.']
316 cur = self._repo['.']
310 dst = self._repo[state[1]]
317 dst = self._repo[state[1]]
311 anc = dst.ancestor(cur)
318 anc = dst.ancestor(cur)
312 if anc == cur:
319 if anc == cur:
313 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
320 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
314 hg.update(self._repo, state[1])
321 hg.update(self._repo, state[1])
315 elif anc == dst:
322 elif anc == dst:
316 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
323 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
317 else:
324 else:
318 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
325 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
319 hg.merge(self._repo, state[1], remind=False)
326 hg.merge(self._repo, state[1], remind=False)
320
327
321 def push(self, force):
328 def push(self, force):
322 # push subrepos depth-first for coherent ordering
329 # push subrepos depth-first for coherent ordering
323 c = self._repo['']
330 c = self._repo['']
324 subs = c.substate # only repos that are committed
331 subs = c.substate # only repos that are committed
325 for s in sorted(subs):
332 for s in sorted(subs):
326 if not c.sub(s).push(force):
333 if not c.sub(s).push(force):
327 return False
334 return False
328
335
329 dsturl = _abssource(self._repo, True)
336 dsturl = _abssource(self._repo, True)
330 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
337 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
331 (relpath(self), dsturl))
338 (relpath(self), dsturl))
332 other = hg.repository(self._repo.ui, dsturl)
339 other = hg.repository(self._repo.ui, dsturl)
333 return self._repo.push(other, force)
340 return self._repo.push(other, force)
334
341
335 class svnsubrepo(abstractsubrepo):
342 class svnsubrepo(abstractsubrepo):
336 def __init__(self, ctx, path, state):
343 def __init__(self, ctx, path, state):
337 self._path = path
344 self._path = path
338 self._state = state
345 self._state = state
339 self._ctx = ctx
346 self._ctx = ctx
340 self._ui = ctx._repo.ui
347 self._ui = ctx._repo.ui
341
348
342 def _svncommand(self, commands, filename=''):
349 def _svncommand(self, commands, filename=''):
343 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
350 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
344 cmd = ['svn'] + commands + [path]
351 cmd = ['svn'] + commands + [path]
345 cmd = [util.shellquote(arg) for arg in cmd]
352 cmd = [util.shellquote(arg) for arg in cmd]
346 cmd = util.quotecommand(' '.join(cmd))
353 cmd = util.quotecommand(' '.join(cmd))
347 env = dict(os.environ)
354 env = dict(os.environ)
348 # Avoid localized output, preserve current locale for everything else.
355 # Avoid localized output, preserve current locale for everything else.
349 env['LC_MESSAGES'] = 'C'
356 env['LC_MESSAGES'] = 'C'
350 write, read, err = util.popen3(cmd, env=env, newlines=True)
357 write, read, err = util.popen3(cmd, env=env, newlines=True)
351 retdata = read.read()
358 retdata = read.read()
352 err = err.read().strip()
359 err = err.read().strip()
353 if err:
360 if err:
354 raise util.Abort(err)
361 raise util.Abort(err)
355 return retdata
362 return retdata
356
363
357 def _wcrev(self):
364 def _wcrev(self):
358 output = self._svncommand(['info', '--xml'])
365 output = self._svncommand(['info', '--xml'])
359 doc = xml.dom.minidom.parseString(output)
366 doc = xml.dom.minidom.parseString(output)
360 entries = doc.getElementsByTagName('entry')
367 entries = doc.getElementsByTagName('entry')
361 if not entries:
368 if not entries:
362 return 0
369 return 0
363 return int(entries[0].getAttribute('revision') or 0)
370 return int(entries[0].getAttribute('revision') or 0)
364
371
365 def _wcchanged(self):
372 def _wcchanged(self):
366 """Return (changes, extchanges) where changes is True
373 """Return (changes, extchanges) where changes is True
367 if the working directory was changed, and extchanges is
374 if the working directory was changed, and extchanges is
368 True if any of these changes concern an external entry.
375 True if any of these changes concern an external entry.
369 """
376 """
370 output = self._svncommand(['status', '--xml'])
377 output = self._svncommand(['status', '--xml'])
371 externals, changes = [], []
378 externals, changes = [], []
372 doc = xml.dom.minidom.parseString(output)
379 doc = xml.dom.minidom.parseString(output)
373 for e in doc.getElementsByTagName('entry'):
380 for e in doc.getElementsByTagName('entry'):
374 s = e.getElementsByTagName('wc-status')
381 s = e.getElementsByTagName('wc-status')
375 if not s:
382 if not s:
376 continue
383 continue
377 item = s[0].getAttribute('item')
384 item = s[0].getAttribute('item')
378 props = s[0].getAttribute('props')
385 props = s[0].getAttribute('props')
379 path = e.getAttribute('path')
386 path = e.getAttribute('path')
380 if item == 'external':
387 if item == 'external':
381 externals.append(path)
388 externals.append(path)
382 if (item not in ('', 'normal', 'unversioned', 'external')
389 if (item not in ('', 'normal', 'unversioned', 'external')
383 or props not in ('', 'none')):
390 or props not in ('', 'none')):
384 changes.append(path)
391 changes.append(path)
385 for path in changes:
392 for path in changes:
386 for ext in externals:
393 for ext in externals:
387 if path == ext or path.startswith(ext + os.sep):
394 if path == ext or path.startswith(ext + os.sep):
388 return True, True
395 return True, True
389 return bool(changes), False
396 return bool(changes), False
390
397
391 def dirty(self):
398 def dirty(self):
392 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
399 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
393 return False
400 return False
394 return True
401 return True
395
402
396 def commit(self, text, user, date):
403 def commit(self, text, user, date):
397 # user and date are out of our hands since svn is centralized
404 # user and date are out of our hands since svn is centralized
398 changed, extchanged = self._wcchanged()
405 changed, extchanged = self._wcchanged()
399 if not changed:
406 if not changed:
400 return self._wcrev()
407 return self._wcrev()
401 if extchanged:
408 if extchanged:
402 # Do not try to commit externals
409 # Do not try to commit externals
403 raise util.Abort(_('cannot commit svn externals'))
410 raise util.Abort(_('cannot commit svn externals'))
404 commitinfo = self._svncommand(['commit', '-m', text])
411 commitinfo = self._svncommand(['commit', '-m', text])
405 self._ui.status(commitinfo)
412 self._ui.status(commitinfo)
406 newrev = re.search('Committed revision ([\d]+).', commitinfo)
413 newrev = re.search('Committed revision ([\d]+).', commitinfo)
407 if not newrev:
414 if not newrev:
408 raise util.Abort(commitinfo.splitlines()[-1])
415 raise util.Abort(commitinfo.splitlines()[-1])
409 newrev = newrev.groups()[0]
416 newrev = newrev.groups()[0]
410 self._ui.status(self._svncommand(['update', '-r', newrev]))
417 self._ui.status(self._svncommand(['update', '-r', newrev]))
411 return newrev
418 return newrev
412
419
413 def remove(self):
420 def remove(self):
414 if self.dirty():
421 if self.dirty():
415 self._ui.warn(_('not removing repo %s because '
422 self._ui.warn(_('not removing repo %s because '
416 'it has changes.\n' % self._path))
423 'it has changes.\n' % self._path))
417 return
424 return
418 self._ui.note(_('removing subrepo %s\n') % self._path)
425 self._ui.note(_('removing subrepo %s\n') % self._path)
419 shutil.rmtree(self._ctx.repo.join(self._path))
426 shutil.rmtree(self._ctx.repo.join(self._path))
420
427
421 def get(self, state):
428 def get(self, state):
422 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
429 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
423 if not re.search('Checked out revision [\d]+.', status):
430 if not re.search('Checked out revision [\d]+.', status):
424 raise util.Abort(status.splitlines()[-1])
431 raise util.Abort(status.splitlines()[-1])
425 self._ui.status(status)
432 self._ui.status(status)
426
433
427 def merge(self, state):
434 def merge(self, state):
428 old = int(self._state[1])
435 old = int(self._state[1])
429 new = int(state[1])
436 new = int(state[1])
430 if new > old:
437 if new > old:
431 self.get(state)
438 self.get(state)
432
439
433 def push(self, force):
440 def push(self, force):
434 # push is a no-op for SVN
441 # push is a no-op for SVN
435 return True
442 return True
436
443
437 types = {
444 types = {
438 'hg': hgsubrepo,
445 'hg': hgsubrepo,
439 'svn': svnsubrepo,
446 'svn': svnsubrepo,
440 }
447 }
@@ -1,35 +1,33 b''
1 $ hg init outer
1 $ hg init outer
2 $ cd outer
2 $ cd outer
3
3
4 hg debugsub with no remapping
4 hg debugsub with no remapping
5
5
6 $ echo 'sub = http://example.net/libfoo' > .hgsub
6 $ echo 'sub = http://example.net/libfoo' > .hgsub
7 $ hg add .hgsub
7 $ hg add .hgsub
8
8
9 $ hg debugsub
9 $ hg debugsub
10 path sub
10 path sub
11 source http://example.net/libfoo
11 source http://example.net/libfoo
12 revision
12 revision
13
13
14 hg debugsub with remapping
14 hg debugsub with remapping
15
15
16 $ cat > .hg/hgrc <<EOF
16 $ echo '[subpaths]' > .hg/hgrc
17 > [subpaths]
17 $ echo 'http://example.net/lib(.*) = C:\libs\\1-lib\' >> .hg/hgrc
18 > http://example.net = ssh://localhost
19 > EOF
20
18
21 $ hg debugsub
19 $ hg debugsub
22 path sub
20 path sub
23 source ssh://localhost/libfoo
21 source C:\libs\foo-lib\
24 revision
22 revision
25
23
26 test bad subpaths pattern
24 test bad subpaths pattern
27
25
28 $ cat > .hg/hgrc <<EOF
26 $ cat > .hg/hgrc <<EOF
29 > [subpaths]
27 > [subpaths]
30 > .* = \1
28 > .* = \1
31 > EOF
29 > EOF
32 $ hg debugsub
30 $ hg debugsub
33 abort: bad subrepository pattern in .*/test-subrepo-paths.t/outer/.hg/hgrc:2: invalid group reference
31 abort: bad subrepository pattern in .*/test-subrepo-paths.t/outer/.hg/hgrc:2: invalid group reference
34
32
35 $ exit 0
33 $ exit 0
General Comments 0
You need to be logged in to leave comments. Login now