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