##// END OF EJS Templates
subrepos: let caller specify a filename for SVN commands
Martin Geisler -
r11560:ea2cdee9 default
parent child Browse files
Show More
@@ -1,419 +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 abstract class:
175 # subrepo classes need to implement the following abstract class:
176
176
177 class abstractsubrepo(object):
177 class abstractsubrepo(object):
178
178
179 def dirty(self):
179 def dirty(self):
180 """returns true if the dirstate of the subrepo does not match
180 """returns true if the dirstate of the subrepo does not match
181 current stored state
181 current stored state
182 """
182 """
183 raise NotImplementedError
183 raise NotImplementedError
184
184
185 def commit(self, text, user, date):
185 def commit(self, text, user, date):
186 """commit the current changes to the subrepo with the given
186 """commit the current changes to the subrepo with the given
187 log message. Use given user and date if possible. Return the
187 log message. Use given user and date if possible. Return the
188 new state of the subrepo.
188 new state of the subrepo.
189 """
189 """
190 raise NotImplementedError
190 raise NotImplementedError
191
191
192 def remove(self):
192 def remove(self):
193 """remove the subrepo
193 """remove the subrepo
194
194
195 (should verify the dirstate is not dirty first)
195 (should verify the dirstate is not dirty first)
196 """
196 """
197 raise NotImplementedError
197 raise NotImplementedError
198
198
199 def get(self, state):
199 def get(self, state):
200 """run whatever commands are needed to put the subrepo into
200 """run whatever commands are needed to put the subrepo into
201 this state
201 this state
202 """
202 """
203 raise NotImplementedError
203 raise NotImplementedError
204
204
205 def merge(self, state):
205 def merge(self, state):
206 """merge currently-saved state with the new state."""
206 """merge currently-saved state with the new state."""
207 raise NotImplementedError
207 raise NotImplementedError
208
208
209 def push(self, force):
209 def push(self, force):
210 """perform whatever action is analagous to 'hg push'
210 """perform whatever action is analagous to 'hg push'
211
211
212 This may be a no-op on some systems.
212 This may be a no-op on some systems.
213 """
213 """
214 raise NotImplementedError
214 raise NotImplementedError
215
215
216
216
217 class hgsubrepo(abstractsubrepo):
217 class hgsubrepo(abstractsubrepo):
218 def __init__(self, ctx, path, state):
218 def __init__(self, ctx, path, state):
219 self._path = path
219 self._path = path
220 self._state = state
220 self._state = state
221 r = ctx._repo
221 r = ctx._repo
222 root = r.wjoin(path)
222 root = r.wjoin(path)
223 create = False
223 create = False
224 if not os.path.exists(os.path.join(root, '.hg')):
224 if not os.path.exists(os.path.join(root, '.hg')):
225 create = True
225 create = True
226 util.makedirs(root)
226 util.makedirs(root)
227 self._repo = hg.repository(r.ui, root, create=create)
227 self._repo = hg.repository(r.ui, root, create=create)
228 self._repo._subparent = r
228 self._repo._subparent = r
229 self._repo._subsource = state[0]
229 self._repo._subsource = state[0]
230
230
231 if create:
231 if create:
232 fp = self._repo.opener("hgrc", "w", text=True)
232 fp = self._repo.opener("hgrc", "w", text=True)
233 fp.write('[paths]\n')
233 fp.write('[paths]\n')
234
234
235 def addpathconfig(key, value):
235 def addpathconfig(key, value):
236 fp.write('%s = %s\n' % (key, value))
236 fp.write('%s = %s\n' % (key, value))
237 self._repo.ui.setconfig('paths', key, value)
237 self._repo.ui.setconfig('paths', key, value)
238
238
239 defpath = _abssource(self._repo)
239 defpath = _abssource(self._repo)
240 defpushpath = _abssource(self._repo, True)
240 defpushpath = _abssource(self._repo, True)
241 addpathconfig('default', defpath)
241 addpathconfig('default', defpath)
242 if defpath != defpushpath:
242 if defpath != defpushpath:
243 addpathconfig('default-push', defpushpath)
243 addpathconfig('default-push', defpushpath)
244 fp.close()
244 fp.close()
245
245
246 def dirty(self):
246 def dirty(self):
247 r = self._state[1]
247 r = self._state[1]
248 if r == '':
248 if r == '':
249 return True
249 return True
250 w = self._repo[None]
250 w = self._repo[None]
251 if w.p1() != self._repo[r]: # version checked out change
251 if w.p1() != self._repo[r]: # version checked out change
252 return True
252 return True
253 return w.dirty() # working directory changed
253 return w.dirty() # working directory changed
254
254
255 def commit(self, text, user, date):
255 def commit(self, text, user, date):
256 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
256 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
257 n = self._repo.commit(text, user, date)
257 n = self._repo.commit(text, user, date)
258 if not n:
258 if not n:
259 return self._repo['.'].hex() # different version checked out
259 return self._repo['.'].hex() # different version checked out
260 return node.hex(n)
260 return node.hex(n)
261
261
262 def remove(self):
262 def remove(self):
263 # we can't fully delete the repository as it may contain
263 # we can't fully delete the repository as it may contain
264 # local-only history
264 # local-only history
265 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
265 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
266 hg.clean(self._repo, node.nullid, False)
266 hg.clean(self._repo, node.nullid, False)
267
267
268 def _get(self, state):
268 def _get(self, state):
269 source, revision, kind = state
269 source, revision, kind = state
270 try:
270 try:
271 self._repo.lookup(revision)
271 self._repo.lookup(revision)
272 except error.RepoError:
272 except error.RepoError:
273 self._repo._subsource = source
273 self._repo._subsource = source
274 srcurl = _abssource(self._repo)
274 srcurl = _abssource(self._repo)
275 self._repo.ui.status(_('pulling subrepo %s from %s\n')
275 self._repo.ui.status(_('pulling subrepo %s from %s\n')
276 % (relpath(self), srcurl))
276 % (relpath(self), srcurl))
277 other = hg.repository(self._repo.ui, srcurl)
277 other = hg.repository(self._repo.ui, srcurl)
278 self._repo.pull(other)
278 self._repo.pull(other)
279
279
280 def get(self, state):
280 def get(self, state):
281 self._get(state)
281 self._get(state)
282 source, revision, kind = state
282 source, revision, kind = state
283 self._repo.ui.debug("getting subrepo %s\n" % self._path)
283 self._repo.ui.debug("getting subrepo %s\n" % self._path)
284 hg.clean(self._repo, revision, False)
284 hg.clean(self._repo, revision, False)
285
285
286 def merge(self, state):
286 def merge(self, state):
287 self._get(state)
287 self._get(state)
288 cur = self._repo['.']
288 cur = self._repo['.']
289 dst = self._repo[state[1]]
289 dst = self._repo[state[1]]
290 anc = dst.ancestor(cur)
290 anc = dst.ancestor(cur)
291 if anc == cur:
291 if anc == cur:
292 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
292 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
293 hg.update(self._repo, state[1])
293 hg.update(self._repo, state[1])
294 elif anc == dst:
294 elif anc == dst:
295 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
295 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
296 else:
296 else:
297 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
297 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
298 hg.merge(self._repo, state[1], remind=False)
298 hg.merge(self._repo, state[1], remind=False)
299
299
300 def push(self, force):
300 def push(self, force):
301 # push subrepos depth-first for coherent ordering
301 # push subrepos depth-first for coherent ordering
302 c = self._repo['']
302 c = self._repo['']
303 subs = c.substate # only repos that are committed
303 subs = c.substate # only repos that are committed
304 for s in sorted(subs):
304 for s in sorted(subs):
305 if not c.sub(s).push(force):
305 if not c.sub(s).push(force):
306 return False
306 return False
307
307
308 dsturl = _abssource(self._repo, True)
308 dsturl = _abssource(self._repo, True)
309 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
309 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
310 (relpath(self), dsturl))
310 (relpath(self), dsturl))
311 other = hg.repository(self._repo.ui, dsturl)
311 other = hg.repository(self._repo.ui, dsturl)
312 return self._repo.push(other, force)
312 return self._repo.push(other, force)
313
313
314 class svnsubrepo(abstractsubrepo):
314 class svnsubrepo(abstractsubrepo):
315 def __init__(self, ctx, path, state):
315 def __init__(self, ctx, path, state):
316 self._path = path
316 self._path = path
317 self._state = state
317 self._state = state
318 self._ctx = ctx
318 self._ctx = ctx
319 self._ui = ctx._repo.ui
319 self._ui = ctx._repo.ui
320
320
321 def _svncommand(self, commands):
321 def _svncommand(self, commands, filename=''):
322 path = os.path.join(self._ctx._repo.origroot, self._path)
322 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
323 cmd = ['svn'] + commands + [path]
323 cmd = ['svn'] + commands + [path]
324 cmd = [util.shellquote(arg) for arg in cmd]
324 cmd = [util.shellquote(arg) for arg in cmd]
325 cmd = util.quotecommand(' '.join(cmd))
325 cmd = util.quotecommand(' '.join(cmd))
326 env = dict(os.environ)
326 env = dict(os.environ)
327 # Avoid localized output, preserve current locale for everything else.
327 # Avoid localized output, preserve current locale for everything else.
328 env['LC_MESSAGES'] = 'C'
328 env['LC_MESSAGES'] = 'C'
329 write, read, err = util.popen3(cmd, env=env, newlines=True)
329 write, read, err = util.popen3(cmd, env=env, newlines=True)
330 retdata = read.read()
330 retdata = read.read()
331 err = err.read().strip()
331 err = err.read().strip()
332 if err:
332 if err:
333 raise util.Abort(err)
333 raise util.Abort(err)
334 return retdata
334 return retdata
335
335
336 def _wcrev(self):
336 def _wcrev(self):
337 output = self._svncommand(['info', '--xml'])
337 output = self._svncommand(['info', '--xml'])
338 doc = xml.dom.minidom.parseString(output)
338 doc = xml.dom.minidom.parseString(output)
339 entries = doc.getElementsByTagName('entry')
339 entries = doc.getElementsByTagName('entry')
340 if not entries:
340 if not entries:
341 return 0
341 return 0
342 return int(entries[0].getAttribute('revision') or 0)
342 return int(entries[0].getAttribute('revision') or 0)
343
343
344 def _wcchanged(self):
344 def _wcchanged(self):
345 """Return (changes, extchanges) where changes is True
345 """Return (changes, extchanges) where changes is True
346 if the working directory was changed, and extchanges is
346 if the working directory was changed, and extchanges is
347 True if any of these changes concern an external entry.
347 True if any of these changes concern an external entry.
348 """
348 """
349 output = self._svncommand(['status', '--xml'])
349 output = self._svncommand(['status', '--xml'])
350 externals, changes = [], []
350 externals, changes = [], []
351 doc = xml.dom.minidom.parseString(output)
351 doc = xml.dom.minidom.parseString(output)
352 for e in doc.getElementsByTagName('entry'):
352 for e in doc.getElementsByTagName('entry'):
353 s = e.getElementsByTagName('wc-status')
353 s = e.getElementsByTagName('wc-status')
354 if not s:
354 if not s:
355 continue
355 continue
356 item = s[0].getAttribute('item')
356 item = s[0].getAttribute('item')
357 props = s[0].getAttribute('props')
357 props = s[0].getAttribute('props')
358 path = e.getAttribute('path')
358 path = e.getAttribute('path')
359 if item == 'external':
359 if item == 'external':
360 externals.append(path)
360 externals.append(path)
361 if (item not in ('', 'normal', 'unversioned', 'external')
361 if (item not in ('', 'normal', 'unversioned', 'external')
362 or props not in ('', 'none')):
362 or props not in ('', 'none')):
363 changes.append(path)
363 changes.append(path)
364 for path in changes:
364 for path in changes:
365 for ext in externals:
365 for ext in externals:
366 if path == ext or path.startswith(ext + os.sep):
366 if path == ext or path.startswith(ext + os.sep):
367 return True, True
367 return True, True
368 return bool(changes), False
368 return bool(changes), False
369
369
370 def dirty(self):
370 def dirty(self):
371 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
371 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
372 return False
372 return False
373 return True
373 return True
374
374
375 def commit(self, text, user, date):
375 def commit(self, text, user, date):
376 # 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
377 changed, extchanged = self._wcchanged()
377 changed, extchanged = self._wcchanged()
378 if not changed:
378 if not changed:
379 return self._wcrev()
379 return self._wcrev()
380 if extchanged:
380 if extchanged:
381 # Do not try to commit externals
381 # Do not try to commit externals
382 raise util.Abort(_('cannot commit svn externals'))
382 raise util.Abort(_('cannot commit svn externals'))
383 commitinfo = self._svncommand(['commit', '-m', text])
383 commitinfo = self._svncommand(['commit', '-m', text])
384 self._ui.status(commitinfo)
384 self._ui.status(commitinfo)
385 newrev = re.search('Committed revision ([\d]+).', commitinfo)
385 newrev = re.search('Committed revision ([\d]+).', commitinfo)
386 if not newrev:
386 if not newrev:
387 raise util.Abort(commitinfo.splitlines()[-1])
387 raise util.Abort(commitinfo.splitlines()[-1])
388 newrev = newrev.groups()[0]
388 newrev = newrev.groups()[0]
389 self._ui.status(self._svncommand(['update', '-r', newrev]))
389 self._ui.status(self._svncommand(['update', '-r', newrev]))
390 return newrev
390 return newrev
391
391
392 def remove(self):
392 def remove(self):
393 if self.dirty():
393 if self.dirty():
394 self._ui.warn(_('not removing repo %s because '
394 self._ui.warn(_('not removing repo %s because '
395 'it has changes.\n' % self._path))
395 'it has changes.\n' % self._path))
396 return
396 return
397 self._ui.note(_('removing subrepo %s\n') % self._path)
397 self._ui.note(_('removing subrepo %s\n') % self._path)
398 shutil.rmtree(self._ctx.repo.join(self._path))
398 shutil.rmtree(self._ctx.repo.join(self._path))
399
399
400 def get(self, state):
400 def get(self, state):
401 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
401 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
402 if not re.search('Checked out revision [\d]+.', status):
402 if not re.search('Checked out revision [\d]+.', status):
403 raise util.Abort(status.splitlines()[-1])
403 raise util.Abort(status.splitlines()[-1])
404 self._ui.status(status)
404 self._ui.status(status)
405
405
406 def merge(self, state):
406 def merge(self, state):
407 old = int(self._state[1])
407 old = int(self._state[1])
408 new = int(state[1])
408 new = int(state[1])
409 if new > old:
409 if new > old:
410 self.get(state)
410 self.get(state)
411
411
412 def push(self, force):
412 def push(self, force):
413 # push is a no-op for SVN
413 # push is a no-op for SVN
414 return True
414 return True
415
415
416 types = {
416 types = {
417 'hg': hgsubrepo,
417 'hg': hgsubrepo,
418 'svn': svnsubrepo,
418 'svn': svnsubrepo,
419 }
419 }
General Comments 0
You need to be logged in to leave comments. Login now