##// END OF EJS Templates
subrepo/svn: fix checked out rev number retrieval (issue2968)...
Patrick Mezard -
r16554:ae2664ee stable
parent child Browse files
Show More
@@ -1,1222 +1,1233 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[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
95 state[util.pconvert(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(util.pconvert(parent))
226 parent = util.url(util.pconvert(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 basestate(self):
278 def basestate(self):
279 """current working directory base state, disregarding .hgsubstate
279 """current working directory base state, disregarding .hgsubstate
280 state and working directory modifications"""
280 state and working directory modifications"""
281 raise NotImplementedError
281 raise NotImplementedError
282
282
283 def checknested(self, path):
283 def checknested(self, path):
284 """check if path is a subrepository within this repository"""
284 """check if path is a subrepository within this repository"""
285 return False
285 return False
286
286
287 def commit(self, text, user, date):
287 def commit(self, text, user, date):
288 """commit the current changes to the subrepo with the given
288 """commit the current changes to the subrepo with the given
289 log message. Use given user and date if possible. Return the
289 log message. Use given user and date if possible. Return the
290 new state of the subrepo.
290 new state of the subrepo.
291 """
291 """
292 raise NotImplementedError
292 raise NotImplementedError
293
293
294 def remove(self):
294 def remove(self):
295 """remove the subrepo
295 """remove the subrepo
296
296
297 (should verify the dirstate is not dirty first)
297 (should verify the dirstate is not dirty first)
298 """
298 """
299 raise NotImplementedError
299 raise NotImplementedError
300
300
301 def get(self, state, overwrite=False):
301 def get(self, state, overwrite=False):
302 """run whatever commands are needed to put the subrepo into
302 """run whatever commands are needed to put the subrepo into
303 this state
303 this state
304 """
304 """
305 raise NotImplementedError
305 raise NotImplementedError
306
306
307 def merge(self, state):
307 def merge(self, state):
308 """merge currently-saved state with the new state."""
308 """merge currently-saved state with the new state."""
309 raise NotImplementedError
309 raise NotImplementedError
310
310
311 def push(self, opts):
311 def push(self, opts):
312 """perform whatever action is analogous to 'hg push'
312 """perform whatever action is analogous to 'hg push'
313
313
314 This may be a no-op on some systems.
314 This may be a no-op on some systems.
315 """
315 """
316 raise NotImplementedError
316 raise NotImplementedError
317
317
318 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
318 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
319 return []
319 return []
320
320
321 def status(self, rev2, **opts):
321 def status(self, rev2, **opts):
322 return [], [], [], [], [], [], []
322 return [], [], [], [], [], [], []
323
323
324 def diff(self, diffopts, node2, match, prefix, **opts):
324 def diff(self, diffopts, node2, match, prefix, **opts):
325 pass
325 pass
326
326
327 def outgoing(self, ui, dest, opts):
327 def outgoing(self, ui, dest, opts):
328 return 1
328 return 1
329
329
330 def incoming(self, ui, source, opts):
330 def incoming(self, ui, source, opts):
331 return 1
331 return 1
332
332
333 def files(self):
333 def files(self):
334 """return filename iterator"""
334 """return filename iterator"""
335 raise NotImplementedError
335 raise NotImplementedError
336
336
337 def filedata(self, name):
337 def filedata(self, name):
338 """return file data"""
338 """return file data"""
339 raise NotImplementedError
339 raise NotImplementedError
340
340
341 def fileflags(self, name):
341 def fileflags(self, name):
342 """return file flags"""
342 """return file flags"""
343 return ''
343 return ''
344
344
345 def archive(self, ui, archiver, prefix):
345 def archive(self, ui, archiver, prefix):
346 files = self.files()
346 files = self.files()
347 total = len(files)
347 total = len(files)
348 relpath = subrelpath(self)
348 relpath = subrelpath(self)
349 ui.progress(_('archiving (%s)') % relpath, 0,
349 ui.progress(_('archiving (%s)') % relpath, 0,
350 unit=_('files'), total=total)
350 unit=_('files'), total=total)
351 for i, name in enumerate(files):
351 for i, name in enumerate(files):
352 flags = self.fileflags(name)
352 flags = self.fileflags(name)
353 mode = 'x' in flags and 0755 or 0644
353 mode = 'x' in flags and 0755 or 0644
354 symlink = 'l' in flags
354 symlink = 'l' in flags
355 archiver.addfile(os.path.join(prefix, self._path, name),
355 archiver.addfile(os.path.join(prefix, self._path, name),
356 mode, symlink, self.filedata(name))
356 mode, symlink, self.filedata(name))
357 ui.progress(_('archiving (%s)') % relpath, i + 1,
357 ui.progress(_('archiving (%s)') % relpath, i + 1,
358 unit=_('files'), total=total)
358 unit=_('files'), total=total)
359 ui.progress(_('archiving (%s)') % relpath, None)
359 ui.progress(_('archiving (%s)') % relpath, None)
360
360
361 def walk(self, match):
361 def walk(self, match):
362 '''
362 '''
363 walk recursively through the directory tree, finding all files
363 walk recursively through the directory tree, finding all files
364 matched by the match function
364 matched by the match function
365 '''
365 '''
366 pass
366 pass
367
367
368 def forget(self, ui, match, prefix):
368 def forget(self, ui, match, prefix):
369 return ([], [])
369 return ([], [])
370
370
371 def revert(self, ui, substate, *pats, **opts):
371 def revert(self, ui, substate, *pats, **opts):
372 ui.warn('%s: reverting %s subrepos is unsupported\n' \
372 ui.warn('%s: reverting %s subrepos is unsupported\n' \
373 % (substate[0], substate[2]))
373 % (substate[0], substate[2]))
374 return []
374 return []
375
375
376 class hgsubrepo(abstractsubrepo):
376 class hgsubrepo(abstractsubrepo):
377 def __init__(self, ctx, path, state):
377 def __init__(self, ctx, path, state):
378 self._path = path
378 self._path = path
379 self._state = state
379 self._state = state
380 r = ctx._repo
380 r = ctx._repo
381 root = r.wjoin(path)
381 root = r.wjoin(path)
382 create = False
382 create = False
383 if not os.path.exists(os.path.join(root, '.hg')):
383 if not os.path.exists(os.path.join(root, '.hg')):
384 create = True
384 create = True
385 util.makedirs(root)
385 util.makedirs(root)
386 self._repo = hg.repository(r.ui, root, create=create)
386 self._repo = hg.repository(r.ui, root, create=create)
387 self._initrepo(r, state[0], create)
387 self._initrepo(r, state[0], create)
388
388
389 def _initrepo(self, parentrepo, source, create):
389 def _initrepo(self, parentrepo, source, create):
390 self._repo._subparent = parentrepo
390 self._repo._subparent = parentrepo
391 self._repo._subsource = source
391 self._repo._subsource = source
392
392
393 if create:
393 if create:
394 fp = self._repo.opener("hgrc", "w", text=True)
394 fp = self._repo.opener("hgrc", "w", text=True)
395 fp.write('[paths]\n')
395 fp.write('[paths]\n')
396
396
397 def addpathconfig(key, value):
397 def addpathconfig(key, value):
398 if value:
398 if value:
399 fp.write('%s = %s\n' % (key, value))
399 fp.write('%s = %s\n' % (key, value))
400 self._repo.ui.setconfig('paths', key, value)
400 self._repo.ui.setconfig('paths', key, value)
401
401
402 defpath = _abssource(self._repo, abort=False)
402 defpath = _abssource(self._repo, abort=False)
403 defpushpath = _abssource(self._repo, True, abort=False)
403 defpushpath = _abssource(self._repo, True, abort=False)
404 addpathconfig('default', defpath)
404 addpathconfig('default', defpath)
405 if defpath != defpushpath:
405 if defpath != defpushpath:
406 addpathconfig('default-push', defpushpath)
406 addpathconfig('default-push', defpushpath)
407 fp.close()
407 fp.close()
408
408
409 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
409 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
410 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
410 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
411 os.path.join(prefix, self._path), explicitonly)
411 os.path.join(prefix, self._path), explicitonly)
412
412
413 def status(self, rev2, **opts):
413 def status(self, rev2, **opts):
414 try:
414 try:
415 rev1 = self._state[1]
415 rev1 = self._state[1]
416 ctx1 = self._repo[rev1]
416 ctx1 = self._repo[rev1]
417 ctx2 = self._repo[rev2]
417 ctx2 = self._repo[rev2]
418 return self._repo.status(ctx1, ctx2, **opts)
418 return self._repo.status(ctx1, ctx2, **opts)
419 except error.RepoLookupError, inst:
419 except error.RepoLookupError, inst:
420 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
420 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
421 % (inst, subrelpath(self)))
421 % (inst, subrelpath(self)))
422 return [], [], [], [], [], [], []
422 return [], [], [], [], [], [], []
423
423
424 def diff(self, diffopts, node2, match, prefix, **opts):
424 def diff(self, diffopts, node2, match, prefix, **opts):
425 try:
425 try:
426 node1 = node.bin(self._state[1])
426 node1 = node.bin(self._state[1])
427 # We currently expect node2 to come from substate and be
427 # We currently expect node2 to come from substate and be
428 # in hex format
428 # in hex format
429 if node2 is not None:
429 if node2 is not None:
430 node2 = node.bin(node2)
430 node2 = node.bin(node2)
431 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
431 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
432 node1, node2, match,
432 node1, node2, match,
433 prefix=os.path.join(prefix, self._path),
433 prefix=os.path.join(prefix, self._path),
434 listsubrepos=True, **opts)
434 listsubrepos=True, **opts)
435 except error.RepoLookupError, inst:
435 except error.RepoLookupError, inst:
436 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
436 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
437 % (inst, subrelpath(self)))
437 % (inst, subrelpath(self)))
438
438
439 def archive(self, ui, archiver, prefix):
439 def archive(self, ui, archiver, prefix):
440 self._get(self._state + ('hg',))
440 self._get(self._state + ('hg',))
441 abstractsubrepo.archive(self, ui, archiver, prefix)
441 abstractsubrepo.archive(self, ui, archiver, prefix)
442
442
443 rev = self._state[1]
443 rev = self._state[1]
444 ctx = self._repo[rev]
444 ctx = self._repo[rev]
445 for subpath in ctx.substate:
445 for subpath in ctx.substate:
446 s = subrepo(ctx, subpath)
446 s = subrepo(ctx, subpath)
447 s.archive(ui, archiver, os.path.join(prefix, self._path))
447 s.archive(ui, archiver, os.path.join(prefix, self._path))
448
448
449 def dirty(self, ignoreupdate=False):
449 def dirty(self, ignoreupdate=False):
450 r = self._state[1]
450 r = self._state[1]
451 if r == '' and not ignoreupdate: # no state recorded
451 if r == '' and not ignoreupdate: # no state recorded
452 return True
452 return True
453 w = self._repo[None]
453 w = self._repo[None]
454 if r != w.p1().hex() and not ignoreupdate:
454 if r != w.p1().hex() and not ignoreupdate:
455 # different version checked out
455 # different version checked out
456 return True
456 return True
457 return w.dirty() # working directory changed
457 return w.dirty() # working directory changed
458
458
459 def basestate(self):
459 def basestate(self):
460 return self._repo['.'].hex()
460 return self._repo['.'].hex()
461
461
462 def checknested(self, path):
462 def checknested(self, path):
463 return self._repo._checknested(self._repo.wjoin(path))
463 return self._repo._checknested(self._repo.wjoin(path))
464
464
465 def commit(self, text, user, date):
465 def commit(self, text, user, date):
466 # don't bother committing in the subrepo if it's only been
466 # don't bother committing in the subrepo if it's only been
467 # updated
467 # updated
468 if not self.dirty(True):
468 if not self.dirty(True):
469 return self._repo['.'].hex()
469 return self._repo['.'].hex()
470 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
470 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
471 n = self._repo.commit(text, user, date)
471 n = self._repo.commit(text, user, date)
472 if not n:
472 if not n:
473 return self._repo['.'].hex() # different version checked out
473 return self._repo['.'].hex() # different version checked out
474 return node.hex(n)
474 return node.hex(n)
475
475
476 def remove(self):
476 def remove(self):
477 # we can't fully delete the repository as it may contain
477 # we can't fully delete the repository as it may contain
478 # local-only history
478 # local-only history
479 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
479 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
480 hg.clean(self._repo, node.nullid, False)
480 hg.clean(self._repo, node.nullid, False)
481
481
482 def _get(self, state):
482 def _get(self, state):
483 source, revision, kind = state
483 source, revision, kind = state
484 if revision not in self._repo:
484 if revision not in self._repo:
485 self._repo._subsource = source
485 self._repo._subsource = source
486 srcurl = _abssource(self._repo)
486 srcurl = _abssource(self._repo)
487 other = hg.peer(self._repo.ui, {}, srcurl)
487 other = hg.peer(self._repo.ui, {}, srcurl)
488 if len(self._repo) == 0:
488 if len(self._repo) == 0:
489 self._repo.ui.status(_('cloning subrepo %s from %s\n')
489 self._repo.ui.status(_('cloning subrepo %s from %s\n')
490 % (subrelpath(self), srcurl))
490 % (subrelpath(self), srcurl))
491 parentrepo = self._repo._subparent
491 parentrepo = self._repo._subparent
492 shutil.rmtree(self._repo.path)
492 shutil.rmtree(self._repo.path)
493 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
493 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
494 self._repo.root, update=False)
494 self._repo.root, update=False)
495 self._initrepo(parentrepo, source, create=True)
495 self._initrepo(parentrepo, source, create=True)
496 else:
496 else:
497 self._repo.ui.status(_('pulling subrepo %s from %s\n')
497 self._repo.ui.status(_('pulling subrepo %s from %s\n')
498 % (subrelpath(self), srcurl))
498 % (subrelpath(self), srcurl))
499 self._repo.pull(other)
499 self._repo.pull(other)
500 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
500 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
501 srcurl)
501 srcurl)
502
502
503 def get(self, state, overwrite=False):
503 def get(self, state, overwrite=False):
504 self._get(state)
504 self._get(state)
505 source, revision, kind = state
505 source, revision, kind = state
506 self._repo.ui.debug("getting subrepo %s\n" % self._path)
506 self._repo.ui.debug("getting subrepo %s\n" % self._path)
507 hg.clean(self._repo, revision, False)
507 hg.clean(self._repo, revision, False)
508
508
509 def merge(self, state):
509 def merge(self, state):
510 self._get(state)
510 self._get(state)
511 cur = self._repo['.']
511 cur = self._repo['.']
512 dst = self._repo[state[1]]
512 dst = self._repo[state[1]]
513 anc = dst.ancestor(cur)
513 anc = dst.ancestor(cur)
514
514
515 def mergefunc():
515 def mergefunc():
516 if anc == cur and dst.branch() == cur.branch():
516 if anc == cur and dst.branch() == cur.branch():
517 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
517 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
518 hg.update(self._repo, state[1])
518 hg.update(self._repo, state[1])
519 elif anc == dst:
519 elif anc == dst:
520 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
520 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
521 else:
521 else:
522 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
522 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
523 hg.merge(self._repo, state[1], remind=False)
523 hg.merge(self._repo, state[1], remind=False)
524
524
525 wctx = self._repo[None]
525 wctx = self._repo[None]
526 if self.dirty():
526 if self.dirty():
527 if anc != dst:
527 if anc != dst:
528 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
528 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
529 mergefunc()
529 mergefunc()
530 else:
530 else:
531 mergefunc()
531 mergefunc()
532 else:
532 else:
533 mergefunc()
533 mergefunc()
534
534
535 def push(self, opts):
535 def push(self, opts):
536 force = opts.get('force')
536 force = opts.get('force')
537 newbranch = opts.get('new_branch')
537 newbranch = opts.get('new_branch')
538 ssh = opts.get('ssh')
538 ssh = opts.get('ssh')
539
539
540 # push subrepos depth-first for coherent ordering
540 # push subrepos depth-first for coherent ordering
541 c = self._repo['']
541 c = self._repo['']
542 subs = c.substate # only repos that are committed
542 subs = c.substate # only repos that are committed
543 for s in sorted(subs):
543 for s in sorted(subs):
544 if c.sub(s).push(opts) == 0:
544 if c.sub(s).push(opts) == 0:
545 return False
545 return False
546
546
547 dsturl = _abssource(self._repo, True)
547 dsturl = _abssource(self._repo, True)
548 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
548 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
549 (subrelpath(self), dsturl))
549 (subrelpath(self), dsturl))
550 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
550 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
551 return self._repo.push(other, force, newbranch=newbranch)
551 return self._repo.push(other, force, newbranch=newbranch)
552
552
553 def outgoing(self, ui, dest, opts):
553 def outgoing(self, ui, dest, opts):
554 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
554 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
555
555
556 def incoming(self, ui, source, opts):
556 def incoming(self, ui, source, opts):
557 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
557 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
558
558
559 def files(self):
559 def files(self):
560 rev = self._state[1]
560 rev = self._state[1]
561 ctx = self._repo[rev]
561 ctx = self._repo[rev]
562 return ctx.manifest()
562 return ctx.manifest()
563
563
564 def filedata(self, name):
564 def filedata(self, name):
565 rev = self._state[1]
565 rev = self._state[1]
566 return self._repo[rev][name].data()
566 return self._repo[rev][name].data()
567
567
568 def fileflags(self, name):
568 def fileflags(self, name):
569 rev = self._state[1]
569 rev = self._state[1]
570 ctx = self._repo[rev]
570 ctx = self._repo[rev]
571 return ctx.flags(name)
571 return ctx.flags(name)
572
572
573 def walk(self, match):
573 def walk(self, match):
574 ctx = self._repo[None]
574 ctx = self._repo[None]
575 return ctx.walk(match)
575 return ctx.walk(match)
576
576
577 def forget(self, ui, match, prefix):
577 def forget(self, ui, match, prefix):
578 return cmdutil.forget(ui, self._repo, match,
578 return cmdutil.forget(ui, self._repo, match,
579 os.path.join(prefix, self._path), True)
579 os.path.join(prefix, self._path), True)
580
580
581 def revert(self, ui, substate, *pats, **opts):
581 def revert(self, ui, substate, *pats, **opts):
582 # reverting a subrepo is a 2 step process:
582 # reverting a subrepo is a 2 step process:
583 # 1. if the no_backup is not set, revert all modified
583 # 1. if the no_backup is not set, revert all modified
584 # files inside the subrepo
584 # files inside the subrepo
585 # 2. update the subrepo to the revision specified in
585 # 2. update the subrepo to the revision specified in
586 # the corresponding substate dictionary
586 # the corresponding substate dictionary
587 ui.status(_('reverting subrepo %s\n') % substate[0])
587 ui.status(_('reverting subrepo %s\n') % substate[0])
588 if not opts.get('no_backup'):
588 if not opts.get('no_backup'):
589 # Revert all files on the subrepo, creating backups
589 # Revert all files on the subrepo, creating backups
590 # Note that this will not recursively revert subrepos
590 # Note that this will not recursively revert subrepos
591 # We could do it if there was a set:subrepos() predicate
591 # We could do it if there was a set:subrepos() predicate
592 opts = opts.copy()
592 opts = opts.copy()
593 opts['date'] = None
593 opts['date'] = None
594 opts['rev'] = substate[1]
594 opts['rev'] = substate[1]
595
595
596 pats = []
596 pats = []
597 if not opts['all']:
597 if not opts['all']:
598 pats = ['set:modified()']
598 pats = ['set:modified()']
599 self.filerevert(ui, *pats, **opts)
599 self.filerevert(ui, *pats, **opts)
600
600
601 # Update the repo to the revision specified in the given substate
601 # Update the repo to the revision specified in the given substate
602 self.get(substate, overwrite=True)
602 self.get(substate, overwrite=True)
603
603
604 def filerevert(self, ui, *pats, **opts):
604 def filerevert(self, ui, *pats, **opts):
605 ctx = self._repo[opts['rev']]
605 ctx = self._repo[opts['rev']]
606 parents = self._repo.dirstate.parents()
606 parents = self._repo.dirstate.parents()
607 if opts['all']:
607 if opts['all']:
608 pats = ['set:modified()']
608 pats = ['set:modified()']
609 else:
609 else:
610 pats = []
610 pats = []
611 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
611 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
612
612
613 class svnsubrepo(abstractsubrepo):
613 class svnsubrepo(abstractsubrepo):
614 def __init__(self, ctx, path, state):
614 def __init__(self, ctx, path, state):
615 self._path = path
615 self._path = path
616 self._state = state
616 self._state = state
617 self._ctx = ctx
617 self._ctx = ctx
618 self._ui = ctx._repo.ui
618 self._ui = ctx._repo.ui
619 self._exe = util.findexe('svn')
619 self._exe = util.findexe('svn')
620 if not self._exe:
620 if not self._exe:
621 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
621 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
622 % self._path)
622 % self._path)
623
623
624 def _svncommand(self, commands, filename='', failok=False):
624 def _svncommand(self, commands, filename='', failok=False):
625 cmd = [self._exe]
625 cmd = [self._exe]
626 extrakw = {}
626 extrakw = {}
627 if not self._ui.interactive():
627 if not self._ui.interactive():
628 # Making stdin be a pipe should prevent svn from behaving
628 # Making stdin be a pipe should prevent svn from behaving
629 # interactively even if we can't pass --non-interactive.
629 # interactively even if we can't pass --non-interactive.
630 extrakw['stdin'] = subprocess.PIPE
630 extrakw['stdin'] = subprocess.PIPE
631 # Starting in svn 1.5 --non-interactive is a global flag
631 # Starting in svn 1.5 --non-interactive is a global flag
632 # instead of being per-command, but we need to support 1.4 so
632 # instead of being per-command, but we need to support 1.4 so
633 # we have to be intelligent about what commands take
633 # we have to be intelligent about what commands take
634 # --non-interactive.
634 # --non-interactive.
635 if commands[0] in ('update', 'checkout', 'commit'):
635 if commands[0] in ('update', 'checkout', 'commit'):
636 cmd.append('--non-interactive')
636 cmd.append('--non-interactive')
637 cmd.extend(commands)
637 cmd.extend(commands)
638 if filename is not None:
638 if filename is not None:
639 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
639 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
640 cmd.append(path)
640 cmd.append(path)
641 env = dict(os.environ)
641 env = dict(os.environ)
642 # Avoid localized output, preserve current locale for everything else.
642 # Avoid localized output, preserve current locale for everything else.
643 env['LC_MESSAGES'] = 'C'
643 env['LC_MESSAGES'] = 'C'
644 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
644 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
645 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
645 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
646 universal_newlines=True, env=env, **extrakw)
646 universal_newlines=True, env=env, **extrakw)
647 stdout, stderr = p.communicate()
647 stdout, stderr = p.communicate()
648 stderr = stderr.strip()
648 stderr = stderr.strip()
649 if not failok:
649 if not failok:
650 if p.returncode:
650 if p.returncode:
651 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
651 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
652 if stderr:
652 if stderr:
653 self._ui.warn(stderr + '\n')
653 self._ui.warn(stderr + '\n')
654 return stdout, stderr
654 return stdout, stderr
655
655
656 @propertycache
656 @propertycache
657 def _svnversion(self):
657 def _svnversion(self):
658 output, err = self._svncommand(['--version'], filename=None)
658 output, err = self._svncommand(['--version'], filename=None)
659 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
659 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
660 if not m:
660 if not m:
661 raise util.Abort(_('cannot retrieve svn tool version'))
661 raise util.Abort(_('cannot retrieve svn tool version'))
662 return (int(m.group(1)), int(m.group(2)))
662 return (int(m.group(1)), int(m.group(2)))
663
663
664 def _wcrevs(self):
664 def _wcrevs(self):
665 # Get the working directory revision as well as the last
665 # Get the working directory revision as well as the last
666 # commit revision so we can compare the subrepo state with
666 # commit revision so we can compare the subrepo state with
667 # both. We used to store the working directory one.
667 # both. We used to store the working directory one.
668 output, err = self._svncommand(['info', '--xml'])
668 output, err = self._svncommand(['info', '--xml'])
669 doc = xml.dom.minidom.parseString(output)
669 doc = xml.dom.minidom.parseString(output)
670 entries = doc.getElementsByTagName('entry')
670 entries = doc.getElementsByTagName('entry')
671 lastrev, rev = '0', '0'
671 lastrev, rev = '0', '0'
672 if entries:
672 if entries:
673 rev = str(entries[0].getAttribute('revision')) or '0'
673 rev = str(entries[0].getAttribute('revision')) or '0'
674 commits = entries[0].getElementsByTagName('commit')
674 commits = entries[0].getElementsByTagName('commit')
675 if commits:
675 if commits:
676 lastrev = str(commits[0].getAttribute('revision')) or '0'
676 lastrev = str(commits[0].getAttribute('revision')) or '0'
677 return (lastrev, rev)
677 return (lastrev, rev)
678
678
679 def _wcrev(self):
679 def _wcrev(self):
680 return self._wcrevs()[0]
680 return self._wcrevs()[0]
681
681
682 def _wcchanged(self):
682 def _wcchanged(self):
683 """Return (changes, extchanges, missing) where changes is True
683 """Return (changes, extchanges, missing) where changes is True
684 if the working directory was changed, extchanges is
684 if the working directory was changed, extchanges is
685 True if any of these changes concern an external entry and missing
685 True if any of these changes concern an external entry and missing
686 is True if any change is a missing entry.
686 is True if any change is a missing entry.
687 """
687 """
688 output, err = self._svncommand(['status', '--xml'])
688 output, err = self._svncommand(['status', '--xml'])
689 externals, changes, missing = [], [], []
689 externals, changes, missing = [], [], []
690 doc = xml.dom.minidom.parseString(output)
690 doc = xml.dom.minidom.parseString(output)
691 for e in doc.getElementsByTagName('entry'):
691 for e in doc.getElementsByTagName('entry'):
692 s = e.getElementsByTagName('wc-status')
692 s = e.getElementsByTagName('wc-status')
693 if not s:
693 if not s:
694 continue
694 continue
695 item = s[0].getAttribute('item')
695 item = s[0].getAttribute('item')
696 props = s[0].getAttribute('props')
696 props = s[0].getAttribute('props')
697 path = e.getAttribute('path')
697 path = e.getAttribute('path')
698 if item == 'external':
698 if item == 'external':
699 externals.append(path)
699 externals.append(path)
700 elif item == 'missing':
700 elif item == 'missing':
701 missing.append(path)
701 missing.append(path)
702 if (item not in ('', 'normal', 'unversioned', 'external')
702 if (item not in ('', 'normal', 'unversioned', 'external')
703 or props not in ('', 'none', 'normal')):
703 or props not in ('', 'none', 'normal')):
704 changes.append(path)
704 changes.append(path)
705 for path in changes:
705 for path in changes:
706 for ext in externals:
706 for ext in externals:
707 if path == ext or path.startswith(ext + os.sep):
707 if path == ext or path.startswith(ext + os.sep):
708 return True, True, bool(missing)
708 return True, True, bool(missing)
709 return bool(changes), False, bool(missing)
709 return bool(changes), False, bool(missing)
710
710
711 def dirty(self, ignoreupdate=False):
711 def dirty(self, ignoreupdate=False):
712 if not self._wcchanged()[0]:
712 if not self._wcchanged()[0]:
713 if self._state[1] in self._wcrevs() or ignoreupdate:
713 if self._state[1] in self._wcrevs() or ignoreupdate:
714 return False
714 return False
715 return True
715 return True
716
716
717 def basestate(self):
717 def basestate(self):
718 return self._wcrev()
718 lastrev, rev = self._wcrevs()
719 if lastrev != rev:
720 # Last committed rev is not the same than rev. We would
721 # like to take lastrev but we do not know if the subrepo
722 # URL exists at lastrev. Test it and fallback to rev it
723 # is not there.
724 try:
725 self._svncommand(['info', '%s@%s' % (self._state[0], lastrev)])
726 return lastrev
727 except error.Abort:
728 pass
729 return rev
719
730
720 def commit(self, text, user, date):
731 def commit(self, text, user, date):
721 # user and date are out of our hands since svn is centralized
732 # user and date are out of our hands since svn is centralized
722 changed, extchanged, missing = self._wcchanged()
733 changed, extchanged, missing = self._wcchanged()
723 if not changed:
734 if not changed:
724 return self._wcrev()
735 return self.basestate()
725 if extchanged:
736 if extchanged:
726 # Do not try to commit externals
737 # Do not try to commit externals
727 raise util.Abort(_('cannot commit svn externals'))
738 raise util.Abort(_('cannot commit svn externals'))
728 if missing:
739 if missing:
729 # svn can commit with missing entries but aborting like hg
740 # svn can commit with missing entries but aborting like hg
730 # seems a better approach.
741 # seems a better approach.
731 raise util.Abort(_('cannot commit missing svn entries'))
742 raise util.Abort(_('cannot commit missing svn entries'))
732 commitinfo, err = self._svncommand(['commit', '-m', text])
743 commitinfo, err = self._svncommand(['commit', '-m', text])
733 self._ui.status(commitinfo)
744 self._ui.status(commitinfo)
734 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
745 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
735 if not newrev:
746 if not newrev:
736 if not commitinfo.strip():
747 if not commitinfo.strip():
737 # Sometimes, our definition of "changed" differs from
748 # Sometimes, our definition of "changed" differs from
738 # svn one. For instance, svn ignores missing files
749 # svn one. For instance, svn ignores missing files
739 # when committing. If there are only missing files, no
750 # when committing. If there are only missing files, no
740 # commit is made, no output and no error code.
751 # commit is made, no output and no error code.
741 raise util.Abort(_('failed to commit svn changes'))
752 raise util.Abort(_('failed to commit svn changes'))
742 raise util.Abort(commitinfo.splitlines()[-1])
753 raise util.Abort(commitinfo.splitlines()[-1])
743 newrev = newrev.groups()[0]
754 newrev = newrev.groups()[0]
744 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
755 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
745 return newrev
756 return newrev
746
757
747 def remove(self):
758 def remove(self):
748 if self.dirty():
759 if self.dirty():
749 self._ui.warn(_('not removing repo %s because '
760 self._ui.warn(_('not removing repo %s because '
750 'it has changes.\n' % self._path))
761 'it has changes.\n' % self._path))
751 return
762 return
752 self._ui.note(_('removing subrepo %s\n') % self._path)
763 self._ui.note(_('removing subrepo %s\n') % self._path)
753
764
754 def onerror(function, path, excinfo):
765 def onerror(function, path, excinfo):
755 if function is not os.remove:
766 if function is not os.remove:
756 raise
767 raise
757 # read-only files cannot be unlinked under Windows
768 # read-only files cannot be unlinked under Windows
758 s = os.stat(path)
769 s = os.stat(path)
759 if (s.st_mode & stat.S_IWRITE) != 0:
770 if (s.st_mode & stat.S_IWRITE) != 0:
760 raise
771 raise
761 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
772 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
762 os.remove(path)
773 os.remove(path)
763
774
764 path = self._ctx._repo.wjoin(self._path)
775 path = self._ctx._repo.wjoin(self._path)
765 shutil.rmtree(path, onerror=onerror)
776 shutil.rmtree(path, onerror=onerror)
766 try:
777 try:
767 os.removedirs(os.path.dirname(path))
778 os.removedirs(os.path.dirname(path))
768 except OSError:
779 except OSError:
769 pass
780 pass
770
781
771 def get(self, state, overwrite=False):
782 def get(self, state, overwrite=False):
772 if overwrite:
783 if overwrite:
773 self._svncommand(['revert', '--recursive'])
784 self._svncommand(['revert', '--recursive'])
774 args = ['checkout']
785 args = ['checkout']
775 if self._svnversion >= (1, 5):
786 if self._svnversion >= (1, 5):
776 args.append('--force')
787 args.append('--force')
777 # The revision must be specified at the end of the URL to properly
788 # The revision must be specified at the end of the URL to properly
778 # update to a directory which has since been deleted and recreated.
789 # update to a directory which has since been deleted and recreated.
779 args.append('%s@%s' % (state[0], state[1]))
790 args.append('%s@%s' % (state[0], state[1]))
780 status, err = self._svncommand(args, failok=True)
791 status, err = self._svncommand(args, failok=True)
781 if not re.search('Checked out revision [0-9]+.', status):
792 if not re.search('Checked out revision [0-9]+.', status):
782 if ('is already a working copy for a different URL' in err
793 if ('is already a working copy for a different URL' in err
783 and (self._wcchanged()[:2] == (False, False))):
794 and (self._wcchanged()[:2] == (False, False))):
784 # obstructed but clean working copy, so just blow it away.
795 # obstructed but clean working copy, so just blow it away.
785 self.remove()
796 self.remove()
786 self.get(state, overwrite=False)
797 self.get(state, overwrite=False)
787 return
798 return
788 raise util.Abort((status or err).splitlines()[-1])
799 raise util.Abort((status or err).splitlines()[-1])
789 self._ui.status(status)
800 self._ui.status(status)
790
801
791 def merge(self, state):
802 def merge(self, state):
792 old = self._state[1]
803 old = self._state[1]
793 new = state[1]
804 new = state[1]
794 if new != self._wcrev():
805 if new != self._wcrev():
795 dirty = old == self._wcrev() or self._wcchanged()[0]
806 dirty = old == self._wcrev() or self._wcchanged()[0]
796 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
807 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
797 self.get(state, False)
808 self.get(state, False)
798
809
799 def push(self, opts):
810 def push(self, opts):
800 # push is a no-op for SVN
811 # push is a no-op for SVN
801 return True
812 return True
802
813
803 def files(self):
814 def files(self):
804 output = self._svncommand(['list', '--recursive', '--xml'])[0]
815 output = self._svncommand(['list', '--recursive', '--xml'])[0]
805 doc = xml.dom.minidom.parseString(output)
816 doc = xml.dom.minidom.parseString(output)
806 paths = []
817 paths = []
807 for e in doc.getElementsByTagName('entry'):
818 for e in doc.getElementsByTagName('entry'):
808 kind = str(e.getAttribute('kind'))
819 kind = str(e.getAttribute('kind'))
809 if kind != 'file':
820 if kind != 'file':
810 continue
821 continue
811 name = ''.join(c.data for c
822 name = ''.join(c.data for c
812 in e.getElementsByTagName('name')[0].childNodes
823 in e.getElementsByTagName('name')[0].childNodes
813 if c.nodeType == c.TEXT_NODE)
824 if c.nodeType == c.TEXT_NODE)
814 paths.append(name)
825 paths.append(name)
815 return paths
826 return paths
816
827
817 def filedata(self, name):
828 def filedata(self, name):
818 return self._svncommand(['cat'], name)[0]
829 return self._svncommand(['cat'], name)[0]
819
830
820
831
821 class gitsubrepo(abstractsubrepo):
832 class gitsubrepo(abstractsubrepo):
822 def __init__(self, ctx, path, state):
833 def __init__(self, ctx, path, state):
823 # TODO add git version check.
834 # TODO add git version check.
824 self._state = state
835 self._state = state
825 self._ctx = ctx
836 self._ctx = ctx
826 self._path = path
837 self._path = path
827 self._relpath = os.path.join(reporelpath(ctx._repo), path)
838 self._relpath = os.path.join(reporelpath(ctx._repo), path)
828 self._abspath = ctx._repo.wjoin(path)
839 self._abspath = ctx._repo.wjoin(path)
829 self._subparent = ctx._repo
840 self._subparent = ctx._repo
830 self._ui = ctx._repo.ui
841 self._ui = ctx._repo.ui
831
842
832 def _gitcommand(self, commands, env=None, stream=False):
843 def _gitcommand(self, commands, env=None, stream=False):
833 return self._gitdir(commands, env=env, stream=stream)[0]
844 return self._gitdir(commands, env=env, stream=stream)[0]
834
845
835 def _gitdir(self, commands, env=None, stream=False):
846 def _gitdir(self, commands, env=None, stream=False):
836 return self._gitnodir(commands, env=env, stream=stream,
847 return self._gitnodir(commands, env=env, stream=stream,
837 cwd=self._abspath)
848 cwd=self._abspath)
838
849
839 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
850 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
840 """Calls the git command
851 """Calls the git command
841
852
842 The methods tries to call the git command. versions previor to 1.6.0
853 The methods tries to call the git command. versions previor to 1.6.0
843 are not supported and very probably fail.
854 are not supported and very probably fail.
844 """
855 """
845 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
856 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
846 # unless ui.quiet is set, print git's stderr,
857 # unless ui.quiet is set, print git's stderr,
847 # which is mostly progress and useful info
858 # which is mostly progress and useful info
848 errpipe = None
859 errpipe = None
849 if self._ui.quiet:
860 if self._ui.quiet:
850 errpipe = open(os.devnull, 'w')
861 errpipe = open(os.devnull, 'w')
851 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
862 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
852 close_fds=util.closefds,
863 close_fds=util.closefds,
853 stdout=subprocess.PIPE, stderr=errpipe)
864 stdout=subprocess.PIPE, stderr=errpipe)
854 if stream:
865 if stream:
855 return p.stdout, None
866 return p.stdout, None
856
867
857 retdata = p.stdout.read().strip()
868 retdata = p.stdout.read().strip()
858 # wait for the child to exit to avoid race condition.
869 # wait for the child to exit to avoid race condition.
859 p.wait()
870 p.wait()
860
871
861 if p.returncode != 0 and p.returncode != 1:
872 if p.returncode != 0 and p.returncode != 1:
862 # there are certain error codes that are ok
873 # there are certain error codes that are ok
863 command = commands[0]
874 command = commands[0]
864 if command in ('cat-file', 'symbolic-ref'):
875 if command in ('cat-file', 'symbolic-ref'):
865 return retdata, p.returncode
876 return retdata, p.returncode
866 # for all others, abort
877 # for all others, abort
867 raise util.Abort('git %s error %d in %s' %
878 raise util.Abort('git %s error %d in %s' %
868 (command, p.returncode, self._relpath))
879 (command, p.returncode, self._relpath))
869
880
870 return retdata, p.returncode
881 return retdata, p.returncode
871
882
872 def _gitmissing(self):
883 def _gitmissing(self):
873 return not os.path.exists(os.path.join(self._abspath, '.git'))
884 return not os.path.exists(os.path.join(self._abspath, '.git'))
874
885
875 def _gitstate(self):
886 def _gitstate(self):
876 return self._gitcommand(['rev-parse', 'HEAD'])
887 return self._gitcommand(['rev-parse', 'HEAD'])
877
888
878 def _gitcurrentbranch(self):
889 def _gitcurrentbranch(self):
879 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
890 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
880 if err:
891 if err:
881 current = None
892 current = None
882 return current
893 return current
883
894
884 def _gitremote(self, remote):
895 def _gitremote(self, remote):
885 out = self._gitcommand(['remote', 'show', '-n', remote])
896 out = self._gitcommand(['remote', 'show', '-n', remote])
886 line = out.split('\n')[1]
897 line = out.split('\n')[1]
887 i = line.index('URL: ') + len('URL: ')
898 i = line.index('URL: ') + len('URL: ')
888 return line[i:]
899 return line[i:]
889
900
890 def _githavelocally(self, revision):
901 def _githavelocally(self, revision):
891 out, code = self._gitdir(['cat-file', '-e', revision])
902 out, code = self._gitdir(['cat-file', '-e', revision])
892 return code == 0
903 return code == 0
893
904
894 def _gitisancestor(self, r1, r2):
905 def _gitisancestor(self, r1, r2):
895 base = self._gitcommand(['merge-base', r1, r2])
906 base = self._gitcommand(['merge-base', r1, r2])
896 return base == r1
907 return base == r1
897
908
898 def _gitisbare(self):
909 def _gitisbare(self):
899 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
910 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
900
911
901 def _gitupdatestat(self):
912 def _gitupdatestat(self):
902 """This must be run before git diff-index.
913 """This must be run before git diff-index.
903 diff-index only looks at changes to file stat;
914 diff-index only looks at changes to file stat;
904 this command looks at file contents and updates the stat."""
915 this command looks at file contents and updates the stat."""
905 self._gitcommand(['update-index', '-q', '--refresh'])
916 self._gitcommand(['update-index', '-q', '--refresh'])
906
917
907 def _gitbranchmap(self):
918 def _gitbranchmap(self):
908 '''returns 2 things:
919 '''returns 2 things:
909 a map from git branch to revision
920 a map from git branch to revision
910 a map from revision to branches'''
921 a map from revision to branches'''
911 branch2rev = {}
922 branch2rev = {}
912 rev2branch = {}
923 rev2branch = {}
913
924
914 out = self._gitcommand(['for-each-ref', '--format',
925 out = self._gitcommand(['for-each-ref', '--format',
915 '%(objectname) %(refname)'])
926 '%(objectname) %(refname)'])
916 for line in out.split('\n'):
927 for line in out.split('\n'):
917 revision, ref = line.split(' ')
928 revision, ref = line.split(' ')
918 if (not ref.startswith('refs/heads/') and
929 if (not ref.startswith('refs/heads/') and
919 not ref.startswith('refs/remotes/')):
930 not ref.startswith('refs/remotes/')):
920 continue
931 continue
921 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
932 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
922 continue # ignore remote/HEAD redirects
933 continue # ignore remote/HEAD redirects
923 branch2rev[ref] = revision
934 branch2rev[ref] = revision
924 rev2branch.setdefault(revision, []).append(ref)
935 rev2branch.setdefault(revision, []).append(ref)
925 return branch2rev, rev2branch
936 return branch2rev, rev2branch
926
937
927 def _gittracking(self, branches):
938 def _gittracking(self, branches):
928 'return map of remote branch to local tracking branch'
939 'return map of remote branch to local tracking branch'
929 # assumes no more than one local tracking branch for each remote
940 # assumes no more than one local tracking branch for each remote
930 tracking = {}
941 tracking = {}
931 for b in branches:
942 for b in branches:
932 if b.startswith('refs/remotes/'):
943 if b.startswith('refs/remotes/'):
933 continue
944 continue
934 bname = b.split('/', 2)[2]
945 bname = b.split('/', 2)[2]
935 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
946 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
936 if remote:
947 if remote:
937 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
948 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
938 tracking['refs/remotes/%s/%s' %
949 tracking['refs/remotes/%s/%s' %
939 (remote, ref.split('/', 2)[2])] = b
950 (remote, ref.split('/', 2)[2])] = b
940 return tracking
951 return tracking
941
952
942 def _abssource(self, source):
953 def _abssource(self, source):
943 if '://' not in source:
954 if '://' not in source:
944 # recognize the scp syntax as an absolute source
955 # recognize the scp syntax as an absolute source
945 colon = source.find(':')
956 colon = source.find(':')
946 if colon != -1 and '/' not in source[:colon]:
957 if colon != -1 and '/' not in source[:colon]:
947 return source
958 return source
948 self._subsource = source
959 self._subsource = source
949 return _abssource(self)
960 return _abssource(self)
950
961
951 def _fetch(self, source, revision):
962 def _fetch(self, source, revision):
952 if self._gitmissing():
963 if self._gitmissing():
953 source = self._abssource(source)
964 source = self._abssource(source)
954 self._ui.status(_('cloning subrepo %s from %s\n') %
965 self._ui.status(_('cloning subrepo %s from %s\n') %
955 (self._relpath, source))
966 (self._relpath, source))
956 self._gitnodir(['clone', source, self._abspath])
967 self._gitnodir(['clone', source, self._abspath])
957 if self._githavelocally(revision):
968 if self._githavelocally(revision):
958 return
969 return
959 self._ui.status(_('pulling subrepo %s from %s\n') %
970 self._ui.status(_('pulling subrepo %s from %s\n') %
960 (self._relpath, self._gitremote('origin')))
971 (self._relpath, self._gitremote('origin')))
961 # try only origin: the originally cloned repo
972 # try only origin: the originally cloned repo
962 self._gitcommand(['fetch'])
973 self._gitcommand(['fetch'])
963 if not self._githavelocally(revision):
974 if not self._githavelocally(revision):
964 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
975 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
965 (revision, self._relpath))
976 (revision, self._relpath))
966
977
967 def dirty(self, ignoreupdate=False):
978 def dirty(self, ignoreupdate=False):
968 if self._gitmissing():
979 if self._gitmissing():
969 return self._state[1] != ''
980 return self._state[1] != ''
970 if self._gitisbare():
981 if self._gitisbare():
971 return True
982 return True
972 if not ignoreupdate and self._state[1] != self._gitstate():
983 if not ignoreupdate and self._state[1] != self._gitstate():
973 # different version checked out
984 # different version checked out
974 return True
985 return True
975 # check for staged changes or modified files; ignore untracked files
986 # check for staged changes or modified files; ignore untracked files
976 self._gitupdatestat()
987 self._gitupdatestat()
977 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
988 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
978 return code == 1
989 return code == 1
979
990
980 def basestate(self):
991 def basestate(self):
981 return self._gitstate()
992 return self._gitstate()
982
993
983 def get(self, state, overwrite=False):
994 def get(self, state, overwrite=False):
984 source, revision, kind = state
995 source, revision, kind = state
985 if not revision:
996 if not revision:
986 self.remove()
997 self.remove()
987 return
998 return
988 self._fetch(source, revision)
999 self._fetch(source, revision)
989 # if the repo was set to be bare, unbare it
1000 # if the repo was set to be bare, unbare it
990 if self._gitisbare():
1001 if self._gitisbare():
991 self._gitcommand(['config', 'core.bare', 'false'])
1002 self._gitcommand(['config', 'core.bare', 'false'])
992 if self._gitstate() == revision:
1003 if self._gitstate() == revision:
993 self._gitcommand(['reset', '--hard', 'HEAD'])
1004 self._gitcommand(['reset', '--hard', 'HEAD'])
994 return
1005 return
995 elif self._gitstate() == revision:
1006 elif self._gitstate() == revision:
996 if overwrite:
1007 if overwrite:
997 # first reset the index to unmark new files for commit, because
1008 # first reset the index to unmark new files for commit, because
998 # reset --hard will otherwise throw away files added for commit,
1009 # reset --hard will otherwise throw away files added for commit,
999 # not just unmark them.
1010 # not just unmark them.
1000 self._gitcommand(['reset', 'HEAD'])
1011 self._gitcommand(['reset', 'HEAD'])
1001 self._gitcommand(['reset', '--hard', 'HEAD'])
1012 self._gitcommand(['reset', '--hard', 'HEAD'])
1002 return
1013 return
1003 branch2rev, rev2branch = self._gitbranchmap()
1014 branch2rev, rev2branch = self._gitbranchmap()
1004
1015
1005 def checkout(args):
1016 def checkout(args):
1006 cmd = ['checkout']
1017 cmd = ['checkout']
1007 if overwrite:
1018 if overwrite:
1008 # first reset the index to unmark new files for commit, because
1019 # first reset the index to unmark new files for commit, because
1009 # the -f option will otherwise throw away files added for
1020 # the -f option will otherwise throw away files added for
1010 # commit, not just unmark them.
1021 # commit, not just unmark them.
1011 self._gitcommand(['reset', 'HEAD'])
1022 self._gitcommand(['reset', 'HEAD'])
1012 cmd.append('-f')
1023 cmd.append('-f')
1013 self._gitcommand(cmd + args)
1024 self._gitcommand(cmd + args)
1014
1025
1015 def rawcheckout():
1026 def rawcheckout():
1016 # no branch to checkout, check it out with no branch
1027 # no branch to checkout, check it out with no branch
1017 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1028 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1018 self._relpath)
1029 self._relpath)
1019 self._ui.warn(_('check out a git branch if you intend '
1030 self._ui.warn(_('check out a git branch if you intend '
1020 'to make changes\n'))
1031 'to make changes\n'))
1021 checkout(['-q', revision])
1032 checkout(['-q', revision])
1022
1033
1023 if revision not in rev2branch:
1034 if revision not in rev2branch:
1024 rawcheckout()
1035 rawcheckout()
1025 return
1036 return
1026 branches = rev2branch[revision]
1037 branches = rev2branch[revision]
1027 firstlocalbranch = None
1038 firstlocalbranch = None
1028 for b in branches:
1039 for b in branches:
1029 if b == 'refs/heads/master':
1040 if b == 'refs/heads/master':
1030 # master trumps all other branches
1041 # master trumps all other branches
1031 checkout(['refs/heads/master'])
1042 checkout(['refs/heads/master'])
1032 return
1043 return
1033 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1044 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1034 firstlocalbranch = b
1045 firstlocalbranch = b
1035 if firstlocalbranch:
1046 if firstlocalbranch:
1036 checkout([firstlocalbranch])
1047 checkout([firstlocalbranch])
1037 return
1048 return
1038
1049
1039 tracking = self._gittracking(branch2rev.keys())
1050 tracking = self._gittracking(branch2rev.keys())
1040 # choose a remote branch already tracked if possible
1051 # choose a remote branch already tracked if possible
1041 remote = branches[0]
1052 remote = branches[0]
1042 if remote not in tracking:
1053 if remote not in tracking:
1043 for b in branches:
1054 for b in branches:
1044 if b in tracking:
1055 if b in tracking:
1045 remote = b
1056 remote = b
1046 break
1057 break
1047
1058
1048 if remote not in tracking:
1059 if remote not in tracking:
1049 # create a new local tracking branch
1060 # create a new local tracking branch
1050 local = remote.split('/', 2)[2]
1061 local = remote.split('/', 2)[2]
1051 checkout(['-b', local, remote])
1062 checkout(['-b', local, remote])
1052 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1063 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1053 # When updating to a tracked remote branch,
1064 # When updating to a tracked remote branch,
1054 # if the local tracking branch is downstream of it,
1065 # if the local tracking branch is downstream of it,
1055 # a normal `git pull` would have performed a "fast-forward merge"
1066 # a normal `git pull` would have performed a "fast-forward merge"
1056 # which is equivalent to updating the local branch to the remote.
1067 # which is equivalent to updating the local branch to the remote.
1057 # Since we are only looking at branching at update, we need to
1068 # Since we are only looking at branching at update, we need to
1058 # detect this situation and perform this action lazily.
1069 # detect this situation and perform this action lazily.
1059 if tracking[remote] != self._gitcurrentbranch():
1070 if tracking[remote] != self._gitcurrentbranch():
1060 checkout([tracking[remote]])
1071 checkout([tracking[remote]])
1061 self._gitcommand(['merge', '--ff', remote])
1072 self._gitcommand(['merge', '--ff', remote])
1062 else:
1073 else:
1063 # a real merge would be required, just checkout the revision
1074 # a real merge would be required, just checkout the revision
1064 rawcheckout()
1075 rawcheckout()
1065
1076
1066 def commit(self, text, user, date):
1077 def commit(self, text, user, date):
1067 if self._gitmissing():
1078 if self._gitmissing():
1068 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1079 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1069 cmd = ['commit', '-a', '-m', text]
1080 cmd = ['commit', '-a', '-m', text]
1070 env = os.environ.copy()
1081 env = os.environ.copy()
1071 if user:
1082 if user:
1072 cmd += ['--author', user]
1083 cmd += ['--author', user]
1073 if date:
1084 if date:
1074 # git's date parser silently ignores when seconds < 1e9
1085 # git's date parser silently ignores when seconds < 1e9
1075 # convert to ISO8601
1086 # convert to ISO8601
1076 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1087 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1077 '%Y-%m-%dT%H:%M:%S %1%2')
1088 '%Y-%m-%dT%H:%M:%S %1%2')
1078 self._gitcommand(cmd, env=env)
1089 self._gitcommand(cmd, env=env)
1079 # make sure commit works otherwise HEAD might not exist under certain
1090 # make sure commit works otherwise HEAD might not exist under certain
1080 # circumstances
1091 # circumstances
1081 return self._gitstate()
1092 return self._gitstate()
1082
1093
1083 def merge(self, state):
1094 def merge(self, state):
1084 source, revision, kind = state
1095 source, revision, kind = state
1085 self._fetch(source, revision)
1096 self._fetch(source, revision)
1086 base = self._gitcommand(['merge-base', revision, self._state[1]])
1097 base = self._gitcommand(['merge-base', revision, self._state[1]])
1087 self._gitupdatestat()
1098 self._gitupdatestat()
1088 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1099 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1089
1100
1090 def mergefunc():
1101 def mergefunc():
1091 if base == revision:
1102 if base == revision:
1092 self.get(state) # fast forward merge
1103 self.get(state) # fast forward merge
1093 elif base != self._state[1]:
1104 elif base != self._state[1]:
1094 self._gitcommand(['merge', '--no-commit', revision])
1105 self._gitcommand(['merge', '--no-commit', revision])
1095
1106
1096 if self.dirty():
1107 if self.dirty():
1097 if self._gitstate() != revision:
1108 if self._gitstate() != revision:
1098 dirty = self._gitstate() == self._state[1] or code != 0
1109 dirty = self._gitstate() == self._state[1] or code != 0
1099 if _updateprompt(self._ui, self, dirty,
1110 if _updateprompt(self._ui, self, dirty,
1100 self._state[1][:7], revision[:7]):
1111 self._state[1][:7], revision[:7]):
1101 mergefunc()
1112 mergefunc()
1102 else:
1113 else:
1103 mergefunc()
1114 mergefunc()
1104
1115
1105 def push(self, opts):
1116 def push(self, opts):
1106 force = opts.get('force')
1117 force = opts.get('force')
1107
1118
1108 if not self._state[1]:
1119 if not self._state[1]:
1109 return True
1120 return True
1110 if self._gitmissing():
1121 if self._gitmissing():
1111 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1122 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1112 # if a branch in origin contains the revision, nothing to do
1123 # if a branch in origin contains the revision, nothing to do
1113 branch2rev, rev2branch = self._gitbranchmap()
1124 branch2rev, rev2branch = self._gitbranchmap()
1114 if self._state[1] in rev2branch:
1125 if self._state[1] in rev2branch:
1115 for b in rev2branch[self._state[1]]:
1126 for b in rev2branch[self._state[1]]:
1116 if b.startswith('refs/remotes/origin/'):
1127 if b.startswith('refs/remotes/origin/'):
1117 return True
1128 return True
1118 for b, revision in branch2rev.iteritems():
1129 for b, revision in branch2rev.iteritems():
1119 if b.startswith('refs/remotes/origin/'):
1130 if b.startswith('refs/remotes/origin/'):
1120 if self._gitisancestor(self._state[1], revision):
1131 if self._gitisancestor(self._state[1], revision):
1121 return True
1132 return True
1122 # otherwise, try to push the currently checked out branch
1133 # otherwise, try to push the currently checked out branch
1123 cmd = ['push']
1134 cmd = ['push']
1124 if force:
1135 if force:
1125 cmd.append('--force')
1136 cmd.append('--force')
1126
1137
1127 current = self._gitcurrentbranch()
1138 current = self._gitcurrentbranch()
1128 if current:
1139 if current:
1129 # determine if the current branch is even useful
1140 # determine if the current branch is even useful
1130 if not self._gitisancestor(self._state[1], current):
1141 if not self._gitisancestor(self._state[1], current):
1131 self._ui.warn(_('unrelated git branch checked out '
1142 self._ui.warn(_('unrelated git branch checked out '
1132 'in subrepo %s\n') % self._relpath)
1143 'in subrepo %s\n') % self._relpath)
1133 return False
1144 return False
1134 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1145 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1135 (current.split('/', 2)[2], self._relpath))
1146 (current.split('/', 2)[2], self._relpath))
1136 self._gitcommand(cmd + ['origin', current])
1147 self._gitcommand(cmd + ['origin', current])
1137 return True
1148 return True
1138 else:
1149 else:
1139 self._ui.warn(_('no branch checked out in subrepo %s\n'
1150 self._ui.warn(_('no branch checked out in subrepo %s\n'
1140 'cannot push revision %s') %
1151 'cannot push revision %s') %
1141 (self._relpath, self._state[1]))
1152 (self._relpath, self._state[1]))
1142 return False
1153 return False
1143
1154
1144 def remove(self):
1155 def remove(self):
1145 if self._gitmissing():
1156 if self._gitmissing():
1146 return
1157 return
1147 if self.dirty():
1158 if self.dirty():
1148 self._ui.warn(_('not removing repo %s because '
1159 self._ui.warn(_('not removing repo %s because '
1149 'it has changes.\n') % self._relpath)
1160 'it has changes.\n') % self._relpath)
1150 return
1161 return
1151 # we can't fully delete the repository as it may contain
1162 # we can't fully delete the repository as it may contain
1152 # local-only history
1163 # local-only history
1153 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1164 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1154 self._gitcommand(['config', 'core.bare', 'true'])
1165 self._gitcommand(['config', 'core.bare', 'true'])
1155 for f in os.listdir(self._abspath):
1166 for f in os.listdir(self._abspath):
1156 if f == '.git':
1167 if f == '.git':
1157 continue
1168 continue
1158 path = os.path.join(self._abspath, f)
1169 path = os.path.join(self._abspath, f)
1159 if os.path.isdir(path) and not os.path.islink(path):
1170 if os.path.isdir(path) and not os.path.islink(path):
1160 shutil.rmtree(path)
1171 shutil.rmtree(path)
1161 else:
1172 else:
1162 os.remove(path)
1173 os.remove(path)
1163
1174
1164 def archive(self, ui, archiver, prefix):
1175 def archive(self, ui, archiver, prefix):
1165 source, revision = self._state
1176 source, revision = self._state
1166 if not revision:
1177 if not revision:
1167 return
1178 return
1168 self._fetch(source, revision)
1179 self._fetch(source, revision)
1169
1180
1170 # Parse git's native archive command.
1181 # Parse git's native archive command.
1171 # This should be much faster than manually traversing the trees
1182 # This should be much faster than manually traversing the trees
1172 # and objects with many subprocess calls.
1183 # and objects with many subprocess calls.
1173 tarstream = self._gitcommand(['archive', revision], stream=True)
1184 tarstream = self._gitcommand(['archive', revision], stream=True)
1174 tar = tarfile.open(fileobj=tarstream, mode='r|')
1185 tar = tarfile.open(fileobj=tarstream, mode='r|')
1175 relpath = subrelpath(self)
1186 relpath = subrelpath(self)
1176 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1187 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1177 for i, info in enumerate(tar):
1188 for i, info in enumerate(tar):
1178 if info.isdir():
1189 if info.isdir():
1179 continue
1190 continue
1180 if info.issym():
1191 if info.issym():
1181 data = info.linkname
1192 data = info.linkname
1182 else:
1193 else:
1183 data = tar.extractfile(info).read()
1194 data = tar.extractfile(info).read()
1184 archiver.addfile(os.path.join(prefix, self._path, info.name),
1195 archiver.addfile(os.path.join(prefix, self._path, info.name),
1185 info.mode, info.issym(), data)
1196 info.mode, info.issym(), data)
1186 ui.progress(_('archiving (%s)') % relpath, i + 1,
1197 ui.progress(_('archiving (%s)') % relpath, i + 1,
1187 unit=_('files'))
1198 unit=_('files'))
1188 ui.progress(_('archiving (%s)') % relpath, None)
1199 ui.progress(_('archiving (%s)') % relpath, None)
1189
1200
1190
1201
1191 def status(self, rev2, **opts):
1202 def status(self, rev2, **opts):
1192 rev1 = self._state[1]
1203 rev1 = self._state[1]
1193 if self._gitmissing() or not rev1:
1204 if self._gitmissing() or not rev1:
1194 # if the repo is missing, return no results
1205 # if the repo is missing, return no results
1195 return [], [], [], [], [], [], []
1206 return [], [], [], [], [], [], []
1196 modified, added, removed = [], [], []
1207 modified, added, removed = [], [], []
1197 self._gitupdatestat()
1208 self._gitupdatestat()
1198 if rev2:
1209 if rev2:
1199 command = ['diff-tree', rev1, rev2]
1210 command = ['diff-tree', rev1, rev2]
1200 else:
1211 else:
1201 command = ['diff-index', rev1]
1212 command = ['diff-index', rev1]
1202 out = self._gitcommand(command)
1213 out = self._gitcommand(command)
1203 for line in out.split('\n'):
1214 for line in out.split('\n'):
1204 tab = line.find('\t')
1215 tab = line.find('\t')
1205 if tab == -1:
1216 if tab == -1:
1206 continue
1217 continue
1207 status, f = line[tab - 1], line[tab + 1:]
1218 status, f = line[tab - 1], line[tab + 1:]
1208 if status == 'M':
1219 if status == 'M':
1209 modified.append(f)
1220 modified.append(f)
1210 elif status == 'A':
1221 elif status == 'A':
1211 added.append(f)
1222 added.append(f)
1212 elif status == 'D':
1223 elif status == 'D':
1213 removed.append(f)
1224 removed.append(f)
1214
1225
1215 deleted = unknown = ignored = clean = []
1226 deleted = unknown = ignored = clean = []
1216 return modified, added, removed, deleted, unknown, ignored, clean
1227 return modified, added, removed, deleted, unknown, ignored, clean
1217
1228
1218 types = {
1229 types = {
1219 'hg': hgsubrepo,
1230 'hg': hgsubrepo,
1220 'svn': svnsubrepo,
1231 'svn': svnsubrepo,
1221 'git': gitsubrepo,
1232 'git': gitsubrepo,
1222 }
1233 }
@@ -1,566 +1,605 b''
1 $ "$TESTDIR/hghave" svn15 || exit 80
1 $ "$TESTDIR/hghave" svn15 || exit 80
2
2
3 $ fix_path()
3 $ fix_path()
4 > {
4 > {
5 > tr '\\' /
5 > tr '\\' /
6 > }
6 > }
7
7
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
9 don't. Handle that.
9 don't. Handle that.
10
10
11 $ escapedwd=`pwd | fix_path`
11 $ escapedwd=`pwd | fix_path`
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
14
14
15 create subversion repo
15 create subversion repo
16
16
17 $ SVNREPO="file://$escapedwd/svn-repo"
17 $ SVNREPO="file://$escapedwd/svn-repo"
18 $ WCROOT="`pwd`/svn-wc"
18 $ WCROOT="`pwd`/svn-wc"
19 $ svnadmin create svn-repo
19 $ svnadmin create svn-repo
20 $ svn co "$SVNREPO" svn-wc
20 $ svn co "$SVNREPO" svn-wc
21 Checked out revision 0.
21 Checked out revision 0.
22 $ cd svn-wc
22 $ cd svn-wc
23 $ mkdir src
23 $ mkdir src
24 $ echo alpha > src/alpha
24 $ echo alpha > src/alpha
25 $ svn add src
25 $ svn add src
26 A src
26 A src
27 A src/alpha
27 A src/alpha
28 $ mkdir externals
28 $ mkdir externals
29 $ echo other > externals/other
29 $ echo other > externals/other
30 $ svn add externals
30 $ svn add externals
31 A externals
31 A externals
32 A externals/other
32 A externals/other
33 $ svn ci -m 'Add alpha'
33 $ svn ci -m 'Add alpha'
34 Adding externals
34 Adding externals
35 Adding externals/other
35 Adding externals/other
36 Adding src
36 Adding src
37 Adding src/alpha
37 Adding src/alpha
38 Transmitting file data ..
38 Transmitting file data ..
39 Committed revision 1.
39 Committed revision 1.
40 $ svn up -q
40 $ svn up -q
41 $ echo "externals -r1 $SVNREPO/externals" > extdef
41 $ echo "externals -r1 $SVNREPO/externals" > extdef
42 $ svn propset -F extdef svn:externals src
42 $ svn propset -F extdef svn:externals src
43 property 'svn:externals' set on 'src'
43 property 'svn:externals' set on 'src'
44 $ svn ci -m 'Setting externals'
44 $ svn ci -m 'Setting externals'
45 Sending src
45 Sending src
46
46
47 Committed revision 2.
47 Committed revision 2.
48 $ cd ..
48 $ cd ..
49
49
50 create hg repo
50 create hg repo
51
51
52 $ mkdir sub
52 $ mkdir sub
53 $ cd sub
53 $ cd sub
54 $ hg init t
54 $ hg init t
55 $ cd t
55 $ cd t
56
56
57 first revision, no sub
57 first revision, no sub
58
58
59 $ echo a > a
59 $ echo a > a
60 $ hg ci -Am0
60 $ hg ci -Am0
61 adding a
61 adding a
62
62
63 add first svn sub with leading whitespaces
63 add first svn sub with leading whitespaces
64
64
65 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
65 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
66 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
66 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
67 $ svn co --quiet "$SVNREPO"/src s
67 $ svn co --quiet "$SVNREPO"/src s
68 $ mkdir subdir
68 $ mkdir subdir
69 $ svn co --quiet "$SVNREPO"/src subdir/s
69 $ svn co --quiet "$SVNREPO"/src subdir/s
70 $ hg add .hgsub
70 $ hg add .hgsub
71 $ hg ci -m1
71 $ hg ci -m1
72
72
73 make sure we avoid empty commits (issue2445)
73 make sure we avoid empty commits (issue2445)
74
74
75 $ hg sum
75 $ hg sum
76 parent: 1:* tip (glob)
76 parent: 1:* tip (glob)
77 1
77 1
78 branch: default
78 branch: default
79 commit: (clean)
79 commit: (clean)
80 update: (current)
80 update: (current)
81 $ hg ci -moops
81 $ hg ci -moops
82 nothing changed
82 nothing changed
83 [1]
83 [1]
84
84
85 debugsub
85 debugsub
86
86
87 $ hg debugsub
87 $ hg debugsub
88 path s
88 path s
89 source file://*/svn-repo/src (glob)
89 source file://*/svn-repo/src (glob)
90 revision 2
90 revision 2
91 path subdir/s
91 path subdir/s
92 source file://*/svn-repo/src (glob)
92 source file://*/svn-repo/src (glob)
93 revision 2
93 revision 2
94
94
95 change file in svn and hg, commit
95 change file in svn and hg, commit
96
96
97 $ echo a >> a
97 $ echo a >> a
98 $ echo alpha >> s/alpha
98 $ echo alpha >> s/alpha
99 $ hg sum
99 $ hg sum
100 parent: 1:* tip (glob)
100 parent: 1:* tip (glob)
101 1
101 1
102 branch: default
102 branch: default
103 commit: 1 modified, 1 subrepos
103 commit: 1 modified, 1 subrepos
104 update: (current)
104 update: (current)
105 $ hg commit --subrepos -m 'Message!' | grep -v Updating
105 $ hg commit --subrepos -m 'Message!' | grep -v Updating
106 committing subrepository s
106 committing subrepository s
107 Sending*s/alpha (glob)
107 Sending*s/alpha (glob)
108 Transmitting file data .
108 Transmitting file data .
109 Committed revision 3.
109 Committed revision 3.
110
110
111 Fetching external item into '*s/externals'* (glob)
111 Fetching external item into '*s/externals'* (glob)
112 External at revision 1.
112 External at revision 1.
113
113
114 At revision 3.
114 At revision 3.
115 $ hg debugsub
115 $ hg debugsub
116 path s
116 path s
117 source file://*/svn-repo/src (glob)
117 source file://*/svn-repo/src (glob)
118 revision 3
118 revision 3
119 path subdir/s
119 path subdir/s
120 source file://*/svn-repo/src (glob)
120 source file://*/svn-repo/src (glob)
121 revision 2
121 revision 2
122
122
123 missing svn file, commit should fail
123 missing svn file, commit should fail
124
124
125 $ rm s/alpha
125 $ rm s/alpha
126 $ hg commit --subrepos -m 'abort on missing file'
126 $ hg commit --subrepos -m 'abort on missing file'
127 committing subrepository s
127 committing subrepository s
128 abort: cannot commit missing svn entries
128 abort: cannot commit missing svn entries
129 [255]
129 [255]
130 $ svn revert s/alpha > /dev/null
130 $ svn revert s/alpha > /dev/null
131
131
132 add an unrelated revision in svn and update the subrepo to without
132 add an unrelated revision in svn and update the subrepo to without
133 bringing any changes.
133 bringing any changes.
134
134
135 $ svn mkdir "$SVNREPO/unrelated" -m 'create unrelated'
135 $ svn mkdir "$SVNREPO/unrelated" -m 'create unrelated'
136
136
137 Committed revision 4.
137 Committed revision 4.
138 $ svn up -q s
138 $ svn up -q s
139 $ hg sum
139 $ hg sum
140 parent: 2:* tip (glob)
140 parent: 2:* tip (glob)
141 Message!
141 Message!
142 branch: default
142 branch: default
143 commit: (clean)
143 commit: (clean)
144 update: (current)
144 update: (current)
145
145
146 $ echo a > s/a
146 $ echo a > s/a
147
147
148 should be empty despite change to s/a
148 should be empty despite change to s/a
149
149
150 $ hg st
150 $ hg st
151
151
152 add a commit from svn
152 add a commit from svn
153
153
154 $ cd "$WCROOT"/src
154 $ cd "$WCROOT"/src
155 $ svn up -q
155 $ svn up -q
156 $ echo xyz >> alpha
156 $ echo xyz >> alpha
157 $ svn propset svn:mime-type 'text/xml' alpha
157 $ svn propset svn:mime-type 'text/xml' alpha
158 property 'svn:mime-type' set on 'alpha'
158 property 'svn:mime-type' set on 'alpha'
159 $ svn ci -m 'amend a from svn'
159 $ svn ci -m 'amend a from svn'
160 Sending *alpha (glob)
160 Sending *alpha (glob)
161 Transmitting file data .
161 Transmitting file data .
162 Committed revision 5.
162 Committed revision 5.
163 $ cd ../../sub/t
163 $ cd ../../sub/t
164
164
165 this commit from hg will fail
165 this commit from hg will fail
166
166
167 $ echo zzz >> s/alpha
167 $ echo zzz >> s/alpha
168 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
168 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
169 committing subrepository s
169 committing subrepository s
170 abort: svn:*Commit failed (details follow): (glob)
170 abort: svn:*Commit failed (details follow): (glob)
171 [255]
171 [255]
172 $ svn revert -q s/alpha
172 $ svn revert -q s/alpha
173
173
174 this commit fails because of meta changes
174 this commit fails because of meta changes
175
175
176 $ svn propset svn:mime-type 'text/html' s/alpha
176 $ svn propset svn:mime-type 'text/html' s/alpha
177 property 'svn:mime-type' set on 's/alpha'
177 property 'svn:mime-type' set on 's/alpha'
178 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
178 $ (hg ci --subrepos -m 'amend alpha from hg' 2>&1; echo "[$?]") | grep -vi 'out of date'
179 committing subrepository s
179 committing subrepository s
180 abort: svn:*Commit failed (details follow): (glob)
180 abort: svn:*Commit failed (details follow): (glob)
181 [255]
181 [255]
182 $ svn revert -q s/alpha
182 $ svn revert -q s/alpha
183
183
184 this commit fails because of externals changes
184 this commit fails because of externals changes
185
185
186 $ echo zzz > s/externals/other
186 $ echo zzz > s/externals/other
187 $ hg ci --subrepos -m 'amend externals from hg'
187 $ hg ci --subrepos -m 'amend externals from hg'
188 committing subrepository s
188 committing subrepository s
189 abort: cannot commit svn externals
189 abort: cannot commit svn externals
190 [255]
190 [255]
191 $ hg diff --subrepos -r 1:2 | grep -v diff
191 $ hg diff --subrepos -r 1:2 | grep -v diff
192 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
192 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
193 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
193 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
194 @@ -1,2 +1,2 @@
194 @@ -1,2 +1,2 @@
195 -2 s
195 -2 s
196 +3 s
196 +3 s
197 2 subdir/s
197 2 subdir/s
198 --- a/a Thu Jan 01 00:00:00 1970 +0000
198 --- a/a Thu Jan 01 00:00:00 1970 +0000
199 +++ b/a Thu Jan 01 00:00:00 1970 +0000
199 +++ b/a Thu Jan 01 00:00:00 1970 +0000
200 @@ -1,1 +1,2 @@
200 @@ -1,1 +1,2 @@
201 a
201 a
202 +a
202 +a
203 $ svn revert -q s/externals/other
203 $ svn revert -q s/externals/other
204
204
205 this commit fails because of externals meta changes
205 this commit fails because of externals meta changes
206
206
207 $ svn propset svn:mime-type 'text/html' s/externals/other
207 $ svn propset svn:mime-type 'text/html' s/externals/other
208 property 'svn:mime-type' set on 's/externals/other'
208 property 'svn:mime-type' set on 's/externals/other'
209 $ hg ci --subrepos -m 'amend externals from hg'
209 $ hg ci --subrepos -m 'amend externals from hg'
210 committing subrepository s
210 committing subrepository s
211 abort: cannot commit svn externals
211 abort: cannot commit svn externals
212 [255]
212 [255]
213 $ svn revert -q s/externals/other
213 $ svn revert -q s/externals/other
214
214
215 clone
215 clone
216
216
217 $ cd ..
217 $ cd ..
218 $ hg clone t tc | fix_path
218 $ hg clone t tc | fix_path
219 updating to branch default
219 updating to branch default
220 A tc/s/alpha
220 A tc/s/alpha
221 U tc/s
221 U tc/s
222
222
223 Fetching external item into 'tc/s/externals'* (glob)
223 Fetching external item into 'tc/s/externals'* (glob)
224 A tc/s/externals/other
224 A tc/s/externals/other
225 Checked out external at revision 1.
225 Checked out external at revision 1.
226
226
227 Checked out revision 3.
227 Checked out revision 3.
228 A tc/subdir/s/alpha
228 A tc/subdir/s/alpha
229 U tc/subdir/s
229 U tc/subdir/s
230
230
231 Fetching external item into 'tc/subdir/s/externals'* (glob)
231 Fetching external item into 'tc/subdir/s/externals'* (glob)
232 A tc/subdir/s/externals/other
232 A tc/subdir/s/externals/other
233 Checked out external at revision 1.
233 Checked out external at revision 1.
234
234
235 Checked out revision 2.
235 Checked out revision 2.
236 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 $ cd tc
237 $ cd tc
238
238
239 debugsub in clone
239 debugsub in clone
240
240
241 $ hg debugsub
241 $ hg debugsub
242 path s
242 path s
243 source file://*/svn-repo/src (glob)
243 source file://*/svn-repo/src (glob)
244 revision 3
244 revision 3
245 path subdir/s
245 path subdir/s
246 source file://*/svn-repo/src (glob)
246 source file://*/svn-repo/src (glob)
247 revision 2
247 revision 2
248
248
249 verify subrepo is contained within the repo directory
249 verify subrepo is contained within the repo directory
250
250
251 $ python -c "import os.path; print os.path.exists('s')"
251 $ python -c "import os.path; print os.path.exists('s')"
252 True
252 True
253
253
254 update to nullrev (must delete the subrepo)
254 update to nullrev (must delete the subrepo)
255
255
256 $ hg up null
256 $ hg up null
257 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
257 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
258 $ ls
258 $ ls
259
259
260 Check hg update --clean
260 Check hg update --clean
261 $ cd $TESTTMP/sub/t
261 $ cd $TESTTMP/sub/t
262 $ cd s
262 $ cd s
263 $ echo c0 > alpha
263 $ echo c0 > alpha
264 $ echo c1 > f1
264 $ echo c1 > f1
265 $ echo c1 > f2
265 $ echo c1 > f2
266 $ svn add f1 -q
266 $ svn add f1 -q
267 $ svn status | sort
267 $ svn status | sort
268
268
269 ? * a (glob)
269 ? * a (glob)
270 ? * f2 (glob)
270 ? * f2 (glob)
271 A * f1 (glob)
271 A * f1 (glob)
272 M * alpha (glob)
272 M * alpha (glob)
273 Performing status on external item at 'externals'* (glob)
273 Performing status on external item at 'externals'* (glob)
274 X * externals (glob)
274 X * externals (glob)
275 $ cd ../..
275 $ cd ../..
276 $ hg -R t update -C
276 $ hg -R t update -C
277
277
278 Fetching external item into 't/s/externals'* (glob)
278 Fetching external item into 't/s/externals'* (glob)
279 Checked out external at revision 1.
279 Checked out external at revision 1.
280
280
281 Checked out revision 3.
281 Checked out revision 3.
282 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
282 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 $ cd t/s
283 $ cd t/s
284 $ svn status | sort
284 $ svn status | sort
285
285
286 ? * a (glob)
286 ? * a (glob)
287 ? * f1 (glob)
287 ? * f1 (glob)
288 ? * f2 (glob)
288 ? * f2 (glob)
289 Performing status on external item at 'externals'* (glob)
289 Performing status on external item at 'externals'* (glob)
290 X * externals (glob)
290 X * externals (glob)
291
291
292 Sticky subrepositories, no changes
292 Sticky subrepositories, no changes
293 $ cd $TESTTMP/sub/t
293 $ cd $TESTTMP/sub/t
294 $ hg id -n
294 $ hg id -n
295 2
295 2
296 $ cd s
296 $ cd s
297 $ svnversion
297 $ svnversion
298 3
298 3
299 $ cd ..
299 $ cd ..
300 $ hg update 1
300 $ hg update 1
301 U *s/alpha (glob)
301 U *s/alpha (glob)
302
302
303 Fetching external item into '*s/externals'* (glob)
303 Fetching external item into '*s/externals'* (glob)
304 Checked out external at revision 1.
304 Checked out external at revision 1.
305
305
306 Checked out revision 2.
306 Checked out revision 2.
307 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
307 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
308 $ hg id -n
308 $ hg id -n
309 1
309 1
310 $ cd s
310 $ cd s
311 $ svnversion
311 $ svnversion
312 2
312 2
313 $ cd ..
313 $ cd ..
314
314
315 Sticky subrepositorys, file changes
315 Sticky subrepositorys, file changes
316 $ touch s/f1
316 $ touch s/f1
317 $ cd s
317 $ cd s
318 $ svn add f1
318 $ svn add f1
319 A f1
319 A f1
320 $ cd ..
320 $ cd ..
321 $ hg id -n
321 $ hg id -n
322 1
322 1
323 $ cd s
323 $ cd s
324 $ svnversion
324 $ svnversion
325 2M
325 2M
326 $ cd ..
326 $ cd ..
327 $ hg update tip
327 $ hg update tip
328 subrepository sources for s differ
328 subrepository sources for s differ
329 use (l)ocal source (2) or (r)emote source (3)?
329 use (l)ocal source (2) or (r)emote source (3)?
330 l
330 l
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 $ hg id -n
332 $ hg id -n
333 2+
333 2+
334 $ cd s
334 $ cd s
335 $ svnversion
335 $ svnversion
336 2M
336 2M
337 $ cd ..
337 $ cd ..
338 $ hg update --clean tip
338 $ hg update --clean tip
339 U *s/alpha (glob)
339 U *s/alpha (glob)
340
340
341 Fetching external item into '*s/externals'* (glob)
341 Fetching external item into '*s/externals'* (glob)
342 Checked out external at revision 1.
342 Checked out external at revision 1.
343
343
344 Checked out revision 3.
344 Checked out revision 3.
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346
346
347 Sticky subrepository, revision updates
347 Sticky subrepository, revision updates
348 $ hg id -n
348 $ hg id -n
349 2
349 2
350 $ cd s
350 $ cd s
351 $ svnversion
351 $ svnversion
352 3
352 3
353 $ cd ..
353 $ cd ..
354 $ cd s
354 $ cd s
355 $ svn update -qr 1
355 $ svn update -qr 1
356 $ cd ..
356 $ cd ..
357 $ hg update 1
357 $ hg update 1
358 subrepository sources for s differ (in checked out version)
358 subrepository sources for s differ (in checked out version)
359 use (l)ocal source (1) or (r)emote source (2)?
359 use (l)ocal source (1) or (r)emote source (2)?
360 l
360 l
361 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
361 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
362 $ hg id -n
362 $ hg id -n
363 1+
363 1+
364 $ cd s
364 $ cd s
365 $ svnversion
365 $ svnversion
366 1
366 1
367 $ cd ..
367 $ cd ..
368
368
369 Sticky subrepository, file changes and revision updates
369 Sticky subrepository, file changes and revision updates
370 $ touch s/f1
370 $ touch s/f1
371 $ cd s
371 $ cd s
372 $ svn add f1
372 $ svn add f1
373 A f1
373 A f1
374 $ svnversion
374 $ svnversion
375 1M
375 1M
376 $ cd ..
376 $ cd ..
377 $ hg id -n
377 $ hg id -n
378 1+
378 1+
379 $ hg update tip
379 $ hg update tip
380 subrepository sources for s differ
380 subrepository sources for s differ
381 use (l)ocal source (1) or (r)emote source (3)?
381 use (l)ocal source (1) or (r)emote source (3)?
382 l
382 l
383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
384 $ hg id -n
384 $ hg id -n
385 2
385 2
386 $ cd s
386 $ cd s
387 $ svnversion
387 $ svnversion
388 1M
388 1M
389 $ cd ..
389 $ cd ..
390
390
391 Sticky repository, update --clean
391 Sticky repository, update --clean
392 $ hg update --clean tip | grep -v s/externals/other
392 $ hg update --clean tip | grep -v s/externals/other
393 U *s/alpha (glob)
393 U *s/alpha (glob)
394 U *s (glob)
394 U *s (glob)
395
395
396 Fetching external item into '*s/externals'* (glob)
396 Fetching external item into '*s/externals'* (glob)
397 Checked out external at revision 1.
397 Checked out external at revision 1.
398
398
399 Checked out revision 3.
399 Checked out revision 3.
400 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
401 $ hg id -n
401 $ hg id -n
402 2
402 2
403 $ cd s
403 $ cd s
404 $ svnversion
404 $ svnversion
405 3
405 3
406 $ cd ..
406 $ cd ..
407
407
408 Test subrepo already at intended revision:
408 Test subrepo already at intended revision:
409 $ cd s
409 $ cd s
410 $ svn update -qr 2
410 $ svn update -qr 2
411 $ cd ..
411 $ cd ..
412 $ hg update 1
412 $ hg update 1
413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 $ hg id -n
414 $ hg id -n
415 1+
415 1+
416 $ cd s
416 $ cd s
417 $ svnversion
417 $ svnversion
418 2
418 2
419 $ cd ..
419 $ cd ..
420
420
421 Test case where subversion would fail to update the subrepo because there
421 Test case where subversion would fail to update the subrepo because there
422 are unknown directories being replaced by tracked ones (happens with rebase).
422 are unknown directories being replaced by tracked ones (happens with rebase).
423
423
424 $ cd $WCROOT/src
424 $ cd $WCROOT/src
425 $ mkdir dir
425 $ mkdir dir
426 $ echo epsilon.py > dir/epsilon.py
426 $ echo epsilon.py > dir/epsilon.py
427 $ svn add dir
427 $ svn add dir
428 A dir
428 A dir
429 A dir/epsilon.py
429 A dir/epsilon.py
430 $ svn ci -m 'Add dir/epsilon.py'
430 $ svn ci -m 'Add dir/epsilon.py'
431 Adding *dir (glob)
431 Adding *dir (glob)
432 Adding *dir/epsilon.py (glob)
432 Adding *dir/epsilon.py (glob)
433 Transmitting file data .
433 Transmitting file data .
434 Committed revision 6.
434 Committed revision 6.
435 $ cd ../..
435 $ cd ../..
436 $ hg init rebaserepo
436 $ hg init rebaserepo
437 $ cd rebaserepo
437 $ cd rebaserepo
438 $ svn co -r5 --quiet "$SVNREPO"/src s
438 $ svn co -r5 --quiet "$SVNREPO"/src s
439 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
439 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
440 $ hg add .hgsub
440 $ hg add .hgsub
441 $ hg ci -m addsub
441 $ hg ci -m addsub
442 $ echo a > a
442 $ echo a > a
443 $ hg ci -Am adda
443 $ hg ci -Am adda
444 adding a
444 adding a
445 $ hg up 0
445 $ hg up 0
446 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
446 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
447 $ svn up -qr6 s
447 $ svn up -qr6 s
448 $ hg ci -m updatesub
448 $ hg ci -m updatesub
449 created new head
449 created new head
450 $ echo pyc > s/dir/epsilon.pyc
450 $ echo pyc > s/dir/epsilon.pyc
451 $ hg up 1
451 $ hg up 1
452 D *s/dir (glob)
452 D *s/dir (glob)
453
453
454 Fetching external item into '*s/externals'* (glob)
454 Fetching external item into '*s/externals'* (glob)
455 Checked out external at revision 1.
455 Checked out external at revision 1.
456
456
457 Checked out revision 5.
457 Checked out revision 5.
458 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
458 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
459 $ if "$TESTDIR/hghave" -q svn15; then
459 $ if "$TESTDIR/hghave" -q svn15; then
460 > hg up 2 >/dev/null 2>&1 || echo update failed
460 > hg up 2 >/dev/null 2>&1 || echo update failed
461 > fi
461 > fi
462
462
463 Modify one of the externals to point to a different path so we can
463 Modify one of the externals to point to a different path so we can
464 test having obstructions when switching branches on checkout:
464 test having obstructions when switching branches on checkout:
465 $ hg checkout tip
465 $ hg checkout tip
466 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
466 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 $ echo "obstruct = [svn] $SVNREPO/externals" >> .hgsub
467 $ echo "obstruct = [svn] $SVNREPO/externals" >> .hgsub
468 $ svn co -r5 --quiet "$SVNREPO"/externals obstruct
468 $ svn co -r5 --quiet "$SVNREPO"/externals obstruct
469 $ hg commit -m 'Start making obstructed working copy'
469 $ hg commit -m 'Start making obstructed working copy'
470 $ hg book other
470 $ hg book other
471 $ hg co -r 'p1(tip)'
471 $ hg co -r 'p1(tip)'
472 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 $ echo "obstruct = [svn] $SVNREPO/src" >> .hgsub
473 $ echo "obstruct = [svn] $SVNREPO/src" >> .hgsub
474 $ svn co -r5 --quiet "$SVNREPO"/src obstruct
474 $ svn co -r5 --quiet "$SVNREPO"/src obstruct
475 $ hg commit -m 'Other branch which will be obstructed'
475 $ hg commit -m 'Other branch which will be obstructed'
476 created new head
476 created new head
477
477
478 Switching back to the head where we have another path mapped to the
478 Switching back to the head where we have another path mapped to the
479 same subrepo should work if the subrepo is clean.
479 same subrepo should work if the subrepo is clean.
480 $ hg co other
480 $ hg co other
481 A *obstruct/other (glob)
481 A *obstruct/other (glob)
482 Checked out revision 1.
482 Checked out revision 1.
483 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
484
484
485 This is surprising, but is also correct based on the current code:
485 This is surprising, but is also correct based on the current code:
486 $ echo "updating should (maybe) fail" > obstruct/other
486 $ echo "updating should (maybe) fail" > obstruct/other
487 $ hg co tip
487 $ hg co tip
488 abort: crosses branches (merge branches or use --clean to discard changes)
488 abort: crosses branches (merge branches or use --clean to discard changes)
489 [255]
489 [255]
490
490
491 Point to a Subversion branch which has since been deleted and recreated
491 Point to a Subversion branch which has since been deleted and recreated
492 First, create that condition in the repository.
492 First, create that condition in the repository.
493
493
494 $ hg ci --subrepos -m cleanup | grep -v Updating
494 $ hg ci --subrepos -m cleanup | grep -v Updating
495 committing subrepository obstruct
495 committing subrepository obstruct
496 Sending obstruct/other
496 Sending obstruct/other
497 Transmitting file data .
497 Transmitting file data .
498 Committed revision 7.
498 Committed revision 7.
499 At revision 7.
499 At revision 7.
500 $ svn mkdir -m "baseline" $SVNREPO/trunk
500 $ svn mkdir -m "baseline" $SVNREPO/trunk
501
501
502 Committed revision 8.
502 Committed revision 8.
503 $ svn copy -m "initial branch" $SVNREPO/trunk $SVNREPO/branch
503 $ svn copy -m "initial branch" $SVNREPO/trunk $SVNREPO/branch
504
504
505 Committed revision 9.
505 Committed revision 9.
506 $ svn co --quiet "$SVNREPO"/branch tempwc
506 $ svn co --quiet "$SVNREPO"/branch tempwc
507 $ cd tempwc
507 $ cd tempwc
508 $ echo "something old" > somethingold
508 $ echo "something old" > somethingold
509 $ svn add somethingold
509 $ svn add somethingold
510 A somethingold
510 A somethingold
511 $ svn ci -m 'Something old'
511 $ svn ci -m 'Something old'
512 Adding somethingold
512 Adding somethingold
513 Transmitting file data .
513 Transmitting file data .
514 Committed revision 10.
514 Committed revision 10.
515 $ svn rm -m "remove branch" $SVNREPO/branch
515 $ svn rm -m "remove branch" $SVNREPO/branch
516
516
517 Committed revision 11.
517 Committed revision 11.
518 $ svn copy -m "recreate branch" $SVNREPO/trunk $SVNREPO/branch
518 $ svn copy -m "recreate branch" $SVNREPO/trunk $SVNREPO/branch
519
519
520 Committed revision 12.
520 Committed revision 12.
521 $ svn up -q
521 $ svn up -q
522 $ echo "something new" > somethingnew
522 $ echo "something new" > somethingnew
523 $ svn add somethingnew
523 $ svn add somethingnew
524 A somethingnew
524 A somethingnew
525 $ svn ci -m 'Something new'
525 $ svn ci -m 'Something new'
526 Adding somethingnew
526 Adding somethingnew
527 Transmitting file data .
527 Transmitting file data .
528 Committed revision 13.
528 Committed revision 13.
529 $ cd ..
529 $ cd ..
530 $ rm -rf tempwc
530 $ rm -rf tempwc
531 $ svn co "$SVNREPO/branch"@10 recreated
531 $ svn co "$SVNREPO/branch"@10 recreated
532 A recreated/somethingold
532 A recreated/somethingold
533 Checked out revision 10.
533 Checked out revision 10.
534 $ echo "recreated = [svn] $SVNREPO/branch" >> .hgsub
534 $ echo "recreated = [svn] $SVNREPO/branch" >> .hgsub
535 $ hg ci -m addsub
535 $ hg ci -m addsub
536 $ cd recreated
536 $ cd recreated
537 $ svn up -q
537 $ svn up -q
538 $ cd ..
538 $ cd ..
539 $ hg ci -m updatesub
539 $ hg ci -m updatesub
540 $ hg up -r-2
540 $ hg up -r-2
541 D *recreated/somethingnew (glob)
541 D *recreated/somethingnew (glob)
542 A *recreated/somethingold (glob)
542 A *recreated/somethingold (glob)
543 Checked out revision 10.
543 Checked out revision 10.
544 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
544 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
545 $ test -f recreated/somethingold
545 $ test -f recreated/somethingold
546
546
547 Test archive
547 Test archive
548
548
549 $ hg archive -S ../archive-all --debug
549 $ hg archive -S ../archive-all --debug
550 archiving: 0/2 files (0.00%)
550 archiving: 0/2 files (0.00%)
551 archiving: .hgsub 1/2 files (50.00%)
551 archiving: .hgsub 1/2 files (50.00%)
552 archiving: .hgsubstate 2/2 files (100.00%)
552 archiving: .hgsubstate 2/2 files (100.00%)
553 archiving (obstruct): 0/1 files (0.00%)
553 archiving (obstruct): 0/1 files (0.00%)
554 archiving (obstruct): 1/1 files (100.00%)
554 archiving (obstruct): 1/1 files (100.00%)
555 archiving (s): 0/2 files (0.00%)
555 archiving (s): 0/2 files (0.00%)
556 archiving (s): 1/2 files (50.00%)
556 archiving (s): 1/2 files (50.00%)
557 archiving (s): 2/2 files (100.00%)
557 archiving (s): 2/2 files (100.00%)
558 archiving (recreated): 0/1 files (0.00%)
558 archiving (recreated): 0/1 files (0.00%)
559 archiving (recreated): 1/1 files (100.00%)
559 archiving (recreated): 1/1 files (100.00%)
560
560
561 Test forgetting files, not implemented in svn subrepo, used to
561 Test forgetting files, not implemented in svn subrepo, used to
562 traceback
562 traceback
563
563
564 $ hg forget 'notafile*'
564 $ hg forget 'notafile*'
565 notafile*: No such file or directory
565 notafile*: No such file or directory
566 [1]
566 [1]
567
568 Test a subrepo referencing a just moved svn path. Last commit rev will
569 be different from the revision, and the path will be different as
570 well.
571
572 $ cd $WCROOT
573 $ svn up > /dev/null
574 $ mkdir trunk/subdir branches
575 $ echo a > trunk/subdir/a
576 $ svn add trunk/subdir branches
577 A trunk/subdir
578 A trunk/subdir/a
579 A branches
580 $ svn ci -m addsubdir
581 Adding branches
582 Adding trunk/subdir
583 Adding trunk/subdir/a
584 Transmitting file data .
585 Committed revision 14.
586 $ svn cp -m branchtrunk $SVNREPO/trunk $SVNREPO/branches/somebranch
587
588 Committed revision 15.
589 $ cd ..
590
591 $ hg init repo2
592 $ cd repo2
593 $ svn co $SVNREPO/branches/somebranch/subdir
594 A subdir/a
595 Checked out revision 15.
596 $ echo "subdir = [svn] $SVNREPO/branches/somebranch/subdir" > .hgsub
597 $ hg add .hgsub
598 $ hg ci -m addsub
599 $ hg up null
600 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
601 $ hg up
602 A *subdir/a (glob)
603 Checked out revision 15.
604 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
605 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now