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