##// END OF EJS Templates
subrepo: introduce files and filedata methods for subrepo classes
Martin Geisler -
r12322:510afb31 default
parent child Browse files
Show More
@@ -1,514 +1,550 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, cmdutil
10 import config, util, node, error, cmdutil
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 itersubrepos(ctx1, ctx2):
187 def itersubrepos(ctx1, ctx2):
188 """find subrepos in ctx1 or ctx2"""
188 """find subrepos in ctx1 or ctx2"""
189 # Create a (subpath, ctx) mapping where we prefer subpaths from
189 # Create a (subpath, ctx) mapping where we prefer subpaths from
190 # ctx1. The subpaths from ctx2 are important when the .hgsub file
190 # ctx1. The subpaths from ctx2 are important when the .hgsub file
191 # has been modified (in ctx2) but not yet committed (in ctx1).
191 # has been modified (in ctx2) but not yet committed (in ctx1).
192 subpaths = dict.fromkeys(ctx2.substate, ctx2)
192 subpaths = dict.fromkeys(ctx2.substate, ctx2)
193 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
193 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
194 for subpath, ctx in sorted(subpaths.iteritems()):
194 for subpath, ctx in sorted(subpaths.iteritems()):
195 yield subpath, ctx.sub(subpath)
195 yield subpath, ctx.sub(subpath)
196
196
197 def subrepo(ctx, path):
197 def subrepo(ctx, path):
198 """return instance of the right subrepo class for subrepo in path"""
198 """return instance of the right subrepo class for subrepo in path"""
199 # subrepo inherently violates our import layering rules
199 # subrepo inherently violates our import layering rules
200 # because it wants to make repo objects from deep inside the stack
200 # because it wants to make repo objects from deep inside the stack
201 # so we manually delay the circular imports to not break
201 # so we manually delay the circular imports to not break
202 # scripts that don't use our demand-loading
202 # scripts that don't use our demand-loading
203 global hg
203 global hg
204 import hg as h
204 import hg as h
205 hg = h
205 hg = h
206
206
207 util.path_auditor(ctx._repo.root)(path)
207 util.path_auditor(ctx._repo.root)(path)
208 state = ctx.substate.get(path, nullstate)
208 state = ctx.substate.get(path, nullstate)
209 if state[2] not in types:
209 if state[2] not in types:
210 raise util.Abort(_('unknown subrepo type %s') % state[2])
210 raise util.Abort(_('unknown subrepo type %s') % state[2])
211 return types[state[2]](ctx, path, state[:2])
211 return types[state[2]](ctx, path, state[:2])
212
212
213 # subrepo classes need to implement the following abstract class:
213 # subrepo classes need to implement the following abstract class:
214
214
215 class abstractsubrepo(object):
215 class abstractsubrepo(object):
216
216
217 def dirty(self):
217 def dirty(self):
218 """returns true if the dirstate of the subrepo does not match
218 """returns true if the dirstate of the subrepo does not match
219 current stored state
219 current stored state
220 """
220 """
221 raise NotImplementedError
221 raise NotImplementedError
222
222
223 def checknested(path):
223 def checknested(path):
224 """check if path is a subrepository within this repository"""
224 """check if path is a subrepository within this repository"""
225 return False
225 return False
226
226
227 def commit(self, text, user, date):
227 def commit(self, text, user, date):
228 """commit the current changes to the subrepo with the given
228 """commit the current changes to the subrepo with the given
229 log message. Use given user and date if possible. Return the
229 log message. Use given user and date if possible. Return the
230 new state of the subrepo.
230 new state of the subrepo.
231 """
231 """
232 raise NotImplementedError
232 raise NotImplementedError
233
233
234 def remove(self):
234 def remove(self):
235 """remove the subrepo
235 """remove the subrepo
236
236
237 (should verify the dirstate is not dirty first)
237 (should verify the dirstate is not dirty first)
238 """
238 """
239 raise NotImplementedError
239 raise NotImplementedError
240
240
241 def get(self, state):
241 def get(self, state):
242 """run whatever commands are needed to put the subrepo into
242 """run whatever commands are needed to put the subrepo into
243 this state
243 this state
244 """
244 """
245 raise NotImplementedError
245 raise NotImplementedError
246
246
247 def merge(self, state):
247 def merge(self, state):
248 """merge currently-saved state with the new state."""
248 """merge currently-saved state with the new state."""
249 raise NotImplementedError
249 raise NotImplementedError
250
250
251 def push(self, force):
251 def push(self, force):
252 """perform whatever action is analogous to 'hg push'
252 """perform whatever action is analogous to 'hg push'
253
253
254 This may be a no-op on some systems.
254 This may be a no-op on some systems.
255 """
255 """
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258 def add(self, ui, match, dryrun, prefix):
258 def add(self, ui, match, dryrun, prefix):
259 return []
259 return []
260
260
261 def status(self, rev2, **opts):
261 def status(self, rev2, **opts):
262 return [], [], [], [], [], [], []
262 return [], [], [], [], [], [], []
263
263
264 def diff(self, diffopts, node2, match, prefix, **opts):
264 def diff(self, diffopts, node2, match, prefix, **opts):
265 pass
265 pass
266
266
267 def outgoing(self, ui, dest, opts):
267 def outgoing(self, ui, dest, opts):
268 return 1
268 return 1
269
269
270 def incoming(self, ui, source, opts):
270 def incoming(self, ui, source, opts):
271 return 1
271 return 1
272
272
273 def files(self):
274 """return filename iterator"""
275 raise NotImplementedError
276
277 def filedata(self, name):
278 """return file data"""
279 raise NotImplementedError
280
281 def fileflags(self, name):
282 """return file flags"""
283 return ''
284
273 class hgsubrepo(abstractsubrepo):
285 class hgsubrepo(abstractsubrepo):
274 def __init__(self, ctx, path, state):
286 def __init__(self, ctx, path, state):
275 self._path = path
287 self._path = path
276 self._state = state
288 self._state = state
277 r = ctx._repo
289 r = ctx._repo
278 root = r.wjoin(path)
290 root = r.wjoin(path)
279 create = False
291 create = False
280 if not os.path.exists(os.path.join(root, '.hg')):
292 if not os.path.exists(os.path.join(root, '.hg')):
281 create = True
293 create = True
282 util.makedirs(root)
294 util.makedirs(root)
283 self._repo = hg.repository(r.ui, root, create=create)
295 self._repo = hg.repository(r.ui, root, create=create)
284 self._repo._subparent = r
296 self._repo._subparent = r
285 self._repo._subsource = state[0]
297 self._repo._subsource = state[0]
286
298
287 if create:
299 if create:
288 fp = self._repo.opener("hgrc", "w", text=True)
300 fp = self._repo.opener("hgrc", "w", text=True)
289 fp.write('[paths]\n')
301 fp.write('[paths]\n')
290
302
291 def addpathconfig(key, value):
303 def addpathconfig(key, value):
292 fp.write('%s = %s\n' % (key, value))
304 fp.write('%s = %s\n' % (key, value))
293 self._repo.ui.setconfig('paths', key, value)
305 self._repo.ui.setconfig('paths', key, value)
294
306
295 defpath = _abssource(self._repo)
307 defpath = _abssource(self._repo)
296 defpushpath = _abssource(self._repo, True)
308 defpushpath = _abssource(self._repo, True)
297 addpathconfig('default', defpath)
309 addpathconfig('default', defpath)
298 if defpath != defpushpath:
310 if defpath != defpushpath:
299 addpathconfig('default-push', defpushpath)
311 addpathconfig('default-push', defpushpath)
300 fp.close()
312 fp.close()
301
313
302 def add(self, ui, match, dryrun, prefix):
314 def add(self, ui, match, dryrun, prefix):
303 return cmdutil.add(ui, self._repo, match, dryrun, True,
315 return cmdutil.add(ui, self._repo, match, dryrun, True,
304 os.path.join(prefix, self._path))
316 os.path.join(prefix, self._path))
305
317
306 def status(self, rev2, **opts):
318 def status(self, rev2, **opts):
307 try:
319 try:
308 rev1 = self._state[1]
320 rev1 = self._state[1]
309 ctx1 = self._repo[rev1]
321 ctx1 = self._repo[rev1]
310 ctx2 = self._repo[rev2]
322 ctx2 = self._repo[rev2]
311 return self._repo.status(ctx1, ctx2, **opts)
323 return self._repo.status(ctx1, ctx2, **opts)
312 except error.RepoLookupError, inst:
324 except error.RepoLookupError, inst:
313 self._repo.ui.warn(_("warning: %s in %s\n")
325 self._repo.ui.warn(_("warning: %s in %s\n")
314 % (inst, relpath(self)))
326 % (inst, relpath(self)))
315 return [], [], [], [], [], [], []
327 return [], [], [], [], [], [], []
316
328
317 def diff(self, diffopts, node2, match, prefix, **opts):
329 def diff(self, diffopts, node2, match, prefix, **opts):
318 try:
330 try:
319 node1 = node.bin(self._state[1])
331 node1 = node.bin(self._state[1])
320 # We currently expect node2 to come from substate and be
332 # We currently expect node2 to come from substate and be
321 # in hex format
333 # in hex format
322 if node2 is not None:
334 if node2 is not None:
323 node2 = node.bin(node2)
335 node2 = node.bin(node2)
324 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
336 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
325 node1, node2, match,
337 node1, node2, match,
326 prefix=os.path.join(prefix, self._path),
338 prefix=os.path.join(prefix, self._path),
327 listsubrepos=True, **opts)
339 listsubrepos=True, **opts)
328 except error.RepoLookupError, inst:
340 except error.RepoLookupError, inst:
329 self._repo.ui.warn(_("warning: %s in %s\n")
341 self._repo.ui.warn(_("warning: %s in %s\n")
330 % (inst, relpath(self)))
342 % (inst, relpath(self)))
331
343
332 def dirty(self):
344 def dirty(self):
333 r = self._state[1]
345 r = self._state[1]
334 if r == '':
346 if r == '':
335 return True
347 return True
336 w = self._repo[None]
348 w = self._repo[None]
337 if w.p1() != self._repo[r]: # version checked out change
349 if w.p1() != self._repo[r]: # version checked out change
338 return True
350 return True
339 return w.dirty() # working directory changed
351 return w.dirty() # working directory changed
340
352
341 def checknested(self, path):
353 def checknested(self, path):
342 return self._repo._checknested(self._repo.wjoin(path))
354 return self._repo._checknested(self._repo.wjoin(path))
343
355
344 def commit(self, text, user, date):
356 def commit(self, text, user, date):
345 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
357 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
346 n = self._repo.commit(text, user, date)
358 n = self._repo.commit(text, user, date)
347 if not n:
359 if not n:
348 return self._repo['.'].hex() # different version checked out
360 return self._repo['.'].hex() # different version checked out
349 return node.hex(n)
361 return node.hex(n)
350
362
351 def remove(self):
363 def remove(self):
352 # we can't fully delete the repository as it may contain
364 # we can't fully delete the repository as it may contain
353 # local-only history
365 # local-only history
354 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
366 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
355 hg.clean(self._repo, node.nullid, False)
367 hg.clean(self._repo, node.nullid, False)
356
368
357 def _get(self, state):
369 def _get(self, state):
358 source, revision, kind = state
370 source, revision, kind = state
359 try:
371 try:
360 self._repo.lookup(revision)
372 self._repo.lookup(revision)
361 except error.RepoError:
373 except error.RepoError:
362 self._repo._subsource = source
374 self._repo._subsource = source
363 srcurl = _abssource(self._repo)
375 srcurl = _abssource(self._repo)
364 self._repo.ui.status(_('pulling subrepo %s from %s\n')
376 self._repo.ui.status(_('pulling subrepo %s from %s\n')
365 % (relpath(self), srcurl))
377 % (relpath(self), srcurl))
366 other = hg.repository(self._repo.ui, srcurl)
378 other = hg.repository(self._repo.ui, srcurl)
367 self._repo.pull(other)
379 self._repo.pull(other)
368
380
369 def get(self, state):
381 def get(self, state):
370 self._get(state)
382 self._get(state)
371 source, revision, kind = state
383 source, revision, kind = state
372 self._repo.ui.debug("getting subrepo %s\n" % self._path)
384 self._repo.ui.debug("getting subrepo %s\n" % self._path)
373 hg.clean(self._repo, revision, False)
385 hg.clean(self._repo, revision, False)
374
386
375 def merge(self, state):
387 def merge(self, state):
376 self._get(state)
388 self._get(state)
377 cur = self._repo['.']
389 cur = self._repo['.']
378 dst = self._repo[state[1]]
390 dst = self._repo[state[1]]
379 anc = dst.ancestor(cur)
391 anc = dst.ancestor(cur)
380 if anc == cur:
392 if anc == cur:
381 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
393 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
382 hg.update(self._repo, state[1])
394 hg.update(self._repo, state[1])
383 elif anc == dst:
395 elif anc == dst:
384 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
396 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
385 else:
397 else:
386 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
398 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
387 hg.merge(self._repo, state[1], remind=False)
399 hg.merge(self._repo, state[1], remind=False)
388
400
389 def push(self, force):
401 def push(self, force):
390 # push subrepos depth-first for coherent ordering
402 # push subrepos depth-first for coherent ordering
391 c = self._repo['']
403 c = self._repo['']
392 subs = c.substate # only repos that are committed
404 subs = c.substate # only repos that are committed
393 for s in sorted(subs):
405 for s in sorted(subs):
394 if not c.sub(s).push(force):
406 if not c.sub(s).push(force):
395 return False
407 return False
396
408
397 dsturl = _abssource(self._repo, True)
409 dsturl = _abssource(self._repo, True)
398 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
410 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
399 (relpath(self), dsturl))
411 (relpath(self), dsturl))
400 other = hg.repository(self._repo.ui, dsturl)
412 other = hg.repository(self._repo.ui, dsturl)
401 return self._repo.push(other, force)
413 return self._repo.push(other, force)
402
414
403 def outgoing(self, ui, dest, opts):
415 def outgoing(self, ui, dest, opts):
404 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
416 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
405
417
406 def incoming(self, ui, source, opts):
418 def incoming(self, ui, source, opts):
407 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
419 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
408
420
421 def files(self):
422 rev = self._state[1]
423 ctx = self._repo[rev]
424 return ctx.manifest()
425
426 def filedata(self, name):
427 rev = self._state[1]
428 return self._repo[rev][name].data()
429
430 def fileflags(self, name):
431 rev = self._state[1]
432 ctx = self._repo[rev]
433 return ctx.flags(name)
434
435
409 class svnsubrepo(abstractsubrepo):
436 class svnsubrepo(abstractsubrepo):
410 def __init__(self, ctx, path, state):
437 def __init__(self, ctx, path, state):
411 self._path = path
438 self._path = path
412 self._state = state
439 self._state = state
413 self._ctx = ctx
440 self._ctx = ctx
414 self._ui = ctx._repo.ui
441 self._ui = ctx._repo.ui
415
442
416 def _svncommand(self, commands, filename=''):
443 def _svncommand(self, commands, filename=''):
417 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
444 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
418 cmd = ['svn'] + commands + [path]
445 cmd = ['svn'] + commands + [path]
419 cmd = [util.shellquote(arg) for arg in cmd]
446 cmd = [util.shellquote(arg) for arg in cmd]
420 cmd = util.quotecommand(' '.join(cmd))
447 cmd = util.quotecommand(' '.join(cmd))
421 env = dict(os.environ)
448 env = dict(os.environ)
422 # Avoid localized output, preserve current locale for everything else.
449 # Avoid localized output, preserve current locale for everything else.
423 env['LC_MESSAGES'] = 'C'
450 env['LC_MESSAGES'] = 'C'
424 write, read, err = util.popen3(cmd, env=env, newlines=True)
451 write, read, err = util.popen3(cmd, env=env, newlines=True)
425 retdata = read.read()
452 retdata = read.read()
426 err = err.read().strip()
453 err = err.read().strip()
427 if err:
454 if err:
428 raise util.Abort(err)
455 raise util.Abort(err)
429 return retdata
456 return retdata
430
457
431 def _wcrev(self):
458 def _wcrev(self):
432 output = self._svncommand(['info', '--xml'])
459 output = self._svncommand(['info', '--xml'])
433 doc = xml.dom.minidom.parseString(output)
460 doc = xml.dom.minidom.parseString(output)
434 entries = doc.getElementsByTagName('entry')
461 entries = doc.getElementsByTagName('entry')
435 if not entries:
462 if not entries:
436 return 0
463 return 0
437 return int(entries[0].getAttribute('revision') or 0)
464 return int(entries[0].getAttribute('revision') or 0)
438
465
439 def _wcchanged(self):
466 def _wcchanged(self):
440 """Return (changes, extchanges) where changes is True
467 """Return (changes, extchanges) where changes is True
441 if the working directory was changed, and extchanges is
468 if the working directory was changed, and extchanges is
442 True if any of these changes concern an external entry.
469 True if any of these changes concern an external entry.
443 """
470 """
444 output = self._svncommand(['status', '--xml'])
471 output = self._svncommand(['status', '--xml'])
445 externals, changes = [], []
472 externals, changes = [], []
446 doc = xml.dom.minidom.parseString(output)
473 doc = xml.dom.minidom.parseString(output)
447 for e in doc.getElementsByTagName('entry'):
474 for e in doc.getElementsByTagName('entry'):
448 s = e.getElementsByTagName('wc-status')
475 s = e.getElementsByTagName('wc-status')
449 if not s:
476 if not s:
450 continue
477 continue
451 item = s[0].getAttribute('item')
478 item = s[0].getAttribute('item')
452 props = s[0].getAttribute('props')
479 props = s[0].getAttribute('props')
453 path = e.getAttribute('path')
480 path = e.getAttribute('path')
454 if item == 'external':
481 if item == 'external':
455 externals.append(path)
482 externals.append(path)
456 if (item not in ('', 'normal', 'unversioned', 'external')
483 if (item not in ('', 'normal', 'unversioned', 'external')
457 or props not in ('', 'none')):
484 or props not in ('', 'none')):
458 changes.append(path)
485 changes.append(path)
459 for path in changes:
486 for path in changes:
460 for ext in externals:
487 for ext in externals:
461 if path == ext or path.startswith(ext + os.sep):
488 if path == ext or path.startswith(ext + os.sep):
462 return True, True
489 return True, True
463 return bool(changes), False
490 return bool(changes), False
464
491
465 def dirty(self):
492 def dirty(self):
466 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
493 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
467 return False
494 return False
468 return True
495 return True
469
496
470 def commit(self, text, user, date):
497 def commit(self, text, user, date):
471 # user and date are out of our hands since svn is centralized
498 # user and date are out of our hands since svn is centralized
472 changed, extchanged = self._wcchanged()
499 changed, extchanged = self._wcchanged()
473 if not changed:
500 if not changed:
474 return self._wcrev()
501 return self._wcrev()
475 if extchanged:
502 if extchanged:
476 # Do not try to commit externals
503 # Do not try to commit externals
477 raise util.Abort(_('cannot commit svn externals'))
504 raise util.Abort(_('cannot commit svn externals'))
478 commitinfo = self._svncommand(['commit', '-m', text])
505 commitinfo = self._svncommand(['commit', '-m', text])
479 self._ui.status(commitinfo)
506 self._ui.status(commitinfo)
480 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
507 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
481 if not newrev:
508 if not newrev:
482 raise util.Abort(commitinfo.splitlines()[-1])
509 raise util.Abort(commitinfo.splitlines()[-1])
483 newrev = newrev.groups()[0]
510 newrev = newrev.groups()[0]
484 self._ui.status(self._svncommand(['update', '-r', newrev]))
511 self._ui.status(self._svncommand(['update', '-r', newrev]))
485 return newrev
512 return newrev
486
513
487 def remove(self):
514 def remove(self):
488 if self.dirty():
515 if self.dirty():
489 self._ui.warn(_('not removing repo %s because '
516 self._ui.warn(_('not removing repo %s because '
490 'it has changes.\n' % self._path))
517 'it has changes.\n' % self._path))
491 return
518 return
492 self._ui.note(_('removing subrepo %s\n') % self._path)
519 self._ui.note(_('removing subrepo %s\n') % self._path)
493 shutil.rmtree(self._ctx.repo.join(self._path))
520 shutil.rmtree(self._ctx.repo.join(self._path))
494
521
495 def get(self, state):
522 def get(self, state):
496 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
523 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
497 if not re.search('Checked out revision [0-9]+.', status):
524 if not re.search('Checked out revision [0-9]+.', status):
498 raise util.Abort(status.splitlines()[-1])
525 raise util.Abort(status.splitlines()[-1])
499 self._ui.status(status)
526 self._ui.status(status)
500
527
501 def merge(self, state):
528 def merge(self, state):
502 old = int(self._state[1])
529 old = int(self._state[1])
503 new = int(state[1])
530 new = int(state[1])
504 if new > old:
531 if new > old:
505 self.get(state)
532 self.get(state)
506
533
507 def push(self, force):
534 def push(self, force):
508 # push is a no-op for SVN
535 # push is a no-op for SVN
509 return True
536 return True
510
537
538 def files(self):
539 output = self._svncommand(['list'])
540 # This works because svn forbids \n in filenames.
541 return output.splitlines()
542
543 def filedata(self, name):
544 return self._svncommand(['cat'], name)
545
546
511 types = {
547 types = {
512 'hg': hgsubrepo,
548 'hg': hgsubrepo,
513 'svn': svnsubrepo,
549 'svn': svnsubrepo,
514 }
550 }
General Comments 0
You need to be logged in to leave comments. Login now