##// END OF EJS Templates
subrepo: change arguments of abstractsubrepo.__init__ (API)...
FUJIWARA Katsunori -
r24671:98ab035e default
parent child Browse files
Show More
@@ -1,1815 +1,1819 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 copy
8 import copy
9 import errno, os, re, shutil, posixpath, sys
9 import errno, os, re, shutil, posixpath, sys
10 import xml.dom.minidom
10 import xml.dom.minidom
11 import stat, subprocess, tarfile
11 import stat, subprocess, tarfile
12 from i18n import _
12 from i18n import _
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 import phases
14 import phases
15 import pathutil
15 import pathutil
16 import exchange
16 import exchange
17 hg = None
17 hg = None
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 nullstate = ('', '', 'empty')
20 nullstate = ('', '', 'empty')
21
21
22 def _expandedabspath(path):
22 def _expandedabspath(path):
23 '''
23 '''
24 get a path or url and if it is a path expand it and return an absolute path
24 get a path or url and if it is a path expand it and return an absolute path
25 '''
25 '''
26 expandedpath = util.urllocalpath(util.expandpath(path))
26 expandedpath = util.urllocalpath(util.expandpath(path))
27 u = util.url(expandedpath)
27 u = util.url(expandedpath)
28 if not u.scheme:
28 if not u.scheme:
29 path = util.normpath(os.path.abspath(u.path))
29 path = util.normpath(os.path.abspath(u.path))
30 return path
30 return path
31
31
32 def _getstorehashcachename(remotepath):
32 def _getstorehashcachename(remotepath):
33 '''get a unique filename for the store hash cache of a remote repository'''
33 '''get a unique filename for the store hash cache of a remote repository'''
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35
35
36 class SubrepoAbort(error.Abort):
36 class SubrepoAbort(error.Abort):
37 """Exception class used to avoid handling a subrepo error more than once"""
37 """Exception class used to avoid handling a subrepo error more than once"""
38 def __init__(self, *args, **kw):
38 def __init__(self, *args, **kw):
39 error.Abort.__init__(self, *args, **kw)
39 error.Abort.__init__(self, *args, **kw)
40 self.subrepo = kw.get('subrepo')
40 self.subrepo = kw.get('subrepo')
41 self.cause = kw.get('cause')
41 self.cause = kw.get('cause')
42
42
43 def annotatesubrepoerror(func):
43 def annotatesubrepoerror(func):
44 def decoratedmethod(self, *args, **kargs):
44 def decoratedmethod(self, *args, **kargs):
45 try:
45 try:
46 res = func(self, *args, **kargs)
46 res = func(self, *args, **kargs)
47 except SubrepoAbort, ex:
47 except SubrepoAbort, ex:
48 # This exception has already been handled
48 # This exception has already been handled
49 raise ex
49 raise ex
50 except error.Abort, ex:
50 except error.Abort, ex:
51 subrepo = subrelpath(self)
51 subrepo = subrelpath(self)
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 # avoid handling this exception by raising a SubrepoAbort exception
53 # avoid handling this exception by raising a SubrepoAbort exception
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 cause=sys.exc_info())
55 cause=sys.exc_info())
56 return res
56 return res
57 return decoratedmethod
57 return decoratedmethod
58
58
59 def state(ctx, ui):
59 def state(ctx, ui):
60 """return a state dict, mapping subrepo paths configured in .hgsub
60 """return a state dict, mapping subrepo paths configured in .hgsub
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 (key in types dict))
62 (key in types dict))
63 """
63 """
64 p = config.config()
64 p = config.config()
65 def read(f, sections=None, remap=None):
65 def read(f, sections=None, remap=None):
66 if f in ctx:
66 if f in ctx:
67 try:
67 try:
68 data = ctx[f].data()
68 data = ctx[f].data()
69 except IOError, err:
69 except IOError, err:
70 if err.errno != errno.ENOENT:
70 if err.errno != errno.ENOENT:
71 raise
71 raise
72 # handle missing subrepo spec files as removed
72 # handle missing subrepo spec files as removed
73 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
73 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
74 util.pathto(ctx.repo().root, ctx.repo().getcwd(), f))
74 util.pathto(ctx.repo().root, ctx.repo().getcwd(), f))
75 return
75 return
76 p.parse(f, data, sections, remap, read)
76 p.parse(f, data, sections, remap, read)
77 else:
77 else:
78 repo = ctx.repo()
78 repo = ctx.repo()
79 raise util.Abort(_("subrepo spec file \'%s\' not found") %
79 raise util.Abort(_("subrepo spec file \'%s\' not found") %
80 util.pathto(repo.root, repo.getcwd(), f))
80 util.pathto(repo.root, repo.getcwd(), f))
81
81
82 if '.hgsub' in ctx:
82 if '.hgsub' in ctx:
83 read('.hgsub')
83 read('.hgsub')
84
84
85 for path, src in ui.configitems('subpaths'):
85 for path, src in ui.configitems('subpaths'):
86 p.set('subpaths', path, src, ui.configsource('subpaths', path))
86 p.set('subpaths', path, src, ui.configsource('subpaths', path))
87
87
88 rev = {}
88 rev = {}
89 if '.hgsubstate' in ctx:
89 if '.hgsubstate' in ctx:
90 try:
90 try:
91 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
91 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
92 l = l.lstrip()
92 l = l.lstrip()
93 if not l:
93 if not l:
94 continue
94 continue
95 try:
95 try:
96 revision, path = l.split(" ", 1)
96 revision, path = l.split(" ", 1)
97 except ValueError:
97 except ValueError:
98 repo = ctx.repo()
98 repo = ctx.repo()
99 raise util.Abort(_("invalid subrepository revision "
99 raise util.Abort(_("invalid subrepository revision "
100 "specifier in \'%s\' line %d")
100 "specifier in \'%s\' line %d")
101 % (util.pathto(repo.root, repo.getcwd(),
101 % (util.pathto(repo.root, repo.getcwd(),
102 '.hgsubstate'), (i + 1)))
102 '.hgsubstate'), (i + 1)))
103 rev[path] = revision
103 rev[path] = revision
104 except IOError, err:
104 except IOError, err:
105 if err.errno != errno.ENOENT:
105 if err.errno != errno.ENOENT:
106 raise
106 raise
107
107
108 def remap(src):
108 def remap(src):
109 for pattern, repl in p.items('subpaths'):
109 for pattern, repl in p.items('subpaths'):
110 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
110 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
111 # does a string decode.
111 # does a string decode.
112 repl = repl.encode('string-escape')
112 repl = repl.encode('string-escape')
113 # However, we still want to allow back references to go
113 # However, we still want to allow back references to go
114 # through unharmed, so we turn r'\\1' into r'\1'. Again,
114 # through unharmed, so we turn r'\\1' into r'\1'. Again,
115 # extra escapes are needed because re.sub string decodes.
115 # extra escapes are needed because re.sub string decodes.
116 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
116 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
117 try:
117 try:
118 src = re.sub(pattern, repl, src, 1)
118 src = re.sub(pattern, repl, src, 1)
119 except re.error, e:
119 except re.error, e:
120 raise util.Abort(_("bad subrepository pattern in %s: %s")
120 raise util.Abort(_("bad subrepository pattern in %s: %s")
121 % (p.source('subpaths', pattern), e))
121 % (p.source('subpaths', pattern), e))
122 return src
122 return src
123
123
124 state = {}
124 state = {}
125 for path, src in p[''].items():
125 for path, src in p[''].items():
126 kind = 'hg'
126 kind = 'hg'
127 if src.startswith('['):
127 if src.startswith('['):
128 if ']' not in src:
128 if ']' not in src:
129 raise util.Abort(_('missing ] in subrepo source'))
129 raise util.Abort(_('missing ] in subrepo source'))
130 kind, src = src.split(']', 1)
130 kind, src = src.split(']', 1)
131 kind = kind[1:]
131 kind = kind[1:]
132 src = src.lstrip() # strip any extra whitespace after ']'
132 src = src.lstrip() # strip any extra whitespace after ']'
133
133
134 if not util.url(src).isabs():
134 if not util.url(src).isabs():
135 parent = _abssource(ctx.repo(), abort=False)
135 parent = _abssource(ctx.repo(), abort=False)
136 if parent:
136 if parent:
137 parent = util.url(parent)
137 parent = util.url(parent)
138 parent.path = posixpath.join(parent.path or '', src)
138 parent.path = posixpath.join(parent.path or '', src)
139 parent.path = posixpath.normpath(parent.path)
139 parent.path = posixpath.normpath(parent.path)
140 joined = str(parent)
140 joined = str(parent)
141 # Remap the full joined path and use it if it changes,
141 # Remap the full joined path and use it if it changes,
142 # else remap the original source.
142 # else remap the original source.
143 remapped = remap(joined)
143 remapped = remap(joined)
144 if remapped == joined:
144 if remapped == joined:
145 src = remap(src)
145 src = remap(src)
146 else:
146 else:
147 src = remapped
147 src = remapped
148
148
149 src = remap(src)
149 src = remap(src)
150 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
150 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
151
151
152 return state
152 return state
153
153
154 def writestate(repo, state):
154 def writestate(repo, state):
155 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
155 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
156 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
156 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
157 repo.wwrite('.hgsubstate', ''.join(lines), '')
157 repo.wwrite('.hgsubstate', ''.join(lines), '')
158
158
159 def submerge(repo, wctx, mctx, actx, overwrite):
159 def submerge(repo, wctx, mctx, actx, overwrite):
160 """delegated from merge.applyupdates: merging of .hgsubstate file
160 """delegated from merge.applyupdates: merging of .hgsubstate file
161 in working context, merging context and ancestor context"""
161 in working context, merging context and ancestor context"""
162 if mctx == actx: # backwards?
162 if mctx == actx: # backwards?
163 actx = wctx.p1()
163 actx = wctx.p1()
164 s1 = wctx.substate
164 s1 = wctx.substate
165 s2 = mctx.substate
165 s2 = mctx.substate
166 sa = actx.substate
166 sa = actx.substate
167 sm = {}
167 sm = {}
168
168
169 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
169 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
170
170
171 def debug(s, msg, r=""):
171 def debug(s, msg, r=""):
172 if r:
172 if r:
173 r = "%s:%s:%s" % r
173 r = "%s:%s:%s" % r
174 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
174 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
175
175
176 for s, l in sorted(s1.iteritems()):
176 for s, l in sorted(s1.iteritems()):
177 a = sa.get(s, nullstate)
177 a = sa.get(s, nullstate)
178 ld = l # local state with possible dirty flag for compares
178 ld = l # local state with possible dirty flag for compares
179 if wctx.sub(s).dirty():
179 if wctx.sub(s).dirty():
180 ld = (l[0], l[1] + "+")
180 ld = (l[0], l[1] + "+")
181 if wctx == actx: # overwrite
181 if wctx == actx: # overwrite
182 a = ld
182 a = ld
183
183
184 if s in s2:
184 if s in s2:
185 r = s2[s]
185 r = s2[s]
186 if ld == r or r == a: # no change or local is newer
186 if ld == r or r == a: # no change or local is newer
187 sm[s] = l
187 sm[s] = l
188 continue
188 continue
189 elif ld == a: # other side changed
189 elif ld == a: # other side changed
190 debug(s, "other changed, get", r)
190 debug(s, "other changed, get", r)
191 wctx.sub(s).get(r, overwrite)
191 wctx.sub(s).get(r, overwrite)
192 sm[s] = r
192 sm[s] = r
193 elif ld[0] != r[0]: # sources differ
193 elif ld[0] != r[0]: # sources differ
194 if repo.ui.promptchoice(
194 if repo.ui.promptchoice(
195 _(' subrepository sources for %s differ\n'
195 _(' subrepository sources for %s differ\n'
196 'use (l)ocal source (%s) or (r)emote source (%s)?'
196 'use (l)ocal source (%s) or (r)emote source (%s)?'
197 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
197 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
198 debug(s, "prompt changed, get", r)
198 debug(s, "prompt changed, get", r)
199 wctx.sub(s).get(r, overwrite)
199 wctx.sub(s).get(r, overwrite)
200 sm[s] = r
200 sm[s] = r
201 elif ld[1] == a[1]: # local side is unchanged
201 elif ld[1] == a[1]: # local side is unchanged
202 debug(s, "other side changed, get", r)
202 debug(s, "other side changed, get", r)
203 wctx.sub(s).get(r, overwrite)
203 wctx.sub(s).get(r, overwrite)
204 sm[s] = r
204 sm[s] = r
205 else:
205 else:
206 debug(s, "both sides changed")
206 debug(s, "both sides changed")
207 srepo = wctx.sub(s)
207 srepo = wctx.sub(s)
208 option = repo.ui.promptchoice(
208 option = repo.ui.promptchoice(
209 _(' subrepository %s diverged (local revision: %s, '
209 _(' subrepository %s diverged (local revision: %s, '
210 'remote revision: %s)\n'
210 'remote revision: %s)\n'
211 '(M)erge, keep (l)ocal or keep (r)emote?'
211 '(M)erge, keep (l)ocal or keep (r)emote?'
212 '$$ &Merge $$ &Local $$ &Remote')
212 '$$ &Merge $$ &Local $$ &Remote')
213 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
213 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
214 if option == 0:
214 if option == 0:
215 wctx.sub(s).merge(r)
215 wctx.sub(s).merge(r)
216 sm[s] = l
216 sm[s] = l
217 debug(s, "merge with", r)
217 debug(s, "merge with", r)
218 elif option == 1:
218 elif option == 1:
219 sm[s] = l
219 sm[s] = l
220 debug(s, "keep local subrepo revision", l)
220 debug(s, "keep local subrepo revision", l)
221 else:
221 else:
222 wctx.sub(s).get(r, overwrite)
222 wctx.sub(s).get(r, overwrite)
223 sm[s] = r
223 sm[s] = r
224 debug(s, "get remote subrepo revision", r)
224 debug(s, "get remote subrepo revision", r)
225 elif ld == a: # remote removed, local unchanged
225 elif ld == a: # remote removed, local unchanged
226 debug(s, "remote removed, remove")
226 debug(s, "remote removed, remove")
227 wctx.sub(s).remove()
227 wctx.sub(s).remove()
228 elif a == nullstate: # not present in remote or ancestor
228 elif a == nullstate: # not present in remote or ancestor
229 debug(s, "local added, keep")
229 debug(s, "local added, keep")
230 sm[s] = l
230 sm[s] = l
231 continue
231 continue
232 else:
232 else:
233 if repo.ui.promptchoice(
233 if repo.ui.promptchoice(
234 _(' local changed subrepository %s which remote removed\n'
234 _(' local changed subrepository %s which remote removed\n'
235 'use (c)hanged version or (d)elete?'
235 'use (c)hanged version or (d)elete?'
236 '$$ &Changed $$ &Delete') % s, 0):
236 '$$ &Changed $$ &Delete') % s, 0):
237 debug(s, "prompt remove")
237 debug(s, "prompt remove")
238 wctx.sub(s).remove()
238 wctx.sub(s).remove()
239
239
240 for s, r in sorted(s2.items()):
240 for s, r in sorted(s2.items()):
241 if s in s1:
241 if s in s1:
242 continue
242 continue
243 elif s not in sa:
243 elif s not in sa:
244 debug(s, "remote added, get", r)
244 debug(s, "remote added, get", r)
245 mctx.sub(s).get(r)
245 mctx.sub(s).get(r)
246 sm[s] = r
246 sm[s] = r
247 elif r != sa[s]:
247 elif r != sa[s]:
248 if repo.ui.promptchoice(
248 if repo.ui.promptchoice(
249 _(' remote changed subrepository %s which local removed\n'
249 _(' remote changed subrepository %s which local removed\n'
250 'use (c)hanged version or (d)elete?'
250 'use (c)hanged version or (d)elete?'
251 '$$ &Changed $$ &Delete') % s, 0) == 0:
251 '$$ &Changed $$ &Delete') % s, 0) == 0:
252 debug(s, "prompt recreate", r)
252 debug(s, "prompt recreate", r)
253 mctx.sub(s).get(r)
253 mctx.sub(s).get(r)
254 sm[s] = r
254 sm[s] = r
255
255
256 # record merged .hgsubstate
256 # record merged .hgsubstate
257 writestate(repo, sm)
257 writestate(repo, sm)
258 return sm
258 return sm
259
259
260 def _updateprompt(ui, sub, dirty, local, remote):
260 def _updateprompt(ui, sub, dirty, local, remote):
261 if dirty:
261 if dirty:
262 msg = (_(' subrepository sources for %s differ\n'
262 msg = (_(' subrepository sources for %s differ\n'
263 'use (l)ocal source (%s) or (r)emote source (%s)?'
263 'use (l)ocal source (%s) or (r)emote source (%s)?'
264 '$$ &Local $$ &Remote')
264 '$$ &Local $$ &Remote')
265 % (subrelpath(sub), local, remote))
265 % (subrelpath(sub), local, remote))
266 else:
266 else:
267 msg = (_(' subrepository sources for %s differ (in checked out '
267 msg = (_(' subrepository sources for %s differ (in checked out '
268 'version)\n'
268 'version)\n'
269 'use (l)ocal source (%s) or (r)emote source (%s)?'
269 'use (l)ocal source (%s) or (r)emote source (%s)?'
270 '$$ &Local $$ &Remote')
270 '$$ &Local $$ &Remote')
271 % (subrelpath(sub), local, remote))
271 % (subrelpath(sub), local, remote))
272 return ui.promptchoice(msg, 0)
272 return ui.promptchoice(msg, 0)
273
273
274 def reporelpath(repo):
274 def reporelpath(repo):
275 """return path to this (sub)repo as seen from outermost repo"""
275 """return path to this (sub)repo as seen from outermost repo"""
276 parent = repo
276 parent = repo
277 while util.safehasattr(parent, '_subparent'):
277 while util.safehasattr(parent, '_subparent'):
278 parent = parent._subparent
278 parent = parent._subparent
279 return repo.root[len(pathutil.normasprefix(parent.root)):]
279 return repo.root[len(pathutil.normasprefix(parent.root)):]
280
280
281 def subrelpath(sub):
281 def subrelpath(sub):
282 """return path to this subrepo as seen from outermost repo"""
282 """return path to this subrepo as seen from outermost repo"""
283 if util.safehasattr(sub, '_relpath'):
283 if util.safehasattr(sub, '_relpath'):
284 return sub._relpath
284 return sub._relpath
285 if not util.safehasattr(sub, '_repo'):
285 if not util.safehasattr(sub, '_repo'):
286 return sub._path
286 return sub._path
287 return reporelpath(sub._repo)
287 return reporelpath(sub._repo)
288
288
289 def _abssource(repo, push=False, abort=True):
289 def _abssource(repo, push=False, abort=True):
290 """return pull/push path of repo - either based on parent repo .hgsub info
290 """return pull/push path of repo - either based on parent repo .hgsub info
291 or on the top repo config. Abort or return None if no source found."""
291 or on the top repo config. Abort or return None if no source found."""
292 if util.safehasattr(repo, '_subparent'):
292 if util.safehasattr(repo, '_subparent'):
293 source = util.url(repo._subsource)
293 source = util.url(repo._subsource)
294 if source.isabs():
294 if source.isabs():
295 return str(source)
295 return str(source)
296 source.path = posixpath.normpath(source.path)
296 source.path = posixpath.normpath(source.path)
297 parent = _abssource(repo._subparent, push, abort=False)
297 parent = _abssource(repo._subparent, push, abort=False)
298 if parent:
298 if parent:
299 parent = util.url(util.pconvert(parent))
299 parent = util.url(util.pconvert(parent))
300 parent.path = posixpath.join(parent.path or '', source.path)
300 parent.path = posixpath.join(parent.path or '', source.path)
301 parent.path = posixpath.normpath(parent.path)
301 parent.path = posixpath.normpath(parent.path)
302 return str(parent)
302 return str(parent)
303 else: # recursion reached top repo
303 else: # recursion reached top repo
304 if util.safehasattr(repo, '_subtoppath'):
304 if util.safehasattr(repo, '_subtoppath'):
305 return repo._subtoppath
305 return repo._subtoppath
306 if push and repo.ui.config('paths', 'default-push'):
306 if push and repo.ui.config('paths', 'default-push'):
307 return repo.ui.config('paths', 'default-push')
307 return repo.ui.config('paths', 'default-push')
308 if repo.ui.config('paths', 'default'):
308 if repo.ui.config('paths', 'default'):
309 return repo.ui.config('paths', 'default')
309 return repo.ui.config('paths', 'default')
310 if repo.shared():
310 if repo.shared():
311 # chop off the .hg component to get the default path form
311 # chop off the .hg component to get the default path form
312 return os.path.dirname(repo.sharedpath)
312 return os.path.dirname(repo.sharedpath)
313 if abort:
313 if abort:
314 raise util.Abort(_("default path for subrepository not found"))
314 raise util.Abort(_("default path for subrepository not found"))
315
315
316 def _sanitize(ui, path, ignore):
316 def _sanitize(ui, path, ignore):
317 for dirname, dirs, names in os.walk(path):
317 for dirname, dirs, names in os.walk(path):
318 for i, d in enumerate(dirs):
318 for i, d in enumerate(dirs):
319 if d.lower() == ignore:
319 if d.lower() == ignore:
320 del dirs[i]
320 del dirs[i]
321 break
321 break
322 if os.path.basename(dirname).lower() != '.hg':
322 if os.path.basename(dirname).lower() != '.hg':
323 continue
323 continue
324 for f in names:
324 for f in names:
325 if f.lower() == 'hgrc':
325 if f.lower() == 'hgrc':
326 ui.warn(_("warning: removing potentially hostile 'hgrc' "
326 ui.warn(_("warning: removing potentially hostile 'hgrc' "
327 "in '%s'\n") % dirname)
327 "in '%s'\n") % dirname)
328 os.unlink(os.path.join(dirname, f))
328 os.unlink(os.path.join(dirname, f))
329
329
330 def subrepo(ctx, path):
330 def subrepo(ctx, path):
331 """return instance of the right subrepo class for subrepo in path"""
331 """return instance of the right subrepo class for subrepo in path"""
332 # subrepo inherently violates our import layering rules
332 # subrepo inherently violates our import layering rules
333 # because it wants to make repo objects from deep inside the stack
333 # because it wants to make repo objects from deep inside the stack
334 # so we manually delay the circular imports to not break
334 # so we manually delay the circular imports to not break
335 # scripts that don't use our demand-loading
335 # scripts that don't use our demand-loading
336 global hg
336 global hg
337 import hg as h
337 import hg as h
338 hg = h
338 hg = h
339
339
340 pathutil.pathauditor(ctx.repo().root)(path)
340 pathutil.pathauditor(ctx.repo().root)(path)
341 state = ctx.substate[path]
341 state = ctx.substate[path]
342 if state[2] not in types:
342 if state[2] not in types:
343 raise util.Abort(_('unknown subrepo type %s') % state[2])
343 raise util.Abort(_('unknown subrepo type %s') % state[2])
344 return types[state[2]](ctx, path, state[:2])
344 return types[state[2]](ctx, path, state[:2])
345
345
346 def newcommitphase(ui, ctx):
346 def newcommitphase(ui, ctx):
347 commitphase = phases.newcommitphase(ui)
347 commitphase = phases.newcommitphase(ui)
348 substate = getattr(ctx, "substate", None)
348 substate = getattr(ctx, "substate", None)
349 if not substate:
349 if not substate:
350 return commitphase
350 return commitphase
351 check = ui.config('phases', 'checksubrepos', 'follow')
351 check = ui.config('phases', 'checksubrepos', 'follow')
352 if check not in ('ignore', 'follow', 'abort'):
352 if check not in ('ignore', 'follow', 'abort'):
353 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
353 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
354 % (check))
354 % (check))
355 if check == 'ignore':
355 if check == 'ignore':
356 return commitphase
356 return commitphase
357 maxphase = phases.public
357 maxphase = phases.public
358 maxsub = None
358 maxsub = None
359 for s in sorted(substate):
359 for s in sorted(substate):
360 sub = ctx.sub(s)
360 sub = ctx.sub(s)
361 subphase = sub.phase(substate[s][1])
361 subphase = sub.phase(substate[s][1])
362 if maxphase < subphase:
362 if maxphase < subphase:
363 maxphase = subphase
363 maxphase = subphase
364 maxsub = s
364 maxsub = s
365 if commitphase < maxphase:
365 if commitphase < maxphase:
366 if check == 'abort':
366 if check == 'abort':
367 raise util.Abort(_("can't commit in %s phase"
367 raise util.Abort(_("can't commit in %s phase"
368 " conflicting %s from subrepository %s") %
368 " conflicting %s from subrepository %s") %
369 (phases.phasenames[commitphase],
369 (phases.phasenames[commitphase],
370 phases.phasenames[maxphase], maxsub))
370 phases.phasenames[maxphase], maxsub))
371 ui.warn(_("warning: changes are committed in"
371 ui.warn(_("warning: changes are committed in"
372 " %s phase from subrepository %s\n") %
372 " %s phase from subrepository %s\n") %
373 (phases.phasenames[maxphase], maxsub))
373 (phases.phasenames[maxphase], maxsub))
374 return maxphase
374 return maxphase
375 return commitphase
375 return commitphase
376
376
377 # subrepo classes need to implement the following abstract class:
377 # subrepo classes need to implement the following abstract class:
378
378
379 class abstractsubrepo(object):
379 class abstractsubrepo(object):
380
380
381 def __init__(self, ui):
381 def __init__(self, ctx, path):
382 self.ui = ui
382 """Initialize abstractsubrepo part
383
384 ``ctx`` is the context referring this subrepository in the
385 parent repository.
386
387 ``path`` is the path to this subrepositiry as seen from
388 innermost repository.
389 """
390 self.ui = ctx.repo().ui
391 self._ctx = ctx
392 self._path = path
383
393
384 def storeclean(self, path):
394 def storeclean(self, path):
385 """
395 """
386 returns true if the repository has not changed since it was last
396 returns true if the repository has not changed since it was last
387 cloned from or pushed to a given repository.
397 cloned from or pushed to a given repository.
388 """
398 """
389 return False
399 return False
390
400
391 def dirty(self, ignoreupdate=False):
401 def dirty(self, ignoreupdate=False):
392 """returns true if the dirstate of the subrepo is dirty or does not
402 """returns true if the dirstate of the subrepo is dirty or does not
393 match current stored state. If ignoreupdate is true, only check
403 match current stored state. If ignoreupdate is true, only check
394 whether the subrepo has uncommitted changes in its dirstate.
404 whether the subrepo has uncommitted changes in its dirstate.
395 """
405 """
396 raise NotImplementedError
406 raise NotImplementedError
397
407
398 def dirtyreason(self, ignoreupdate=False):
408 def dirtyreason(self, ignoreupdate=False):
399 """return reason string if it is ``dirty()``
409 """return reason string if it is ``dirty()``
400
410
401 Returned string should have enough information for the message
411 Returned string should have enough information for the message
402 of exception.
412 of exception.
403
413
404 This returns None, otherwise.
414 This returns None, otherwise.
405 """
415 """
406 if self.dirty(ignoreupdate=ignoreupdate):
416 if self.dirty(ignoreupdate=ignoreupdate):
407 return _("uncommitted changes in subrepository '%s'"
417 return _("uncommitted changes in subrepository '%s'"
408 ) % subrelpath(self)
418 ) % subrelpath(self)
409
419
410 def bailifchanged(self, ignoreupdate=False):
420 def bailifchanged(self, ignoreupdate=False):
411 """raise Abort if subrepository is ``dirty()``
421 """raise Abort if subrepository is ``dirty()``
412 """
422 """
413 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
423 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
414 if dirtyreason:
424 if dirtyreason:
415 raise util.Abort(dirtyreason)
425 raise util.Abort(dirtyreason)
416
426
417 def basestate(self):
427 def basestate(self):
418 """current working directory base state, disregarding .hgsubstate
428 """current working directory base state, disregarding .hgsubstate
419 state and working directory modifications"""
429 state and working directory modifications"""
420 raise NotImplementedError
430 raise NotImplementedError
421
431
422 def checknested(self, path):
432 def checknested(self, path):
423 """check if path is a subrepository within this repository"""
433 """check if path is a subrepository within this repository"""
424 return False
434 return False
425
435
426 def commit(self, text, user, date):
436 def commit(self, text, user, date):
427 """commit the current changes to the subrepo with the given
437 """commit the current changes to the subrepo with the given
428 log message. Use given user and date if possible. Return the
438 log message. Use given user and date if possible. Return the
429 new state of the subrepo.
439 new state of the subrepo.
430 """
440 """
431 raise NotImplementedError
441 raise NotImplementedError
432
442
433 def phase(self, state):
443 def phase(self, state):
434 """returns phase of specified state in the subrepository.
444 """returns phase of specified state in the subrepository.
435 """
445 """
436 return phases.public
446 return phases.public
437
447
438 def remove(self):
448 def remove(self):
439 """remove the subrepo
449 """remove the subrepo
440
450
441 (should verify the dirstate is not dirty first)
451 (should verify the dirstate is not dirty first)
442 """
452 """
443 raise NotImplementedError
453 raise NotImplementedError
444
454
445 def get(self, state, overwrite=False):
455 def get(self, state, overwrite=False):
446 """run whatever commands are needed to put the subrepo into
456 """run whatever commands are needed to put the subrepo into
447 this state
457 this state
448 """
458 """
449 raise NotImplementedError
459 raise NotImplementedError
450
460
451 def merge(self, state):
461 def merge(self, state):
452 """merge currently-saved state with the new state."""
462 """merge currently-saved state with the new state."""
453 raise NotImplementedError
463 raise NotImplementedError
454
464
455 def push(self, opts):
465 def push(self, opts):
456 """perform whatever action is analogous to 'hg push'
466 """perform whatever action is analogous to 'hg push'
457
467
458 This may be a no-op on some systems.
468 This may be a no-op on some systems.
459 """
469 """
460 raise NotImplementedError
470 raise NotImplementedError
461
471
462 def add(self, ui, match, prefix, explicitonly, **opts):
472 def add(self, ui, match, prefix, explicitonly, **opts):
463 return []
473 return []
464
474
465 def addremove(self, matcher, prefix, opts, dry_run, similarity):
475 def addremove(self, matcher, prefix, opts, dry_run, similarity):
466 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
476 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
467 return 1
477 return 1
468
478
469 def cat(self, match, prefix, **opts):
479 def cat(self, match, prefix, **opts):
470 return 1
480 return 1
471
481
472 def status(self, rev2, **opts):
482 def status(self, rev2, **opts):
473 return scmutil.status([], [], [], [], [], [], [])
483 return scmutil.status([], [], [], [], [], [], [])
474
484
475 def diff(self, ui, diffopts, node2, match, prefix, **opts):
485 def diff(self, ui, diffopts, node2, match, prefix, **opts):
476 pass
486 pass
477
487
478 def outgoing(self, ui, dest, opts):
488 def outgoing(self, ui, dest, opts):
479 return 1
489 return 1
480
490
481 def incoming(self, ui, source, opts):
491 def incoming(self, ui, source, opts):
482 return 1
492 return 1
483
493
484 def files(self):
494 def files(self):
485 """return filename iterator"""
495 """return filename iterator"""
486 raise NotImplementedError
496 raise NotImplementedError
487
497
488 def filedata(self, name):
498 def filedata(self, name):
489 """return file data"""
499 """return file data"""
490 raise NotImplementedError
500 raise NotImplementedError
491
501
492 def fileflags(self, name):
502 def fileflags(self, name):
493 """return file flags"""
503 """return file flags"""
494 return ''
504 return ''
495
505
496 def printfiles(self, ui, m, fm, fmt):
506 def printfiles(self, ui, m, fm, fmt):
497 """handle the files command for this subrepo"""
507 """handle the files command for this subrepo"""
498 return 1
508 return 1
499
509
500 def archive(self, archiver, prefix, match=None):
510 def archive(self, archiver, prefix, match=None):
501 if match is not None:
511 if match is not None:
502 files = [f for f in self.files() if match(f)]
512 files = [f for f in self.files() if match(f)]
503 else:
513 else:
504 files = self.files()
514 files = self.files()
505 total = len(files)
515 total = len(files)
506 relpath = subrelpath(self)
516 relpath = subrelpath(self)
507 self.ui.progress(_('archiving (%s)') % relpath, 0,
517 self.ui.progress(_('archiving (%s)') % relpath, 0,
508 unit=_('files'), total=total)
518 unit=_('files'), total=total)
509 for i, name in enumerate(files):
519 for i, name in enumerate(files):
510 flags = self.fileflags(name)
520 flags = self.fileflags(name)
511 mode = 'x' in flags and 0755 or 0644
521 mode = 'x' in flags and 0755 or 0644
512 symlink = 'l' in flags
522 symlink = 'l' in flags
513 archiver.addfile(os.path.join(prefix, self._path, name),
523 archiver.addfile(os.path.join(prefix, self._path, name),
514 mode, symlink, self.filedata(name))
524 mode, symlink, self.filedata(name))
515 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
525 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
516 unit=_('files'), total=total)
526 unit=_('files'), total=total)
517 self.ui.progress(_('archiving (%s)') % relpath, None)
527 self.ui.progress(_('archiving (%s)') % relpath, None)
518 return total
528 return total
519
529
520 def walk(self, match):
530 def walk(self, match):
521 '''
531 '''
522 walk recursively through the directory tree, finding all files
532 walk recursively through the directory tree, finding all files
523 matched by the match function
533 matched by the match function
524 '''
534 '''
525 pass
535 pass
526
536
527 def forget(self, match, prefix):
537 def forget(self, match, prefix):
528 return ([], [])
538 return ([], [])
529
539
530 def removefiles(self, matcher, prefix, after, force, subrepos):
540 def removefiles(self, matcher, prefix, after, force, subrepos):
531 """remove the matched files from the subrepository and the filesystem,
541 """remove the matched files from the subrepository and the filesystem,
532 possibly by force and/or after the file has been removed from the
542 possibly by force and/or after the file has been removed from the
533 filesystem. Return 0 on success, 1 on any warning.
543 filesystem. Return 0 on success, 1 on any warning.
534 """
544 """
535 return 1
545 return 1
536
546
537 def revert(self, substate, *pats, **opts):
547 def revert(self, substate, *pats, **opts):
538 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
548 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
539 % (substate[0], substate[2]))
549 % (substate[0], substate[2]))
540 return []
550 return []
541
551
542 def shortid(self, revid):
552 def shortid(self, revid):
543 return revid
553 return revid
544
554
545 class hgsubrepo(abstractsubrepo):
555 class hgsubrepo(abstractsubrepo):
546 def __init__(self, ctx, path, state):
556 def __init__(self, ctx, path, state):
547 super(hgsubrepo, self).__init__(ctx.repo().ui)
557 super(hgsubrepo, self).__init__(ctx, path)
548 self._path = path
549 self._state = state
558 self._state = state
550 self._ctx = ctx
551 r = ctx.repo()
559 r = ctx.repo()
552 root = r.wjoin(path)
560 root = r.wjoin(path)
553 create = not r.wvfs.exists('%s/.hg' % path)
561 create = not r.wvfs.exists('%s/.hg' % path)
554 self._repo = hg.repository(r.baseui, root, create=create)
562 self._repo = hg.repository(r.baseui, root, create=create)
555 self.ui = self._repo.ui
563 self.ui = self._repo.ui
556 for s, k in [('ui', 'commitsubrepos')]:
564 for s, k in [('ui', 'commitsubrepos')]:
557 v = r.ui.config(s, k)
565 v = r.ui.config(s, k)
558 if v:
566 if v:
559 self.ui.setconfig(s, k, v, 'subrepo')
567 self.ui.setconfig(s, k, v, 'subrepo')
560 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
568 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
561 self._initrepo(r, state[0], create)
569 self._initrepo(r, state[0], create)
562
570
563 def storeclean(self, path):
571 def storeclean(self, path):
564 lock = self._repo.lock()
572 lock = self._repo.lock()
565 try:
573 try:
566 return self._storeclean(path)
574 return self._storeclean(path)
567 finally:
575 finally:
568 lock.release()
576 lock.release()
569
577
570 def _storeclean(self, path):
578 def _storeclean(self, path):
571 clean = True
579 clean = True
572 itercache = self._calcstorehash(path)
580 itercache = self._calcstorehash(path)
573 try:
581 try:
574 for filehash in self._readstorehashcache(path):
582 for filehash in self._readstorehashcache(path):
575 if filehash != itercache.next():
583 if filehash != itercache.next():
576 clean = False
584 clean = False
577 break
585 break
578 except StopIteration:
586 except StopIteration:
579 # the cached and current pull states have a different size
587 # the cached and current pull states have a different size
580 clean = False
588 clean = False
581 if clean:
589 if clean:
582 try:
590 try:
583 itercache.next()
591 itercache.next()
584 # the cached and current pull states have a different size
592 # the cached and current pull states have a different size
585 clean = False
593 clean = False
586 except StopIteration:
594 except StopIteration:
587 pass
595 pass
588 return clean
596 return clean
589
597
590 def _calcstorehash(self, remotepath):
598 def _calcstorehash(self, remotepath):
591 '''calculate a unique "store hash"
599 '''calculate a unique "store hash"
592
600
593 This method is used to to detect when there are changes that may
601 This method is used to to detect when there are changes that may
594 require a push to a given remote path.'''
602 require a push to a given remote path.'''
595 # sort the files that will be hashed in increasing (likely) file size
603 # sort the files that will be hashed in increasing (likely) file size
596 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
604 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
597 yield '# %s\n' % _expandedabspath(remotepath)
605 yield '# %s\n' % _expandedabspath(remotepath)
598 vfs = self._repo.vfs
606 vfs = self._repo.vfs
599 for relname in filelist:
607 for relname in filelist:
600 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
608 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
601 yield '%s = %s\n' % (relname, filehash)
609 yield '%s = %s\n' % (relname, filehash)
602
610
603 @propertycache
611 @propertycache
604 def _cachestorehashvfs(self):
612 def _cachestorehashvfs(self):
605 return scmutil.vfs(self._repo.join('cache/storehash'))
613 return scmutil.vfs(self._repo.join('cache/storehash'))
606
614
607 def _readstorehashcache(self, remotepath):
615 def _readstorehashcache(self, remotepath):
608 '''read the store hash cache for a given remote repository'''
616 '''read the store hash cache for a given remote repository'''
609 cachefile = _getstorehashcachename(remotepath)
617 cachefile = _getstorehashcachename(remotepath)
610 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
618 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
611
619
612 def _cachestorehash(self, remotepath):
620 def _cachestorehash(self, remotepath):
613 '''cache the current store hash
621 '''cache the current store hash
614
622
615 Each remote repo requires its own store hash cache, because a subrepo
623 Each remote repo requires its own store hash cache, because a subrepo
616 store may be "clean" versus a given remote repo, but not versus another
624 store may be "clean" versus a given remote repo, but not versus another
617 '''
625 '''
618 cachefile = _getstorehashcachename(remotepath)
626 cachefile = _getstorehashcachename(remotepath)
619 lock = self._repo.lock()
627 lock = self._repo.lock()
620 try:
628 try:
621 storehash = list(self._calcstorehash(remotepath))
629 storehash = list(self._calcstorehash(remotepath))
622 vfs = self._cachestorehashvfs
630 vfs = self._cachestorehashvfs
623 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
631 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
624 finally:
632 finally:
625 lock.release()
633 lock.release()
626
634
627 @annotatesubrepoerror
635 @annotatesubrepoerror
628 def _initrepo(self, parentrepo, source, create):
636 def _initrepo(self, parentrepo, source, create):
629 self._repo._subparent = parentrepo
637 self._repo._subparent = parentrepo
630 self._repo._subsource = source
638 self._repo._subsource = source
631
639
632 if create:
640 if create:
633 lines = ['[paths]\n']
641 lines = ['[paths]\n']
634
642
635 def addpathconfig(key, value):
643 def addpathconfig(key, value):
636 if value:
644 if value:
637 lines.append('%s = %s\n' % (key, value))
645 lines.append('%s = %s\n' % (key, value))
638 self.ui.setconfig('paths', key, value, 'subrepo')
646 self.ui.setconfig('paths', key, value, 'subrepo')
639
647
640 defpath = _abssource(self._repo, abort=False)
648 defpath = _abssource(self._repo, abort=False)
641 defpushpath = _abssource(self._repo, True, abort=False)
649 defpushpath = _abssource(self._repo, True, abort=False)
642 addpathconfig('default', defpath)
650 addpathconfig('default', defpath)
643 if defpath != defpushpath:
651 if defpath != defpushpath:
644 addpathconfig('default-push', defpushpath)
652 addpathconfig('default-push', defpushpath)
645
653
646 fp = self._repo.vfs("hgrc", "w", text=True)
654 fp = self._repo.vfs("hgrc", "w", text=True)
647 try:
655 try:
648 fp.write(''.join(lines))
656 fp.write(''.join(lines))
649 finally:
657 finally:
650 fp.close()
658 fp.close()
651
659
652 @annotatesubrepoerror
660 @annotatesubrepoerror
653 def add(self, ui, match, prefix, explicitonly, **opts):
661 def add(self, ui, match, prefix, explicitonly, **opts):
654 return cmdutil.add(ui, self._repo, match,
662 return cmdutil.add(ui, self._repo, match,
655 os.path.join(prefix, self._path), explicitonly,
663 os.path.join(prefix, self._path), explicitonly,
656 **opts)
664 **opts)
657
665
658 @annotatesubrepoerror
666 @annotatesubrepoerror
659 def addremove(self, m, prefix, opts, dry_run, similarity):
667 def addremove(self, m, prefix, opts, dry_run, similarity):
660 # In the same way as sub directories are processed, once in a subrepo,
668 # In the same way as sub directories are processed, once in a subrepo,
661 # always entry any of its subrepos. Don't corrupt the options that will
669 # always entry any of its subrepos. Don't corrupt the options that will
662 # be used to process sibling subrepos however.
670 # be used to process sibling subrepos however.
663 opts = copy.copy(opts)
671 opts = copy.copy(opts)
664 opts['subrepos'] = True
672 opts['subrepos'] = True
665 return scmutil.addremove(self._repo, m,
673 return scmutil.addremove(self._repo, m,
666 os.path.join(prefix, self._path), opts,
674 os.path.join(prefix, self._path), opts,
667 dry_run, similarity)
675 dry_run, similarity)
668
676
669 @annotatesubrepoerror
677 @annotatesubrepoerror
670 def cat(self, match, prefix, **opts):
678 def cat(self, match, prefix, **opts):
671 rev = self._state[1]
679 rev = self._state[1]
672 ctx = self._repo[rev]
680 ctx = self._repo[rev]
673 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
681 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
674
682
675 @annotatesubrepoerror
683 @annotatesubrepoerror
676 def status(self, rev2, **opts):
684 def status(self, rev2, **opts):
677 try:
685 try:
678 rev1 = self._state[1]
686 rev1 = self._state[1]
679 ctx1 = self._repo[rev1]
687 ctx1 = self._repo[rev1]
680 ctx2 = self._repo[rev2]
688 ctx2 = self._repo[rev2]
681 return self._repo.status(ctx1, ctx2, **opts)
689 return self._repo.status(ctx1, ctx2, **opts)
682 except error.RepoLookupError, inst:
690 except error.RepoLookupError, inst:
683 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
691 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
684 % (inst, subrelpath(self)))
692 % (inst, subrelpath(self)))
685 return scmutil.status([], [], [], [], [], [], [])
693 return scmutil.status([], [], [], [], [], [], [])
686
694
687 @annotatesubrepoerror
695 @annotatesubrepoerror
688 def diff(self, ui, diffopts, node2, match, prefix, **opts):
696 def diff(self, ui, diffopts, node2, match, prefix, **opts):
689 try:
697 try:
690 node1 = node.bin(self._state[1])
698 node1 = node.bin(self._state[1])
691 # We currently expect node2 to come from substate and be
699 # We currently expect node2 to come from substate and be
692 # in hex format
700 # in hex format
693 if node2 is not None:
701 if node2 is not None:
694 node2 = node.bin(node2)
702 node2 = node.bin(node2)
695 cmdutil.diffordiffstat(ui, self._repo, diffopts,
703 cmdutil.diffordiffstat(ui, self._repo, diffopts,
696 node1, node2, match,
704 node1, node2, match,
697 prefix=posixpath.join(prefix, self._path),
705 prefix=posixpath.join(prefix, self._path),
698 listsubrepos=True, **opts)
706 listsubrepos=True, **opts)
699 except error.RepoLookupError, inst:
707 except error.RepoLookupError, inst:
700 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
708 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
701 % (inst, subrelpath(self)))
709 % (inst, subrelpath(self)))
702
710
703 @annotatesubrepoerror
711 @annotatesubrepoerror
704 def archive(self, archiver, prefix, match=None):
712 def archive(self, archiver, prefix, match=None):
705 self._get(self._state + ('hg',))
713 self._get(self._state + ('hg',))
706 total = abstractsubrepo.archive(self, archiver, prefix, match)
714 total = abstractsubrepo.archive(self, archiver, prefix, match)
707 rev = self._state[1]
715 rev = self._state[1]
708 ctx = self._repo[rev]
716 ctx = self._repo[rev]
709 for subpath in ctx.substate:
717 for subpath in ctx.substate:
710 s = subrepo(ctx, subpath)
718 s = subrepo(ctx, subpath)
711 submatch = matchmod.narrowmatcher(subpath, match)
719 submatch = matchmod.narrowmatcher(subpath, match)
712 total += s.archive(
720 total += s.archive(
713 archiver, os.path.join(prefix, self._path), submatch)
721 archiver, os.path.join(prefix, self._path), submatch)
714 return total
722 return total
715
723
716 @annotatesubrepoerror
724 @annotatesubrepoerror
717 def dirty(self, ignoreupdate=False):
725 def dirty(self, ignoreupdate=False):
718 r = self._state[1]
726 r = self._state[1]
719 if r == '' and not ignoreupdate: # no state recorded
727 if r == '' and not ignoreupdate: # no state recorded
720 return True
728 return True
721 w = self._repo[None]
729 w = self._repo[None]
722 if r != w.p1().hex() and not ignoreupdate:
730 if r != w.p1().hex() and not ignoreupdate:
723 # different version checked out
731 # different version checked out
724 return True
732 return True
725 return w.dirty() # working directory changed
733 return w.dirty() # working directory changed
726
734
727 def basestate(self):
735 def basestate(self):
728 return self._repo['.'].hex()
736 return self._repo['.'].hex()
729
737
730 def checknested(self, path):
738 def checknested(self, path):
731 return self._repo._checknested(self._repo.wjoin(path))
739 return self._repo._checknested(self._repo.wjoin(path))
732
740
733 @annotatesubrepoerror
741 @annotatesubrepoerror
734 def commit(self, text, user, date):
742 def commit(self, text, user, date):
735 # don't bother committing in the subrepo if it's only been
743 # don't bother committing in the subrepo if it's only been
736 # updated
744 # updated
737 if not self.dirty(True):
745 if not self.dirty(True):
738 return self._repo['.'].hex()
746 return self._repo['.'].hex()
739 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
747 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
740 n = self._repo.commit(text, user, date)
748 n = self._repo.commit(text, user, date)
741 if not n:
749 if not n:
742 return self._repo['.'].hex() # different version checked out
750 return self._repo['.'].hex() # different version checked out
743 return node.hex(n)
751 return node.hex(n)
744
752
745 @annotatesubrepoerror
753 @annotatesubrepoerror
746 def phase(self, state):
754 def phase(self, state):
747 return self._repo[state].phase()
755 return self._repo[state].phase()
748
756
749 @annotatesubrepoerror
757 @annotatesubrepoerror
750 def remove(self):
758 def remove(self):
751 # we can't fully delete the repository as it may contain
759 # we can't fully delete the repository as it may contain
752 # local-only history
760 # local-only history
753 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
761 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
754 hg.clean(self._repo, node.nullid, False)
762 hg.clean(self._repo, node.nullid, False)
755
763
756 def _get(self, state):
764 def _get(self, state):
757 source, revision, kind = state
765 source, revision, kind = state
758 if revision in self._repo.unfiltered():
766 if revision in self._repo.unfiltered():
759 return True
767 return True
760 self._repo._subsource = source
768 self._repo._subsource = source
761 srcurl = _abssource(self._repo)
769 srcurl = _abssource(self._repo)
762 other = hg.peer(self._repo, {}, srcurl)
770 other = hg.peer(self._repo, {}, srcurl)
763 if len(self._repo) == 0:
771 if len(self._repo) == 0:
764 self.ui.status(_('cloning subrepo %s from %s\n')
772 self.ui.status(_('cloning subrepo %s from %s\n')
765 % (subrelpath(self), srcurl))
773 % (subrelpath(self), srcurl))
766 parentrepo = self._repo._subparent
774 parentrepo = self._repo._subparent
767 shutil.rmtree(self._repo.path)
775 shutil.rmtree(self._repo.path)
768 other, cloned = hg.clone(self._repo._subparent.baseui, {},
776 other, cloned = hg.clone(self._repo._subparent.baseui, {},
769 other, self._repo.root,
777 other, self._repo.root,
770 update=False)
778 update=False)
771 self._repo = cloned.local()
779 self._repo = cloned.local()
772 self._initrepo(parentrepo, source, create=True)
780 self._initrepo(parentrepo, source, create=True)
773 self._cachestorehash(srcurl)
781 self._cachestorehash(srcurl)
774 else:
782 else:
775 self.ui.status(_('pulling subrepo %s from %s\n')
783 self.ui.status(_('pulling subrepo %s from %s\n')
776 % (subrelpath(self), srcurl))
784 % (subrelpath(self), srcurl))
777 cleansub = self.storeclean(srcurl)
785 cleansub = self.storeclean(srcurl)
778 exchange.pull(self._repo, other)
786 exchange.pull(self._repo, other)
779 if cleansub:
787 if cleansub:
780 # keep the repo clean after pull
788 # keep the repo clean after pull
781 self._cachestorehash(srcurl)
789 self._cachestorehash(srcurl)
782 return False
790 return False
783
791
784 @annotatesubrepoerror
792 @annotatesubrepoerror
785 def get(self, state, overwrite=False):
793 def get(self, state, overwrite=False):
786 inrepo = self._get(state)
794 inrepo = self._get(state)
787 source, revision, kind = state
795 source, revision, kind = state
788 repo = self._repo
796 repo = self._repo
789 repo.ui.debug("getting subrepo %s\n" % self._path)
797 repo.ui.debug("getting subrepo %s\n" % self._path)
790 if inrepo:
798 if inrepo:
791 urepo = repo.unfiltered()
799 urepo = repo.unfiltered()
792 ctx = urepo[revision]
800 ctx = urepo[revision]
793 if ctx.hidden():
801 if ctx.hidden():
794 urepo.ui.warn(
802 urepo.ui.warn(
795 _('revision %s in subrepo %s is hidden\n') \
803 _('revision %s in subrepo %s is hidden\n') \
796 % (revision[0:12], self._path))
804 % (revision[0:12], self._path))
797 repo = urepo
805 repo = urepo
798 hg.updaterepo(repo, revision, overwrite)
806 hg.updaterepo(repo, revision, overwrite)
799
807
800 @annotatesubrepoerror
808 @annotatesubrepoerror
801 def merge(self, state):
809 def merge(self, state):
802 self._get(state)
810 self._get(state)
803 cur = self._repo['.']
811 cur = self._repo['.']
804 dst = self._repo[state[1]]
812 dst = self._repo[state[1]]
805 anc = dst.ancestor(cur)
813 anc = dst.ancestor(cur)
806
814
807 def mergefunc():
815 def mergefunc():
808 if anc == cur and dst.branch() == cur.branch():
816 if anc == cur and dst.branch() == cur.branch():
809 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
817 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
810 hg.update(self._repo, state[1])
818 hg.update(self._repo, state[1])
811 elif anc == dst:
819 elif anc == dst:
812 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
820 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
813 else:
821 else:
814 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
822 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
815 hg.merge(self._repo, state[1], remind=False)
823 hg.merge(self._repo, state[1], remind=False)
816
824
817 wctx = self._repo[None]
825 wctx = self._repo[None]
818 if self.dirty():
826 if self.dirty():
819 if anc != dst:
827 if anc != dst:
820 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
828 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
821 mergefunc()
829 mergefunc()
822 else:
830 else:
823 mergefunc()
831 mergefunc()
824 else:
832 else:
825 mergefunc()
833 mergefunc()
826
834
827 @annotatesubrepoerror
835 @annotatesubrepoerror
828 def push(self, opts):
836 def push(self, opts):
829 force = opts.get('force')
837 force = opts.get('force')
830 newbranch = opts.get('new_branch')
838 newbranch = opts.get('new_branch')
831 ssh = opts.get('ssh')
839 ssh = opts.get('ssh')
832
840
833 # push subrepos depth-first for coherent ordering
841 # push subrepos depth-first for coherent ordering
834 c = self._repo['']
842 c = self._repo['']
835 subs = c.substate # only repos that are committed
843 subs = c.substate # only repos that are committed
836 for s in sorted(subs):
844 for s in sorted(subs):
837 if c.sub(s).push(opts) == 0:
845 if c.sub(s).push(opts) == 0:
838 return False
846 return False
839
847
840 dsturl = _abssource(self._repo, True)
848 dsturl = _abssource(self._repo, True)
841 if not force:
849 if not force:
842 if self.storeclean(dsturl):
850 if self.storeclean(dsturl):
843 self.ui.status(
851 self.ui.status(
844 _('no changes made to subrepo %s since last push to %s\n')
852 _('no changes made to subrepo %s since last push to %s\n')
845 % (subrelpath(self), dsturl))
853 % (subrelpath(self), dsturl))
846 return None
854 return None
847 self.ui.status(_('pushing subrepo %s to %s\n') %
855 self.ui.status(_('pushing subrepo %s to %s\n') %
848 (subrelpath(self), dsturl))
856 (subrelpath(self), dsturl))
849 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
857 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
850 res = exchange.push(self._repo, other, force, newbranch=newbranch)
858 res = exchange.push(self._repo, other, force, newbranch=newbranch)
851
859
852 # the repo is now clean
860 # the repo is now clean
853 self._cachestorehash(dsturl)
861 self._cachestorehash(dsturl)
854 return res.cgresult
862 return res.cgresult
855
863
856 @annotatesubrepoerror
864 @annotatesubrepoerror
857 def outgoing(self, ui, dest, opts):
865 def outgoing(self, ui, dest, opts):
858 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
866 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
859
867
860 @annotatesubrepoerror
868 @annotatesubrepoerror
861 def incoming(self, ui, source, opts):
869 def incoming(self, ui, source, opts):
862 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
870 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
863
871
864 @annotatesubrepoerror
872 @annotatesubrepoerror
865 def files(self):
873 def files(self):
866 rev = self._state[1]
874 rev = self._state[1]
867 ctx = self._repo[rev]
875 ctx = self._repo[rev]
868 return ctx.manifest().keys()
876 return ctx.manifest().keys()
869
877
870 def filedata(self, name):
878 def filedata(self, name):
871 rev = self._state[1]
879 rev = self._state[1]
872 return self._repo[rev][name].data()
880 return self._repo[rev][name].data()
873
881
874 def fileflags(self, name):
882 def fileflags(self, name):
875 rev = self._state[1]
883 rev = self._state[1]
876 ctx = self._repo[rev]
884 ctx = self._repo[rev]
877 return ctx.flags(name)
885 return ctx.flags(name)
878
886
879 @annotatesubrepoerror
887 @annotatesubrepoerror
880 def printfiles(self, ui, m, fm, fmt):
888 def printfiles(self, ui, m, fm, fmt):
881 # If the parent context is a workingctx, use the workingctx here for
889 # If the parent context is a workingctx, use the workingctx here for
882 # consistency.
890 # consistency.
883 if self._ctx.rev() is None:
891 if self._ctx.rev() is None:
884 ctx = self._repo[None]
892 ctx = self._repo[None]
885 else:
893 else:
886 rev = self._state[1]
894 rev = self._state[1]
887 ctx = self._repo[rev]
895 ctx = self._repo[rev]
888 return cmdutil.files(ui, ctx, m, fm, fmt, True)
896 return cmdutil.files(ui, ctx, m, fm, fmt, True)
889
897
890 def walk(self, match):
898 def walk(self, match):
891 ctx = self._repo[None]
899 ctx = self._repo[None]
892 return ctx.walk(match)
900 return ctx.walk(match)
893
901
894 @annotatesubrepoerror
902 @annotatesubrepoerror
895 def forget(self, match, prefix):
903 def forget(self, match, prefix):
896 return cmdutil.forget(self.ui, self._repo, match,
904 return cmdutil.forget(self.ui, self._repo, match,
897 os.path.join(prefix, self._path), True)
905 os.path.join(prefix, self._path), True)
898
906
899 @annotatesubrepoerror
907 @annotatesubrepoerror
900 def removefiles(self, matcher, prefix, after, force, subrepos):
908 def removefiles(self, matcher, prefix, after, force, subrepos):
901 return cmdutil.remove(self.ui, self._repo, matcher,
909 return cmdutil.remove(self.ui, self._repo, matcher,
902 os.path.join(prefix, self._path), after, force,
910 os.path.join(prefix, self._path), after, force,
903 subrepos)
911 subrepos)
904
912
905 @annotatesubrepoerror
913 @annotatesubrepoerror
906 def revert(self, substate, *pats, **opts):
914 def revert(self, substate, *pats, **opts):
907 # reverting a subrepo is a 2 step process:
915 # reverting a subrepo is a 2 step process:
908 # 1. if the no_backup is not set, revert all modified
916 # 1. if the no_backup is not set, revert all modified
909 # files inside the subrepo
917 # files inside the subrepo
910 # 2. update the subrepo to the revision specified in
918 # 2. update the subrepo to the revision specified in
911 # the corresponding substate dictionary
919 # the corresponding substate dictionary
912 self.ui.status(_('reverting subrepo %s\n') % substate[0])
920 self.ui.status(_('reverting subrepo %s\n') % substate[0])
913 if not opts.get('no_backup'):
921 if not opts.get('no_backup'):
914 # Revert all files on the subrepo, creating backups
922 # Revert all files on the subrepo, creating backups
915 # Note that this will not recursively revert subrepos
923 # Note that this will not recursively revert subrepos
916 # We could do it if there was a set:subrepos() predicate
924 # We could do it if there was a set:subrepos() predicate
917 opts = opts.copy()
925 opts = opts.copy()
918 opts['date'] = None
926 opts['date'] = None
919 opts['rev'] = substate[1]
927 opts['rev'] = substate[1]
920
928
921 self.filerevert(*pats, **opts)
929 self.filerevert(*pats, **opts)
922
930
923 # Update the repo to the revision specified in the given substate
931 # Update the repo to the revision specified in the given substate
924 if not opts.get('dry_run'):
932 if not opts.get('dry_run'):
925 self.get(substate, overwrite=True)
933 self.get(substate, overwrite=True)
926
934
927 def filerevert(self, *pats, **opts):
935 def filerevert(self, *pats, **opts):
928 ctx = self._repo[opts['rev']]
936 ctx = self._repo[opts['rev']]
929 parents = self._repo.dirstate.parents()
937 parents = self._repo.dirstate.parents()
930 if opts.get('all'):
938 if opts.get('all'):
931 pats = ['set:modified()']
939 pats = ['set:modified()']
932 else:
940 else:
933 pats = []
941 pats = []
934 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
942 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
935
943
936 def shortid(self, revid):
944 def shortid(self, revid):
937 return revid[:12]
945 return revid[:12]
938
946
939 class svnsubrepo(abstractsubrepo):
947 class svnsubrepo(abstractsubrepo):
940 def __init__(self, ctx, path, state):
948 def __init__(self, ctx, path, state):
941 super(svnsubrepo, self).__init__(ctx.repo().ui)
949 super(svnsubrepo, self).__init__(ctx, path)
942 self._path = path
943 self._state = state
950 self._state = state
944 self._ctx = ctx
945 self._exe = util.findexe('svn')
951 self._exe = util.findexe('svn')
946 if not self._exe:
952 if not self._exe:
947 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
953 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
948 % self._path)
954 % self._path)
949
955
950 def _svncommand(self, commands, filename='', failok=False):
956 def _svncommand(self, commands, filename='', failok=False):
951 cmd = [self._exe]
957 cmd = [self._exe]
952 extrakw = {}
958 extrakw = {}
953 if not self.ui.interactive():
959 if not self.ui.interactive():
954 # Making stdin be a pipe should prevent svn from behaving
960 # Making stdin be a pipe should prevent svn from behaving
955 # interactively even if we can't pass --non-interactive.
961 # interactively even if we can't pass --non-interactive.
956 extrakw['stdin'] = subprocess.PIPE
962 extrakw['stdin'] = subprocess.PIPE
957 # Starting in svn 1.5 --non-interactive is a global flag
963 # Starting in svn 1.5 --non-interactive is a global flag
958 # instead of being per-command, but we need to support 1.4 so
964 # instead of being per-command, but we need to support 1.4 so
959 # we have to be intelligent about what commands take
965 # we have to be intelligent about what commands take
960 # --non-interactive.
966 # --non-interactive.
961 if commands[0] in ('update', 'checkout', 'commit'):
967 if commands[0] in ('update', 'checkout', 'commit'):
962 cmd.append('--non-interactive')
968 cmd.append('--non-interactive')
963 cmd.extend(commands)
969 cmd.extend(commands)
964 if filename is not None:
970 if filename is not None:
965 path = os.path.join(self._ctx.repo().origroot, self._path, filename)
971 path = os.path.join(self._ctx.repo().origroot, self._path, filename)
966 cmd.append(path)
972 cmd.append(path)
967 env = dict(os.environ)
973 env = dict(os.environ)
968 # Avoid localized output, preserve current locale for everything else.
974 # Avoid localized output, preserve current locale for everything else.
969 lc_all = env.get('LC_ALL')
975 lc_all = env.get('LC_ALL')
970 if lc_all:
976 if lc_all:
971 env['LANG'] = lc_all
977 env['LANG'] = lc_all
972 del env['LC_ALL']
978 del env['LC_ALL']
973 env['LC_MESSAGES'] = 'C'
979 env['LC_MESSAGES'] = 'C'
974 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
980 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
975 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
981 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
976 universal_newlines=True, env=env, **extrakw)
982 universal_newlines=True, env=env, **extrakw)
977 stdout, stderr = p.communicate()
983 stdout, stderr = p.communicate()
978 stderr = stderr.strip()
984 stderr = stderr.strip()
979 if not failok:
985 if not failok:
980 if p.returncode:
986 if p.returncode:
981 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
987 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
982 if stderr:
988 if stderr:
983 self.ui.warn(stderr + '\n')
989 self.ui.warn(stderr + '\n')
984 return stdout, stderr
990 return stdout, stderr
985
991
986 @propertycache
992 @propertycache
987 def _svnversion(self):
993 def _svnversion(self):
988 output, err = self._svncommand(['--version', '--quiet'], filename=None)
994 output, err = self._svncommand(['--version', '--quiet'], filename=None)
989 m = re.search(r'^(\d+)\.(\d+)', output)
995 m = re.search(r'^(\d+)\.(\d+)', output)
990 if not m:
996 if not m:
991 raise util.Abort(_('cannot retrieve svn tool version'))
997 raise util.Abort(_('cannot retrieve svn tool version'))
992 return (int(m.group(1)), int(m.group(2)))
998 return (int(m.group(1)), int(m.group(2)))
993
999
994 def _wcrevs(self):
1000 def _wcrevs(self):
995 # Get the working directory revision as well as the last
1001 # Get the working directory revision as well as the last
996 # commit revision so we can compare the subrepo state with
1002 # commit revision so we can compare the subrepo state with
997 # both. We used to store the working directory one.
1003 # both. We used to store the working directory one.
998 output, err = self._svncommand(['info', '--xml'])
1004 output, err = self._svncommand(['info', '--xml'])
999 doc = xml.dom.minidom.parseString(output)
1005 doc = xml.dom.minidom.parseString(output)
1000 entries = doc.getElementsByTagName('entry')
1006 entries = doc.getElementsByTagName('entry')
1001 lastrev, rev = '0', '0'
1007 lastrev, rev = '0', '0'
1002 if entries:
1008 if entries:
1003 rev = str(entries[0].getAttribute('revision')) or '0'
1009 rev = str(entries[0].getAttribute('revision')) or '0'
1004 commits = entries[0].getElementsByTagName('commit')
1010 commits = entries[0].getElementsByTagName('commit')
1005 if commits:
1011 if commits:
1006 lastrev = str(commits[0].getAttribute('revision')) or '0'
1012 lastrev = str(commits[0].getAttribute('revision')) or '0'
1007 return (lastrev, rev)
1013 return (lastrev, rev)
1008
1014
1009 def _wcrev(self):
1015 def _wcrev(self):
1010 return self._wcrevs()[0]
1016 return self._wcrevs()[0]
1011
1017
1012 def _wcchanged(self):
1018 def _wcchanged(self):
1013 """Return (changes, extchanges, missing) where changes is True
1019 """Return (changes, extchanges, missing) where changes is True
1014 if the working directory was changed, extchanges is
1020 if the working directory was changed, extchanges is
1015 True if any of these changes concern an external entry and missing
1021 True if any of these changes concern an external entry and missing
1016 is True if any change is a missing entry.
1022 is True if any change is a missing entry.
1017 """
1023 """
1018 output, err = self._svncommand(['status', '--xml'])
1024 output, err = self._svncommand(['status', '--xml'])
1019 externals, changes, missing = [], [], []
1025 externals, changes, missing = [], [], []
1020 doc = xml.dom.minidom.parseString(output)
1026 doc = xml.dom.minidom.parseString(output)
1021 for e in doc.getElementsByTagName('entry'):
1027 for e in doc.getElementsByTagName('entry'):
1022 s = e.getElementsByTagName('wc-status')
1028 s = e.getElementsByTagName('wc-status')
1023 if not s:
1029 if not s:
1024 continue
1030 continue
1025 item = s[0].getAttribute('item')
1031 item = s[0].getAttribute('item')
1026 props = s[0].getAttribute('props')
1032 props = s[0].getAttribute('props')
1027 path = e.getAttribute('path')
1033 path = e.getAttribute('path')
1028 if item == 'external':
1034 if item == 'external':
1029 externals.append(path)
1035 externals.append(path)
1030 elif item == 'missing':
1036 elif item == 'missing':
1031 missing.append(path)
1037 missing.append(path)
1032 if (item not in ('', 'normal', 'unversioned', 'external')
1038 if (item not in ('', 'normal', 'unversioned', 'external')
1033 or props not in ('', 'none', 'normal')):
1039 or props not in ('', 'none', 'normal')):
1034 changes.append(path)
1040 changes.append(path)
1035 for path in changes:
1041 for path in changes:
1036 for ext in externals:
1042 for ext in externals:
1037 if path == ext or path.startswith(ext + os.sep):
1043 if path == ext or path.startswith(ext + os.sep):
1038 return True, True, bool(missing)
1044 return True, True, bool(missing)
1039 return bool(changes), False, bool(missing)
1045 return bool(changes), False, bool(missing)
1040
1046
1041 def dirty(self, ignoreupdate=False):
1047 def dirty(self, ignoreupdate=False):
1042 if not self._wcchanged()[0]:
1048 if not self._wcchanged()[0]:
1043 if self._state[1] in self._wcrevs() or ignoreupdate:
1049 if self._state[1] in self._wcrevs() or ignoreupdate:
1044 return False
1050 return False
1045 return True
1051 return True
1046
1052
1047 def basestate(self):
1053 def basestate(self):
1048 lastrev, rev = self._wcrevs()
1054 lastrev, rev = self._wcrevs()
1049 if lastrev != rev:
1055 if lastrev != rev:
1050 # Last committed rev is not the same than rev. We would
1056 # Last committed rev is not the same than rev. We would
1051 # like to take lastrev but we do not know if the subrepo
1057 # like to take lastrev but we do not know if the subrepo
1052 # URL exists at lastrev. Test it and fallback to rev it
1058 # URL exists at lastrev. Test it and fallback to rev it
1053 # is not there.
1059 # is not there.
1054 try:
1060 try:
1055 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1061 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1056 return lastrev
1062 return lastrev
1057 except error.Abort:
1063 except error.Abort:
1058 pass
1064 pass
1059 return rev
1065 return rev
1060
1066
1061 @annotatesubrepoerror
1067 @annotatesubrepoerror
1062 def commit(self, text, user, date):
1068 def commit(self, text, user, date):
1063 # user and date are out of our hands since svn is centralized
1069 # user and date are out of our hands since svn is centralized
1064 changed, extchanged, missing = self._wcchanged()
1070 changed, extchanged, missing = self._wcchanged()
1065 if not changed:
1071 if not changed:
1066 return self.basestate()
1072 return self.basestate()
1067 if extchanged:
1073 if extchanged:
1068 # Do not try to commit externals
1074 # Do not try to commit externals
1069 raise util.Abort(_('cannot commit svn externals'))
1075 raise util.Abort(_('cannot commit svn externals'))
1070 if missing:
1076 if missing:
1071 # svn can commit with missing entries but aborting like hg
1077 # svn can commit with missing entries but aborting like hg
1072 # seems a better approach.
1078 # seems a better approach.
1073 raise util.Abort(_('cannot commit missing svn entries'))
1079 raise util.Abort(_('cannot commit missing svn entries'))
1074 commitinfo, err = self._svncommand(['commit', '-m', text])
1080 commitinfo, err = self._svncommand(['commit', '-m', text])
1075 self.ui.status(commitinfo)
1081 self.ui.status(commitinfo)
1076 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1082 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1077 if not newrev:
1083 if not newrev:
1078 if not commitinfo.strip():
1084 if not commitinfo.strip():
1079 # Sometimes, our definition of "changed" differs from
1085 # Sometimes, our definition of "changed" differs from
1080 # svn one. For instance, svn ignores missing files
1086 # svn one. For instance, svn ignores missing files
1081 # when committing. If there are only missing files, no
1087 # when committing. If there are only missing files, no
1082 # commit is made, no output and no error code.
1088 # commit is made, no output and no error code.
1083 raise util.Abort(_('failed to commit svn changes'))
1089 raise util.Abort(_('failed to commit svn changes'))
1084 raise util.Abort(commitinfo.splitlines()[-1])
1090 raise util.Abort(commitinfo.splitlines()[-1])
1085 newrev = newrev.groups()[0]
1091 newrev = newrev.groups()[0]
1086 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1092 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1087 return newrev
1093 return newrev
1088
1094
1089 @annotatesubrepoerror
1095 @annotatesubrepoerror
1090 def remove(self):
1096 def remove(self):
1091 if self.dirty():
1097 if self.dirty():
1092 self.ui.warn(_('not removing repo %s because '
1098 self.ui.warn(_('not removing repo %s because '
1093 'it has changes.\n') % self._path)
1099 'it has changes.\n') % self._path)
1094 return
1100 return
1095 self.ui.note(_('removing subrepo %s\n') % self._path)
1101 self.ui.note(_('removing subrepo %s\n') % self._path)
1096
1102
1097 def onerror(function, path, excinfo):
1103 def onerror(function, path, excinfo):
1098 if function is not os.remove:
1104 if function is not os.remove:
1099 raise
1105 raise
1100 # read-only files cannot be unlinked under Windows
1106 # read-only files cannot be unlinked under Windows
1101 s = os.stat(path)
1107 s = os.stat(path)
1102 if (s.st_mode & stat.S_IWRITE) != 0:
1108 if (s.st_mode & stat.S_IWRITE) != 0:
1103 raise
1109 raise
1104 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1110 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1105 os.remove(path)
1111 os.remove(path)
1106
1112
1107 path = self._ctx.repo().wjoin(self._path)
1113 path = self._ctx.repo().wjoin(self._path)
1108 shutil.rmtree(path, onerror=onerror)
1114 shutil.rmtree(path, onerror=onerror)
1109 try:
1115 try:
1110 os.removedirs(os.path.dirname(path))
1116 os.removedirs(os.path.dirname(path))
1111 except OSError:
1117 except OSError:
1112 pass
1118 pass
1113
1119
1114 @annotatesubrepoerror
1120 @annotatesubrepoerror
1115 def get(self, state, overwrite=False):
1121 def get(self, state, overwrite=False):
1116 if overwrite:
1122 if overwrite:
1117 self._svncommand(['revert', '--recursive'])
1123 self._svncommand(['revert', '--recursive'])
1118 args = ['checkout']
1124 args = ['checkout']
1119 if self._svnversion >= (1, 5):
1125 if self._svnversion >= (1, 5):
1120 args.append('--force')
1126 args.append('--force')
1121 # The revision must be specified at the end of the URL to properly
1127 # The revision must be specified at the end of the URL to properly
1122 # update to a directory which has since been deleted and recreated.
1128 # update to a directory which has since been deleted and recreated.
1123 args.append('%s@%s' % (state[0], state[1]))
1129 args.append('%s@%s' % (state[0], state[1]))
1124 status, err = self._svncommand(args, failok=True)
1130 status, err = self._svncommand(args, failok=True)
1125 _sanitize(self.ui, self._ctx.repo().wjoin(self._path), '.svn')
1131 _sanitize(self.ui, self._ctx.repo().wjoin(self._path), '.svn')
1126 if not re.search('Checked out revision [0-9]+.', status):
1132 if not re.search('Checked out revision [0-9]+.', status):
1127 if ('is already a working copy for a different URL' in err
1133 if ('is already a working copy for a different URL' in err
1128 and (self._wcchanged()[:2] == (False, False))):
1134 and (self._wcchanged()[:2] == (False, False))):
1129 # obstructed but clean working copy, so just blow it away.
1135 # obstructed but clean working copy, so just blow it away.
1130 self.remove()
1136 self.remove()
1131 self.get(state, overwrite=False)
1137 self.get(state, overwrite=False)
1132 return
1138 return
1133 raise util.Abort((status or err).splitlines()[-1])
1139 raise util.Abort((status or err).splitlines()[-1])
1134 self.ui.status(status)
1140 self.ui.status(status)
1135
1141
1136 @annotatesubrepoerror
1142 @annotatesubrepoerror
1137 def merge(self, state):
1143 def merge(self, state):
1138 old = self._state[1]
1144 old = self._state[1]
1139 new = state[1]
1145 new = state[1]
1140 wcrev = self._wcrev()
1146 wcrev = self._wcrev()
1141 if new != wcrev:
1147 if new != wcrev:
1142 dirty = old == wcrev or self._wcchanged()[0]
1148 dirty = old == wcrev or self._wcchanged()[0]
1143 if _updateprompt(self.ui, self, dirty, wcrev, new):
1149 if _updateprompt(self.ui, self, dirty, wcrev, new):
1144 self.get(state, False)
1150 self.get(state, False)
1145
1151
1146 def push(self, opts):
1152 def push(self, opts):
1147 # push is a no-op for SVN
1153 # push is a no-op for SVN
1148 return True
1154 return True
1149
1155
1150 @annotatesubrepoerror
1156 @annotatesubrepoerror
1151 def files(self):
1157 def files(self):
1152 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1158 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1153 doc = xml.dom.minidom.parseString(output)
1159 doc = xml.dom.minidom.parseString(output)
1154 paths = []
1160 paths = []
1155 for e in doc.getElementsByTagName('entry'):
1161 for e in doc.getElementsByTagName('entry'):
1156 kind = str(e.getAttribute('kind'))
1162 kind = str(e.getAttribute('kind'))
1157 if kind != 'file':
1163 if kind != 'file':
1158 continue
1164 continue
1159 name = ''.join(c.data for c
1165 name = ''.join(c.data for c
1160 in e.getElementsByTagName('name')[0].childNodes
1166 in e.getElementsByTagName('name')[0].childNodes
1161 if c.nodeType == c.TEXT_NODE)
1167 if c.nodeType == c.TEXT_NODE)
1162 paths.append(name.encode('utf-8'))
1168 paths.append(name.encode('utf-8'))
1163 return paths
1169 return paths
1164
1170
1165 def filedata(self, name):
1171 def filedata(self, name):
1166 return self._svncommand(['cat'], name)[0]
1172 return self._svncommand(['cat'], name)[0]
1167
1173
1168
1174
1169 class gitsubrepo(abstractsubrepo):
1175 class gitsubrepo(abstractsubrepo):
1170 def __init__(self, ctx, path, state):
1176 def __init__(self, ctx, path, state):
1171 super(gitsubrepo, self).__init__(ctx.repo().ui)
1177 super(gitsubrepo, self).__init__(ctx, path)
1172 self._state = state
1178 self._state = state
1173 self._ctx = ctx
1174 self._path = path
1175 self._relpath = os.path.join(reporelpath(ctx.repo()), path)
1179 self._relpath = os.path.join(reporelpath(ctx.repo()), path)
1176 self._abspath = ctx.repo().wjoin(path)
1180 self._abspath = ctx.repo().wjoin(path)
1177 self._subparent = ctx.repo()
1181 self._subparent = ctx.repo()
1178 self._ensuregit()
1182 self._ensuregit()
1179
1183
1180 def _ensuregit(self):
1184 def _ensuregit(self):
1181 try:
1185 try:
1182 self._gitexecutable = 'git'
1186 self._gitexecutable = 'git'
1183 out, err = self._gitnodir(['--version'])
1187 out, err = self._gitnodir(['--version'])
1184 except OSError, e:
1188 except OSError, e:
1185 if e.errno != 2 or os.name != 'nt':
1189 if e.errno != 2 or os.name != 'nt':
1186 raise
1190 raise
1187 self._gitexecutable = 'git.cmd'
1191 self._gitexecutable = 'git.cmd'
1188 out, err = self._gitnodir(['--version'])
1192 out, err = self._gitnodir(['--version'])
1189 versionstatus = self._checkversion(out)
1193 versionstatus = self._checkversion(out)
1190 if versionstatus == 'unknown':
1194 if versionstatus == 'unknown':
1191 self.ui.warn(_('cannot retrieve git version\n'))
1195 self.ui.warn(_('cannot retrieve git version\n'))
1192 elif versionstatus == 'abort':
1196 elif versionstatus == 'abort':
1193 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1197 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1194 elif versionstatus == 'warning':
1198 elif versionstatus == 'warning':
1195 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1199 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1196
1200
1197 @staticmethod
1201 @staticmethod
1198 def _gitversion(out):
1202 def _gitversion(out):
1199 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1203 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1200 if m:
1204 if m:
1201 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1205 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1202
1206
1203 m = re.search(r'^git version (\d+)\.(\d+)', out)
1207 m = re.search(r'^git version (\d+)\.(\d+)', out)
1204 if m:
1208 if m:
1205 return (int(m.group(1)), int(m.group(2)), 0)
1209 return (int(m.group(1)), int(m.group(2)), 0)
1206
1210
1207 return -1
1211 return -1
1208
1212
1209 @staticmethod
1213 @staticmethod
1210 def _checkversion(out):
1214 def _checkversion(out):
1211 '''ensure git version is new enough
1215 '''ensure git version is new enough
1212
1216
1213 >>> _checkversion = gitsubrepo._checkversion
1217 >>> _checkversion = gitsubrepo._checkversion
1214 >>> _checkversion('git version 1.6.0')
1218 >>> _checkversion('git version 1.6.0')
1215 'ok'
1219 'ok'
1216 >>> _checkversion('git version 1.8.5')
1220 >>> _checkversion('git version 1.8.5')
1217 'ok'
1221 'ok'
1218 >>> _checkversion('git version 1.4.0')
1222 >>> _checkversion('git version 1.4.0')
1219 'abort'
1223 'abort'
1220 >>> _checkversion('git version 1.5.0')
1224 >>> _checkversion('git version 1.5.0')
1221 'warning'
1225 'warning'
1222 >>> _checkversion('git version 1.9-rc0')
1226 >>> _checkversion('git version 1.9-rc0')
1223 'ok'
1227 'ok'
1224 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1228 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1225 'ok'
1229 'ok'
1226 >>> _checkversion('git version 1.9.0.GIT')
1230 >>> _checkversion('git version 1.9.0.GIT')
1227 'ok'
1231 'ok'
1228 >>> _checkversion('git version 12345')
1232 >>> _checkversion('git version 12345')
1229 'unknown'
1233 'unknown'
1230 >>> _checkversion('no')
1234 >>> _checkversion('no')
1231 'unknown'
1235 'unknown'
1232 '''
1236 '''
1233 version = gitsubrepo._gitversion(out)
1237 version = gitsubrepo._gitversion(out)
1234 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1238 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1235 # despite the docstring comment. For now, error on 1.4.0, warn on
1239 # despite the docstring comment. For now, error on 1.4.0, warn on
1236 # 1.5.0 but attempt to continue.
1240 # 1.5.0 but attempt to continue.
1237 if version == -1:
1241 if version == -1:
1238 return 'unknown'
1242 return 'unknown'
1239 if version < (1, 5, 0):
1243 if version < (1, 5, 0):
1240 return 'abort'
1244 return 'abort'
1241 elif version < (1, 6, 0):
1245 elif version < (1, 6, 0):
1242 return 'warning'
1246 return 'warning'
1243 return 'ok'
1247 return 'ok'
1244
1248
1245 def _gitcommand(self, commands, env=None, stream=False):
1249 def _gitcommand(self, commands, env=None, stream=False):
1246 return self._gitdir(commands, env=env, stream=stream)[0]
1250 return self._gitdir(commands, env=env, stream=stream)[0]
1247
1251
1248 def _gitdir(self, commands, env=None, stream=False):
1252 def _gitdir(self, commands, env=None, stream=False):
1249 return self._gitnodir(commands, env=env, stream=stream,
1253 return self._gitnodir(commands, env=env, stream=stream,
1250 cwd=self._abspath)
1254 cwd=self._abspath)
1251
1255
1252 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1256 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1253 """Calls the git command
1257 """Calls the git command
1254
1258
1255 The methods tries to call the git command. versions prior to 1.6.0
1259 The methods tries to call the git command. versions prior to 1.6.0
1256 are not supported and very probably fail.
1260 are not supported and very probably fail.
1257 """
1261 """
1258 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1262 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1259 # unless ui.quiet is set, print git's stderr,
1263 # unless ui.quiet is set, print git's stderr,
1260 # which is mostly progress and useful info
1264 # which is mostly progress and useful info
1261 errpipe = None
1265 errpipe = None
1262 if self.ui.quiet:
1266 if self.ui.quiet:
1263 errpipe = open(os.devnull, 'w')
1267 errpipe = open(os.devnull, 'w')
1264 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1268 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1265 cwd=cwd, env=env, close_fds=util.closefds,
1269 cwd=cwd, env=env, close_fds=util.closefds,
1266 stdout=subprocess.PIPE, stderr=errpipe)
1270 stdout=subprocess.PIPE, stderr=errpipe)
1267 if stream:
1271 if stream:
1268 return p.stdout, None
1272 return p.stdout, None
1269
1273
1270 retdata = p.stdout.read().strip()
1274 retdata = p.stdout.read().strip()
1271 # wait for the child to exit to avoid race condition.
1275 # wait for the child to exit to avoid race condition.
1272 p.wait()
1276 p.wait()
1273
1277
1274 if p.returncode != 0 and p.returncode != 1:
1278 if p.returncode != 0 and p.returncode != 1:
1275 # there are certain error codes that are ok
1279 # there are certain error codes that are ok
1276 command = commands[0]
1280 command = commands[0]
1277 if command in ('cat-file', 'symbolic-ref'):
1281 if command in ('cat-file', 'symbolic-ref'):
1278 return retdata, p.returncode
1282 return retdata, p.returncode
1279 # for all others, abort
1283 # for all others, abort
1280 raise util.Abort('git %s error %d in %s' %
1284 raise util.Abort('git %s error %d in %s' %
1281 (command, p.returncode, self._relpath))
1285 (command, p.returncode, self._relpath))
1282
1286
1283 return retdata, p.returncode
1287 return retdata, p.returncode
1284
1288
1285 def _gitmissing(self):
1289 def _gitmissing(self):
1286 return not os.path.exists(os.path.join(self._abspath, '.git'))
1290 return not os.path.exists(os.path.join(self._abspath, '.git'))
1287
1291
1288 def _gitstate(self):
1292 def _gitstate(self):
1289 return self._gitcommand(['rev-parse', 'HEAD'])
1293 return self._gitcommand(['rev-parse', 'HEAD'])
1290
1294
1291 def _gitcurrentbranch(self):
1295 def _gitcurrentbranch(self):
1292 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1296 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1293 if err:
1297 if err:
1294 current = None
1298 current = None
1295 return current
1299 return current
1296
1300
1297 def _gitremote(self, remote):
1301 def _gitremote(self, remote):
1298 out = self._gitcommand(['remote', 'show', '-n', remote])
1302 out = self._gitcommand(['remote', 'show', '-n', remote])
1299 line = out.split('\n')[1]
1303 line = out.split('\n')[1]
1300 i = line.index('URL: ') + len('URL: ')
1304 i = line.index('URL: ') + len('URL: ')
1301 return line[i:]
1305 return line[i:]
1302
1306
1303 def _githavelocally(self, revision):
1307 def _githavelocally(self, revision):
1304 out, code = self._gitdir(['cat-file', '-e', revision])
1308 out, code = self._gitdir(['cat-file', '-e', revision])
1305 return code == 0
1309 return code == 0
1306
1310
1307 def _gitisancestor(self, r1, r2):
1311 def _gitisancestor(self, r1, r2):
1308 base = self._gitcommand(['merge-base', r1, r2])
1312 base = self._gitcommand(['merge-base', r1, r2])
1309 return base == r1
1313 return base == r1
1310
1314
1311 def _gitisbare(self):
1315 def _gitisbare(self):
1312 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1316 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1313
1317
1314 def _gitupdatestat(self):
1318 def _gitupdatestat(self):
1315 """This must be run before git diff-index.
1319 """This must be run before git diff-index.
1316 diff-index only looks at changes to file stat;
1320 diff-index only looks at changes to file stat;
1317 this command looks at file contents and updates the stat."""
1321 this command looks at file contents and updates the stat."""
1318 self._gitcommand(['update-index', '-q', '--refresh'])
1322 self._gitcommand(['update-index', '-q', '--refresh'])
1319
1323
1320 def _gitbranchmap(self):
1324 def _gitbranchmap(self):
1321 '''returns 2 things:
1325 '''returns 2 things:
1322 a map from git branch to revision
1326 a map from git branch to revision
1323 a map from revision to branches'''
1327 a map from revision to branches'''
1324 branch2rev = {}
1328 branch2rev = {}
1325 rev2branch = {}
1329 rev2branch = {}
1326
1330
1327 out = self._gitcommand(['for-each-ref', '--format',
1331 out = self._gitcommand(['for-each-ref', '--format',
1328 '%(objectname) %(refname)'])
1332 '%(objectname) %(refname)'])
1329 for line in out.split('\n'):
1333 for line in out.split('\n'):
1330 revision, ref = line.split(' ')
1334 revision, ref = line.split(' ')
1331 if (not ref.startswith('refs/heads/') and
1335 if (not ref.startswith('refs/heads/') and
1332 not ref.startswith('refs/remotes/')):
1336 not ref.startswith('refs/remotes/')):
1333 continue
1337 continue
1334 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1338 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1335 continue # ignore remote/HEAD redirects
1339 continue # ignore remote/HEAD redirects
1336 branch2rev[ref] = revision
1340 branch2rev[ref] = revision
1337 rev2branch.setdefault(revision, []).append(ref)
1341 rev2branch.setdefault(revision, []).append(ref)
1338 return branch2rev, rev2branch
1342 return branch2rev, rev2branch
1339
1343
1340 def _gittracking(self, branches):
1344 def _gittracking(self, branches):
1341 'return map of remote branch to local tracking branch'
1345 'return map of remote branch to local tracking branch'
1342 # assumes no more than one local tracking branch for each remote
1346 # assumes no more than one local tracking branch for each remote
1343 tracking = {}
1347 tracking = {}
1344 for b in branches:
1348 for b in branches:
1345 if b.startswith('refs/remotes/'):
1349 if b.startswith('refs/remotes/'):
1346 continue
1350 continue
1347 bname = b.split('/', 2)[2]
1351 bname = b.split('/', 2)[2]
1348 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1352 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1349 if remote:
1353 if remote:
1350 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1354 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1351 tracking['refs/remotes/%s/%s' %
1355 tracking['refs/remotes/%s/%s' %
1352 (remote, ref.split('/', 2)[2])] = b
1356 (remote, ref.split('/', 2)[2])] = b
1353 return tracking
1357 return tracking
1354
1358
1355 def _abssource(self, source):
1359 def _abssource(self, source):
1356 if '://' not in source:
1360 if '://' not in source:
1357 # recognize the scp syntax as an absolute source
1361 # recognize the scp syntax as an absolute source
1358 colon = source.find(':')
1362 colon = source.find(':')
1359 if colon != -1 and '/' not in source[:colon]:
1363 if colon != -1 and '/' not in source[:colon]:
1360 return source
1364 return source
1361 self._subsource = source
1365 self._subsource = source
1362 return _abssource(self)
1366 return _abssource(self)
1363
1367
1364 def _fetch(self, source, revision):
1368 def _fetch(self, source, revision):
1365 if self._gitmissing():
1369 if self._gitmissing():
1366 source = self._abssource(source)
1370 source = self._abssource(source)
1367 self.ui.status(_('cloning subrepo %s from %s\n') %
1371 self.ui.status(_('cloning subrepo %s from %s\n') %
1368 (self._relpath, source))
1372 (self._relpath, source))
1369 self._gitnodir(['clone', source, self._abspath])
1373 self._gitnodir(['clone', source, self._abspath])
1370 if self._githavelocally(revision):
1374 if self._githavelocally(revision):
1371 return
1375 return
1372 self.ui.status(_('pulling subrepo %s from %s\n') %
1376 self.ui.status(_('pulling subrepo %s from %s\n') %
1373 (self._relpath, self._gitremote('origin')))
1377 (self._relpath, self._gitremote('origin')))
1374 # try only origin: the originally cloned repo
1378 # try only origin: the originally cloned repo
1375 self._gitcommand(['fetch'])
1379 self._gitcommand(['fetch'])
1376 if not self._githavelocally(revision):
1380 if not self._githavelocally(revision):
1377 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1381 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1378 (revision, self._relpath))
1382 (revision, self._relpath))
1379
1383
1380 @annotatesubrepoerror
1384 @annotatesubrepoerror
1381 def dirty(self, ignoreupdate=False):
1385 def dirty(self, ignoreupdate=False):
1382 if self._gitmissing():
1386 if self._gitmissing():
1383 return self._state[1] != ''
1387 return self._state[1] != ''
1384 if self._gitisbare():
1388 if self._gitisbare():
1385 return True
1389 return True
1386 if not ignoreupdate and self._state[1] != self._gitstate():
1390 if not ignoreupdate and self._state[1] != self._gitstate():
1387 # different version checked out
1391 # different version checked out
1388 return True
1392 return True
1389 # check for staged changes or modified files; ignore untracked files
1393 # check for staged changes or modified files; ignore untracked files
1390 self._gitupdatestat()
1394 self._gitupdatestat()
1391 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1395 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1392 return code == 1
1396 return code == 1
1393
1397
1394 def basestate(self):
1398 def basestate(self):
1395 return self._gitstate()
1399 return self._gitstate()
1396
1400
1397 @annotatesubrepoerror
1401 @annotatesubrepoerror
1398 def get(self, state, overwrite=False):
1402 def get(self, state, overwrite=False):
1399 source, revision, kind = state
1403 source, revision, kind = state
1400 if not revision:
1404 if not revision:
1401 self.remove()
1405 self.remove()
1402 return
1406 return
1403 self._fetch(source, revision)
1407 self._fetch(source, revision)
1404 # if the repo was set to be bare, unbare it
1408 # if the repo was set to be bare, unbare it
1405 if self._gitisbare():
1409 if self._gitisbare():
1406 self._gitcommand(['config', 'core.bare', 'false'])
1410 self._gitcommand(['config', 'core.bare', 'false'])
1407 if self._gitstate() == revision:
1411 if self._gitstate() == revision:
1408 self._gitcommand(['reset', '--hard', 'HEAD'])
1412 self._gitcommand(['reset', '--hard', 'HEAD'])
1409 return
1413 return
1410 elif self._gitstate() == revision:
1414 elif self._gitstate() == revision:
1411 if overwrite:
1415 if overwrite:
1412 # first reset the index to unmark new files for commit, because
1416 # first reset the index to unmark new files for commit, because
1413 # reset --hard will otherwise throw away files added for commit,
1417 # reset --hard will otherwise throw away files added for commit,
1414 # not just unmark them.
1418 # not just unmark them.
1415 self._gitcommand(['reset', 'HEAD'])
1419 self._gitcommand(['reset', 'HEAD'])
1416 self._gitcommand(['reset', '--hard', 'HEAD'])
1420 self._gitcommand(['reset', '--hard', 'HEAD'])
1417 return
1421 return
1418 branch2rev, rev2branch = self._gitbranchmap()
1422 branch2rev, rev2branch = self._gitbranchmap()
1419
1423
1420 def checkout(args):
1424 def checkout(args):
1421 cmd = ['checkout']
1425 cmd = ['checkout']
1422 if overwrite:
1426 if overwrite:
1423 # first reset the index to unmark new files for commit, because
1427 # first reset the index to unmark new files for commit, because
1424 # the -f option will otherwise throw away files added for
1428 # the -f option will otherwise throw away files added for
1425 # commit, not just unmark them.
1429 # commit, not just unmark them.
1426 self._gitcommand(['reset', 'HEAD'])
1430 self._gitcommand(['reset', 'HEAD'])
1427 cmd.append('-f')
1431 cmd.append('-f')
1428 self._gitcommand(cmd + args)
1432 self._gitcommand(cmd + args)
1429 _sanitize(self.ui, self._abspath, '.git')
1433 _sanitize(self.ui, self._abspath, '.git')
1430
1434
1431 def rawcheckout():
1435 def rawcheckout():
1432 # no branch to checkout, check it out with no branch
1436 # no branch to checkout, check it out with no branch
1433 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1437 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1434 self._relpath)
1438 self._relpath)
1435 self.ui.warn(_('check out a git branch if you intend '
1439 self.ui.warn(_('check out a git branch if you intend '
1436 'to make changes\n'))
1440 'to make changes\n'))
1437 checkout(['-q', revision])
1441 checkout(['-q', revision])
1438
1442
1439 if revision not in rev2branch:
1443 if revision not in rev2branch:
1440 rawcheckout()
1444 rawcheckout()
1441 return
1445 return
1442 branches = rev2branch[revision]
1446 branches = rev2branch[revision]
1443 firstlocalbranch = None
1447 firstlocalbranch = None
1444 for b in branches:
1448 for b in branches:
1445 if b == 'refs/heads/master':
1449 if b == 'refs/heads/master':
1446 # master trumps all other branches
1450 # master trumps all other branches
1447 checkout(['refs/heads/master'])
1451 checkout(['refs/heads/master'])
1448 return
1452 return
1449 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1453 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1450 firstlocalbranch = b
1454 firstlocalbranch = b
1451 if firstlocalbranch:
1455 if firstlocalbranch:
1452 checkout([firstlocalbranch])
1456 checkout([firstlocalbranch])
1453 return
1457 return
1454
1458
1455 tracking = self._gittracking(branch2rev.keys())
1459 tracking = self._gittracking(branch2rev.keys())
1456 # choose a remote branch already tracked if possible
1460 # choose a remote branch already tracked if possible
1457 remote = branches[0]
1461 remote = branches[0]
1458 if remote not in tracking:
1462 if remote not in tracking:
1459 for b in branches:
1463 for b in branches:
1460 if b in tracking:
1464 if b in tracking:
1461 remote = b
1465 remote = b
1462 break
1466 break
1463
1467
1464 if remote not in tracking:
1468 if remote not in tracking:
1465 # create a new local tracking branch
1469 # create a new local tracking branch
1466 local = remote.split('/', 3)[3]
1470 local = remote.split('/', 3)[3]
1467 checkout(['-b', local, remote])
1471 checkout(['-b', local, remote])
1468 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1472 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1469 # When updating to a tracked remote branch,
1473 # When updating to a tracked remote branch,
1470 # if the local tracking branch is downstream of it,
1474 # if the local tracking branch is downstream of it,
1471 # a normal `git pull` would have performed a "fast-forward merge"
1475 # a normal `git pull` would have performed a "fast-forward merge"
1472 # which is equivalent to updating the local branch to the remote.
1476 # which is equivalent to updating the local branch to the remote.
1473 # Since we are only looking at branching at update, we need to
1477 # Since we are only looking at branching at update, we need to
1474 # detect this situation and perform this action lazily.
1478 # detect this situation and perform this action lazily.
1475 if tracking[remote] != self._gitcurrentbranch():
1479 if tracking[remote] != self._gitcurrentbranch():
1476 checkout([tracking[remote]])
1480 checkout([tracking[remote]])
1477 self._gitcommand(['merge', '--ff', remote])
1481 self._gitcommand(['merge', '--ff', remote])
1478 _sanitize(self.ui, self._abspath, '.git')
1482 _sanitize(self.ui, self._abspath, '.git')
1479 else:
1483 else:
1480 # a real merge would be required, just checkout the revision
1484 # a real merge would be required, just checkout the revision
1481 rawcheckout()
1485 rawcheckout()
1482
1486
1483 @annotatesubrepoerror
1487 @annotatesubrepoerror
1484 def commit(self, text, user, date):
1488 def commit(self, text, user, date):
1485 if self._gitmissing():
1489 if self._gitmissing():
1486 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1490 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1487 cmd = ['commit', '-a', '-m', text]
1491 cmd = ['commit', '-a', '-m', text]
1488 env = os.environ.copy()
1492 env = os.environ.copy()
1489 if user:
1493 if user:
1490 cmd += ['--author', user]
1494 cmd += ['--author', user]
1491 if date:
1495 if date:
1492 # git's date parser silently ignores when seconds < 1e9
1496 # git's date parser silently ignores when seconds < 1e9
1493 # convert to ISO8601
1497 # convert to ISO8601
1494 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1498 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1495 '%Y-%m-%dT%H:%M:%S %1%2')
1499 '%Y-%m-%dT%H:%M:%S %1%2')
1496 self._gitcommand(cmd, env=env)
1500 self._gitcommand(cmd, env=env)
1497 # make sure commit works otherwise HEAD might not exist under certain
1501 # make sure commit works otherwise HEAD might not exist under certain
1498 # circumstances
1502 # circumstances
1499 return self._gitstate()
1503 return self._gitstate()
1500
1504
1501 @annotatesubrepoerror
1505 @annotatesubrepoerror
1502 def merge(self, state):
1506 def merge(self, state):
1503 source, revision, kind = state
1507 source, revision, kind = state
1504 self._fetch(source, revision)
1508 self._fetch(source, revision)
1505 base = self._gitcommand(['merge-base', revision, self._state[1]])
1509 base = self._gitcommand(['merge-base', revision, self._state[1]])
1506 self._gitupdatestat()
1510 self._gitupdatestat()
1507 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1511 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1508
1512
1509 def mergefunc():
1513 def mergefunc():
1510 if base == revision:
1514 if base == revision:
1511 self.get(state) # fast forward merge
1515 self.get(state) # fast forward merge
1512 elif base != self._state[1]:
1516 elif base != self._state[1]:
1513 self._gitcommand(['merge', '--no-commit', revision])
1517 self._gitcommand(['merge', '--no-commit', revision])
1514 _sanitize(self.ui, self._abspath, '.git')
1518 _sanitize(self.ui, self._abspath, '.git')
1515
1519
1516 if self.dirty():
1520 if self.dirty():
1517 if self._gitstate() != revision:
1521 if self._gitstate() != revision:
1518 dirty = self._gitstate() == self._state[1] or code != 0
1522 dirty = self._gitstate() == self._state[1] or code != 0
1519 if _updateprompt(self.ui, self, dirty,
1523 if _updateprompt(self.ui, self, dirty,
1520 self._state[1][:7], revision[:7]):
1524 self._state[1][:7], revision[:7]):
1521 mergefunc()
1525 mergefunc()
1522 else:
1526 else:
1523 mergefunc()
1527 mergefunc()
1524
1528
1525 @annotatesubrepoerror
1529 @annotatesubrepoerror
1526 def push(self, opts):
1530 def push(self, opts):
1527 force = opts.get('force')
1531 force = opts.get('force')
1528
1532
1529 if not self._state[1]:
1533 if not self._state[1]:
1530 return True
1534 return True
1531 if self._gitmissing():
1535 if self._gitmissing():
1532 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1536 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1533 # if a branch in origin contains the revision, nothing to do
1537 # if a branch in origin contains the revision, nothing to do
1534 branch2rev, rev2branch = self._gitbranchmap()
1538 branch2rev, rev2branch = self._gitbranchmap()
1535 if self._state[1] in rev2branch:
1539 if self._state[1] in rev2branch:
1536 for b in rev2branch[self._state[1]]:
1540 for b in rev2branch[self._state[1]]:
1537 if b.startswith('refs/remotes/origin/'):
1541 if b.startswith('refs/remotes/origin/'):
1538 return True
1542 return True
1539 for b, revision in branch2rev.iteritems():
1543 for b, revision in branch2rev.iteritems():
1540 if b.startswith('refs/remotes/origin/'):
1544 if b.startswith('refs/remotes/origin/'):
1541 if self._gitisancestor(self._state[1], revision):
1545 if self._gitisancestor(self._state[1], revision):
1542 return True
1546 return True
1543 # otherwise, try to push the currently checked out branch
1547 # otherwise, try to push the currently checked out branch
1544 cmd = ['push']
1548 cmd = ['push']
1545 if force:
1549 if force:
1546 cmd.append('--force')
1550 cmd.append('--force')
1547
1551
1548 current = self._gitcurrentbranch()
1552 current = self._gitcurrentbranch()
1549 if current:
1553 if current:
1550 # determine if the current branch is even useful
1554 # determine if the current branch is even useful
1551 if not self._gitisancestor(self._state[1], current):
1555 if not self._gitisancestor(self._state[1], current):
1552 self.ui.warn(_('unrelated git branch checked out '
1556 self.ui.warn(_('unrelated git branch checked out '
1553 'in subrepo %s\n') % self._relpath)
1557 'in subrepo %s\n') % self._relpath)
1554 return False
1558 return False
1555 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1559 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1556 (current.split('/', 2)[2], self._relpath))
1560 (current.split('/', 2)[2], self._relpath))
1557 ret = self._gitdir(cmd + ['origin', current])
1561 ret = self._gitdir(cmd + ['origin', current])
1558 return ret[1] == 0
1562 return ret[1] == 0
1559 else:
1563 else:
1560 self.ui.warn(_('no branch checked out in subrepo %s\n'
1564 self.ui.warn(_('no branch checked out in subrepo %s\n'
1561 'cannot push revision %s\n') %
1565 'cannot push revision %s\n') %
1562 (self._relpath, self._state[1]))
1566 (self._relpath, self._state[1]))
1563 return False
1567 return False
1564
1568
1565 @annotatesubrepoerror
1569 @annotatesubrepoerror
1566 def add(self, ui, match, prefix, explicitonly, **opts):
1570 def add(self, ui, match, prefix, explicitonly, **opts):
1567 if self._gitmissing():
1571 if self._gitmissing():
1568 return []
1572 return []
1569
1573
1570 (modified, added, removed,
1574 (modified, added, removed,
1571 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1575 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1572 clean=True)
1576 clean=True)
1573
1577
1574 tracked = set()
1578 tracked = set()
1575 # dirstates 'amn' warn, 'r' is added again
1579 # dirstates 'amn' warn, 'r' is added again
1576 for l in (modified, added, deleted, clean):
1580 for l in (modified, added, deleted, clean):
1577 tracked.update(l)
1581 tracked.update(l)
1578
1582
1579 # Unknown files not of interest will be rejected by the matcher
1583 # Unknown files not of interest will be rejected by the matcher
1580 files = unknown
1584 files = unknown
1581 files.extend(match.files())
1585 files.extend(match.files())
1582
1586
1583 rejected = []
1587 rejected = []
1584
1588
1585 files = [f for f in sorted(set(files)) if match(f)]
1589 files = [f for f in sorted(set(files)) if match(f)]
1586 for f in files:
1590 for f in files:
1587 exact = match.exact(f)
1591 exact = match.exact(f)
1588 command = ["add"]
1592 command = ["add"]
1589 if exact:
1593 if exact:
1590 command.append("-f") #should be added, even if ignored
1594 command.append("-f") #should be added, even if ignored
1591 if ui.verbose or not exact:
1595 if ui.verbose or not exact:
1592 ui.status(_('adding %s\n') % match.rel(f))
1596 ui.status(_('adding %s\n') % match.rel(f))
1593
1597
1594 if f in tracked: # hg prints 'adding' even if already tracked
1598 if f in tracked: # hg prints 'adding' even if already tracked
1595 if exact:
1599 if exact:
1596 rejected.append(f)
1600 rejected.append(f)
1597 continue
1601 continue
1598 if not opts.get('dry_run'):
1602 if not opts.get('dry_run'):
1599 self._gitcommand(command + [f])
1603 self._gitcommand(command + [f])
1600
1604
1601 for f in rejected:
1605 for f in rejected:
1602 ui.warn(_("%s already tracked!\n") % match.abs(f))
1606 ui.warn(_("%s already tracked!\n") % match.abs(f))
1603
1607
1604 return rejected
1608 return rejected
1605
1609
1606 @annotatesubrepoerror
1610 @annotatesubrepoerror
1607 def remove(self):
1611 def remove(self):
1608 if self._gitmissing():
1612 if self._gitmissing():
1609 return
1613 return
1610 if self.dirty():
1614 if self.dirty():
1611 self.ui.warn(_('not removing repo %s because '
1615 self.ui.warn(_('not removing repo %s because '
1612 'it has changes.\n') % self._relpath)
1616 'it has changes.\n') % self._relpath)
1613 return
1617 return
1614 # we can't fully delete the repository as it may contain
1618 # we can't fully delete the repository as it may contain
1615 # local-only history
1619 # local-only history
1616 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1620 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1617 self._gitcommand(['config', 'core.bare', 'true'])
1621 self._gitcommand(['config', 'core.bare', 'true'])
1618 for f in os.listdir(self._abspath):
1622 for f in os.listdir(self._abspath):
1619 if f == '.git':
1623 if f == '.git':
1620 continue
1624 continue
1621 path = os.path.join(self._abspath, f)
1625 path = os.path.join(self._abspath, f)
1622 if os.path.isdir(path) and not os.path.islink(path):
1626 if os.path.isdir(path) and not os.path.islink(path):
1623 shutil.rmtree(path)
1627 shutil.rmtree(path)
1624 else:
1628 else:
1625 os.remove(path)
1629 os.remove(path)
1626
1630
1627 def archive(self, archiver, prefix, match=None):
1631 def archive(self, archiver, prefix, match=None):
1628 total = 0
1632 total = 0
1629 source, revision = self._state
1633 source, revision = self._state
1630 if not revision:
1634 if not revision:
1631 return total
1635 return total
1632 self._fetch(source, revision)
1636 self._fetch(source, revision)
1633
1637
1634 # Parse git's native archive command.
1638 # Parse git's native archive command.
1635 # This should be much faster than manually traversing the trees
1639 # This should be much faster than manually traversing the trees
1636 # and objects with many subprocess calls.
1640 # and objects with many subprocess calls.
1637 tarstream = self._gitcommand(['archive', revision], stream=True)
1641 tarstream = self._gitcommand(['archive', revision], stream=True)
1638 tar = tarfile.open(fileobj=tarstream, mode='r|')
1642 tar = tarfile.open(fileobj=tarstream, mode='r|')
1639 relpath = subrelpath(self)
1643 relpath = subrelpath(self)
1640 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1644 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1641 for i, info in enumerate(tar):
1645 for i, info in enumerate(tar):
1642 if info.isdir():
1646 if info.isdir():
1643 continue
1647 continue
1644 if match and not match(info.name):
1648 if match and not match(info.name):
1645 continue
1649 continue
1646 if info.issym():
1650 if info.issym():
1647 data = info.linkname
1651 data = info.linkname
1648 else:
1652 else:
1649 data = tar.extractfile(info).read()
1653 data = tar.extractfile(info).read()
1650 archiver.addfile(os.path.join(prefix, self._path, info.name),
1654 archiver.addfile(os.path.join(prefix, self._path, info.name),
1651 info.mode, info.issym(), data)
1655 info.mode, info.issym(), data)
1652 total += 1
1656 total += 1
1653 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1657 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1654 unit=_('files'))
1658 unit=_('files'))
1655 self.ui.progress(_('archiving (%s)') % relpath, None)
1659 self.ui.progress(_('archiving (%s)') % relpath, None)
1656 return total
1660 return total
1657
1661
1658
1662
1659 @annotatesubrepoerror
1663 @annotatesubrepoerror
1660 def cat(self, match, prefix, **opts):
1664 def cat(self, match, prefix, **opts):
1661 rev = self._state[1]
1665 rev = self._state[1]
1662 if match.anypats():
1666 if match.anypats():
1663 return 1 #No support for include/exclude yet
1667 return 1 #No support for include/exclude yet
1664
1668
1665 if not match.files():
1669 if not match.files():
1666 return 1
1670 return 1
1667
1671
1668 for f in match.files():
1672 for f in match.files():
1669 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1673 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1670 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1674 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1671 self._ctx.node(),
1675 self._ctx.node(),
1672 pathname=os.path.join(prefix, f))
1676 pathname=os.path.join(prefix, f))
1673 fp.write(output)
1677 fp.write(output)
1674 fp.close()
1678 fp.close()
1675 return 0
1679 return 0
1676
1680
1677
1681
1678 @annotatesubrepoerror
1682 @annotatesubrepoerror
1679 def status(self, rev2, **opts):
1683 def status(self, rev2, **opts):
1680 rev1 = self._state[1]
1684 rev1 = self._state[1]
1681 if self._gitmissing() or not rev1:
1685 if self._gitmissing() or not rev1:
1682 # if the repo is missing, return no results
1686 # if the repo is missing, return no results
1683 return scmutil.status([], [], [], [], [], [], [])
1687 return scmutil.status([], [], [], [], [], [], [])
1684 modified, added, removed = [], [], []
1688 modified, added, removed = [], [], []
1685 self._gitupdatestat()
1689 self._gitupdatestat()
1686 if rev2:
1690 if rev2:
1687 command = ['diff-tree', rev1, rev2]
1691 command = ['diff-tree', rev1, rev2]
1688 else:
1692 else:
1689 command = ['diff-index', rev1]
1693 command = ['diff-index', rev1]
1690 out = self._gitcommand(command)
1694 out = self._gitcommand(command)
1691 for line in out.split('\n'):
1695 for line in out.split('\n'):
1692 tab = line.find('\t')
1696 tab = line.find('\t')
1693 if tab == -1:
1697 if tab == -1:
1694 continue
1698 continue
1695 status, f = line[tab - 1], line[tab + 1:]
1699 status, f = line[tab - 1], line[tab + 1:]
1696 if status == 'M':
1700 if status == 'M':
1697 modified.append(f)
1701 modified.append(f)
1698 elif status == 'A':
1702 elif status == 'A':
1699 added.append(f)
1703 added.append(f)
1700 elif status == 'D':
1704 elif status == 'D':
1701 removed.append(f)
1705 removed.append(f)
1702
1706
1703 deleted, unknown, ignored, clean = [], [], [], []
1707 deleted, unknown, ignored, clean = [], [], [], []
1704
1708
1705 command = ['status', '--porcelain', '-z']
1709 command = ['status', '--porcelain', '-z']
1706 if opts.get('unknown'):
1710 if opts.get('unknown'):
1707 command += ['--untracked-files=all']
1711 command += ['--untracked-files=all']
1708 if opts.get('ignored'):
1712 if opts.get('ignored'):
1709 command += ['--ignored']
1713 command += ['--ignored']
1710 out = self._gitcommand(command)
1714 out = self._gitcommand(command)
1711
1715
1712 changedfiles = set()
1716 changedfiles = set()
1713 changedfiles.update(modified)
1717 changedfiles.update(modified)
1714 changedfiles.update(added)
1718 changedfiles.update(added)
1715 changedfiles.update(removed)
1719 changedfiles.update(removed)
1716 for line in out.split('\0'):
1720 for line in out.split('\0'):
1717 if not line:
1721 if not line:
1718 continue
1722 continue
1719 st = line[0:2]
1723 st = line[0:2]
1720 #moves and copies show 2 files on one line
1724 #moves and copies show 2 files on one line
1721 if line.find('\0') >= 0:
1725 if line.find('\0') >= 0:
1722 filename1, filename2 = line[3:].split('\0')
1726 filename1, filename2 = line[3:].split('\0')
1723 else:
1727 else:
1724 filename1 = line[3:]
1728 filename1 = line[3:]
1725 filename2 = None
1729 filename2 = None
1726
1730
1727 changedfiles.add(filename1)
1731 changedfiles.add(filename1)
1728 if filename2:
1732 if filename2:
1729 changedfiles.add(filename2)
1733 changedfiles.add(filename2)
1730
1734
1731 if st == '??':
1735 if st == '??':
1732 unknown.append(filename1)
1736 unknown.append(filename1)
1733 elif st == '!!':
1737 elif st == '!!':
1734 ignored.append(filename1)
1738 ignored.append(filename1)
1735
1739
1736 if opts.get('clean'):
1740 if opts.get('clean'):
1737 out = self._gitcommand(['ls-files'])
1741 out = self._gitcommand(['ls-files'])
1738 for f in out.split('\n'):
1742 for f in out.split('\n'):
1739 if not f in changedfiles:
1743 if not f in changedfiles:
1740 clean.append(f)
1744 clean.append(f)
1741
1745
1742 return scmutil.status(modified, added, removed, deleted,
1746 return scmutil.status(modified, added, removed, deleted,
1743 unknown, ignored, clean)
1747 unknown, ignored, clean)
1744
1748
1745 @annotatesubrepoerror
1749 @annotatesubrepoerror
1746 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1750 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1747 node1 = self._state[1]
1751 node1 = self._state[1]
1748 cmd = ['diff']
1752 cmd = ['diff']
1749 if opts['stat']:
1753 if opts['stat']:
1750 cmd.append('--stat')
1754 cmd.append('--stat')
1751 else:
1755 else:
1752 # for Git, this also implies '-p'
1756 # for Git, this also implies '-p'
1753 cmd.append('-U%d' % diffopts.context)
1757 cmd.append('-U%d' % diffopts.context)
1754
1758
1755 gitprefix = os.path.join(prefix, self._path)
1759 gitprefix = os.path.join(prefix, self._path)
1756
1760
1757 if diffopts.noprefix:
1761 if diffopts.noprefix:
1758 cmd.extend(['--src-prefix=%s/' % gitprefix,
1762 cmd.extend(['--src-prefix=%s/' % gitprefix,
1759 '--dst-prefix=%s/' % gitprefix])
1763 '--dst-prefix=%s/' % gitprefix])
1760 else:
1764 else:
1761 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1765 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1762 '--dst-prefix=b/%s/' % gitprefix])
1766 '--dst-prefix=b/%s/' % gitprefix])
1763
1767
1764 if diffopts.ignorews:
1768 if diffopts.ignorews:
1765 cmd.append('--ignore-all-space')
1769 cmd.append('--ignore-all-space')
1766 if diffopts.ignorewsamount:
1770 if diffopts.ignorewsamount:
1767 cmd.append('--ignore-space-change')
1771 cmd.append('--ignore-space-change')
1768 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1772 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1769 and diffopts.ignoreblanklines:
1773 and diffopts.ignoreblanklines:
1770 cmd.append('--ignore-blank-lines')
1774 cmd.append('--ignore-blank-lines')
1771
1775
1772 cmd.append(node1)
1776 cmd.append(node1)
1773 if node2:
1777 if node2:
1774 cmd.append(node2)
1778 cmd.append(node2)
1775
1779
1776 if match.anypats():
1780 if match.anypats():
1777 return #No support for include/exclude yet
1781 return #No support for include/exclude yet
1778
1782
1779 output = ""
1783 output = ""
1780 if match.always():
1784 if match.always():
1781 output += self._gitcommand(cmd) + '\n'
1785 output += self._gitcommand(cmd) + '\n'
1782 elif match.files():
1786 elif match.files():
1783 for f in match.files():
1787 for f in match.files():
1784 output += self._gitcommand(cmd + [f]) + '\n'
1788 output += self._gitcommand(cmd + [f]) + '\n'
1785 elif match(gitprefix): #Subrepo is matched
1789 elif match(gitprefix): #Subrepo is matched
1786 output += self._gitcommand(cmd) + '\n'
1790 output += self._gitcommand(cmd) + '\n'
1787
1791
1788 if output.strip():
1792 if output.strip():
1789 ui.write(output)
1793 ui.write(output)
1790
1794
1791 @annotatesubrepoerror
1795 @annotatesubrepoerror
1792 def revert(self, substate, *pats, **opts):
1796 def revert(self, substate, *pats, **opts):
1793 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1797 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1794 if not opts.get('no_backup'):
1798 if not opts.get('no_backup'):
1795 status = self.status(None)
1799 status = self.status(None)
1796 names = status.modified
1800 names = status.modified
1797 for name in names:
1801 for name in names:
1798 bakname = "%s.orig" % name
1802 bakname = "%s.orig" % name
1799 self.ui.note(_('saving current version of %s as %s\n') %
1803 self.ui.note(_('saving current version of %s as %s\n') %
1800 (name, bakname))
1804 (name, bakname))
1801 util.rename(os.path.join(self._abspath, name),
1805 util.rename(os.path.join(self._abspath, name),
1802 os.path.join(self._abspath, bakname))
1806 os.path.join(self._abspath, bakname))
1803
1807
1804 if not opts.get('dry_run'):
1808 if not opts.get('dry_run'):
1805 self.get(substate, overwrite=True)
1809 self.get(substate, overwrite=True)
1806 return []
1810 return []
1807
1811
1808 def shortid(self, revid):
1812 def shortid(self, revid):
1809 return revid[:7]
1813 return revid[:7]
1810
1814
1811 types = {
1815 types = {
1812 'hg': hgsubrepo,
1816 'hg': hgsubrepo,
1813 'svn': svnsubrepo,
1817 'svn': svnsubrepo,
1814 'git': gitsubrepo,
1818 'git': gitsubrepo,
1815 }
1819 }
General Comments 0
You need to be logged in to leave comments. Login now