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