##// END OF EJS Templates
subrepos: add missing self argument to abstractsubrepo.checknested
Brodie Rao -
r12506:e7d45e41 default
parent child Browse files
Show More
@@ -1,568 +1,568
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(self, 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):
273 def files(self):
274 """return filename iterator"""
274 """return filename iterator"""
275 raise NotImplementedError
275 raise NotImplementedError
276
276
277 def filedata(self, name):
277 def filedata(self, name):
278 """return file data"""
278 """return file data"""
279 raise NotImplementedError
279 raise NotImplementedError
280
280
281 def fileflags(self, name):
281 def fileflags(self, name):
282 """return file flags"""
282 """return file flags"""
283 return ''
283 return ''
284
284
285 def archive(self, archiver, prefix):
285 def archive(self, archiver, prefix):
286 for name in self.files():
286 for name in self.files():
287 flags = self.fileflags(name)
287 flags = self.fileflags(name)
288 mode = 'x' in flags and 0755 or 0644
288 mode = 'x' in flags and 0755 or 0644
289 symlink = 'l' in flags
289 symlink = 'l' in flags
290 archiver.addfile(os.path.join(prefix, self._path, name),
290 archiver.addfile(os.path.join(prefix, self._path, name),
291 mode, symlink, self.filedata(name))
291 mode, symlink, self.filedata(name))
292
292
293
293
294 class hgsubrepo(abstractsubrepo):
294 class hgsubrepo(abstractsubrepo):
295 def __init__(self, ctx, path, state):
295 def __init__(self, ctx, path, state):
296 self._path = path
296 self._path = path
297 self._state = state
297 self._state = state
298 r = ctx._repo
298 r = ctx._repo
299 root = r.wjoin(path)
299 root = r.wjoin(path)
300 create = False
300 create = False
301 if not os.path.exists(os.path.join(root, '.hg')):
301 if not os.path.exists(os.path.join(root, '.hg')):
302 create = True
302 create = True
303 util.makedirs(root)
303 util.makedirs(root)
304 self._repo = hg.repository(r.ui, root, create=create)
304 self._repo = hg.repository(r.ui, root, create=create)
305 self._repo._subparent = r
305 self._repo._subparent = r
306 self._repo._subsource = state[0]
306 self._repo._subsource = state[0]
307
307
308 if create:
308 if create:
309 fp = self._repo.opener("hgrc", "w", text=True)
309 fp = self._repo.opener("hgrc", "w", text=True)
310 fp.write('[paths]\n')
310 fp.write('[paths]\n')
311
311
312 def addpathconfig(key, value):
312 def addpathconfig(key, value):
313 fp.write('%s = %s\n' % (key, value))
313 fp.write('%s = %s\n' % (key, value))
314 self._repo.ui.setconfig('paths', key, value)
314 self._repo.ui.setconfig('paths', key, value)
315
315
316 defpath = _abssource(self._repo)
316 defpath = _abssource(self._repo)
317 defpushpath = _abssource(self._repo, True)
317 defpushpath = _abssource(self._repo, True)
318 addpathconfig('default', defpath)
318 addpathconfig('default', defpath)
319 if defpath != defpushpath:
319 if defpath != defpushpath:
320 addpathconfig('default-push', defpushpath)
320 addpathconfig('default-push', defpushpath)
321 fp.close()
321 fp.close()
322
322
323 def add(self, ui, match, dryrun, prefix):
323 def add(self, ui, match, dryrun, prefix):
324 return cmdutil.add(ui, self._repo, match, dryrun, True,
324 return cmdutil.add(ui, self._repo, match, dryrun, True,
325 os.path.join(prefix, self._path))
325 os.path.join(prefix, self._path))
326
326
327 def status(self, rev2, **opts):
327 def status(self, rev2, **opts):
328 try:
328 try:
329 rev1 = self._state[1]
329 rev1 = self._state[1]
330 ctx1 = self._repo[rev1]
330 ctx1 = self._repo[rev1]
331 ctx2 = self._repo[rev2]
331 ctx2 = self._repo[rev2]
332 return self._repo.status(ctx1, ctx2, **opts)
332 return self._repo.status(ctx1, ctx2, **opts)
333 except error.RepoLookupError, inst:
333 except error.RepoLookupError, inst:
334 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
334 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
335 % (inst, relpath(self)))
335 % (inst, relpath(self)))
336 return [], [], [], [], [], [], []
336 return [], [], [], [], [], [], []
337
337
338 def diff(self, diffopts, node2, match, prefix, **opts):
338 def diff(self, diffopts, node2, match, prefix, **opts):
339 try:
339 try:
340 node1 = node.bin(self._state[1])
340 node1 = node.bin(self._state[1])
341 # We currently expect node2 to come from substate and be
341 # We currently expect node2 to come from substate and be
342 # in hex format
342 # in hex format
343 if node2 is not None:
343 if node2 is not None:
344 node2 = node.bin(node2)
344 node2 = node.bin(node2)
345 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
345 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
346 node1, node2, match,
346 node1, node2, match,
347 prefix=os.path.join(prefix, self._path),
347 prefix=os.path.join(prefix, self._path),
348 listsubrepos=True, **opts)
348 listsubrepos=True, **opts)
349 except error.RepoLookupError, inst:
349 except error.RepoLookupError, inst:
350 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
350 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
351 % (inst, relpath(self)))
351 % (inst, relpath(self)))
352
352
353 def archive(self, archiver, prefix):
353 def archive(self, archiver, prefix):
354 abstractsubrepo.archive(self, archiver, prefix)
354 abstractsubrepo.archive(self, archiver, prefix)
355
355
356 rev = self._state[1]
356 rev = self._state[1]
357 ctx = self._repo[rev]
357 ctx = self._repo[rev]
358 for subpath in ctx.substate:
358 for subpath in ctx.substate:
359 s = subrepo(ctx, subpath)
359 s = subrepo(ctx, subpath)
360 s.archive(archiver, os.path.join(prefix, self._path))
360 s.archive(archiver, os.path.join(prefix, self._path))
361
361
362 def dirty(self):
362 def dirty(self):
363 r = self._state[1]
363 r = self._state[1]
364 if r == '':
364 if r == '':
365 return True
365 return True
366 w = self._repo[None]
366 w = self._repo[None]
367 if w.p1() != self._repo[r]: # version checked out change
367 if w.p1() != self._repo[r]: # version checked out change
368 return True
368 return True
369 return w.dirty() # working directory changed
369 return w.dirty() # working directory changed
370
370
371 def checknested(self, path):
371 def checknested(self, path):
372 return self._repo._checknested(self._repo.wjoin(path))
372 return self._repo._checknested(self._repo.wjoin(path))
373
373
374 def commit(self, text, user, date):
374 def commit(self, text, user, date):
375 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
375 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
376 n = self._repo.commit(text, user, date)
376 n = self._repo.commit(text, user, date)
377 if not n:
377 if not n:
378 return self._repo['.'].hex() # different version checked out
378 return self._repo['.'].hex() # different version checked out
379 return node.hex(n)
379 return node.hex(n)
380
380
381 def remove(self):
381 def remove(self):
382 # we can't fully delete the repository as it may contain
382 # we can't fully delete the repository as it may contain
383 # local-only history
383 # local-only history
384 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
384 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
385 hg.clean(self._repo, node.nullid, False)
385 hg.clean(self._repo, node.nullid, False)
386
386
387 def _get(self, state):
387 def _get(self, state):
388 source, revision, kind = state
388 source, revision, kind = state
389 try:
389 try:
390 self._repo.lookup(revision)
390 self._repo.lookup(revision)
391 except error.RepoError:
391 except error.RepoError:
392 self._repo._subsource = source
392 self._repo._subsource = source
393 srcurl = _abssource(self._repo)
393 srcurl = _abssource(self._repo)
394 self._repo.ui.status(_('pulling subrepo %s from %s\n')
394 self._repo.ui.status(_('pulling subrepo %s from %s\n')
395 % (relpath(self), srcurl))
395 % (relpath(self), srcurl))
396 other = hg.repository(self._repo.ui, srcurl)
396 other = hg.repository(self._repo.ui, srcurl)
397 self._repo.pull(other)
397 self._repo.pull(other)
398
398
399 def get(self, state):
399 def get(self, state):
400 self._get(state)
400 self._get(state)
401 source, revision, kind = state
401 source, revision, kind = state
402 self._repo.ui.debug("getting subrepo %s\n" % self._path)
402 self._repo.ui.debug("getting subrepo %s\n" % self._path)
403 hg.clean(self._repo, revision, False)
403 hg.clean(self._repo, revision, False)
404
404
405 def merge(self, state):
405 def merge(self, state):
406 self._get(state)
406 self._get(state)
407 cur = self._repo['.']
407 cur = self._repo['.']
408 dst = self._repo[state[1]]
408 dst = self._repo[state[1]]
409 anc = dst.ancestor(cur)
409 anc = dst.ancestor(cur)
410 if anc == cur:
410 if anc == cur:
411 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
411 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
412 hg.update(self._repo, state[1])
412 hg.update(self._repo, state[1])
413 elif anc == dst:
413 elif anc == dst:
414 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
414 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
415 else:
415 else:
416 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
416 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
417 hg.merge(self._repo, state[1], remind=False)
417 hg.merge(self._repo, state[1], remind=False)
418
418
419 def push(self, force):
419 def push(self, force):
420 # push subrepos depth-first for coherent ordering
420 # push subrepos depth-first for coherent ordering
421 c = self._repo['']
421 c = self._repo['']
422 subs = c.substate # only repos that are committed
422 subs = c.substate # only repos that are committed
423 for s in sorted(subs):
423 for s in sorted(subs):
424 if not c.sub(s).push(force):
424 if not c.sub(s).push(force):
425 return False
425 return False
426
426
427 dsturl = _abssource(self._repo, True)
427 dsturl = _abssource(self._repo, True)
428 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
428 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
429 (relpath(self), dsturl))
429 (relpath(self), dsturl))
430 other = hg.repository(self._repo.ui, dsturl)
430 other = hg.repository(self._repo.ui, dsturl)
431 return self._repo.push(other, force)
431 return self._repo.push(other, force)
432
432
433 def outgoing(self, ui, dest, opts):
433 def outgoing(self, ui, dest, opts):
434 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
434 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
435
435
436 def incoming(self, ui, source, opts):
436 def incoming(self, ui, source, opts):
437 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
437 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
438
438
439 def files(self):
439 def files(self):
440 rev = self._state[1]
440 rev = self._state[1]
441 ctx = self._repo[rev]
441 ctx = self._repo[rev]
442 return ctx.manifest()
442 return ctx.manifest()
443
443
444 def filedata(self, name):
444 def filedata(self, name):
445 rev = self._state[1]
445 rev = self._state[1]
446 return self._repo[rev][name].data()
446 return self._repo[rev][name].data()
447
447
448 def fileflags(self, name):
448 def fileflags(self, name):
449 rev = self._state[1]
449 rev = self._state[1]
450 ctx = self._repo[rev]
450 ctx = self._repo[rev]
451 return ctx.flags(name)
451 return ctx.flags(name)
452
452
453
453
454 class svnsubrepo(abstractsubrepo):
454 class svnsubrepo(abstractsubrepo):
455 def __init__(self, ctx, path, state):
455 def __init__(self, ctx, path, state):
456 self._path = path
456 self._path = path
457 self._state = state
457 self._state = state
458 self._ctx = ctx
458 self._ctx = ctx
459 self._ui = ctx._repo.ui
459 self._ui = ctx._repo.ui
460
460
461 def _svncommand(self, commands, filename=''):
461 def _svncommand(self, commands, filename=''):
462 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
462 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
463 cmd = ['svn'] + commands + [path]
463 cmd = ['svn'] + commands + [path]
464 cmd = [util.shellquote(arg) for arg in cmd]
464 cmd = [util.shellquote(arg) for arg in cmd]
465 cmd = util.quotecommand(' '.join(cmd))
465 cmd = util.quotecommand(' '.join(cmd))
466 env = dict(os.environ)
466 env = dict(os.environ)
467 # Avoid localized output, preserve current locale for everything else.
467 # Avoid localized output, preserve current locale for everything else.
468 env['LC_MESSAGES'] = 'C'
468 env['LC_MESSAGES'] = 'C'
469 write, read, err = util.popen3(cmd, env=env, newlines=True)
469 write, read, err = util.popen3(cmd, env=env, newlines=True)
470 retdata = read.read()
470 retdata = read.read()
471 err = err.read().strip()
471 err = err.read().strip()
472 if err:
472 if err:
473 raise util.Abort(err)
473 raise util.Abort(err)
474 return retdata
474 return retdata
475
475
476 def _wcrev(self):
476 def _wcrev(self):
477 output = self._svncommand(['info', '--xml'])
477 output = self._svncommand(['info', '--xml'])
478 doc = xml.dom.minidom.parseString(output)
478 doc = xml.dom.minidom.parseString(output)
479 entries = doc.getElementsByTagName('entry')
479 entries = doc.getElementsByTagName('entry')
480 if not entries:
480 if not entries:
481 return 0
481 return 0
482 return int(entries[0].getAttribute('revision') or 0)
482 return int(entries[0].getAttribute('revision') or 0)
483
483
484 def _wcchanged(self):
484 def _wcchanged(self):
485 """Return (changes, extchanges) where changes is True
485 """Return (changes, extchanges) where changes is True
486 if the working directory was changed, and extchanges is
486 if the working directory was changed, and extchanges is
487 True if any of these changes concern an external entry.
487 True if any of these changes concern an external entry.
488 """
488 """
489 output = self._svncommand(['status', '--xml'])
489 output = self._svncommand(['status', '--xml'])
490 externals, changes = [], []
490 externals, changes = [], []
491 doc = xml.dom.minidom.parseString(output)
491 doc = xml.dom.minidom.parseString(output)
492 for e in doc.getElementsByTagName('entry'):
492 for e in doc.getElementsByTagName('entry'):
493 s = e.getElementsByTagName('wc-status')
493 s = e.getElementsByTagName('wc-status')
494 if not s:
494 if not s:
495 continue
495 continue
496 item = s[0].getAttribute('item')
496 item = s[0].getAttribute('item')
497 props = s[0].getAttribute('props')
497 props = s[0].getAttribute('props')
498 path = e.getAttribute('path')
498 path = e.getAttribute('path')
499 if item == 'external':
499 if item == 'external':
500 externals.append(path)
500 externals.append(path)
501 if (item not in ('', 'normal', 'unversioned', 'external')
501 if (item not in ('', 'normal', 'unversioned', 'external')
502 or props not in ('', 'none')):
502 or props not in ('', 'none')):
503 changes.append(path)
503 changes.append(path)
504 for path in changes:
504 for path in changes:
505 for ext in externals:
505 for ext in externals:
506 if path == ext or path.startswith(ext + os.sep):
506 if path == ext or path.startswith(ext + os.sep):
507 return True, True
507 return True, True
508 return bool(changes), False
508 return bool(changes), False
509
509
510 def dirty(self):
510 def dirty(self):
511 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
511 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
512 return False
512 return False
513 return True
513 return True
514
514
515 def commit(self, text, user, date):
515 def commit(self, text, user, date):
516 # user and date are out of our hands since svn is centralized
516 # user and date are out of our hands since svn is centralized
517 changed, extchanged = self._wcchanged()
517 changed, extchanged = self._wcchanged()
518 if not changed:
518 if not changed:
519 return self._wcrev()
519 return self._wcrev()
520 if extchanged:
520 if extchanged:
521 # Do not try to commit externals
521 # Do not try to commit externals
522 raise util.Abort(_('cannot commit svn externals'))
522 raise util.Abort(_('cannot commit svn externals'))
523 commitinfo = self._svncommand(['commit', '-m', text])
523 commitinfo = self._svncommand(['commit', '-m', text])
524 self._ui.status(commitinfo)
524 self._ui.status(commitinfo)
525 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
525 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
526 if not newrev:
526 if not newrev:
527 raise util.Abort(commitinfo.splitlines()[-1])
527 raise util.Abort(commitinfo.splitlines()[-1])
528 newrev = newrev.groups()[0]
528 newrev = newrev.groups()[0]
529 self._ui.status(self._svncommand(['update', '-r', newrev]))
529 self._ui.status(self._svncommand(['update', '-r', newrev]))
530 return newrev
530 return newrev
531
531
532 def remove(self):
532 def remove(self):
533 if self.dirty():
533 if self.dirty():
534 self._ui.warn(_('not removing repo %s because '
534 self._ui.warn(_('not removing repo %s because '
535 'it has changes.\n' % self._path))
535 'it has changes.\n' % self._path))
536 return
536 return
537 self._ui.note(_('removing subrepo %s\n') % self._path)
537 self._ui.note(_('removing subrepo %s\n') % self._path)
538 shutil.rmtree(self._ctx.repo.join(self._path))
538 shutil.rmtree(self._ctx.repo.join(self._path))
539
539
540 def get(self, state):
540 def get(self, state):
541 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
541 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
542 if not re.search('Checked out revision [0-9]+.', status):
542 if not re.search('Checked out revision [0-9]+.', status):
543 raise util.Abort(status.splitlines()[-1])
543 raise util.Abort(status.splitlines()[-1])
544 self._ui.status(status)
544 self._ui.status(status)
545
545
546 def merge(self, state):
546 def merge(self, state):
547 old = int(self._state[1])
547 old = int(self._state[1])
548 new = int(state[1])
548 new = int(state[1])
549 if new > old:
549 if new > old:
550 self.get(state)
550 self.get(state)
551
551
552 def push(self, force):
552 def push(self, force):
553 # push is a no-op for SVN
553 # push is a no-op for SVN
554 return True
554 return True
555
555
556 def files(self):
556 def files(self):
557 output = self._svncommand(['list'])
557 output = self._svncommand(['list'])
558 # This works because svn forbids \n in filenames.
558 # This works because svn forbids \n in filenames.
559 return output.splitlines()
559 return output.splitlines()
560
560
561 def filedata(self, name):
561 def filedata(self, name):
562 return self._svncommand(['cat'], name)
562 return self._svncommand(['cat'], name)
563
563
564
564
565 types = {
565 types = {
566 'hg': hgsubrepo,
566 'hg': hgsubrepo,
567 'svn': svnsubrepo,
567 'svn': svnsubrepo,
568 }
568 }
General Comments 0
You need to be logged in to leave comments. Login now