##// END OF EJS Templates
subrepo: fix exception on revert when "all" option is omitted...
Yuya Nishihara -
r18943:27e8dfc2 default
parent child Browse files
Show More
@@ -1,1444 +1,1444 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, match as matchmod
11 import config, scmutil, util, node, error, cmdutil, bookmarks, match as matchmod
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 _expandedabspath(path):
17 def _expandedabspath(path):
18 '''
18 '''
19 get a path or url and if it is a path expand it and return an absolute path
19 get a path or url and if it is a path expand it and return an absolute path
20 '''
20 '''
21 expandedpath = util.urllocalpath(util.expandpath(path))
21 expandedpath = util.urllocalpath(util.expandpath(path))
22 u = util.url(expandedpath)
22 u = util.url(expandedpath)
23 if not u.scheme:
23 if not u.scheme:
24 path = util.normpath(os.path.abspath(u.path))
24 path = util.normpath(os.path.abspath(u.path))
25 return path
25 return path
26
26
27 def _getstorehashcachename(remotepath):
27 def _getstorehashcachename(remotepath):
28 '''get a unique filename for the store hash cache of a remote repository'''
28 '''get a unique filename for the store hash cache of a remote repository'''
29 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
29 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
30
30
31 def _calcfilehash(filename):
31 def _calcfilehash(filename):
32 data = ''
32 data = ''
33 if os.path.exists(filename):
33 if os.path.exists(filename):
34 fd = open(filename)
34 fd = open(filename)
35 data = fd.read()
35 data = fd.read()
36 fd.close()
36 fd.close()
37 return util.sha1(data).hexdigest()
37 return util.sha1(data).hexdigest()
38
38
39 class SubrepoAbort(error.Abort):
39 class SubrepoAbort(error.Abort):
40 """Exception class used to avoid handling a subrepo error more than once"""
40 """Exception class used to avoid handling a subrepo error more than once"""
41 def __init__(self, *args, **kw):
41 def __init__(self, *args, **kw):
42 error.Abort.__init__(self, *args, **kw)
42 error.Abort.__init__(self, *args, **kw)
43 self.subrepo = kw.get('subrepo')
43 self.subrepo = kw.get('subrepo')
44
44
45 def annotatesubrepoerror(func):
45 def annotatesubrepoerror(func):
46 def decoratedmethod(self, *args, **kargs):
46 def decoratedmethod(self, *args, **kargs):
47 try:
47 try:
48 res = func(self, *args, **kargs)
48 res = func(self, *args, **kargs)
49 except SubrepoAbort, ex:
49 except SubrepoAbort, ex:
50 # This exception has already been handled
50 # This exception has already been handled
51 raise ex
51 raise ex
52 except error.Abort, ex:
52 except error.Abort, ex:
53 subrepo = subrelpath(self)
53 subrepo = subrelpath(self)
54 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
54 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
55 # avoid handling this exception by raising a SubrepoAbort exception
55 # avoid handling this exception by raising a SubrepoAbort exception
56 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo)
56 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo)
57 return res
57 return res
58 return decoratedmethod
58 return decoratedmethod
59
59
60 def state(ctx, ui):
60 def state(ctx, ui):
61 """return a state dict, mapping subrepo paths configured in .hgsub
61 """return a state dict, mapping subrepo paths configured in .hgsub
62 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 to tuple: (source from .hgsub, revision from .hgsubstate, kind
63 (key in types dict))
63 (key in types dict))
64 """
64 """
65 p = config.config()
65 p = config.config()
66 def read(f, sections=None, remap=None):
66 def read(f, sections=None, remap=None):
67 if f in ctx:
67 if f in ctx:
68 try:
68 try:
69 data = ctx[f].data()
69 data = ctx[f].data()
70 except IOError, err:
70 except IOError, err:
71 if err.errno != errno.ENOENT:
71 if err.errno != errno.ENOENT:
72 raise
72 raise
73 # handle missing subrepo spec files as removed
73 # handle missing subrepo spec files as removed
74 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
74 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
75 return
75 return
76 p.parse(f, data, sections, remap, read)
76 p.parse(f, data, sections, remap, read)
77 else:
77 else:
78 raise util.Abort(_("subrepo spec file %s not found") % f)
78 raise util.Abort(_("subrepo spec file %s not found") % f)
79
79
80 if '.hgsub' in ctx:
80 if '.hgsub' in ctx:
81 read('.hgsub')
81 read('.hgsub')
82
82
83 for path, src in ui.configitems('subpaths'):
83 for path, src in ui.configitems('subpaths'):
84 p.set('subpaths', path, src, ui.configsource('subpaths', path))
84 p.set('subpaths', path, src, ui.configsource('subpaths', path))
85
85
86 rev = {}
86 rev = {}
87 if '.hgsubstate' in ctx:
87 if '.hgsubstate' in ctx:
88 try:
88 try:
89 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
89 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
90 l = l.lstrip()
90 l = l.lstrip()
91 if not l:
91 if not l:
92 continue
92 continue
93 try:
93 try:
94 revision, path = l.split(" ", 1)
94 revision, path = l.split(" ", 1)
95 except ValueError:
95 except ValueError:
96 raise util.Abort(_("invalid subrepository revision "
96 raise util.Abort(_("invalid subrepository revision "
97 "specifier in .hgsubstate line %d")
97 "specifier in .hgsubstate line %d")
98 % (i + 1))
98 % (i + 1))
99 rev[path] = revision
99 rev[path] = revision
100 except IOError, err:
100 except IOError, err:
101 if err.errno != errno.ENOENT:
101 if err.errno != errno.ENOENT:
102 raise
102 raise
103
103
104 def remap(src):
104 def remap(src):
105 for pattern, repl in p.items('subpaths'):
105 for pattern, repl in p.items('subpaths'):
106 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
106 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
107 # does a string decode.
107 # does a string decode.
108 repl = repl.encode('string-escape')
108 repl = repl.encode('string-escape')
109 # However, we still want to allow back references to go
109 # However, we still want to allow back references to go
110 # through unharmed, so we turn r'\\1' into r'\1'. Again,
110 # through unharmed, so we turn r'\\1' into r'\1'. Again,
111 # extra escapes are needed because re.sub string decodes.
111 # extra escapes are needed because re.sub string decodes.
112 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
112 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
113 try:
113 try:
114 src = re.sub(pattern, repl, src, 1)
114 src = re.sub(pattern, repl, src, 1)
115 except re.error, e:
115 except re.error, e:
116 raise util.Abort(_("bad subrepository pattern in %s: %s")
116 raise util.Abort(_("bad subrepository pattern in %s: %s")
117 % (p.source('subpaths', pattern), e))
117 % (p.source('subpaths', pattern), e))
118 return src
118 return src
119
119
120 state = {}
120 state = {}
121 for path, src in p[''].items():
121 for path, src in p[''].items():
122 kind = 'hg'
122 kind = 'hg'
123 if src.startswith('['):
123 if src.startswith('['):
124 if ']' not in src:
124 if ']' not in src:
125 raise util.Abort(_('missing ] in subrepo source'))
125 raise util.Abort(_('missing ] in subrepo source'))
126 kind, src = src.split(']', 1)
126 kind, src = src.split(']', 1)
127 kind = kind[1:]
127 kind = kind[1:]
128 src = src.lstrip() # strip any extra whitespace after ']'
128 src = src.lstrip() # strip any extra whitespace after ']'
129
129
130 if not util.url(src).isabs():
130 if not util.url(src).isabs():
131 parent = _abssource(ctx._repo, abort=False)
131 parent = _abssource(ctx._repo, abort=False)
132 if parent:
132 if parent:
133 parent = util.url(parent)
133 parent = util.url(parent)
134 parent.path = posixpath.join(parent.path or '', src)
134 parent.path = posixpath.join(parent.path or '', src)
135 parent.path = posixpath.normpath(parent.path)
135 parent.path = posixpath.normpath(parent.path)
136 joined = str(parent)
136 joined = str(parent)
137 # Remap the full joined path and use it if it changes,
137 # Remap the full joined path and use it if it changes,
138 # else remap the original source.
138 # else remap the original source.
139 remapped = remap(joined)
139 remapped = remap(joined)
140 if remapped == joined:
140 if remapped == joined:
141 src = remap(src)
141 src = remap(src)
142 else:
142 else:
143 src = remapped
143 src = remapped
144
144
145 src = remap(src)
145 src = remap(src)
146 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
146 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
147
147
148 return state
148 return state
149
149
150 def writestate(repo, state):
150 def writestate(repo, state):
151 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
151 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
152 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
152 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
153 repo.wwrite('.hgsubstate', ''.join(lines), '')
153 repo.wwrite('.hgsubstate', ''.join(lines), '')
154
154
155 def submerge(repo, wctx, mctx, actx, overwrite):
155 def submerge(repo, wctx, mctx, actx, overwrite):
156 """delegated from merge.applyupdates: merging of .hgsubstate file
156 """delegated from merge.applyupdates: merging of .hgsubstate file
157 in working context, merging context and ancestor context"""
157 in working context, merging context and ancestor context"""
158 if mctx == actx: # backwards?
158 if mctx == actx: # backwards?
159 actx = wctx.p1()
159 actx = wctx.p1()
160 s1 = wctx.substate
160 s1 = wctx.substate
161 s2 = mctx.substate
161 s2 = mctx.substate
162 sa = actx.substate
162 sa = actx.substate
163 sm = {}
163 sm = {}
164
164
165 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
165 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
166
166
167 def debug(s, msg, r=""):
167 def debug(s, msg, r=""):
168 if r:
168 if r:
169 r = "%s:%s:%s" % r
169 r = "%s:%s:%s" % r
170 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
170 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
171
171
172 for s, l in sorted(s1.iteritems()):
172 for s, l in sorted(s1.iteritems()):
173 a = sa.get(s, nullstate)
173 a = sa.get(s, nullstate)
174 ld = l # local state with possible dirty flag for compares
174 ld = l # local state with possible dirty flag for compares
175 if wctx.sub(s).dirty():
175 if wctx.sub(s).dirty():
176 ld = (l[0], l[1] + "+")
176 ld = (l[0], l[1] + "+")
177 if wctx == actx: # overwrite
177 if wctx == actx: # overwrite
178 a = ld
178 a = ld
179
179
180 if s in s2:
180 if s in s2:
181 r = s2[s]
181 r = s2[s]
182 if ld == r or r == a: # no change or local is newer
182 if ld == r or r == a: # no change or local is newer
183 sm[s] = l
183 sm[s] = l
184 continue
184 continue
185 elif ld == a: # other side changed
185 elif ld == a: # other side changed
186 debug(s, "other changed, get", r)
186 debug(s, "other changed, get", r)
187 wctx.sub(s).get(r, overwrite)
187 wctx.sub(s).get(r, overwrite)
188 sm[s] = r
188 sm[s] = r
189 elif ld[0] != r[0]: # sources differ
189 elif ld[0] != r[0]: # sources differ
190 if repo.ui.promptchoice(
190 if repo.ui.promptchoice(
191 _(' subrepository sources for %s differ\n'
191 _(' subrepository sources for %s differ\n'
192 'use (l)ocal source (%s) or (r)emote source (%s)?')
192 'use (l)ocal source (%s) or (r)emote source (%s)?')
193 % (s, l[0], r[0]),
193 % (s, l[0], r[0]),
194 (_('&Local'), _('&Remote')), 0):
194 (_('&Local'), _('&Remote')), 0):
195 debug(s, "prompt changed, get", r)
195 debug(s, "prompt changed, get", r)
196 wctx.sub(s).get(r, overwrite)
196 wctx.sub(s).get(r, overwrite)
197 sm[s] = r
197 sm[s] = r
198 elif ld[1] == a[1]: # local side is unchanged
198 elif ld[1] == a[1]: # local side is unchanged
199 debug(s, "other side changed, get", r)
199 debug(s, "other side changed, get", r)
200 wctx.sub(s).get(r, overwrite)
200 wctx.sub(s).get(r, overwrite)
201 sm[s] = r
201 sm[s] = r
202 else:
202 else:
203 debug(s, "both sides changed, merge with", r)
203 debug(s, "both sides changed, merge with", r)
204 wctx.sub(s).merge(r)
204 wctx.sub(s).merge(r)
205 sm[s] = l
205 sm[s] = l
206 elif ld == a: # remote removed, local unchanged
206 elif ld == a: # remote removed, local unchanged
207 debug(s, "remote removed, remove")
207 debug(s, "remote removed, remove")
208 wctx.sub(s).remove()
208 wctx.sub(s).remove()
209 elif a == nullstate: # not present in remote or ancestor
209 elif a == nullstate: # not present in remote or ancestor
210 debug(s, "local added, keep")
210 debug(s, "local added, keep")
211 sm[s] = l
211 sm[s] = l
212 continue
212 continue
213 else:
213 else:
214 if repo.ui.promptchoice(
214 if repo.ui.promptchoice(
215 _(' local changed subrepository %s which remote removed\n'
215 _(' local changed subrepository %s which remote removed\n'
216 'use (c)hanged version or (d)elete?') % s,
216 'use (c)hanged version or (d)elete?') % s,
217 (_('&Changed'), _('&Delete')), 0):
217 (_('&Changed'), _('&Delete')), 0):
218 debug(s, "prompt remove")
218 debug(s, "prompt remove")
219 wctx.sub(s).remove()
219 wctx.sub(s).remove()
220
220
221 for s, r in sorted(s2.items()):
221 for s, r in sorted(s2.items()):
222 if s in s1:
222 if s in s1:
223 continue
223 continue
224 elif s not in sa:
224 elif s not in sa:
225 debug(s, "remote added, get", r)
225 debug(s, "remote added, get", r)
226 mctx.sub(s).get(r)
226 mctx.sub(s).get(r)
227 sm[s] = r
227 sm[s] = r
228 elif r != sa[s]:
228 elif r != sa[s]:
229 if repo.ui.promptchoice(
229 if repo.ui.promptchoice(
230 _(' remote changed subrepository %s which local removed\n'
230 _(' remote changed subrepository %s which local removed\n'
231 'use (c)hanged version or (d)elete?') % s,
231 'use (c)hanged version or (d)elete?') % s,
232 (_('&Changed'), _('&Delete')), 0) == 0:
232 (_('&Changed'), _('&Delete')), 0) == 0:
233 debug(s, "prompt recreate", r)
233 debug(s, "prompt recreate", r)
234 wctx.sub(s).get(r)
234 wctx.sub(s).get(r)
235 sm[s] = r
235 sm[s] = r
236
236
237 # record merged .hgsubstate
237 # record merged .hgsubstate
238 writestate(repo, sm)
238 writestate(repo, sm)
239
239
240 def _updateprompt(ui, sub, dirty, local, remote):
240 def _updateprompt(ui, sub, dirty, local, remote):
241 if dirty:
241 if dirty:
242 msg = (_(' subrepository sources for %s differ\n'
242 msg = (_(' subrepository sources for %s differ\n'
243 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
243 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
244 % (subrelpath(sub), local, remote))
244 % (subrelpath(sub), local, remote))
245 else:
245 else:
246 msg = (_(' subrepository sources for %s differ (in checked out '
246 msg = (_(' subrepository sources for %s differ (in checked out '
247 'version)\n'
247 'version)\n'
248 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
248 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
249 % (subrelpath(sub), local, remote))
249 % (subrelpath(sub), local, remote))
250 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
250 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
251
251
252 def reporelpath(repo):
252 def reporelpath(repo):
253 """return path to this (sub)repo as seen from outermost repo"""
253 """return path to this (sub)repo as seen from outermost repo"""
254 parent = repo
254 parent = repo
255 while util.safehasattr(parent, '_subparent'):
255 while util.safehasattr(parent, '_subparent'):
256 parent = parent._subparent
256 parent = parent._subparent
257 p = parent.root.rstrip(os.sep)
257 p = parent.root.rstrip(os.sep)
258 return repo.root[len(p) + 1:]
258 return repo.root[len(p) + 1:]
259
259
260 def subrelpath(sub):
260 def subrelpath(sub):
261 """return path to this subrepo as seen from outermost repo"""
261 """return path to this subrepo as seen from outermost repo"""
262 if util.safehasattr(sub, '_relpath'):
262 if util.safehasattr(sub, '_relpath'):
263 return sub._relpath
263 return sub._relpath
264 if not util.safehasattr(sub, '_repo'):
264 if not util.safehasattr(sub, '_repo'):
265 return sub._path
265 return sub._path
266 return reporelpath(sub._repo)
266 return reporelpath(sub._repo)
267
267
268 def _abssource(repo, push=False, abort=True):
268 def _abssource(repo, push=False, abort=True):
269 """return pull/push path of repo - either based on parent repo .hgsub info
269 """return pull/push path of repo - either based on parent repo .hgsub info
270 or on the top repo config. Abort or return None if no source found."""
270 or on the top repo config. Abort or return None if no source found."""
271 if util.safehasattr(repo, '_subparent'):
271 if util.safehasattr(repo, '_subparent'):
272 source = util.url(repo._subsource)
272 source = util.url(repo._subsource)
273 if source.isabs():
273 if source.isabs():
274 return str(source)
274 return str(source)
275 source.path = posixpath.normpath(source.path)
275 source.path = posixpath.normpath(source.path)
276 parent = _abssource(repo._subparent, push, abort=False)
276 parent = _abssource(repo._subparent, push, abort=False)
277 if parent:
277 if parent:
278 parent = util.url(util.pconvert(parent))
278 parent = util.url(util.pconvert(parent))
279 parent.path = posixpath.join(parent.path or '', source.path)
279 parent.path = posixpath.join(parent.path or '', source.path)
280 parent.path = posixpath.normpath(parent.path)
280 parent.path = posixpath.normpath(parent.path)
281 return str(parent)
281 return str(parent)
282 else: # recursion reached top repo
282 else: # recursion reached top repo
283 if util.safehasattr(repo, '_subtoppath'):
283 if util.safehasattr(repo, '_subtoppath'):
284 return repo._subtoppath
284 return repo._subtoppath
285 if push and repo.ui.config('paths', 'default-push'):
285 if push and repo.ui.config('paths', 'default-push'):
286 return repo.ui.config('paths', 'default-push')
286 return repo.ui.config('paths', 'default-push')
287 if repo.ui.config('paths', 'default'):
287 if repo.ui.config('paths', 'default'):
288 return repo.ui.config('paths', 'default')
288 return repo.ui.config('paths', 'default')
289 if repo.sharedpath != repo.path:
289 if repo.sharedpath != repo.path:
290 # chop off the .hg component to get the default path form
290 # chop off the .hg component to get the default path form
291 return os.path.dirname(repo.sharedpath)
291 return os.path.dirname(repo.sharedpath)
292 if abort:
292 if abort:
293 raise util.Abort(_("default path for subrepository not found"))
293 raise util.Abort(_("default path for subrepository not found"))
294
294
295 def itersubrepos(ctx1, ctx2):
295 def itersubrepos(ctx1, ctx2):
296 """find subrepos in ctx1 or ctx2"""
296 """find subrepos in ctx1 or ctx2"""
297 # Create a (subpath, ctx) mapping where we prefer subpaths from
297 # Create a (subpath, ctx) mapping where we prefer subpaths from
298 # ctx1. The subpaths from ctx2 are important when the .hgsub file
298 # ctx1. The subpaths from ctx2 are important when the .hgsub file
299 # has been modified (in ctx2) but not yet committed (in ctx1).
299 # has been modified (in ctx2) but not yet committed (in ctx1).
300 subpaths = dict.fromkeys(ctx2.substate, ctx2)
300 subpaths = dict.fromkeys(ctx2.substate, ctx2)
301 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
301 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
302 for subpath, ctx in sorted(subpaths.iteritems()):
302 for subpath, ctx in sorted(subpaths.iteritems()):
303 yield subpath, ctx.sub(subpath)
303 yield subpath, ctx.sub(subpath)
304
304
305 def subrepo(ctx, path):
305 def subrepo(ctx, path):
306 """return instance of the right subrepo class for subrepo in path"""
306 """return instance of the right subrepo class for subrepo in path"""
307 # subrepo inherently violates our import layering rules
307 # subrepo inherently violates our import layering rules
308 # because it wants to make repo objects from deep inside the stack
308 # because it wants to make repo objects from deep inside the stack
309 # so we manually delay the circular imports to not break
309 # so we manually delay the circular imports to not break
310 # scripts that don't use our demand-loading
310 # scripts that don't use our demand-loading
311 global hg
311 global hg
312 import hg as h
312 import hg as h
313 hg = h
313 hg = h
314
314
315 scmutil.pathauditor(ctx._repo.root)(path)
315 scmutil.pathauditor(ctx._repo.root)(path)
316 state = ctx.substate[path]
316 state = ctx.substate[path]
317 if state[2] not in types:
317 if state[2] not in types:
318 raise util.Abort(_('unknown subrepo type %s') % state[2])
318 raise util.Abort(_('unknown subrepo type %s') % state[2])
319 return types[state[2]](ctx, path, state[:2])
319 return types[state[2]](ctx, path, state[:2])
320
320
321 # subrepo classes need to implement the following abstract class:
321 # subrepo classes need to implement the following abstract class:
322
322
323 class abstractsubrepo(object):
323 class abstractsubrepo(object):
324
324
325 def storeclean(self, path):
325 def storeclean(self, path):
326 """
326 """
327 returns true if the repository has not changed since it was last
327 returns true if the repository has not changed since it was last
328 cloned from or pushed to a given repository.
328 cloned from or pushed to a given repository.
329 """
329 """
330 return False
330 return False
331
331
332 def dirty(self, ignoreupdate=False):
332 def dirty(self, ignoreupdate=False):
333 """returns true if the dirstate of the subrepo is dirty or does not
333 """returns true if the dirstate of the subrepo is dirty or does not
334 match current stored state. If ignoreupdate is true, only check
334 match current stored state. If ignoreupdate is true, only check
335 whether the subrepo has uncommitted changes in its dirstate.
335 whether the subrepo has uncommitted changes in its dirstate.
336 """
336 """
337 raise NotImplementedError
337 raise NotImplementedError
338
338
339 def basestate(self):
339 def basestate(self):
340 """current working directory base state, disregarding .hgsubstate
340 """current working directory base state, disregarding .hgsubstate
341 state and working directory modifications"""
341 state and working directory modifications"""
342 raise NotImplementedError
342 raise NotImplementedError
343
343
344 def checknested(self, path):
344 def checknested(self, path):
345 """check if path is a subrepository within this repository"""
345 """check if path is a subrepository within this repository"""
346 return False
346 return False
347
347
348 def commit(self, text, user, date):
348 def commit(self, text, user, date):
349 """commit the current changes to the subrepo with the given
349 """commit the current changes to the subrepo with the given
350 log message. Use given user and date if possible. Return the
350 log message. Use given user and date if possible. Return the
351 new state of the subrepo.
351 new state of the subrepo.
352 """
352 """
353 raise NotImplementedError
353 raise NotImplementedError
354
354
355 def remove(self):
355 def remove(self):
356 """remove the subrepo
356 """remove the subrepo
357
357
358 (should verify the dirstate is not dirty first)
358 (should verify the dirstate is not dirty first)
359 """
359 """
360 raise NotImplementedError
360 raise NotImplementedError
361
361
362 def get(self, state, overwrite=False):
362 def get(self, state, overwrite=False):
363 """run whatever commands are needed to put the subrepo into
363 """run whatever commands are needed to put the subrepo into
364 this state
364 this state
365 """
365 """
366 raise NotImplementedError
366 raise NotImplementedError
367
367
368 def merge(self, state):
368 def merge(self, state):
369 """merge currently-saved state with the new state."""
369 """merge currently-saved state with the new state."""
370 raise NotImplementedError
370 raise NotImplementedError
371
371
372 def push(self, opts):
372 def push(self, opts):
373 """perform whatever action is analogous to 'hg push'
373 """perform whatever action is analogous to 'hg push'
374
374
375 This may be a no-op on some systems.
375 This may be a no-op on some systems.
376 """
376 """
377 raise NotImplementedError
377 raise NotImplementedError
378
378
379 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
379 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
380 return []
380 return []
381
381
382 def status(self, rev2, **opts):
382 def status(self, rev2, **opts):
383 return [], [], [], [], [], [], []
383 return [], [], [], [], [], [], []
384
384
385 def diff(self, ui, diffopts, node2, match, prefix, **opts):
385 def diff(self, ui, diffopts, node2, match, prefix, **opts):
386 pass
386 pass
387
387
388 def outgoing(self, ui, dest, opts):
388 def outgoing(self, ui, dest, opts):
389 return 1
389 return 1
390
390
391 def incoming(self, ui, source, opts):
391 def incoming(self, ui, source, opts):
392 return 1
392 return 1
393
393
394 def files(self):
394 def files(self):
395 """return filename iterator"""
395 """return filename iterator"""
396 raise NotImplementedError
396 raise NotImplementedError
397
397
398 def filedata(self, name):
398 def filedata(self, name):
399 """return file data"""
399 """return file data"""
400 raise NotImplementedError
400 raise NotImplementedError
401
401
402 def fileflags(self, name):
402 def fileflags(self, name):
403 """return file flags"""
403 """return file flags"""
404 return ''
404 return ''
405
405
406 def archive(self, ui, archiver, prefix, match=None):
406 def archive(self, ui, archiver, prefix, match=None):
407 if match is not None:
407 if match is not None:
408 files = [f for f in self.files() if match(f)]
408 files = [f for f in self.files() if match(f)]
409 else:
409 else:
410 files = self.files()
410 files = self.files()
411 total = len(files)
411 total = len(files)
412 relpath = subrelpath(self)
412 relpath = subrelpath(self)
413 ui.progress(_('archiving (%s)') % relpath, 0,
413 ui.progress(_('archiving (%s)') % relpath, 0,
414 unit=_('files'), total=total)
414 unit=_('files'), total=total)
415 for i, name in enumerate(files):
415 for i, name in enumerate(files):
416 flags = self.fileflags(name)
416 flags = self.fileflags(name)
417 mode = 'x' in flags and 0755 or 0644
417 mode = 'x' in flags and 0755 or 0644
418 symlink = 'l' in flags
418 symlink = 'l' in flags
419 archiver.addfile(os.path.join(prefix, self._path, name),
419 archiver.addfile(os.path.join(prefix, self._path, name),
420 mode, symlink, self.filedata(name))
420 mode, symlink, self.filedata(name))
421 ui.progress(_('archiving (%s)') % relpath, i + 1,
421 ui.progress(_('archiving (%s)') % relpath, i + 1,
422 unit=_('files'), total=total)
422 unit=_('files'), total=total)
423 ui.progress(_('archiving (%s)') % relpath, None)
423 ui.progress(_('archiving (%s)') % relpath, None)
424
424
425 def walk(self, match):
425 def walk(self, match):
426 '''
426 '''
427 walk recursively through the directory tree, finding all files
427 walk recursively through the directory tree, finding all files
428 matched by the match function
428 matched by the match function
429 '''
429 '''
430 pass
430 pass
431
431
432 def forget(self, ui, match, prefix):
432 def forget(self, ui, match, prefix):
433 return ([], [])
433 return ([], [])
434
434
435 def revert(self, ui, substate, *pats, **opts):
435 def revert(self, ui, substate, *pats, **opts):
436 ui.warn('%s: reverting %s subrepos is unsupported\n' \
436 ui.warn('%s: reverting %s subrepos is unsupported\n' \
437 % (substate[0], substate[2]))
437 % (substate[0], substate[2]))
438 return []
438 return []
439
439
440 class hgsubrepo(abstractsubrepo):
440 class hgsubrepo(abstractsubrepo):
441 def __init__(self, ctx, path, state):
441 def __init__(self, ctx, path, state):
442 self._path = path
442 self._path = path
443 self._state = state
443 self._state = state
444 r = ctx._repo
444 r = ctx._repo
445 root = r.wjoin(path)
445 root = r.wjoin(path)
446 create = False
446 create = False
447 if not os.path.exists(os.path.join(root, '.hg')):
447 if not os.path.exists(os.path.join(root, '.hg')):
448 create = True
448 create = True
449 util.makedirs(root)
449 util.makedirs(root)
450 self._repo = hg.repository(r.baseui, root, create=create)
450 self._repo = hg.repository(r.baseui, root, create=create)
451 for s, k in [('ui', 'commitsubrepos')]:
451 for s, k in [('ui', 'commitsubrepos')]:
452 v = r.ui.config(s, k)
452 v = r.ui.config(s, k)
453 if v:
453 if v:
454 self._repo.ui.setconfig(s, k, v)
454 self._repo.ui.setconfig(s, k, v)
455 self._repo.ui.setconfig('ui', '_usedassubrepo', 'True')
455 self._repo.ui.setconfig('ui', '_usedassubrepo', 'True')
456 self._initrepo(r, state[0], create)
456 self._initrepo(r, state[0], create)
457
457
458 def storeclean(self, path):
458 def storeclean(self, path):
459 clean = True
459 clean = True
460 lock = self._repo.lock()
460 lock = self._repo.lock()
461 itercache = self._calcstorehash(path)
461 itercache = self._calcstorehash(path)
462 try:
462 try:
463 for filehash in self._readstorehashcache(path):
463 for filehash in self._readstorehashcache(path):
464 if filehash != itercache.next():
464 if filehash != itercache.next():
465 clean = False
465 clean = False
466 break
466 break
467 except StopIteration:
467 except StopIteration:
468 # the cached and current pull states have a different size
468 # the cached and current pull states have a different size
469 clean = False
469 clean = False
470 if clean:
470 if clean:
471 try:
471 try:
472 itercache.next()
472 itercache.next()
473 # the cached and current pull states have a different size
473 # the cached and current pull states have a different size
474 clean = False
474 clean = False
475 except StopIteration:
475 except StopIteration:
476 pass
476 pass
477 lock.release()
477 lock.release()
478 return clean
478 return clean
479
479
480 def _calcstorehash(self, remotepath):
480 def _calcstorehash(self, remotepath):
481 '''calculate a unique "store hash"
481 '''calculate a unique "store hash"
482
482
483 This method is used to to detect when there are changes that may
483 This method is used to to detect when there are changes that may
484 require a push to a given remote path.'''
484 require a push to a given remote path.'''
485 # sort the files that will be hashed in increasing (likely) file size
485 # sort the files that will be hashed in increasing (likely) file size
486 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
486 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
487 yield '# %s\n' % _expandedabspath(remotepath)
487 yield '# %s\n' % _expandedabspath(remotepath)
488 for relname in filelist:
488 for relname in filelist:
489 absname = os.path.normpath(self._repo.join(relname))
489 absname = os.path.normpath(self._repo.join(relname))
490 yield '%s = %s\n' % (relname, _calcfilehash(absname))
490 yield '%s = %s\n' % (relname, _calcfilehash(absname))
491
491
492 def _getstorehashcachepath(self, remotepath):
492 def _getstorehashcachepath(self, remotepath):
493 '''get a unique path for the store hash cache'''
493 '''get a unique path for the store hash cache'''
494 return self._repo.join(os.path.join(
494 return self._repo.join(os.path.join(
495 'cache', 'storehash', _getstorehashcachename(remotepath)))
495 'cache', 'storehash', _getstorehashcachename(remotepath)))
496
496
497 def _readstorehashcache(self, remotepath):
497 def _readstorehashcache(self, remotepath):
498 '''read the store hash cache for a given remote repository'''
498 '''read the store hash cache for a given remote repository'''
499 cachefile = self._getstorehashcachepath(remotepath)
499 cachefile = self._getstorehashcachepath(remotepath)
500 if not os.path.exists(cachefile):
500 if not os.path.exists(cachefile):
501 return ''
501 return ''
502 fd = open(cachefile, 'r')
502 fd = open(cachefile, 'r')
503 pullstate = fd.readlines()
503 pullstate = fd.readlines()
504 fd.close()
504 fd.close()
505 return pullstate
505 return pullstate
506
506
507 def _cachestorehash(self, remotepath):
507 def _cachestorehash(self, remotepath):
508 '''cache the current store hash
508 '''cache the current store hash
509
509
510 Each remote repo requires its own store hash cache, because a subrepo
510 Each remote repo requires its own store hash cache, because a subrepo
511 store may be "clean" versus a given remote repo, but not versus another
511 store may be "clean" versus a given remote repo, but not versus another
512 '''
512 '''
513 cachefile = self._getstorehashcachepath(remotepath)
513 cachefile = self._getstorehashcachepath(remotepath)
514 lock = self._repo.lock()
514 lock = self._repo.lock()
515 storehash = list(self._calcstorehash(remotepath))
515 storehash = list(self._calcstorehash(remotepath))
516 cachedir = os.path.dirname(cachefile)
516 cachedir = os.path.dirname(cachefile)
517 if not os.path.exists(cachedir):
517 if not os.path.exists(cachedir):
518 util.makedirs(cachedir, notindexed=True)
518 util.makedirs(cachedir, notindexed=True)
519 fd = open(cachefile, 'w')
519 fd = open(cachefile, 'w')
520 fd.writelines(storehash)
520 fd.writelines(storehash)
521 fd.close()
521 fd.close()
522 lock.release()
522 lock.release()
523
523
524 @annotatesubrepoerror
524 @annotatesubrepoerror
525 def _initrepo(self, parentrepo, source, create):
525 def _initrepo(self, parentrepo, source, create):
526 self._repo._subparent = parentrepo
526 self._repo._subparent = parentrepo
527 self._repo._subsource = source
527 self._repo._subsource = source
528
528
529 if create:
529 if create:
530 fp = self._repo.opener("hgrc", "w", text=True)
530 fp = self._repo.opener("hgrc", "w", text=True)
531 fp.write('[paths]\n')
531 fp.write('[paths]\n')
532
532
533 def addpathconfig(key, value):
533 def addpathconfig(key, value):
534 if value:
534 if value:
535 fp.write('%s = %s\n' % (key, value))
535 fp.write('%s = %s\n' % (key, value))
536 self._repo.ui.setconfig('paths', key, value)
536 self._repo.ui.setconfig('paths', key, value)
537
537
538 defpath = _abssource(self._repo, abort=False)
538 defpath = _abssource(self._repo, abort=False)
539 defpushpath = _abssource(self._repo, True, abort=False)
539 defpushpath = _abssource(self._repo, True, abort=False)
540 addpathconfig('default', defpath)
540 addpathconfig('default', defpath)
541 if defpath != defpushpath:
541 if defpath != defpushpath:
542 addpathconfig('default-push', defpushpath)
542 addpathconfig('default-push', defpushpath)
543 fp.close()
543 fp.close()
544
544
545 @annotatesubrepoerror
545 @annotatesubrepoerror
546 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
546 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
547 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
547 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
548 os.path.join(prefix, self._path), explicitonly)
548 os.path.join(prefix, self._path), explicitonly)
549
549
550 @annotatesubrepoerror
550 @annotatesubrepoerror
551 def status(self, rev2, **opts):
551 def status(self, rev2, **opts):
552 try:
552 try:
553 rev1 = self._state[1]
553 rev1 = self._state[1]
554 ctx1 = self._repo[rev1]
554 ctx1 = self._repo[rev1]
555 ctx2 = self._repo[rev2]
555 ctx2 = self._repo[rev2]
556 return self._repo.status(ctx1, ctx2, **opts)
556 return self._repo.status(ctx1, ctx2, **opts)
557 except error.RepoLookupError, inst:
557 except error.RepoLookupError, inst:
558 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
558 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
559 % (inst, subrelpath(self)))
559 % (inst, subrelpath(self)))
560 return [], [], [], [], [], [], []
560 return [], [], [], [], [], [], []
561
561
562 @annotatesubrepoerror
562 @annotatesubrepoerror
563 def diff(self, ui, diffopts, node2, match, prefix, **opts):
563 def diff(self, ui, diffopts, node2, match, prefix, **opts):
564 try:
564 try:
565 node1 = node.bin(self._state[1])
565 node1 = node.bin(self._state[1])
566 # We currently expect node2 to come from substate and be
566 # We currently expect node2 to come from substate and be
567 # in hex format
567 # in hex format
568 if node2 is not None:
568 if node2 is not None:
569 node2 = node.bin(node2)
569 node2 = node.bin(node2)
570 cmdutil.diffordiffstat(ui, self._repo, diffopts,
570 cmdutil.diffordiffstat(ui, self._repo, diffopts,
571 node1, node2, match,
571 node1, node2, match,
572 prefix=posixpath.join(prefix, self._path),
572 prefix=posixpath.join(prefix, self._path),
573 listsubrepos=True, **opts)
573 listsubrepos=True, **opts)
574 except error.RepoLookupError, inst:
574 except error.RepoLookupError, inst:
575 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
575 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
576 % (inst, subrelpath(self)))
576 % (inst, subrelpath(self)))
577
577
578 @annotatesubrepoerror
578 @annotatesubrepoerror
579 def archive(self, ui, archiver, prefix, match=None):
579 def archive(self, ui, archiver, prefix, match=None):
580 self._get(self._state + ('hg',))
580 self._get(self._state + ('hg',))
581 abstractsubrepo.archive(self, ui, archiver, prefix, match)
581 abstractsubrepo.archive(self, ui, archiver, prefix, match)
582
582
583 rev = self._state[1]
583 rev = self._state[1]
584 ctx = self._repo[rev]
584 ctx = self._repo[rev]
585 for subpath in ctx.substate:
585 for subpath in ctx.substate:
586 s = subrepo(ctx, subpath)
586 s = subrepo(ctx, subpath)
587 submatch = matchmod.narrowmatcher(subpath, match)
587 submatch = matchmod.narrowmatcher(subpath, match)
588 s.archive(ui, archiver, os.path.join(prefix, self._path), submatch)
588 s.archive(ui, archiver, os.path.join(prefix, self._path), submatch)
589
589
590 @annotatesubrepoerror
590 @annotatesubrepoerror
591 def dirty(self, ignoreupdate=False):
591 def dirty(self, ignoreupdate=False):
592 r = self._state[1]
592 r = self._state[1]
593 if r == '' and not ignoreupdate: # no state recorded
593 if r == '' and not ignoreupdate: # no state recorded
594 return True
594 return True
595 w = self._repo[None]
595 w = self._repo[None]
596 if r != w.p1().hex() and not ignoreupdate:
596 if r != w.p1().hex() and not ignoreupdate:
597 # different version checked out
597 # different version checked out
598 return True
598 return True
599 return w.dirty() # working directory changed
599 return w.dirty() # working directory changed
600
600
601 def basestate(self):
601 def basestate(self):
602 return self._repo['.'].hex()
602 return self._repo['.'].hex()
603
603
604 def checknested(self, path):
604 def checknested(self, path):
605 return self._repo._checknested(self._repo.wjoin(path))
605 return self._repo._checknested(self._repo.wjoin(path))
606
606
607 @annotatesubrepoerror
607 @annotatesubrepoerror
608 def commit(self, text, user, date):
608 def commit(self, text, user, date):
609 # don't bother committing in the subrepo if it's only been
609 # don't bother committing in the subrepo if it's only been
610 # updated
610 # updated
611 if not self.dirty(True):
611 if not self.dirty(True):
612 return self._repo['.'].hex()
612 return self._repo['.'].hex()
613 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
613 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
614 n = self._repo.commit(text, user, date)
614 n = self._repo.commit(text, user, date)
615 if not n:
615 if not n:
616 return self._repo['.'].hex() # different version checked out
616 return self._repo['.'].hex() # different version checked out
617 return node.hex(n)
617 return node.hex(n)
618
618
619 @annotatesubrepoerror
619 @annotatesubrepoerror
620 def remove(self):
620 def remove(self):
621 # we can't fully delete the repository as it may contain
621 # we can't fully delete the repository as it may contain
622 # local-only history
622 # local-only history
623 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
623 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
624 hg.clean(self._repo, node.nullid, False)
624 hg.clean(self._repo, node.nullid, False)
625
625
626 def _get(self, state):
626 def _get(self, state):
627 source, revision, kind = state
627 source, revision, kind = state
628 if revision not in self._repo:
628 if revision not in self._repo:
629 self._repo._subsource = source
629 self._repo._subsource = source
630 srcurl = _abssource(self._repo)
630 srcurl = _abssource(self._repo)
631 other = hg.peer(self._repo, {}, srcurl)
631 other = hg.peer(self._repo, {}, srcurl)
632 if len(self._repo) == 0:
632 if len(self._repo) == 0:
633 self._repo.ui.status(_('cloning subrepo %s from %s\n')
633 self._repo.ui.status(_('cloning subrepo %s from %s\n')
634 % (subrelpath(self), srcurl))
634 % (subrelpath(self), srcurl))
635 parentrepo = self._repo._subparent
635 parentrepo = self._repo._subparent
636 shutil.rmtree(self._repo.path)
636 shutil.rmtree(self._repo.path)
637 other, cloned = hg.clone(self._repo._subparent.baseui, {},
637 other, cloned = hg.clone(self._repo._subparent.baseui, {},
638 other, self._repo.root,
638 other, self._repo.root,
639 update=False)
639 update=False)
640 self._repo = cloned.local()
640 self._repo = cloned.local()
641 self._initrepo(parentrepo, source, create=True)
641 self._initrepo(parentrepo, source, create=True)
642 self._cachestorehash(srcurl)
642 self._cachestorehash(srcurl)
643 else:
643 else:
644 self._repo.ui.status(_('pulling subrepo %s from %s\n')
644 self._repo.ui.status(_('pulling subrepo %s from %s\n')
645 % (subrelpath(self), srcurl))
645 % (subrelpath(self), srcurl))
646 cleansub = self.storeclean(srcurl)
646 cleansub = self.storeclean(srcurl)
647 remotebookmarks = other.listkeys('bookmarks')
647 remotebookmarks = other.listkeys('bookmarks')
648 self._repo.pull(other)
648 self._repo.pull(other)
649 bookmarks.updatefromremote(self._repo.ui, self._repo,
649 bookmarks.updatefromremote(self._repo.ui, self._repo,
650 remotebookmarks, srcurl)
650 remotebookmarks, srcurl)
651 if cleansub:
651 if cleansub:
652 # keep the repo clean after pull
652 # keep the repo clean after pull
653 self._cachestorehash(srcurl)
653 self._cachestorehash(srcurl)
654
654
655 @annotatesubrepoerror
655 @annotatesubrepoerror
656 def get(self, state, overwrite=False):
656 def get(self, state, overwrite=False):
657 self._get(state)
657 self._get(state)
658 source, revision, kind = state
658 source, revision, kind = state
659 self._repo.ui.debug("getting subrepo %s\n" % self._path)
659 self._repo.ui.debug("getting subrepo %s\n" % self._path)
660 hg.updaterepo(self._repo, revision, overwrite)
660 hg.updaterepo(self._repo, revision, overwrite)
661
661
662 @annotatesubrepoerror
662 @annotatesubrepoerror
663 def merge(self, state):
663 def merge(self, state):
664 self._get(state)
664 self._get(state)
665 cur = self._repo['.']
665 cur = self._repo['.']
666 dst = self._repo[state[1]]
666 dst = self._repo[state[1]]
667 anc = dst.ancestor(cur)
667 anc = dst.ancestor(cur)
668
668
669 def mergefunc():
669 def mergefunc():
670 if anc == cur and dst.branch() == cur.branch():
670 if anc == cur and dst.branch() == cur.branch():
671 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
671 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
672 hg.update(self._repo, state[1])
672 hg.update(self._repo, state[1])
673 elif anc == dst:
673 elif anc == dst:
674 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
674 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
675 else:
675 else:
676 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
676 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
677 hg.merge(self._repo, state[1], remind=False)
677 hg.merge(self._repo, state[1], remind=False)
678
678
679 wctx = self._repo[None]
679 wctx = self._repo[None]
680 if self.dirty():
680 if self.dirty():
681 if anc != dst:
681 if anc != dst:
682 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
682 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
683 mergefunc()
683 mergefunc()
684 else:
684 else:
685 mergefunc()
685 mergefunc()
686 else:
686 else:
687 mergefunc()
687 mergefunc()
688
688
689 @annotatesubrepoerror
689 @annotatesubrepoerror
690 def push(self, opts):
690 def push(self, opts):
691 force = opts.get('force')
691 force = opts.get('force')
692 newbranch = opts.get('new_branch')
692 newbranch = opts.get('new_branch')
693 ssh = opts.get('ssh')
693 ssh = opts.get('ssh')
694
694
695 # push subrepos depth-first for coherent ordering
695 # push subrepos depth-first for coherent ordering
696 c = self._repo['']
696 c = self._repo['']
697 subs = c.substate # only repos that are committed
697 subs = c.substate # only repos that are committed
698 for s in sorted(subs):
698 for s in sorted(subs):
699 if c.sub(s).push(opts) == 0:
699 if c.sub(s).push(opts) == 0:
700 return False
700 return False
701
701
702 dsturl = _abssource(self._repo, True)
702 dsturl = _abssource(self._repo, True)
703 if not force:
703 if not force:
704 if self.storeclean(dsturl):
704 if self.storeclean(dsturl):
705 self._repo.ui.status(
705 self._repo.ui.status(
706 _('no changes made to subrepo %s since last push to %s\n')
706 _('no changes made to subrepo %s since last push to %s\n')
707 % (subrelpath(self), dsturl))
707 % (subrelpath(self), dsturl))
708 return None
708 return None
709 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
709 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
710 (subrelpath(self), dsturl))
710 (subrelpath(self), dsturl))
711 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
711 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
712 res = self._repo.push(other, force, newbranch=newbranch)
712 res = self._repo.push(other, force, newbranch=newbranch)
713
713
714 # the repo is now clean
714 # the repo is now clean
715 self._cachestorehash(dsturl)
715 self._cachestorehash(dsturl)
716 return res
716 return res
717
717
718 @annotatesubrepoerror
718 @annotatesubrepoerror
719 def outgoing(self, ui, dest, opts):
719 def outgoing(self, ui, dest, opts):
720 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
720 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
721
721
722 @annotatesubrepoerror
722 @annotatesubrepoerror
723 def incoming(self, ui, source, opts):
723 def incoming(self, ui, source, opts):
724 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
724 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
725
725
726 @annotatesubrepoerror
726 @annotatesubrepoerror
727 def files(self):
727 def files(self):
728 rev = self._state[1]
728 rev = self._state[1]
729 ctx = self._repo[rev]
729 ctx = self._repo[rev]
730 return ctx.manifest()
730 return ctx.manifest()
731
731
732 def filedata(self, name):
732 def filedata(self, name):
733 rev = self._state[1]
733 rev = self._state[1]
734 return self._repo[rev][name].data()
734 return self._repo[rev][name].data()
735
735
736 def fileflags(self, name):
736 def fileflags(self, name):
737 rev = self._state[1]
737 rev = self._state[1]
738 ctx = self._repo[rev]
738 ctx = self._repo[rev]
739 return ctx.flags(name)
739 return ctx.flags(name)
740
740
741 def walk(self, match):
741 def walk(self, match):
742 ctx = self._repo[None]
742 ctx = self._repo[None]
743 return ctx.walk(match)
743 return ctx.walk(match)
744
744
745 @annotatesubrepoerror
745 @annotatesubrepoerror
746 def forget(self, ui, match, prefix):
746 def forget(self, ui, match, prefix):
747 return cmdutil.forget(ui, self._repo, match,
747 return cmdutil.forget(ui, self._repo, match,
748 os.path.join(prefix, self._path), True)
748 os.path.join(prefix, self._path), True)
749
749
750 @annotatesubrepoerror
750 @annotatesubrepoerror
751 def revert(self, ui, substate, *pats, **opts):
751 def revert(self, ui, substate, *pats, **opts):
752 # reverting a subrepo is a 2 step process:
752 # reverting a subrepo is a 2 step process:
753 # 1. if the no_backup is not set, revert all modified
753 # 1. if the no_backup is not set, revert all modified
754 # files inside the subrepo
754 # files inside the subrepo
755 # 2. update the subrepo to the revision specified in
755 # 2. update the subrepo to the revision specified in
756 # the corresponding substate dictionary
756 # the corresponding substate dictionary
757 ui.status(_('reverting subrepo %s\n') % substate[0])
757 ui.status(_('reverting subrepo %s\n') % substate[0])
758 if not opts.get('no_backup'):
758 if not opts.get('no_backup'):
759 # Revert all files on the subrepo, creating backups
759 # Revert all files on the subrepo, creating backups
760 # Note that this will not recursively revert subrepos
760 # Note that this will not recursively revert subrepos
761 # We could do it if there was a set:subrepos() predicate
761 # We could do it if there was a set:subrepos() predicate
762 opts = opts.copy()
762 opts = opts.copy()
763 opts['date'] = None
763 opts['date'] = None
764 opts['rev'] = substate[1]
764 opts['rev'] = substate[1]
765
765
766 pats = []
766 pats = []
767 if not opts['all']:
767 if not opts.get('all'):
768 pats = ['set:modified()']
768 pats = ['set:modified()']
769 self.filerevert(ui, *pats, **opts)
769 self.filerevert(ui, *pats, **opts)
770
770
771 # Update the repo to the revision specified in the given substate
771 # Update the repo to the revision specified in the given substate
772 self.get(substate, overwrite=True)
772 self.get(substate, overwrite=True)
773
773
774 def filerevert(self, ui, *pats, **opts):
774 def filerevert(self, ui, *pats, **opts):
775 ctx = self._repo[opts['rev']]
775 ctx = self._repo[opts['rev']]
776 parents = self._repo.dirstate.parents()
776 parents = self._repo.dirstate.parents()
777 if opts['all']:
777 if opts.get('all'):
778 pats = ['set:modified()']
778 pats = ['set:modified()']
779 else:
779 else:
780 pats = []
780 pats = []
781 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
781 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
782
782
783 class svnsubrepo(abstractsubrepo):
783 class svnsubrepo(abstractsubrepo):
784 def __init__(self, ctx, path, state):
784 def __init__(self, ctx, path, state):
785 self._path = path
785 self._path = path
786 self._state = state
786 self._state = state
787 self._ctx = ctx
787 self._ctx = ctx
788 self._ui = ctx._repo.ui
788 self._ui = ctx._repo.ui
789 self._exe = util.findexe('svn')
789 self._exe = util.findexe('svn')
790 if not self._exe:
790 if not self._exe:
791 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
791 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
792 % self._path)
792 % self._path)
793
793
794 def _svncommand(self, commands, filename='', failok=False):
794 def _svncommand(self, commands, filename='', failok=False):
795 cmd = [self._exe]
795 cmd = [self._exe]
796 extrakw = {}
796 extrakw = {}
797 if not self._ui.interactive():
797 if not self._ui.interactive():
798 # Making stdin be a pipe should prevent svn from behaving
798 # Making stdin be a pipe should prevent svn from behaving
799 # interactively even if we can't pass --non-interactive.
799 # interactively even if we can't pass --non-interactive.
800 extrakw['stdin'] = subprocess.PIPE
800 extrakw['stdin'] = subprocess.PIPE
801 # Starting in svn 1.5 --non-interactive is a global flag
801 # Starting in svn 1.5 --non-interactive is a global flag
802 # instead of being per-command, but we need to support 1.4 so
802 # instead of being per-command, but we need to support 1.4 so
803 # we have to be intelligent about what commands take
803 # we have to be intelligent about what commands take
804 # --non-interactive.
804 # --non-interactive.
805 if commands[0] in ('update', 'checkout', 'commit'):
805 if commands[0] in ('update', 'checkout', 'commit'):
806 cmd.append('--non-interactive')
806 cmd.append('--non-interactive')
807 cmd.extend(commands)
807 cmd.extend(commands)
808 if filename is not None:
808 if filename is not None:
809 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
809 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
810 cmd.append(path)
810 cmd.append(path)
811 env = dict(os.environ)
811 env = dict(os.environ)
812 # Avoid localized output, preserve current locale for everything else.
812 # Avoid localized output, preserve current locale for everything else.
813 lc_all = env.get('LC_ALL')
813 lc_all = env.get('LC_ALL')
814 if lc_all:
814 if lc_all:
815 env['LANG'] = lc_all
815 env['LANG'] = lc_all
816 del env['LC_ALL']
816 del env['LC_ALL']
817 env['LC_MESSAGES'] = 'C'
817 env['LC_MESSAGES'] = 'C'
818 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
818 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
819 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
819 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
820 universal_newlines=True, env=env, **extrakw)
820 universal_newlines=True, env=env, **extrakw)
821 stdout, stderr = p.communicate()
821 stdout, stderr = p.communicate()
822 stderr = stderr.strip()
822 stderr = stderr.strip()
823 if not failok:
823 if not failok:
824 if p.returncode:
824 if p.returncode:
825 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
825 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
826 if stderr:
826 if stderr:
827 self._ui.warn(stderr + '\n')
827 self._ui.warn(stderr + '\n')
828 return stdout, stderr
828 return stdout, stderr
829
829
830 @propertycache
830 @propertycache
831 def _svnversion(self):
831 def _svnversion(self):
832 output, err = self._svncommand(['--version', '--quiet'], filename=None)
832 output, err = self._svncommand(['--version', '--quiet'], filename=None)
833 m = re.search(r'^(\d+)\.(\d+)', output)
833 m = re.search(r'^(\d+)\.(\d+)', output)
834 if not m:
834 if not m:
835 raise util.Abort(_('cannot retrieve svn tool version'))
835 raise util.Abort(_('cannot retrieve svn tool version'))
836 return (int(m.group(1)), int(m.group(2)))
836 return (int(m.group(1)), int(m.group(2)))
837
837
838 def _wcrevs(self):
838 def _wcrevs(self):
839 # Get the working directory revision as well as the last
839 # Get the working directory revision as well as the last
840 # commit revision so we can compare the subrepo state with
840 # commit revision so we can compare the subrepo state with
841 # both. We used to store the working directory one.
841 # both. We used to store the working directory one.
842 output, err = self._svncommand(['info', '--xml'])
842 output, err = self._svncommand(['info', '--xml'])
843 doc = xml.dom.minidom.parseString(output)
843 doc = xml.dom.minidom.parseString(output)
844 entries = doc.getElementsByTagName('entry')
844 entries = doc.getElementsByTagName('entry')
845 lastrev, rev = '0', '0'
845 lastrev, rev = '0', '0'
846 if entries:
846 if entries:
847 rev = str(entries[0].getAttribute('revision')) or '0'
847 rev = str(entries[0].getAttribute('revision')) or '0'
848 commits = entries[0].getElementsByTagName('commit')
848 commits = entries[0].getElementsByTagName('commit')
849 if commits:
849 if commits:
850 lastrev = str(commits[0].getAttribute('revision')) or '0'
850 lastrev = str(commits[0].getAttribute('revision')) or '0'
851 return (lastrev, rev)
851 return (lastrev, rev)
852
852
853 def _wcrev(self):
853 def _wcrev(self):
854 return self._wcrevs()[0]
854 return self._wcrevs()[0]
855
855
856 def _wcchanged(self):
856 def _wcchanged(self):
857 """Return (changes, extchanges, missing) where changes is True
857 """Return (changes, extchanges, missing) where changes is True
858 if the working directory was changed, extchanges is
858 if the working directory was changed, extchanges is
859 True if any of these changes concern an external entry and missing
859 True if any of these changes concern an external entry and missing
860 is True if any change is a missing entry.
860 is True if any change is a missing entry.
861 """
861 """
862 output, err = self._svncommand(['status', '--xml'])
862 output, err = self._svncommand(['status', '--xml'])
863 externals, changes, missing = [], [], []
863 externals, changes, missing = [], [], []
864 doc = xml.dom.minidom.parseString(output)
864 doc = xml.dom.minidom.parseString(output)
865 for e in doc.getElementsByTagName('entry'):
865 for e in doc.getElementsByTagName('entry'):
866 s = e.getElementsByTagName('wc-status')
866 s = e.getElementsByTagName('wc-status')
867 if not s:
867 if not s:
868 continue
868 continue
869 item = s[0].getAttribute('item')
869 item = s[0].getAttribute('item')
870 props = s[0].getAttribute('props')
870 props = s[0].getAttribute('props')
871 path = e.getAttribute('path')
871 path = e.getAttribute('path')
872 if item == 'external':
872 if item == 'external':
873 externals.append(path)
873 externals.append(path)
874 elif item == 'missing':
874 elif item == 'missing':
875 missing.append(path)
875 missing.append(path)
876 if (item not in ('', 'normal', 'unversioned', 'external')
876 if (item not in ('', 'normal', 'unversioned', 'external')
877 or props not in ('', 'none', 'normal')):
877 or props not in ('', 'none', 'normal')):
878 changes.append(path)
878 changes.append(path)
879 for path in changes:
879 for path in changes:
880 for ext in externals:
880 for ext in externals:
881 if path == ext or path.startswith(ext + os.sep):
881 if path == ext or path.startswith(ext + os.sep):
882 return True, True, bool(missing)
882 return True, True, bool(missing)
883 return bool(changes), False, bool(missing)
883 return bool(changes), False, bool(missing)
884
884
885 def dirty(self, ignoreupdate=False):
885 def dirty(self, ignoreupdate=False):
886 if not self._wcchanged()[0]:
886 if not self._wcchanged()[0]:
887 if self._state[1] in self._wcrevs() or ignoreupdate:
887 if self._state[1] in self._wcrevs() or ignoreupdate:
888 return False
888 return False
889 return True
889 return True
890
890
891 def basestate(self):
891 def basestate(self):
892 lastrev, rev = self._wcrevs()
892 lastrev, rev = self._wcrevs()
893 if lastrev != rev:
893 if lastrev != rev:
894 # Last committed rev is not the same than rev. We would
894 # Last committed rev is not the same than rev. We would
895 # like to take lastrev but we do not know if the subrepo
895 # like to take lastrev but we do not know if the subrepo
896 # URL exists at lastrev. Test it and fallback to rev it
896 # URL exists at lastrev. Test it and fallback to rev it
897 # is not there.
897 # is not there.
898 try:
898 try:
899 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
899 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
900 return lastrev
900 return lastrev
901 except error.Abort:
901 except error.Abort:
902 pass
902 pass
903 return rev
903 return rev
904
904
905 @annotatesubrepoerror
905 @annotatesubrepoerror
906 def commit(self, text, user, date):
906 def commit(self, text, user, date):
907 # user and date are out of our hands since svn is centralized
907 # user and date are out of our hands since svn is centralized
908 changed, extchanged, missing = self._wcchanged()
908 changed, extchanged, missing = self._wcchanged()
909 if not changed:
909 if not changed:
910 return self.basestate()
910 return self.basestate()
911 if extchanged:
911 if extchanged:
912 # Do not try to commit externals
912 # Do not try to commit externals
913 raise util.Abort(_('cannot commit svn externals'))
913 raise util.Abort(_('cannot commit svn externals'))
914 if missing:
914 if missing:
915 # svn can commit with missing entries but aborting like hg
915 # svn can commit with missing entries but aborting like hg
916 # seems a better approach.
916 # seems a better approach.
917 raise util.Abort(_('cannot commit missing svn entries'))
917 raise util.Abort(_('cannot commit missing svn entries'))
918 commitinfo, err = self._svncommand(['commit', '-m', text])
918 commitinfo, err = self._svncommand(['commit', '-m', text])
919 self._ui.status(commitinfo)
919 self._ui.status(commitinfo)
920 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
920 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
921 if not newrev:
921 if not newrev:
922 if not commitinfo.strip():
922 if not commitinfo.strip():
923 # Sometimes, our definition of "changed" differs from
923 # Sometimes, our definition of "changed" differs from
924 # svn one. For instance, svn ignores missing files
924 # svn one. For instance, svn ignores missing files
925 # when committing. If there are only missing files, no
925 # when committing. If there are only missing files, no
926 # commit is made, no output and no error code.
926 # commit is made, no output and no error code.
927 raise util.Abort(_('failed to commit svn changes'))
927 raise util.Abort(_('failed to commit svn changes'))
928 raise util.Abort(commitinfo.splitlines()[-1])
928 raise util.Abort(commitinfo.splitlines()[-1])
929 newrev = newrev.groups()[0]
929 newrev = newrev.groups()[0]
930 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
930 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
931 return newrev
931 return newrev
932
932
933 @annotatesubrepoerror
933 @annotatesubrepoerror
934 def remove(self):
934 def remove(self):
935 if self.dirty():
935 if self.dirty():
936 self._ui.warn(_('not removing repo %s because '
936 self._ui.warn(_('not removing repo %s because '
937 'it has changes.\n' % self._path))
937 'it has changes.\n' % self._path))
938 return
938 return
939 self._ui.note(_('removing subrepo %s\n') % self._path)
939 self._ui.note(_('removing subrepo %s\n') % self._path)
940
940
941 def onerror(function, path, excinfo):
941 def onerror(function, path, excinfo):
942 if function is not os.remove:
942 if function is not os.remove:
943 raise
943 raise
944 # read-only files cannot be unlinked under Windows
944 # read-only files cannot be unlinked under Windows
945 s = os.stat(path)
945 s = os.stat(path)
946 if (s.st_mode & stat.S_IWRITE) != 0:
946 if (s.st_mode & stat.S_IWRITE) != 0:
947 raise
947 raise
948 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
948 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
949 os.remove(path)
949 os.remove(path)
950
950
951 path = self._ctx._repo.wjoin(self._path)
951 path = self._ctx._repo.wjoin(self._path)
952 shutil.rmtree(path, onerror=onerror)
952 shutil.rmtree(path, onerror=onerror)
953 try:
953 try:
954 os.removedirs(os.path.dirname(path))
954 os.removedirs(os.path.dirname(path))
955 except OSError:
955 except OSError:
956 pass
956 pass
957
957
958 @annotatesubrepoerror
958 @annotatesubrepoerror
959 def get(self, state, overwrite=False):
959 def get(self, state, overwrite=False):
960 if overwrite:
960 if overwrite:
961 self._svncommand(['revert', '--recursive'])
961 self._svncommand(['revert', '--recursive'])
962 args = ['checkout']
962 args = ['checkout']
963 if self._svnversion >= (1, 5):
963 if self._svnversion >= (1, 5):
964 args.append('--force')
964 args.append('--force')
965 # The revision must be specified at the end of the URL to properly
965 # The revision must be specified at the end of the URL to properly
966 # update to a directory which has since been deleted and recreated.
966 # update to a directory which has since been deleted and recreated.
967 args.append('%s@%s' % (state[0], state[1]))
967 args.append('%s@%s' % (state[0], state[1]))
968 status, err = self._svncommand(args, failok=True)
968 status, err = self._svncommand(args, failok=True)
969 if not re.search('Checked out revision [0-9]+.', status):
969 if not re.search('Checked out revision [0-9]+.', status):
970 if ('is already a working copy for a different URL' in err
970 if ('is already a working copy for a different URL' in err
971 and (self._wcchanged()[:2] == (False, False))):
971 and (self._wcchanged()[:2] == (False, False))):
972 # obstructed but clean working copy, so just blow it away.
972 # obstructed but clean working copy, so just blow it away.
973 self.remove()
973 self.remove()
974 self.get(state, overwrite=False)
974 self.get(state, overwrite=False)
975 return
975 return
976 raise util.Abort((status or err).splitlines()[-1])
976 raise util.Abort((status or err).splitlines()[-1])
977 self._ui.status(status)
977 self._ui.status(status)
978
978
979 @annotatesubrepoerror
979 @annotatesubrepoerror
980 def merge(self, state):
980 def merge(self, state):
981 old = self._state[1]
981 old = self._state[1]
982 new = state[1]
982 new = state[1]
983 wcrev = self._wcrev()
983 wcrev = self._wcrev()
984 if new != wcrev:
984 if new != wcrev:
985 dirty = old == wcrev or self._wcchanged()[0]
985 dirty = old == wcrev or self._wcchanged()[0]
986 if _updateprompt(self._ui, self, dirty, wcrev, new):
986 if _updateprompt(self._ui, self, dirty, wcrev, new):
987 self.get(state, False)
987 self.get(state, False)
988
988
989 def push(self, opts):
989 def push(self, opts):
990 # push is a no-op for SVN
990 # push is a no-op for SVN
991 return True
991 return True
992
992
993 @annotatesubrepoerror
993 @annotatesubrepoerror
994 def files(self):
994 def files(self):
995 output = self._svncommand(['list', '--recursive', '--xml'])[0]
995 output = self._svncommand(['list', '--recursive', '--xml'])[0]
996 doc = xml.dom.minidom.parseString(output)
996 doc = xml.dom.minidom.parseString(output)
997 paths = []
997 paths = []
998 for e in doc.getElementsByTagName('entry'):
998 for e in doc.getElementsByTagName('entry'):
999 kind = str(e.getAttribute('kind'))
999 kind = str(e.getAttribute('kind'))
1000 if kind != 'file':
1000 if kind != 'file':
1001 continue
1001 continue
1002 name = ''.join(c.data for c
1002 name = ''.join(c.data for c
1003 in e.getElementsByTagName('name')[0].childNodes
1003 in e.getElementsByTagName('name')[0].childNodes
1004 if c.nodeType == c.TEXT_NODE)
1004 if c.nodeType == c.TEXT_NODE)
1005 paths.append(name.encode('utf-8'))
1005 paths.append(name.encode('utf-8'))
1006 return paths
1006 return paths
1007
1007
1008 def filedata(self, name):
1008 def filedata(self, name):
1009 return self._svncommand(['cat'], name)[0]
1009 return self._svncommand(['cat'], name)[0]
1010
1010
1011
1011
1012 class gitsubrepo(abstractsubrepo):
1012 class gitsubrepo(abstractsubrepo):
1013 def __init__(self, ctx, path, state):
1013 def __init__(self, ctx, path, state):
1014 self._state = state
1014 self._state = state
1015 self._ctx = ctx
1015 self._ctx = ctx
1016 self._path = path
1016 self._path = path
1017 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1017 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1018 self._abspath = ctx._repo.wjoin(path)
1018 self._abspath = ctx._repo.wjoin(path)
1019 self._subparent = ctx._repo
1019 self._subparent = ctx._repo
1020 self._ui = ctx._repo.ui
1020 self._ui = ctx._repo.ui
1021 self._ensuregit()
1021 self._ensuregit()
1022
1022
1023 def _ensuregit(self):
1023 def _ensuregit(self):
1024 try:
1024 try:
1025 self._gitexecutable = 'git'
1025 self._gitexecutable = 'git'
1026 out, err = self._gitnodir(['--version'])
1026 out, err = self._gitnodir(['--version'])
1027 except OSError, e:
1027 except OSError, e:
1028 if e.errno != 2 or os.name != 'nt':
1028 if e.errno != 2 or os.name != 'nt':
1029 raise
1029 raise
1030 self._gitexecutable = 'git.cmd'
1030 self._gitexecutable = 'git.cmd'
1031 out, err = self._gitnodir(['--version'])
1031 out, err = self._gitnodir(['--version'])
1032 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1032 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1033 if not m:
1033 if not m:
1034 self._ui.warn(_('cannot retrieve git version'))
1034 self._ui.warn(_('cannot retrieve git version'))
1035 return
1035 return
1036 version = (int(m.group(1)), m.group(2), m.group(3))
1036 version = (int(m.group(1)), m.group(2), m.group(3))
1037 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1037 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1038 # despite the docstring comment. For now, error on 1.4.0, warn on
1038 # despite the docstring comment. For now, error on 1.4.0, warn on
1039 # 1.5.0 but attempt to continue.
1039 # 1.5.0 but attempt to continue.
1040 if version < (1, 5, 0):
1040 if version < (1, 5, 0):
1041 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1041 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1042 elif version < (1, 6, 0):
1042 elif version < (1, 6, 0):
1043 self._ui.warn(_('git subrepo requires at least 1.6.0 or later'))
1043 self._ui.warn(_('git subrepo requires at least 1.6.0 or later'))
1044
1044
1045 def _gitcommand(self, commands, env=None, stream=False):
1045 def _gitcommand(self, commands, env=None, stream=False):
1046 return self._gitdir(commands, env=env, stream=stream)[0]
1046 return self._gitdir(commands, env=env, stream=stream)[0]
1047
1047
1048 def _gitdir(self, commands, env=None, stream=False):
1048 def _gitdir(self, commands, env=None, stream=False):
1049 return self._gitnodir(commands, env=env, stream=stream,
1049 return self._gitnodir(commands, env=env, stream=stream,
1050 cwd=self._abspath)
1050 cwd=self._abspath)
1051
1051
1052 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1052 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1053 """Calls the git command
1053 """Calls the git command
1054
1054
1055 The methods tries to call the git command. versions prior to 1.6.0
1055 The methods tries to call the git command. versions prior to 1.6.0
1056 are not supported and very probably fail.
1056 are not supported and very probably fail.
1057 """
1057 """
1058 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1058 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1059 # unless ui.quiet is set, print git's stderr,
1059 # unless ui.quiet is set, print git's stderr,
1060 # which is mostly progress and useful info
1060 # which is mostly progress and useful info
1061 errpipe = None
1061 errpipe = None
1062 if self._ui.quiet:
1062 if self._ui.quiet:
1063 errpipe = open(os.devnull, 'w')
1063 errpipe = open(os.devnull, 'w')
1064 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1064 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1065 cwd=cwd, env=env, close_fds=util.closefds,
1065 cwd=cwd, env=env, close_fds=util.closefds,
1066 stdout=subprocess.PIPE, stderr=errpipe)
1066 stdout=subprocess.PIPE, stderr=errpipe)
1067 if stream:
1067 if stream:
1068 return p.stdout, None
1068 return p.stdout, None
1069
1069
1070 retdata = p.stdout.read().strip()
1070 retdata = p.stdout.read().strip()
1071 # wait for the child to exit to avoid race condition.
1071 # wait for the child to exit to avoid race condition.
1072 p.wait()
1072 p.wait()
1073
1073
1074 if p.returncode != 0 and p.returncode != 1:
1074 if p.returncode != 0 and p.returncode != 1:
1075 # there are certain error codes that are ok
1075 # there are certain error codes that are ok
1076 command = commands[0]
1076 command = commands[0]
1077 if command in ('cat-file', 'symbolic-ref'):
1077 if command in ('cat-file', 'symbolic-ref'):
1078 return retdata, p.returncode
1078 return retdata, p.returncode
1079 # for all others, abort
1079 # for all others, abort
1080 raise util.Abort('git %s error %d in %s' %
1080 raise util.Abort('git %s error %d in %s' %
1081 (command, p.returncode, self._relpath))
1081 (command, p.returncode, self._relpath))
1082
1082
1083 return retdata, p.returncode
1083 return retdata, p.returncode
1084
1084
1085 def _gitmissing(self):
1085 def _gitmissing(self):
1086 return not os.path.exists(os.path.join(self._abspath, '.git'))
1086 return not os.path.exists(os.path.join(self._abspath, '.git'))
1087
1087
1088 def _gitstate(self):
1088 def _gitstate(self):
1089 return self._gitcommand(['rev-parse', 'HEAD'])
1089 return self._gitcommand(['rev-parse', 'HEAD'])
1090
1090
1091 def _gitcurrentbranch(self):
1091 def _gitcurrentbranch(self):
1092 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1092 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1093 if err:
1093 if err:
1094 current = None
1094 current = None
1095 return current
1095 return current
1096
1096
1097 def _gitremote(self, remote):
1097 def _gitremote(self, remote):
1098 out = self._gitcommand(['remote', 'show', '-n', remote])
1098 out = self._gitcommand(['remote', 'show', '-n', remote])
1099 line = out.split('\n')[1]
1099 line = out.split('\n')[1]
1100 i = line.index('URL: ') + len('URL: ')
1100 i = line.index('URL: ') + len('URL: ')
1101 return line[i:]
1101 return line[i:]
1102
1102
1103 def _githavelocally(self, revision):
1103 def _githavelocally(self, revision):
1104 out, code = self._gitdir(['cat-file', '-e', revision])
1104 out, code = self._gitdir(['cat-file', '-e', revision])
1105 return code == 0
1105 return code == 0
1106
1106
1107 def _gitisancestor(self, r1, r2):
1107 def _gitisancestor(self, r1, r2):
1108 base = self._gitcommand(['merge-base', r1, r2])
1108 base = self._gitcommand(['merge-base', r1, r2])
1109 return base == r1
1109 return base == r1
1110
1110
1111 def _gitisbare(self):
1111 def _gitisbare(self):
1112 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1112 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1113
1113
1114 def _gitupdatestat(self):
1114 def _gitupdatestat(self):
1115 """This must be run before git diff-index.
1115 """This must be run before git diff-index.
1116 diff-index only looks at changes to file stat;
1116 diff-index only looks at changes to file stat;
1117 this command looks at file contents and updates the stat."""
1117 this command looks at file contents and updates the stat."""
1118 self._gitcommand(['update-index', '-q', '--refresh'])
1118 self._gitcommand(['update-index', '-q', '--refresh'])
1119
1119
1120 def _gitbranchmap(self):
1120 def _gitbranchmap(self):
1121 '''returns 2 things:
1121 '''returns 2 things:
1122 a map from git branch to revision
1122 a map from git branch to revision
1123 a map from revision to branches'''
1123 a map from revision to branches'''
1124 branch2rev = {}
1124 branch2rev = {}
1125 rev2branch = {}
1125 rev2branch = {}
1126
1126
1127 out = self._gitcommand(['for-each-ref', '--format',
1127 out = self._gitcommand(['for-each-ref', '--format',
1128 '%(objectname) %(refname)'])
1128 '%(objectname) %(refname)'])
1129 for line in out.split('\n'):
1129 for line in out.split('\n'):
1130 revision, ref = line.split(' ')
1130 revision, ref = line.split(' ')
1131 if (not ref.startswith('refs/heads/') and
1131 if (not ref.startswith('refs/heads/') and
1132 not ref.startswith('refs/remotes/')):
1132 not ref.startswith('refs/remotes/')):
1133 continue
1133 continue
1134 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1134 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1135 continue # ignore remote/HEAD redirects
1135 continue # ignore remote/HEAD redirects
1136 branch2rev[ref] = revision
1136 branch2rev[ref] = revision
1137 rev2branch.setdefault(revision, []).append(ref)
1137 rev2branch.setdefault(revision, []).append(ref)
1138 return branch2rev, rev2branch
1138 return branch2rev, rev2branch
1139
1139
1140 def _gittracking(self, branches):
1140 def _gittracking(self, branches):
1141 'return map of remote branch to local tracking branch'
1141 'return map of remote branch to local tracking branch'
1142 # assumes no more than one local tracking branch for each remote
1142 # assumes no more than one local tracking branch for each remote
1143 tracking = {}
1143 tracking = {}
1144 for b in branches:
1144 for b in branches:
1145 if b.startswith('refs/remotes/'):
1145 if b.startswith('refs/remotes/'):
1146 continue
1146 continue
1147 bname = b.split('/', 2)[2]
1147 bname = b.split('/', 2)[2]
1148 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1148 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1149 if remote:
1149 if remote:
1150 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1150 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1151 tracking['refs/remotes/%s/%s' %
1151 tracking['refs/remotes/%s/%s' %
1152 (remote, ref.split('/', 2)[2])] = b
1152 (remote, ref.split('/', 2)[2])] = b
1153 return tracking
1153 return tracking
1154
1154
1155 def _abssource(self, source):
1155 def _abssource(self, source):
1156 if '://' not in source:
1156 if '://' not in source:
1157 # recognize the scp syntax as an absolute source
1157 # recognize the scp syntax as an absolute source
1158 colon = source.find(':')
1158 colon = source.find(':')
1159 if colon != -1 and '/' not in source[:colon]:
1159 if colon != -1 and '/' not in source[:colon]:
1160 return source
1160 return source
1161 self._subsource = source
1161 self._subsource = source
1162 return _abssource(self)
1162 return _abssource(self)
1163
1163
1164 def _fetch(self, source, revision):
1164 def _fetch(self, source, revision):
1165 if self._gitmissing():
1165 if self._gitmissing():
1166 source = self._abssource(source)
1166 source = self._abssource(source)
1167 self._ui.status(_('cloning subrepo %s from %s\n') %
1167 self._ui.status(_('cloning subrepo %s from %s\n') %
1168 (self._relpath, source))
1168 (self._relpath, source))
1169 self._gitnodir(['clone', source, self._abspath])
1169 self._gitnodir(['clone', source, self._abspath])
1170 if self._githavelocally(revision):
1170 if self._githavelocally(revision):
1171 return
1171 return
1172 self._ui.status(_('pulling subrepo %s from %s\n') %
1172 self._ui.status(_('pulling subrepo %s from %s\n') %
1173 (self._relpath, self._gitremote('origin')))
1173 (self._relpath, self._gitremote('origin')))
1174 # try only origin: the originally cloned repo
1174 # try only origin: the originally cloned repo
1175 self._gitcommand(['fetch'])
1175 self._gitcommand(['fetch'])
1176 if not self._githavelocally(revision):
1176 if not self._githavelocally(revision):
1177 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1177 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1178 (revision, self._relpath))
1178 (revision, self._relpath))
1179
1179
1180 @annotatesubrepoerror
1180 @annotatesubrepoerror
1181 def dirty(self, ignoreupdate=False):
1181 def dirty(self, ignoreupdate=False):
1182 if self._gitmissing():
1182 if self._gitmissing():
1183 return self._state[1] != ''
1183 return self._state[1] != ''
1184 if self._gitisbare():
1184 if self._gitisbare():
1185 return True
1185 return True
1186 if not ignoreupdate and self._state[1] != self._gitstate():
1186 if not ignoreupdate and self._state[1] != self._gitstate():
1187 # different version checked out
1187 # different version checked out
1188 return True
1188 return True
1189 # check for staged changes or modified files; ignore untracked files
1189 # check for staged changes or modified files; ignore untracked files
1190 self._gitupdatestat()
1190 self._gitupdatestat()
1191 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1191 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1192 return code == 1
1192 return code == 1
1193
1193
1194 def basestate(self):
1194 def basestate(self):
1195 return self._gitstate()
1195 return self._gitstate()
1196
1196
1197 @annotatesubrepoerror
1197 @annotatesubrepoerror
1198 def get(self, state, overwrite=False):
1198 def get(self, state, overwrite=False):
1199 source, revision, kind = state
1199 source, revision, kind = state
1200 if not revision:
1200 if not revision:
1201 self.remove()
1201 self.remove()
1202 return
1202 return
1203 self._fetch(source, revision)
1203 self._fetch(source, revision)
1204 # if the repo was set to be bare, unbare it
1204 # if the repo was set to be bare, unbare it
1205 if self._gitisbare():
1205 if self._gitisbare():
1206 self._gitcommand(['config', 'core.bare', 'false'])
1206 self._gitcommand(['config', 'core.bare', 'false'])
1207 if self._gitstate() == revision:
1207 if self._gitstate() == revision:
1208 self._gitcommand(['reset', '--hard', 'HEAD'])
1208 self._gitcommand(['reset', '--hard', 'HEAD'])
1209 return
1209 return
1210 elif self._gitstate() == revision:
1210 elif self._gitstate() == revision:
1211 if overwrite:
1211 if overwrite:
1212 # first reset the index to unmark new files for commit, because
1212 # first reset the index to unmark new files for commit, because
1213 # reset --hard will otherwise throw away files added for commit,
1213 # reset --hard will otherwise throw away files added for commit,
1214 # not just unmark them.
1214 # not just unmark them.
1215 self._gitcommand(['reset', 'HEAD'])
1215 self._gitcommand(['reset', 'HEAD'])
1216 self._gitcommand(['reset', '--hard', 'HEAD'])
1216 self._gitcommand(['reset', '--hard', 'HEAD'])
1217 return
1217 return
1218 branch2rev, rev2branch = self._gitbranchmap()
1218 branch2rev, rev2branch = self._gitbranchmap()
1219
1219
1220 def checkout(args):
1220 def checkout(args):
1221 cmd = ['checkout']
1221 cmd = ['checkout']
1222 if overwrite:
1222 if overwrite:
1223 # first reset the index to unmark new files for commit, because
1223 # first reset the index to unmark new files for commit, because
1224 # the -f option will otherwise throw away files added for
1224 # the -f option will otherwise throw away files added for
1225 # commit, not just unmark them.
1225 # commit, not just unmark them.
1226 self._gitcommand(['reset', 'HEAD'])
1226 self._gitcommand(['reset', 'HEAD'])
1227 cmd.append('-f')
1227 cmd.append('-f')
1228 self._gitcommand(cmd + args)
1228 self._gitcommand(cmd + args)
1229
1229
1230 def rawcheckout():
1230 def rawcheckout():
1231 # no branch to checkout, check it out with no branch
1231 # no branch to checkout, check it out with no branch
1232 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1232 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1233 self._relpath)
1233 self._relpath)
1234 self._ui.warn(_('check out a git branch if you intend '
1234 self._ui.warn(_('check out a git branch if you intend '
1235 'to make changes\n'))
1235 'to make changes\n'))
1236 checkout(['-q', revision])
1236 checkout(['-q', revision])
1237
1237
1238 if revision not in rev2branch:
1238 if revision not in rev2branch:
1239 rawcheckout()
1239 rawcheckout()
1240 return
1240 return
1241 branches = rev2branch[revision]
1241 branches = rev2branch[revision]
1242 firstlocalbranch = None
1242 firstlocalbranch = None
1243 for b in branches:
1243 for b in branches:
1244 if b == 'refs/heads/master':
1244 if b == 'refs/heads/master':
1245 # master trumps all other branches
1245 # master trumps all other branches
1246 checkout(['refs/heads/master'])
1246 checkout(['refs/heads/master'])
1247 return
1247 return
1248 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1248 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1249 firstlocalbranch = b
1249 firstlocalbranch = b
1250 if firstlocalbranch:
1250 if firstlocalbranch:
1251 checkout([firstlocalbranch])
1251 checkout([firstlocalbranch])
1252 return
1252 return
1253
1253
1254 tracking = self._gittracking(branch2rev.keys())
1254 tracking = self._gittracking(branch2rev.keys())
1255 # choose a remote branch already tracked if possible
1255 # choose a remote branch already tracked if possible
1256 remote = branches[0]
1256 remote = branches[0]
1257 if remote not in tracking:
1257 if remote not in tracking:
1258 for b in branches:
1258 for b in branches:
1259 if b in tracking:
1259 if b in tracking:
1260 remote = b
1260 remote = b
1261 break
1261 break
1262
1262
1263 if remote not in tracking:
1263 if remote not in tracking:
1264 # create a new local tracking branch
1264 # create a new local tracking branch
1265 local = remote.split('/', 2)[2]
1265 local = remote.split('/', 2)[2]
1266 checkout(['-b', local, remote])
1266 checkout(['-b', local, remote])
1267 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1267 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1268 # When updating to a tracked remote branch,
1268 # When updating to a tracked remote branch,
1269 # if the local tracking branch is downstream of it,
1269 # if the local tracking branch is downstream of it,
1270 # a normal `git pull` would have performed a "fast-forward merge"
1270 # a normal `git pull` would have performed a "fast-forward merge"
1271 # which is equivalent to updating the local branch to the remote.
1271 # which is equivalent to updating the local branch to the remote.
1272 # Since we are only looking at branching at update, we need to
1272 # Since we are only looking at branching at update, we need to
1273 # detect this situation and perform this action lazily.
1273 # detect this situation and perform this action lazily.
1274 if tracking[remote] != self._gitcurrentbranch():
1274 if tracking[remote] != self._gitcurrentbranch():
1275 checkout([tracking[remote]])
1275 checkout([tracking[remote]])
1276 self._gitcommand(['merge', '--ff', remote])
1276 self._gitcommand(['merge', '--ff', remote])
1277 else:
1277 else:
1278 # a real merge would be required, just checkout the revision
1278 # a real merge would be required, just checkout the revision
1279 rawcheckout()
1279 rawcheckout()
1280
1280
1281 @annotatesubrepoerror
1281 @annotatesubrepoerror
1282 def commit(self, text, user, date):
1282 def commit(self, text, user, date):
1283 if self._gitmissing():
1283 if self._gitmissing():
1284 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1284 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1285 cmd = ['commit', '-a', '-m', text]
1285 cmd = ['commit', '-a', '-m', text]
1286 env = os.environ.copy()
1286 env = os.environ.copy()
1287 if user:
1287 if user:
1288 cmd += ['--author', user]
1288 cmd += ['--author', user]
1289 if date:
1289 if date:
1290 # git's date parser silently ignores when seconds < 1e9
1290 # git's date parser silently ignores when seconds < 1e9
1291 # convert to ISO8601
1291 # convert to ISO8601
1292 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1292 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1293 '%Y-%m-%dT%H:%M:%S %1%2')
1293 '%Y-%m-%dT%H:%M:%S %1%2')
1294 self._gitcommand(cmd, env=env)
1294 self._gitcommand(cmd, env=env)
1295 # make sure commit works otherwise HEAD might not exist under certain
1295 # make sure commit works otherwise HEAD might not exist under certain
1296 # circumstances
1296 # circumstances
1297 return self._gitstate()
1297 return self._gitstate()
1298
1298
1299 @annotatesubrepoerror
1299 @annotatesubrepoerror
1300 def merge(self, state):
1300 def merge(self, state):
1301 source, revision, kind = state
1301 source, revision, kind = state
1302 self._fetch(source, revision)
1302 self._fetch(source, revision)
1303 base = self._gitcommand(['merge-base', revision, self._state[1]])
1303 base = self._gitcommand(['merge-base', revision, self._state[1]])
1304 self._gitupdatestat()
1304 self._gitupdatestat()
1305 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1305 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1306
1306
1307 def mergefunc():
1307 def mergefunc():
1308 if base == revision:
1308 if base == revision:
1309 self.get(state) # fast forward merge
1309 self.get(state) # fast forward merge
1310 elif base != self._state[1]:
1310 elif base != self._state[1]:
1311 self._gitcommand(['merge', '--no-commit', revision])
1311 self._gitcommand(['merge', '--no-commit', revision])
1312
1312
1313 if self.dirty():
1313 if self.dirty():
1314 if self._gitstate() != revision:
1314 if self._gitstate() != revision:
1315 dirty = self._gitstate() == self._state[1] or code != 0
1315 dirty = self._gitstate() == self._state[1] or code != 0
1316 if _updateprompt(self._ui, self, dirty,
1316 if _updateprompt(self._ui, self, dirty,
1317 self._state[1][:7], revision[:7]):
1317 self._state[1][:7], revision[:7]):
1318 mergefunc()
1318 mergefunc()
1319 else:
1319 else:
1320 mergefunc()
1320 mergefunc()
1321
1321
1322 @annotatesubrepoerror
1322 @annotatesubrepoerror
1323 def push(self, opts):
1323 def push(self, opts):
1324 force = opts.get('force')
1324 force = opts.get('force')
1325
1325
1326 if not self._state[1]:
1326 if not self._state[1]:
1327 return True
1327 return True
1328 if self._gitmissing():
1328 if self._gitmissing():
1329 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1329 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1330 # if a branch in origin contains the revision, nothing to do
1330 # if a branch in origin contains the revision, nothing to do
1331 branch2rev, rev2branch = self._gitbranchmap()
1331 branch2rev, rev2branch = self._gitbranchmap()
1332 if self._state[1] in rev2branch:
1332 if self._state[1] in rev2branch:
1333 for b in rev2branch[self._state[1]]:
1333 for b in rev2branch[self._state[1]]:
1334 if b.startswith('refs/remotes/origin/'):
1334 if b.startswith('refs/remotes/origin/'):
1335 return True
1335 return True
1336 for b, revision in branch2rev.iteritems():
1336 for b, revision in branch2rev.iteritems():
1337 if b.startswith('refs/remotes/origin/'):
1337 if b.startswith('refs/remotes/origin/'):
1338 if self._gitisancestor(self._state[1], revision):
1338 if self._gitisancestor(self._state[1], revision):
1339 return True
1339 return True
1340 # otherwise, try to push the currently checked out branch
1340 # otherwise, try to push the currently checked out branch
1341 cmd = ['push']
1341 cmd = ['push']
1342 if force:
1342 if force:
1343 cmd.append('--force')
1343 cmd.append('--force')
1344
1344
1345 current = self._gitcurrentbranch()
1345 current = self._gitcurrentbranch()
1346 if current:
1346 if current:
1347 # determine if the current branch is even useful
1347 # determine if the current branch is even useful
1348 if not self._gitisancestor(self._state[1], current):
1348 if not self._gitisancestor(self._state[1], current):
1349 self._ui.warn(_('unrelated git branch checked out '
1349 self._ui.warn(_('unrelated git branch checked out '
1350 'in subrepo %s\n') % self._relpath)
1350 'in subrepo %s\n') % self._relpath)
1351 return False
1351 return False
1352 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1352 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1353 (current.split('/', 2)[2], self._relpath))
1353 (current.split('/', 2)[2], self._relpath))
1354 self._gitcommand(cmd + ['origin', current])
1354 self._gitcommand(cmd + ['origin', current])
1355 return True
1355 return True
1356 else:
1356 else:
1357 self._ui.warn(_('no branch checked out in subrepo %s\n'
1357 self._ui.warn(_('no branch checked out in subrepo %s\n'
1358 'cannot push revision %s\n') %
1358 'cannot push revision %s\n') %
1359 (self._relpath, self._state[1]))
1359 (self._relpath, self._state[1]))
1360 return False
1360 return False
1361
1361
1362 @annotatesubrepoerror
1362 @annotatesubrepoerror
1363 def remove(self):
1363 def remove(self):
1364 if self._gitmissing():
1364 if self._gitmissing():
1365 return
1365 return
1366 if self.dirty():
1366 if self.dirty():
1367 self._ui.warn(_('not removing repo %s because '
1367 self._ui.warn(_('not removing repo %s because '
1368 'it has changes.\n') % self._relpath)
1368 'it has changes.\n') % self._relpath)
1369 return
1369 return
1370 # we can't fully delete the repository as it may contain
1370 # we can't fully delete the repository as it may contain
1371 # local-only history
1371 # local-only history
1372 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1372 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1373 self._gitcommand(['config', 'core.bare', 'true'])
1373 self._gitcommand(['config', 'core.bare', 'true'])
1374 for f in os.listdir(self._abspath):
1374 for f in os.listdir(self._abspath):
1375 if f == '.git':
1375 if f == '.git':
1376 continue
1376 continue
1377 path = os.path.join(self._abspath, f)
1377 path = os.path.join(self._abspath, f)
1378 if os.path.isdir(path) and not os.path.islink(path):
1378 if os.path.isdir(path) and not os.path.islink(path):
1379 shutil.rmtree(path)
1379 shutil.rmtree(path)
1380 else:
1380 else:
1381 os.remove(path)
1381 os.remove(path)
1382
1382
1383 def archive(self, ui, archiver, prefix, match=None):
1383 def archive(self, ui, archiver, prefix, match=None):
1384 source, revision = self._state
1384 source, revision = self._state
1385 if not revision:
1385 if not revision:
1386 return
1386 return
1387 self._fetch(source, revision)
1387 self._fetch(source, revision)
1388
1388
1389 # Parse git's native archive command.
1389 # Parse git's native archive command.
1390 # This should be much faster than manually traversing the trees
1390 # This should be much faster than manually traversing the trees
1391 # and objects with many subprocess calls.
1391 # and objects with many subprocess calls.
1392 tarstream = self._gitcommand(['archive', revision], stream=True)
1392 tarstream = self._gitcommand(['archive', revision], stream=True)
1393 tar = tarfile.open(fileobj=tarstream, mode='r|')
1393 tar = tarfile.open(fileobj=tarstream, mode='r|')
1394 relpath = subrelpath(self)
1394 relpath = subrelpath(self)
1395 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1395 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1396 for i, info in enumerate(tar):
1396 for i, info in enumerate(tar):
1397 if info.isdir():
1397 if info.isdir():
1398 continue
1398 continue
1399 if match and not match(info.name):
1399 if match and not match(info.name):
1400 continue
1400 continue
1401 if info.issym():
1401 if info.issym():
1402 data = info.linkname
1402 data = info.linkname
1403 else:
1403 else:
1404 data = tar.extractfile(info).read()
1404 data = tar.extractfile(info).read()
1405 archiver.addfile(os.path.join(prefix, self._path, info.name),
1405 archiver.addfile(os.path.join(prefix, self._path, info.name),
1406 info.mode, info.issym(), data)
1406 info.mode, info.issym(), data)
1407 ui.progress(_('archiving (%s)') % relpath, i + 1,
1407 ui.progress(_('archiving (%s)') % relpath, i + 1,
1408 unit=_('files'))
1408 unit=_('files'))
1409 ui.progress(_('archiving (%s)') % relpath, None)
1409 ui.progress(_('archiving (%s)') % relpath, None)
1410
1410
1411
1411
1412 @annotatesubrepoerror
1412 @annotatesubrepoerror
1413 def status(self, rev2, **opts):
1413 def status(self, rev2, **opts):
1414 rev1 = self._state[1]
1414 rev1 = self._state[1]
1415 if self._gitmissing() or not rev1:
1415 if self._gitmissing() or not rev1:
1416 # if the repo is missing, return no results
1416 # if the repo is missing, return no results
1417 return [], [], [], [], [], [], []
1417 return [], [], [], [], [], [], []
1418 modified, added, removed = [], [], []
1418 modified, added, removed = [], [], []
1419 self._gitupdatestat()
1419 self._gitupdatestat()
1420 if rev2:
1420 if rev2:
1421 command = ['diff-tree', rev1, rev2]
1421 command = ['diff-tree', rev1, rev2]
1422 else:
1422 else:
1423 command = ['diff-index', rev1]
1423 command = ['diff-index', rev1]
1424 out = self._gitcommand(command)
1424 out = self._gitcommand(command)
1425 for line in out.split('\n'):
1425 for line in out.split('\n'):
1426 tab = line.find('\t')
1426 tab = line.find('\t')
1427 if tab == -1:
1427 if tab == -1:
1428 continue
1428 continue
1429 status, f = line[tab - 1], line[tab + 1:]
1429 status, f = line[tab - 1], line[tab + 1:]
1430 if status == 'M':
1430 if status == 'M':
1431 modified.append(f)
1431 modified.append(f)
1432 elif status == 'A':
1432 elif status == 'A':
1433 added.append(f)
1433 added.append(f)
1434 elif status == 'D':
1434 elif status == 'D':
1435 removed.append(f)
1435 removed.append(f)
1436
1436
1437 deleted = unknown = ignored = clean = []
1437 deleted = unknown = ignored = clean = []
1438 return modified, added, removed, deleted, unknown, ignored, clean
1438 return modified, added, removed, deleted, unknown, ignored, clean
1439
1439
1440 types = {
1440 types = {
1441 'hg': hgsubrepo,
1441 'hg': hgsubrepo,
1442 'svn': svnsubrepo,
1442 'svn': svnsubrepo,
1443 'git': gitsubrepo,
1443 'git': gitsubrepo,
1444 }
1444 }
@@ -1,1197 +1,1211 b''
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2
2
3 $ echo "[ui]" >> $HGRCPATH
3 $ echo "[ui]" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5
5
6 $ hg init t
6 $ hg init t
7 $ cd t
7 $ cd t
8
8
9 first revision, no sub
9 first revision, no sub
10
10
11 $ echo a > a
11 $ echo a > a
12 $ hg ci -Am0
12 $ hg ci -Am0
13 adding a
13 adding a
14
14
15 add first sub
15 add first sub
16
16
17 $ echo s = s > .hgsub
17 $ echo s = s > .hgsub
18 $ hg add .hgsub
18 $ hg add .hgsub
19 $ hg init s
19 $ hg init s
20 $ echo a > s/a
20 $ echo a > s/a
21
21
22 Issue2232: committing a subrepo without .hgsub
22 Issue2232: committing a subrepo without .hgsub
23
23
24 $ hg ci -mbad s
24 $ hg ci -mbad s
25 abort: can't commit subrepos without .hgsub
25 abort: can't commit subrepos without .hgsub
26 [255]
26 [255]
27
27
28 $ hg -R s ci -Ams0
28 $ hg -R s ci -Ams0
29 adding a
29 adding a
30 $ hg sum
30 $ hg sum
31 parent: 0:f7b1eb17ad24 tip
31 parent: 0:f7b1eb17ad24 tip
32 0
32 0
33 branch: default
33 branch: default
34 commit: 1 added, 1 subrepos
34 commit: 1 added, 1 subrepos
35 update: (current)
35 update: (current)
36 $ hg ci -m1
36 $ hg ci -m1
37
37
38 Revert subrepo and test subrepo fileset keyword:
38 Revert subrepo and test subrepo fileset keyword:
39
39
40 $ echo b > s/a
40 $ echo b > s/a
41 $ hg revert "set:subrepo('glob:s*')"
41 $ hg revert "set:subrepo('glob:s*')"
42 reverting subrepo s
42 reverting subrepo s
43 reverting s/a (glob)
43 reverting s/a (glob)
44 $ rm s/a.orig
44 $ rm s/a.orig
45
45
46 Revert subrepo with no backup. The "reverting s/a" line is gone since
46 Revert subrepo with no backup. The "reverting s/a" line is gone since
47 we're really running 'hg update' in the subrepo:
47 we're really running 'hg update' in the subrepo:
48
48
49 $ echo b > s/a
49 $ echo b > s/a
50 $ hg revert --no-backup s
50 $ hg revert --no-backup s
51 reverting subrepo s
51 reverting subrepo s
52
52
53 Issue2022: update -C
53 Issue2022: update -C
54
54
55 $ echo b > s/a
55 $ echo b > s/a
56 $ hg sum
56 $ hg sum
57 parent: 1:7cf8cfea66e4 tip
57 parent: 1:7cf8cfea66e4 tip
58 1
58 1
59 branch: default
59 branch: default
60 commit: 1 subrepos
60 commit: 1 subrepos
61 update: (current)
61 update: (current)
62 $ hg co -C 1
62 $ hg co -C 1
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 $ hg sum
64 $ hg sum
65 parent: 1:7cf8cfea66e4 tip
65 parent: 1:7cf8cfea66e4 tip
66 1
66 1
67 branch: default
67 branch: default
68 commit: (clean)
68 commit: (clean)
69 update: (current)
69 update: (current)
70
70
71 commands that require a clean repo should respect subrepos
71 commands that require a clean repo should respect subrepos
72
72
73 $ echo b >> s/a
73 $ echo b >> s/a
74 $ hg backout tip
74 $ hg backout tip
75 abort: uncommitted changes in subrepo s
75 abort: uncommitted changes in subrepo s
76 [255]
76 [255]
77 $ hg revert -C -R s s/a
77 $ hg revert -C -R s s/a
78
78
79 add sub sub
79 add sub sub
80
80
81 $ echo ss = ss > s/.hgsub
81 $ echo ss = ss > s/.hgsub
82 $ hg init s/ss
82 $ hg init s/ss
83 $ echo a > s/ss/a
83 $ echo a > s/ss/a
84 $ hg -R s add s/.hgsub
84 $ hg -R s add s/.hgsub
85 $ hg -R s/ss add s/ss/a
85 $ hg -R s/ss add s/ss/a
86 $ hg sum
86 $ hg sum
87 parent: 1:7cf8cfea66e4 tip
87 parent: 1:7cf8cfea66e4 tip
88 1
88 1
89 branch: default
89 branch: default
90 commit: 1 subrepos
90 commit: 1 subrepos
91 update: (current)
91 update: (current)
92 $ hg ci -m2
92 $ hg ci -m2
93 committing subrepository s
93 committing subrepository s
94 committing subrepository s/ss (glob)
94 committing subrepository s/ss (glob)
95 $ hg sum
95 $ hg sum
96 parent: 2:df30734270ae tip
96 parent: 2:df30734270ae tip
97 2
97 2
98 branch: default
98 branch: default
99 commit: (clean)
99 commit: (clean)
100 update: (current)
100 update: (current)
101
101
102 bump sub rev (and check it is ignored by ui.commitsubrepos)
102 bump sub rev (and check it is ignored by ui.commitsubrepos)
103
103
104 $ echo b > s/a
104 $ echo b > s/a
105 $ hg -R s ci -ms1
105 $ hg -R s ci -ms1
106 $ hg --config ui.commitsubrepos=no ci -m3
106 $ hg --config ui.commitsubrepos=no ci -m3
107
107
108 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
108 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
109
109
110 $ echo c > s/a
110 $ echo c > s/a
111 $ hg --config ui.commitsubrepos=no ci -m4
111 $ hg --config ui.commitsubrepos=no ci -m4
112 abort: uncommitted changes in subrepo s
112 abort: uncommitted changes in subrepo s
113 (use --subrepos for recursive commit)
113 (use --subrepos for recursive commit)
114 [255]
114 [255]
115 $ hg id
115 $ hg id
116 f6affe3fbfaa+ tip
116 f6affe3fbfaa+ tip
117 $ hg -R s ci -mc
117 $ hg -R s ci -mc
118 $ hg id
118 $ hg id
119 f6affe3fbfaa+ tip
119 f6affe3fbfaa+ tip
120 $ echo d > s/a
120 $ echo d > s/a
121 $ hg ci -m4
121 $ hg ci -m4
122 committing subrepository s
122 committing subrepository s
123 $ hg tip -R s
123 $ hg tip -R s
124 changeset: 4:02dcf1d70411
124 changeset: 4:02dcf1d70411
125 tag: tip
125 tag: tip
126 user: test
126 user: test
127 date: Thu Jan 01 00:00:00 1970 +0000
127 date: Thu Jan 01 00:00:00 1970 +0000
128 summary: 4
128 summary: 4
129
129
130
130
131 check caching
131 check caching
132
132
133 $ hg co 0
133 $ hg co 0
134 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
134 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
135 $ hg debugsub
135 $ hg debugsub
136
136
137 restore
137 restore
138
138
139 $ hg co
139 $ hg co
140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 $ hg debugsub
141 $ hg debugsub
142 path s
142 path s
143 source s
143 source s
144 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
144 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
145
145
146 new branch for merge tests
146 new branch for merge tests
147
147
148 $ hg co 1
148 $ hg co 1
149 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 $ echo t = t >> .hgsub
150 $ echo t = t >> .hgsub
151 $ hg init t
151 $ hg init t
152 $ echo t > t/t
152 $ echo t > t/t
153 $ hg -R t add t
153 $ hg -R t add t
154 adding t/t (glob)
154 adding t/t (glob)
155
155
156 5
156 5
157
157
158 $ hg ci -m5 # add sub
158 $ hg ci -m5 # add sub
159 committing subrepository t
159 committing subrepository t
160 created new head
160 created new head
161 $ echo t2 > t/t
161 $ echo t2 > t/t
162
162
163 6
163 6
164
164
165 $ hg st -R s
165 $ hg st -R s
166 $ hg ci -m6 # change sub
166 $ hg ci -m6 # change sub
167 committing subrepository t
167 committing subrepository t
168 $ hg debugsub
168 $ hg debugsub
169 path s
169 path s
170 source s
170 source s
171 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
171 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
172 path t
172 path t
173 source t
173 source t
174 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
174 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
175 $ echo t3 > t/t
175 $ echo t3 > t/t
176
176
177 7
177 7
178
178
179 $ hg ci -m7 # change sub again for conflict test
179 $ hg ci -m7 # change sub again for conflict test
180 committing subrepository t
180 committing subrepository t
181 $ hg rm .hgsub
181 $ hg rm .hgsub
182
182
183 8
183 8
184
184
185 $ hg ci -m8 # remove sub
185 $ hg ci -m8 # remove sub
186
186
187 merge tests
187 merge tests
188
188
189 $ hg co -C 3
189 $ hg co -C 3
190 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 $ hg merge 5 # test adding
191 $ hg merge 5 # test adding
192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 (branch merge, don't forget to commit)
193 (branch merge, don't forget to commit)
194 $ hg debugsub
194 $ hg debugsub
195 path s
195 path s
196 source s
196 source s
197 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
197 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
198 path t
198 path t
199 source t
199 source t
200 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
200 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
201 $ hg ci -m9
201 $ hg ci -m9
202 created new head
202 created new head
203 $ hg merge 6 --debug # test change
203 $ hg merge 6 --debug # test change
204 searching for copies back to rev 2
204 searching for copies back to rev 2
205 resolving manifests
205 resolving manifests
206 branchmerge: True, force: False, partial: False
206 branchmerge: True, force: False, partial: False
207 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
207 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
208 .hgsubstate: versions differ -> m
208 .hgsubstate: versions differ -> m
209 updating: .hgsubstate 1/1 files (100.00%)
209 updating: .hgsubstate 1/1 files (100.00%)
210 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
210 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
211 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
211 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
212 getting subrepo t
212 getting subrepo t
213 resolving manifests
213 resolving manifests
214 branchmerge: False, force: False, partial: False
214 branchmerge: False, force: False, partial: False
215 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
215 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
216 t: remote is newer -> g
216 t: remote is newer -> g
217 getting t
217 getting t
218 updating: t 1/1 files (100.00%)
218 updating: t 1/1 files (100.00%)
219 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 (branch merge, don't forget to commit)
220 (branch merge, don't forget to commit)
221 $ hg debugsub
221 $ hg debugsub
222 path s
222 path s
223 source s
223 source s
224 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
224 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
225 path t
225 path t
226 source t
226 source t
227 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
227 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
228 $ echo conflict > t/t
228 $ echo conflict > t/t
229 $ hg ci -m10
229 $ hg ci -m10
230 committing subrepository t
230 committing subrepository t
231 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
231 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
232 searching for copies back to rev 2
232 searching for copies back to rev 2
233 resolving manifests
233 resolving manifests
234 branchmerge: True, force: False, partial: False
234 branchmerge: True, force: False, partial: False
235 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
235 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
236 .hgsubstate: versions differ -> m
236 .hgsubstate: versions differ -> m
237 updating: .hgsubstate 1/1 files (100.00%)
237 updating: .hgsubstate 1/1 files (100.00%)
238 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
238 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
239 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
239 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
240 merging subrepo t
240 merging subrepo t
241 searching for copies back to rev 2
241 searching for copies back to rev 2
242 resolving manifests
242 resolving manifests
243 branchmerge: True, force: False, partial: False
243 branchmerge: True, force: False, partial: False
244 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
244 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
245 t: versions differ -> m
245 t: versions differ -> m
246 preserving t for resolve of t
246 preserving t for resolve of t
247 updating: t 1/1 files (100.00%)
247 updating: t 1/1 files (100.00%)
248 picked tool 'internal:merge' for t (binary False symlink False)
248 picked tool 'internal:merge' for t (binary False symlink False)
249 merging t
249 merging t
250 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
250 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
251 warning: conflicts during merge.
251 warning: conflicts during merge.
252 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
252 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
253 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
253 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
254 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
254 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
255 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
255 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 (branch merge, don't forget to commit)
256 (branch merge, don't forget to commit)
257
257
258 should conflict
258 should conflict
259
259
260 $ cat t/t
260 $ cat t/t
261 <<<<<<< local
261 <<<<<<< local
262 conflict
262 conflict
263 =======
263 =======
264 t3
264 t3
265 >>>>>>> other
265 >>>>>>> other
266
266
267 clone
267 clone
268
268
269 $ cd ..
269 $ cd ..
270 $ hg clone t tc
270 $ hg clone t tc
271 updating to branch default
271 updating to branch default
272 cloning subrepo s from $TESTTMP/t/s
272 cloning subrepo s from $TESTTMP/t/s
273 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
273 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
274 cloning subrepo t from $TESTTMP/t/t
274 cloning subrepo t from $TESTTMP/t/t
275 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
275 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 $ cd tc
276 $ cd tc
277 $ hg debugsub
277 $ hg debugsub
278 path s
278 path s
279 source s
279 source s
280 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
280 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
281 path t
281 path t
282 source t
282 source t
283 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
283 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
284
284
285 push
285 push
286
286
287 $ echo bah > t/t
287 $ echo bah > t/t
288 $ hg ci -m11
288 $ hg ci -m11
289 committing subrepository t
289 committing subrepository t
290 $ hg push
290 $ hg push
291 pushing to $TESTTMP/t (glob)
291 pushing to $TESTTMP/t (glob)
292 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
292 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
293 no changes made to subrepo s since last push to $TESTTMP/t/s
293 no changes made to subrepo s since last push to $TESTTMP/t/s
294 pushing subrepo t to $TESTTMP/t/t
294 pushing subrepo t to $TESTTMP/t/t
295 searching for changes
295 searching for changes
296 adding changesets
296 adding changesets
297 adding manifests
297 adding manifests
298 adding file changes
298 adding file changes
299 added 1 changesets with 1 changes to 1 files
299 added 1 changesets with 1 changes to 1 files
300 searching for changes
300 searching for changes
301 adding changesets
301 adding changesets
302 adding manifests
302 adding manifests
303 adding file changes
303 adding file changes
304 added 1 changesets with 1 changes to 1 files
304 added 1 changesets with 1 changes to 1 files
305
305
306 push -f
306 push -f
307
307
308 $ echo bah > s/a
308 $ echo bah > s/a
309 $ hg ci -m12
309 $ hg ci -m12
310 committing subrepository s
310 committing subrepository s
311 $ hg push
311 $ hg push
312 pushing to $TESTTMP/t (glob)
312 pushing to $TESTTMP/t (glob)
313 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
313 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
314 pushing subrepo s to $TESTTMP/t/s
314 pushing subrepo s to $TESTTMP/t/s
315 searching for changes
315 searching for changes
316 abort: push creates new remote head 12a213df6fa9! (in subrepo s)
316 abort: push creates new remote head 12a213df6fa9! (in subrepo s)
317 (did you forget to merge? use push -f to force)
317 (did you forget to merge? use push -f to force)
318 [255]
318 [255]
319 $ hg push -f
319 $ hg push -f
320 pushing to $TESTTMP/t (glob)
320 pushing to $TESTTMP/t (glob)
321 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
321 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
322 searching for changes
322 searching for changes
323 no changes found
323 no changes found
324 pushing subrepo s to $TESTTMP/t/s
324 pushing subrepo s to $TESTTMP/t/s
325 searching for changes
325 searching for changes
326 adding changesets
326 adding changesets
327 adding manifests
327 adding manifests
328 adding file changes
328 adding file changes
329 added 1 changesets with 1 changes to 1 files (+1 heads)
329 added 1 changesets with 1 changes to 1 files (+1 heads)
330 pushing subrepo t to $TESTTMP/t/t
330 pushing subrepo t to $TESTTMP/t/t
331 searching for changes
331 searching for changes
332 no changes found
332 no changes found
333 searching for changes
333 searching for changes
334 adding changesets
334 adding changesets
335 adding manifests
335 adding manifests
336 adding file changes
336 adding file changes
337 added 1 changesets with 1 changes to 1 files
337 added 1 changesets with 1 changes to 1 files
338
338
339 check that unmodified subrepos are not pushed
339 check that unmodified subrepos are not pushed
340
340
341 $ hg clone . ../tcc
341 $ hg clone . ../tcc
342 updating to branch default
342 updating to branch default
343 cloning subrepo s from $TESTTMP/tc/s
343 cloning subrepo s from $TESTTMP/tc/s
344 cloning subrepo s/ss from $TESTTMP/tc/s/ss
344 cloning subrepo s/ss from $TESTTMP/tc/s/ss
345 cloning subrepo t from $TESTTMP/tc/t
345 cloning subrepo t from $TESTTMP/tc/t
346 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
347
347
348 the subrepos on the new clone have nothing to push to its source
348 the subrepos on the new clone have nothing to push to its source
349
349
350 $ hg push -R ../tcc .
350 $ hg push -R ../tcc .
351 pushing to .
351 pushing to .
352 no changes made to subrepo s/ss since last push to s/ss
352 no changes made to subrepo s/ss since last push to s/ss
353 no changes made to subrepo s since last push to s
353 no changes made to subrepo s since last push to s
354 no changes made to subrepo t since last push to t
354 no changes made to subrepo t since last push to t
355 searching for changes
355 searching for changes
356 no changes found
356 no changes found
357 [1]
357 [1]
358
358
359 the subrepos on the source do not have a clean store versus the clone target
359 the subrepos on the source do not have a clean store versus the clone target
360 because they were never explicitly pushed to the source
360 because they were never explicitly pushed to the source
361
361
362 $ hg push ../tcc
362 $ hg push ../tcc
363 pushing to ../tcc
363 pushing to ../tcc
364 pushing subrepo s/ss to ../tcc/s/ss
364 pushing subrepo s/ss to ../tcc/s/ss
365 searching for changes
365 searching for changes
366 no changes found
366 no changes found
367 pushing subrepo s to ../tcc/s
367 pushing subrepo s to ../tcc/s
368 searching for changes
368 searching for changes
369 no changes found
369 no changes found
370 pushing subrepo t to ../tcc/t
370 pushing subrepo t to ../tcc/t
371 searching for changes
371 searching for changes
372 no changes found
372 no changes found
373 searching for changes
373 searching for changes
374 no changes found
374 no changes found
375 [1]
375 [1]
376
376
377 after push their stores become clean
377 after push their stores become clean
378
378
379 $ hg push ../tcc
379 $ hg push ../tcc
380 pushing to ../tcc
380 pushing to ../tcc
381 no changes made to subrepo s/ss since last push to ../tcc/s/ss
381 no changes made to subrepo s/ss since last push to ../tcc/s/ss
382 no changes made to subrepo s since last push to ../tcc/s
382 no changes made to subrepo s since last push to ../tcc/s
383 no changes made to subrepo t since last push to ../tcc/t
383 no changes made to subrepo t since last push to ../tcc/t
384 searching for changes
384 searching for changes
385 no changes found
385 no changes found
386 [1]
386 [1]
387
387
388 updating a subrepo to a different revision or changing
388 updating a subrepo to a different revision or changing
389 its working directory does not make its store dirty
389 its working directory does not make its store dirty
390
390
391 $ hg -R s update '.^'
391 $ hg -R s update '.^'
392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 $ hg push
393 $ hg push
394 pushing to $TESTTMP/t
394 pushing to $TESTTMP/t
395 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
395 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
396 no changes made to subrepo s since last push to $TESTTMP/t/s
396 no changes made to subrepo s since last push to $TESTTMP/t/s
397 no changes made to subrepo t since last push to $TESTTMP/t/t
397 no changes made to subrepo t since last push to $TESTTMP/t/t
398 searching for changes
398 searching for changes
399 no changes found
399 no changes found
400 [1]
400 [1]
401 $ echo foo >> s/a
401 $ echo foo >> s/a
402 $ hg push
402 $ hg push
403 pushing to $TESTTMP/t
403 pushing to $TESTTMP/t
404 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
404 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
405 no changes made to subrepo s since last push to $TESTTMP/t/s
405 no changes made to subrepo s since last push to $TESTTMP/t/s
406 no changes made to subrepo t since last push to $TESTTMP/t/t
406 no changes made to subrepo t since last push to $TESTTMP/t/t
407 searching for changes
407 searching for changes
408 no changes found
408 no changes found
409 [1]
409 [1]
410 $ hg -R s update -C tip
410 $ hg -R s update -C tip
411 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
412
412
413 committing into a subrepo makes its store (but not its parent's store) dirty
413 committing into a subrepo makes its store (but not its parent's store) dirty
414
414
415 $ echo foo >> s/ss/a
415 $ echo foo >> s/ss/a
416 $ hg -R s/ss commit -m 'test dirty store detection'
416 $ hg -R s/ss commit -m 'test dirty store detection'
417 $ hg push
417 $ hg push
418 pushing to $TESTTMP/t
418 pushing to $TESTTMP/t
419 pushing subrepo s/ss to $TESTTMP/t/s/ss
419 pushing subrepo s/ss to $TESTTMP/t/s/ss
420 searching for changes
420 searching for changes
421 adding changesets
421 adding changesets
422 adding manifests
422 adding manifests
423 adding file changes
423 adding file changes
424 added 1 changesets with 1 changes to 1 files
424 added 1 changesets with 1 changes to 1 files
425 no changes made to subrepo s since last push to $TESTTMP/t/s
425 no changes made to subrepo s since last push to $TESTTMP/t/s
426 no changes made to subrepo t since last push to $TESTTMP/t/t
426 no changes made to subrepo t since last push to $TESTTMP/t/t
427 searching for changes
427 searching for changes
428 no changes found
428 no changes found
429 [1]
429 [1]
430
430
431 a subrepo store may be clean versus one repo but not versus another
431 a subrepo store may be clean versus one repo but not versus another
432
432
433 $ hg push
433 $ hg push
434 pushing to $TESTTMP/t
434 pushing to $TESTTMP/t
435 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
435 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
436 no changes made to subrepo s since last push to $TESTTMP/t/s
436 no changes made to subrepo s since last push to $TESTTMP/t/s
437 no changes made to subrepo t since last push to $TESTTMP/t/t
437 no changes made to subrepo t since last push to $TESTTMP/t/t
438 searching for changes
438 searching for changes
439 no changes found
439 no changes found
440 [1]
440 [1]
441 $ hg push ../tcc
441 $ hg push ../tcc
442 pushing to ../tcc
442 pushing to ../tcc
443 pushing subrepo s/ss to ../tcc/s/ss
443 pushing subrepo s/ss to ../tcc/s/ss
444 searching for changes
444 searching for changes
445 adding changesets
445 adding changesets
446 adding manifests
446 adding manifests
447 adding file changes
447 adding file changes
448 added 1 changesets with 1 changes to 1 files
448 added 1 changesets with 1 changes to 1 files
449 no changes made to subrepo s since last push to ../tcc/s
449 no changes made to subrepo s since last push to ../tcc/s
450 no changes made to subrepo t since last push to ../tcc/t
450 no changes made to subrepo t since last push to ../tcc/t
451 searching for changes
451 searching for changes
452 no changes found
452 no changes found
453 [1]
453 [1]
454
454
455 update
455 update
456
456
457 $ cd ../t
457 $ cd ../t
458 $ hg up -C # discard our earlier merge
458 $ hg up -C # discard our earlier merge
459 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
459 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
460 $ echo blah > t/t
460 $ echo blah > t/t
461 $ hg ci -m13
461 $ hg ci -m13
462 committing subrepository t
462 committing subrepository t
463
463
464 backout calls revert internally with minimal opts, which should not raise
465 KeyError
466
467 $ hg backout ".^"
468 reverting .hgsubstate
469 reverting subrepo s
470 reverting s/a
471 reverting subrepo ss
472 reverting subrepo t
473 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
474
475 $ hg up -C # discard changes
476 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
477
464 pull
478 pull
465
479
466 $ cd ../tc
480 $ cd ../tc
467 $ hg pull
481 $ hg pull
468 pulling from $TESTTMP/t (glob)
482 pulling from $TESTTMP/t (glob)
469 searching for changes
483 searching for changes
470 adding changesets
484 adding changesets
471 adding manifests
485 adding manifests
472 adding file changes
486 adding file changes
473 added 1 changesets with 1 changes to 1 files
487 added 1 changesets with 1 changes to 1 files
474 (run 'hg update' to get a working copy)
488 (run 'hg update' to get a working copy)
475
489
476 should pull t
490 should pull t
477
491
478 $ hg up
492 $ hg up
479 pulling subrepo t from $TESTTMP/t/t
493 pulling subrepo t from $TESTTMP/t/t
480 searching for changes
494 searching for changes
481 adding changesets
495 adding changesets
482 adding manifests
496 adding manifests
483 adding file changes
497 adding file changes
484 added 1 changesets with 1 changes to 1 files
498 added 1 changesets with 1 changes to 1 files
485 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 $ cat t/t
500 $ cat t/t
487 blah
501 blah
488
502
489 bogus subrepo path aborts
503 bogus subrepo path aborts
490
504
491 $ echo 'bogus=[boguspath' >> .hgsub
505 $ echo 'bogus=[boguspath' >> .hgsub
492 $ hg ci -m 'bogus subrepo path'
506 $ hg ci -m 'bogus subrepo path'
493 abort: missing ] in subrepo source
507 abort: missing ] in subrepo source
494 [255]
508 [255]
495
509
496 Issue1986: merge aborts when trying to merge a subrepo that
510 Issue1986: merge aborts when trying to merge a subrepo that
497 shouldn't need merging
511 shouldn't need merging
498
512
499 # subrepo layout
513 # subrepo layout
500 #
514 #
501 # o 5 br
515 # o 5 br
502 # /|
516 # /|
503 # o | 4 default
517 # o | 4 default
504 # | |
518 # | |
505 # | o 3 br
519 # | o 3 br
506 # |/|
520 # |/|
507 # o | 2 default
521 # o | 2 default
508 # | |
522 # | |
509 # | o 1 br
523 # | o 1 br
510 # |/
524 # |/
511 # o 0 default
525 # o 0 default
512
526
513 $ cd ..
527 $ cd ..
514 $ rm -rf sub
528 $ rm -rf sub
515 $ hg init main
529 $ hg init main
516 $ cd main
530 $ cd main
517 $ hg init s
531 $ hg init s
518 $ cd s
532 $ cd s
519 $ echo a > a
533 $ echo a > a
520 $ hg ci -Am1
534 $ hg ci -Am1
521 adding a
535 adding a
522 $ hg branch br
536 $ hg branch br
523 marked working directory as branch br
537 marked working directory as branch br
524 (branches are permanent and global, did you want a bookmark?)
538 (branches are permanent and global, did you want a bookmark?)
525 $ echo a >> a
539 $ echo a >> a
526 $ hg ci -m1
540 $ hg ci -m1
527 $ hg up default
541 $ hg up default
528 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
542 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
529 $ echo b > b
543 $ echo b > b
530 $ hg ci -Am1
544 $ hg ci -Am1
531 adding b
545 adding b
532 $ hg up br
546 $ hg up br
533 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
547 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
534 $ hg merge tip
548 $ hg merge tip
535 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
549 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
536 (branch merge, don't forget to commit)
550 (branch merge, don't forget to commit)
537 $ hg ci -m1
551 $ hg ci -m1
538 $ hg up 2
552 $ hg up 2
539 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
553 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
540 $ echo c > c
554 $ echo c > c
541 $ hg ci -Am1
555 $ hg ci -Am1
542 adding c
556 adding c
543 $ hg up 3
557 $ hg up 3
544 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
558 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
545 $ hg merge 4
559 $ hg merge 4
546 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
560 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
547 (branch merge, don't forget to commit)
561 (branch merge, don't forget to commit)
548 $ hg ci -m1
562 $ hg ci -m1
549
563
550 # main repo layout:
564 # main repo layout:
551 #
565 #
552 # * <-- try to merge default into br again
566 # * <-- try to merge default into br again
553 # .`|
567 # .`|
554 # . o 5 br --> substate = 5
568 # . o 5 br --> substate = 5
555 # . |
569 # . |
556 # o | 4 default --> substate = 4
570 # o | 4 default --> substate = 4
557 # | |
571 # | |
558 # | o 3 br --> substate = 2
572 # | o 3 br --> substate = 2
559 # |/|
573 # |/|
560 # o | 2 default --> substate = 2
574 # o | 2 default --> substate = 2
561 # | |
575 # | |
562 # | o 1 br --> substate = 3
576 # | o 1 br --> substate = 3
563 # |/
577 # |/
564 # o 0 default --> substate = 2
578 # o 0 default --> substate = 2
565
579
566 $ cd ..
580 $ cd ..
567 $ echo 's = s' > .hgsub
581 $ echo 's = s' > .hgsub
568 $ hg -R s up 2
582 $ hg -R s up 2
569 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
583 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
570 $ hg ci -Am1
584 $ hg ci -Am1
571 adding .hgsub
585 adding .hgsub
572 $ hg branch br
586 $ hg branch br
573 marked working directory as branch br
587 marked working directory as branch br
574 (branches are permanent and global, did you want a bookmark?)
588 (branches are permanent and global, did you want a bookmark?)
575 $ echo b > b
589 $ echo b > b
576 $ hg -R s up 3
590 $ hg -R s up 3
577 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
591 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
578 $ hg ci -Am1
592 $ hg ci -Am1
579 adding b
593 adding b
580 $ hg up default
594 $ hg up default
581 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
595 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
582 $ echo c > c
596 $ echo c > c
583 $ hg ci -Am1
597 $ hg ci -Am1
584 adding c
598 adding c
585 $ hg up 1
599 $ hg up 1
586 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
600 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
587 $ hg merge 2
601 $ hg merge 2
588 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
602 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
589 (branch merge, don't forget to commit)
603 (branch merge, don't forget to commit)
590 $ hg ci -m1
604 $ hg ci -m1
591 $ hg up 2
605 $ hg up 2
592 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
606 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
593 $ hg -R s up 4
607 $ hg -R s up 4
594 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
608 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
595 $ echo d > d
609 $ echo d > d
596 $ hg ci -Am1
610 $ hg ci -Am1
597 adding d
611 adding d
598 $ hg up 3
612 $ hg up 3
599 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
613 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
600 $ hg -R s up 5
614 $ hg -R s up 5
601 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
615 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
602 $ echo e > e
616 $ echo e > e
603 $ hg ci -Am1
617 $ hg ci -Am1
604 adding e
618 adding e
605
619
606 $ hg up 5
620 $ hg up 5
607 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
621 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
608 $ hg merge 4 # try to merge default into br again
622 $ hg merge 4 # try to merge default into br again
609 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
623 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
610 (branch merge, don't forget to commit)
624 (branch merge, don't forget to commit)
611 $ cd ..
625 $ cd ..
612
626
613 test subrepo delete from .hgsubstate
627 test subrepo delete from .hgsubstate
614
628
615 $ hg init testdelete
629 $ hg init testdelete
616 $ mkdir testdelete/nested testdelete/nested2
630 $ mkdir testdelete/nested testdelete/nested2
617 $ hg init testdelete/nested
631 $ hg init testdelete/nested
618 $ hg init testdelete/nested2
632 $ hg init testdelete/nested2
619 $ echo test > testdelete/nested/foo
633 $ echo test > testdelete/nested/foo
620 $ echo test > testdelete/nested2/foo
634 $ echo test > testdelete/nested2/foo
621 $ hg -R testdelete/nested add
635 $ hg -R testdelete/nested add
622 adding testdelete/nested/foo (glob)
636 adding testdelete/nested/foo (glob)
623 $ hg -R testdelete/nested2 add
637 $ hg -R testdelete/nested2 add
624 adding testdelete/nested2/foo (glob)
638 adding testdelete/nested2/foo (glob)
625 $ hg -R testdelete/nested ci -m test
639 $ hg -R testdelete/nested ci -m test
626 $ hg -R testdelete/nested2 ci -m test
640 $ hg -R testdelete/nested2 ci -m test
627 $ echo nested = nested > testdelete/.hgsub
641 $ echo nested = nested > testdelete/.hgsub
628 $ echo nested2 = nested2 >> testdelete/.hgsub
642 $ echo nested2 = nested2 >> testdelete/.hgsub
629 $ hg -R testdelete add
643 $ hg -R testdelete add
630 adding testdelete/.hgsub (glob)
644 adding testdelete/.hgsub (glob)
631 $ hg -R testdelete ci -m "nested 1 & 2 added"
645 $ hg -R testdelete ci -m "nested 1 & 2 added"
632 $ echo nested = nested > testdelete/.hgsub
646 $ echo nested = nested > testdelete/.hgsub
633 $ hg -R testdelete ci -m "nested 2 deleted"
647 $ hg -R testdelete ci -m "nested 2 deleted"
634 $ cat testdelete/.hgsubstate
648 $ cat testdelete/.hgsubstate
635 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
649 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
636 $ hg -R testdelete remove testdelete/.hgsub
650 $ hg -R testdelete remove testdelete/.hgsub
637 $ hg -R testdelete ci -m ".hgsub deleted"
651 $ hg -R testdelete ci -m ".hgsub deleted"
638 $ cat testdelete/.hgsubstate
652 $ cat testdelete/.hgsubstate
639 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
653 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
640
654
641 test repository cloning
655 test repository cloning
642
656
643 $ mkdir mercurial mercurial2
657 $ mkdir mercurial mercurial2
644 $ hg init nested_absolute
658 $ hg init nested_absolute
645 $ echo test > nested_absolute/foo
659 $ echo test > nested_absolute/foo
646 $ hg -R nested_absolute add
660 $ hg -R nested_absolute add
647 adding nested_absolute/foo (glob)
661 adding nested_absolute/foo (glob)
648 $ hg -R nested_absolute ci -mtest
662 $ hg -R nested_absolute ci -mtest
649 $ cd mercurial
663 $ cd mercurial
650 $ hg init nested_relative
664 $ hg init nested_relative
651 $ echo test2 > nested_relative/foo2
665 $ echo test2 > nested_relative/foo2
652 $ hg -R nested_relative add
666 $ hg -R nested_relative add
653 adding nested_relative/foo2 (glob)
667 adding nested_relative/foo2 (glob)
654 $ hg -R nested_relative ci -mtest2
668 $ hg -R nested_relative ci -mtest2
655 $ hg init main
669 $ hg init main
656 $ echo "nested_relative = ../nested_relative" > main/.hgsub
670 $ echo "nested_relative = ../nested_relative" > main/.hgsub
657 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
671 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
658 $ hg -R main add
672 $ hg -R main add
659 adding main/.hgsub (glob)
673 adding main/.hgsub (glob)
660 $ hg -R main ci -m "add subrepos"
674 $ hg -R main ci -m "add subrepos"
661 $ cd ..
675 $ cd ..
662 $ hg clone mercurial/main mercurial2/main
676 $ hg clone mercurial/main mercurial2/main
663 updating to branch default
677 updating to branch default
664 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
678 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
665 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
679 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
666 > mercurial2/main/nested_relative/.hg/hgrc
680 > mercurial2/main/nested_relative/.hg/hgrc
667 [paths]
681 [paths]
668 default = $TESTTMP/mercurial/nested_absolute
682 default = $TESTTMP/mercurial/nested_absolute
669 [paths]
683 [paths]
670 default = $TESTTMP/mercurial/nested_relative
684 default = $TESTTMP/mercurial/nested_relative
671 $ rm -rf mercurial mercurial2
685 $ rm -rf mercurial mercurial2
672
686
673 Issue1977: multirepo push should fail if subrepo push fails
687 Issue1977: multirepo push should fail if subrepo push fails
674
688
675 $ hg init repo
689 $ hg init repo
676 $ hg init repo/s
690 $ hg init repo/s
677 $ echo a > repo/s/a
691 $ echo a > repo/s/a
678 $ hg -R repo/s ci -Am0
692 $ hg -R repo/s ci -Am0
679 adding a
693 adding a
680 $ echo s = s > repo/.hgsub
694 $ echo s = s > repo/.hgsub
681 $ hg -R repo ci -Am1
695 $ hg -R repo ci -Am1
682 adding .hgsub
696 adding .hgsub
683 $ hg clone repo repo2
697 $ hg clone repo repo2
684 updating to branch default
698 updating to branch default
685 cloning subrepo s from $TESTTMP/repo/s
699 cloning subrepo s from $TESTTMP/repo/s
686 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
700 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
687 $ hg -q -R repo2 pull -u
701 $ hg -q -R repo2 pull -u
688 $ echo 1 > repo2/s/a
702 $ echo 1 > repo2/s/a
689 $ hg -R repo2/s ci -m2
703 $ hg -R repo2/s ci -m2
690 $ hg -q -R repo2/s push
704 $ hg -q -R repo2/s push
691 $ hg -R repo2/s up -C 0
705 $ hg -R repo2/s up -C 0
692 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
706 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
693 $ echo 2 > repo2/s/b
707 $ echo 2 > repo2/s/b
694 $ hg -R repo2/s ci -m3 -A
708 $ hg -R repo2/s ci -m3 -A
695 adding b
709 adding b
696 created new head
710 created new head
697 $ hg -R repo2 ci -m3
711 $ hg -R repo2 ci -m3
698 $ hg -q -R repo2 push
712 $ hg -q -R repo2 push
699 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
713 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
700 (did you forget to merge? use push -f to force)
714 (did you forget to merge? use push -f to force)
701 [255]
715 [255]
702 $ hg -R repo update
716 $ hg -R repo update
703 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
717 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
704
718
705 test if untracked file is not overwritten
719 test if untracked file is not overwritten
706
720
707 $ echo issue3276_ok > repo/s/b
721 $ echo issue3276_ok > repo/s/b
708 $ hg -R repo2 push -f -q
722 $ hg -R repo2 push -f -q
709 $ hg -R repo update
723 $ hg -R repo update
710 b: untracked file differs
724 b: untracked file differs
711 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
725 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
712 [255]
726 [255]
713
727
714 $ cat repo/s/b
728 $ cat repo/s/b
715 issue3276_ok
729 issue3276_ok
716 $ rm repo/s/b
730 $ rm repo/s/b
717 $ hg -R repo revert --all
731 $ hg -R repo revert --all
718 reverting repo/.hgsubstate (glob)
732 reverting repo/.hgsubstate (glob)
719 reverting subrepo s
733 reverting subrepo s
720 $ hg -R repo update
734 $ hg -R repo update
721 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
735 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
722 $ cat repo/s/b
736 $ cat repo/s/b
723 2
737 2
724 $ rm -rf repo2 repo
738 $ rm -rf repo2 repo
725
739
726
740
727 Issue1852 subrepos with relative paths always push/pull relative to default
741 Issue1852 subrepos with relative paths always push/pull relative to default
728
742
729 Prepare a repo with subrepo
743 Prepare a repo with subrepo
730
744
731 $ hg init issue1852a
745 $ hg init issue1852a
732 $ cd issue1852a
746 $ cd issue1852a
733 $ hg init sub/repo
747 $ hg init sub/repo
734 $ echo test > sub/repo/foo
748 $ echo test > sub/repo/foo
735 $ hg -R sub/repo add sub/repo/foo
749 $ hg -R sub/repo add sub/repo/foo
736 $ echo sub/repo = sub/repo > .hgsub
750 $ echo sub/repo = sub/repo > .hgsub
737 $ hg add .hgsub
751 $ hg add .hgsub
738 $ hg ci -mtest
752 $ hg ci -mtest
739 committing subrepository sub/repo (glob)
753 committing subrepository sub/repo (glob)
740 $ echo test >> sub/repo/foo
754 $ echo test >> sub/repo/foo
741 $ hg ci -mtest
755 $ hg ci -mtest
742 committing subrepository sub/repo (glob)
756 committing subrepository sub/repo (glob)
743 $ cd ..
757 $ cd ..
744
758
745 Create repo without default path, pull top repo, and see what happens on update
759 Create repo without default path, pull top repo, and see what happens on update
746
760
747 $ hg init issue1852b
761 $ hg init issue1852b
748 $ hg -R issue1852b pull issue1852a
762 $ hg -R issue1852b pull issue1852a
749 pulling from issue1852a
763 pulling from issue1852a
750 requesting all changes
764 requesting all changes
751 adding changesets
765 adding changesets
752 adding manifests
766 adding manifests
753 adding file changes
767 adding file changes
754 added 2 changesets with 3 changes to 2 files
768 added 2 changesets with 3 changes to 2 files
755 (run 'hg update' to get a working copy)
769 (run 'hg update' to get a working copy)
756 $ hg -R issue1852b update
770 $ hg -R issue1852b update
757 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
771 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
758 [255]
772 [255]
759
773
760 Pull -u now doesn't help
774 Pull -u now doesn't help
761
775
762 $ hg -R issue1852b pull -u issue1852a
776 $ hg -R issue1852b pull -u issue1852a
763 pulling from issue1852a
777 pulling from issue1852a
764 searching for changes
778 searching for changes
765 no changes found
779 no changes found
766
780
767 Try the same, but with pull -u
781 Try the same, but with pull -u
768
782
769 $ hg init issue1852c
783 $ hg init issue1852c
770 $ hg -R issue1852c pull -r0 -u issue1852a
784 $ hg -R issue1852c pull -r0 -u issue1852a
771 pulling from issue1852a
785 pulling from issue1852a
772 adding changesets
786 adding changesets
773 adding manifests
787 adding manifests
774 adding file changes
788 adding file changes
775 added 1 changesets with 2 changes to 2 files
789 added 1 changesets with 2 changes to 2 files
776 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
790 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
777 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
791 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
778
792
779 Try to push from the other side
793 Try to push from the other side
780
794
781 $ hg -R issue1852a push `pwd`/issue1852c
795 $ hg -R issue1852a push `pwd`/issue1852c
782 pushing to $TESTTMP/issue1852c
796 pushing to $TESTTMP/issue1852c
783 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
797 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
784 searching for changes
798 searching for changes
785 no changes found
799 no changes found
786 searching for changes
800 searching for changes
787 adding changesets
801 adding changesets
788 adding manifests
802 adding manifests
789 adding file changes
803 adding file changes
790 added 1 changesets with 1 changes to 1 files
804 added 1 changesets with 1 changes to 1 files
791
805
792 Incoming and outgoing should not use the default path:
806 Incoming and outgoing should not use the default path:
793
807
794 $ hg clone -q issue1852a issue1852d
808 $ hg clone -q issue1852a issue1852d
795 $ hg -R issue1852d outgoing --subrepos issue1852c
809 $ hg -R issue1852d outgoing --subrepos issue1852c
796 comparing with issue1852c
810 comparing with issue1852c
797 searching for changes
811 searching for changes
798 no changes found
812 no changes found
799 comparing with issue1852c/sub/repo
813 comparing with issue1852c/sub/repo
800 searching for changes
814 searching for changes
801 no changes found
815 no changes found
802 [1]
816 [1]
803 $ hg -R issue1852d incoming --subrepos issue1852c
817 $ hg -R issue1852d incoming --subrepos issue1852c
804 comparing with issue1852c
818 comparing with issue1852c
805 searching for changes
819 searching for changes
806 no changes found
820 no changes found
807 comparing with issue1852c/sub/repo
821 comparing with issue1852c/sub/repo
808 searching for changes
822 searching for changes
809 no changes found
823 no changes found
810 [1]
824 [1]
811
825
812 Check status of files when none of them belong to the first
826 Check status of files when none of them belong to the first
813 subrepository:
827 subrepository:
814
828
815 $ hg init subrepo-status
829 $ hg init subrepo-status
816 $ cd subrepo-status
830 $ cd subrepo-status
817 $ hg init subrepo-1
831 $ hg init subrepo-1
818 $ hg init subrepo-2
832 $ hg init subrepo-2
819 $ cd subrepo-2
833 $ cd subrepo-2
820 $ touch file
834 $ touch file
821 $ hg add file
835 $ hg add file
822 $ cd ..
836 $ cd ..
823 $ echo subrepo-1 = subrepo-1 > .hgsub
837 $ echo subrepo-1 = subrepo-1 > .hgsub
824 $ echo subrepo-2 = subrepo-2 >> .hgsub
838 $ echo subrepo-2 = subrepo-2 >> .hgsub
825 $ hg add .hgsub
839 $ hg add .hgsub
826 $ hg ci -m 'Added subrepos'
840 $ hg ci -m 'Added subrepos'
827 committing subrepository subrepo-2
841 committing subrepository subrepo-2
828 $ hg st subrepo-2/file
842 $ hg st subrepo-2/file
829
843
830 Check that share works with subrepo
844 Check that share works with subrepo
831 $ hg --config extensions.share= share . ../shared
845 $ hg --config extensions.share= share . ../shared
832 updating working directory
846 updating working directory
833 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
847 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
834 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
848 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
835 $ test -f ../shared/subrepo-1/.hg/sharedpath
849 $ test -f ../shared/subrepo-1/.hg/sharedpath
836 [1]
850 [1]
837 $ hg -R ../shared in
851 $ hg -R ../shared in
838 abort: repository default not found!
852 abort: repository default not found!
839 [255]
853 [255]
840 $ hg -R ../shared/subrepo-2 showconfig paths
854 $ hg -R ../shared/subrepo-2 showconfig paths
841 paths.default=$TESTTMP/subrepo-status/subrepo-2
855 paths.default=$TESTTMP/subrepo-status/subrepo-2
842 $ hg -R ../shared/subrepo-1 sum --remote
856 $ hg -R ../shared/subrepo-1 sum --remote
843 parent: -1:000000000000 tip (empty repository)
857 parent: -1:000000000000 tip (empty repository)
844 branch: default
858 branch: default
845 commit: (clean)
859 commit: (clean)
846 update: (current)
860 update: (current)
847 remote: (synced)
861 remote: (synced)
848
862
849 Check hg update --clean
863 Check hg update --clean
850 $ cd $TESTTMP/t
864 $ cd $TESTTMP/t
851 $ rm -r t/t.orig
865 $ rm -r t/t.orig
852 $ hg status -S --all
866 $ hg status -S --all
853 C .hgsub
867 C .hgsub
854 C .hgsubstate
868 C .hgsubstate
855 C a
869 C a
856 C s/.hgsub
870 C s/.hgsub
857 C s/.hgsubstate
871 C s/.hgsubstate
858 C s/a
872 C s/a
859 C s/ss/a
873 C s/ss/a
860 C t/t
874 C t/t
861 $ echo c1 > s/a
875 $ echo c1 > s/a
862 $ cd s
876 $ cd s
863 $ echo c1 > b
877 $ echo c1 > b
864 $ echo c1 > c
878 $ echo c1 > c
865 $ hg add b
879 $ hg add b
866 $ cd ..
880 $ cd ..
867 $ hg status -S
881 $ hg status -S
868 M s/a
882 M s/a
869 A s/b
883 A s/b
870 ? s/c
884 ? s/c
871 $ hg update -C
885 $ hg update -C
872 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
886 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
873 $ hg status -S
887 $ hg status -S
874 ? s/b
888 ? s/b
875 ? s/c
889 ? s/c
876
890
877 Sticky subrepositories, no changes
891 Sticky subrepositories, no changes
878 $ cd $TESTTMP/t
892 $ cd $TESTTMP/t
879 $ hg id
893 $ hg id
880 925c17564ef8 tip
894 925c17564ef8 tip
881 $ hg -R s id
895 $ hg -R s id
882 12a213df6fa9 tip
896 12a213df6fa9 tip
883 $ hg -R t id
897 $ hg -R t id
884 52c0adc0515a tip
898 52c0adc0515a tip
885 $ hg update 11
899 $ hg update 11
886 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
900 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
887 $ hg id
901 $ hg id
888 365661e5936a
902 365661e5936a
889 $ hg -R s id
903 $ hg -R s id
890 fc627a69481f
904 fc627a69481f
891 $ hg -R t id
905 $ hg -R t id
892 e95bcfa18a35
906 e95bcfa18a35
893
907
894 Sticky subrepositorys, file changes
908 Sticky subrepositorys, file changes
895 $ touch s/f1
909 $ touch s/f1
896 $ touch t/f1
910 $ touch t/f1
897 $ hg add -S s/f1
911 $ hg add -S s/f1
898 $ hg add -S t/f1
912 $ hg add -S t/f1
899 $ hg id
913 $ hg id
900 365661e5936a+
914 365661e5936a+
901 $ hg -R s id
915 $ hg -R s id
902 fc627a69481f+
916 fc627a69481f+
903 $ hg -R t id
917 $ hg -R t id
904 e95bcfa18a35+
918 e95bcfa18a35+
905 $ hg update tip
919 $ hg update tip
906 subrepository sources for s differ
920 subrepository sources for s differ
907 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
921 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
908 l
922 l
909 subrepository sources for t differ
923 subrepository sources for t differ
910 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
924 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
911 l
925 l
912 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
926 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
913 $ hg id
927 $ hg id
914 925c17564ef8+ tip
928 925c17564ef8+ tip
915 $ hg -R s id
929 $ hg -R s id
916 fc627a69481f+
930 fc627a69481f+
917 $ hg -R t id
931 $ hg -R t id
918 e95bcfa18a35+
932 e95bcfa18a35+
919 $ hg update --clean tip
933 $ hg update --clean tip
920 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
934 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
921
935
922 Sticky subrepository, revision updates
936 Sticky subrepository, revision updates
923 $ hg id
937 $ hg id
924 925c17564ef8 tip
938 925c17564ef8 tip
925 $ hg -R s id
939 $ hg -R s id
926 12a213df6fa9 tip
940 12a213df6fa9 tip
927 $ hg -R t id
941 $ hg -R t id
928 52c0adc0515a tip
942 52c0adc0515a tip
929 $ cd s
943 $ cd s
930 $ hg update -r -2
944 $ hg update -r -2
931 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
945 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
932 $ cd ../t
946 $ cd ../t
933 $ hg update -r 2
947 $ hg update -r 2
934 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
948 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
935 $ cd ..
949 $ cd ..
936 $ hg update 10
950 $ hg update 10
937 subrepository sources for t differ (in checked out version)
951 subrepository sources for t differ (in checked out version)
938 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
952 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
939 l
953 l
940 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
954 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
941 $ hg id
955 $ hg id
942 e45c8b14af55+
956 e45c8b14af55+
943 $ hg -R s id
957 $ hg -R s id
944 02dcf1d70411
958 02dcf1d70411
945 $ hg -R t id
959 $ hg -R t id
946 7af322bc1198
960 7af322bc1198
947
961
948 Sticky subrepository, file changes and revision updates
962 Sticky subrepository, file changes and revision updates
949 $ touch s/f1
963 $ touch s/f1
950 $ touch t/f1
964 $ touch t/f1
951 $ hg add -S s/f1
965 $ hg add -S s/f1
952 $ hg add -S t/f1
966 $ hg add -S t/f1
953 $ hg id
967 $ hg id
954 e45c8b14af55+
968 e45c8b14af55+
955 $ hg -R s id
969 $ hg -R s id
956 02dcf1d70411+
970 02dcf1d70411+
957 $ hg -R t id
971 $ hg -R t id
958 7af322bc1198+
972 7af322bc1198+
959 $ hg update tip
973 $ hg update tip
960 subrepository sources for s differ
974 subrepository sources for s differ
961 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)?
975 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)?
962 l
976 l
963 subrepository sources for t differ
977 subrepository sources for t differ
964 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
978 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
965 l
979 l
966 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
980 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
967 $ hg id
981 $ hg id
968 925c17564ef8+ tip
982 925c17564ef8+ tip
969 $ hg -R s id
983 $ hg -R s id
970 02dcf1d70411+
984 02dcf1d70411+
971 $ hg -R t id
985 $ hg -R t id
972 7af322bc1198+
986 7af322bc1198+
973
987
974 Sticky repository, update --clean
988 Sticky repository, update --clean
975 $ hg update --clean tip
989 $ hg update --clean tip
976 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
990 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
977 $ hg id
991 $ hg id
978 925c17564ef8 tip
992 925c17564ef8 tip
979 $ hg -R s id
993 $ hg -R s id
980 12a213df6fa9 tip
994 12a213df6fa9 tip
981 $ hg -R t id
995 $ hg -R t id
982 52c0adc0515a tip
996 52c0adc0515a tip
983
997
984 Test subrepo already at intended revision:
998 Test subrepo already at intended revision:
985 $ cd s
999 $ cd s
986 $ hg update fc627a69481f
1000 $ hg update fc627a69481f
987 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1001 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
988 $ cd ..
1002 $ cd ..
989 $ hg update 11
1003 $ hg update 11
990 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1004 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
991 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1005 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
992 $ hg id -n
1006 $ hg id -n
993 11+
1007 11+
994 $ hg -R s id
1008 $ hg -R s id
995 fc627a69481f
1009 fc627a69481f
996 $ hg -R t id
1010 $ hg -R t id
997 e95bcfa18a35
1011 e95bcfa18a35
998
1012
999 Test that removing .hgsubstate doesn't break anything:
1013 Test that removing .hgsubstate doesn't break anything:
1000
1014
1001 $ hg rm -f .hgsubstate
1015 $ hg rm -f .hgsubstate
1002 $ hg ci -mrm
1016 $ hg ci -mrm
1003 nothing changed
1017 nothing changed
1004 [1]
1018 [1]
1005 $ hg log -vr tip
1019 $ hg log -vr tip
1006 changeset: 13:925c17564ef8
1020 changeset: 13:925c17564ef8
1007 tag: tip
1021 tag: tip
1008 user: test
1022 user: test
1009 date: Thu Jan 01 00:00:00 1970 +0000
1023 date: Thu Jan 01 00:00:00 1970 +0000
1010 files: .hgsubstate
1024 files: .hgsubstate
1011 description:
1025 description:
1012 13
1026 13
1013
1027
1014
1028
1015
1029
1016 Test that removing .hgsub removes .hgsubstate:
1030 Test that removing .hgsub removes .hgsubstate:
1017
1031
1018 $ hg rm .hgsub
1032 $ hg rm .hgsub
1019 $ hg ci -mrm2
1033 $ hg ci -mrm2
1020 created new head
1034 created new head
1021 $ hg log -vr tip
1035 $ hg log -vr tip
1022 changeset: 14:2400bccd50af
1036 changeset: 14:2400bccd50af
1023 tag: tip
1037 tag: tip
1024 parent: 11:365661e5936a
1038 parent: 11:365661e5936a
1025 user: test
1039 user: test
1026 date: Thu Jan 01 00:00:00 1970 +0000
1040 date: Thu Jan 01 00:00:00 1970 +0000
1027 files: .hgsub .hgsubstate
1041 files: .hgsub .hgsubstate
1028 description:
1042 description:
1029 rm2
1043 rm2
1030
1044
1031
1045
1032 Test issue3153: diff -S with deleted subrepos
1046 Test issue3153: diff -S with deleted subrepos
1033
1047
1034 $ hg diff --nodates -S -c .
1048 $ hg diff --nodates -S -c .
1035 diff -r 365661e5936a -r 2400bccd50af .hgsub
1049 diff -r 365661e5936a -r 2400bccd50af .hgsub
1036 --- a/.hgsub
1050 --- a/.hgsub
1037 +++ /dev/null
1051 +++ /dev/null
1038 @@ -1,2 +0,0 @@
1052 @@ -1,2 +0,0 @@
1039 -s = s
1053 -s = s
1040 -t = t
1054 -t = t
1041 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1055 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1042 --- a/.hgsubstate
1056 --- a/.hgsubstate
1043 +++ /dev/null
1057 +++ /dev/null
1044 @@ -1,2 +0,0 @@
1058 @@ -1,2 +0,0 @@
1045 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1059 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1046 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1060 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1047
1061
1048 Test behavior of add for explicit path in subrepo:
1062 Test behavior of add for explicit path in subrepo:
1049 $ cd ..
1063 $ cd ..
1050 $ hg init explicit
1064 $ hg init explicit
1051 $ cd explicit
1065 $ cd explicit
1052 $ echo s = s > .hgsub
1066 $ echo s = s > .hgsub
1053 $ hg add .hgsub
1067 $ hg add .hgsub
1054 $ hg init s
1068 $ hg init s
1055 $ hg ci -m0
1069 $ hg ci -m0
1056 Adding with an explicit path in a subrepo adds the file
1070 Adding with an explicit path in a subrepo adds the file
1057 $ echo c1 > f1
1071 $ echo c1 > f1
1058 $ echo c2 > s/f2
1072 $ echo c2 > s/f2
1059 $ hg st -S
1073 $ hg st -S
1060 ? f1
1074 ? f1
1061 ? s/f2
1075 ? s/f2
1062 $ hg add s/f2
1076 $ hg add s/f2
1063 $ hg st -S
1077 $ hg st -S
1064 A s/f2
1078 A s/f2
1065 ? f1
1079 ? f1
1066 $ hg ci -R s -m0
1080 $ hg ci -R s -m0
1067 $ hg ci -Am1
1081 $ hg ci -Am1
1068 adding f1
1082 adding f1
1069 Adding with an explicit path in a subrepo with -S has the same behavior
1083 Adding with an explicit path in a subrepo with -S has the same behavior
1070 $ echo c3 > f3
1084 $ echo c3 > f3
1071 $ echo c4 > s/f4
1085 $ echo c4 > s/f4
1072 $ hg st -S
1086 $ hg st -S
1073 ? f3
1087 ? f3
1074 ? s/f4
1088 ? s/f4
1075 $ hg add -S s/f4
1089 $ hg add -S s/f4
1076 $ hg st -S
1090 $ hg st -S
1077 A s/f4
1091 A s/f4
1078 ? f3
1092 ? f3
1079 $ hg ci -R s -m1
1093 $ hg ci -R s -m1
1080 $ hg ci -Ama2
1094 $ hg ci -Ama2
1081 adding f3
1095 adding f3
1082 Adding without a path or pattern silently ignores subrepos
1096 Adding without a path or pattern silently ignores subrepos
1083 $ echo c5 > f5
1097 $ echo c5 > f5
1084 $ echo c6 > s/f6
1098 $ echo c6 > s/f6
1085 $ echo c7 > s/f7
1099 $ echo c7 > s/f7
1086 $ hg st -S
1100 $ hg st -S
1087 ? f5
1101 ? f5
1088 ? s/f6
1102 ? s/f6
1089 ? s/f7
1103 ? s/f7
1090 $ hg add
1104 $ hg add
1091 adding f5
1105 adding f5
1092 $ hg st -S
1106 $ hg st -S
1093 A f5
1107 A f5
1094 ? s/f6
1108 ? s/f6
1095 ? s/f7
1109 ? s/f7
1096 $ hg ci -R s -Am2
1110 $ hg ci -R s -Am2
1097 adding f6
1111 adding f6
1098 adding f7
1112 adding f7
1099 $ hg ci -m3
1113 $ hg ci -m3
1100 Adding without a path or pattern with -S also adds files in subrepos
1114 Adding without a path or pattern with -S also adds files in subrepos
1101 $ echo c8 > f8
1115 $ echo c8 > f8
1102 $ echo c9 > s/f9
1116 $ echo c9 > s/f9
1103 $ echo c10 > s/f10
1117 $ echo c10 > s/f10
1104 $ hg st -S
1118 $ hg st -S
1105 ? f8
1119 ? f8
1106 ? s/f10
1120 ? s/f10
1107 ? s/f9
1121 ? s/f9
1108 $ hg add -S
1122 $ hg add -S
1109 adding f8
1123 adding f8
1110 adding s/f10 (glob)
1124 adding s/f10 (glob)
1111 adding s/f9 (glob)
1125 adding s/f9 (glob)
1112 $ hg st -S
1126 $ hg st -S
1113 A f8
1127 A f8
1114 A s/f10
1128 A s/f10
1115 A s/f9
1129 A s/f9
1116 $ hg ci -R s -m3
1130 $ hg ci -R s -m3
1117 $ hg ci -m4
1131 $ hg ci -m4
1118 Adding with a pattern silently ignores subrepos
1132 Adding with a pattern silently ignores subrepos
1119 $ echo c11 > fm11
1133 $ echo c11 > fm11
1120 $ echo c12 > fn12
1134 $ echo c12 > fn12
1121 $ echo c13 > s/fm13
1135 $ echo c13 > s/fm13
1122 $ echo c14 > s/fn14
1136 $ echo c14 > s/fn14
1123 $ hg st -S
1137 $ hg st -S
1124 ? fm11
1138 ? fm11
1125 ? fn12
1139 ? fn12
1126 ? s/fm13
1140 ? s/fm13
1127 ? s/fn14
1141 ? s/fn14
1128 $ hg add 'glob:**fm*'
1142 $ hg add 'glob:**fm*'
1129 adding fm11
1143 adding fm11
1130 $ hg st -S
1144 $ hg st -S
1131 A fm11
1145 A fm11
1132 ? fn12
1146 ? fn12
1133 ? s/fm13
1147 ? s/fm13
1134 ? s/fn14
1148 ? s/fn14
1135 $ hg ci -R s -Am4
1149 $ hg ci -R s -Am4
1136 adding fm13
1150 adding fm13
1137 adding fn14
1151 adding fn14
1138 $ hg ci -Am5
1152 $ hg ci -Am5
1139 adding fn12
1153 adding fn12
1140 Adding with a pattern with -S also adds matches in subrepos
1154 Adding with a pattern with -S also adds matches in subrepos
1141 $ echo c15 > fm15
1155 $ echo c15 > fm15
1142 $ echo c16 > fn16
1156 $ echo c16 > fn16
1143 $ echo c17 > s/fm17
1157 $ echo c17 > s/fm17
1144 $ echo c18 > s/fn18
1158 $ echo c18 > s/fn18
1145 $ hg st -S
1159 $ hg st -S
1146 ? fm15
1160 ? fm15
1147 ? fn16
1161 ? fn16
1148 ? s/fm17
1162 ? s/fm17
1149 ? s/fn18
1163 ? s/fn18
1150 $ hg add -S 'glob:**fm*'
1164 $ hg add -S 'glob:**fm*'
1151 adding fm15
1165 adding fm15
1152 adding s/fm17 (glob)
1166 adding s/fm17 (glob)
1153 $ hg st -S
1167 $ hg st -S
1154 A fm15
1168 A fm15
1155 A s/fm17
1169 A s/fm17
1156 ? fn16
1170 ? fn16
1157 ? s/fn18
1171 ? s/fn18
1158 $ hg ci -R s -Am5
1172 $ hg ci -R s -Am5
1159 adding fn18
1173 adding fn18
1160 $ hg ci -Am6
1174 $ hg ci -Am6
1161 adding fn16
1175 adding fn16
1162
1176
1163 Test behavior of forget for explicit path in subrepo:
1177 Test behavior of forget for explicit path in subrepo:
1164 Forgetting an explicit path in a subrepo untracks the file
1178 Forgetting an explicit path in a subrepo untracks the file
1165 $ echo c19 > s/f19
1179 $ echo c19 > s/f19
1166 $ hg add s/f19
1180 $ hg add s/f19
1167 $ hg st -S
1181 $ hg st -S
1168 A s/f19
1182 A s/f19
1169 $ hg forget s/f19
1183 $ hg forget s/f19
1170 $ hg st -S
1184 $ hg st -S
1171 ? s/f19
1185 ? s/f19
1172 $ rm s/f19
1186 $ rm s/f19
1173 $ cd ..
1187 $ cd ..
1174
1188
1175 Courtesy phases synchronisation to publishing server does not block the push
1189 Courtesy phases synchronisation to publishing server does not block the push
1176 (issue3781)
1190 (issue3781)
1177
1191
1178 $ cp -r main issue3781
1192 $ cp -r main issue3781
1179 $ cp -r main issue3781-dest
1193 $ cp -r main issue3781-dest
1180 $ cd issue3781-dest/s
1194 $ cd issue3781-dest/s
1181 $ hg phase tip # show we have draft changeset
1195 $ hg phase tip # show we have draft changeset
1182 5: draft
1196 5: draft
1183 $ chmod a-w .hg/store/phaseroots # prevent phase push
1197 $ chmod a-w .hg/store/phaseroots # prevent phase push
1184 $ cd ../../issue3781
1198 $ cd ../../issue3781
1185 $ cat >> .hg/hgrc << EOF
1199 $ cat >> .hg/hgrc << EOF
1186 > [paths]
1200 > [paths]
1187 > default=../issue3781-dest/
1201 > default=../issue3781-dest/
1188 > EOF
1202 > EOF
1189 $ hg push
1203 $ hg push
1190 pushing to $TESTTMP/issue3781-dest (glob)
1204 pushing to $TESTTMP/issue3781-dest (glob)
1191 pushing subrepo s to $TESTTMP/issue3781-dest/s
1205 pushing subrepo s to $TESTTMP/issue3781-dest/s
1192 searching for changes
1206 searching for changes
1193 no changes found
1207 no changes found
1194 searching for changes
1208 searching for changes
1195 no changes found
1209 no changes found
1196 [1]
1210 [1]
1197
1211
General Comments 0
You need to be logged in to leave comments. Login now