##// END OF EJS Templates
subrepo: use subprocess directly to avoid python 2.6 bug...
Patrick Mezard -
r13014:d1c52354 stable
parent child Browse files
Show More
@@ -1,594 +1,597 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 import stat
9 import stat, subprocess
10 from i18n import _
10 from i18n import _
11 import config, util, node, error, cmdutil
11 import config, util, node, error, cmdutil
12 hg = None
12 hg = None
13
13
14 nullstate = ('', '', 'empty')
14 nullstate = ('', '', 'empty')
15
15
16 def state(ctx, ui):
16 def state(ctx, ui):
17 """return a state dict, mapping subrepo paths configured in .hgsub
17 """return a state dict, mapping subrepo paths configured in .hgsub
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 (key in types dict))
19 (key in types dict))
20 """
20 """
21 p = config.config()
21 p = config.config()
22 def read(f, sections=None, remap=None):
22 def read(f, sections=None, remap=None):
23 if f in ctx:
23 if f in ctx:
24 p.parse(f, ctx[f].data(), sections, remap, read)
24 p.parse(f, ctx[f].data(), sections, remap, read)
25 else:
25 else:
26 raise util.Abort(_("subrepo spec file %s not found") % f)
26 raise util.Abort(_("subrepo spec file %s not found") % f)
27
27
28 if '.hgsub' in ctx:
28 if '.hgsub' in ctx:
29 read('.hgsub')
29 read('.hgsub')
30
30
31 for path, src in ui.configitems('subpaths'):
31 for path, src in ui.configitems('subpaths'):
32 p.set('subpaths', path, src, ui.configsource('subpaths', path))
32 p.set('subpaths', path, src, ui.configsource('subpaths', path))
33
33
34 rev = {}
34 rev = {}
35 if '.hgsubstate' in ctx:
35 if '.hgsubstate' in ctx:
36 try:
36 try:
37 for l in ctx['.hgsubstate'].data().splitlines():
37 for l in ctx['.hgsubstate'].data().splitlines():
38 revision, path = l.split(" ", 1)
38 revision, path = l.split(" ", 1)
39 rev[path] = revision
39 rev[path] = revision
40 except IOError, err:
40 except IOError, err:
41 if err.errno != errno.ENOENT:
41 if err.errno != errno.ENOENT:
42 raise
42 raise
43
43
44 state = {}
44 state = {}
45 for path, src in p[''].items():
45 for path, src in p[''].items():
46 kind = 'hg'
46 kind = 'hg'
47 if src.startswith('['):
47 if src.startswith('['):
48 if ']' not in src:
48 if ']' not in src:
49 raise util.Abort(_('missing ] in subrepo source'))
49 raise util.Abort(_('missing ] in subrepo source'))
50 kind, src = src.split(']', 1)
50 kind, src = src.split(']', 1)
51 kind = kind[1:]
51 kind = kind[1:]
52
52
53 for pattern, repl in p.items('subpaths'):
53 for pattern, repl in p.items('subpaths'):
54 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
54 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
55 # does a string decode.
55 # does a string decode.
56 repl = repl.encode('string-escape')
56 repl = repl.encode('string-escape')
57 # However, we still want to allow back references to go
57 # However, we still want to allow back references to go
58 # through unharmed, so we turn r'\\1' into r'\1'. Again,
58 # through unharmed, so we turn r'\\1' into r'\1'. Again,
59 # extra escapes are needed because re.sub string decodes.
59 # extra escapes are needed because re.sub string decodes.
60 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
60 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
61 try:
61 try:
62 src = re.sub(pattern, repl, src, 1)
62 src = re.sub(pattern, repl, src, 1)
63 except re.error, e:
63 except re.error, e:
64 raise util.Abort(_("bad subrepository pattern in %s: %s")
64 raise util.Abort(_("bad subrepository pattern in %s: %s")
65 % (p.source('subpaths', pattern), e))
65 % (p.source('subpaths', pattern), e))
66
66
67 state[path] = (src.strip(), rev.get(path, ''), kind)
67 state[path] = (src.strip(), rev.get(path, ''), kind)
68
68
69 return state
69 return state
70
70
71 def writestate(repo, state):
71 def writestate(repo, state):
72 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
72 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
73 repo.wwrite('.hgsubstate',
73 repo.wwrite('.hgsubstate',
74 ''.join(['%s %s\n' % (state[s][1], s)
74 ''.join(['%s %s\n' % (state[s][1], s)
75 for s in sorted(state)]), '')
75 for s in sorted(state)]), '')
76
76
77 def submerge(repo, wctx, mctx, actx):
77 def submerge(repo, wctx, mctx, actx):
78 """delegated from merge.applyupdates: merging of .hgsubstate file
78 """delegated from merge.applyupdates: merging of .hgsubstate file
79 in working context, merging context and ancestor context"""
79 in working context, merging context and ancestor context"""
80 if mctx == actx: # backwards?
80 if mctx == actx: # backwards?
81 actx = wctx.p1()
81 actx = wctx.p1()
82 s1 = wctx.substate
82 s1 = wctx.substate
83 s2 = mctx.substate
83 s2 = mctx.substate
84 sa = actx.substate
84 sa = actx.substate
85 sm = {}
85 sm = {}
86
86
87 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
87 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
88
88
89 def debug(s, msg, r=""):
89 def debug(s, msg, r=""):
90 if r:
90 if r:
91 r = "%s:%s:%s" % r
91 r = "%s:%s:%s" % r
92 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
92 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
93
93
94 for s, l in s1.items():
94 for s, l in s1.items():
95 a = sa.get(s, nullstate)
95 a = sa.get(s, nullstate)
96 ld = l # local state with possible dirty flag for compares
96 ld = l # local state with possible dirty flag for compares
97 if wctx.sub(s).dirty():
97 if wctx.sub(s).dirty():
98 ld = (l[0], l[1] + "+")
98 ld = (l[0], l[1] + "+")
99 if wctx == actx: # overwrite
99 if wctx == actx: # overwrite
100 a = ld
100 a = ld
101
101
102 if s in s2:
102 if s in s2:
103 r = s2[s]
103 r = s2[s]
104 if ld == r or r == a: # no change or local is newer
104 if ld == r or r == a: # no change or local is newer
105 sm[s] = l
105 sm[s] = l
106 continue
106 continue
107 elif ld == a: # other side changed
107 elif ld == a: # other side changed
108 debug(s, "other changed, get", r)
108 debug(s, "other changed, get", r)
109 wctx.sub(s).get(r)
109 wctx.sub(s).get(r)
110 sm[s] = r
110 sm[s] = r
111 elif ld[0] != r[0]: # sources differ
111 elif ld[0] != r[0]: # sources differ
112 if repo.ui.promptchoice(
112 if repo.ui.promptchoice(
113 _(' subrepository sources for %s differ\n'
113 _(' subrepository sources for %s differ\n'
114 'use (l)ocal source (%s) or (r)emote source (%s)?')
114 'use (l)ocal source (%s) or (r)emote source (%s)?')
115 % (s, l[0], r[0]),
115 % (s, l[0], r[0]),
116 (_('&Local'), _('&Remote')), 0):
116 (_('&Local'), _('&Remote')), 0):
117 debug(s, "prompt changed, get", r)
117 debug(s, "prompt changed, get", r)
118 wctx.sub(s).get(r)
118 wctx.sub(s).get(r)
119 sm[s] = r
119 sm[s] = r
120 elif ld[1] == a[1]: # local side is unchanged
120 elif ld[1] == a[1]: # local side is unchanged
121 debug(s, "other side changed, get", r)
121 debug(s, "other side changed, get", r)
122 wctx.sub(s).get(r)
122 wctx.sub(s).get(r)
123 sm[s] = r
123 sm[s] = r
124 else:
124 else:
125 debug(s, "both sides changed, merge with", r)
125 debug(s, "both sides changed, merge with", r)
126 wctx.sub(s).merge(r)
126 wctx.sub(s).merge(r)
127 sm[s] = l
127 sm[s] = l
128 elif ld == a: # remote removed, local unchanged
128 elif ld == a: # remote removed, local unchanged
129 debug(s, "remote removed, remove")
129 debug(s, "remote removed, remove")
130 wctx.sub(s).remove()
130 wctx.sub(s).remove()
131 else:
131 else:
132 if repo.ui.promptchoice(
132 if repo.ui.promptchoice(
133 _(' local changed subrepository %s which remote removed\n'
133 _(' local changed subrepository %s which remote removed\n'
134 'use (c)hanged version or (d)elete?') % s,
134 'use (c)hanged version or (d)elete?') % s,
135 (_('&Changed'), _('&Delete')), 0):
135 (_('&Changed'), _('&Delete')), 0):
136 debug(s, "prompt remove")
136 debug(s, "prompt remove")
137 wctx.sub(s).remove()
137 wctx.sub(s).remove()
138
138
139 for s, r in s2.items():
139 for s, r in s2.items():
140 if s in s1:
140 if s in s1:
141 continue
141 continue
142 elif s not in sa:
142 elif s not in sa:
143 debug(s, "remote added, get", r)
143 debug(s, "remote added, get", r)
144 mctx.sub(s).get(r)
144 mctx.sub(s).get(r)
145 sm[s] = r
145 sm[s] = r
146 elif r != sa[s]:
146 elif r != sa[s]:
147 if repo.ui.promptchoice(
147 if repo.ui.promptchoice(
148 _(' remote changed subrepository %s which local removed\n'
148 _(' remote changed subrepository %s which local removed\n'
149 'use (c)hanged version or (d)elete?') % s,
149 'use (c)hanged version or (d)elete?') % s,
150 (_('&Changed'), _('&Delete')), 0) == 0:
150 (_('&Changed'), _('&Delete')), 0) == 0:
151 debug(s, "prompt recreate", r)
151 debug(s, "prompt recreate", r)
152 wctx.sub(s).get(r)
152 wctx.sub(s).get(r)
153 sm[s] = r
153 sm[s] = r
154
154
155 # record merged .hgsubstate
155 # record merged .hgsubstate
156 writestate(repo, sm)
156 writestate(repo, sm)
157
157
158 def reporelpath(repo):
158 def reporelpath(repo):
159 """return path to this (sub)repo as seen from outermost repo"""
159 """return path to this (sub)repo as seen from outermost repo"""
160 parent = repo
160 parent = repo
161 while hasattr(parent, '_subparent'):
161 while hasattr(parent, '_subparent'):
162 parent = parent._subparent
162 parent = parent._subparent
163 return repo.root[len(parent.root)+1:]
163 return repo.root[len(parent.root)+1:]
164
164
165 def subrelpath(sub):
165 def subrelpath(sub):
166 """return path to this subrepo as seen from outermost repo"""
166 """return path to this subrepo as seen from outermost repo"""
167 if not hasattr(sub, '_repo'):
167 if not hasattr(sub, '_repo'):
168 return sub._path
168 return sub._path
169 return reporelpath(sub._repo)
169 return reporelpath(sub._repo)
170
170
171 def _abssource(repo, push=False, abort=True):
171 def _abssource(repo, push=False, abort=True):
172 """return pull/push path of repo - either based on parent repo .hgsub info
172 """return pull/push path of repo - either based on parent repo .hgsub info
173 or on the top repo config. Abort or return None if no source found."""
173 or on the top repo config. Abort or return None if no source found."""
174 if hasattr(repo, '_subparent'):
174 if hasattr(repo, '_subparent'):
175 source = repo._subsource
175 source = repo._subsource
176 if source.startswith('/') or '://' in source:
176 if source.startswith('/') or '://' in source:
177 return source
177 return source
178 parent = _abssource(repo._subparent, push, abort=False)
178 parent = _abssource(repo._subparent, push, abort=False)
179 if parent:
179 if parent:
180 if '://' in parent:
180 if '://' in parent:
181 if parent[-1] == '/':
181 if parent[-1] == '/':
182 parent = parent[:-1]
182 parent = parent[:-1]
183 r = urlparse.urlparse(parent + '/' + source)
183 r = urlparse.urlparse(parent + '/' + source)
184 r = urlparse.urlunparse((r[0], r[1],
184 r = urlparse.urlunparse((r[0], r[1],
185 posixpath.normpath(r[2]),
185 posixpath.normpath(r[2]),
186 r[3], r[4], r[5]))
186 r[3], r[4], r[5]))
187 return r
187 return r
188 else: # plain file system path
188 else: # plain file system path
189 return posixpath.normpath(os.path.join(parent, repo._subsource))
189 return posixpath.normpath(os.path.join(parent, repo._subsource))
190 else: # recursion reached top repo
190 else: # recursion reached top repo
191 if hasattr(repo, '_subtoppath'):
191 if hasattr(repo, '_subtoppath'):
192 return repo._subtoppath
192 return repo._subtoppath
193 if push and repo.ui.config('paths', 'default-push'):
193 if push and repo.ui.config('paths', 'default-push'):
194 return repo.ui.config('paths', 'default-push')
194 return repo.ui.config('paths', 'default-push')
195 if repo.ui.config('paths', 'default'):
195 if repo.ui.config('paths', 'default'):
196 return repo.ui.config('paths', 'default')
196 return repo.ui.config('paths', 'default')
197 if abort:
197 if abort:
198 raise util.Abort(_("default path for subrepository %s not found") %
198 raise util.Abort(_("default path for subrepository %s not found") %
199 reporelpath(repo))
199 reporelpath(repo))
200
200
201 def itersubrepos(ctx1, ctx2):
201 def itersubrepos(ctx1, ctx2):
202 """find subrepos in ctx1 or ctx2"""
202 """find subrepos in ctx1 or ctx2"""
203 # Create a (subpath, ctx) mapping where we prefer subpaths from
203 # Create a (subpath, ctx) mapping where we prefer subpaths from
204 # ctx1. The subpaths from ctx2 are important when the .hgsub file
204 # ctx1. The subpaths from ctx2 are important when the .hgsub file
205 # has been modified (in ctx2) but not yet committed (in ctx1).
205 # has been modified (in ctx2) but not yet committed (in ctx1).
206 subpaths = dict.fromkeys(ctx2.substate, ctx2)
206 subpaths = dict.fromkeys(ctx2.substate, ctx2)
207 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
207 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
208 for subpath, ctx in sorted(subpaths.iteritems()):
208 for subpath, ctx in sorted(subpaths.iteritems()):
209 yield subpath, ctx.sub(subpath)
209 yield subpath, ctx.sub(subpath)
210
210
211 def subrepo(ctx, path):
211 def subrepo(ctx, path):
212 """return instance of the right subrepo class for subrepo in path"""
212 """return instance of the right subrepo class for subrepo in path"""
213 # subrepo inherently violates our import layering rules
213 # subrepo inherently violates our import layering rules
214 # because it wants to make repo objects from deep inside the stack
214 # because it wants to make repo objects from deep inside the stack
215 # so we manually delay the circular imports to not break
215 # so we manually delay the circular imports to not break
216 # scripts that don't use our demand-loading
216 # scripts that don't use our demand-loading
217 global hg
217 global hg
218 import hg as h
218 import hg as h
219 hg = h
219 hg = h
220
220
221 util.path_auditor(ctx._repo.root)(path)
221 util.path_auditor(ctx._repo.root)(path)
222 state = ctx.substate.get(path, nullstate)
222 state = ctx.substate.get(path, nullstate)
223 if state[2] not in types:
223 if state[2] not in types:
224 raise util.Abort(_('unknown subrepo type %s') % state[2])
224 raise util.Abort(_('unknown subrepo type %s') % state[2])
225 return types[state[2]](ctx, path, state[:2])
225 return types[state[2]](ctx, path, state[:2])
226
226
227 # subrepo classes need to implement the following abstract class:
227 # subrepo classes need to implement the following abstract class:
228
228
229 class abstractsubrepo(object):
229 class abstractsubrepo(object):
230
230
231 def dirty(self):
231 def dirty(self):
232 """returns true if the dirstate of the subrepo does not match
232 """returns true if the dirstate of the subrepo does not match
233 current stored state
233 current stored state
234 """
234 """
235 raise NotImplementedError
235 raise NotImplementedError
236
236
237 def checknested(self, path):
237 def checknested(self, path):
238 """check if path is a subrepository within this repository"""
238 """check if path is a subrepository within this repository"""
239 return False
239 return False
240
240
241 def commit(self, text, user, date):
241 def commit(self, text, user, date):
242 """commit the current changes to the subrepo with the given
242 """commit the current changes to the subrepo with the given
243 log message. Use given user and date if possible. Return the
243 log message. Use given user and date if possible. Return the
244 new state of the subrepo.
244 new state of the subrepo.
245 """
245 """
246 raise NotImplementedError
246 raise NotImplementedError
247
247
248 def remove(self):
248 def remove(self):
249 """remove the subrepo
249 """remove the subrepo
250
250
251 (should verify the dirstate is not dirty first)
251 (should verify the dirstate is not dirty first)
252 """
252 """
253 raise NotImplementedError
253 raise NotImplementedError
254
254
255 def get(self, state):
255 def get(self, state):
256 """run whatever commands are needed to put the subrepo into
256 """run whatever commands are needed to put the subrepo into
257 this state
257 this state
258 """
258 """
259 raise NotImplementedError
259 raise NotImplementedError
260
260
261 def merge(self, state):
261 def merge(self, state):
262 """merge currently-saved state with the new state."""
262 """merge currently-saved state with the new state."""
263 raise NotImplementedError
263 raise NotImplementedError
264
264
265 def push(self, force):
265 def push(self, force):
266 """perform whatever action is analogous to 'hg push'
266 """perform whatever action is analogous to 'hg push'
267
267
268 This may be a no-op on some systems.
268 This may be a no-op on some systems.
269 """
269 """
270 raise NotImplementedError
270 raise NotImplementedError
271
271
272 def add(self, ui, match, dryrun, prefix):
272 def add(self, ui, match, dryrun, prefix):
273 return []
273 return []
274
274
275 def status(self, rev2, **opts):
275 def status(self, rev2, **opts):
276 return [], [], [], [], [], [], []
276 return [], [], [], [], [], [], []
277
277
278 def diff(self, diffopts, node2, match, prefix, **opts):
278 def diff(self, diffopts, node2, match, prefix, **opts):
279 pass
279 pass
280
280
281 def outgoing(self, ui, dest, opts):
281 def outgoing(self, ui, dest, opts):
282 return 1
282 return 1
283
283
284 def incoming(self, ui, source, opts):
284 def incoming(self, ui, source, opts):
285 return 1
285 return 1
286
286
287 def files(self):
287 def files(self):
288 """return filename iterator"""
288 """return filename iterator"""
289 raise NotImplementedError
289 raise NotImplementedError
290
290
291 def filedata(self, name):
291 def filedata(self, name):
292 """return file data"""
292 """return file data"""
293 raise NotImplementedError
293 raise NotImplementedError
294
294
295 def fileflags(self, name):
295 def fileflags(self, name):
296 """return file flags"""
296 """return file flags"""
297 return ''
297 return ''
298
298
299 def archive(self, archiver, prefix):
299 def archive(self, archiver, prefix):
300 for name in self.files():
300 for name in self.files():
301 flags = self.fileflags(name)
301 flags = self.fileflags(name)
302 mode = 'x' in flags and 0755 or 0644
302 mode = 'x' in flags and 0755 or 0644
303 symlink = 'l' in flags
303 symlink = 'l' in flags
304 archiver.addfile(os.path.join(prefix, self._path, name),
304 archiver.addfile(os.path.join(prefix, self._path, name),
305 mode, symlink, self.filedata(name))
305 mode, symlink, self.filedata(name))
306
306
307
307
308 class hgsubrepo(abstractsubrepo):
308 class hgsubrepo(abstractsubrepo):
309 def __init__(self, ctx, path, state):
309 def __init__(self, ctx, path, state):
310 self._path = path
310 self._path = path
311 self._state = state
311 self._state = state
312 r = ctx._repo
312 r = ctx._repo
313 root = r.wjoin(path)
313 root = r.wjoin(path)
314 create = False
314 create = False
315 if not os.path.exists(os.path.join(root, '.hg')):
315 if not os.path.exists(os.path.join(root, '.hg')):
316 create = True
316 create = True
317 util.makedirs(root)
317 util.makedirs(root)
318 self._repo = hg.repository(r.ui, root, create=create)
318 self._repo = hg.repository(r.ui, root, create=create)
319 self._repo._subparent = r
319 self._repo._subparent = r
320 self._repo._subsource = state[0]
320 self._repo._subsource = state[0]
321
321
322 if create:
322 if create:
323 fp = self._repo.opener("hgrc", "w", text=True)
323 fp = self._repo.opener("hgrc", "w", text=True)
324 fp.write('[paths]\n')
324 fp.write('[paths]\n')
325
325
326 def addpathconfig(key, value):
326 def addpathconfig(key, value):
327 if value:
327 if value:
328 fp.write('%s = %s\n' % (key, value))
328 fp.write('%s = %s\n' % (key, value))
329 self._repo.ui.setconfig('paths', key, value)
329 self._repo.ui.setconfig('paths', key, value)
330
330
331 defpath = _abssource(self._repo, abort=False)
331 defpath = _abssource(self._repo, abort=False)
332 defpushpath = _abssource(self._repo, True, abort=False)
332 defpushpath = _abssource(self._repo, True, abort=False)
333 addpathconfig('default', defpath)
333 addpathconfig('default', defpath)
334 if defpath != defpushpath:
334 if defpath != defpushpath:
335 addpathconfig('default-push', defpushpath)
335 addpathconfig('default-push', defpushpath)
336 fp.close()
336 fp.close()
337
337
338 def add(self, ui, match, dryrun, prefix):
338 def add(self, ui, match, dryrun, prefix):
339 return cmdutil.add(ui, self._repo, match, dryrun, True,
339 return cmdutil.add(ui, self._repo, match, dryrun, True,
340 os.path.join(prefix, self._path))
340 os.path.join(prefix, self._path))
341
341
342 def status(self, rev2, **opts):
342 def status(self, rev2, **opts):
343 try:
343 try:
344 rev1 = self._state[1]
344 rev1 = self._state[1]
345 ctx1 = self._repo[rev1]
345 ctx1 = self._repo[rev1]
346 ctx2 = self._repo[rev2]
346 ctx2 = self._repo[rev2]
347 return self._repo.status(ctx1, ctx2, **opts)
347 return self._repo.status(ctx1, ctx2, **opts)
348 except error.RepoLookupError, inst:
348 except error.RepoLookupError, inst:
349 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
349 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
350 % (inst, subrelpath(self)))
350 % (inst, subrelpath(self)))
351 return [], [], [], [], [], [], []
351 return [], [], [], [], [], [], []
352
352
353 def diff(self, diffopts, node2, match, prefix, **opts):
353 def diff(self, diffopts, node2, match, prefix, **opts):
354 try:
354 try:
355 node1 = node.bin(self._state[1])
355 node1 = node.bin(self._state[1])
356 # We currently expect node2 to come from substate and be
356 # We currently expect node2 to come from substate and be
357 # in hex format
357 # in hex format
358 if node2 is not None:
358 if node2 is not None:
359 node2 = node.bin(node2)
359 node2 = node.bin(node2)
360 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
360 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
361 node1, node2, match,
361 node1, node2, match,
362 prefix=os.path.join(prefix, self._path),
362 prefix=os.path.join(prefix, self._path),
363 listsubrepos=True, **opts)
363 listsubrepos=True, **opts)
364 except error.RepoLookupError, inst:
364 except error.RepoLookupError, inst:
365 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
365 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
366 % (inst, subrelpath(self)))
366 % (inst, subrelpath(self)))
367
367
368 def archive(self, archiver, prefix):
368 def archive(self, archiver, prefix):
369 abstractsubrepo.archive(self, archiver, prefix)
369 abstractsubrepo.archive(self, archiver, prefix)
370
370
371 rev = self._state[1]
371 rev = self._state[1]
372 ctx = self._repo[rev]
372 ctx = self._repo[rev]
373 for subpath in ctx.substate:
373 for subpath in ctx.substate:
374 s = subrepo(ctx, subpath)
374 s = subrepo(ctx, subpath)
375 s.archive(archiver, os.path.join(prefix, self._path))
375 s.archive(archiver, os.path.join(prefix, self._path))
376
376
377 def dirty(self):
377 def dirty(self):
378 r = self._state[1]
378 r = self._state[1]
379 if r == '':
379 if r == '':
380 return True
380 return True
381 w = self._repo[None]
381 w = self._repo[None]
382 if w.p1() != self._repo[r]: # version checked out change
382 if w.p1() != self._repo[r]: # version checked out change
383 return True
383 return True
384 return w.dirty() # working directory changed
384 return w.dirty() # working directory changed
385
385
386 def checknested(self, path):
386 def checknested(self, path):
387 return self._repo._checknested(self._repo.wjoin(path))
387 return self._repo._checknested(self._repo.wjoin(path))
388
388
389 def commit(self, text, user, date):
389 def commit(self, text, user, date):
390 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
390 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
391 n = self._repo.commit(text, user, date)
391 n = self._repo.commit(text, user, date)
392 if not n:
392 if not n:
393 return self._repo['.'].hex() # different version checked out
393 return self._repo['.'].hex() # different version checked out
394 return node.hex(n)
394 return node.hex(n)
395
395
396 def remove(self):
396 def remove(self):
397 # we can't fully delete the repository as it may contain
397 # we can't fully delete the repository as it may contain
398 # local-only history
398 # local-only history
399 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
399 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
400 hg.clean(self._repo, node.nullid, False)
400 hg.clean(self._repo, node.nullid, False)
401
401
402 def _get(self, state):
402 def _get(self, state):
403 source, revision, kind = state
403 source, revision, kind = state
404 try:
404 try:
405 self._repo.lookup(revision)
405 self._repo.lookup(revision)
406 except error.RepoError:
406 except error.RepoError:
407 self._repo._subsource = source
407 self._repo._subsource = source
408 srcurl = _abssource(self._repo)
408 srcurl = _abssource(self._repo)
409 self._repo.ui.status(_('pulling subrepo %s from %s\n')
409 self._repo.ui.status(_('pulling subrepo %s from %s\n')
410 % (subrelpath(self), srcurl))
410 % (subrelpath(self), srcurl))
411 other = hg.repository(self._repo.ui, srcurl)
411 other = hg.repository(self._repo.ui, srcurl)
412 self._repo.pull(other)
412 self._repo.pull(other)
413
413
414 def get(self, state):
414 def get(self, state):
415 self._get(state)
415 self._get(state)
416 source, revision, kind = state
416 source, revision, kind = state
417 self._repo.ui.debug("getting subrepo %s\n" % self._path)
417 self._repo.ui.debug("getting subrepo %s\n" % self._path)
418 hg.clean(self._repo, revision, False)
418 hg.clean(self._repo, revision, False)
419
419
420 def merge(self, state):
420 def merge(self, state):
421 self._get(state)
421 self._get(state)
422 cur = self._repo['.']
422 cur = self._repo['.']
423 dst = self._repo[state[1]]
423 dst = self._repo[state[1]]
424 anc = dst.ancestor(cur)
424 anc = dst.ancestor(cur)
425 if anc == cur:
425 if anc == cur:
426 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
426 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
427 hg.update(self._repo, state[1])
427 hg.update(self._repo, state[1])
428 elif anc == dst:
428 elif anc == dst:
429 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
429 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
430 else:
430 else:
431 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
431 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
432 hg.merge(self._repo, state[1], remind=False)
432 hg.merge(self._repo, state[1], remind=False)
433
433
434 def push(self, force):
434 def push(self, force):
435 # push subrepos depth-first for coherent ordering
435 # push subrepos depth-first for coherent ordering
436 c = self._repo['']
436 c = self._repo['']
437 subs = c.substate # only repos that are committed
437 subs = c.substate # only repos that are committed
438 for s in sorted(subs):
438 for s in sorted(subs):
439 if not c.sub(s).push(force):
439 if not c.sub(s).push(force):
440 return False
440 return False
441
441
442 dsturl = _abssource(self._repo, True)
442 dsturl = _abssource(self._repo, True)
443 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
443 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
444 (subrelpath(self), dsturl))
444 (subrelpath(self), dsturl))
445 other = hg.repository(self._repo.ui, dsturl)
445 other = hg.repository(self._repo.ui, dsturl)
446 return self._repo.push(other, force)
446 return self._repo.push(other, force)
447
447
448 def outgoing(self, ui, dest, opts):
448 def outgoing(self, ui, dest, opts):
449 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
449 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
450
450
451 def incoming(self, ui, source, opts):
451 def incoming(self, ui, source, opts):
452 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
452 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
453
453
454 def files(self):
454 def files(self):
455 rev = self._state[1]
455 rev = self._state[1]
456 ctx = self._repo[rev]
456 ctx = self._repo[rev]
457 return ctx.manifest()
457 return ctx.manifest()
458
458
459 def filedata(self, name):
459 def filedata(self, name):
460 rev = self._state[1]
460 rev = self._state[1]
461 return self._repo[rev][name].data()
461 return self._repo[rev][name].data()
462
462
463 def fileflags(self, name):
463 def fileflags(self, name):
464 rev = self._state[1]
464 rev = self._state[1]
465 ctx = self._repo[rev]
465 ctx = self._repo[rev]
466 return ctx.flags(name)
466 return ctx.flags(name)
467
467
468
468
469 class svnsubrepo(abstractsubrepo):
469 class svnsubrepo(abstractsubrepo):
470 def __init__(self, ctx, path, state):
470 def __init__(self, ctx, path, state):
471 self._path = path
471 self._path = path
472 self._state = state
472 self._state = state
473 self._ctx = ctx
473 self._ctx = ctx
474 self._ui = ctx._repo.ui
474 self._ui = ctx._repo.ui
475
475
476 def _svncommand(self, commands, filename=''):
476 def _svncommand(self, commands, filename=''):
477 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
477 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
478 cmd = ['svn'] + commands + [path]
478 cmd = ['svn'] + commands + [path]
479 cmd = [util.shellquote(arg) for arg in cmd]
479 cmd = [util.shellquote(arg) for arg in cmd]
480 cmd = util.quotecommand(' '.join(cmd))
480 cmd = util.quotecommand(' '.join(cmd))
481 env = dict(os.environ)
481 env = dict(os.environ)
482 # Avoid localized output, preserve current locale for everything else.
482 # Avoid localized output, preserve current locale for everything else.
483 env['LC_MESSAGES'] = 'C'
483 env['LC_MESSAGES'] = 'C'
484 write, read, err = util.popen3(cmd, env=env, newlines=True)
484 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
485 retdata = read.read()
485 close_fds=util.closefds,
486 err = err.read().strip()
486 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
487 if err:
487 universal_newlines=True, env=env)
488 raise util.Abort(err)
488 stdout, stderr = p.communicate()
489 return retdata
489 stderr = stderr.strip()
490 if stderr:
491 raise util.Abort(stderr)
492 return stdout
490
493
491 def _wcrev(self):
494 def _wcrev(self):
492 output = self._svncommand(['info', '--xml'])
495 output = self._svncommand(['info', '--xml'])
493 doc = xml.dom.minidom.parseString(output)
496 doc = xml.dom.minidom.parseString(output)
494 entries = doc.getElementsByTagName('entry')
497 entries = doc.getElementsByTagName('entry')
495 if not entries:
498 if not entries:
496 return '0'
499 return '0'
497 return str(entries[0].getAttribute('revision')) or '0'
500 return str(entries[0].getAttribute('revision')) or '0'
498
501
499 def _wcchanged(self):
502 def _wcchanged(self):
500 """Return (changes, extchanges) where changes is True
503 """Return (changes, extchanges) where changes is True
501 if the working directory was changed, and extchanges is
504 if the working directory was changed, and extchanges is
502 True if any of these changes concern an external entry.
505 True if any of these changes concern an external entry.
503 """
506 """
504 output = self._svncommand(['status', '--xml'])
507 output = self._svncommand(['status', '--xml'])
505 externals, changes = [], []
508 externals, changes = [], []
506 doc = xml.dom.minidom.parseString(output)
509 doc = xml.dom.minidom.parseString(output)
507 for e in doc.getElementsByTagName('entry'):
510 for e in doc.getElementsByTagName('entry'):
508 s = e.getElementsByTagName('wc-status')
511 s = e.getElementsByTagName('wc-status')
509 if not s:
512 if not s:
510 continue
513 continue
511 item = s[0].getAttribute('item')
514 item = s[0].getAttribute('item')
512 props = s[0].getAttribute('props')
515 props = s[0].getAttribute('props')
513 path = e.getAttribute('path')
516 path = e.getAttribute('path')
514 if item == 'external':
517 if item == 'external':
515 externals.append(path)
518 externals.append(path)
516 if (item not in ('', 'normal', 'unversioned', 'external')
519 if (item not in ('', 'normal', 'unversioned', 'external')
517 or props not in ('', 'none')):
520 or props not in ('', 'none')):
518 changes.append(path)
521 changes.append(path)
519 for path in changes:
522 for path in changes:
520 for ext in externals:
523 for ext in externals:
521 if path == ext or path.startswith(ext + os.sep):
524 if path == ext or path.startswith(ext + os.sep):
522 return True, True
525 return True, True
523 return bool(changes), False
526 return bool(changes), False
524
527
525 def dirty(self):
528 def dirty(self):
526 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
529 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
527 return False
530 return False
528 return True
531 return True
529
532
530 def commit(self, text, user, date):
533 def commit(self, text, user, date):
531 # user and date are out of our hands since svn is centralized
534 # user and date are out of our hands since svn is centralized
532 changed, extchanged = self._wcchanged()
535 changed, extchanged = self._wcchanged()
533 if not changed:
536 if not changed:
534 return self._wcrev()
537 return self._wcrev()
535 if extchanged:
538 if extchanged:
536 # Do not try to commit externals
539 # Do not try to commit externals
537 raise util.Abort(_('cannot commit svn externals'))
540 raise util.Abort(_('cannot commit svn externals'))
538 commitinfo = self._svncommand(['commit', '-m', text])
541 commitinfo = self._svncommand(['commit', '-m', text])
539 self._ui.status(commitinfo)
542 self._ui.status(commitinfo)
540 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
543 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
541 if not newrev:
544 if not newrev:
542 raise util.Abort(commitinfo.splitlines()[-1])
545 raise util.Abort(commitinfo.splitlines()[-1])
543 newrev = newrev.groups()[0]
546 newrev = newrev.groups()[0]
544 self._ui.status(self._svncommand(['update', '-r', newrev]))
547 self._ui.status(self._svncommand(['update', '-r', newrev]))
545 return newrev
548 return newrev
546
549
547 def remove(self):
550 def remove(self):
548 if self.dirty():
551 if self.dirty():
549 self._ui.warn(_('not removing repo %s because '
552 self._ui.warn(_('not removing repo %s because '
550 'it has changes.\n' % self._path))
553 'it has changes.\n' % self._path))
551 return
554 return
552 self._ui.note(_('removing subrepo %s\n') % self._path)
555 self._ui.note(_('removing subrepo %s\n') % self._path)
553
556
554 def onerror(function, path, excinfo):
557 def onerror(function, path, excinfo):
555 if function is not os.remove:
558 if function is not os.remove:
556 raise
559 raise
557 # read-only files cannot be unlinked under Windows
560 # read-only files cannot be unlinked under Windows
558 s = os.stat(path)
561 s = os.stat(path)
559 if (s.st_mode & stat.S_IWRITE) != 0:
562 if (s.st_mode & stat.S_IWRITE) != 0:
560 raise
563 raise
561 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
564 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
562 os.remove(path)
565 os.remove(path)
563
566
564 shutil.rmtree(self._ctx._repo.wjoin(self._path), onerror=onerror)
567 shutil.rmtree(self._ctx._repo.wjoin(self._path), onerror=onerror)
565
568
566 def get(self, state):
569 def get(self, state):
567 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
570 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
568 if not re.search('Checked out revision [0-9]+.', status):
571 if not re.search('Checked out revision [0-9]+.', status):
569 raise util.Abort(status.splitlines()[-1])
572 raise util.Abort(status.splitlines()[-1])
570 self._ui.status(status)
573 self._ui.status(status)
571
574
572 def merge(self, state):
575 def merge(self, state):
573 old = int(self._state[1])
576 old = int(self._state[1])
574 new = int(state[1])
577 new = int(state[1])
575 if new > old:
578 if new > old:
576 self.get(state)
579 self.get(state)
577
580
578 def push(self, force):
581 def push(self, force):
579 # push is a no-op for SVN
582 # push is a no-op for SVN
580 return True
583 return True
581
584
582 def files(self):
585 def files(self):
583 output = self._svncommand(['list'])
586 output = self._svncommand(['list'])
584 # This works because svn forbids \n in filenames.
587 # This works because svn forbids \n in filenames.
585 return output.splitlines()
588 return output.splitlines()
586
589
587 def filedata(self, name):
590 def filedata(self, name):
588 return self._svncommand(['cat'], name)
591 return self._svncommand(['cat'], name)
589
592
590
593
591 types = {
594 types = {
592 'hg': hgsubrepo,
595 'hg': hgsubrepo,
593 'svn': svnsubrepo,
596 'svn': svnsubrepo,
594 }
597 }
General Comments 0
You need to be logged in to leave comments. Login now