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