##// END OF EJS Templates
subrepo: abort in hgsubrepo._get if the destination is obstructed...
Martin Geisler -
r15287:b3e19c35 stable
parent child Browse files
Show More
@@ -1,1117 +1,1117 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
9 import stat, subprocess, tarfile
9 import stat, subprocess, tarfile
10 from i18n import _
10 from i18n import _
11 import config, scmutil, util, node, error, cmdutil, bookmarks
11 import config, scmutil, util, node, error, cmdutil, bookmarks
12 hg = None
12 hg = None
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 nullstate = ('', '', 'empty')
15 nullstate = ('', '', 'empty')
16
16
17 def state(ctx, ui):
17 def state(ctx, ui):
18 """return a state dict, mapping subrepo paths configured in .hgsub
18 """return a state dict, mapping subrepo paths configured in .hgsub
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
20 (key in types dict))
20 (key in types dict))
21 """
21 """
22 p = config.config()
22 p = config.config()
23 def read(f, sections=None, remap=None):
23 def read(f, sections=None, remap=None):
24 if f in ctx:
24 if f in ctx:
25 try:
25 try:
26 data = ctx[f].data()
26 data = ctx[f].data()
27 except IOError, err:
27 except IOError, err:
28 if err.errno != errno.ENOENT:
28 if err.errno != errno.ENOENT:
29 raise
29 raise
30 # handle missing subrepo spec files as removed
30 # handle missing subrepo spec files as removed
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
32 return
32 return
33 p.parse(f, data, sections, remap, read)
33 p.parse(f, data, sections, remap, read)
34 else:
34 else:
35 raise util.Abort(_("subrepo spec file %s not found") % f)
35 raise util.Abort(_("subrepo spec file %s not found") % f)
36
36
37 if '.hgsub' in ctx:
37 if '.hgsub' in ctx:
38 read('.hgsub')
38 read('.hgsub')
39
39
40 for path, src in ui.configitems('subpaths'):
40 for path, src in ui.configitems('subpaths'):
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
42
42
43 rev = {}
43 rev = {}
44 if '.hgsubstate' in ctx:
44 if '.hgsubstate' in ctx:
45 try:
45 try:
46 for l in ctx['.hgsubstate'].data().splitlines():
46 for l in ctx['.hgsubstate'].data().splitlines():
47 revision, path = l.split(" ", 1)
47 revision, path = l.split(" ", 1)
48 rev[path] = revision
48 rev[path] = revision
49 except IOError, err:
49 except IOError, err:
50 if err.errno != errno.ENOENT:
50 if err.errno != errno.ENOENT:
51 raise
51 raise
52
52
53 def remap(src):
53 def remap(src):
54 for pattern, repl in p.items('subpaths'):
54 for pattern, repl in p.items('subpaths'):
55 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
55 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
56 # does a string decode.
56 # does a string decode.
57 repl = repl.encode('string-escape')
57 repl = repl.encode('string-escape')
58 # However, we still want to allow back references to go
58 # However, we still want to allow back references to go
59 # through unharmed, so we turn r'\\1' into r'\1'. Again,
59 # through unharmed, so we turn r'\\1' into r'\1'. Again,
60 # extra escapes are needed because re.sub string decodes.
60 # extra escapes are needed because re.sub string decodes.
61 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
61 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
62 try:
62 try:
63 src = re.sub(pattern, repl, src, 1)
63 src = re.sub(pattern, repl, src, 1)
64 except re.error, e:
64 except re.error, e:
65 raise util.Abort(_("bad subrepository pattern in %s: %s")
65 raise util.Abort(_("bad subrepository pattern in %s: %s")
66 % (p.source('subpaths', pattern), e))
66 % (p.source('subpaths', pattern), e))
67 return src
67 return src
68
68
69 state = {}
69 state = {}
70 for path, src in p[''].items():
70 for path, src in p[''].items():
71 kind = 'hg'
71 kind = 'hg'
72 if src.startswith('['):
72 if src.startswith('['):
73 if ']' not in src:
73 if ']' not in src:
74 raise util.Abort(_('missing ] in subrepo source'))
74 raise util.Abort(_('missing ] in subrepo source'))
75 kind, src = src.split(']', 1)
75 kind, src = src.split(']', 1)
76 kind = kind[1:]
76 kind = kind[1:]
77 src = src.lstrip() # strip any extra whitespace after ']'
77 src = src.lstrip() # strip any extra whitespace after ']'
78
78
79 if not util.url(src).isabs():
79 if not util.url(src).isabs():
80 parent = _abssource(ctx._repo, abort=False)
80 parent = _abssource(ctx._repo, abort=False)
81 if parent:
81 if parent:
82 parent = util.url(parent)
82 parent = util.url(parent)
83 parent.path = posixpath.join(parent.path or '', src)
83 parent.path = posixpath.join(parent.path or '', src)
84 parent.path = posixpath.normpath(parent.path)
84 parent.path = posixpath.normpath(parent.path)
85 joined = str(parent)
85 joined = str(parent)
86 # Remap the full joined path and use it if it changes,
86 # Remap the full joined path and use it if it changes,
87 # else remap the original source.
87 # else remap the original source.
88 remapped = remap(joined)
88 remapped = remap(joined)
89 if remapped == joined:
89 if remapped == joined:
90 src = remap(src)
90 src = remap(src)
91 else:
91 else:
92 src = remapped
92 src = remapped
93
93
94 src = remap(src)
94 src = remap(src)
95 state[path] = (src.strip(), rev.get(path, ''), kind)
95 state[path] = (src.strip(), rev.get(path, ''), kind)
96
96
97 return state
97 return state
98
98
99 def writestate(repo, state):
99 def writestate(repo, state):
100 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
100 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
101 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
101 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
102 repo.wwrite('.hgsubstate', ''.join(lines), '')
102 repo.wwrite('.hgsubstate', ''.join(lines), '')
103
103
104 def submerge(repo, wctx, mctx, actx, overwrite):
104 def submerge(repo, wctx, mctx, actx, overwrite):
105 """delegated from merge.applyupdates: merging of .hgsubstate file
105 """delegated from merge.applyupdates: merging of .hgsubstate file
106 in working context, merging context and ancestor context"""
106 in working context, merging context and ancestor context"""
107 if mctx == actx: # backwards?
107 if mctx == actx: # backwards?
108 actx = wctx.p1()
108 actx = wctx.p1()
109 s1 = wctx.substate
109 s1 = wctx.substate
110 s2 = mctx.substate
110 s2 = mctx.substate
111 sa = actx.substate
111 sa = actx.substate
112 sm = {}
112 sm = {}
113
113
114 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
114 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
115
115
116 def debug(s, msg, r=""):
116 def debug(s, msg, r=""):
117 if r:
117 if r:
118 r = "%s:%s:%s" % r
118 r = "%s:%s:%s" % r
119 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
119 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
120
120
121 for s, l in s1.items():
121 for s, l in s1.items():
122 a = sa.get(s, nullstate)
122 a = sa.get(s, nullstate)
123 ld = l # local state with possible dirty flag for compares
123 ld = l # local state with possible dirty flag for compares
124 if wctx.sub(s).dirty():
124 if wctx.sub(s).dirty():
125 ld = (l[0], l[1] + "+")
125 ld = (l[0], l[1] + "+")
126 if wctx == actx: # overwrite
126 if wctx == actx: # overwrite
127 a = ld
127 a = ld
128
128
129 if s in s2:
129 if s in s2:
130 r = s2[s]
130 r = s2[s]
131 if ld == r or r == a: # no change or local is newer
131 if ld == r or r == a: # no change or local is newer
132 sm[s] = l
132 sm[s] = l
133 continue
133 continue
134 elif ld == a: # other side changed
134 elif ld == a: # other side changed
135 debug(s, "other changed, get", r)
135 debug(s, "other changed, get", r)
136 wctx.sub(s).get(r, overwrite)
136 wctx.sub(s).get(r, overwrite)
137 sm[s] = r
137 sm[s] = r
138 elif ld[0] != r[0]: # sources differ
138 elif ld[0] != r[0]: # sources differ
139 if repo.ui.promptchoice(
139 if repo.ui.promptchoice(
140 _(' subrepository sources for %s differ\n'
140 _(' subrepository sources for %s differ\n'
141 'use (l)ocal source (%s) or (r)emote source (%s)?')
141 'use (l)ocal source (%s) or (r)emote source (%s)?')
142 % (s, l[0], r[0]),
142 % (s, l[0], r[0]),
143 (_('&Local'), _('&Remote')), 0):
143 (_('&Local'), _('&Remote')), 0):
144 debug(s, "prompt changed, get", r)
144 debug(s, "prompt changed, get", r)
145 wctx.sub(s).get(r, overwrite)
145 wctx.sub(s).get(r, overwrite)
146 sm[s] = r
146 sm[s] = r
147 elif ld[1] == a[1]: # local side is unchanged
147 elif ld[1] == a[1]: # local side is unchanged
148 debug(s, "other side changed, get", r)
148 debug(s, "other side changed, get", r)
149 wctx.sub(s).get(r, overwrite)
149 wctx.sub(s).get(r, overwrite)
150 sm[s] = r
150 sm[s] = r
151 else:
151 else:
152 debug(s, "both sides changed, merge with", r)
152 debug(s, "both sides changed, merge with", r)
153 wctx.sub(s).merge(r)
153 wctx.sub(s).merge(r)
154 sm[s] = l
154 sm[s] = l
155 elif ld == a: # remote removed, local unchanged
155 elif ld == a: # remote removed, local unchanged
156 debug(s, "remote removed, remove")
156 debug(s, "remote removed, remove")
157 wctx.sub(s).remove()
157 wctx.sub(s).remove()
158 elif a == nullstate: # not present in remote or ancestor
158 elif a == nullstate: # not present in remote or ancestor
159 debug(s, "local added, keep")
159 debug(s, "local added, keep")
160 sm[s] = l
160 sm[s] = l
161 continue
161 continue
162 else:
162 else:
163 if repo.ui.promptchoice(
163 if repo.ui.promptchoice(
164 _(' local changed subrepository %s which remote removed\n'
164 _(' local changed subrepository %s which remote removed\n'
165 'use (c)hanged version or (d)elete?') % s,
165 'use (c)hanged version or (d)elete?') % s,
166 (_('&Changed'), _('&Delete')), 0):
166 (_('&Changed'), _('&Delete')), 0):
167 debug(s, "prompt remove")
167 debug(s, "prompt remove")
168 wctx.sub(s).remove()
168 wctx.sub(s).remove()
169
169
170 for s, r in sorted(s2.items()):
170 for s, r in sorted(s2.items()):
171 if s in s1:
171 if s in s1:
172 continue
172 continue
173 elif s not in sa:
173 elif s not in sa:
174 debug(s, "remote added, get", r)
174 debug(s, "remote added, get", r)
175 mctx.sub(s).get(r)
175 mctx.sub(s).get(r)
176 sm[s] = r
176 sm[s] = r
177 elif r != sa[s]:
177 elif r != sa[s]:
178 if repo.ui.promptchoice(
178 if repo.ui.promptchoice(
179 _(' remote changed subrepository %s which local removed\n'
179 _(' remote changed subrepository %s which local removed\n'
180 'use (c)hanged version or (d)elete?') % s,
180 'use (c)hanged version or (d)elete?') % s,
181 (_('&Changed'), _('&Delete')), 0) == 0:
181 (_('&Changed'), _('&Delete')), 0) == 0:
182 debug(s, "prompt recreate", r)
182 debug(s, "prompt recreate", r)
183 wctx.sub(s).get(r)
183 wctx.sub(s).get(r)
184 sm[s] = r
184 sm[s] = r
185
185
186 # record merged .hgsubstate
186 # record merged .hgsubstate
187 writestate(repo, sm)
187 writestate(repo, sm)
188
188
189 def _updateprompt(ui, sub, dirty, local, remote):
189 def _updateprompt(ui, sub, dirty, local, remote):
190 if dirty:
190 if dirty:
191 msg = (_(' subrepository sources for %s differ\n'
191 msg = (_(' subrepository sources for %s differ\n'
192 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
192 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
193 % (subrelpath(sub), local, remote))
193 % (subrelpath(sub), local, remote))
194 else:
194 else:
195 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
195 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
196 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
196 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
197 % (subrelpath(sub), local, remote))
197 % (subrelpath(sub), local, remote))
198 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
198 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
199
199
200 def reporelpath(repo):
200 def reporelpath(repo):
201 """return path to this (sub)repo as seen from outermost repo"""
201 """return path to this (sub)repo as seen from outermost repo"""
202 parent = repo
202 parent = repo
203 while util.safehasattr(parent, '_subparent'):
203 while util.safehasattr(parent, '_subparent'):
204 parent = parent._subparent
204 parent = parent._subparent
205 p = parent.root.rstrip(os.sep)
205 p = parent.root.rstrip(os.sep)
206 return repo.root[len(p) + 1:]
206 return repo.root[len(p) + 1:]
207
207
208 def subrelpath(sub):
208 def subrelpath(sub):
209 """return path to this subrepo as seen from outermost repo"""
209 """return path to this subrepo as seen from outermost repo"""
210 if util.safehasattr(sub, '_relpath'):
210 if util.safehasattr(sub, '_relpath'):
211 return sub._relpath
211 return sub._relpath
212 if not util.safehasattr(sub, '_repo'):
212 if not util.safehasattr(sub, '_repo'):
213 return sub._path
213 return sub._path
214 return reporelpath(sub._repo)
214 return reporelpath(sub._repo)
215
215
216 def _abssource(repo, push=False, abort=True):
216 def _abssource(repo, push=False, abort=True):
217 """return pull/push path of repo - either based on parent repo .hgsub info
217 """return pull/push path of repo - either based on parent repo .hgsub info
218 or on the top repo config. Abort or return None if no source found."""
218 or on the top repo config. Abort or return None if no source found."""
219 if util.safehasattr(repo, '_subparent'):
219 if util.safehasattr(repo, '_subparent'):
220 source = util.url(repo._subsource)
220 source = util.url(repo._subsource)
221 if source.isabs():
221 if source.isabs():
222 return str(source)
222 return str(source)
223 source.path = posixpath.normpath(source.path)
223 source.path = posixpath.normpath(source.path)
224 parent = _abssource(repo._subparent, push, abort=False)
224 parent = _abssource(repo._subparent, push, abort=False)
225 if parent:
225 if parent:
226 parent = util.url(parent)
226 parent = util.url(parent)
227 parent.path = posixpath.join(parent.path or '', source.path)
227 parent.path = posixpath.join(parent.path or '', source.path)
228 parent.path = posixpath.normpath(parent.path)
228 parent.path = posixpath.normpath(parent.path)
229 return str(parent)
229 return str(parent)
230 else: # recursion reached top repo
230 else: # recursion reached top repo
231 if util.safehasattr(repo, '_subtoppath'):
231 if util.safehasattr(repo, '_subtoppath'):
232 return repo._subtoppath
232 return repo._subtoppath
233 if push and repo.ui.config('paths', 'default-push'):
233 if push and repo.ui.config('paths', 'default-push'):
234 return repo.ui.config('paths', 'default-push')
234 return repo.ui.config('paths', 'default-push')
235 if repo.ui.config('paths', 'default'):
235 if repo.ui.config('paths', 'default'):
236 return repo.ui.config('paths', 'default')
236 return repo.ui.config('paths', 'default')
237 if abort:
237 if abort:
238 raise util.Abort(_("default path for subrepository %s not found") %
238 raise util.Abort(_("default path for subrepository %s not found") %
239 reporelpath(repo))
239 reporelpath(repo))
240
240
241 def itersubrepos(ctx1, ctx2):
241 def itersubrepos(ctx1, ctx2):
242 """find subrepos in ctx1 or ctx2"""
242 """find subrepos in ctx1 or ctx2"""
243 # Create a (subpath, ctx) mapping where we prefer subpaths from
243 # Create a (subpath, ctx) mapping where we prefer subpaths from
244 # ctx1. The subpaths from ctx2 are important when the .hgsub file
244 # ctx1. The subpaths from ctx2 are important when the .hgsub file
245 # has been modified (in ctx2) but not yet committed (in ctx1).
245 # has been modified (in ctx2) but not yet committed (in ctx1).
246 subpaths = dict.fromkeys(ctx2.substate, ctx2)
246 subpaths = dict.fromkeys(ctx2.substate, ctx2)
247 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
247 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
248 for subpath, ctx in sorted(subpaths.iteritems()):
248 for subpath, ctx in sorted(subpaths.iteritems()):
249 yield subpath, ctx.sub(subpath)
249 yield subpath, ctx.sub(subpath)
250
250
251 def subrepo(ctx, path):
251 def subrepo(ctx, path):
252 """return instance of the right subrepo class for subrepo in path"""
252 """return instance of the right subrepo class for subrepo in path"""
253 # subrepo inherently violates our import layering rules
253 # subrepo inherently violates our import layering rules
254 # because it wants to make repo objects from deep inside the stack
254 # because it wants to make repo objects from deep inside the stack
255 # so we manually delay the circular imports to not break
255 # so we manually delay the circular imports to not break
256 # scripts that don't use our demand-loading
256 # scripts that don't use our demand-loading
257 global hg
257 global hg
258 import hg as h
258 import hg as h
259 hg = h
259 hg = h
260
260
261 scmutil.pathauditor(ctx._repo.root)(path)
261 scmutil.pathauditor(ctx._repo.root)(path)
262 state = ctx.substate.get(path, nullstate)
262 state = ctx.substate.get(path, nullstate)
263 if state[2] not in types:
263 if state[2] not in types:
264 raise util.Abort(_('unknown subrepo type %s') % state[2])
264 raise util.Abort(_('unknown subrepo type %s') % state[2])
265 return types[state[2]](ctx, path, state[:2])
265 return types[state[2]](ctx, path, state[:2])
266
266
267 # subrepo classes need to implement the following abstract class:
267 # subrepo classes need to implement the following abstract class:
268
268
269 class abstractsubrepo(object):
269 class abstractsubrepo(object):
270
270
271 def dirty(self, ignoreupdate=False):
271 def dirty(self, ignoreupdate=False):
272 """returns true if the dirstate of the subrepo is dirty or does not
272 """returns true if the dirstate of the subrepo is dirty or does not
273 match current stored state. If ignoreupdate is true, only check
273 match current stored state. If ignoreupdate is true, only check
274 whether the subrepo has uncommitted changes in its dirstate.
274 whether the subrepo has uncommitted changes in its dirstate.
275 """
275 """
276 raise NotImplementedError
276 raise NotImplementedError
277
277
278 def checknested(self, path):
278 def checknested(self, path):
279 """check if path is a subrepository within this repository"""
279 """check if path is a subrepository within this repository"""
280 return False
280 return False
281
281
282 def commit(self, text, user, date):
282 def commit(self, text, user, date):
283 """commit the current changes to the subrepo with the given
283 """commit the current changes to the subrepo with the given
284 log message. Use given user and date if possible. Return the
284 log message. Use given user and date if possible. Return the
285 new state of the subrepo.
285 new state of the subrepo.
286 """
286 """
287 raise NotImplementedError
287 raise NotImplementedError
288
288
289 def remove(self):
289 def remove(self):
290 """remove the subrepo
290 """remove the subrepo
291
291
292 (should verify the dirstate is not dirty first)
292 (should verify the dirstate is not dirty first)
293 """
293 """
294 raise NotImplementedError
294 raise NotImplementedError
295
295
296 def get(self, state, overwrite=False):
296 def get(self, state, overwrite=False):
297 """run whatever commands are needed to put the subrepo into
297 """run whatever commands are needed to put the subrepo into
298 this state
298 this state
299 """
299 """
300 raise NotImplementedError
300 raise NotImplementedError
301
301
302 def merge(self, state):
302 def merge(self, state):
303 """merge currently-saved state with the new state."""
303 """merge currently-saved state with the new state."""
304 raise NotImplementedError
304 raise NotImplementedError
305
305
306 def push(self, force):
306 def push(self, force):
307 """perform whatever action is analogous to 'hg push'
307 """perform whatever action is analogous to 'hg push'
308
308
309 This may be a no-op on some systems.
309 This may be a no-op on some systems.
310 """
310 """
311 raise NotImplementedError
311 raise NotImplementedError
312
312
313 def add(self, ui, match, dryrun, prefix):
313 def add(self, ui, match, dryrun, prefix):
314 return []
314 return []
315
315
316 def status(self, rev2, **opts):
316 def status(self, rev2, **opts):
317 return [], [], [], [], [], [], []
317 return [], [], [], [], [], [], []
318
318
319 def diff(self, diffopts, node2, match, prefix, **opts):
319 def diff(self, diffopts, node2, match, prefix, **opts):
320 pass
320 pass
321
321
322 def outgoing(self, ui, dest, opts):
322 def outgoing(self, ui, dest, opts):
323 return 1
323 return 1
324
324
325 def incoming(self, ui, source, opts):
325 def incoming(self, ui, source, opts):
326 return 1
326 return 1
327
327
328 def files(self):
328 def files(self):
329 """return filename iterator"""
329 """return filename iterator"""
330 raise NotImplementedError
330 raise NotImplementedError
331
331
332 def filedata(self, name):
332 def filedata(self, name):
333 """return file data"""
333 """return file data"""
334 raise NotImplementedError
334 raise NotImplementedError
335
335
336 def fileflags(self, name):
336 def fileflags(self, name):
337 """return file flags"""
337 """return file flags"""
338 return ''
338 return ''
339
339
340 def archive(self, ui, archiver, prefix):
340 def archive(self, ui, archiver, prefix):
341 files = self.files()
341 files = self.files()
342 total = len(files)
342 total = len(files)
343 relpath = subrelpath(self)
343 relpath = subrelpath(self)
344 ui.progress(_('archiving (%s)') % relpath, 0,
344 ui.progress(_('archiving (%s)') % relpath, 0,
345 unit=_('files'), total=total)
345 unit=_('files'), total=total)
346 for i, name in enumerate(files):
346 for i, name in enumerate(files):
347 flags = self.fileflags(name)
347 flags = self.fileflags(name)
348 mode = 'x' in flags and 0755 or 0644
348 mode = 'x' in flags and 0755 or 0644
349 symlink = 'l' in flags
349 symlink = 'l' in flags
350 archiver.addfile(os.path.join(prefix, self._path, name),
350 archiver.addfile(os.path.join(prefix, self._path, name),
351 mode, symlink, self.filedata(name))
351 mode, symlink, self.filedata(name))
352 ui.progress(_('archiving (%s)') % relpath, i + 1,
352 ui.progress(_('archiving (%s)') % relpath, i + 1,
353 unit=_('files'), total=total)
353 unit=_('files'), total=total)
354 ui.progress(_('archiving (%s)') % relpath, None)
354 ui.progress(_('archiving (%s)') % relpath, None)
355
355
356
356
357 class hgsubrepo(abstractsubrepo):
357 class hgsubrepo(abstractsubrepo):
358 def __init__(self, ctx, path, state):
358 def __init__(self, ctx, path, state):
359 self._path = path
359 self._path = path
360 self._state = state
360 self._state = state
361 r = ctx._repo
361 r = ctx._repo
362 root = r.wjoin(path)
362 root = r.wjoin(path)
363 create = False
363 create = False
364 if not os.path.exists(os.path.join(root, '.hg')):
364 if not os.path.exists(os.path.join(root, '.hg')):
365 create = True
365 create = True
366 util.makedirs(root)
366 util.makedirs(root)
367 self._repo = hg.repository(r.ui, root, create=create)
367 self._repo = hg.repository(r.ui, root, create=create)
368 self._initrepo(r, state[0], create)
368 self._initrepo(r, state[0], create)
369
369
370 def _initrepo(self, parentrepo, source, create):
370 def _initrepo(self, parentrepo, source, create):
371 self._repo._subparent = parentrepo
371 self._repo._subparent = parentrepo
372 self._repo._subsource = source
372 self._repo._subsource = source
373
373
374 if create:
374 if create:
375 fp = self._repo.opener("hgrc", "w", text=True)
375 fp = self._repo.opener("hgrc", "w", text=True)
376 fp.write('[paths]\n')
376 fp.write('[paths]\n')
377
377
378 def addpathconfig(key, value):
378 def addpathconfig(key, value):
379 if value:
379 if value:
380 fp.write('%s = %s\n' % (key, value))
380 fp.write('%s = %s\n' % (key, value))
381 self._repo.ui.setconfig('paths', key, value)
381 self._repo.ui.setconfig('paths', key, value)
382
382
383 defpath = _abssource(self._repo, abort=False)
383 defpath = _abssource(self._repo, abort=False)
384 defpushpath = _abssource(self._repo, True, abort=False)
384 defpushpath = _abssource(self._repo, True, abort=False)
385 addpathconfig('default', defpath)
385 addpathconfig('default', defpath)
386 if defpath != defpushpath:
386 if defpath != defpushpath:
387 addpathconfig('default-push', defpushpath)
387 addpathconfig('default-push', defpushpath)
388 fp.close()
388 fp.close()
389
389
390 def add(self, ui, match, dryrun, prefix):
390 def add(self, ui, match, dryrun, prefix):
391 return cmdutil.add(ui, self._repo, match, dryrun, True,
391 return cmdutil.add(ui, self._repo, match, dryrun, True,
392 os.path.join(prefix, self._path))
392 os.path.join(prefix, self._path))
393
393
394 def status(self, rev2, **opts):
394 def status(self, rev2, **opts):
395 try:
395 try:
396 rev1 = self._state[1]
396 rev1 = self._state[1]
397 ctx1 = self._repo[rev1]
397 ctx1 = self._repo[rev1]
398 ctx2 = self._repo[rev2]
398 ctx2 = self._repo[rev2]
399 return self._repo.status(ctx1, ctx2, **opts)
399 return self._repo.status(ctx1, ctx2, **opts)
400 except error.RepoLookupError, inst:
400 except error.RepoLookupError, inst:
401 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
401 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
402 % (inst, subrelpath(self)))
402 % (inst, subrelpath(self)))
403 return [], [], [], [], [], [], []
403 return [], [], [], [], [], [], []
404
404
405 def diff(self, diffopts, node2, match, prefix, **opts):
405 def diff(self, diffopts, node2, match, prefix, **opts):
406 try:
406 try:
407 node1 = node.bin(self._state[1])
407 node1 = node.bin(self._state[1])
408 # We currently expect node2 to come from substate and be
408 # We currently expect node2 to come from substate and be
409 # in hex format
409 # in hex format
410 if node2 is not None:
410 if node2 is not None:
411 node2 = node.bin(node2)
411 node2 = node.bin(node2)
412 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
412 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
413 node1, node2, match,
413 node1, node2, match,
414 prefix=os.path.join(prefix, self._path),
414 prefix=os.path.join(prefix, self._path),
415 listsubrepos=True, **opts)
415 listsubrepos=True, **opts)
416 except error.RepoLookupError, inst:
416 except error.RepoLookupError, inst:
417 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
417 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
418 % (inst, subrelpath(self)))
418 % (inst, subrelpath(self)))
419
419
420 def archive(self, ui, archiver, prefix):
420 def archive(self, ui, archiver, prefix):
421 self._get(self._state + ('hg',))
421 self._get(self._state + ('hg',))
422 abstractsubrepo.archive(self, ui, archiver, prefix)
422 abstractsubrepo.archive(self, ui, archiver, prefix)
423
423
424 rev = self._state[1]
424 rev = self._state[1]
425 ctx = self._repo[rev]
425 ctx = self._repo[rev]
426 for subpath in ctx.substate:
426 for subpath in ctx.substate:
427 s = subrepo(ctx, subpath)
427 s = subrepo(ctx, subpath)
428 s.archive(ui, archiver, os.path.join(prefix, self._path))
428 s.archive(ui, archiver, os.path.join(prefix, self._path))
429
429
430 def dirty(self, ignoreupdate=False):
430 def dirty(self, ignoreupdate=False):
431 r = self._state[1]
431 r = self._state[1]
432 if r == '' and not ignoreupdate: # no state recorded
432 if r == '' and not ignoreupdate: # no state recorded
433 return True
433 return True
434 w = self._repo[None]
434 w = self._repo[None]
435 if r != w.p1().hex() and not ignoreupdate:
435 if r != w.p1().hex() and not ignoreupdate:
436 # different version checked out
436 # different version checked out
437 return True
437 return True
438 return w.dirty() # working directory changed
438 return w.dirty() # working directory changed
439
439
440 def checknested(self, path):
440 def checknested(self, path):
441 return self._repo._checknested(self._repo.wjoin(path))
441 return self._repo._checknested(self._repo.wjoin(path))
442
442
443 def commit(self, text, user, date):
443 def commit(self, text, user, date):
444 # don't bother committing in the subrepo if it's only been
444 # don't bother committing in the subrepo if it's only been
445 # updated
445 # updated
446 if not self.dirty(True):
446 if not self.dirty(True):
447 return self._repo['.'].hex()
447 return self._repo['.'].hex()
448 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
448 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
449 n = self._repo.commit(text, user, date)
449 n = self._repo.commit(text, user, date)
450 if not n:
450 if not n:
451 return self._repo['.'].hex() # different version checked out
451 return self._repo['.'].hex() # different version checked out
452 return node.hex(n)
452 return node.hex(n)
453
453
454 def remove(self):
454 def remove(self):
455 # we can't fully delete the repository as it may contain
455 # we can't fully delete the repository as it may contain
456 # local-only history
456 # local-only history
457 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
457 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
458 hg.clean(self._repo, node.nullid, False)
458 hg.clean(self._repo, node.nullid, False)
459
459
460 def _get(self, state):
460 def _get(self, state):
461 source, revision, kind = state
461 source, revision, kind = state
462 if revision not in self._repo:
462 if revision not in self._repo:
463 self._repo._subsource = source
463 self._repo._subsource = source
464 srcurl = _abssource(self._repo)
464 srcurl = _abssource(self._repo)
465 other = hg.peer(self._repo.ui, {}, srcurl)
465 other = hg.peer(self._repo.ui, {}, srcurl)
466 if len(self._repo) == 0:
466 if len(self._repo) == 0:
467 self._repo.ui.status(_('cloning subrepo %s from %s\n')
467 self._repo.ui.status(_('cloning subrepo %s from %s\n')
468 % (subrelpath(self), srcurl))
468 % (subrelpath(self), srcurl))
469 parentrepo = self._repo._subparent
469 parentrepo = self._repo._subparent
470 shutil.rmtree(self._repo.root)
470 shutil.rmtree(self._repo.path)
471 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
471 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
472 self._repo.root, update=False)
472 self._repo.root, update=False)
473 self._initrepo(parentrepo, source, create=True)
473 self._initrepo(parentrepo, source, create=True)
474 else:
474 else:
475 self._repo.ui.status(_('pulling subrepo %s from %s\n')
475 self._repo.ui.status(_('pulling subrepo %s from %s\n')
476 % (subrelpath(self), srcurl))
476 % (subrelpath(self), srcurl))
477 self._repo.pull(other)
477 self._repo.pull(other)
478 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
478 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
479
479
480 def get(self, state, overwrite=False):
480 def get(self, state, overwrite=False):
481 self._get(state)
481 self._get(state)
482 source, revision, kind = state
482 source, revision, kind = state
483 self._repo.ui.debug("getting subrepo %s\n" % self._path)
483 self._repo.ui.debug("getting subrepo %s\n" % self._path)
484 hg.clean(self._repo, revision, False)
484 hg.clean(self._repo, revision, False)
485
485
486 def merge(self, state):
486 def merge(self, state):
487 self._get(state)
487 self._get(state)
488 cur = self._repo['.']
488 cur = self._repo['.']
489 dst = self._repo[state[1]]
489 dst = self._repo[state[1]]
490 anc = dst.ancestor(cur)
490 anc = dst.ancestor(cur)
491
491
492 def mergefunc():
492 def mergefunc():
493 if anc == cur:
493 if anc == cur:
494 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
494 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
495 hg.update(self._repo, state[1])
495 hg.update(self._repo, state[1])
496 elif anc == dst:
496 elif anc == dst:
497 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
497 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
498 else:
498 else:
499 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
499 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
500 hg.merge(self._repo, state[1], remind=False)
500 hg.merge(self._repo, state[1], remind=False)
501
501
502 wctx = self._repo[None]
502 wctx = self._repo[None]
503 if self.dirty():
503 if self.dirty():
504 if anc != dst:
504 if anc != dst:
505 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
505 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
506 mergefunc()
506 mergefunc()
507 else:
507 else:
508 mergefunc()
508 mergefunc()
509 else:
509 else:
510 mergefunc()
510 mergefunc()
511
511
512 def push(self, force):
512 def push(self, force):
513 # push subrepos depth-first for coherent ordering
513 # push subrepos depth-first for coherent ordering
514 c = self._repo['']
514 c = self._repo['']
515 subs = c.substate # only repos that are committed
515 subs = c.substate # only repos that are committed
516 for s in sorted(subs):
516 for s in sorted(subs):
517 if not c.sub(s).push(force):
517 if not c.sub(s).push(force):
518 return False
518 return False
519
519
520 dsturl = _abssource(self._repo, True)
520 dsturl = _abssource(self._repo, True)
521 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
521 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
522 (subrelpath(self), dsturl))
522 (subrelpath(self), dsturl))
523 other = hg.peer(self._repo.ui, {}, dsturl)
523 other = hg.peer(self._repo.ui, {}, dsturl)
524 return self._repo.push(other, force)
524 return self._repo.push(other, force)
525
525
526 def outgoing(self, ui, dest, opts):
526 def outgoing(self, ui, dest, opts):
527 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
527 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
528
528
529 def incoming(self, ui, source, opts):
529 def incoming(self, ui, source, opts):
530 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
530 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
531
531
532 def files(self):
532 def files(self):
533 rev = self._state[1]
533 rev = self._state[1]
534 ctx = self._repo[rev]
534 ctx = self._repo[rev]
535 return ctx.manifest()
535 return ctx.manifest()
536
536
537 def filedata(self, name):
537 def filedata(self, name):
538 rev = self._state[1]
538 rev = self._state[1]
539 return self._repo[rev][name].data()
539 return self._repo[rev][name].data()
540
540
541 def fileflags(self, name):
541 def fileflags(self, name):
542 rev = self._state[1]
542 rev = self._state[1]
543 ctx = self._repo[rev]
543 ctx = self._repo[rev]
544 return ctx.flags(name)
544 return ctx.flags(name)
545
545
546
546
547 class svnsubrepo(abstractsubrepo):
547 class svnsubrepo(abstractsubrepo):
548 def __init__(self, ctx, path, state):
548 def __init__(self, ctx, path, state):
549 self._path = path
549 self._path = path
550 self._state = state
550 self._state = state
551 self._ctx = ctx
551 self._ctx = ctx
552 self._ui = ctx._repo.ui
552 self._ui = ctx._repo.ui
553 self._exe = util.findexe('svn')
553 self._exe = util.findexe('svn')
554 if not self._exe:
554 if not self._exe:
555 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
555 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
556 % self._path)
556 % self._path)
557
557
558 def _svncommand(self, commands, filename='', failok=False):
558 def _svncommand(self, commands, filename='', failok=False):
559 cmd = [self._exe]
559 cmd = [self._exe]
560 extrakw = {}
560 extrakw = {}
561 if not self._ui.interactive():
561 if not self._ui.interactive():
562 # Making stdin be a pipe should prevent svn from behaving
562 # Making stdin be a pipe should prevent svn from behaving
563 # interactively even if we can't pass --non-interactive.
563 # interactively even if we can't pass --non-interactive.
564 extrakw['stdin'] = subprocess.PIPE
564 extrakw['stdin'] = subprocess.PIPE
565 # Starting in svn 1.5 --non-interactive is a global flag
565 # Starting in svn 1.5 --non-interactive is a global flag
566 # instead of being per-command, but we need to support 1.4 so
566 # instead of being per-command, but we need to support 1.4 so
567 # we have to be intelligent about what commands take
567 # we have to be intelligent about what commands take
568 # --non-interactive.
568 # --non-interactive.
569 if commands[0] in ('update', 'checkout', 'commit'):
569 if commands[0] in ('update', 'checkout', 'commit'):
570 cmd.append('--non-interactive')
570 cmd.append('--non-interactive')
571 cmd.extend(commands)
571 cmd.extend(commands)
572 if filename is not None:
572 if filename is not None:
573 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
573 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
574 cmd.append(path)
574 cmd.append(path)
575 env = dict(os.environ)
575 env = dict(os.environ)
576 # Avoid localized output, preserve current locale for everything else.
576 # Avoid localized output, preserve current locale for everything else.
577 env['LC_MESSAGES'] = 'C'
577 env['LC_MESSAGES'] = 'C'
578 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
578 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
579 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
579 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
580 universal_newlines=True, env=env, **extrakw)
580 universal_newlines=True, env=env, **extrakw)
581 stdout, stderr = p.communicate()
581 stdout, stderr = p.communicate()
582 stderr = stderr.strip()
582 stderr = stderr.strip()
583 if not failok:
583 if not failok:
584 if p.returncode:
584 if p.returncode:
585 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
585 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
586 if stderr:
586 if stderr:
587 self._ui.warn(stderr + '\n')
587 self._ui.warn(stderr + '\n')
588 return stdout, stderr
588 return stdout, stderr
589
589
590 @propertycache
590 @propertycache
591 def _svnversion(self):
591 def _svnversion(self):
592 output, err = self._svncommand(['--version'], filename=None)
592 output, err = self._svncommand(['--version'], filename=None)
593 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
593 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
594 if not m:
594 if not m:
595 raise util.Abort(_('cannot retrieve svn tool version'))
595 raise util.Abort(_('cannot retrieve svn tool version'))
596 return (int(m.group(1)), int(m.group(2)))
596 return (int(m.group(1)), int(m.group(2)))
597
597
598 def _wcrevs(self):
598 def _wcrevs(self):
599 # Get the working directory revision as well as the last
599 # Get the working directory revision as well as the last
600 # commit revision so we can compare the subrepo state with
600 # commit revision so we can compare the subrepo state with
601 # both. We used to store the working directory one.
601 # both. We used to store the working directory one.
602 output, err = self._svncommand(['info', '--xml'])
602 output, err = self._svncommand(['info', '--xml'])
603 doc = xml.dom.minidom.parseString(output)
603 doc = xml.dom.minidom.parseString(output)
604 entries = doc.getElementsByTagName('entry')
604 entries = doc.getElementsByTagName('entry')
605 lastrev, rev = '0', '0'
605 lastrev, rev = '0', '0'
606 if entries:
606 if entries:
607 rev = str(entries[0].getAttribute('revision')) or '0'
607 rev = str(entries[0].getAttribute('revision')) or '0'
608 commits = entries[0].getElementsByTagName('commit')
608 commits = entries[0].getElementsByTagName('commit')
609 if commits:
609 if commits:
610 lastrev = str(commits[0].getAttribute('revision')) or '0'
610 lastrev = str(commits[0].getAttribute('revision')) or '0'
611 return (lastrev, rev)
611 return (lastrev, rev)
612
612
613 def _wcrev(self):
613 def _wcrev(self):
614 return self._wcrevs()[0]
614 return self._wcrevs()[0]
615
615
616 def _wcchanged(self):
616 def _wcchanged(self):
617 """Return (changes, extchanges) where changes is True
617 """Return (changes, extchanges) where changes is True
618 if the working directory was changed, and extchanges is
618 if the working directory was changed, and extchanges is
619 True if any of these changes concern an external entry.
619 True if any of these changes concern an external entry.
620 """
620 """
621 output, err = self._svncommand(['status', '--xml'])
621 output, err = self._svncommand(['status', '--xml'])
622 externals, changes = [], []
622 externals, changes = [], []
623 doc = xml.dom.minidom.parseString(output)
623 doc = xml.dom.minidom.parseString(output)
624 for e in doc.getElementsByTagName('entry'):
624 for e in doc.getElementsByTagName('entry'):
625 s = e.getElementsByTagName('wc-status')
625 s = e.getElementsByTagName('wc-status')
626 if not s:
626 if not s:
627 continue
627 continue
628 item = s[0].getAttribute('item')
628 item = s[0].getAttribute('item')
629 props = s[0].getAttribute('props')
629 props = s[0].getAttribute('props')
630 path = e.getAttribute('path')
630 path = e.getAttribute('path')
631 if item == 'external':
631 if item == 'external':
632 externals.append(path)
632 externals.append(path)
633 if (item not in ('', 'normal', 'unversioned', 'external')
633 if (item not in ('', 'normal', 'unversioned', 'external')
634 or props not in ('', 'none', 'normal')):
634 or props not in ('', 'none', 'normal')):
635 changes.append(path)
635 changes.append(path)
636 for path in changes:
636 for path in changes:
637 for ext in externals:
637 for ext in externals:
638 if path == ext or path.startswith(ext + os.sep):
638 if path == ext or path.startswith(ext + os.sep):
639 return True, True
639 return True, True
640 return bool(changes), False
640 return bool(changes), False
641
641
642 def dirty(self, ignoreupdate=False):
642 def dirty(self, ignoreupdate=False):
643 if not self._wcchanged()[0]:
643 if not self._wcchanged()[0]:
644 if self._state[1] in self._wcrevs() or ignoreupdate:
644 if self._state[1] in self._wcrevs() or ignoreupdate:
645 return False
645 return False
646 return True
646 return True
647
647
648 def commit(self, text, user, date):
648 def commit(self, text, user, date):
649 # user and date are out of our hands since svn is centralized
649 # user and date are out of our hands since svn is centralized
650 changed, extchanged = self._wcchanged()
650 changed, extchanged = self._wcchanged()
651 if not changed:
651 if not changed:
652 return self._wcrev()
652 return self._wcrev()
653 if extchanged:
653 if extchanged:
654 # Do not try to commit externals
654 # Do not try to commit externals
655 raise util.Abort(_('cannot commit svn externals'))
655 raise util.Abort(_('cannot commit svn externals'))
656 commitinfo, err = self._svncommand(['commit', '-m', text])
656 commitinfo, err = self._svncommand(['commit', '-m', text])
657 self._ui.status(commitinfo)
657 self._ui.status(commitinfo)
658 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
658 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
659 if not newrev:
659 if not newrev:
660 raise util.Abort(commitinfo.splitlines()[-1])
660 raise util.Abort(commitinfo.splitlines()[-1])
661 newrev = newrev.groups()[0]
661 newrev = newrev.groups()[0]
662 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
662 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
663 return newrev
663 return newrev
664
664
665 def remove(self):
665 def remove(self):
666 if self.dirty():
666 if self.dirty():
667 self._ui.warn(_('not removing repo %s because '
667 self._ui.warn(_('not removing repo %s because '
668 'it has changes.\n' % self._path))
668 'it has changes.\n' % self._path))
669 return
669 return
670 self._ui.note(_('removing subrepo %s\n') % self._path)
670 self._ui.note(_('removing subrepo %s\n') % self._path)
671
671
672 def onerror(function, path, excinfo):
672 def onerror(function, path, excinfo):
673 if function is not os.remove:
673 if function is not os.remove:
674 raise
674 raise
675 # read-only files cannot be unlinked under Windows
675 # read-only files cannot be unlinked under Windows
676 s = os.stat(path)
676 s = os.stat(path)
677 if (s.st_mode & stat.S_IWRITE) != 0:
677 if (s.st_mode & stat.S_IWRITE) != 0:
678 raise
678 raise
679 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
679 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
680 os.remove(path)
680 os.remove(path)
681
681
682 path = self._ctx._repo.wjoin(self._path)
682 path = self._ctx._repo.wjoin(self._path)
683 shutil.rmtree(path, onerror=onerror)
683 shutil.rmtree(path, onerror=onerror)
684 try:
684 try:
685 os.removedirs(os.path.dirname(path))
685 os.removedirs(os.path.dirname(path))
686 except OSError:
686 except OSError:
687 pass
687 pass
688
688
689 def get(self, state, overwrite=False):
689 def get(self, state, overwrite=False):
690 if overwrite:
690 if overwrite:
691 self._svncommand(['revert', '--recursive'])
691 self._svncommand(['revert', '--recursive'])
692 args = ['checkout']
692 args = ['checkout']
693 if self._svnversion >= (1, 5):
693 if self._svnversion >= (1, 5):
694 args.append('--force')
694 args.append('--force')
695 # The revision must be specified at the end of the URL to properly
695 # The revision must be specified at the end of the URL to properly
696 # update to a directory which has since been deleted and recreated.
696 # update to a directory which has since been deleted and recreated.
697 args.append('%s@%s' % (state[0], state[1]))
697 args.append('%s@%s' % (state[0], state[1]))
698 status, err = self._svncommand(args, failok=True)
698 status, err = self._svncommand(args, failok=True)
699 if not re.search('Checked out revision [0-9]+.', status):
699 if not re.search('Checked out revision [0-9]+.', status):
700 if ('is already a working copy for a different URL' in err
700 if ('is already a working copy for a different URL' in err
701 and (self._wcchanged() == (False, False))):
701 and (self._wcchanged() == (False, False))):
702 # obstructed but clean working copy, so just blow it away.
702 # obstructed but clean working copy, so just blow it away.
703 self.remove()
703 self.remove()
704 self.get(state, overwrite=False)
704 self.get(state, overwrite=False)
705 return
705 return
706 raise util.Abort((status or err).splitlines()[-1])
706 raise util.Abort((status or err).splitlines()[-1])
707 self._ui.status(status)
707 self._ui.status(status)
708
708
709 def merge(self, state):
709 def merge(self, state):
710 old = self._state[1]
710 old = self._state[1]
711 new = state[1]
711 new = state[1]
712 if new != self._wcrev():
712 if new != self._wcrev():
713 dirty = old == self._wcrev() or self._wcchanged()[0]
713 dirty = old == self._wcrev() or self._wcchanged()[0]
714 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
714 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
715 self.get(state, False)
715 self.get(state, False)
716
716
717 def push(self, force):
717 def push(self, force):
718 # push is a no-op for SVN
718 # push is a no-op for SVN
719 return True
719 return True
720
720
721 def files(self):
721 def files(self):
722 output = self._svncommand(['list'])
722 output = self._svncommand(['list'])
723 # This works because svn forbids \n in filenames.
723 # This works because svn forbids \n in filenames.
724 return output.splitlines()
724 return output.splitlines()
725
725
726 def filedata(self, name):
726 def filedata(self, name):
727 return self._svncommand(['cat'], name)
727 return self._svncommand(['cat'], name)
728
728
729
729
730 class gitsubrepo(abstractsubrepo):
730 class gitsubrepo(abstractsubrepo):
731 def __init__(self, ctx, path, state):
731 def __init__(self, ctx, path, state):
732 # TODO add git version check.
732 # TODO add git version check.
733 self._state = state
733 self._state = state
734 self._ctx = ctx
734 self._ctx = ctx
735 self._path = path
735 self._path = path
736 self._relpath = os.path.join(reporelpath(ctx._repo), path)
736 self._relpath = os.path.join(reporelpath(ctx._repo), path)
737 self._abspath = ctx._repo.wjoin(path)
737 self._abspath = ctx._repo.wjoin(path)
738 self._subparent = ctx._repo
738 self._subparent = ctx._repo
739 self._ui = ctx._repo.ui
739 self._ui = ctx._repo.ui
740
740
741 def _gitcommand(self, commands, env=None, stream=False):
741 def _gitcommand(self, commands, env=None, stream=False):
742 return self._gitdir(commands, env=env, stream=stream)[0]
742 return self._gitdir(commands, env=env, stream=stream)[0]
743
743
744 def _gitdir(self, commands, env=None, stream=False):
744 def _gitdir(self, commands, env=None, stream=False):
745 return self._gitnodir(commands, env=env, stream=stream,
745 return self._gitnodir(commands, env=env, stream=stream,
746 cwd=self._abspath)
746 cwd=self._abspath)
747
747
748 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
748 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
749 """Calls the git command
749 """Calls the git command
750
750
751 The methods tries to call the git command. versions previor to 1.6.0
751 The methods tries to call the git command. versions previor to 1.6.0
752 are not supported and very probably fail.
752 are not supported and very probably fail.
753 """
753 """
754 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
754 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
755 # unless ui.quiet is set, print git's stderr,
755 # unless ui.quiet is set, print git's stderr,
756 # which is mostly progress and useful info
756 # which is mostly progress and useful info
757 errpipe = None
757 errpipe = None
758 if self._ui.quiet:
758 if self._ui.quiet:
759 errpipe = open(os.devnull, 'w')
759 errpipe = open(os.devnull, 'w')
760 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
760 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
761 close_fds=util.closefds,
761 close_fds=util.closefds,
762 stdout=subprocess.PIPE, stderr=errpipe)
762 stdout=subprocess.PIPE, stderr=errpipe)
763 if stream:
763 if stream:
764 return p.stdout, None
764 return p.stdout, None
765
765
766 retdata = p.stdout.read().strip()
766 retdata = p.stdout.read().strip()
767 # wait for the child to exit to avoid race condition.
767 # wait for the child to exit to avoid race condition.
768 p.wait()
768 p.wait()
769
769
770 if p.returncode != 0 and p.returncode != 1:
770 if p.returncode != 0 and p.returncode != 1:
771 # there are certain error codes that are ok
771 # there are certain error codes that are ok
772 command = commands[0]
772 command = commands[0]
773 if command in ('cat-file', 'symbolic-ref'):
773 if command in ('cat-file', 'symbolic-ref'):
774 return retdata, p.returncode
774 return retdata, p.returncode
775 # for all others, abort
775 # for all others, abort
776 raise util.Abort('git %s error %d in %s' %
776 raise util.Abort('git %s error %d in %s' %
777 (command, p.returncode, self._relpath))
777 (command, p.returncode, self._relpath))
778
778
779 return retdata, p.returncode
779 return retdata, p.returncode
780
780
781 def _gitmissing(self):
781 def _gitmissing(self):
782 return not os.path.exists(os.path.join(self._abspath, '.git'))
782 return not os.path.exists(os.path.join(self._abspath, '.git'))
783
783
784 def _gitstate(self):
784 def _gitstate(self):
785 return self._gitcommand(['rev-parse', 'HEAD'])
785 return self._gitcommand(['rev-parse', 'HEAD'])
786
786
787 def _gitcurrentbranch(self):
787 def _gitcurrentbranch(self):
788 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
788 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
789 if err:
789 if err:
790 current = None
790 current = None
791 return current
791 return current
792
792
793 def _gitremote(self, remote):
793 def _gitremote(self, remote):
794 out = self._gitcommand(['remote', 'show', '-n', remote])
794 out = self._gitcommand(['remote', 'show', '-n', remote])
795 line = out.split('\n')[1]
795 line = out.split('\n')[1]
796 i = line.index('URL: ') + len('URL: ')
796 i = line.index('URL: ') + len('URL: ')
797 return line[i:]
797 return line[i:]
798
798
799 def _githavelocally(self, revision):
799 def _githavelocally(self, revision):
800 out, code = self._gitdir(['cat-file', '-e', revision])
800 out, code = self._gitdir(['cat-file', '-e', revision])
801 return code == 0
801 return code == 0
802
802
803 def _gitisancestor(self, r1, r2):
803 def _gitisancestor(self, r1, r2):
804 base = self._gitcommand(['merge-base', r1, r2])
804 base = self._gitcommand(['merge-base', r1, r2])
805 return base == r1
805 return base == r1
806
806
807 def _gitisbare(self):
807 def _gitisbare(self):
808 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
808 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
809
809
810 def _gitbranchmap(self):
810 def _gitbranchmap(self):
811 '''returns 2 things:
811 '''returns 2 things:
812 a map from git branch to revision
812 a map from git branch to revision
813 a map from revision to branches'''
813 a map from revision to branches'''
814 branch2rev = {}
814 branch2rev = {}
815 rev2branch = {}
815 rev2branch = {}
816
816
817 out = self._gitcommand(['for-each-ref', '--format',
817 out = self._gitcommand(['for-each-ref', '--format',
818 '%(objectname) %(refname)'])
818 '%(objectname) %(refname)'])
819 for line in out.split('\n'):
819 for line in out.split('\n'):
820 revision, ref = line.split(' ')
820 revision, ref = line.split(' ')
821 if (not ref.startswith('refs/heads/') and
821 if (not ref.startswith('refs/heads/') and
822 not ref.startswith('refs/remotes/')):
822 not ref.startswith('refs/remotes/')):
823 continue
823 continue
824 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
824 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
825 continue # ignore remote/HEAD redirects
825 continue # ignore remote/HEAD redirects
826 branch2rev[ref] = revision
826 branch2rev[ref] = revision
827 rev2branch.setdefault(revision, []).append(ref)
827 rev2branch.setdefault(revision, []).append(ref)
828 return branch2rev, rev2branch
828 return branch2rev, rev2branch
829
829
830 def _gittracking(self, branches):
830 def _gittracking(self, branches):
831 'return map of remote branch to local tracking branch'
831 'return map of remote branch to local tracking branch'
832 # assumes no more than one local tracking branch for each remote
832 # assumes no more than one local tracking branch for each remote
833 tracking = {}
833 tracking = {}
834 for b in branches:
834 for b in branches:
835 if b.startswith('refs/remotes/'):
835 if b.startswith('refs/remotes/'):
836 continue
836 continue
837 bname = b.split('/', 2)[2]
837 bname = b.split('/', 2)[2]
838 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
838 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
839 if remote:
839 if remote:
840 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
840 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
841 tracking['refs/remotes/%s/%s' %
841 tracking['refs/remotes/%s/%s' %
842 (remote, ref.split('/', 2)[2])] = b
842 (remote, ref.split('/', 2)[2])] = b
843 return tracking
843 return tracking
844
844
845 def _abssource(self, source):
845 def _abssource(self, source):
846 if '://' not in source:
846 if '://' not in source:
847 # recognize the scp syntax as an absolute source
847 # recognize the scp syntax as an absolute source
848 colon = source.find(':')
848 colon = source.find(':')
849 if colon != -1 and '/' not in source[:colon]:
849 if colon != -1 and '/' not in source[:colon]:
850 return source
850 return source
851 self._subsource = source
851 self._subsource = source
852 return _abssource(self)
852 return _abssource(self)
853
853
854 def _fetch(self, source, revision):
854 def _fetch(self, source, revision):
855 if self._gitmissing():
855 if self._gitmissing():
856 source = self._abssource(source)
856 source = self._abssource(source)
857 self._ui.status(_('cloning subrepo %s from %s\n') %
857 self._ui.status(_('cloning subrepo %s from %s\n') %
858 (self._relpath, source))
858 (self._relpath, source))
859 self._gitnodir(['clone', source, self._abspath])
859 self._gitnodir(['clone', source, self._abspath])
860 if self._githavelocally(revision):
860 if self._githavelocally(revision):
861 return
861 return
862 self._ui.status(_('pulling subrepo %s from %s\n') %
862 self._ui.status(_('pulling subrepo %s from %s\n') %
863 (self._relpath, self._gitremote('origin')))
863 (self._relpath, self._gitremote('origin')))
864 # try only origin: the originally cloned repo
864 # try only origin: the originally cloned repo
865 self._gitcommand(['fetch'])
865 self._gitcommand(['fetch'])
866 if not self._githavelocally(revision):
866 if not self._githavelocally(revision):
867 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
867 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
868 (revision, self._relpath))
868 (revision, self._relpath))
869
869
870 def dirty(self, ignoreupdate=False):
870 def dirty(self, ignoreupdate=False):
871 if self._gitmissing():
871 if self._gitmissing():
872 return self._state[1] != ''
872 return self._state[1] != ''
873 if self._gitisbare():
873 if self._gitisbare():
874 return True
874 return True
875 if not ignoreupdate and self._state[1] != self._gitstate():
875 if not ignoreupdate and self._state[1] != self._gitstate():
876 # different version checked out
876 # different version checked out
877 return True
877 return True
878 # check for staged changes or modified files; ignore untracked files
878 # check for staged changes or modified files; ignore untracked files
879 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
879 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
880 return code == 1
880 return code == 1
881
881
882 def get(self, state, overwrite=False):
882 def get(self, state, overwrite=False):
883 source, revision, kind = state
883 source, revision, kind = state
884 if not revision:
884 if not revision:
885 self.remove()
885 self.remove()
886 return
886 return
887 self._fetch(source, revision)
887 self._fetch(source, revision)
888 # if the repo was set to be bare, unbare it
888 # if the repo was set to be bare, unbare it
889 if self._gitisbare():
889 if self._gitisbare():
890 self._gitcommand(['config', 'core.bare', 'false'])
890 self._gitcommand(['config', 'core.bare', 'false'])
891 if self._gitstate() == revision:
891 if self._gitstate() == revision:
892 self._gitcommand(['reset', '--hard', 'HEAD'])
892 self._gitcommand(['reset', '--hard', 'HEAD'])
893 return
893 return
894 elif self._gitstate() == revision:
894 elif self._gitstate() == revision:
895 if overwrite:
895 if overwrite:
896 # first reset the index to unmark new files for commit, because
896 # first reset the index to unmark new files for commit, because
897 # reset --hard will otherwise throw away files added for commit,
897 # reset --hard will otherwise throw away files added for commit,
898 # not just unmark them.
898 # not just unmark them.
899 self._gitcommand(['reset', 'HEAD'])
899 self._gitcommand(['reset', 'HEAD'])
900 self._gitcommand(['reset', '--hard', 'HEAD'])
900 self._gitcommand(['reset', '--hard', 'HEAD'])
901 return
901 return
902 branch2rev, rev2branch = self._gitbranchmap()
902 branch2rev, rev2branch = self._gitbranchmap()
903
903
904 def checkout(args):
904 def checkout(args):
905 cmd = ['checkout']
905 cmd = ['checkout']
906 if overwrite:
906 if overwrite:
907 # first reset the index to unmark new files for commit, because
907 # first reset the index to unmark new files for commit, because
908 # the -f option will otherwise throw away files added for
908 # the -f option will otherwise throw away files added for
909 # commit, not just unmark them.
909 # commit, not just unmark them.
910 self._gitcommand(['reset', 'HEAD'])
910 self._gitcommand(['reset', 'HEAD'])
911 cmd.append('-f')
911 cmd.append('-f')
912 self._gitcommand(cmd + args)
912 self._gitcommand(cmd + args)
913
913
914 def rawcheckout():
914 def rawcheckout():
915 # no branch to checkout, check it out with no branch
915 # no branch to checkout, check it out with no branch
916 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
916 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
917 self._relpath)
917 self._relpath)
918 self._ui.warn(_('check out a git branch if you intend '
918 self._ui.warn(_('check out a git branch if you intend '
919 'to make changes\n'))
919 'to make changes\n'))
920 checkout(['-q', revision])
920 checkout(['-q', revision])
921
921
922 if revision not in rev2branch:
922 if revision not in rev2branch:
923 rawcheckout()
923 rawcheckout()
924 return
924 return
925 branches = rev2branch[revision]
925 branches = rev2branch[revision]
926 firstlocalbranch = None
926 firstlocalbranch = None
927 for b in branches:
927 for b in branches:
928 if b == 'refs/heads/master':
928 if b == 'refs/heads/master':
929 # master trumps all other branches
929 # master trumps all other branches
930 checkout(['refs/heads/master'])
930 checkout(['refs/heads/master'])
931 return
931 return
932 if not firstlocalbranch and not b.startswith('refs/remotes/'):
932 if not firstlocalbranch and not b.startswith('refs/remotes/'):
933 firstlocalbranch = b
933 firstlocalbranch = b
934 if firstlocalbranch:
934 if firstlocalbranch:
935 checkout([firstlocalbranch])
935 checkout([firstlocalbranch])
936 return
936 return
937
937
938 tracking = self._gittracking(branch2rev.keys())
938 tracking = self._gittracking(branch2rev.keys())
939 # choose a remote branch already tracked if possible
939 # choose a remote branch already tracked if possible
940 remote = branches[0]
940 remote = branches[0]
941 if remote not in tracking:
941 if remote not in tracking:
942 for b in branches:
942 for b in branches:
943 if b in tracking:
943 if b in tracking:
944 remote = b
944 remote = b
945 break
945 break
946
946
947 if remote not in tracking:
947 if remote not in tracking:
948 # create a new local tracking branch
948 # create a new local tracking branch
949 local = remote.split('/', 2)[2]
949 local = remote.split('/', 2)[2]
950 checkout(['-b', local, remote])
950 checkout(['-b', local, remote])
951 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
951 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
952 # When updating to a tracked remote branch,
952 # When updating to a tracked remote branch,
953 # if the local tracking branch is downstream of it,
953 # if the local tracking branch is downstream of it,
954 # a normal `git pull` would have performed a "fast-forward merge"
954 # a normal `git pull` would have performed a "fast-forward merge"
955 # which is equivalent to updating the local branch to the remote.
955 # which is equivalent to updating the local branch to the remote.
956 # Since we are only looking at branching at update, we need to
956 # Since we are only looking at branching at update, we need to
957 # detect this situation and perform this action lazily.
957 # detect this situation and perform this action lazily.
958 if tracking[remote] != self._gitcurrentbranch():
958 if tracking[remote] != self._gitcurrentbranch():
959 checkout([tracking[remote]])
959 checkout([tracking[remote]])
960 self._gitcommand(['merge', '--ff', remote])
960 self._gitcommand(['merge', '--ff', remote])
961 else:
961 else:
962 # a real merge would be required, just checkout the revision
962 # a real merge would be required, just checkout the revision
963 rawcheckout()
963 rawcheckout()
964
964
965 def commit(self, text, user, date):
965 def commit(self, text, user, date):
966 if self._gitmissing():
966 if self._gitmissing():
967 raise util.Abort(_("subrepo %s is missing") % self._relpath)
967 raise util.Abort(_("subrepo %s is missing") % self._relpath)
968 cmd = ['commit', '-a', '-m', text]
968 cmd = ['commit', '-a', '-m', text]
969 env = os.environ.copy()
969 env = os.environ.copy()
970 if user:
970 if user:
971 cmd += ['--author', user]
971 cmd += ['--author', user]
972 if date:
972 if date:
973 # git's date parser silently ignores when seconds < 1e9
973 # git's date parser silently ignores when seconds < 1e9
974 # convert to ISO8601
974 # convert to ISO8601
975 env['GIT_AUTHOR_DATE'] = util.datestr(date,
975 env['GIT_AUTHOR_DATE'] = util.datestr(date,
976 '%Y-%m-%dT%H:%M:%S %1%2')
976 '%Y-%m-%dT%H:%M:%S %1%2')
977 self._gitcommand(cmd, env=env)
977 self._gitcommand(cmd, env=env)
978 # make sure commit works otherwise HEAD might not exist under certain
978 # make sure commit works otherwise HEAD might not exist under certain
979 # circumstances
979 # circumstances
980 return self._gitstate()
980 return self._gitstate()
981
981
982 def merge(self, state):
982 def merge(self, state):
983 source, revision, kind = state
983 source, revision, kind = state
984 self._fetch(source, revision)
984 self._fetch(source, revision)
985 base = self._gitcommand(['merge-base', revision, self._state[1]])
985 base = self._gitcommand(['merge-base', revision, self._state[1]])
986 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
986 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
987
987
988 def mergefunc():
988 def mergefunc():
989 if base == revision:
989 if base == revision:
990 self.get(state) # fast forward merge
990 self.get(state) # fast forward merge
991 elif base != self._state[1]:
991 elif base != self._state[1]:
992 self._gitcommand(['merge', '--no-commit', revision])
992 self._gitcommand(['merge', '--no-commit', revision])
993
993
994 if self.dirty():
994 if self.dirty():
995 if self._gitstate() != revision:
995 if self._gitstate() != revision:
996 dirty = self._gitstate() == self._state[1] or code != 0
996 dirty = self._gitstate() == self._state[1] or code != 0
997 if _updateprompt(self._ui, self, dirty,
997 if _updateprompt(self._ui, self, dirty,
998 self._state[1][:7], revision[:7]):
998 self._state[1][:7], revision[:7]):
999 mergefunc()
999 mergefunc()
1000 else:
1000 else:
1001 mergefunc()
1001 mergefunc()
1002
1002
1003 def push(self, force):
1003 def push(self, force):
1004 if not self._state[1]:
1004 if not self._state[1]:
1005 return True
1005 return True
1006 if self._gitmissing():
1006 if self._gitmissing():
1007 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1007 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1008 # if a branch in origin contains the revision, nothing to do
1008 # if a branch in origin contains the revision, nothing to do
1009 branch2rev, rev2branch = self._gitbranchmap()
1009 branch2rev, rev2branch = self._gitbranchmap()
1010 if self._state[1] in rev2branch:
1010 if self._state[1] in rev2branch:
1011 for b in rev2branch[self._state[1]]:
1011 for b in rev2branch[self._state[1]]:
1012 if b.startswith('refs/remotes/origin/'):
1012 if b.startswith('refs/remotes/origin/'):
1013 return True
1013 return True
1014 for b, revision in branch2rev.iteritems():
1014 for b, revision in branch2rev.iteritems():
1015 if b.startswith('refs/remotes/origin/'):
1015 if b.startswith('refs/remotes/origin/'):
1016 if self._gitisancestor(self._state[1], revision):
1016 if self._gitisancestor(self._state[1], revision):
1017 return True
1017 return True
1018 # otherwise, try to push the currently checked out branch
1018 # otherwise, try to push the currently checked out branch
1019 cmd = ['push']
1019 cmd = ['push']
1020 if force:
1020 if force:
1021 cmd.append('--force')
1021 cmd.append('--force')
1022
1022
1023 current = self._gitcurrentbranch()
1023 current = self._gitcurrentbranch()
1024 if current:
1024 if current:
1025 # determine if the current branch is even useful
1025 # determine if the current branch is even useful
1026 if not self._gitisancestor(self._state[1], current):
1026 if not self._gitisancestor(self._state[1], current):
1027 self._ui.warn(_('unrelated git branch checked out '
1027 self._ui.warn(_('unrelated git branch checked out '
1028 'in subrepo %s\n') % self._relpath)
1028 'in subrepo %s\n') % self._relpath)
1029 return False
1029 return False
1030 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1030 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1031 (current.split('/', 2)[2], self._relpath))
1031 (current.split('/', 2)[2], self._relpath))
1032 self._gitcommand(cmd + ['origin', current])
1032 self._gitcommand(cmd + ['origin', current])
1033 return True
1033 return True
1034 else:
1034 else:
1035 self._ui.warn(_('no branch checked out in subrepo %s\n'
1035 self._ui.warn(_('no branch checked out in subrepo %s\n'
1036 'cannot push revision %s') %
1036 'cannot push revision %s') %
1037 (self._relpath, self._state[1]))
1037 (self._relpath, self._state[1]))
1038 return False
1038 return False
1039
1039
1040 def remove(self):
1040 def remove(self):
1041 if self._gitmissing():
1041 if self._gitmissing():
1042 return
1042 return
1043 if self.dirty():
1043 if self.dirty():
1044 self._ui.warn(_('not removing repo %s because '
1044 self._ui.warn(_('not removing repo %s because '
1045 'it has changes.\n') % self._relpath)
1045 'it has changes.\n') % self._relpath)
1046 return
1046 return
1047 # we can't fully delete the repository as it may contain
1047 # we can't fully delete the repository as it may contain
1048 # local-only history
1048 # local-only history
1049 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1049 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1050 self._gitcommand(['config', 'core.bare', 'true'])
1050 self._gitcommand(['config', 'core.bare', 'true'])
1051 for f in os.listdir(self._abspath):
1051 for f in os.listdir(self._abspath):
1052 if f == '.git':
1052 if f == '.git':
1053 continue
1053 continue
1054 path = os.path.join(self._abspath, f)
1054 path = os.path.join(self._abspath, f)
1055 if os.path.isdir(path) and not os.path.islink(path):
1055 if os.path.isdir(path) and not os.path.islink(path):
1056 shutil.rmtree(path)
1056 shutil.rmtree(path)
1057 else:
1057 else:
1058 os.remove(path)
1058 os.remove(path)
1059
1059
1060 def archive(self, ui, archiver, prefix):
1060 def archive(self, ui, archiver, prefix):
1061 source, revision = self._state
1061 source, revision = self._state
1062 if not revision:
1062 if not revision:
1063 return
1063 return
1064 self._fetch(source, revision)
1064 self._fetch(source, revision)
1065
1065
1066 # Parse git's native archive command.
1066 # Parse git's native archive command.
1067 # This should be much faster than manually traversing the trees
1067 # This should be much faster than manually traversing the trees
1068 # and objects with many subprocess calls.
1068 # and objects with many subprocess calls.
1069 tarstream = self._gitcommand(['archive', revision], stream=True)
1069 tarstream = self._gitcommand(['archive', revision], stream=True)
1070 tar = tarfile.open(fileobj=tarstream, mode='r|')
1070 tar = tarfile.open(fileobj=tarstream, mode='r|')
1071 relpath = subrelpath(self)
1071 relpath = subrelpath(self)
1072 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1072 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1073 for i, info in enumerate(tar):
1073 for i, info in enumerate(tar):
1074 if info.isdir():
1074 if info.isdir():
1075 continue
1075 continue
1076 if info.issym():
1076 if info.issym():
1077 data = info.linkname
1077 data = info.linkname
1078 else:
1078 else:
1079 data = tar.extractfile(info).read()
1079 data = tar.extractfile(info).read()
1080 archiver.addfile(os.path.join(prefix, self._path, info.name),
1080 archiver.addfile(os.path.join(prefix, self._path, info.name),
1081 info.mode, info.issym(), data)
1081 info.mode, info.issym(), data)
1082 ui.progress(_('archiving (%s)') % relpath, i + 1,
1082 ui.progress(_('archiving (%s)') % relpath, i + 1,
1083 unit=_('files'))
1083 unit=_('files'))
1084 ui.progress(_('archiving (%s)') % relpath, None)
1084 ui.progress(_('archiving (%s)') % relpath, None)
1085
1085
1086
1086
1087 def status(self, rev2, **opts):
1087 def status(self, rev2, **opts):
1088 rev1 = self._state[1]
1088 rev1 = self._state[1]
1089 if self._gitmissing() or not rev1:
1089 if self._gitmissing() or not rev1:
1090 # if the repo is missing, return no results
1090 # if the repo is missing, return no results
1091 return [], [], [], [], [], [], []
1091 return [], [], [], [], [], [], []
1092 modified, added, removed = [], [], []
1092 modified, added, removed = [], [], []
1093 if rev2:
1093 if rev2:
1094 command = ['diff-tree', rev1, rev2]
1094 command = ['diff-tree', rev1, rev2]
1095 else:
1095 else:
1096 command = ['diff-index', rev1]
1096 command = ['diff-index', rev1]
1097 out = self._gitcommand(command)
1097 out = self._gitcommand(command)
1098 for line in out.split('\n'):
1098 for line in out.split('\n'):
1099 tab = line.find('\t')
1099 tab = line.find('\t')
1100 if tab == -1:
1100 if tab == -1:
1101 continue
1101 continue
1102 status, f = line[tab - 1], line[tab + 1:]
1102 status, f = line[tab - 1], line[tab + 1:]
1103 if status == 'M':
1103 if status == 'M':
1104 modified.append(f)
1104 modified.append(f)
1105 elif status == 'A':
1105 elif status == 'A':
1106 added.append(f)
1106 added.append(f)
1107 elif status == 'D':
1107 elif status == 'D':
1108 removed.append(f)
1108 removed.append(f)
1109
1109
1110 deleted = unknown = ignored = clean = []
1110 deleted = unknown = ignored = clean = []
1111 return modified, added, removed, deleted, unknown, ignored, clean
1111 return modified, added, removed, deleted, unknown, ignored, clean
1112
1112
1113 types = {
1113 types = {
1114 'hg': hgsubrepo,
1114 'hg': hgsubrepo,
1115 'svn': svnsubrepo,
1115 'svn': svnsubrepo,
1116 'git': gitsubrepo,
1116 'git': gitsubrepo,
1117 }
1117 }
@@ -1,458 +1,470 b''
1 Create test repository:
1 Create test repository:
2
2
3 $ hg init repo
3 $ hg init repo
4 $ cd repo
4 $ cd repo
5 $ echo x1 > x.txt
5 $ echo x1 > x.txt
6
6
7 $ hg init foo
7 $ hg init foo
8 $ cd foo
8 $ cd foo
9 $ echo y1 > y.txt
9 $ echo y1 > y.txt
10
10
11 $ hg init bar
11 $ hg init bar
12 $ cd bar
12 $ cd bar
13 $ echo z1 > z.txt
13 $ echo z1 > z.txt
14
14
15 $ cd ..
15 $ cd ..
16 $ echo 'bar = bar' > .hgsub
16 $ echo 'bar = bar' > .hgsub
17
17
18 $ cd ..
18 $ cd ..
19 $ echo 'foo = foo' > .hgsub
19 $ echo 'foo = foo' > .hgsub
20
20
21 Add files --- .hgsub files must go first to trigger subrepos:
21 Add files --- .hgsub files must go first to trigger subrepos:
22
22
23 $ hg add -S .hgsub
23 $ hg add -S .hgsub
24 $ hg add -S foo/.hgsub
24 $ hg add -S foo/.hgsub
25 $ hg add -S foo/bar
25 $ hg add -S foo/bar
26 adding foo/bar/z.txt
26 adding foo/bar/z.txt
27 $ hg add -S
27 $ hg add -S
28 adding x.txt
28 adding x.txt
29 adding foo/y.txt
29 adding foo/y.txt
30
30
31 Test recursive status without committing anything:
31 Test recursive status without committing anything:
32
32
33 $ hg status -S
33 $ hg status -S
34 A .hgsub
34 A .hgsub
35 A foo/.hgsub
35 A foo/.hgsub
36 A foo/bar/z.txt
36 A foo/bar/z.txt
37 A foo/y.txt
37 A foo/y.txt
38 A x.txt
38 A x.txt
39
39
40 Test recursive diff without committing anything:
40 Test recursive diff without committing anything:
41
41
42 $ hg diff --nodates -S foo
42 $ hg diff --nodates -S foo
43 diff -r 000000000000 foo/.hgsub
43 diff -r 000000000000 foo/.hgsub
44 --- /dev/null
44 --- /dev/null
45 +++ b/foo/.hgsub
45 +++ b/foo/.hgsub
46 @@ -0,0 +1,1 @@
46 @@ -0,0 +1,1 @@
47 +bar = bar
47 +bar = bar
48 diff -r 000000000000 foo/y.txt
48 diff -r 000000000000 foo/y.txt
49 --- /dev/null
49 --- /dev/null
50 +++ b/foo/y.txt
50 +++ b/foo/y.txt
51 @@ -0,0 +1,1 @@
51 @@ -0,0 +1,1 @@
52 +y1
52 +y1
53 diff -r 000000000000 foo/bar/z.txt
53 diff -r 000000000000 foo/bar/z.txt
54 --- /dev/null
54 --- /dev/null
55 +++ b/foo/bar/z.txt
55 +++ b/foo/bar/z.txt
56 @@ -0,0 +1,1 @@
56 @@ -0,0 +1,1 @@
57 +z1
57 +z1
58
58
59 Commits:
59 Commits:
60
60
61 $ hg commit -m 0-0-0
61 $ hg commit -m 0-0-0
62 committing subrepository foo
62 committing subrepository foo
63 committing subrepository foo/bar
63 committing subrepository foo/bar
64
64
65 $ cd foo
65 $ cd foo
66 $ echo y2 >> y.txt
66 $ echo y2 >> y.txt
67 $ hg commit -m 0-1-0
67 $ hg commit -m 0-1-0
68
68
69 $ cd bar
69 $ cd bar
70 $ echo z2 >> z.txt
70 $ echo z2 >> z.txt
71 $ hg commit -m 0-1-1
71 $ hg commit -m 0-1-1
72
72
73 $ cd ..
73 $ cd ..
74 $ hg commit -m 0-2-1
74 $ hg commit -m 0-2-1
75 committing subrepository bar
75 committing subrepository bar
76
76
77 $ cd ..
77 $ cd ..
78 $ hg commit -m 1-2-1
78 $ hg commit -m 1-2-1
79 committing subrepository foo
79 committing subrepository foo
80
80
81 Change working directory:
81 Change working directory:
82
82
83 $ echo y3 >> foo/y.txt
83 $ echo y3 >> foo/y.txt
84 $ echo z3 >> foo/bar/z.txt
84 $ echo z3 >> foo/bar/z.txt
85 $ hg status -S
85 $ hg status -S
86 M foo/bar/z.txt
86 M foo/bar/z.txt
87 M foo/y.txt
87 M foo/y.txt
88 $ hg diff --nodates -S
88 $ hg diff --nodates -S
89 diff -r d254738c5f5e foo/y.txt
89 diff -r d254738c5f5e foo/y.txt
90 --- a/foo/y.txt
90 --- a/foo/y.txt
91 +++ b/foo/y.txt
91 +++ b/foo/y.txt
92 @@ -1,2 +1,3 @@
92 @@ -1,2 +1,3 @@
93 y1
93 y1
94 y2
94 y2
95 +y3
95 +y3
96 diff -r 9647f22de499 foo/bar/z.txt
96 diff -r 9647f22de499 foo/bar/z.txt
97 --- a/foo/bar/z.txt
97 --- a/foo/bar/z.txt
98 +++ b/foo/bar/z.txt
98 +++ b/foo/bar/z.txt
99 @@ -1,2 +1,3 @@
99 @@ -1,2 +1,3 @@
100 z1
100 z1
101 z2
101 z2
102 +z3
102 +z3
103
103
104 Status call crossing repository boundaries:
104 Status call crossing repository boundaries:
105
105
106 $ hg status -S foo/bar/z.txt
106 $ hg status -S foo/bar/z.txt
107 M foo/bar/z.txt
107 M foo/bar/z.txt
108 $ hg status -S -I 'foo/?.txt'
108 $ hg status -S -I 'foo/?.txt'
109 M foo/y.txt
109 M foo/y.txt
110 $ hg status -S -I '**/?.txt'
110 $ hg status -S -I '**/?.txt'
111 M foo/bar/z.txt
111 M foo/bar/z.txt
112 M foo/y.txt
112 M foo/y.txt
113 $ hg diff --nodates -S -I '**/?.txt'
113 $ hg diff --nodates -S -I '**/?.txt'
114 diff -r d254738c5f5e foo/y.txt
114 diff -r d254738c5f5e foo/y.txt
115 --- a/foo/y.txt
115 --- a/foo/y.txt
116 +++ b/foo/y.txt
116 +++ b/foo/y.txt
117 @@ -1,2 +1,3 @@
117 @@ -1,2 +1,3 @@
118 y1
118 y1
119 y2
119 y2
120 +y3
120 +y3
121 diff -r 9647f22de499 foo/bar/z.txt
121 diff -r 9647f22de499 foo/bar/z.txt
122 --- a/foo/bar/z.txt
122 --- a/foo/bar/z.txt
123 +++ b/foo/bar/z.txt
123 +++ b/foo/bar/z.txt
124 @@ -1,2 +1,3 @@
124 @@ -1,2 +1,3 @@
125 z1
125 z1
126 z2
126 z2
127 +z3
127 +z3
128
128
129 Status from within a subdirectory:
129 Status from within a subdirectory:
130
130
131 $ mkdir dir
131 $ mkdir dir
132 $ cd dir
132 $ cd dir
133 $ echo a1 > a.txt
133 $ echo a1 > a.txt
134 $ hg status -S
134 $ hg status -S
135 M foo/bar/z.txt
135 M foo/bar/z.txt
136 M foo/y.txt
136 M foo/y.txt
137 ? dir/a.txt
137 ? dir/a.txt
138 $ hg diff --nodates -S
138 $ hg diff --nodates -S
139 diff -r d254738c5f5e foo/y.txt
139 diff -r d254738c5f5e foo/y.txt
140 --- a/foo/y.txt
140 --- a/foo/y.txt
141 +++ b/foo/y.txt
141 +++ b/foo/y.txt
142 @@ -1,2 +1,3 @@
142 @@ -1,2 +1,3 @@
143 y1
143 y1
144 y2
144 y2
145 +y3
145 +y3
146 diff -r 9647f22de499 foo/bar/z.txt
146 diff -r 9647f22de499 foo/bar/z.txt
147 --- a/foo/bar/z.txt
147 --- a/foo/bar/z.txt
148 +++ b/foo/bar/z.txt
148 +++ b/foo/bar/z.txt
149 @@ -1,2 +1,3 @@
149 @@ -1,2 +1,3 @@
150 z1
150 z1
151 z2
151 z2
152 +z3
152 +z3
153
153
154 Status with relative path:
154 Status with relative path:
155
155
156 $ hg status -S ..
156 $ hg status -S ..
157 M ../foo/bar/z.txt
157 M ../foo/bar/z.txt
158 M ../foo/y.txt
158 M ../foo/y.txt
159 ? a.txt
159 ? a.txt
160 $ hg diff --nodates -S ..
160 $ hg diff --nodates -S ..
161 diff -r d254738c5f5e foo/y.txt
161 diff -r d254738c5f5e foo/y.txt
162 --- a/foo/y.txt
162 --- a/foo/y.txt
163 +++ b/foo/y.txt
163 +++ b/foo/y.txt
164 @@ -1,2 +1,3 @@
164 @@ -1,2 +1,3 @@
165 y1
165 y1
166 y2
166 y2
167 +y3
167 +y3
168 diff -r 9647f22de499 foo/bar/z.txt
168 diff -r 9647f22de499 foo/bar/z.txt
169 --- a/foo/bar/z.txt
169 --- a/foo/bar/z.txt
170 +++ b/foo/bar/z.txt
170 +++ b/foo/bar/z.txt
171 @@ -1,2 +1,3 @@
171 @@ -1,2 +1,3 @@
172 z1
172 z1
173 z2
173 z2
174 +z3
174 +z3
175 $ cd ..
175 $ cd ..
176
176
177 Cleanup and final commit:
177 Cleanup and final commit:
178
178
179 $ rm -r dir
179 $ rm -r dir
180 $ hg commit -m 2-3-2
180 $ hg commit -m 2-3-2
181 committing subrepository foo
181 committing subrepository foo
182 committing subrepository foo/bar
182 committing subrepository foo/bar
183
183
184 Log with the relationships between repo and its subrepo:
184 Log with the relationships between repo and its subrepo:
185
185
186 $ hg log --template '{rev}:{node|short} {desc}\n'
186 $ hg log --template '{rev}:{node|short} {desc}\n'
187 2:1326fa26d0c0 2-3-2
187 2:1326fa26d0c0 2-3-2
188 1:4b3c9ff4f66b 1-2-1
188 1:4b3c9ff4f66b 1-2-1
189 0:23376cbba0d8 0-0-0
189 0:23376cbba0d8 0-0-0
190
190
191 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
191 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
192 3:65903cebad86 2-3-2
192 3:65903cebad86 2-3-2
193 2:d254738c5f5e 0-2-1
193 2:d254738c5f5e 0-2-1
194 1:8629ce7dcc39 0-1-0
194 1:8629ce7dcc39 0-1-0
195 0:af048e97ade2 0-0-0
195 0:af048e97ade2 0-0-0
196
196
197 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
197 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
198 2:31ecbdafd357 2-3-2
198 2:31ecbdafd357 2-3-2
199 1:9647f22de499 0-1-1
199 1:9647f22de499 0-1-1
200 0:4904098473f9 0-0-0
200 0:4904098473f9 0-0-0
201
201
202 Status between revisions:
202 Status between revisions:
203
203
204 $ hg status -S
204 $ hg status -S
205 $ hg status -S --rev 0:1
205 $ hg status -S --rev 0:1
206 M .hgsubstate
206 M .hgsubstate
207 M foo/.hgsubstate
207 M foo/.hgsubstate
208 M foo/bar/z.txt
208 M foo/bar/z.txt
209 M foo/y.txt
209 M foo/y.txt
210 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
210 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
211 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
211 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
212 --- a/foo/y.txt
212 --- a/foo/y.txt
213 +++ b/foo/y.txt
213 +++ b/foo/y.txt
214 @@ -1,1 +1,2 @@
214 @@ -1,1 +1,2 @@
215 y1
215 y1
216 +y2
216 +y2
217 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
217 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
218 --- a/foo/bar/z.txt
218 --- a/foo/bar/z.txt
219 +++ b/foo/bar/z.txt
219 +++ b/foo/bar/z.txt
220 @@ -1,1 +1,2 @@
220 @@ -1,1 +1,2 @@
221 z1
221 z1
222 +z2
222 +z2
223
223
224 Enable progress extension for archive tests:
224 Enable progress extension for archive tests:
225
225
226 $ cp $HGRCPATH $HGRCPATH.no-progress
226 $ cp $HGRCPATH $HGRCPATH.no-progress
227 $ cat >> $HGRCPATH <<EOF
227 $ cat >> $HGRCPATH <<EOF
228 > [extensions]
228 > [extensions]
229 > progress =
229 > progress =
230 > [progress]
230 > [progress]
231 > assume-tty = 1
231 > assume-tty = 1
232 > delay = 0
232 > delay = 0
233 > format = topic bar number
233 > format = topic bar number
234 > refresh = 0
234 > refresh = 0
235 > width = 60
235 > width = 60
236 > EOF
236 > EOF
237
237
238 Test archiving to a directory tree (the doubled lines in the output
238 Test archiving to a directory tree (the doubled lines in the output
239 only show up in the test output, not in real usage):
239 only show up in the test output, not in real usage):
240
240
241 $ hg archive --subrepos ../archive 2>&1 | $TESTDIR/filtercr.py
241 $ hg archive --subrepos ../archive 2>&1 | $TESTDIR/filtercr.py
242
242
243 archiving [ ] 0/3
243 archiving [ ] 0/3
244 archiving [ ] 0/3
244 archiving [ ] 0/3
245 archiving [=============> ] 1/3
245 archiving [=============> ] 1/3
246 archiving [=============> ] 1/3
246 archiving [=============> ] 1/3
247 archiving [===========================> ] 2/3
247 archiving [===========================> ] 2/3
248 archiving [===========================> ] 2/3
248 archiving [===========================> ] 2/3
249 archiving [==========================================>] 3/3
249 archiving [==========================================>] 3/3
250 archiving [==========================================>] 3/3
250 archiving [==========================================>] 3/3
251
251
252 archiving (foo) [ ] 0/3
252 archiving (foo) [ ] 0/3
253 archiving (foo) [ ] 0/3
253 archiving (foo) [ ] 0/3
254 archiving (foo) [===========> ] 1/3
254 archiving (foo) [===========> ] 1/3
255 archiving (foo) [===========> ] 1/3
255 archiving (foo) [===========> ] 1/3
256 archiving (foo) [=======================> ] 2/3
256 archiving (foo) [=======================> ] 2/3
257 archiving (foo) [=======================> ] 2/3
257 archiving (foo) [=======================> ] 2/3
258 archiving (foo) [====================================>] 3/3
258 archiving (foo) [====================================>] 3/3
259 archiving (foo) [====================================>] 3/3
259 archiving (foo) [====================================>] 3/3
260
260
261 archiving (foo/bar) [ ] 0/1
261 archiving (foo/bar) [ ] 0/1
262 archiving (foo/bar) [ ] 0/1
262 archiving (foo/bar) [ ] 0/1
263 archiving (foo/bar) [================================>] 1/1
263 archiving (foo/bar) [================================>] 1/1
264 archiving (foo/bar) [================================>] 1/1
264 archiving (foo/bar) [================================>] 1/1
265 \r (esc)
265 \r (esc)
266 $ find ../archive | sort
266 $ find ../archive | sort
267 ../archive
267 ../archive
268 ../archive/.hg_archival.txt
268 ../archive/.hg_archival.txt
269 ../archive/.hgsub
269 ../archive/.hgsub
270 ../archive/.hgsubstate
270 ../archive/.hgsubstate
271 ../archive/foo
271 ../archive/foo
272 ../archive/foo/.hgsub
272 ../archive/foo/.hgsub
273 ../archive/foo/.hgsubstate
273 ../archive/foo/.hgsubstate
274 ../archive/foo/bar
274 ../archive/foo/bar
275 ../archive/foo/bar/z.txt
275 ../archive/foo/bar/z.txt
276 ../archive/foo/y.txt
276 ../archive/foo/y.txt
277 ../archive/x.txt
277 ../archive/x.txt
278
278
279 Test archiving to zip file (unzip output is unstable):
279 Test archiving to zip file (unzip output is unstable):
280
280
281 $ hg archive --subrepos ../archive.zip 2>&1 | $TESTDIR/filtercr.py
281 $ hg archive --subrepos ../archive.zip 2>&1 | $TESTDIR/filtercr.py
282
282
283 archiving [ ] 0/3
283 archiving [ ] 0/3
284 archiving [ ] 0/3
284 archiving [ ] 0/3
285 archiving [=============> ] 1/3
285 archiving [=============> ] 1/3
286 archiving [=============> ] 1/3
286 archiving [=============> ] 1/3
287 archiving [===========================> ] 2/3
287 archiving [===========================> ] 2/3
288 archiving [===========================> ] 2/3
288 archiving [===========================> ] 2/3
289 archiving [==========================================>] 3/3
289 archiving [==========================================>] 3/3
290 archiving [==========================================>] 3/3
290 archiving [==========================================>] 3/3
291
291
292 archiving (foo) [ ] 0/3
292 archiving (foo) [ ] 0/3
293 archiving (foo) [ ] 0/3
293 archiving (foo) [ ] 0/3
294 archiving (foo) [===========> ] 1/3
294 archiving (foo) [===========> ] 1/3
295 archiving (foo) [===========> ] 1/3
295 archiving (foo) [===========> ] 1/3
296 archiving (foo) [=======================> ] 2/3
296 archiving (foo) [=======================> ] 2/3
297 archiving (foo) [=======================> ] 2/3
297 archiving (foo) [=======================> ] 2/3
298 archiving (foo) [====================================>] 3/3
298 archiving (foo) [====================================>] 3/3
299 archiving (foo) [====================================>] 3/3
299 archiving (foo) [====================================>] 3/3
300
300
301 archiving (foo/bar) [ ] 0/1
301 archiving (foo/bar) [ ] 0/1
302 archiving (foo/bar) [ ] 0/1
302 archiving (foo/bar) [ ] 0/1
303 archiving (foo/bar) [================================>] 1/1
303 archiving (foo/bar) [================================>] 1/1
304 archiving (foo/bar) [================================>] 1/1
304 archiving (foo/bar) [================================>] 1/1
305 \r (esc)
305 \r (esc)
306
306
307 Test archiving a revision that references a subrepo that is not yet
307 Test archiving a revision that references a subrepo that is not yet
308 cloned:
308 cloned:
309
309
310 $ hg clone -U . ../empty
310 $ hg clone -U . ../empty
311 $ cd ../empty
311 $ cd ../empty
312 $ hg archive --subrepos -r tip ../archive.tar.gz 2>&1 | $TESTDIR/filtercr.py
312 $ hg archive --subrepos -r tip ../archive.tar.gz 2>&1 | $TESTDIR/filtercr.py
313
313
314 archiving [ ] 0/3
314 archiving [ ] 0/3
315 archiving [ ] 0/3
315 archiving [ ] 0/3
316 archiving [=============> ] 1/3
316 archiving [=============> ] 1/3
317 archiving [=============> ] 1/3
317 archiving [=============> ] 1/3
318 archiving [===========================> ] 2/3
318 archiving [===========================> ] 2/3
319 archiving [===========================> ] 2/3
319 archiving [===========================> ] 2/3
320 archiving [==========================================>] 3/3
320 archiving [==========================================>] 3/3
321 archiving [==========================================>] 3/3
321 archiving [==========================================>] 3/3
322
322
323 archiving (foo) [ ] 0/3
323 archiving (foo) [ ] 0/3
324 archiving (foo) [ ] 0/3
324 archiving (foo) [ ] 0/3
325 archiving (foo) [===========> ] 1/3
325 archiving (foo) [===========> ] 1/3
326 archiving (foo) [===========> ] 1/3
326 archiving (foo) [===========> ] 1/3
327 archiving (foo) [=======================> ] 2/3
327 archiving (foo) [=======================> ] 2/3
328 archiving (foo) [=======================> ] 2/3
328 archiving (foo) [=======================> ] 2/3
329 archiving (foo) [====================================>] 3/3
329 archiving (foo) [====================================>] 3/3
330 archiving (foo) [====================================>] 3/3
330 archiving (foo) [====================================>] 3/3
331
331
332 archiving (foo/bar) [ ] 0/1
332 archiving (foo/bar) [ ] 0/1
333 archiving (foo/bar) [ ] 0/1
333 archiving (foo/bar) [ ] 0/1
334 archiving (foo/bar) [================================>] 1/1
334 archiving (foo/bar) [================================>] 1/1
335 archiving (foo/bar) [================================>] 1/1
335 archiving (foo/bar) [================================>] 1/1
336
336
337 cloning subrepo foo from $TESTTMP/repo/foo
337 cloning subrepo foo from $TESTTMP/repo/foo
338 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
338 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
339
339
340 The newly cloned subrepos contain no working copy:
340 The newly cloned subrepos contain no working copy:
341
341
342 $ hg -R foo summary
342 $ hg -R foo summary
343 parent: -1:000000000000 (no revision checked out)
343 parent: -1:000000000000 (no revision checked out)
344 branch: default
344 branch: default
345 commit: (clean)
345 commit: (clean)
346 update: 4 new changesets (update)
346 update: 4 new changesets (update)
347
347
348 Disable progress extension and cleanup:
348 Disable progress extension and cleanup:
349
349
350 $ mv $HGRCPATH.no-progress $HGRCPATH
350 $ mv $HGRCPATH.no-progress $HGRCPATH
351
351
352 Test archiving when there is a directory in the way for a subrepo
353 created by archive:
354
355 $ hg clone -U . ../almost-empty
356 $ cd ../almost-empty
357 $ mkdir foo
358 $ echo f > foo/f
359 $ hg archive --subrepos -r tip archive
360 cloning subrepo foo from $TESTTMP/empty/foo
361 abort: destination '$TESTTMP/almost-empty/foo' is not empty
362 [255]
363
352 Clone and test outgoing:
364 Clone and test outgoing:
353
365
354 $ cd ..
366 $ cd ..
355 $ hg clone repo repo2
367 $ hg clone repo repo2
356 updating to branch default
368 updating to branch default
357 cloning subrepo foo from $TESTTMP/repo/foo
369 cloning subrepo foo from $TESTTMP/repo/foo
358 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
370 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar
359 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
360 $ cd repo2
372 $ cd repo2
361 $ hg outgoing -S
373 $ hg outgoing -S
362 comparing with $TESTTMP/repo
374 comparing with $TESTTMP/repo
363 searching for changes
375 searching for changes
364 no changes found
376 no changes found
365 comparing with $TESTTMP/repo/foo
377 comparing with $TESTTMP/repo/foo
366 searching for changes
378 searching for changes
367 no changes found
379 no changes found
368 comparing with $TESTTMP/repo/foo/bar
380 comparing with $TESTTMP/repo/foo/bar
369 searching for changes
381 searching for changes
370 no changes found
382 no changes found
371 [1]
383 [1]
372
384
373 Make nested change:
385 Make nested change:
374
386
375 $ echo y4 >> foo/y.txt
387 $ echo y4 >> foo/y.txt
376 $ hg diff --nodates -S
388 $ hg diff --nodates -S
377 diff -r 65903cebad86 foo/y.txt
389 diff -r 65903cebad86 foo/y.txt
378 --- a/foo/y.txt
390 --- a/foo/y.txt
379 +++ b/foo/y.txt
391 +++ b/foo/y.txt
380 @@ -1,3 +1,4 @@
392 @@ -1,3 +1,4 @@
381 y1
393 y1
382 y2
394 y2
383 y3
395 y3
384 +y4
396 +y4
385 $ hg commit -m 3-4-2
397 $ hg commit -m 3-4-2
386 committing subrepository foo
398 committing subrepository foo
387 $ hg outgoing -S
399 $ hg outgoing -S
388 comparing with $TESTTMP/repo
400 comparing with $TESTTMP/repo
389 searching for changes
401 searching for changes
390 changeset: 3:2655b8ecc4ee
402 changeset: 3:2655b8ecc4ee
391 tag: tip
403 tag: tip
392 user: test
404 user: test
393 date: Thu Jan 01 00:00:00 1970 +0000
405 date: Thu Jan 01 00:00:00 1970 +0000
394 summary: 3-4-2
406 summary: 3-4-2
395
407
396 comparing with $TESTTMP/repo/foo
408 comparing with $TESTTMP/repo/foo
397 searching for changes
409 searching for changes
398 changeset: 4:e96193d6cb36
410 changeset: 4:e96193d6cb36
399 tag: tip
411 tag: tip
400 user: test
412 user: test
401 date: Thu Jan 01 00:00:00 1970 +0000
413 date: Thu Jan 01 00:00:00 1970 +0000
402 summary: 3-4-2
414 summary: 3-4-2
403
415
404 comparing with $TESTTMP/repo/foo/bar
416 comparing with $TESTTMP/repo/foo/bar
405 searching for changes
417 searching for changes
406 no changes found
418 no changes found
407
419
408
420
409 Switch to original repo and setup default path:
421 Switch to original repo and setup default path:
410
422
411 $ cd ../repo
423 $ cd ../repo
412 $ echo '[paths]' >> .hg/hgrc
424 $ echo '[paths]' >> .hg/hgrc
413 $ echo 'default = ../repo2' >> .hg/hgrc
425 $ echo 'default = ../repo2' >> .hg/hgrc
414
426
415 Test incoming:
427 Test incoming:
416
428
417 $ hg incoming -S
429 $ hg incoming -S
418 comparing with $TESTTMP/repo2
430 comparing with $TESTTMP/repo2
419 searching for changes
431 searching for changes
420 changeset: 3:2655b8ecc4ee
432 changeset: 3:2655b8ecc4ee
421 tag: tip
433 tag: tip
422 user: test
434 user: test
423 date: Thu Jan 01 00:00:00 1970 +0000
435 date: Thu Jan 01 00:00:00 1970 +0000
424 summary: 3-4-2
436 summary: 3-4-2
425
437
426 comparing with $TESTTMP/repo2/foo
438 comparing with $TESTTMP/repo2/foo
427 searching for changes
439 searching for changes
428 changeset: 4:e96193d6cb36
440 changeset: 4:e96193d6cb36
429 tag: tip
441 tag: tip
430 user: test
442 user: test
431 date: Thu Jan 01 00:00:00 1970 +0000
443 date: Thu Jan 01 00:00:00 1970 +0000
432 summary: 3-4-2
444 summary: 3-4-2
433
445
434 comparing with $TESTTMP/repo2/foo/bar
446 comparing with $TESTTMP/repo2/foo/bar
435 searching for changes
447 searching for changes
436 no changes found
448 no changes found
437
449
438 $ hg incoming -S --bundle incoming.hg
450 $ hg incoming -S --bundle incoming.hg
439 abort: cannot combine --bundle and --subrepos
451 abort: cannot combine --bundle and --subrepos
440 [255]
452 [255]
441
453
442 Test missing subrepo:
454 Test missing subrepo:
443
455
444 $ rm -r foo
456 $ rm -r foo
445 $ hg status -S
457 $ hg status -S
446 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
458 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
447
459
448 Issue2619: IndexError: list index out of range on hg add with subrepos
460 Issue2619: IndexError: list index out of range on hg add with subrepos
449 The subrepo must sorts after the explicit filename.
461 The subrepo must sorts after the explicit filename.
450
462
451 $ cd ..
463 $ cd ..
452 $ hg init test
464 $ hg init test
453 $ cd test
465 $ cd test
454 $ hg init x
466 $ hg init x
455 $ echo "x = x" >> .hgsub
467 $ echo "x = x" >> .hgsub
456 $ hg add .hgsub
468 $ hg add .hgsub
457 $ touch a x/a
469 $ touch a x/a
458 $ hg add a x/a
470 $ hg add a x/a
General Comments 0
You need to be logged in to leave comments. Login now