##// END OF EJS Templates
subrepo: share instead of clone if the parent repo is shared (issue5675) (BC)...
Matt Harbison -
r34816:68e0bcb9 default
parent child Browse files
Show More
@@ -1,231 +1,232 b''
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''share a common history between several working directories
6 '''share a common history between several working directories
7
7
8 Automatic Pooled Storage for Clones
8 Automatic Pooled Storage for Clones
9 -----------------------------------
9 -----------------------------------
10
10
11 When this extension is active, :hg:`clone` can be configured to
11 When this extension is active, :hg:`clone` can be configured to
12 automatically share/pool storage across multiple clones. This
12 automatically share/pool storage across multiple clones. This
13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
14 The benefit of using this mode is the automatic management of
14 The benefit of using this mode is the automatic management of
15 store paths and intelligent pooling of related repositories.
15 store paths and intelligent pooling of related repositories.
16
16
17 The following ``share.`` config options influence this feature:
17 The following ``share.`` config options influence this feature:
18
18
19 ``share.pool``
19 ``share.pool``
20 Filesystem path where shared repository data will be stored. When
20 Filesystem path where shared repository data will be stored. When
21 defined, :hg:`clone` will automatically use shared repository
21 defined, :hg:`clone` will automatically use shared repository
22 storage instead of creating a store inside each clone.
22 storage instead of creating a store inside each clone.
23
23
24 ``share.poolnaming``
24 ``share.poolnaming``
25 How directory names in ``share.pool`` are constructed.
25 How directory names in ``share.pool`` are constructed.
26
26
27 "identity" means the name is derived from the first changeset in the
27 "identity" means the name is derived from the first changeset in the
28 repository. In this mode, different remotes share storage if their
28 repository. In this mode, different remotes share storage if their
29 root/initial changeset is identical. In this mode, the local shared
29 root/initial changeset is identical. In this mode, the local shared
30 repository is an aggregate of all encountered remote repositories.
30 repository is an aggregate of all encountered remote repositories.
31
31
32 "remote" means the name is derived from the source repository's
32 "remote" means the name is derived from the source repository's
33 path or URL. In this mode, storage is only shared if the path or URL
33 path or URL. In this mode, storage is only shared if the path or URL
34 requested in the :hg:`clone` command matches exactly to a repository
34 requested in the :hg:`clone` command matches exactly to a repository
35 that was cloned before.
35 that was cloned before.
36
36
37 The default naming mode is "identity."
37 The default naming mode is "identity."
38 '''
38 '''
39
39
40 from __future__ import absolute_import
40 from __future__ import absolute_import
41
41
42 import errno
42 import errno
43 from mercurial.i18n import _
43 from mercurial.i18n import _
44 from mercurial import (
44 from mercurial import (
45 bookmarks,
45 bookmarks,
46 commands,
46 commands,
47 error,
47 error,
48 extensions,
48 extensions,
49 hg,
49 hg,
50 registrar,
50 registrar,
51 txnutil,
51 txnutil,
52 util,
52 util,
53 )
53 )
54
54
55 repository = hg.repository
55 repository = hg.repository
56 parseurl = hg.parseurl
56 parseurl = hg.parseurl
57
57
58 cmdtable = {}
58 cmdtable = {}
59 command = registrar.command(cmdtable)
59 command = registrar.command(cmdtable)
60 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
60 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
61 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 # be specifying the version(s) of Mercurial they are tested with, or
62 # be specifying the version(s) of Mercurial they are tested with, or
63 # leave the attribute unspecified.
63 # leave the attribute unspecified.
64 testedwith = 'ships-with-hg-core'
64 testedwith = 'ships-with-hg-core'
65
65
66 configtable = {}
66 configtable = {}
67 configitem = registrar.configitem(configtable)
67 configitem = registrar.configitem(configtable)
68
68
69 configitem('share', 'pool',
69 configitem('share', 'pool',
70 default=None,
70 default=None,
71 )
71 )
72 configitem('share', 'poolnaming',
72 configitem('share', 'poolnaming',
73 default='identity',
73 default='identity',
74 )
74 )
75
75
76 @command('share',
76 @command('share',
77 [('U', 'noupdate', None, _('do not create a working directory')),
77 [('U', 'noupdate', None, _('do not create a working directory')),
78 ('B', 'bookmarks', None, _('also share bookmarks')),
78 ('B', 'bookmarks', None, _('also share bookmarks')),
79 ('', 'relative', None, _('point to source using a relative path '
79 ('', 'relative', None, _('point to source using a relative path '
80 '(EXPERIMENTAL)')),
80 '(EXPERIMENTAL)')),
81 ],
81 ],
82 _('[-U] [-B] SOURCE [DEST]'),
82 _('[-U] [-B] SOURCE [DEST]'),
83 norepo=True)
83 norepo=True)
84 def share(ui, source, dest=None, noupdate=False, bookmarks=False,
84 def share(ui, source, dest=None, noupdate=False, bookmarks=False,
85 relative=False):
85 relative=False):
86 """create a new shared repository
86 """create a new shared repository
87
87
88 Initialize a new repository and working directory that shares its
88 Initialize a new repository and working directory that shares its
89 history (and optionally bookmarks) with another repository.
89 history (and optionally bookmarks) with another repository.
90
90
91 .. note::
91 .. note::
92
92
93 using rollback or extensions that destroy/modify history (mq,
93 using rollback or extensions that destroy/modify history (mq,
94 rebase, etc.) can cause considerable confusion with shared
94 rebase, etc.) can cause considerable confusion with shared
95 clones. In particular, if two shared clones are both updated to
95 clones. In particular, if two shared clones are both updated to
96 the same changeset, and one of them destroys that changeset
96 the same changeset, and one of them destroys that changeset
97 with rollback, the other clone will suddenly stop working: all
97 with rollback, the other clone will suddenly stop working: all
98 operations will fail with "abort: working directory has unknown
98 operations will fail with "abort: working directory has unknown
99 parent". The only known workaround is to use debugsetparents on
99 parent". The only known workaround is to use debugsetparents on
100 the broken clone to reset it to a changeset that still exists.
100 the broken clone to reset it to a changeset that still exists.
101 """
101 """
102
102
103 return hg.share(ui, source, dest=dest, update=not noupdate,
103 hg.share(ui, source, dest=dest, update=not noupdate,
104 bookmarks=bookmarks, relative=relative)
104 bookmarks=bookmarks, relative=relative)
105 return 0
105
106
106 @command('unshare', [], '')
107 @command('unshare', [], '')
107 def unshare(ui, repo):
108 def unshare(ui, repo):
108 """convert a shared repository to a normal one
109 """convert a shared repository to a normal one
109
110
110 Copy the store data to the repo and remove the sharedpath data.
111 Copy the store data to the repo and remove the sharedpath data.
111 """
112 """
112
113
113 if not repo.shared():
114 if not repo.shared():
114 raise error.Abort(_("this is not a shared repo"))
115 raise error.Abort(_("this is not a shared repo"))
115
116
116 destlock = lock = None
117 destlock = lock = None
117 lock = repo.lock()
118 lock = repo.lock()
118 try:
119 try:
119 # we use locks here because if we race with commit, we
120 # we use locks here because if we race with commit, we
120 # can end up with extra data in the cloned revlogs that's
121 # can end up with extra data in the cloned revlogs that's
121 # not pointed to by changesets, thus causing verify to
122 # not pointed to by changesets, thus causing verify to
122 # fail
123 # fail
123
124
124 destlock = hg.copystore(ui, repo, repo.path)
125 destlock = hg.copystore(ui, repo, repo.path)
125
126
126 sharefile = repo.vfs.join('sharedpath')
127 sharefile = repo.vfs.join('sharedpath')
127 util.rename(sharefile, sharefile + '.old')
128 util.rename(sharefile, sharefile + '.old')
128
129
129 repo.requirements.discard('shared')
130 repo.requirements.discard('shared')
130 repo.requirements.discard('relshared')
131 repo.requirements.discard('relshared')
131 repo._writerequirements()
132 repo._writerequirements()
132 finally:
133 finally:
133 destlock and destlock.release()
134 destlock and destlock.release()
134 lock and lock.release()
135 lock and lock.release()
135
136
136 # update store, spath, svfs and sjoin of repo
137 # update store, spath, svfs and sjoin of repo
137 repo.unfiltered().__init__(repo.baseui, repo.root)
138 repo.unfiltered().__init__(repo.baseui, repo.root)
138
139
139 # Wrap clone command to pass auto share options.
140 # Wrap clone command to pass auto share options.
140 def clone(orig, ui, source, *args, **opts):
141 def clone(orig, ui, source, *args, **opts):
141 pool = ui.config('share', 'pool')
142 pool = ui.config('share', 'pool')
142 if pool:
143 if pool:
143 pool = util.expandpath(pool)
144 pool = util.expandpath(pool)
144
145
145 opts[r'shareopts'] = {
146 opts[r'shareopts'] = {
146 'pool': pool,
147 'pool': pool,
147 'mode': ui.config('share', 'poolnaming'),
148 'mode': ui.config('share', 'poolnaming'),
148 }
149 }
149
150
150 return orig(ui, source, *args, **opts)
151 return orig(ui, source, *args, **opts)
151
152
152 def extsetup(ui):
153 def extsetup(ui):
153 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
154 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
154 extensions.wrapfunction(bookmarks.bmstore, '_recordchange', recordchange)
155 extensions.wrapfunction(bookmarks.bmstore, '_recordchange', recordchange)
155 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
156 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
156 extensions.wrapcommand(commands.table, 'clone', clone)
157 extensions.wrapcommand(commands.table, 'clone', clone)
157
158
158 def _hassharedbookmarks(repo):
159 def _hassharedbookmarks(repo):
159 """Returns whether this repo has shared bookmarks"""
160 """Returns whether this repo has shared bookmarks"""
160 try:
161 try:
161 shared = repo.vfs.read('shared').splitlines()
162 shared = repo.vfs.read('shared').splitlines()
162 except IOError as inst:
163 except IOError as inst:
163 if inst.errno != errno.ENOENT:
164 if inst.errno != errno.ENOENT:
164 raise
165 raise
165 return False
166 return False
166 return hg.sharedbookmarks in shared
167 return hg.sharedbookmarks in shared
167
168
168 def _getsrcrepo(repo):
169 def _getsrcrepo(repo):
169 """
170 """
170 Returns the source repository object for a given shared repository.
171 Returns the source repository object for a given shared repository.
171 If repo is not a shared repository, return None.
172 If repo is not a shared repository, return None.
172 """
173 """
173 if repo.sharedpath == repo.path:
174 if repo.sharedpath == repo.path:
174 return None
175 return None
175
176
176 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
177 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
177 return repo.srcrepo
178 return repo.srcrepo
178
179
179 # the sharedpath always ends in the .hg; we want the path to the repo
180 # the sharedpath always ends in the .hg; we want the path to the repo
180 source = repo.vfs.split(repo.sharedpath)[0]
181 source = repo.vfs.split(repo.sharedpath)[0]
181 srcurl, branches = parseurl(source)
182 srcurl, branches = parseurl(source)
182 srcrepo = repository(repo.ui, srcurl)
183 srcrepo = repository(repo.ui, srcurl)
183 repo.srcrepo = srcrepo
184 repo.srcrepo = srcrepo
184 return srcrepo
185 return srcrepo
185
186
186 def getbkfile(orig, repo):
187 def getbkfile(orig, repo):
187 if _hassharedbookmarks(repo):
188 if _hassharedbookmarks(repo):
188 srcrepo = _getsrcrepo(repo)
189 srcrepo = _getsrcrepo(repo)
189 if srcrepo is not None:
190 if srcrepo is not None:
190 # just orig(srcrepo) doesn't work as expected, because
191 # just orig(srcrepo) doesn't work as expected, because
191 # HG_PENDING refers repo.root.
192 # HG_PENDING refers repo.root.
192 try:
193 try:
193 fp, pending = txnutil.trypending(repo.root, repo.vfs,
194 fp, pending = txnutil.trypending(repo.root, repo.vfs,
194 'bookmarks')
195 'bookmarks')
195 if pending:
196 if pending:
196 # only in this case, bookmark information in repo
197 # only in this case, bookmark information in repo
197 # is up-to-date.
198 # is up-to-date.
198 return fp
199 return fp
199 fp.close()
200 fp.close()
200 except IOError as inst:
201 except IOError as inst:
201 if inst.errno != errno.ENOENT:
202 if inst.errno != errno.ENOENT:
202 raise
203 raise
203
204
204 # otherwise, we should read bookmarks from srcrepo,
205 # otherwise, we should read bookmarks from srcrepo,
205 # because .hg/bookmarks in srcrepo might be already
206 # because .hg/bookmarks in srcrepo might be already
206 # changed via another sharing repo
207 # changed via another sharing repo
207 repo = srcrepo
208 repo = srcrepo
208
209
209 # TODO: Pending changes in repo are still invisible in
210 # TODO: Pending changes in repo are still invisible in
210 # srcrepo, because bookmarks.pending is written only into repo.
211 # srcrepo, because bookmarks.pending is written only into repo.
211 # See also https://www.mercurial-scm.org/wiki/SharedRepository
212 # See also https://www.mercurial-scm.org/wiki/SharedRepository
212 return orig(repo)
213 return orig(repo)
213
214
214 def recordchange(orig, self, tr):
215 def recordchange(orig, self, tr):
215 # Continue with write to local bookmarks file as usual
216 # Continue with write to local bookmarks file as usual
216 orig(self, tr)
217 orig(self, tr)
217
218
218 if _hassharedbookmarks(self._repo):
219 if _hassharedbookmarks(self._repo):
219 srcrepo = _getsrcrepo(self._repo)
220 srcrepo = _getsrcrepo(self._repo)
220 if srcrepo is not None:
221 if srcrepo is not None:
221 category = 'share-bookmarks'
222 category = 'share-bookmarks'
222 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
223 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
223
224
224 def writerepo(orig, self, repo):
225 def writerepo(orig, self, repo):
225 # First write local bookmarks file in case we ever unshare
226 # First write local bookmarks file in case we ever unshare
226 orig(self, repo)
227 orig(self, repo)
227
228
228 if _hassharedbookmarks(self._repo):
229 if _hassharedbookmarks(self._repo):
229 srcrepo = _getsrcrepo(self._repo)
230 srcrepo = _getsrcrepo(self._repo)
230 if srcrepo is not None:
231 if srcrepo is not None:
231 orig(self, srcrepo)
232 orig(self, srcrepo)
@@ -1,1064 +1,1065 b''
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 __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import shutil
14 import shutil
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18
18
19 from . import (
19 from . import (
20 bookmarks,
20 bookmarks,
21 bundlerepo,
21 bundlerepo,
22 cmdutil,
22 cmdutil,
23 destutil,
23 destutil,
24 discovery,
24 discovery,
25 error,
25 error,
26 exchange,
26 exchange,
27 extensions,
27 extensions,
28 httppeer,
28 httppeer,
29 localrepo,
29 localrepo,
30 lock,
30 lock,
31 merge as mergemod,
31 merge as mergemod,
32 node,
32 node,
33 phases,
33 phases,
34 repoview,
34 repoview,
35 scmutil,
35 scmutil,
36 sshpeer,
36 sshpeer,
37 statichttprepo,
37 statichttprepo,
38 ui as uimod,
38 ui as uimod,
39 unionrepo,
39 unionrepo,
40 url,
40 url,
41 util,
41 util,
42 verify as verifymod,
42 verify as verifymod,
43 vfs as vfsmod,
43 vfs as vfsmod,
44 )
44 )
45
45
46 release = lock.release
46 release = lock.release
47
47
48 # shared features
48 # shared features
49 sharedbookmarks = 'bookmarks'
49 sharedbookmarks = 'bookmarks'
50
50
51 def _local(path):
51 def _local(path):
52 path = util.expandpath(util.urllocalpath(path))
52 path = util.expandpath(util.urllocalpath(path))
53 return (os.path.isfile(path) and bundlerepo or localrepo)
53 return (os.path.isfile(path) and bundlerepo or localrepo)
54
54
55 def addbranchrevs(lrepo, other, branches, revs):
55 def addbranchrevs(lrepo, other, branches, revs):
56 peer = other.peer() # a courtesy to callers using a localrepo for other
56 peer = other.peer() # a courtesy to callers using a localrepo for other
57 hashbranch, branches = branches
57 hashbranch, branches = branches
58 if not hashbranch and not branches:
58 if not hashbranch and not branches:
59 x = revs or None
59 x = revs or None
60 if util.safehasattr(revs, 'first'):
60 if util.safehasattr(revs, 'first'):
61 y = revs.first()
61 y = revs.first()
62 elif revs:
62 elif revs:
63 y = revs[0]
63 y = revs[0]
64 else:
64 else:
65 y = None
65 y = None
66 return x, y
66 return x, y
67 if revs:
67 if revs:
68 revs = list(revs)
68 revs = list(revs)
69 else:
69 else:
70 revs = []
70 revs = []
71
71
72 if not peer.capable('branchmap'):
72 if not peer.capable('branchmap'):
73 if branches:
73 if branches:
74 raise error.Abort(_("remote branch lookup not supported"))
74 raise error.Abort(_("remote branch lookup not supported"))
75 revs.append(hashbranch)
75 revs.append(hashbranch)
76 return revs, revs[0]
76 return revs, revs[0]
77 branchmap = peer.branchmap()
77 branchmap = peer.branchmap()
78
78
79 def primary(branch):
79 def primary(branch):
80 if branch == '.':
80 if branch == '.':
81 if not lrepo:
81 if not lrepo:
82 raise error.Abort(_("dirstate branch not accessible"))
82 raise error.Abort(_("dirstate branch not accessible"))
83 branch = lrepo.dirstate.branch()
83 branch = lrepo.dirstate.branch()
84 if branch in branchmap:
84 if branch in branchmap:
85 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
85 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
86 return True
86 return True
87 else:
87 else:
88 return False
88 return False
89
89
90 for branch in branches:
90 for branch in branches:
91 if not primary(branch):
91 if not primary(branch):
92 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
92 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
93 if hashbranch:
93 if hashbranch:
94 if not primary(hashbranch):
94 if not primary(hashbranch):
95 revs.append(hashbranch)
95 revs.append(hashbranch)
96 return revs, revs[0]
96 return revs, revs[0]
97
97
98 def parseurl(path, branches=None):
98 def parseurl(path, branches=None):
99 '''parse url#branch, returning (url, (branch, branches))'''
99 '''parse url#branch, returning (url, (branch, branches))'''
100
100
101 u = util.url(path)
101 u = util.url(path)
102 branch = None
102 branch = None
103 if u.fragment:
103 if u.fragment:
104 branch = u.fragment
104 branch = u.fragment
105 u.fragment = None
105 u.fragment = None
106 return bytes(u), (branch, branches or [])
106 return bytes(u), (branch, branches or [])
107
107
108 schemes = {
108 schemes = {
109 'bundle': bundlerepo,
109 'bundle': bundlerepo,
110 'union': unionrepo,
110 'union': unionrepo,
111 'file': _local,
111 'file': _local,
112 'http': httppeer,
112 'http': httppeer,
113 'https': httppeer,
113 'https': httppeer,
114 'ssh': sshpeer,
114 'ssh': sshpeer,
115 'static-http': statichttprepo,
115 'static-http': statichttprepo,
116 }
116 }
117
117
118 def _peerlookup(path):
118 def _peerlookup(path):
119 u = util.url(path)
119 u = util.url(path)
120 scheme = u.scheme or 'file'
120 scheme = u.scheme or 'file'
121 thing = schemes.get(scheme) or schemes['file']
121 thing = schemes.get(scheme) or schemes['file']
122 try:
122 try:
123 return thing(path)
123 return thing(path)
124 except TypeError:
124 except TypeError:
125 # we can't test callable(thing) because 'thing' can be an unloaded
125 # we can't test callable(thing) because 'thing' can be an unloaded
126 # module that implements __call__
126 # module that implements __call__
127 if not util.safehasattr(thing, 'instance'):
127 if not util.safehasattr(thing, 'instance'):
128 raise
128 raise
129 return thing
129 return thing
130
130
131 def islocal(repo):
131 def islocal(repo):
132 '''return true if repo (or path pointing to repo) is local'''
132 '''return true if repo (or path pointing to repo) is local'''
133 if isinstance(repo, bytes):
133 if isinstance(repo, bytes):
134 try:
134 try:
135 return _peerlookup(repo).islocal(repo)
135 return _peerlookup(repo).islocal(repo)
136 except AttributeError:
136 except AttributeError:
137 return False
137 return False
138 return repo.local()
138 return repo.local()
139
139
140 def openpath(ui, path):
140 def openpath(ui, path):
141 '''open path with open if local, url.open if remote'''
141 '''open path with open if local, url.open if remote'''
142 pathurl = util.url(path, parsequery=False, parsefragment=False)
142 pathurl = util.url(path, parsequery=False, parsefragment=False)
143 if pathurl.islocal():
143 if pathurl.islocal():
144 return util.posixfile(pathurl.localpath(), 'rb')
144 return util.posixfile(pathurl.localpath(), 'rb')
145 else:
145 else:
146 return url.open(ui, path)
146 return url.open(ui, path)
147
147
148 # a list of (ui, repo) functions called for wire peer initialization
148 # a list of (ui, repo) functions called for wire peer initialization
149 wirepeersetupfuncs = []
149 wirepeersetupfuncs = []
150
150
151 def _peerorrepo(ui, path, create=False, presetupfuncs=None):
151 def _peerorrepo(ui, path, create=False, presetupfuncs=None):
152 """return a repository object for the specified path"""
152 """return a repository object for the specified path"""
153 obj = _peerlookup(path).instance(ui, path, create)
153 obj = _peerlookup(path).instance(ui, path, create)
154 ui = getattr(obj, "ui", ui)
154 ui = getattr(obj, "ui", ui)
155 for f in presetupfuncs or []:
155 for f in presetupfuncs or []:
156 f(ui, obj)
156 f(ui, obj)
157 for name, module in extensions.extensions(ui):
157 for name, module in extensions.extensions(ui):
158 hook = getattr(module, 'reposetup', None)
158 hook = getattr(module, 'reposetup', None)
159 if hook:
159 if hook:
160 hook(ui, obj)
160 hook(ui, obj)
161 if not obj.local():
161 if not obj.local():
162 for f in wirepeersetupfuncs:
162 for f in wirepeersetupfuncs:
163 f(ui, obj)
163 f(ui, obj)
164 return obj
164 return obj
165
165
166 def repository(ui, path='', create=False, presetupfuncs=None):
166 def repository(ui, path='', create=False, presetupfuncs=None):
167 """return a repository object for the specified path"""
167 """return a repository object for the specified path"""
168 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs)
168 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs)
169 repo = peer.local()
169 repo = peer.local()
170 if not repo:
170 if not repo:
171 raise error.Abort(_("repository '%s' is not local") %
171 raise error.Abort(_("repository '%s' is not local") %
172 (path or peer.url()))
172 (path or peer.url()))
173 return repo.filtered('visible')
173 return repo.filtered('visible')
174
174
175 def peer(uiorrepo, opts, path, create=False):
175 def peer(uiorrepo, opts, path, create=False):
176 '''return a repository peer for the specified path'''
176 '''return a repository peer for the specified path'''
177 rui = remoteui(uiorrepo, opts)
177 rui = remoteui(uiorrepo, opts)
178 return _peerorrepo(rui, path, create).peer()
178 return _peerorrepo(rui, path, create).peer()
179
179
180 def defaultdest(source):
180 def defaultdest(source):
181 '''return default destination of clone if none is given
181 '''return default destination of clone if none is given
182
182
183 >>> defaultdest(b'foo')
183 >>> defaultdest(b'foo')
184 'foo'
184 'foo'
185 >>> defaultdest(b'/foo/bar')
185 >>> defaultdest(b'/foo/bar')
186 'bar'
186 'bar'
187 >>> defaultdest(b'/')
187 >>> defaultdest(b'/')
188 ''
188 ''
189 >>> defaultdest(b'')
189 >>> defaultdest(b'')
190 ''
190 ''
191 >>> defaultdest(b'http://example.org/')
191 >>> defaultdest(b'http://example.org/')
192 ''
192 ''
193 >>> defaultdest(b'http://example.org/foo/')
193 >>> defaultdest(b'http://example.org/foo/')
194 'foo'
194 'foo'
195 '''
195 '''
196 path = util.url(source).path
196 path = util.url(source).path
197 if not path:
197 if not path:
198 return ''
198 return ''
199 return os.path.basename(os.path.normpath(path))
199 return os.path.basename(os.path.normpath(path))
200
200
201 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
201 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
202 relative=False):
202 relative=False):
203 '''create a shared repository'''
203 '''create a shared repository'''
204
204
205 if not islocal(source):
205 if not islocal(source):
206 raise error.Abort(_('can only share local repositories'))
206 raise error.Abort(_('can only share local repositories'))
207
207
208 if not dest:
208 if not dest:
209 dest = defaultdest(source)
209 dest = defaultdest(source)
210 else:
210 else:
211 dest = ui.expandpath(dest)
211 dest = ui.expandpath(dest)
212
212
213 if isinstance(source, str):
213 if isinstance(source, str):
214 origsource = ui.expandpath(source)
214 origsource = ui.expandpath(source)
215 source, branches = parseurl(origsource)
215 source, branches = parseurl(origsource)
216 srcrepo = repository(ui, source)
216 srcrepo = repository(ui, source)
217 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
217 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
218 else:
218 else:
219 srcrepo = source.local()
219 srcrepo = source.local()
220 origsource = source = srcrepo.url()
220 origsource = source = srcrepo.url()
221 checkout = None
221 checkout = None
222
222
223 sharedpath = srcrepo.sharedpath # if our source is already sharing
223 sharedpath = srcrepo.sharedpath # if our source is already sharing
224
224
225 destwvfs = vfsmod.vfs(dest, realpath=True)
225 destwvfs = vfsmod.vfs(dest, realpath=True)
226 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
226 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
227
227
228 if destvfs.lexists():
228 if destvfs.lexists():
229 raise error.Abort(_('destination already exists'))
229 raise error.Abort(_('destination already exists'))
230
230
231 if not destwvfs.isdir():
231 if not destwvfs.isdir():
232 destwvfs.mkdir()
232 destwvfs.mkdir()
233 destvfs.makedir()
233 destvfs.makedir()
234
234
235 requirements = ''
235 requirements = ''
236 try:
236 try:
237 requirements = srcrepo.vfs.read('requires')
237 requirements = srcrepo.vfs.read('requires')
238 except IOError as inst:
238 except IOError as inst:
239 if inst.errno != errno.ENOENT:
239 if inst.errno != errno.ENOENT:
240 raise
240 raise
241
241
242 if relative:
242 if relative:
243 try:
243 try:
244 sharedpath = os.path.relpath(sharedpath, destvfs.base)
244 sharedpath = os.path.relpath(sharedpath, destvfs.base)
245 requirements += 'relshared\n'
245 requirements += 'relshared\n'
246 except IOError as e:
246 except IOError as e:
247 raise error.Abort(_('cannot calculate relative path'),
247 raise error.Abort(_('cannot calculate relative path'),
248 hint=str(e))
248 hint=str(e))
249 else:
249 else:
250 requirements += 'shared\n'
250 requirements += 'shared\n'
251
251
252 destvfs.write('requires', requirements)
252 destvfs.write('requires', requirements)
253 destvfs.write('sharedpath', sharedpath)
253 destvfs.write('sharedpath', sharedpath)
254
254
255 r = repository(ui, destwvfs.base)
255 r = repository(ui, destwvfs.base)
256 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
256 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
257 _postshareupdate(r, update, checkout=checkout)
257 _postshareupdate(r, update, checkout=checkout)
258 return r
258
259
259 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
260 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
260 """Called after a new shared repo is created.
261 """Called after a new shared repo is created.
261
262
262 The new repo only has a requirements file and pointer to the source.
263 The new repo only has a requirements file and pointer to the source.
263 This function configures additional shared data.
264 This function configures additional shared data.
264
265
265 Extensions can wrap this function and write additional entries to
266 Extensions can wrap this function and write additional entries to
266 destrepo/.hg/shared to indicate additional pieces of data to be shared.
267 destrepo/.hg/shared to indicate additional pieces of data to be shared.
267 """
268 """
268 default = defaultpath or sourcerepo.ui.config('paths', 'default')
269 default = defaultpath or sourcerepo.ui.config('paths', 'default')
269 if default:
270 if default:
270 fp = destrepo.vfs("hgrc", "w", text=True)
271 fp = destrepo.vfs("hgrc", "w", text=True)
271 fp.write("[paths]\n")
272 fp.write("[paths]\n")
272 fp.write("default = %s\n" % default)
273 fp.write("default = %s\n" % default)
273 fp.close()
274 fp.close()
274
275
275 with destrepo.wlock():
276 with destrepo.wlock():
276 if bookmarks:
277 if bookmarks:
277 fp = destrepo.vfs('shared', 'w')
278 fp = destrepo.vfs('shared', 'w')
278 fp.write(sharedbookmarks + '\n')
279 fp.write(sharedbookmarks + '\n')
279 fp.close()
280 fp.close()
280
281
281 def _postshareupdate(repo, update, checkout=None):
282 def _postshareupdate(repo, update, checkout=None):
282 """Maybe perform a working directory update after a shared repo is created.
283 """Maybe perform a working directory update after a shared repo is created.
283
284
284 ``update`` can be a boolean or a revision to update to.
285 ``update`` can be a boolean or a revision to update to.
285 """
286 """
286 if not update:
287 if not update:
287 return
288 return
288
289
289 repo.ui.status(_("updating working directory\n"))
290 repo.ui.status(_("updating working directory\n"))
290 if update is not True:
291 if update is not True:
291 checkout = update
292 checkout = update
292 for test in (checkout, 'default', 'tip'):
293 for test in (checkout, 'default', 'tip'):
293 if test is None:
294 if test is None:
294 continue
295 continue
295 try:
296 try:
296 uprev = repo.lookup(test)
297 uprev = repo.lookup(test)
297 break
298 break
298 except error.RepoLookupError:
299 except error.RepoLookupError:
299 continue
300 continue
300 _update(repo, uprev)
301 _update(repo, uprev)
301
302
302 def copystore(ui, srcrepo, destpath):
303 def copystore(ui, srcrepo, destpath):
303 '''copy files from store of srcrepo in destpath
304 '''copy files from store of srcrepo in destpath
304
305
305 returns destlock
306 returns destlock
306 '''
307 '''
307 destlock = None
308 destlock = None
308 try:
309 try:
309 hardlink = None
310 hardlink = None
310 num = 0
311 num = 0
311 closetopic = [None]
312 closetopic = [None]
312 def prog(topic, pos):
313 def prog(topic, pos):
313 if pos is None:
314 if pos is None:
314 closetopic[0] = topic
315 closetopic[0] = topic
315 else:
316 else:
316 ui.progress(topic, pos + num)
317 ui.progress(topic, pos + num)
317 srcpublishing = srcrepo.publishing()
318 srcpublishing = srcrepo.publishing()
318 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
319 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
319 dstvfs = vfsmod.vfs(destpath)
320 dstvfs = vfsmod.vfs(destpath)
320 for f in srcrepo.store.copylist():
321 for f in srcrepo.store.copylist():
321 if srcpublishing and f.endswith('phaseroots'):
322 if srcpublishing and f.endswith('phaseroots'):
322 continue
323 continue
323 dstbase = os.path.dirname(f)
324 dstbase = os.path.dirname(f)
324 if dstbase and not dstvfs.exists(dstbase):
325 if dstbase and not dstvfs.exists(dstbase):
325 dstvfs.mkdir(dstbase)
326 dstvfs.mkdir(dstbase)
326 if srcvfs.exists(f):
327 if srcvfs.exists(f):
327 if f.endswith('data'):
328 if f.endswith('data'):
328 # 'dstbase' may be empty (e.g. revlog format 0)
329 # 'dstbase' may be empty (e.g. revlog format 0)
329 lockfile = os.path.join(dstbase, "lock")
330 lockfile = os.path.join(dstbase, "lock")
330 # lock to avoid premature writing to the target
331 # lock to avoid premature writing to the target
331 destlock = lock.lock(dstvfs, lockfile)
332 destlock = lock.lock(dstvfs, lockfile)
332 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
333 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
333 hardlink, progress=prog)
334 hardlink, progress=prog)
334 num += n
335 num += n
335 if hardlink:
336 if hardlink:
336 ui.debug("linked %d files\n" % num)
337 ui.debug("linked %d files\n" % num)
337 if closetopic[0]:
338 if closetopic[0]:
338 ui.progress(closetopic[0], None)
339 ui.progress(closetopic[0], None)
339 else:
340 else:
340 ui.debug("copied %d files\n" % num)
341 ui.debug("copied %d files\n" % num)
341 if closetopic[0]:
342 if closetopic[0]:
342 ui.progress(closetopic[0], None)
343 ui.progress(closetopic[0], None)
343 return destlock
344 return destlock
344 except: # re-raises
345 except: # re-raises
345 release(destlock)
346 release(destlock)
346 raise
347 raise
347
348
348 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
349 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
349 rev=None, update=True, stream=False):
350 rev=None, update=True, stream=False):
350 """Perform a clone using a shared repo.
351 """Perform a clone using a shared repo.
351
352
352 The store for the repository will be located at <sharepath>/.hg. The
353 The store for the repository will be located at <sharepath>/.hg. The
353 specified revisions will be cloned or pulled from "source". A shared repo
354 specified revisions will be cloned or pulled from "source". A shared repo
354 will be created at "dest" and a working copy will be created if "update" is
355 will be created at "dest" and a working copy will be created if "update" is
355 True.
356 True.
356 """
357 """
357 revs = None
358 revs = None
358 if rev:
359 if rev:
359 if not srcpeer.capable('lookup'):
360 if not srcpeer.capable('lookup'):
360 raise error.Abort(_("src repository does not support "
361 raise error.Abort(_("src repository does not support "
361 "revision lookup and so doesn't "
362 "revision lookup and so doesn't "
362 "support clone by revision"))
363 "support clone by revision"))
363 revs = [srcpeer.lookup(r) for r in rev]
364 revs = [srcpeer.lookup(r) for r in rev]
364
365
365 # Obtain a lock before checking for or cloning the pooled repo otherwise
366 # Obtain a lock before checking for or cloning the pooled repo otherwise
366 # 2 clients may race creating or populating it.
367 # 2 clients may race creating or populating it.
367 pooldir = os.path.dirname(sharepath)
368 pooldir = os.path.dirname(sharepath)
368 # lock class requires the directory to exist.
369 # lock class requires the directory to exist.
369 try:
370 try:
370 util.makedir(pooldir, False)
371 util.makedir(pooldir, False)
371 except OSError as e:
372 except OSError as e:
372 if e.errno != errno.EEXIST:
373 if e.errno != errno.EEXIST:
373 raise
374 raise
374
375
375 poolvfs = vfsmod.vfs(pooldir)
376 poolvfs = vfsmod.vfs(pooldir)
376 basename = os.path.basename(sharepath)
377 basename = os.path.basename(sharepath)
377
378
378 with lock.lock(poolvfs, '%s.lock' % basename):
379 with lock.lock(poolvfs, '%s.lock' % basename):
379 if os.path.exists(sharepath):
380 if os.path.exists(sharepath):
380 ui.status(_('(sharing from existing pooled repository %s)\n') %
381 ui.status(_('(sharing from existing pooled repository %s)\n') %
381 basename)
382 basename)
382 else:
383 else:
383 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
384 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
384 # Always use pull mode because hardlinks in share mode don't work
385 # Always use pull mode because hardlinks in share mode don't work
385 # well. Never update because working copies aren't necessary in
386 # well. Never update because working copies aren't necessary in
386 # share mode.
387 # share mode.
387 clone(ui, peeropts, source, dest=sharepath, pull=True,
388 clone(ui, peeropts, source, dest=sharepath, pull=True,
388 rev=rev, update=False, stream=stream)
389 rev=rev, update=False, stream=stream)
389
390
390 # Resolve the value to put in [paths] section for the source.
391 # Resolve the value to put in [paths] section for the source.
391 if islocal(source):
392 if islocal(source):
392 defaultpath = os.path.abspath(util.urllocalpath(source))
393 defaultpath = os.path.abspath(util.urllocalpath(source))
393 else:
394 else:
394 defaultpath = source
395 defaultpath = source
395
396
396 sharerepo = repository(ui, path=sharepath)
397 sharerepo = repository(ui, path=sharepath)
397 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
398 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
398 defaultpath=defaultpath)
399 defaultpath=defaultpath)
399
400
400 # We need to perform a pull against the dest repo to fetch bookmarks
401 # We need to perform a pull against the dest repo to fetch bookmarks
401 # and other non-store data that isn't shared by default. In the case of
402 # and other non-store data that isn't shared by default. In the case of
402 # non-existing shared repo, this means we pull from the remote twice. This
403 # non-existing shared repo, this means we pull from the remote twice. This
403 # is a bit weird. But at the time it was implemented, there wasn't an easy
404 # is a bit weird. But at the time it was implemented, there wasn't an easy
404 # way to pull just non-changegroup data.
405 # way to pull just non-changegroup data.
405 destrepo = repository(ui, path=dest)
406 destrepo = repository(ui, path=dest)
406 exchange.pull(destrepo, srcpeer, heads=revs)
407 exchange.pull(destrepo, srcpeer, heads=revs)
407
408
408 _postshareupdate(destrepo, update)
409 _postshareupdate(destrepo, update)
409
410
410 return srcpeer, peer(ui, peeropts, dest)
411 return srcpeer, peer(ui, peeropts, dest)
411
412
412 # Recomputing branch cache might be slow on big repos,
413 # Recomputing branch cache might be slow on big repos,
413 # so just copy it
414 # so just copy it
414 def _copycache(srcrepo, dstcachedir, fname):
415 def _copycache(srcrepo, dstcachedir, fname):
415 """copy a cache from srcrepo to destcachedir (if it exists)"""
416 """copy a cache from srcrepo to destcachedir (if it exists)"""
416 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
417 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
417 dstbranchcache = os.path.join(dstcachedir, fname)
418 dstbranchcache = os.path.join(dstcachedir, fname)
418 if os.path.exists(srcbranchcache):
419 if os.path.exists(srcbranchcache):
419 if not os.path.exists(dstcachedir):
420 if not os.path.exists(dstcachedir):
420 os.mkdir(dstcachedir)
421 os.mkdir(dstcachedir)
421 util.copyfile(srcbranchcache, dstbranchcache)
422 util.copyfile(srcbranchcache, dstbranchcache)
422
423
423 def _cachetocopy(srcrepo):
424 def _cachetocopy(srcrepo):
424 """return the list of cache file valuable to copy during a clone"""
425 """return the list of cache file valuable to copy during a clone"""
425 # In local clones we're copying all nodes, not just served
426 # In local clones we're copying all nodes, not just served
426 # ones. Therefore copy all branch caches over.
427 # ones. Therefore copy all branch caches over.
427 cachefiles = ['branch2']
428 cachefiles = ['branch2']
428 cachefiles += ['branch2-%s' % f for f in repoview.filtertable]
429 cachefiles += ['branch2-%s' % f for f in repoview.filtertable]
429 cachefiles += ['rbc-names-v1', 'rbc-revs-v1']
430 cachefiles += ['rbc-names-v1', 'rbc-revs-v1']
430 cachefiles += ['tags2']
431 cachefiles += ['tags2']
431 cachefiles += ['tags2-%s' % f for f in repoview.filtertable]
432 cachefiles += ['tags2-%s' % f for f in repoview.filtertable]
432 cachefiles += ['hgtagsfnodes1']
433 cachefiles += ['hgtagsfnodes1']
433 return cachefiles
434 return cachefiles
434
435
435 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
436 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
436 update=True, stream=False, branch=None, shareopts=None):
437 update=True, stream=False, branch=None, shareopts=None):
437 """Make a copy of an existing repository.
438 """Make a copy of an existing repository.
438
439
439 Create a copy of an existing repository in a new directory. The
440 Create a copy of an existing repository in a new directory. The
440 source and destination are URLs, as passed to the repository
441 source and destination are URLs, as passed to the repository
441 function. Returns a pair of repository peers, the source and
442 function. Returns a pair of repository peers, the source and
442 newly created destination.
443 newly created destination.
443
444
444 The location of the source is added to the new repository's
445 The location of the source is added to the new repository's
445 .hg/hgrc file, as the default to be used for future pulls and
446 .hg/hgrc file, as the default to be used for future pulls and
446 pushes.
447 pushes.
447
448
448 If an exception is raised, the partly cloned/updated destination
449 If an exception is raised, the partly cloned/updated destination
449 repository will be deleted.
450 repository will be deleted.
450
451
451 Arguments:
452 Arguments:
452
453
453 source: repository object or URL
454 source: repository object or URL
454
455
455 dest: URL of destination repository to create (defaults to base
456 dest: URL of destination repository to create (defaults to base
456 name of source repository)
457 name of source repository)
457
458
458 pull: always pull from source repository, even in local case or if the
459 pull: always pull from source repository, even in local case or if the
459 server prefers streaming
460 server prefers streaming
460
461
461 stream: stream raw data uncompressed from repository (fast over
462 stream: stream raw data uncompressed from repository (fast over
462 LAN, slow over WAN)
463 LAN, slow over WAN)
463
464
464 rev: revision to clone up to (implies pull=True)
465 rev: revision to clone up to (implies pull=True)
465
466
466 update: update working directory after clone completes, if
467 update: update working directory after clone completes, if
467 destination is local repository (True means update to default rev,
468 destination is local repository (True means update to default rev,
468 anything else is treated as a revision)
469 anything else is treated as a revision)
469
470
470 branch: branches to clone
471 branch: branches to clone
471
472
472 shareopts: dict of options to control auto sharing behavior. The "pool" key
473 shareopts: dict of options to control auto sharing behavior. The "pool" key
473 activates auto sharing mode and defines the directory for stores. The
474 activates auto sharing mode and defines the directory for stores. The
474 "mode" key determines how to construct the directory name of the shared
475 "mode" key determines how to construct the directory name of the shared
475 repository. "identity" means the name is derived from the node of the first
476 repository. "identity" means the name is derived from the node of the first
476 changeset in the repository. "remote" means the name is derived from the
477 changeset in the repository. "remote" means the name is derived from the
477 remote's path/URL. Defaults to "identity."
478 remote's path/URL. Defaults to "identity."
478 """
479 """
479
480
480 if isinstance(source, bytes):
481 if isinstance(source, bytes):
481 origsource = ui.expandpath(source)
482 origsource = ui.expandpath(source)
482 source, branch = parseurl(origsource, branch)
483 source, branch = parseurl(origsource, branch)
483 srcpeer = peer(ui, peeropts, source)
484 srcpeer = peer(ui, peeropts, source)
484 else:
485 else:
485 srcpeer = source.peer() # in case we were called with a localrepo
486 srcpeer = source.peer() # in case we were called with a localrepo
486 branch = (None, branch or [])
487 branch = (None, branch or [])
487 origsource = source = srcpeer.url()
488 origsource = source = srcpeer.url()
488 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
489 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
489
490
490 if dest is None:
491 if dest is None:
491 dest = defaultdest(source)
492 dest = defaultdest(source)
492 if dest:
493 if dest:
493 ui.status(_("destination directory: %s\n") % dest)
494 ui.status(_("destination directory: %s\n") % dest)
494 else:
495 else:
495 dest = ui.expandpath(dest)
496 dest = ui.expandpath(dest)
496
497
497 dest = util.urllocalpath(dest)
498 dest = util.urllocalpath(dest)
498 source = util.urllocalpath(source)
499 source = util.urllocalpath(source)
499
500
500 if not dest:
501 if not dest:
501 raise error.Abort(_("empty destination path is not valid"))
502 raise error.Abort(_("empty destination path is not valid"))
502
503
503 destvfs = vfsmod.vfs(dest, expandpath=True)
504 destvfs = vfsmod.vfs(dest, expandpath=True)
504 if destvfs.lexists():
505 if destvfs.lexists():
505 if not destvfs.isdir():
506 if not destvfs.isdir():
506 raise error.Abort(_("destination '%s' already exists") % dest)
507 raise error.Abort(_("destination '%s' already exists") % dest)
507 elif destvfs.listdir():
508 elif destvfs.listdir():
508 raise error.Abort(_("destination '%s' is not empty") % dest)
509 raise error.Abort(_("destination '%s' is not empty") % dest)
509
510
510 shareopts = shareopts or {}
511 shareopts = shareopts or {}
511 sharepool = shareopts.get('pool')
512 sharepool = shareopts.get('pool')
512 sharenamemode = shareopts.get('mode')
513 sharenamemode = shareopts.get('mode')
513 if sharepool and islocal(dest):
514 if sharepool and islocal(dest):
514 sharepath = None
515 sharepath = None
515 if sharenamemode == 'identity':
516 if sharenamemode == 'identity':
516 # Resolve the name from the initial changeset in the remote
517 # Resolve the name from the initial changeset in the remote
517 # repository. This returns nullid when the remote is empty. It
518 # repository. This returns nullid when the remote is empty. It
518 # raises RepoLookupError if revision 0 is filtered or otherwise
519 # raises RepoLookupError if revision 0 is filtered or otherwise
519 # not available. If we fail to resolve, sharing is not enabled.
520 # not available. If we fail to resolve, sharing is not enabled.
520 try:
521 try:
521 rootnode = srcpeer.lookup('0')
522 rootnode = srcpeer.lookup('0')
522 if rootnode != node.nullid:
523 if rootnode != node.nullid:
523 sharepath = os.path.join(sharepool, node.hex(rootnode))
524 sharepath = os.path.join(sharepool, node.hex(rootnode))
524 else:
525 else:
525 ui.status(_('(not using pooled storage: '
526 ui.status(_('(not using pooled storage: '
526 'remote appears to be empty)\n'))
527 'remote appears to be empty)\n'))
527 except error.RepoLookupError:
528 except error.RepoLookupError:
528 ui.status(_('(not using pooled storage: '
529 ui.status(_('(not using pooled storage: '
529 'unable to resolve identity of remote)\n'))
530 'unable to resolve identity of remote)\n'))
530 elif sharenamemode == 'remote':
531 elif sharenamemode == 'remote':
531 sharepath = os.path.join(
532 sharepath = os.path.join(
532 sharepool, hashlib.sha1(source).hexdigest())
533 sharepool, hashlib.sha1(source).hexdigest())
533 else:
534 else:
534 raise error.Abort(_('unknown share naming mode: %s') %
535 raise error.Abort(_('unknown share naming mode: %s') %
535 sharenamemode)
536 sharenamemode)
536
537
537 if sharepath:
538 if sharepath:
538 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
539 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
539 dest, pull=pull, rev=rev, update=update,
540 dest, pull=pull, rev=rev, update=update,
540 stream=stream)
541 stream=stream)
541
542
542 srclock = destlock = cleandir = None
543 srclock = destlock = cleandir = None
543 srcrepo = srcpeer.local()
544 srcrepo = srcpeer.local()
544 try:
545 try:
545 abspath = origsource
546 abspath = origsource
546 if islocal(origsource):
547 if islocal(origsource):
547 abspath = os.path.abspath(util.urllocalpath(origsource))
548 abspath = os.path.abspath(util.urllocalpath(origsource))
548
549
549 if islocal(dest):
550 if islocal(dest):
550 cleandir = dest
551 cleandir = dest
551
552
552 copy = False
553 copy = False
553 if (srcrepo and srcrepo.cancopy() and islocal(dest)
554 if (srcrepo and srcrepo.cancopy() and islocal(dest)
554 and not phases.hassecret(srcrepo)):
555 and not phases.hassecret(srcrepo)):
555 copy = not pull and not rev
556 copy = not pull and not rev
556
557
557 if copy:
558 if copy:
558 try:
559 try:
559 # we use a lock here because if we race with commit, we
560 # we use a lock here because if we race with commit, we
560 # can end up with extra data in the cloned revlogs that's
561 # can end up with extra data in the cloned revlogs that's
561 # not pointed to by changesets, thus causing verify to
562 # not pointed to by changesets, thus causing verify to
562 # fail
563 # fail
563 srclock = srcrepo.lock(wait=False)
564 srclock = srcrepo.lock(wait=False)
564 except error.LockError:
565 except error.LockError:
565 copy = False
566 copy = False
566
567
567 if copy:
568 if copy:
568 srcrepo.hook('preoutgoing', throw=True, source='clone')
569 srcrepo.hook('preoutgoing', throw=True, source='clone')
569 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
570 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
570 if not os.path.exists(dest):
571 if not os.path.exists(dest):
571 os.mkdir(dest)
572 os.mkdir(dest)
572 else:
573 else:
573 # only clean up directories we create ourselves
574 # only clean up directories we create ourselves
574 cleandir = hgdir
575 cleandir = hgdir
575 try:
576 try:
576 destpath = hgdir
577 destpath = hgdir
577 util.makedir(destpath, notindexed=True)
578 util.makedir(destpath, notindexed=True)
578 except OSError as inst:
579 except OSError as inst:
579 if inst.errno == errno.EEXIST:
580 if inst.errno == errno.EEXIST:
580 cleandir = None
581 cleandir = None
581 raise error.Abort(_("destination '%s' already exists")
582 raise error.Abort(_("destination '%s' already exists")
582 % dest)
583 % dest)
583 raise
584 raise
584
585
585 destlock = copystore(ui, srcrepo, destpath)
586 destlock = copystore(ui, srcrepo, destpath)
586 # copy bookmarks over
587 # copy bookmarks over
587 srcbookmarks = srcrepo.vfs.join('bookmarks')
588 srcbookmarks = srcrepo.vfs.join('bookmarks')
588 dstbookmarks = os.path.join(destpath, 'bookmarks')
589 dstbookmarks = os.path.join(destpath, 'bookmarks')
589 if os.path.exists(srcbookmarks):
590 if os.path.exists(srcbookmarks):
590 util.copyfile(srcbookmarks, dstbookmarks)
591 util.copyfile(srcbookmarks, dstbookmarks)
591
592
592 dstcachedir = os.path.join(destpath, 'cache')
593 dstcachedir = os.path.join(destpath, 'cache')
593 for cache in _cachetocopy(srcrepo):
594 for cache in _cachetocopy(srcrepo):
594 _copycache(srcrepo, dstcachedir, cache)
595 _copycache(srcrepo, dstcachedir, cache)
595
596
596 # we need to re-init the repo after manually copying the data
597 # we need to re-init the repo after manually copying the data
597 # into it
598 # into it
598 destpeer = peer(srcrepo, peeropts, dest)
599 destpeer = peer(srcrepo, peeropts, dest)
599 srcrepo.hook('outgoing', source='clone',
600 srcrepo.hook('outgoing', source='clone',
600 node=node.hex(node.nullid))
601 node=node.hex(node.nullid))
601 else:
602 else:
602 try:
603 try:
603 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
604 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
604 # only pass ui when no srcrepo
605 # only pass ui when no srcrepo
605 except OSError as inst:
606 except OSError as inst:
606 if inst.errno == errno.EEXIST:
607 if inst.errno == errno.EEXIST:
607 cleandir = None
608 cleandir = None
608 raise error.Abort(_("destination '%s' already exists")
609 raise error.Abort(_("destination '%s' already exists")
609 % dest)
610 % dest)
610 raise
611 raise
611
612
612 revs = None
613 revs = None
613 if rev:
614 if rev:
614 if not srcpeer.capable('lookup'):
615 if not srcpeer.capable('lookup'):
615 raise error.Abort(_("src repository does not support "
616 raise error.Abort(_("src repository does not support "
616 "revision lookup and so doesn't "
617 "revision lookup and so doesn't "
617 "support clone by revision"))
618 "support clone by revision"))
618 revs = [srcpeer.lookup(r) for r in rev]
619 revs = [srcpeer.lookup(r) for r in rev]
619 checkout = revs[0]
620 checkout = revs[0]
620 local = destpeer.local()
621 local = destpeer.local()
621 if local:
622 if local:
622 if not stream:
623 if not stream:
623 if pull:
624 if pull:
624 stream = False
625 stream = False
625 else:
626 else:
626 stream = None
627 stream = None
627 # internal config: ui.quietbookmarkmove
628 # internal config: ui.quietbookmarkmove
628 overrides = {('ui', 'quietbookmarkmove'): True}
629 overrides = {('ui', 'quietbookmarkmove'): True}
629 with local.ui.configoverride(overrides, 'clone'):
630 with local.ui.configoverride(overrides, 'clone'):
630 exchange.pull(local, srcpeer, revs,
631 exchange.pull(local, srcpeer, revs,
631 streamclonerequested=stream)
632 streamclonerequested=stream)
632 elif srcrepo:
633 elif srcrepo:
633 exchange.push(srcrepo, destpeer, revs=revs,
634 exchange.push(srcrepo, destpeer, revs=revs,
634 bookmarks=srcrepo._bookmarks.keys())
635 bookmarks=srcrepo._bookmarks.keys())
635 else:
636 else:
636 raise error.Abort(_("clone from remote to remote not supported")
637 raise error.Abort(_("clone from remote to remote not supported")
637 )
638 )
638
639
639 cleandir = None
640 cleandir = None
640
641
641 destrepo = destpeer.local()
642 destrepo = destpeer.local()
642 if destrepo:
643 if destrepo:
643 template = uimod.samplehgrcs['cloned']
644 template = uimod.samplehgrcs['cloned']
644 fp = destrepo.vfs("hgrc", "wb")
645 fp = destrepo.vfs("hgrc", "wb")
645 u = util.url(abspath)
646 u = util.url(abspath)
646 u.passwd = None
647 u.passwd = None
647 defaulturl = bytes(u)
648 defaulturl = bytes(u)
648 fp.write(util.tonativeeol(template % defaulturl))
649 fp.write(util.tonativeeol(template % defaulturl))
649 fp.close()
650 fp.close()
650
651
651 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
652 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
652
653
653 if update:
654 if update:
654 if update is not True:
655 if update is not True:
655 checkout = srcpeer.lookup(update)
656 checkout = srcpeer.lookup(update)
656 uprev = None
657 uprev = None
657 status = None
658 status = None
658 if checkout is not None:
659 if checkout is not None:
659 try:
660 try:
660 uprev = destrepo.lookup(checkout)
661 uprev = destrepo.lookup(checkout)
661 except error.RepoLookupError:
662 except error.RepoLookupError:
662 if update is not True:
663 if update is not True:
663 try:
664 try:
664 uprev = destrepo.lookup(update)
665 uprev = destrepo.lookup(update)
665 except error.RepoLookupError:
666 except error.RepoLookupError:
666 pass
667 pass
667 if uprev is None:
668 if uprev is None:
668 try:
669 try:
669 uprev = destrepo._bookmarks['@']
670 uprev = destrepo._bookmarks['@']
670 update = '@'
671 update = '@'
671 bn = destrepo[uprev].branch()
672 bn = destrepo[uprev].branch()
672 if bn == 'default':
673 if bn == 'default':
673 status = _("updating to bookmark @\n")
674 status = _("updating to bookmark @\n")
674 else:
675 else:
675 status = (_("updating to bookmark @ on branch %s\n")
676 status = (_("updating to bookmark @ on branch %s\n")
676 % bn)
677 % bn)
677 except KeyError:
678 except KeyError:
678 try:
679 try:
679 uprev = destrepo.branchtip('default')
680 uprev = destrepo.branchtip('default')
680 except error.RepoLookupError:
681 except error.RepoLookupError:
681 uprev = destrepo.lookup('tip')
682 uprev = destrepo.lookup('tip')
682 if not status:
683 if not status:
683 bn = destrepo[uprev].branch()
684 bn = destrepo[uprev].branch()
684 status = _("updating to branch %s\n") % bn
685 status = _("updating to branch %s\n") % bn
685 destrepo.ui.status(status)
686 destrepo.ui.status(status)
686 _update(destrepo, uprev)
687 _update(destrepo, uprev)
687 if update in destrepo._bookmarks:
688 if update in destrepo._bookmarks:
688 bookmarks.activate(destrepo, update)
689 bookmarks.activate(destrepo, update)
689 finally:
690 finally:
690 release(srclock, destlock)
691 release(srclock, destlock)
691 if cleandir is not None:
692 if cleandir is not None:
692 shutil.rmtree(cleandir, True)
693 shutil.rmtree(cleandir, True)
693 if srcpeer is not None:
694 if srcpeer is not None:
694 srcpeer.close()
695 srcpeer.close()
695 return srcpeer, destpeer
696 return srcpeer, destpeer
696
697
697 def _showstats(repo, stats, quietempty=False):
698 def _showstats(repo, stats, quietempty=False):
698 if quietempty and not any(stats):
699 if quietempty and not any(stats):
699 return
700 return
700 repo.ui.status(_("%d files updated, %d files merged, "
701 repo.ui.status(_("%d files updated, %d files merged, "
701 "%d files removed, %d files unresolved\n") % stats)
702 "%d files removed, %d files unresolved\n") % stats)
702
703
703 def updaterepo(repo, node, overwrite, updatecheck=None):
704 def updaterepo(repo, node, overwrite, updatecheck=None):
704 """Update the working directory to node.
705 """Update the working directory to node.
705
706
706 When overwrite is set, changes are clobbered, merged else
707 When overwrite is set, changes are clobbered, merged else
707
708
708 returns stats (see pydoc mercurial.merge.applyupdates)"""
709 returns stats (see pydoc mercurial.merge.applyupdates)"""
709 return mergemod.update(repo, node, False, overwrite,
710 return mergemod.update(repo, node, False, overwrite,
710 labels=['working copy', 'destination'],
711 labels=['working copy', 'destination'],
711 updatecheck=updatecheck)
712 updatecheck=updatecheck)
712
713
713 def update(repo, node, quietempty=False, updatecheck=None):
714 def update(repo, node, quietempty=False, updatecheck=None):
714 """update the working directory to node"""
715 """update the working directory to node"""
715 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
716 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
716 _showstats(repo, stats, quietempty)
717 _showstats(repo, stats, quietempty)
717 if stats[3]:
718 if stats[3]:
718 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
719 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
719 return stats[3] > 0
720 return stats[3] > 0
720
721
721 # naming conflict in clone()
722 # naming conflict in clone()
722 _update = update
723 _update = update
723
724
724 def clean(repo, node, show_stats=True, quietempty=False):
725 def clean(repo, node, show_stats=True, quietempty=False):
725 """forcibly switch the working directory to node, clobbering changes"""
726 """forcibly switch the working directory to node, clobbering changes"""
726 stats = updaterepo(repo, node, True)
727 stats = updaterepo(repo, node, True)
727 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
728 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
728 if show_stats:
729 if show_stats:
729 _showstats(repo, stats, quietempty)
730 _showstats(repo, stats, quietempty)
730 return stats[3] > 0
731 return stats[3] > 0
731
732
732 # naming conflict in updatetotally()
733 # naming conflict in updatetotally()
733 _clean = clean
734 _clean = clean
734
735
735 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
736 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
736 """Update the working directory with extra care for non-file components
737 """Update the working directory with extra care for non-file components
737
738
738 This takes care of non-file components below:
739 This takes care of non-file components below:
739
740
740 :bookmark: might be advanced or (in)activated
741 :bookmark: might be advanced or (in)activated
741
742
742 This takes arguments below:
743 This takes arguments below:
743
744
744 :checkout: to which revision the working directory is updated
745 :checkout: to which revision the working directory is updated
745 :brev: a name, which might be a bookmark to be activated after updating
746 :brev: a name, which might be a bookmark to be activated after updating
746 :clean: whether changes in the working directory can be discarded
747 :clean: whether changes in the working directory can be discarded
747 :updatecheck: how to deal with a dirty working directory
748 :updatecheck: how to deal with a dirty working directory
748
749
749 Valid values for updatecheck are (None => linear):
750 Valid values for updatecheck are (None => linear):
750
751
751 * abort: abort if the working directory is dirty
752 * abort: abort if the working directory is dirty
752 * none: don't check (merge working directory changes into destination)
753 * none: don't check (merge working directory changes into destination)
753 * linear: check that update is linear before merging working directory
754 * linear: check that update is linear before merging working directory
754 changes into destination
755 changes into destination
755 * noconflict: check that the update does not result in file merges
756 * noconflict: check that the update does not result in file merges
756
757
757 This returns whether conflict is detected at updating or not.
758 This returns whether conflict is detected at updating or not.
758 """
759 """
759 if updatecheck is None:
760 if updatecheck is None:
760 updatecheck = ui.config('commands', 'update.check')
761 updatecheck = ui.config('commands', 'update.check')
761 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
762 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
762 # If not configured, or invalid value configured
763 # If not configured, or invalid value configured
763 updatecheck = 'linear'
764 updatecheck = 'linear'
764 with repo.wlock():
765 with repo.wlock():
765 movemarkfrom = None
766 movemarkfrom = None
766 warndest = False
767 warndest = False
767 if checkout is None:
768 if checkout is None:
768 updata = destutil.destupdate(repo, clean=clean)
769 updata = destutil.destupdate(repo, clean=clean)
769 checkout, movemarkfrom, brev = updata
770 checkout, movemarkfrom, brev = updata
770 warndest = True
771 warndest = True
771
772
772 if clean:
773 if clean:
773 ret = _clean(repo, checkout)
774 ret = _clean(repo, checkout)
774 else:
775 else:
775 if updatecheck == 'abort':
776 if updatecheck == 'abort':
776 cmdutil.bailifchanged(repo, merge=False)
777 cmdutil.bailifchanged(repo, merge=False)
777 updatecheck = 'none'
778 updatecheck = 'none'
778 ret = _update(repo, checkout, updatecheck=updatecheck)
779 ret = _update(repo, checkout, updatecheck=updatecheck)
779
780
780 if not ret and movemarkfrom:
781 if not ret and movemarkfrom:
781 if movemarkfrom == repo['.'].node():
782 if movemarkfrom == repo['.'].node():
782 pass # no-op update
783 pass # no-op update
783 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
784 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
784 b = ui.label(repo._activebookmark, 'bookmarks.active')
785 b = ui.label(repo._activebookmark, 'bookmarks.active')
785 ui.status(_("updating bookmark %s\n") % b)
786 ui.status(_("updating bookmark %s\n") % b)
786 else:
787 else:
787 # this can happen with a non-linear update
788 # this can happen with a non-linear update
788 b = ui.label(repo._activebookmark, 'bookmarks')
789 b = ui.label(repo._activebookmark, 'bookmarks')
789 ui.status(_("(leaving bookmark %s)\n") % b)
790 ui.status(_("(leaving bookmark %s)\n") % b)
790 bookmarks.deactivate(repo)
791 bookmarks.deactivate(repo)
791 elif brev in repo._bookmarks:
792 elif brev in repo._bookmarks:
792 if brev != repo._activebookmark:
793 if brev != repo._activebookmark:
793 b = ui.label(brev, 'bookmarks.active')
794 b = ui.label(brev, 'bookmarks.active')
794 ui.status(_("(activating bookmark %s)\n") % b)
795 ui.status(_("(activating bookmark %s)\n") % b)
795 bookmarks.activate(repo, brev)
796 bookmarks.activate(repo, brev)
796 elif brev:
797 elif brev:
797 if repo._activebookmark:
798 if repo._activebookmark:
798 b = ui.label(repo._activebookmark, 'bookmarks')
799 b = ui.label(repo._activebookmark, 'bookmarks')
799 ui.status(_("(leaving bookmark %s)\n") % b)
800 ui.status(_("(leaving bookmark %s)\n") % b)
800 bookmarks.deactivate(repo)
801 bookmarks.deactivate(repo)
801
802
802 if warndest:
803 if warndest:
803 destutil.statusotherdests(ui, repo)
804 destutil.statusotherdests(ui, repo)
804
805
805 return ret
806 return ret
806
807
807 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None):
808 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None):
808 """Branch merge with node, resolving changes. Return true if any
809 """Branch merge with node, resolving changes. Return true if any
809 unresolved conflicts."""
810 unresolved conflicts."""
810 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
811 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
811 labels=labels)
812 labels=labels)
812 _showstats(repo, stats)
813 _showstats(repo, stats)
813 if stats[3]:
814 if stats[3]:
814 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
815 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
815 "or 'hg update -C .' to abandon\n"))
816 "or 'hg update -C .' to abandon\n"))
816 elif remind:
817 elif remind:
817 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
818 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
818 return stats[3] > 0
819 return stats[3] > 0
819
820
820 def _incoming(displaychlist, subreporecurse, ui, repo, source,
821 def _incoming(displaychlist, subreporecurse, ui, repo, source,
821 opts, buffered=False):
822 opts, buffered=False):
822 """
823 """
823 Helper for incoming / gincoming.
824 Helper for incoming / gincoming.
824 displaychlist gets called with
825 displaychlist gets called with
825 (remoterepo, incomingchangesetlist, displayer) parameters,
826 (remoterepo, incomingchangesetlist, displayer) parameters,
826 and is supposed to contain only code that can't be unified.
827 and is supposed to contain only code that can't be unified.
827 """
828 """
828 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
829 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
829 other = peer(repo, opts, source)
830 other = peer(repo, opts, source)
830 ui.status(_('comparing with %s\n') % util.hidepassword(source))
831 ui.status(_('comparing with %s\n') % util.hidepassword(source))
831 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
832 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
832
833
833 if revs:
834 if revs:
834 revs = [other.lookup(rev) for rev in revs]
835 revs = [other.lookup(rev) for rev in revs]
835 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
836 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
836 revs, opts["bundle"], opts["force"])
837 revs, opts["bundle"], opts["force"])
837 try:
838 try:
838 if not chlist:
839 if not chlist:
839 ui.status(_("no changes found\n"))
840 ui.status(_("no changes found\n"))
840 return subreporecurse()
841 return subreporecurse()
841 ui.pager('incoming')
842 ui.pager('incoming')
842 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
843 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
843 displaychlist(other, chlist, displayer)
844 displaychlist(other, chlist, displayer)
844 displayer.close()
845 displayer.close()
845 finally:
846 finally:
846 cleanupfn()
847 cleanupfn()
847 subreporecurse()
848 subreporecurse()
848 return 0 # exit code is zero since we found incoming changes
849 return 0 # exit code is zero since we found incoming changes
849
850
850 def incoming(ui, repo, source, opts):
851 def incoming(ui, repo, source, opts):
851 def subreporecurse():
852 def subreporecurse():
852 ret = 1
853 ret = 1
853 if opts.get('subrepos'):
854 if opts.get('subrepos'):
854 ctx = repo[None]
855 ctx = repo[None]
855 for subpath in sorted(ctx.substate):
856 for subpath in sorted(ctx.substate):
856 sub = ctx.sub(subpath)
857 sub = ctx.sub(subpath)
857 ret = min(ret, sub.incoming(ui, source, opts))
858 ret = min(ret, sub.incoming(ui, source, opts))
858 return ret
859 return ret
859
860
860 def display(other, chlist, displayer):
861 def display(other, chlist, displayer):
861 limit = cmdutil.loglimit(opts)
862 limit = cmdutil.loglimit(opts)
862 if opts.get('newest_first'):
863 if opts.get('newest_first'):
863 chlist.reverse()
864 chlist.reverse()
864 count = 0
865 count = 0
865 for n in chlist:
866 for n in chlist:
866 if limit is not None and count >= limit:
867 if limit is not None and count >= limit:
867 break
868 break
868 parents = [p for p in other.changelog.parents(n) if p != nullid]
869 parents = [p for p in other.changelog.parents(n) if p != nullid]
869 if opts.get('no_merges') and len(parents) == 2:
870 if opts.get('no_merges') and len(parents) == 2:
870 continue
871 continue
871 count += 1
872 count += 1
872 displayer.show(other[n])
873 displayer.show(other[n])
873 return _incoming(display, subreporecurse, ui, repo, source, opts)
874 return _incoming(display, subreporecurse, ui, repo, source, opts)
874
875
875 def _outgoing(ui, repo, dest, opts):
876 def _outgoing(ui, repo, dest, opts):
876 dest = ui.expandpath(dest or 'default-push', dest or 'default')
877 dest = ui.expandpath(dest or 'default-push', dest or 'default')
877 dest, branches = parseurl(dest, opts.get('branch'))
878 dest, branches = parseurl(dest, opts.get('branch'))
878 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
879 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
879 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
880 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
880 if revs:
881 if revs:
881 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
882 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
882
883
883 other = peer(repo, opts, dest)
884 other = peer(repo, opts, dest)
884 outgoing = discovery.findcommonoutgoing(repo, other, revs,
885 outgoing = discovery.findcommonoutgoing(repo, other, revs,
885 force=opts.get('force'))
886 force=opts.get('force'))
886 o = outgoing.missing
887 o = outgoing.missing
887 if not o:
888 if not o:
888 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
889 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
889 return o, other
890 return o, other
890
891
891 def outgoing(ui, repo, dest, opts):
892 def outgoing(ui, repo, dest, opts):
892 def recurse():
893 def recurse():
893 ret = 1
894 ret = 1
894 if opts.get('subrepos'):
895 if opts.get('subrepos'):
895 ctx = repo[None]
896 ctx = repo[None]
896 for subpath in sorted(ctx.substate):
897 for subpath in sorted(ctx.substate):
897 sub = ctx.sub(subpath)
898 sub = ctx.sub(subpath)
898 ret = min(ret, sub.outgoing(ui, dest, opts))
899 ret = min(ret, sub.outgoing(ui, dest, opts))
899 return ret
900 return ret
900
901
901 limit = cmdutil.loglimit(opts)
902 limit = cmdutil.loglimit(opts)
902 o, other = _outgoing(ui, repo, dest, opts)
903 o, other = _outgoing(ui, repo, dest, opts)
903 if not o:
904 if not o:
904 cmdutil.outgoinghooks(ui, repo, other, opts, o)
905 cmdutil.outgoinghooks(ui, repo, other, opts, o)
905 return recurse()
906 return recurse()
906
907
907 if opts.get('newest_first'):
908 if opts.get('newest_first'):
908 o.reverse()
909 o.reverse()
909 ui.pager('outgoing')
910 ui.pager('outgoing')
910 displayer = cmdutil.show_changeset(ui, repo, opts)
911 displayer = cmdutil.show_changeset(ui, repo, opts)
911 count = 0
912 count = 0
912 for n in o:
913 for n in o:
913 if limit is not None and count >= limit:
914 if limit is not None and count >= limit:
914 break
915 break
915 parents = [p for p in repo.changelog.parents(n) if p != nullid]
916 parents = [p for p in repo.changelog.parents(n) if p != nullid]
916 if opts.get('no_merges') and len(parents) == 2:
917 if opts.get('no_merges') and len(parents) == 2:
917 continue
918 continue
918 count += 1
919 count += 1
919 displayer.show(repo[n])
920 displayer.show(repo[n])
920 displayer.close()
921 displayer.close()
921 cmdutil.outgoinghooks(ui, repo, other, opts, o)
922 cmdutil.outgoinghooks(ui, repo, other, opts, o)
922 recurse()
923 recurse()
923 return 0 # exit code is zero since we found outgoing changes
924 return 0 # exit code is zero since we found outgoing changes
924
925
925 def verify(repo):
926 def verify(repo):
926 """verify the consistency of a repository"""
927 """verify the consistency of a repository"""
927 ret = verifymod.verify(repo)
928 ret = verifymod.verify(repo)
928
929
929 # Broken subrepo references in hidden csets don't seem worth worrying about,
930 # Broken subrepo references in hidden csets don't seem worth worrying about,
930 # since they can't be pushed/pulled, and --hidden can be used if they are a
931 # since they can't be pushed/pulled, and --hidden can be used if they are a
931 # concern.
932 # concern.
932
933
933 # pathto() is needed for -R case
934 # pathto() is needed for -R case
934 revs = repo.revs("filelog(%s)",
935 revs = repo.revs("filelog(%s)",
935 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
936 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
936
937
937 if revs:
938 if revs:
938 repo.ui.status(_('checking subrepo links\n'))
939 repo.ui.status(_('checking subrepo links\n'))
939 for rev in revs:
940 for rev in revs:
940 ctx = repo[rev]
941 ctx = repo[rev]
941 try:
942 try:
942 for subpath in ctx.substate:
943 for subpath in ctx.substate:
943 try:
944 try:
944 ret = (ctx.sub(subpath, allowcreate=False).verify()
945 ret = (ctx.sub(subpath, allowcreate=False).verify()
945 or ret)
946 or ret)
946 except error.RepoError as e:
947 except error.RepoError as e:
947 repo.ui.warn(('%s: %s\n') % (rev, e))
948 repo.ui.warn(('%s: %s\n') % (rev, e))
948 except Exception:
949 except Exception:
949 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
950 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
950 node.short(ctx.node()))
951 node.short(ctx.node()))
951
952
952 return ret
953 return ret
953
954
954 def remoteui(src, opts):
955 def remoteui(src, opts):
955 'build a remote ui from ui or repo and opts'
956 'build a remote ui from ui or repo and opts'
956 if util.safehasattr(src, 'baseui'): # looks like a repository
957 if util.safehasattr(src, 'baseui'): # looks like a repository
957 dst = src.baseui.copy() # drop repo-specific config
958 dst = src.baseui.copy() # drop repo-specific config
958 src = src.ui # copy target options from repo
959 src = src.ui # copy target options from repo
959 else: # assume it's a global ui object
960 else: # assume it's a global ui object
960 dst = src.copy() # keep all global options
961 dst = src.copy() # keep all global options
961
962
962 # copy ssh-specific options
963 # copy ssh-specific options
963 for o in 'ssh', 'remotecmd':
964 for o in 'ssh', 'remotecmd':
964 v = opts.get(o) or src.config('ui', o)
965 v = opts.get(o) or src.config('ui', o)
965 if v:
966 if v:
966 dst.setconfig("ui", o, v, 'copied')
967 dst.setconfig("ui", o, v, 'copied')
967
968
968 # copy bundle-specific options
969 # copy bundle-specific options
969 r = src.config('bundle', 'mainreporoot')
970 r = src.config('bundle', 'mainreporoot')
970 if r:
971 if r:
971 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
972 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
972
973
973 # copy selected local settings to the remote ui
974 # copy selected local settings to the remote ui
974 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
975 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
975 for key, val in src.configitems(sect):
976 for key, val in src.configitems(sect):
976 dst.setconfig(sect, key, val, 'copied')
977 dst.setconfig(sect, key, val, 'copied')
977 v = src.config('web', 'cacerts')
978 v = src.config('web', 'cacerts')
978 if v:
979 if v:
979 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
980 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
980
981
981 return dst
982 return dst
982
983
983 # Files of interest
984 # Files of interest
984 # Used to check if the repository has changed looking at mtime and size of
985 # Used to check if the repository has changed looking at mtime and size of
985 # these files.
986 # these files.
986 foi = [('spath', '00changelog.i'),
987 foi = [('spath', '00changelog.i'),
987 ('spath', 'phaseroots'), # ! phase can change content at the same size
988 ('spath', 'phaseroots'), # ! phase can change content at the same size
988 ('spath', 'obsstore'),
989 ('spath', 'obsstore'),
989 ('path', 'bookmarks'), # ! bookmark can change content at the same size
990 ('path', 'bookmarks'), # ! bookmark can change content at the same size
990 ]
991 ]
991
992
992 class cachedlocalrepo(object):
993 class cachedlocalrepo(object):
993 """Holds a localrepository that can be cached and reused."""
994 """Holds a localrepository that can be cached and reused."""
994
995
995 def __init__(self, repo):
996 def __init__(self, repo):
996 """Create a new cached repo from an existing repo.
997 """Create a new cached repo from an existing repo.
997
998
998 We assume the passed in repo was recently created. If the
999 We assume the passed in repo was recently created. If the
999 repo has changed between when it was created and when it was
1000 repo has changed between when it was created and when it was
1000 turned into a cache, it may not refresh properly.
1001 turned into a cache, it may not refresh properly.
1001 """
1002 """
1002 assert isinstance(repo, localrepo.localrepository)
1003 assert isinstance(repo, localrepo.localrepository)
1003 self._repo = repo
1004 self._repo = repo
1004 self._state, self.mtime = self._repostate()
1005 self._state, self.mtime = self._repostate()
1005 self._filtername = repo.filtername
1006 self._filtername = repo.filtername
1006
1007
1007 def fetch(self):
1008 def fetch(self):
1008 """Refresh (if necessary) and return a repository.
1009 """Refresh (if necessary) and return a repository.
1009
1010
1010 If the cached instance is out of date, it will be recreated
1011 If the cached instance is out of date, it will be recreated
1011 automatically and returned.
1012 automatically and returned.
1012
1013
1013 Returns a tuple of the repo and a boolean indicating whether a new
1014 Returns a tuple of the repo and a boolean indicating whether a new
1014 repo instance was created.
1015 repo instance was created.
1015 """
1016 """
1016 # We compare the mtimes and sizes of some well-known files to
1017 # We compare the mtimes and sizes of some well-known files to
1017 # determine if the repo changed. This is not precise, as mtimes
1018 # determine if the repo changed. This is not precise, as mtimes
1018 # are susceptible to clock skew and imprecise filesystems and
1019 # are susceptible to clock skew and imprecise filesystems and
1019 # file content can change while maintaining the same size.
1020 # file content can change while maintaining the same size.
1020
1021
1021 state, mtime = self._repostate()
1022 state, mtime = self._repostate()
1022 if state == self._state:
1023 if state == self._state:
1023 return self._repo, False
1024 return self._repo, False
1024
1025
1025 repo = repository(self._repo.baseui, self._repo.url())
1026 repo = repository(self._repo.baseui, self._repo.url())
1026 if self._filtername:
1027 if self._filtername:
1027 self._repo = repo.filtered(self._filtername)
1028 self._repo = repo.filtered(self._filtername)
1028 else:
1029 else:
1029 self._repo = repo.unfiltered()
1030 self._repo = repo.unfiltered()
1030 self._state = state
1031 self._state = state
1031 self.mtime = mtime
1032 self.mtime = mtime
1032
1033
1033 return self._repo, True
1034 return self._repo, True
1034
1035
1035 def _repostate(self):
1036 def _repostate(self):
1036 state = []
1037 state = []
1037 maxmtime = -1
1038 maxmtime = -1
1038 for attr, fname in foi:
1039 for attr, fname in foi:
1039 prefix = getattr(self._repo, attr)
1040 prefix = getattr(self._repo, attr)
1040 p = os.path.join(prefix, fname)
1041 p = os.path.join(prefix, fname)
1041 try:
1042 try:
1042 st = os.stat(p)
1043 st = os.stat(p)
1043 except OSError:
1044 except OSError:
1044 st = os.stat(prefix)
1045 st = os.stat(prefix)
1045 state.append((st.st_mtime, st.st_size))
1046 state.append((st.st_mtime, st.st_size))
1046 maxmtime = max(maxmtime, st.st_mtime)
1047 maxmtime = max(maxmtime, st.st_mtime)
1047
1048
1048 return tuple(state), maxmtime
1049 return tuple(state), maxmtime
1049
1050
1050 def copy(self):
1051 def copy(self):
1051 """Obtain a copy of this class instance.
1052 """Obtain a copy of this class instance.
1052
1053
1053 A new localrepository instance is obtained. The new instance should be
1054 A new localrepository instance is obtained. The new instance should be
1054 completely independent of the original.
1055 completely independent of the original.
1055 """
1056 """
1056 repo = repository(self._repo.baseui, self._repo.origroot)
1057 repo = repository(self._repo.baseui, self._repo.origroot)
1057 if self._filtername:
1058 if self._filtername:
1058 repo = repo.filtered(self._filtername)
1059 repo = repo.filtered(self._filtername)
1059 else:
1060 else:
1060 repo = repo.unfiltered()
1061 repo = repo.unfiltered()
1061 c = cachedlocalrepo(repo)
1062 c = cachedlocalrepo(repo)
1062 c._state = self._state
1063 c._state = self._state
1063 c.mtime = self.mtime
1064 c.mtime = self.mtime
1064 return c
1065 return c
@@ -1,2002 +1,2013 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import posixpath
14 import posixpath
15 import re
15 import re
16 import stat
16 import stat
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import tarfile
19 import tarfile
20 import xml.dom.minidom
20 import xml.dom.minidom
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 config,
26 config,
27 encoding,
27 encoding,
28 error,
28 error,
29 exchange,
29 exchange,
30 filemerge,
30 filemerge,
31 match as matchmod,
31 match as matchmod,
32 node,
32 node,
33 pathutil,
33 pathutil,
34 phases,
34 phases,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 vfs as vfsmod,
38 vfs as vfsmod,
39 )
39 )
40
40
41 hg = None
41 hg = None
42 propertycache = util.propertycache
42 propertycache = util.propertycache
43
43
44 nullstate = ('', '', 'empty')
44 nullstate = ('', '', 'empty')
45
45
46 def _expandedabspath(path):
46 def _expandedabspath(path):
47 '''
47 '''
48 get a path or url and if it is a path expand it and return an absolute path
48 get a path or url and if it is a path expand it and return an absolute path
49 '''
49 '''
50 expandedpath = util.urllocalpath(util.expandpath(path))
50 expandedpath = util.urllocalpath(util.expandpath(path))
51 u = util.url(expandedpath)
51 u = util.url(expandedpath)
52 if not u.scheme:
52 if not u.scheme:
53 path = util.normpath(os.path.abspath(u.path))
53 path = util.normpath(os.path.abspath(u.path))
54 return path
54 return path
55
55
56 def _getstorehashcachename(remotepath):
56 def _getstorehashcachename(remotepath):
57 '''get a unique filename for the store hash cache of a remote repository'''
57 '''get a unique filename for the store hash cache of a remote repository'''
58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
59
59
60 class SubrepoAbort(error.Abort):
60 class SubrepoAbort(error.Abort):
61 """Exception class used to avoid handling a subrepo error more than once"""
61 """Exception class used to avoid handling a subrepo error more than once"""
62 def __init__(self, *args, **kw):
62 def __init__(self, *args, **kw):
63 self.subrepo = kw.pop('subrepo', None)
63 self.subrepo = kw.pop('subrepo', None)
64 self.cause = kw.pop('cause', None)
64 self.cause = kw.pop('cause', None)
65 error.Abort.__init__(self, *args, **kw)
65 error.Abort.__init__(self, *args, **kw)
66
66
67 def annotatesubrepoerror(func):
67 def annotatesubrepoerror(func):
68 def decoratedmethod(self, *args, **kargs):
68 def decoratedmethod(self, *args, **kargs):
69 try:
69 try:
70 res = func(self, *args, **kargs)
70 res = func(self, *args, **kargs)
71 except SubrepoAbort as ex:
71 except SubrepoAbort as ex:
72 # This exception has already been handled
72 # This exception has already been handled
73 raise ex
73 raise ex
74 except error.Abort as ex:
74 except error.Abort as ex:
75 subrepo = subrelpath(self)
75 subrepo = subrelpath(self)
76 errormsg = str(ex) + ' ' + _('(in subrepository "%s")') % subrepo
76 errormsg = str(ex) + ' ' + _('(in subrepository "%s")') % subrepo
77 # avoid handling this exception by raising a SubrepoAbort exception
77 # avoid handling this exception by raising a SubrepoAbort exception
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
79 cause=sys.exc_info())
79 cause=sys.exc_info())
80 return res
80 return res
81 return decoratedmethod
81 return decoratedmethod
82
82
83 def state(ctx, ui):
83 def state(ctx, ui):
84 """return a state dict, mapping subrepo paths configured in .hgsub
84 """return a state dict, mapping subrepo paths configured in .hgsub
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
86 (key in types dict))
86 (key in types dict))
87 """
87 """
88 p = config.config()
88 p = config.config()
89 repo = ctx.repo()
89 repo = ctx.repo()
90 def read(f, sections=None, remap=None):
90 def read(f, sections=None, remap=None):
91 if f in ctx:
91 if f in ctx:
92 try:
92 try:
93 data = ctx[f].data()
93 data = ctx[f].data()
94 except IOError as err:
94 except IOError as err:
95 if err.errno != errno.ENOENT:
95 if err.errno != errno.ENOENT:
96 raise
96 raise
97 # handle missing subrepo spec files as removed
97 # handle missing subrepo spec files as removed
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
99 repo.pathto(f))
99 repo.pathto(f))
100 return
100 return
101 p.parse(f, data, sections, remap, read)
101 p.parse(f, data, sections, remap, read)
102 else:
102 else:
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
104 repo.pathto(f))
104 repo.pathto(f))
105 if '.hgsub' in ctx:
105 if '.hgsub' in ctx:
106 read('.hgsub')
106 read('.hgsub')
107
107
108 for path, src in ui.configitems('subpaths'):
108 for path, src in ui.configitems('subpaths'):
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
110
110
111 rev = {}
111 rev = {}
112 if '.hgsubstate' in ctx:
112 if '.hgsubstate' in ctx:
113 try:
113 try:
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
115 l = l.lstrip()
115 l = l.lstrip()
116 if not l:
116 if not l:
117 continue
117 continue
118 try:
118 try:
119 revision, path = l.split(" ", 1)
119 revision, path = l.split(" ", 1)
120 except ValueError:
120 except ValueError:
121 raise error.Abort(_("invalid subrepository revision "
121 raise error.Abort(_("invalid subrepository revision "
122 "specifier in \'%s\' line %d")
122 "specifier in \'%s\' line %d")
123 % (repo.pathto('.hgsubstate'), (i + 1)))
123 % (repo.pathto('.hgsubstate'), (i + 1)))
124 rev[path] = revision
124 rev[path] = revision
125 except IOError as err:
125 except IOError as err:
126 if err.errno != errno.ENOENT:
126 if err.errno != errno.ENOENT:
127 raise
127 raise
128
128
129 def remap(src):
129 def remap(src):
130 for pattern, repl in p.items('subpaths'):
130 for pattern, repl in p.items('subpaths'):
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
132 # does a string decode.
132 # does a string decode.
133 repl = util.escapestr(repl)
133 repl = util.escapestr(repl)
134 # However, we still want to allow back references to go
134 # However, we still want to allow back references to go
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
136 # extra escapes are needed because re.sub string decodes.
136 # extra escapes are needed because re.sub string decodes.
137 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
137 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
138 try:
138 try:
139 src = re.sub(pattern, repl, src, 1)
139 src = re.sub(pattern, repl, src, 1)
140 except re.error as e:
140 except re.error as e:
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
142 % (p.source('subpaths', pattern), e))
142 % (p.source('subpaths', pattern), e))
143 return src
143 return src
144
144
145 state = {}
145 state = {}
146 for path, src in p[''].items():
146 for path, src in p[''].items():
147 kind = 'hg'
147 kind = 'hg'
148 if src.startswith('['):
148 if src.startswith('['):
149 if ']' not in src:
149 if ']' not in src:
150 raise error.Abort(_('missing ] in subrepository source'))
150 raise error.Abort(_('missing ] in subrepository source'))
151 kind, src = src.split(']', 1)
151 kind, src = src.split(']', 1)
152 kind = kind[1:]
152 kind = kind[1:]
153 src = src.lstrip() # strip any extra whitespace after ']'
153 src = src.lstrip() # strip any extra whitespace after ']'
154
154
155 if not util.url(src).isabs():
155 if not util.url(src).isabs():
156 parent = _abssource(repo, abort=False)
156 parent = _abssource(repo, abort=False)
157 if parent:
157 if parent:
158 parent = util.url(parent)
158 parent = util.url(parent)
159 parent.path = posixpath.join(parent.path or '', src)
159 parent.path = posixpath.join(parent.path or '', src)
160 parent.path = posixpath.normpath(parent.path)
160 parent.path = posixpath.normpath(parent.path)
161 joined = str(parent)
161 joined = str(parent)
162 # Remap the full joined path and use it if it changes,
162 # Remap the full joined path and use it if it changes,
163 # else remap the original source.
163 # else remap the original source.
164 remapped = remap(joined)
164 remapped = remap(joined)
165 if remapped == joined:
165 if remapped == joined:
166 src = remap(src)
166 src = remap(src)
167 else:
167 else:
168 src = remapped
168 src = remapped
169
169
170 src = remap(src)
170 src = remap(src)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
172
172
173 return state
173 return state
174
174
175 def writestate(repo, state):
175 def writestate(repo, state):
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
178 if state[s][1] != nullstate[1]]
178 if state[s][1] != nullstate[1]]
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
180
180
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
182 """delegated from merge.applyupdates: merging of .hgsubstate file
182 """delegated from merge.applyupdates: merging of .hgsubstate file
183 in working context, merging context and ancestor context"""
183 in working context, merging context and ancestor context"""
184 if mctx == actx: # backwards?
184 if mctx == actx: # backwards?
185 actx = wctx.p1()
185 actx = wctx.p1()
186 s1 = wctx.substate
186 s1 = wctx.substate
187 s2 = mctx.substate
187 s2 = mctx.substate
188 sa = actx.substate
188 sa = actx.substate
189 sm = {}
189 sm = {}
190
190
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
192
192
193 def debug(s, msg, r=""):
193 def debug(s, msg, r=""):
194 if r:
194 if r:
195 r = "%s:%s:%s" % r
195 r = "%s:%s:%s" % r
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
197
197
198 promptssrc = filemerge.partextras(labels)
198 promptssrc = filemerge.partextras(labels)
199 for s, l in sorted(s1.iteritems()):
199 for s, l in sorted(s1.iteritems()):
200 prompts = None
200 prompts = None
201 a = sa.get(s, nullstate)
201 a = sa.get(s, nullstate)
202 ld = l # local state with possible dirty flag for compares
202 ld = l # local state with possible dirty flag for compares
203 if wctx.sub(s).dirty():
203 if wctx.sub(s).dirty():
204 ld = (l[0], l[1] + "+")
204 ld = (l[0], l[1] + "+")
205 if wctx == actx: # overwrite
205 if wctx == actx: # overwrite
206 a = ld
206 a = ld
207
207
208 prompts = promptssrc.copy()
208 prompts = promptssrc.copy()
209 prompts['s'] = s
209 prompts['s'] = s
210 if s in s2:
210 if s in s2:
211 r = s2[s]
211 r = s2[s]
212 if ld == r or r == a: # no change or local is newer
212 if ld == r or r == a: # no change or local is newer
213 sm[s] = l
213 sm[s] = l
214 continue
214 continue
215 elif ld == a: # other side changed
215 elif ld == a: # other side changed
216 debug(s, "other changed, get", r)
216 debug(s, "other changed, get", r)
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 elif ld[0] != r[0]: # sources differ
219 elif ld[0] != r[0]: # sources differ
220 prompts['lo'] = l[0]
220 prompts['lo'] = l[0]
221 prompts['ro'] = r[0]
221 prompts['ro'] = r[0]
222 if repo.ui.promptchoice(
222 if repo.ui.promptchoice(
223 _(' subrepository sources for %(s)s differ\n'
223 _(' subrepository sources for %(s)s differ\n'
224 'use (l)ocal%(l)s source (%(lo)s)'
224 'use (l)ocal%(l)s source (%(lo)s)'
225 ' or (r)emote%(o)s source (%(ro)s)?'
225 ' or (r)emote%(o)s source (%(ro)s)?'
226 '$$ &Local $$ &Remote') % prompts, 0):
226 '$$ &Local $$ &Remote') % prompts, 0):
227 debug(s, "prompt changed, get", r)
227 debug(s, "prompt changed, get", r)
228 wctx.sub(s).get(r, overwrite)
228 wctx.sub(s).get(r, overwrite)
229 sm[s] = r
229 sm[s] = r
230 elif ld[1] == a[1]: # local side is unchanged
230 elif ld[1] == a[1]: # local side is unchanged
231 debug(s, "other side changed, get", r)
231 debug(s, "other side changed, get", r)
232 wctx.sub(s).get(r, overwrite)
232 wctx.sub(s).get(r, overwrite)
233 sm[s] = r
233 sm[s] = r
234 else:
234 else:
235 debug(s, "both sides changed")
235 debug(s, "both sides changed")
236 srepo = wctx.sub(s)
236 srepo = wctx.sub(s)
237 prompts['sl'] = srepo.shortid(l[1])
237 prompts['sl'] = srepo.shortid(l[1])
238 prompts['sr'] = srepo.shortid(r[1])
238 prompts['sr'] = srepo.shortid(r[1])
239 option = repo.ui.promptchoice(
239 option = repo.ui.promptchoice(
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
241 'remote revision: %(sr)s)\n'
241 'remote revision: %(sr)s)\n'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
243 '$$ &Merge $$ &Local $$ &Remote')
243 '$$ &Merge $$ &Local $$ &Remote')
244 % prompts, 0)
244 % prompts, 0)
245 if option == 0:
245 if option == 0:
246 wctx.sub(s).merge(r)
246 wctx.sub(s).merge(r)
247 sm[s] = l
247 sm[s] = l
248 debug(s, "merge with", r)
248 debug(s, "merge with", r)
249 elif option == 1:
249 elif option == 1:
250 sm[s] = l
250 sm[s] = l
251 debug(s, "keep local subrepo revision", l)
251 debug(s, "keep local subrepo revision", l)
252 else:
252 else:
253 wctx.sub(s).get(r, overwrite)
253 wctx.sub(s).get(r, overwrite)
254 sm[s] = r
254 sm[s] = r
255 debug(s, "get remote subrepo revision", r)
255 debug(s, "get remote subrepo revision", r)
256 elif ld == a: # remote removed, local unchanged
256 elif ld == a: # remote removed, local unchanged
257 debug(s, "remote removed, remove")
257 debug(s, "remote removed, remove")
258 wctx.sub(s).remove()
258 wctx.sub(s).remove()
259 elif a == nullstate: # not present in remote or ancestor
259 elif a == nullstate: # not present in remote or ancestor
260 debug(s, "local added, keep")
260 debug(s, "local added, keep")
261 sm[s] = l
261 sm[s] = l
262 continue
262 continue
263 else:
263 else:
264 if repo.ui.promptchoice(
264 if repo.ui.promptchoice(
265 _(' local%(l)s changed subrepository %(s)s'
265 _(' local%(l)s changed subrepository %(s)s'
266 ' which remote%(o)s removed\n'
266 ' which remote%(o)s removed\n'
267 'use (c)hanged version or (d)elete?'
267 'use (c)hanged version or (d)elete?'
268 '$$ &Changed $$ &Delete') % prompts, 0):
268 '$$ &Changed $$ &Delete') % prompts, 0):
269 debug(s, "prompt remove")
269 debug(s, "prompt remove")
270 wctx.sub(s).remove()
270 wctx.sub(s).remove()
271
271
272 for s, r in sorted(s2.items()):
272 for s, r in sorted(s2.items()):
273 prompts = None
273 prompts = None
274 if s in s1:
274 if s in s1:
275 continue
275 continue
276 elif s not in sa:
276 elif s not in sa:
277 debug(s, "remote added, get", r)
277 debug(s, "remote added, get", r)
278 mctx.sub(s).get(r)
278 mctx.sub(s).get(r)
279 sm[s] = r
279 sm[s] = r
280 elif r != sa[s]:
280 elif r != sa[s]:
281 prompts = promptssrc.copy()
281 prompts = promptssrc.copy()
282 prompts['s'] = s
282 prompts['s'] = s
283 if repo.ui.promptchoice(
283 if repo.ui.promptchoice(
284 _(' remote%(o)s changed subrepository %(s)s'
284 _(' remote%(o)s changed subrepository %(s)s'
285 ' which local%(l)s removed\n'
285 ' which local%(l)s removed\n'
286 'use (c)hanged version or (d)elete?'
286 'use (c)hanged version or (d)elete?'
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
288 debug(s, "prompt recreate", r)
288 debug(s, "prompt recreate", r)
289 mctx.sub(s).get(r)
289 mctx.sub(s).get(r)
290 sm[s] = r
290 sm[s] = r
291
291
292 # record merged .hgsubstate
292 # record merged .hgsubstate
293 writestate(repo, sm)
293 writestate(repo, sm)
294 return sm
294 return sm
295
295
296 def _updateprompt(ui, sub, dirty, local, remote):
296 def _updateprompt(ui, sub, dirty, local, remote):
297 if dirty:
297 if dirty:
298 msg = (_(' subrepository sources for %s differ\n'
298 msg = (_(' subrepository sources for %s differ\n'
299 'use (l)ocal source (%s) or (r)emote source (%s)?'
299 'use (l)ocal source (%s) or (r)emote source (%s)?'
300 '$$ &Local $$ &Remote')
300 '$$ &Local $$ &Remote')
301 % (subrelpath(sub), local, remote))
301 % (subrelpath(sub), local, remote))
302 else:
302 else:
303 msg = (_(' subrepository sources for %s differ (in checked out '
303 msg = (_(' subrepository sources for %s differ (in checked out '
304 'version)\n'
304 'version)\n'
305 'use (l)ocal source (%s) or (r)emote source (%s)?'
305 'use (l)ocal source (%s) or (r)emote source (%s)?'
306 '$$ &Local $$ &Remote')
306 '$$ &Local $$ &Remote')
307 % (subrelpath(sub), local, remote))
307 % (subrelpath(sub), local, remote))
308 return ui.promptchoice(msg, 0)
308 return ui.promptchoice(msg, 0)
309
309
310 def reporelpath(repo):
310 def reporelpath(repo):
311 """return path to this (sub)repo as seen from outermost repo"""
311 """return path to this (sub)repo as seen from outermost repo"""
312 parent = repo
312 parent = repo
313 while util.safehasattr(parent, '_subparent'):
313 while util.safehasattr(parent, '_subparent'):
314 parent = parent._subparent
314 parent = parent._subparent
315 return repo.root[len(pathutil.normasprefix(parent.root)):]
315 return repo.root[len(pathutil.normasprefix(parent.root)):]
316
316
317 def subrelpath(sub):
317 def subrelpath(sub):
318 """return path to this subrepo as seen from outermost repo"""
318 """return path to this subrepo as seen from outermost repo"""
319 return sub._relpath
319 return sub._relpath
320
320
321 def _abssource(repo, push=False, abort=True):
321 def _abssource(repo, push=False, abort=True):
322 """return pull/push path of repo - either based on parent repo .hgsub info
322 """return pull/push path of repo - either based on parent repo .hgsub info
323 or on the top repo config. Abort or return None if no source found."""
323 or on the top repo config. Abort or return None if no source found."""
324 if util.safehasattr(repo, '_subparent'):
324 if util.safehasattr(repo, '_subparent'):
325 source = util.url(repo._subsource)
325 source = util.url(repo._subsource)
326 if source.isabs():
326 if source.isabs():
327 return str(source)
327 return str(source)
328 source.path = posixpath.normpath(source.path)
328 source.path = posixpath.normpath(source.path)
329 parent = _abssource(repo._subparent, push, abort=False)
329 parent = _abssource(repo._subparent, push, abort=False)
330 if parent:
330 if parent:
331 parent = util.url(util.pconvert(parent))
331 parent = util.url(util.pconvert(parent))
332 parent.path = posixpath.join(parent.path or '', source.path)
332 parent.path = posixpath.join(parent.path or '', source.path)
333 parent.path = posixpath.normpath(parent.path)
333 parent.path = posixpath.normpath(parent.path)
334 return str(parent)
334 return str(parent)
335 else: # recursion reached top repo
335 else: # recursion reached top repo
336 if util.safehasattr(repo, '_subtoppath'):
336 if util.safehasattr(repo, '_subtoppath'):
337 return repo._subtoppath
337 return repo._subtoppath
338 if push and repo.ui.config('paths', 'default-push'):
338 if push and repo.ui.config('paths', 'default-push'):
339 return repo.ui.config('paths', 'default-push')
339 return repo.ui.config('paths', 'default-push')
340 if repo.ui.config('paths', 'default'):
340 if repo.ui.config('paths', 'default'):
341 return repo.ui.config('paths', 'default')
341 return repo.ui.config('paths', 'default')
342 if repo.shared():
342 if repo.shared():
343 # chop off the .hg component to get the default path form
343 # chop off the .hg component to get the default path form
344 return os.path.dirname(repo.sharedpath)
344 return os.path.dirname(repo.sharedpath)
345 if abort:
345 if abort:
346 raise error.Abort(_("default path for subrepository not found"))
346 raise error.Abort(_("default path for subrepository not found"))
347
347
348 def _sanitize(ui, vfs, ignore):
348 def _sanitize(ui, vfs, ignore):
349 for dirname, dirs, names in vfs.walk():
349 for dirname, dirs, names in vfs.walk():
350 for i, d in enumerate(dirs):
350 for i, d in enumerate(dirs):
351 if d.lower() == ignore:
351 if d.lower() == ignore:
352 del dirs[i]
352 del dirs[i]
353 break
353 break
354 if vfs.basename(dirname).lower() != '.hg':
354 if vfs.basename(dirname).lower() != '.hg':
355 continue
355 continue
356 for f in names:
356 for f in names:
357 if f.lower() == 'hgrc':
357 if f.lower() == 'hgrc':
358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
359 "in '%s'\n") % vfs.join(dirname))
359 "in '%s'\n") % vfs.join(dirname))
360 vfs.unlink(vfs.reljoin(dirname, f))
360 vfs.unlink(vfs.reljoin(dirname, f))
361
361
362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
363 """return instance of the right subrepo class for subrepo in path"""
363 """return instance of the right subrepo class for subrepo in path"""
364 # subrepo inherently violates our import layering rules
364 # subrepo inherently violates our import layering rules
365 # because it wants to make repo objects from deep inside the stack
365 # because it wants to make repo objects from deep inside the stack
366 # so we manually delay the circular imports to not break
366 # so we manually delay the circular imports to not break
367 # scripts that don't use our demand-loading
367 # scripts that don't use our demand-loading
368 global hg
368 global hg
369 from . import hg as h
369 from . import hg as h
370 hg = h
370 hg = h
371
371
372 pathutil.pathauditor(ctx.repo().root)(path)
372 pathutil.pathauditor(ctx.repo().root)(path)
373 state = ctx.substate[path]
373 state = ctx.substate[path]
374 if state[2] not in types:
374 if state[2] not in types:
375 raise error.Abort(_('unknown subrepo type %s') % state[2])
375 raise error.Abort(_('unknown subrepo type %s') % state[2])
376 if allowwdir:
376 if allowwdir:
377 state = (state[0], ctx.subrev(path), state[2])
377 state = (state[0], ctx.subrev(path), state[2])
378 return types[state[2]](ctx, path, state[:2], allowcreate)
378 return types[state[2]](ctx, path, state[:2], allowcreate)
379
379
380 def nullsubrepo(ctx, path, pctx):
380 def nullsubrepo(ctx, path, pctx):
381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
382 # subrepo inherently violates our import layering rules
382 # subrepo inherently violates our import layering rules
383 # because it wants to make repo objects from deep inside the stack
383 # because it wants to make repo objects from deep inside the stack
384 # so we manually delay the circular imports to not break
384 # so we manually delay the circular imports to not break
385 # scripts that don't use our demand-loading
385 # scripts that don't use our demand-loading
386 global hg
386 global hg
387 from . import hg as h
387 from . import hg as h
388 hg = h
388 hg = h
389
389
390 pathutil.pathauditor(ctx.repo().root)(path)
390 pathutil.pathauditor(ctx.repo().root)(path)
391 state = ctx.substate[path]
391 state = ctx.substate[path]
392 if state[2] not in types:
392 if state[2] not in types:
393 raise error.Abort(_('unknown subrepo type %s') % state[2])
393 raise error.Abort(_('unknown subrepo type %s') % state[2])
394 subrev = ''
394 subrev = ''
395 if state[2] == 'hg':
395 if state[2] == 'hg':
396 subrev = "0" * 40
396 subrev = "0" * 40
397 return types[state[2]](pctx, path, (state[0], subrev), True)
397 return types[state[2]](pctx, path, (state[0], subrev), True)
398
398
399 def newcommitphase(ui, ctx):
399 def newcommitphase(ui, ctx):
400 commitphase = phases.newcommitphase(ui)
400 commitphase = phases.newcommitphase(ui)
401 substate = getattr(ctx, "substate", None)
401 substate = getattr(ctx, "substate", None)
402 if not substate:
402 if not substate:
403 return commitphase
403 return commitphase
404 check = ui.config('phases', 'checksubrepos')
404 check = ui.config('phases', 'checksubrepos')
405 if check not in ('ignore', 'follow', 'abort'):
405 if check not in ('ignore', 'follow', 'abort'):
406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
407 % (check))
407 % (check))
408 if check == 'ignore':
408 if check == 'ignore':
409 return commitphase
409 return commitphase
410 maxphase = phases.public
410 maxphase = phases.public
411 maxsub = None
411 maxsub = None
412 for s in sorted(substate):
412 for s in sorted(substate):
413 sub = ctx.sub(s)
413 sub = ctx.sub(s)
414 subphase = sub.phase(substate[s][1])
414 subphase = sub.phase(substate[s][1])
415 if maxphase < subphase:
415 if maxphase < subphase:
416 maxphase = subphase
416 maxphase = subphase
417 maxsub = s
417 maxsub = s
418 if commitphase < maxphase:
418 if commitphase < maxphase:
419 if check == 'abort':
419 if check == 'abort':
420 raise error.Abort(_("can't commit in %s phase"
420 raise error.Abort(_("can't commit in %s phase"
421 " conflicting %s from subrepository %s") %
421 " conflicting %s from subrepository %s") %
422 (phases.phasenames[commitphase],
422 (phases.phasenames[commitphase],
423 phases.phasenames[maxphase], maxsub))
423 phases.phasenames[maxphase], maxsub))
424 ui.warn(_("warning: changes are committed in"
424 ui.warn(_("warning: changes are committed in"
425 " %s phase from subrepository %s\n") %
425 " %s phase from subrepository %s\n") %
426 (phases.phasenames[maxphase], maxsub))
426 (phases.phasenames[maxphase], maxsub))
427 return maxphase
427 return maxphase
428 return commitphase
428 return commitphase
429
429
430 # subrepo classes need to implement the following abstract class:
430 # subrepo classes need to implement the following abstract class:
431
431
432 class abstractsubrepo(object):
432 class abstractsubrepo(object):
433
433
434 def __init__(self, ctx, path):
434 def __init__(self, ctx, path):
435 """Initialize abstractsubrepo part
435 """Initialize abstractsubrepo part
436
436
437 ``ctx`` is the context referring this subrepository in the
437 ``ctx`` is the context referring this subrepository in the
438 parent repository.
438 parent repository.
439
439
440 ``path`` is the path to this subrepository as seen from
440 ``path`` is the path to this subrepository as seen from
441 innermost repository.
441 innermost repository.
442 """
442 """
443 self.ui = ctx.repo().ui
443 self.ui = ctx.repo().ui
444 self._ctx = ctx
444 self._ctx = ctx
445 self._path = path
445 self._path = path
446
446
447 def addwebdirpath(self, serverpath, webconf):
447 def addwebdirpath(self, serverpath, webconf):
448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
449
449
450 ``serverpath`` is the path component of the URL for this repo.
450 ``serverpath`` is the path component of the URL for this repo.
451
451
452 ``webconf`` is the dictionary of hgwebdir entries.
452 ``webconf`` is the dictionary of hgwebdir entries.
453 """
453 """
454 pass
454 pass
455
455
456 def storeclean(self, path):
456 def storeclean(self, path):
457 """
457 """
458 returns true if the repository has not changed since it was last
458 returns true if the repository has not changed since it was last
459 cloned from or pushed to a given repository.
459 cloned from or pushed to a given repository.
460 """
460 """
461 return False
461 return False
462
462
463 def dirty(self, ignoreupdate=False, missing=False):
463 def dirty(self, ignoreupdate=False, missing=False):
464 """returns true if the dirstate of the subrepo is dirty or does not
464 """returns true if the dirstate of the subrepo is dirty or does not
465 match current stored state. If ignoreupdate is true, only check
465 match current stored state. If ignoreupdate is true, only check
466 whether the subrepo has uncommitted changes in its dirstate. If missing
466 whether the subrepo has uncommitted changes in its dirstate. If missing
467 is true, check for deleted files.
467 is true, check for deleted files.
468 """
468 """
469 raise NotImplementedError
469 raise NotImplementedError
470
470
471 def dirtyreason(self, ignoreupdate=False, missing=False):
471 def dirtyreason(self, ignoreupdate=False, missing=False):
472 """return reason string if it is ``dirty()``
472 """return reason string if it is ``dirty()``
473
473
474 Returned string should have enough information for the message
474 Returned string should have enough information for the message
475 of exception.
475 of exception.
476
476
477 This returns None, otherwise.
477 This returns None, otherwise.
478 """
478 """
479 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
479 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
480 return _('uncommitted changes in subrepository "%s"'
480 return _('uncommitted changes in subrepository "%s"'
481 ) % subrelpath(self)
481 ) % subrelpath(self)
482
482
483 def bailifchanged(self, ignoreupdate=False, hint=None):
483 def bailifchanged(self, ignoreupdate=False, hint=None):
484 """raise Abort if subrepository is ``dirty()``
484 """raise Abort if subrepository is ``dirty()``
485 """
485 """
486 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
486 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
487 missing=True)
487 missing=True)
488 if dirtyreason:
488 if dirtyreason:
489 raise error.Abort(dirtyreason, hint=hint)
489 raise error.Abort(dirtyreason, hint=hint)
490
490
491 def basestate(self):
491 def basestate(self):
492 """current working directory base state, disregarding .hgsubstate
492 """current working directory base state, disregarding .hgsubstate
493 state and working directory modifications"""
493 state and working directory modifications"""
494 raise NotImplementedError
494 raise NotImplementedError
495
495
496 def checknested(self, path):
496 def checknested(self, path):
497 """check if path is a subrepository within this repository"""
497 """check if path is a subrepository within this repository"""
498 return False
498 return False
499
499
500 def commit(self, text, user, date):
500 def commit(self, text, user, date):
501 """commit the current changes to the subrepo with the given
501 """commit the current changes to the subrepo with the given
502 log message. Use given user and date if possible. Return the
502 log message. Use given user and date if possible. Return the
503 new state of the subrepo.
503 new state of the subrepo.
504 """
504 """
505 raise NotImplementedError
505 raise NotImplementedError
506
506
507 def phase(self, state):
507 def phase(self, state):
508 """returns phase of specified state in the subrepository.
508 """returns phase of specified state in the subrepository.
509 """
509 """
510 return phases.public
510 return phases.public
511
511
512 def remove(self):
512 def remove(self):
513 """remove the subrepo
513 """remove the subrepo
514
514
515 (should verify the dirstate is not dirty first)
515 (should verify the dirstate is not dirty first)
516 """
516 """
517 raise NotImplementedError
517 raise NotImplementedError
518
518
519 def get(self, state, overwrite=False):
519 def get(self, state, overwrite=False):
520 """run whatever commands are needed to put the subrepo into
520 """run whatever commands are needed to put the subrepo into
521 this state
521 this state
522 """
522 """
523 raise NotImplementedError
523 raise NotImplementedError
524
524
525 def merge(self, state):
525 def merge(self, state):
526 """merge currently-saved state with the new state."""
526 """merge currently-saved state with the new state."""
527 raise NotImplementedError
527 raise NotImplementedError
528
528
529 def push(self, opts):
529 def push(self, opts):
530 """perform whatever action is analogous to 'hg push'
530 """perform whatever action is analogous to 'hg push'
531
531
532 This may be a no-op on some systems.
532 This may be a no-op on some systems.
533 """
533 """
534 raise NotImplementedError
534 raise NotImplementedError
535
535
536 def add(self, ui, match, prefix, explicitonly, **opts):
536 def add(self, ui, match, prefix, explicitonly, **opts):
537 return []
537 return []
538
538
539 def addremove(self, matcher, prefix, opts, dry_run, similarity):
539 def addremove(self, matcher, prefix, opts, dry_run, similarity):
540 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
540 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
541 return 1
541 return 1
542
542
543 def cat(self, match, fm, fntemplate, prefix, **opts):
543 def cat(self, match, fm, fntemplate, prefix, **opts):
544 return 1
544 return 1
545
545
546 def status(self, rev2, **opts):
546 def status(self, rev2, **opts):
547 return scmutil.status([], [], [], [], [], [], [])
547 return scmutil.status([], [], [], [], [], [], [])
548
548
549 def diff(self, ui, diffopts, node2, match, prefix, **opts):
549 def diff(self, ui, diffopts, node2, match, prefix, **opts):
550 pass
550 pass
551
551
552 def outgoing(self, ui, dest, opts):
552 def outgoing(self, ui, dest, opts):
553 return 1
553 return 1
554
554
555 def incoming(self, ui, source, opts):
555 def incoming(self, ui, source, opts):
556 return 1
556 return 1
557
557
558 def files(self):
558 def files(self):
559 """return filename iterator"""
559 """return filename iterator"""
560 raise NotImplementedError
560 raise NotImplementedError
561
561
562 def filedata(self, name, decode):
562 def filedata(self, name, decode):
563 """return file data, optionally passed through repo decoders"""
563 """return file data, optionally passed through repo decoders"""
564 raise NotImplementedError
564 raise NotImplementedError
565
565
566 def fileflags(self, name):
566 def fileflags(self, name):
567 """return file flags"""
567 """return file flags"""
568 return ''
568 return ''
569
569
570 def getfileset(self, expr):
570 def getfileset(self, expr):
571 """Resolve the fileset expression for this repo"""
571 """Resolve the fileset expression for this repo"""
572 return set()
572 return set()
573
573
574 def printfiles(self, ui, m, fm, fmt, subrepos):
574 def printfiles(self, ui, m, fm, fmt, subrepos):
575 """handle the files command for this subrepo"""
575 """handle the files command for this subrepo"""
576 return 1
576 return 1
577
577
578 def archive(self, archiver, prefix, match=None, decode=True):
578 def archive(self, archiver, prefix, match=None, decode=True):
579 if match is not None:
579 if match is not None:
580 files = [f for f in self.files() if match(f)]
580 files = [f for f in self.files() if match(f)]
581 else:
581 else:
582 files = self.files()
582 files = self.files()
583 total = len(files)
583 total = len(files)
584 relpath = subrelpath(self)
584 relpath = subrelpath(self)
585 self.ui.progress(_('archiving (%s)') % relpath, 0,
585 self.ui.progress(_('archiving (%s)') % relpath, 0,
586 unit=_('files'), total=total)
586 unit=_('files'), total=total)
587 for i, name in enumerate(files):
587 for i, name in enumerate(files):
588 flags = self.fileflags(name)
588 flags = self.fileflags(name)
589 mode = 'x' in flags and 0o755 or 0o644
589 mode = 'x' in flags and 0o755 or 0o644
590 symlink = 'l' in flags
590 symlink = 'l' in flags
591 archiver.addfile(prefix + self._path + '/' + name,
591 archiver.addfile(prefix + self._path + '/' + name,
592 mode, symlink, self.filedata(name, decode))
592 mode, symlink, self.filedata(name, decode))
593 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
593 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
594 unit=_('files'), total=total)
594 unit=_('files'), total=total)
595 self.ui.progress(_('archiving (%s)') % relpath, None)
595 self.ui.progress(_('archiving (%s)') % relpath, None)
596 return total
596 return total
597
597
598 def walk(self, match):
598 def walk(self, match):
599 '''
599 '''
600 walk recursively through the directory tree, finding all files
600 walk recursively through the directory tree, finding all files
601 matched by the match function
601 matched by the match function
602 '''
602 '''
603
603
604 def forget(self, match, prefix):
604 def forget(self, match, prefix):
605 return ([], [])
605 return ([], [])
606
606
607 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
607 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
608 """remove the matched files from the subrepository and the filesystem,
608 """remove the matched files from the subrepository and the filesystem,
609 possibly by force and/or after the file has been removed from the
609 possibly by force and/or after the file has been removed from the
610 filesystem. Return 0 on success, 1 on any warning.
610 filesystem. Return 0 on success, 1 on any warning.
611 """
611 """
612 warnings.append(_("warning: removefiles not implemented (%s)")
612 warnings.append(_("warning: removefiles not implemented (%s)")
613 % self._path)
613 % self._path)
614 return 1
614 return 1
615
615
616 def revert(self, substate, *pats, **opts):
616 def revert(self, substate, *pats, **opts):
617 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
617 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
618 % (substate[0], substate[2]))
618 % (substate[0], substate[2]))
619 return []
619 return []
620
620
621 def shortid(self, revid):
621 def shortid(self, revid):
622 return revid
622 return revid
623
623
624 def verify(self):
624 def verify(self):
625 '''verify the integrity of the repository. Return 0 on success or
625 '''verify the integrity of the repository. Return 0 on success or
626 warning, 1 on any error.
626 warning, 1 on any error.
627 '''
627 '''
628 return 0
628 return 0
629
629
630 @propertycache
630 @propertycache
631 def wvfs(self):
631 def wvfs(self):
632 """return vfs to access the working directory of this subrepository
632 """return vfs to access the working directory of this subrepository
633 """
633 """
634 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
634 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
635
635
636 @propertycache
636 @propertycache
637 def _relpath(self):
637 def _relpath(self):
638 """return path to this subrepository as seen from outermost repository
638 """return path to this subrepository as seen from outermost repository
639 """
639 """
640 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
640 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
641
641
642 class hgsubrepo(abstractsubrepo):
642 class hgsubrepo(abstractsubrepo):
643 def __init__(self, ctx, path, state, allowcreate):
643 def __init__(self, ctx, path, state, allowcreate):
644 super(hgsubrepo, self).__init__(ctx, path)
644 super(hgsubrepo, self).__init__(ctx, path)
645 self._state = state
645 self._state = state
646 r = ctx.repo()
646 r = ctx.repo()
647 root = r.wjoin(path)
647 root = r.wjoin(path)
648 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
648 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
649 self._repo = hg.repository(r.baseui, root, create=create)
649 self._repo = hg.repository(r.baseui, root, create=create)
650
650
651 # Propagate the parent's --hidden option
651 # Propagate the parent's --hidden option
652 if r is r.unfiltered():
652 if r is r.unfiltered():
653 self._repo = self._repo.unfiltered()
653 self._repo = self._repo.unfiltered()
654
654
655 self.ui = self._repo.ui
655 self.ui = self._repo.ui
656 for s, k in [('ui', 'commitsubrepos')]:
656 for s, k in [('ui', 'commitsubrepos')]:
657 v = r.ui.config(s, k)
657 v = r.ui.config(s, k)
658 if v:
658 if v:
659 self.ui.setconfig(s, k, v, 'subrepo')
659 self.ui.setconfig(s, k, v, 'subrepo')
660 # internal config: ui._usedassubrepo
660 # internal config: ui._usedassubrepo
661 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
661 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
662 self._initrepo(r, state[0], create)
662 self._initrepo(r, state[0], create)
663
663
664 @annotatesubrepoerror
664 @annotatesubrepoerror
665 def addwebdirpath(self, serverpath, webconf):
665 def addwebdirpath(self, serverpath, webconf):
666 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
666 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
667
667
668 def storeclean(self, path):
668 def storeclean(self, path):
669 with self._repo.lock():
669 with self._repo.lock():
670 return self._storeclean(path)
670 return self._storeclean(path)
671
671
672 def _storeclean(self, path):
672 def _storeclean(self, path):
673 clean = True
673 clean = True
674 itercache = self._calcstorehash(path)
674 itercache = self._calcstorehash(path)
675 for filehash in self._readstorehashcache(path):
675 for filehash in self._readstorehashcache(path):
676 if filehash != next(itercache, None):
676 if filehash != next(itercache, None):
677 clean = False
677 clean = False
678 break
678 break
679 if clean:
679 if clean:
680 # if not empty:
680 # if not empty:
681 # the cached and current pull states have a different size
681 # the cached and current pull states have a different size
682 clean = next(itercache, None) is None
682 clean = next(itercache, None) is None
683 return clean
683 return clean
684
684
685 def _calcstorehash(self, remotepath):
685 def _calcstorehash(self, remotepath):
686 '''calculate a unique "store hash"
686 '''calculate a unique "store hash"
687
687
688 This method is used to to detect when there are changes that may
688 This method is used to to detect when there are changes that may
689 require a push to a given remote path.'''
689 require a push to a given remote path.'''
690 # sort the files that will be hashed in increasing (likely) file size
690 # sort the files that will be hashed in increasing (likely) file size
691 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
691 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
692 yield '# %s\n' % _expandedabspath(remotepath)
692 yield '# %s\n' % _expandedabspath(remotepath)
693 vfs = self._repo.vfs
693 vfs = self._repo.vfs
694 for relname in filelist:
694 for relname in filelist:
695 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
695 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
696 yield '%s = %s\n' % (relname, filehash)
696 yield '%s = %s\n' % (relname, filehash)
697
697
698 @propertycache
698 @propertycache
699 def _cachestorehashvfs(self):
699 def _cachestorehashvfs(self):
700 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
700 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
701
701
702 def _readstorehashcache(self, remotepath):
702 def _readstorehashcache(self, remotepath):
703 '''read the store hash cache for a given remote repository'''
703 '''read the store hash cache for a given remote repository'''
704 cachefile = _getstorehashcachename(remotepath)
704 cachefile = _getstorehashcachename(remotepath)
705 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
705 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
706
706
707 def _cachestorehash(self, remotepath):
707 def _cachestorehash(self, remotepath):
708 '''cache the current store hash
708 '''cache the current store hash
709
709
710 Each remote repo requires its own store hash cache, because a subrepo
710 Each remote repo requires its own store hash cache, because a subrepo
711 store may be "clean" versus a given remote repo, but not versus another
711 store may be "clean" versus a given remote repo, but not versus another
712 '''
712 '''
713 cachefile = _getstorehashcachename(remotepath)
713 cachefile = _getstorehashcachename(remotepath)
714 with self._repo.lock():
714 with self._repo.lock():
715 storehash = list(self._calcstorehash(remotepath))
715 storehash = list(self._calcstorehash(remotepath))
716 vfs = self._cachestorehashvfs
716 vfs = self._cachestorehashvfs
717 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
717 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
718
718
719 def _getctx(self):
719 def _getctx(self):
720 '''fetch the context for this subrepo revision, possibly a workingctx
720 '''fetch the context for this subrepo revision, possibly a workingctx
721 '''
721 '''
722 if self._ctx.rev() is None:
722 if self._ctx.rev() is None:
723 return self._repo[None] # workingctx if parent is workingctx
723 return self._repo[None] # workingctx if parent is workingctx
724 else:
724 else:
725 rev = self._state[1]
725 rev = self._state[1]
726 return self._repo[rev]
726 return self._repo[rev]
727
727
728 @annotatesubrepoerror
728 @annotatesubrepoerror
729 def _initrepo(self, parentrepo, source, create):
729 def _initrepo(self, parentrepo, source, create):
730 self._repo._subparent = parentrepo
730 self._repo._subparent = parentrepo
731 self._repo._subsource = source
731 self._repo._subsource = source
732
732
733 if create:
733 if create:
734 lines = ['[paths]\n']
734 lines = ['[paths]\n']
735
735
736 def addpathconfig(key, value):
736 def addpathconfig(key, value):
737 if value:
737 if value:
738 lines.append('%s = %s\n' % (key, value))
738 lines.append('%s = %s\n' % (key, value))
739 self.ui.setconfig('paths', key, value, 'subrepo')
739 self.ui.setconfig('paths', key, value, 'subrepo')
740
740
741 defpath = _abssource(self._repo, abort=False)
741 defpath = _abssource(self._repo, abort=False)
742 defpushpath = _abssource(self._repo, True, abort=False)
742 defpushpath = _abssource(self._repo, True, abort=False)
743 addpathconfig('default', defpath)
743 addpathconfig('default', defpath)
744 if defpath != defpushpath:
744 if defpath != defpushpath:
745 addpathconfig('default-push', defpushpath)
745 addpathconfig('default-push', defpushpath)
746
746
747 fp = self._repo.vfs("hgrc", "w", text=True)
747 fp = self._repo.vfs("hgrc", "w", text=True)
748 try:
748 try:
749 fp.write(''.join(lines))
749 fp.write(''.join(lines))
750 finally:
750 finally:
751 fp.close()
751 fp.close()
752
752
753 @annotatesubrepoerror
753 @annotatesubrepoerror
754 def add(self, ui, match, prefix, explicitonly, **opts):
754 def add(self, ui, match, prefix, explicitonly, **opts):
755 return cmdutil.add(ui, self._repo, match,
755 return cmdutil.add(ui, self._repo, match,
756 self.wvfs.reljoin(prefix, self._path),
756 self.wvfs.reljoin(prefix, self._path),
757 explicitonly, **opts)
757 explicitonly, **opts)
758
758
759 @annotatesubrepoerror
759 @annotatesubrepoerror
760 def addremove(self, m, prefix, opts, dry_run, similarity):
760 def addremove(self, m, prefix, opts, dry_run, similarity):
761 # In the same way as sub directories are processed, once in a subrepo,
761 # In the same way as sub directories are processed, once in a subrepo,
762 # always entry any of its subrepos. Don't corrupt the options that will
762 # always entry any of its subrepos. Don't corrupt the options that will
763 # be used to process sibling subrepos however.
763 # be used to process sibling subrepos however.
764 opts = copy.copy(opts)
764 opts = copy.copy(opts)
765 opts['subrepos'] = True
765 opts['subrepos'] = True
766 return scmutil.addremove(self._repo, m,
766 return scmutil.addremove(self._repo, m,
767 self.wvfs.reljoin(prefix, self._path), opts,
767 self.wvfs.reljoin(prefix, self._path), opts,
768 dry_run, similarity)
768 dry_run, similarity)
769
769
770 @annotatesubrepoerror
770 @annotatesubrepoerror
771 def cat(self, match, fm, fntemplate, prefix, **opts):
771 def cat(self, match, fm, fntemplate, prefix, **opts):
772 rev = self._state[1]
772 rev = self._state[1]
773 ctx = self._repo[rev]
773 ctx = self._repo[rev]
774 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
774 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
775 prefix, **opts)
775 prefix, **opts)
776
776
777 @annotatesubrepoerror
777 @annotatesubrepoerror
778 def status(self, rev2, **opts):
778 def status(self, rev2, **opts):
779 try:
779 try:
780 rev1 = self._state[1]
780 rev1 = self._state[1]
781 ctx1 = self._repo[rev1]
781 ctx1 = self._repo[rev1]
782 ctx2 = self._repo[rev2]
782 ctx2 = self._repo[rev2]
783 return self._repo.status(ctx1, ctx2, **opts)
783 return self._repo.status(ctx1, ctx2, **opts)
784 except error.RepoLookupError as inst:
784 except error.RepoLookupError as inst:
785 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
785 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
786 % (inst, subrelpath(self)))
786 % (inst, subrelpath(self)))
787 return scmutil.status([], [], [], [], [], [], [])
787 return scmutil.status([], [], [], [], [], [], [])
788
788
789 @annotatesubrepoerror
789 @annotatesubrepoerror
790 def diff(self, ui, diffopts, node2, match, prefix, **opts):
790 def diff(self, ui, diffopts, node2, match, prefix, **opts):
791 try:
791 try:
792 node1 = node.bin(self._state[1])
792 node1 = node.bin(self._state[1])
793 # We currently expect node2 to come from substate and be
793 # We currently expect node2 to come from substate and be
794 # in hex format
794 # in hex format
795 if node2 is not None:
795 if node2 is not None:
796 node2 = node.bin(node2)
796 node2 = node.bin(node2)
797 cmdutil.diffordiffstat(ui, self._repo, diffopts,
797 cmdutil.diffordiffstat(ui, self._repo, diffopts,
798 node1, node2, match,
798 node1, node2, match,
799 prefix=posixpath.join(prefix, self._path),
799 prefix=posixpath.join(prefix, self._path),
800 listsubrepos=True, **opts)
800 listsubrepos=True, **opts)
801 except error.RepoLookupError as inst:
801 except error.RepoLookupError as inst:
802 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
802 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
803 % (inst, subrelpath(self)))
803 % (inst, subrelpath(self)))
804
804
805 @annotatesubrepoerror
805 @annotatesubrepoerror
806 def archive(self, archiver, prefix, match=None, decode=True):
806 def archive(self, archiver, prefix, match=None, decode=True):
807 self._get(self._state + ('hg',))
807 self._get(self._state + ('hg',))
808 total = abstractsubrepo.archive(self, archiver, prefix, match)
808 total = abstractsubrepo.archive(self, archiver, prefix, match)
809 rev = self._state[1]
809 rev = self._state[1]
810 ctx = self._repo[rev]
810 ctx = self._repo[rev]
811 for subpath in ctx.substate:
811 for subpath in ctx.substate:
812 s = subrepo(ctx, subpath, True)
812 s = subrepo(ctx, subpath, True)
813 submatch = matchmod.subdirmatcher(subpath, match)
813 submatch = matchmod.subdirmatcher(subpath, match)
814 total += s.archive(archiver, prefix + self._path + '/', submatch,
814 total += s.archive(archiver, prefix + self._path + '/', submatch,
815 decode)
815 decode)
816 return total
816 return total
817
817
818 @annotatesubrepoerror
818 @annotatesubrepoerror
819 def dirty(self, ignoreupdate=False, missing=False):
819 def dirty(self, ignoreupdate=False, missing=False):
820 r = self._state[1]
820 r = self._state[1]
821 if r == '' and not ignoreupdate: # no state recorded
821 if r == '' and not ignoreupdate: # no state recorded
822 return True
822 return True
823 w = self._repo[None]
823 w = self._repo[None]
824 if r != w.p1().hex() and not ignoreupdate:
824 if r != w.p1().hex() and not ignoreupdate:
825 # different version checked out
825 # different version checked out
826 return True
826 return True
827 return w.dirty(missing=missing) # working directory changed
827 return w.dirty(missing=missing) # working directory changed
828
828
829 def basestate(self):
829 def basestate(self):
830 return self._repo['.'].hex()
830 return self._repo['.'].hex()
831
831
832 def checknested(self, path):
832 def checknested(self, path):
833 return self._repo._checknested(self._repo.wjoin(path))
833 return self._repo._checknested(self._repo.wjoin(path))
834
834
835 @annotatesubrepoerror
835 @annotatesubrepoerror
836 def commit(self, text, user, date):
836 def commit(self, text, user, date):
837 # don't bother committing in the subrepo if it's only been
837 # don't bother committing in the subrepo if it's only been
838 # updated
838 # updated
839 if not self.dirty(True):
839 if not self.dirty(True):
840 return self._repo['.'].hex()
840 return self._repo['.'].hex()
841 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
841 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
842 n = self._repo.commit(text, user, date)
842 n = self._repo.commit(text, user, date)
843 if not n:
843 if not n:
844 return self._repo['.'].hex() # different version checked out
844 return self._repo['.'].hex() # different version checked out
845 return node.hex(n)
845 return node.hex(n)
846
846
847 @annotatesubrepoerror
847 @annotatesubrepoerror
848 def phase(self, state):
848 def phase(self, state):
849 return self._repo[state].phase()
849 return self._repo[state].phase()
850
850
851 @annotatesubrepoerror
851 @annotatesubrepoerror
852 def remove(self):
852 def remove(self):
853 # we can't fully delete the repository as it may contain
853 # we can't fully delete the repository as it may contain
854 # local-only history
854 # local-only history
855 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
855 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
856 hg.clean(self._repo, node.nullid, False)
856 hg.clean(self._repo, node.nullid, False)
857
857
858 def _get(self, state):
858 def _get(self, state):
859 source, revision, kind = state
859 source, revision, kind = state
860 parentrepo = self._repo._subparent
861
860 if revision in self._repo.unfiltered():
862 if revision in self._repo.unfiltered():
861 return True
863 # Allow shared subrepos tracked at null to setup the sharedpath
864 if len(self._repo) != 0 or not parentrepo.shared():
865 return True
862 self._repo._subsource = source
866 self._repo._subsource = source
863 srcurl = _abssource(self._repo)
867 srcurl = _abssource(self._repo)
864 other = hg.peer(self._repo, {}, srcurl)
868 other = hg.peer(self._repo, {}, srcurl)
865 if len(self._repo) == 0:
869 if len(self._repo) == 0:
866 self.ui.status(_('cloning subrepo %s from %s\n')
867 % (subrelpath(self), srcurl))
868 parentrepo = self._repo._subparent
869 # use self._repo.vfs instead of self.wvfs to remove .hg only
870 # use self._repo.vfs instead of self.wvfs to remove .hg only
870 self._repo.vfs.rmtree()
871 self._repo.vfs.rmtree()
871 other, cloned = hg.clone(self._repo._subparent.baseui, {},
872 if parentrepo.shared():
872 other, self._repo.root,
873 self.ui.status(_('sharing subrepo %s from %s\n')
873 update=False)
874 % (subrelpath(self), srcurl))
874 self._repo = cloned.local()
875 shared = hg.share(self._repo._subparent.baseui,
876 other, self._repo.root,
877 update=False, bookmarks=False)
878 self._repo = shared.local()
879 else:
880 self.ui.status(_('cloning subrepo %s from %s\n')
881 % (subrelpath(self), srcurl))
882 other, cloned = hg.clone(self._repo._subparent.baseui, {},
883 other, self._repo.root,
884 update=False)
885 self._repo = cloned.local()
875 self._initrepo(parentrepo, source, create=True)
886 self._initrepo(parentrepo, source, create=True)
876 self._cachestorehash(srcurl)
887 self._cachestorehash(srcurl)
877 else:
888 else:
878 self.ui.status(_('pulling subrepo %s from %s\n')
889 self.ui.status(_('pulling subrepo %s from %s\n')
879 % (subrelpath(self), srcurl))
890 % (subrelpath(self), srcurl))
880 cleansub = self.storeclean(srcurl)
891 cleansub = self.storeclean(srcurl)
881 exchange.pull(self._repo, other)
892 exchange.pull(self._repo, other)
882 if cleansub:
893 if cleansub:
883 # keep the repo clean after pull
894 # keep the repo clean after pull
884 self._cachestorehash(srcurl)
895 self._cachestorehash(srcurl)
885 return False
896 return False
886
897
887 @annotatesubrepoerror
898 @annotatesubrepoerror
888 def get(self, state, overwrite=False):
899 def get(self, state, overwrite=False):
889 inrepo = self._get(state)
900 inrepo = self._get(state)
890 source, revision, kind = state
901 source, revision, kind = state
891 repo = self._repo
902 repo = self._repo
892 repo.ui.debug("getting subrepo %s\n" % self._path)
903 repo.ui.debug("getting subrepo %s\n" % self._path)
893 if inrepo:
904 if inrepo:
894 urepo = repo.unfiltered()
905 urepo = repo.unfiltered()
895 ctx = urepo[revision]
906 ctx = urepo[revision]
896 if ctx.hidden():
907 if ctx.hidden():
897 urepo.ui.warn(
908 urepo.ui.warn(
898 _('revision %s in subrepository "%s" is hidden\n') \
909 _('revision %s in subrepository "%s" is hidden\n') \
899 % (revision[0:12], self._path))
910 % (revision[0:12], self._path))
900 repo = urepo
911 repo = urepo
901 hg.updaterepo(repo, revision, overwrite)
912 hg.updaterepo(repo, revision, overwrite)
902
913
903 @annotatesubrepoerror
914 @annotatesubrepoerror
904 def merge(self, state):
915 def merge(self, state):
905 self._get(state)
916 self._get(state)
906 cur = self._repo['.']
917 cur = self._repo['.']
907 dst = self._repo[state[1]]
918 dst = self._repo[state[1]]
908 anc = dst.ancestor(cur)
919 anc = dst.ancestor(cur)
909
920
910 def mergefunc():
921 def mergefunc():
911 if anc == cur and dst.branch() == cur.branch():
922 if anc == cur and dst.branch() == cur.branch():
912 self.ui.debug('updating subrepository "%s"\n'
923 self.ui.debug('updating subrepository "%s"\n'
913 % subrelpath(self))
924 % subrelpath(self))
914 hg.update(self._repo, state[1])
925 hg.update(self._repo, state[1])
915 elif anc == dst:
926 elif anc == dst:
916 self.ui.debug('skipping subrepository "%s"\n'
927 self.ui.debug('skipping subrepository "%s"\n'
917 % subrelpath(self))
928 % subrelpath(self))
918 else:
929 else:
919 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
930 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
920 hg.merge(self._repo, state[1], remind=False)
931 hg.merge(self._repo, state[1], remind=False)
921
932
922 wctx = self._repo[None]
933 wctx = self._repo[None]
923 if self.dirty():
934 if self.dirty():
924 if anc != dst:
935 if anc != dst:
925 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
936 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
926 mergefunc()
937 mergefunc()
927 else:
938 else:
928 mergefunc()
939 mergefunc()
929 else:
940 else:
930 mergefunc()
941 mergefunc()
931
942
932 @annotatesubrepoerror
943 @annotatesubrepoerror
933 def push(self, opts):
944 def push(self, opts):
934 force = opts.get('force')
945 force = opts.get('force')
935 newbranch = opts.get('new_branch')
946 newbranch = opts.get('new_branch')
936 ssh = opts.get('ssh')
947 ssh = opts.get('ssh')
937
948
938 # push subrepos depth-first for coherent ordering
949 # push subrepos depth-first for coherent ordering
939 c = self._repo['']
950 c = self._repo['']
940 subs = c.substate # only repos that are committed
951 subs = c.substate # only repos that are committed
941 for s in sorted(subs):
952 for s in sorted(subs):
942 if c.sub(s).push(opts) == 0:
953 if c.sub(s).push(opts) == 0:
943 return False
954 return False
944
955
945 dsturl = _abssource(self._repo, True)
956 dsturl = _abssource(self._repo, True)
946 if not force:
957 if not force:
947 if self.storeclean(dsturl):
958 if self.storeclean(dsturl):
948 self.ui.status(
959 self.ui.status(
949 _('no changes made to subrepo %s since last push to %s\n')
960 _('no changes made to subrepo %s since last push to %s\n')
950 % (subrelpath(self), dsturl))
961 % (subrelpath(self), dsturl))
951 return None
962 return None
952 self.ui.status(_('pushing subrepo %s to %s\n') %
963 self.ui.status(_('pushing subrepo %s to %s\n') %
953 (subrelpath(self), dsturl))
964 (subrelpath(self), dsturl))
954 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
965 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
955 res = exchange.push(self._repo, other, force, newbranch=newbranch)
966 res = exchange.push(self._repo, other, force, newbranch=newbranch)
956
967
957 # the repo is now clean
968 # the repo is now clean
958 self._cachestorehash(dsturl)
969 self._cachestorehash(dsturl)
959 return res.cgresult
970 return res.cgresult
960
971
961 @annotatesubrepoerror
972 @annotatesubrepoerror
962 def outgoing(self, ui, dest, opts):
973 def outgoing(self, ui, dest, opts):
963 if 'rev' in opts or 'branch' in opts:
974 if 'rev' in opts or 'branch' in opts:
964 opts = copy.copy(opts)
975 opts = copy.copy(opts)
965 opts.pop('rev', None)
976 opts.pop('rev', None)
966 opts.pop('branch', None)
977 opts.pop('branch', None)
967 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
978 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
968
979
969 @annotatesubrepoerror
980 @annotatesubrepoerror
970 def incoming(self, ui, source, opts):
981 def incoming(self, ui, source, opts):
971 if 'rev' in opts or 'branch' in opts:
982 if 'rev' in opts or 'branch' in opts:
972 opts = copy.copy(opts)
983 opts = copy.copy(opts)
973 opts.pop('rev', None)
984 opts.pop('rev', None)
974 opts.pop('branch', None)
985 opts.pop('branch', None)
975 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
986 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
976
987
977 @annotatesubrepoerror
988 @annotatesubrepoerror
978 def files(self):
989 def files(self):
979 rev = self._state[1]
990 rev = self._state[1]
980 ctx = self._repo[rev]
991 ctx = self._repo[rev]
981 return ctx.manifest().keys()
992 return ctx.manifest().keys()
982
993
983 def filedata(self, name, decode):
994 def filedata(self, name, decode):
984 rev = self._state[1]
995 rev = self._state[1]
985 data = self._repo[rev][name].data()
996 data = self._repo[rev][name].data()
986 if decode:
997 if decode:
987 data = self._repo.wwritedata(name, data)
998 data = self._repo.wwritedata(name, data)
988 return data
999 return data
989
1000
990 def fileflags(self, name):
1001 def fileflags(self, name):
991 rev = self._state[1]
1002 rev = self._state[1]
992 ctx = self._repo[rev]
1003 ctx = self._repo[rev]
993 return ctx.flags(name)
1004 return ctx.flags(name)
994
1005
995 @annotatesubrepoerror
1006 @annotatesubrepoerror
996 def printfiles(self, ui, m, fm, fmt, subrepos):
1007 def printfiles(self, ui, m, fm, fmt, subrepos):
997 # If the parent context is a workingctx, use the workingctx here for
1008 # If the parent context is a workingctx, use the workingctx here for
998 # consistency.
1009 # consistency.
999 if self._ctx.rev() is None:
1010 if self._ctx.rev() is None:
1000 ctx = self._repo[None]
1011 ctx = self._repo[None]
1001 else:
1012 else:
1002 rev = self._state[1]
1013 rev = self._state[1]
1003 ctx = self._repo[rev]
1014 ctx = self._repo[rev]
1004 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1015 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1005
1016
1006 @annotatesubrepoerror
1017 @annotatesubrepoerror
1007 def getfileset(self, expr):
1018 def getfileset(self, expr):
1008 if self._ctx.rev() is None:
1019 if self._ctx.rev() is None:
1009 ctx = self._repo[None]
1020 ctx = self._repo[None]
1010 else:
1021 else:
1011 rev = self._state[1]
1022 rev = self._state[1]
1012 ctx = self._repo[rev]
1023 ctx = self._repo[rev]
1013
1024
1014 files = ctx.getfileset(expr)
1025 files = ctx.getfileset(expr)
1015
1026
1016 for subpath in ctx.substate:
1027 for subpath in ctx.substate:
1017 sub = ctx.sub(subpath)
1028 sub = ctx.sub(subpath)
1018
1029
1019 try:
1030 try:
1020 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1031 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1021 except error.LookupError:
1032 except error.LookupError:
1022 self.ui.status(_("skipping missing subrepository: %s\n")
1033 self.ui.status(_("skipping missing subrepository: %s\n")
1023 % self.wvfs.reljoin(reporelpath(self), subpath))
1034 % self.wvfs.reljoin(reporelpath(self), subpath))
1024 return files
1035 return files
1025
1036
1026 def walk(self, match):
1037 def walk(self, match):
1027 ctx = self._repo[None]
1038 ctx = self._repo[None]
1028 return ctx.walk(match)
1039 return ctx.walk(match)
1029
1040
1030 @annotatesubrepoerror
1041 @annotatesubrepoerror
1031 def forget(self, match, prefix):
1042 def forget(self, match, prefix):
1032 return cmdutil.forget(self.ui, self._repo, match,
1043 return cmdutil.forget(self.ui, self._repo, match,
1033 self.wvfs.reljoin(prefix, self._path), True)
1044 self.wvfs.reljoin(prefix, self._path), True)
1034
1045
1035 @annotatesubrepoerror
1046 @annotatesubrepoerror
1036 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1047 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1037 return cmdutil.remove(self.ui, self._repo, matcher,
1048 return cmdutil.remove(self.ui, self._repo, matcher,
1038 self.wvfs.reljoin(prefix, self._path),
1049 self.wvfs.reljoin(prefix, self._path),
1039 after, force, subrepos)
1050 after, force, subrepos)
1040
1051
1041 @annotatesubrepoerror
1052 @annotatesubrepoerror
1042 def revert(self, substate, *pats, **opts):
1053 def revert(self, substate, *pats, **opts):
1043 # reverting a subrepo is a 2 step process:
1054 # reverting a subrepo is a 2 step process:
1044 # 1. if the no_backup is not set, revert all modified
1055 # 1. if the no_backup is not set, revert all modified
1045 # files inside the subrepo
1056 # files inside the subrepo
1046 # 2. update the subrepo to the revision specified in
1057 # 2. update the subrepo to the revision specified in
1047 # the corresponding substate dictionary
1058 # the corresponding substate dictionary
1048 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1059 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1049 if not opts.get('no_backup'):
1060 if not opts.get('no_backup'):
1050 # Revert all files on the subrepo, creating backups
1061 # Revert all files on the subrepo, creating backups
1051 # Note that this will not recursively revert subrepos
1062 # Note that this will not recursively revert subrepos
1052 # We could do it if there was a set:subrepos() predicate
1063 # We could do it if there was a set:subrepos() predicate
1053 opts = opts.copy()
1064 opts = opts.copy()
1054 opts['date'] = None
1065 opts['date'] = None
1055 opts['rev'] = substate[1]
1066 opts['rev'] = substate[1]
1056
1067
1057 self.filerevert(*pats, **opts)
1068 self.filerevert(*pats, **opts)
1058
1069
1059 # Update the repo to the revision specified in the given substate
1070 # Update the repo to the revision specified in the given substate
1060 if not opts.get('dry_run'):
1071 if not opts.get('dry_run'):
1061 self.get(substate, overwrite=True)
1072 self.get(substate, overwrite=True)
1062
1073
1063 def filerevert(self, *pats, **opts):
1074 def filerevert(self, *pats, **opts):
1064 ctx = self._repo[opts['rev']]
1075 ctx = self._repo[opts['rev']]
1065 parents = self._repo.dirstate.parents()
1076 parents = self._repo.dirstate.parents()
1066 if opts.get('all'):
1077 if opts.get('all'):
1067 pats = ['set:modified()']
1078 pats = ['set:modified()']
1068 else:
1079 else:
1069 pats = []
1080 pats = []
1070 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1081 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1071
1082
1072 def shortid(self, revid):
1083 def shortid(self, revid):
1073 return revid[:12]
1084 return revid[:12]
1074
1085
1075 def verify(self):
1086 def verify(self):
1076 try:
1087 try:
1077 rev = self._state[1]
1088 rev = self._state[1]
1078 ctx = self._repo.unfiltered()[rev]
1089 ctx = self._repo.unfiltered()[rev]
1079 if ctx.hidden():
1090 if ctx.hidden():
1080 # Since hidden revisions aren't pushed/pulled, it seems worth an
1091 # Since hidden revisions aren't pushed/pulled, it seems worth an
1081 # explicit warning.
1092 # explicit warning.
1082 ui = self._repo.ui
1093 ui = self._repo.ui
1083 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1094 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1084 (self._relpath, node.short(self._ctx.node())))
1095 (self._relpath, node.short(self._ctx.node())))
1085 return 0
1096 return 0
1086 except error.RepoLookupError:
1097 except error.RepoLookupError:
1087 # A missing subrepo revision may be a case of needing to pull it, so
1098 # A missing subrepo revision may be a case of needing to pull it, so
1088 # don't treat this as an error.
1099 # don't treat this as an error.
1089 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1100 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1090 (self._relpath, node.short(self._ctx.node())))
1101 (self._relpath, node.short(self._ctx.node())))
1091 return 0
1102 return 0
1092
1103
1093 @propertycache
1104 @propertycache
1094 def wvfs(self):
1105 def wvfs(self):
1095 """return own wvfs for efficiency and consistency
1106 """return own wvfs for efficiency and consistency
1096 """
1107 """
1097 return self._repo.wvfs
1108 return self._repo.wvfs
1098
1109
1099 @propertycache
1110 @propertycache
1100 def _relpath(self):
1111 def _relpath(self):
1101 """return path to this subrepository as seen from outermost repository
1112 """return path to this subrepository as seen from outermost repository
1102 """
1113 """
1103 # Keep consistent dir separators by avoiding vfs.join(self._path)
1114 # Keep consistent dir separators by avoiding vfs.join(self._path)
1104 return reporelpath(self._repo)
1115 return reporelpath(self._repo)
1105
1116
1106 class svnsubrepo(abstractsubrepo):
1117 class svnsubrepo(abstractsubrepo):
1107 def __init__(self, ctx, path, state, allowcreate):
1118 def __init__(self, ctx, path, state, allowcreate):
1108 super(svnsubrepo, self).__init__(ctx, path)
1119 super(svnsubrepo, self).__init__(ctx, path)
1109 self._state = state
1120 self._state = state
1110 self._exe = util.findexe('svn')
1121 self._exe = util.findexe('svn')
1111 if not self._exe:
1122 if not self._exe:
1112 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1123 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1113 % self._path)
1124 % self._path)
1114
1125
1115 def _svncommand(self, commands, filename='', failok=False):
1126 def _svncommand(self, commands, filename='', failok=False):
1116 cmd = [self._exe]
1127 cmd = [self._exe]
1117 extrakw = {}
1128 extrakw = {}
1118 if not self.ui.interactive():
1129 if not self.ui.interactive():
1119 # Making stdin be a pipe should prevent svn from behaving
1130 # Making stdin be a pipe should prevent svn from behaving
1120 # interactively even if we can't pass --non-interactive.
1131 # interactively even if we can't pass --non-interactive.
1121 extrakw['stdin'] = subprocess.PIPE
1132 extrakw['stdin'] = subprocess.PIPE
1122 # Starting in svn 1.5 --non-interactive is a global flag
1133 # Starting in svn 1.5 --non-interactive is a global flag
1123 # instead of being per-command, but we need to support 1.4 so
1134 # instead of being per-command, but we need to support 1.4 so
1124 # we have to be intelligent about what commands take
1135 # we have to be intelligent about what commands take
1125 # --non-interactive.
1136 # --non-interactive.
1126 if commands[0] in ('update', 'checkout', 'commit'):
1137 if commands[0] in ('update', 'checkout', 'commit'):
1127 cmd.append('--non-interactive')
1138 cmd.append('--non-interactive')
1128 cmd.extend(commands)
1139 cmd.extend(commands)
1129 if filename is not None:
1140 if filename is not None:
1130 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1141 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1131 self._path, filename)
1142 self._path, filename)
1132 cmd.append(path)
1143 cmd.append(path)
1133 env = dict(encoding.environ)
1144 env = dict(encoding.environ)
1134 # Avoid localized output, preserve current locale for everything else.
1145 # Avoid localized output, preserve current locale for everything else.
1135 lc_all = env.get('LC_ALL')
1146 lc_all = env.get('LC_ALL')
1136 if lc_all:
1147 if lc_all:
1137 env['LANG'] = lc_all
1148 env['LANG'] = lc_all
1138 del env['LC_ALL']
1149 del env['LC_ALL']
1139 env['LC_MESSAGES'] = 'C'
1150 env['LC_MESSAGES'] = 'C'
1140 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1151 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1141 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1152 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1142 universal_newlines=True, env=env, **extrakw)
1153 universal_newlines=True, env=env, **extrakw)
1143 stdout, stderr = p.communicate()
1154 stdout, stderr = p.communicate()
1144 stderr = stderr.strip()
1155 stderr = stderr.strip()
1145 if not failok:
1156 if not failok:
1146 if p.returncode:
1157 if p.returncode:
1147 raise error.Abort(stderr or 'exited with code %d'
1158 raise error.Abort(stderr or 'exited with code %d'
1148 % p.returncode)
1159 % p.returncode)
1149 if stderr:
1160 if stderr:
1150 self.ui.warn(stderr + '\n')
1161 self.ui.warn(stderr + '\n')
1151 return stdout, stderr
1162 return stdout, stderr
1152
1163
1153 @propertycache
1164 @propertycache
1154 def _svnversion(self):
1165 def _svnversion(self):
1155 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1166 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1156 m = re.search(br'^(\d+)\.(\d+)', output)
1167 m = re.search(br'^(\d+)\.(\d+)', output)
1157 if not m:
1168 if not m:
1158 raise error.Abort(_('cannot retrieve svn tool version'))
1169 raise error.Abort(_('cannot retrieve svn tool version'))
1159 return (int(m.group(1)), int(m.group(2)))
1170 return (int(m.group(1)), int(m.group(2)))
1160
1171
1161 def _wcrevs(self):
1172 def _wcrevs(self):
1162 # Get the working directory revision as well as the last
1173 # Get the working directory revision as well as the last
1163 # commit revision so we can compare the subrepo state with
1174 # commit revision so we can compare the subrepo state with
1164 # both. We used to store the working directory one.
1175 # both. We used to store the working directory one.
1165 output, err = self._svncommand(['info', '--xml'])
1176 output, err = self._svncommand(['info', '--xml'])
1166 doc = xml.dom.minidom.parseString(output)
1177 doc = xml.dom.minidom.parseString(output)
1167 entries = doc.getElementsByTagName('entry')
1178 entries = doc.getElementsByTagName('entry')
1168 lastrev, rev = '0', '0'
1179 lastrev, rev = '0', '0'
1169 if entries:
1180 if entries:
1170 rev = str(entries[0].getAttribute('revision')) or '0'
1181 rev = str(entries[0].getAttribute('revision')) or '0'
1171 commits = entries[0].getElementsByTagName('commit')
1182 commits = entries[0].getElementsByTagName('commit')
1172 if commits:
1183 if commits:
1173 lastrev = str(commits[0].getAttribute('revision')) or '0'
1184 lastrev = str(commits[0].getAttribute('revision')) or '0'
1174 return (lastrev, rev)
1185 return (lastrev, rev)
1175
1186
1176 def _wcrev(self):
1187 def _wcrev(self):
1177 return self._wcrevs()[0]
1188 return self._wcrevs()[0]
1178
1189
1179 def _wcchanged(self):
1190 def _wcchanged(self):
1180 """Return (changes, extchanges, missing) where changes is True
1191 """Return (changes, extchanges, missing) where changes is True
1181 if the working directory was changed, extchanges is
1192 if the working directory was changed, extchanges is
1182 True if any of these changes concern an external entry and missing
1193 True if any of these changes concern an external entry and missing
1183 is True if any change is a missing entry.
1194 is True if any change is a missing entry.
1184 """
1195 """
1185 output, err = self._svncommand(['status', '--xml'])
1196 output, err = self._svncommand(['status', '--xml'])
1186 externals, changes, missing = [], [], []
1197 externals, changes, missing = [], [], []
1187 doc = xml.dom.minidom.parseString(output)
1198 doc = xml.dom.minidom.parseString(output)
1188 for e in doc.getElementsByTagName('entry'):
1199 for e in doc.getElementsByTagName('entry'):
1189 s = e.getElementsByTagName('wc-status')
1200 s = e.getElementsByTagName('wc-status')
1190 if not s:
1201 if not s:
1191 continue
1202 continue
1192 item = s[0].getAttribute('item')
1203 item = s[0].getAttribute('item')
1193 props = s[0].getAttribute('props')
1204 props = s[0].getAttribute('props')
1194 path = e.getAttribute('path')
1205 path = e.getAttribute('path')
1195 if item == 'external':
1206 if item == 'external':
1196 externals.append(path)
1207 externals.append(path)
1197 elif item == 'missing':
1208 elif item == 'missing':
1198 missing.append(path)
1209 missing.append(path)
1199 if (item not in ('', 'normal', 'unversioned', 'external')
1210 if (item not in ('', 'normal', 'unversioned', 'external')
1200 or props not in ('', 'none', 'normal')):
1211 or props not in ('', 'none', 'normal')):
1201 changes.append(path)
1212 changes.append(path)
1202 for path in changes:
1213 for path in changes:
1203 for ext in externals:
1214 for ext in externals:
1204 if path == ext or path.startswith(ext + pycompat.ossep):
1215 if path == ext or path.startswith(ext + pycompat.ossep):
1205 return True, True, bool(missing)
1216 return True, True, bool(missing)
1206 return bool(changes), False, bool(missing)
1217 return bool(changes), False, bool(missing)
1207
1218
1208 def dirty(self, ignoreupdate=False, missing=False):
1219 def dirty(self, ignoreupdate=False, missing=False):
1209 wcchanged = self._wcchanged()
1220 wcchanged = self._wcchanged()
1210 changed = wcchanged[0] or (missing and wcchanged[2])
1221 changed = wcchanged[0] or (missing and wcchanged[2])
1211 if not changed:
1222 if not changed:
1212 if self._state[1] in self._wcrevs() or ignoreupdate:
1223 if self._state[1] in self._wcrevs() or ignoreupdate:
1213 return False
1224 return False
1214 return True
1225 return True
1215
1226
1216 def basestate(self):
1227 def basestate(self):
1217 lastrev, rev = self._wcrevs()
1228 lastrev, rev = self._wcrevs()
1218 if lastrev != rev:
1229 if lastrev != rev:
1219 # Last committed rev is not the same than rev. We would
1230 # Last committed rev is not the same than rev. We would
1220 # like to take lastrev but we do not know if the subrepo
1231 # like to take lastrev but we do not know if the subrepo
1221 # URL exists at lastrev. Test it and fallback to rev it
1232 # URL exists at lastrev. Test it and fallback to rev it
1222 # is not there.
1233 # is not there.
1223 try:
1234 try:
1224 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1235 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1225 return lastrev
1236 return lastrev
1226 except error.Abort:
1237 except error.Abort:
1227 pass
1238 pass
1228 return rev
1239 return rev
1229
1240
1230 @annotatesubrepoerror
1241 @annotatesubrepoerror
1231 def commit(self, text, user, date):
1242 def commit(self, text, user, date):
1232 # user and date are out of our hands since svn is centralized
1243 # user and date are out of our hands since svn is centralized
1233 changed, extchanged, missing = self._wcchanged()
1244 changed, extchanged, missing = self._wcchanged()
1234 if not changed:
1245 if not changed:
1235 return self.basestate()
1246 return self.basestate()
1236 if extchanged:
1247 if extchanged:
1237 # Do not try to commit externals
1248 # Do not try to commit externals
1238 raise error.Abort(_('cannot commit svn externals'))
1249 raise error.Abort(_('cannot commit svn externals'))
1239 if missing:
1250 if missing:
1240 # svn can commit with missing entries but aborting like hg
1251 # svn can commit with missing entries but aborting like hg
1241 # seems a better approach.
1252 # seems a better approach.
1242 raise error.Abort(_('cannot commit missing svn entries'))
1253 raise error.Abort(_('cannot commit missing svn entries'))
1243 commitinfo, err = self._svncommand(['commit', '-m', text])
1254 commitinfo, err = self._svncommand(['commit', '-m', text])
1244 self.ui.status(commitinfo)
1255 self.ui.status(commitinfo)
1245 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1256 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1246 if not newrev:
1257 if not newrev:
1247 if not commitinfo.strip():
1258 if not commitinfo.strip():
1248 # Sometimes, our definition of "changed" differs from
1259 # Sometimes, our definition of "changed" differs from
1249 # svn one. For instance, svn ignores missing files
1260 # svn one. For instance, svn ignores missing files
1250 # when committing. If there are only missing files, no
1261 # when committing. If there are only missing files, no
1251 # commit is made, no output and no error code.
1262 # commit is made, no output and no error code.
1252 raise error.Abort(_('failed to commit svn changes'))
1263 raise error.Abort(_('failed to commit svn changes'))
1253 raise error.Abort(commitinfo.splitlines()[-1])
1264 raise error.Abort(commitinfo.splitlines()[-1])
1254 newrev = newrev.groups()[0]
1265 newrev = newrev.groups()[0]
1255 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1266 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1256 return newrev
1267 return newrev
1257
1268
1258 @annotatesubrepoerror
1269 @annotatesubrepoerror
1259 def remove(self):
1270 def remove(self):
1260 if self.dirty():
1271 if self.dirty():
1261 self.ui.warn(_('not removing repo %s because '
1272 self.ui.warn(_('not removing repo %s because '
1262 'it has changes.\n') % self._path)
1273 'it has changes.\n') % self._path)
1263 return
1274 return
1264 self.ui.note(_('removing subrepo %s\n') % self._path)
1275 self.ui.note(_('removing subrepo %s\n') % self._path)
1265
1276
1266 self.wvfs.rmtree(forcibly=True)
1277 self.wvfs.rmtree(forcibly=True)
1267 try:
1278 try:
1268 pwvfs = self._ctx.repo().wvfs
1279 pwvfs = self._ctx.repo().wvfs
1269 pwvfs.removedirs(pwvfs.dirname(self._path))
1280 pwvfs.removedirs(pwvfs.dirname(self._path))
1270 except OSError:
1281 except OSError:
1271 pass
1282 pass
1272
1283
1273 @annotatesubrepoerror
1284 @annotatesubrepoerror
1274 def get(self, state, overwrite=False):
1285 def get(self, state, overwrite=False):
1275 if overwrite:
1286 if overwrite:
1276 self._svncommand(['revert', '--recursive'])
1287 self._svncommand(['revert', '--recursive'])
1277 args = ['checkout']
1288 args = ['checkout']
1278 if self._svnversion >= (1, 5):
1289 if self._svnversion >= (1, 5):
1279 args.append('--force')
1290 args.append('--force')
1280 # The revision must be specified at the end of the URL to properly
1291 # The revision must be specified at the end of the URL to properly
1281 # update to a directory which has since been deleted and recreated.
1292 # update to a directory which has since been deleted and recreated.
1282 args.append('%s@%s' % (state[0], state[1]))
1293 args.append('%s@%s' % (state[0], state[1]))
1283
1294
1284 # SEC: check that the ssh url is safe
1295 # SEC: check that the ssh url is safe
1285 util.checksafessh(state[0])
1296 util.checksafessh(state[0])
1286
1297
1287 status, err = self._svncommand(args, failok=True)
1298 status, err = self._svncommand(args, failok=True)
1288 _sanitize(self.ui, self.wvfs, '.svn')
1299 _sanitize(self.ui, self.wvfs, '.svn')
1289 if not re.search('Checked out revision [0-9]+.', status):
1300 if not re.search('Checked out revision [0-9]+.', status):
1290 if ('is already a working copy for a different URL' in err
1301 if ('is already a working copy for a different URL' in err
1291 and (self._wcchanged()[:2] == (False, False))):
1302 and (self._wcchanged()[:2] == (False, False))):
1292 # obstructed but clean working copy, so just blow it away.
1303 # obstructed but clean working copy, so just blow it away.
1293 self.remove()
1304 self.remove()
1294 self.get(state, overwrite=False)
1305 self.get(state, overwrite=False)
1295 return
1306 return
1296 raise error.Abort((status or err).splitlines()[-1])
1307 raise error.Abort((status or err).splitlines()[-1])
1297 self.ui.status(status)
1308 self.ui.status(status)
1298
1309
1299 @annotatesubrepoerror
1310 @annotatesubrepoerror
1300 def merge(self, state):
1311 def merge(self, state):
1301 old = self._state[1]
1312 old = self._state[1]
1302 new = state[1]
1313 new = state[1]
1303 wcrev = self._wcrev()
1314 wcrev = self._wcrev()
1304 if new != wcrev:
1315 if new != wcrev:
1305 dirty = old == wcrev or self._wcchanged()[0]
1316 dirty = old == wcrev or self._wcchanged()[0]
1306 if _updateprompt(self.ui, self, dirty, wcrev, new):
1317 if _updateprompt(self.ui, self, dirty, wcrev, new):
1307 self.get(state, False)
1318 self.get(state, False)
1308
1319
1309 def push(self, opts):
1320 def push(self, opts):
1310 # push is a no-op for SVN
1321 # push is a no-op for SVN
1311 return True
1322 return True
1312
1323
1313 @annotatesubrepoerror
1324 @annotatesubrepoerror
1314 def files(self):
1325 def files(self):
1315 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1326 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1316 doc = xml.dom.minidom.parseString(output)
1327 doc = xml.dom.minidom.parseString(output)
1317 paths = []
1328 paths = []
1318 for e in doc.getElementsByTagName('entry'):
1329 for e in doc.getElementsByTagName('entry'):
1319 kind = str(e.getAttribute('kind'))
1330 kind = str(e.getAttribute('kind'))
1320 if kind != 'file':
1331 if kind != 'file':
1321 continue
1332 continue
1322 name = ''.join(c.data for c
1333 name = ''.join(c.data for c
1323 in e.getElementsByTagName('name')[0].childNodes
1334 in e.getElementsByTagName('name')[0].childNodes
1324 if c.nodeType == c.TEXT_NODE)
1335 if c.nodeType == c.TEXT_NODE)
1325 paths.append(name.encode('utf-8'))
1336 paths.append(name.encode('utf-8'))
1326 return paths
1337 return paths
1327
1338
1328 def filedata(self, name, decode):
1339 def filedata(self, name, decode):
1329 return self._svncommand(['cat'], name)[0]
1340 return self._svncommand(['cat'], name)[0]
1330
1341
1331
1342
1332 class gitsubrepo(abstractsubrepo):
1343 class gitsubrepo(abstractsubrepo):
1333 def __init__(self, ctx, path, state, allowcreate):
1344 def __init__(self, ctx, path, state, allowcreate):
1334 super(gitsubrepo, self).__init__(ctx, path)
1345 super(gitsubrepo, self).__init__(ctx, path)
1335 self._state = state
1346 self._state = state
1336 self._abspath = ctx.repo().wjoin(path)
1347 self._abspath = ctx.repo().wjoin(path)
1337 self._subparent = ctx.repo()
1348 self._subparent = ctx.repo()
1338 self._ensuregit()
1349 self._ensuregit()
1339
1350
1340 def _ensuregit(self):
1351 def _ensuregit(self):
1341 try:
1352 try:
1342 self._gitexecutable = 'git'
1353 self._gitexecutable = 'git'
1343 out, err = self._gitnodir(['--version'])
1354 out, err = self._gitnodir(['--version'])
1344 except OSError as e:
1355 except OSError as e:
1345 genericerror = _("error executing git for subrepo '%s': %s")
1356 genericerror = _("error executing git for subrepo '%s': %s")
1346 notfoundhint = _("check git is installed and in your PATH")
1357 notfoundhint = _("check git is installed and in your PATH")
1347 if e.errno != errno.ENOENT:
1358 if e.errno != errno.ENOENT:
1348 raise error.Abort(genericerror % (
1359 raise error.Abort(genericerror % (
1349 self._path, encoding.strtolocal(e.strerror)))
1360 self._path, encoding.strtolocal(e.strerror)))
1350 elif pycompat.iswindows:
1361 elif pycompat.iswindows:
1351 try:
1362 try:
1352 self._gitexecutable = 'git.cmd'
1363 self._gitexecutable = 'git.cmd'
1353 out, err = self._gitnodir(['--version'])
1364 out, err = self._gitnodir(['--version'])
1354 except OSError as e2:
1365 except OSError as e2:
1355 if e2.errno == errno.ENOENT:
1366 if e2.errno == errno.ENOENT:
1356 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1367 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1357 " for subrepo '%s'") % self._path,
1368 " for subrepo '%s'") % self._path,
1358 hint=notfoundhint)
1369 hint=notfoundhint)
1359 else:
1370 else:
1360 raise error.Abort(genericerror % (self._path,
1371 raise error.Abort(genericerror % (self._path,
1361 encoding.strtolocal(e2.strerror)))
1372 encoding.strtolocal(e2.strerror)))
1362 else:
1373 else:
1363 raise error.Abort(_("couldn't find git for subrepo '%s'")
1374 raise error.Abort(_("couldn't find git for subrepo '%s'")
1364 % self._path, hint=notfoundhint)
1375 % self._path, hint=notfoundhint)
1365 versionstatus = self._checkversion(out)
1376 versionstatus = self._checkversion(out)
1366 if versionstatus == 'unknown':
1377 if versionstatus == 'unknown':
1367 self.ui.warn(_('cannot retrieve git version\n'))
1378 self.ui.warn(_('cannot retrieve git version\n'))
1368 elif versionstatus == 'abort':
1379 elif versionstatus == 'abort':
1369 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1380 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1370 elif versionstatus == 'warning':
1381 elif versionstatus == 'warning':
1371 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1382 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1372
1383
1373 @staticmethod
1384 @staticmethod
1374 def _gitversion(out):
1385 def _gitversion(out):
1375 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1386 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1376 if m:
1387 if m:
1377 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1388 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1378
1389
1379 m = re.search(br'^git version (\d+)\.(\d+)', out)
1390 m = re.search(br'^git version (\d+)\.(\d+)', out)
1380 if m:
1391 if m:
1381 return (int(m.group(1)), int(m.group(2)), 0)
1392 return (int(m.group(1)), int(m.group(2)), 0)
1382
1393
1383 return -1
1394 return -1
1384
1395
1385 @staticmethod
1396 @staticmethod
1386 def _checkversion(out):
1397 def _checkversion(out):
1387 '''ensure git version is new enough
1398 '''ensure git version is new enough
1388
1399
1389 >>> _checkversion = gitsubrepo._checkversion
1400 >>> _checkversion = gitsubrepo._checkversion
1390 >>> _checkversion(b'git version 1.6.0')
1401 >>> _checkversion(b'git version 1.6.0')
1391 'ok'
1402 'ok'
1392 >>> _checkversion(b'git version 1.8.5')
1403 >>> _checkversion(b'git version 1.8.5')
1393 'ok'
1404 'ok'
1394 >>> _checkversion(b'git version 1.4.0')
1405 >>> _checkversion(b'git version 1.4.0')
1395 'abort'
1406 'abort'
1396 >>> _checkversion(b'git version 1.5.0')
1407 >>> _checkversion(b'git version 1.5.0')
1397 'warning'
1408 'warning'
1398 >>> _checkversion(b'git version 1.9-rc0')
1409 >>> _checkversion(b'git version 1.9-rc0')
1399 'ok'
1410 'ok'
1400 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1411 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1401 'ok'
1412 'ok'
1402 >>> _checkversion(b'git version 1.9.0.GIT')
1413 >>> _checkversion(b'git version 1.9.0.GIT')
1403 'ok'
1414 'ok'
1404 >>> _checkversion(b'git version 12345')
1415 >>> _checkversion(b'git version 12345')
1405 'unknown'
1416 'unknown'
1406 >>> _checkversion(b'no')
1417 >>> _checkversion(b'no')
1407 'unknown'
1418 'unknown'
1408 '''
1419 '''
1409 version = gitsubrepo._gitversion(out)
1420 version = gitsubrepo._gitversion(out)
1410 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1421 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1411 # despite the docstring comment. For now, error on 1.4.0, warn on
1422 # despite the docstring comment. For now, error on 1.4.0, warn on
1412 # 1.5.0 but attempt to continue.
1423 # 1.5.0 but attempt to continue.
1413 if version == -1:
1424 if version == -1:
1414 return 'unknown'
1425 return 'unknown'
1415 if version < (1, 5, 0):
1426 if version < (1, 5, 0):
1416 return 'abort'
1427 return 'abort'
1417 elif version < (1, 6, 0):
1428 elif version < (1, 6, 0):
1418 return 'warning'
1429 return 'warning'
1419 return 'ok'
1430 return 'ok'
1420
1431
1421 def _gitcommand(self, commands, env=None, stream=False):
1432 def _gitcommand(self, commands, env=None, stream=False):
1422 return self._gitdir(commands, env=env, stream=stream)[0]
1433 return self._gitdir(commands, env=env, stream=stream)[0]
1423
1434
1424 def _gitdir(self, commands, env=None, stream=False):
1435 def _gitdir(self, commands, env=None, stream=False):
1425 return self._gitnodir(commands, env=env, stream=stream,
1436 return self._gitnodir(commands, env=env, stream=stream,
1426 cwd=self._abspath)
1437 cwd=self._abspath)
1427
1438
1428 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1439 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1429 """Calls the git command
1440 """Calls the git command
1430
1441
1431 The methods tries to call the git command. versions prior to 1.6.0
1442 The methods tries to call the git command. versions prior to 1.6.0
1432 are not supported and very probably fail.
1443 are not supported and very probably fail.
1433 """
1444 """
1434 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1445 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1435 if env is None:
1446 if env is None:
1436 env = encoding.environ.copy()
1447 env = encoding.environ.copy()
1437 # disable localization for Git output (issue5176)
1448 # disable localization for Git output (issue5176)
1438 env['LC_ALL'] = 'C'
1449 env['LC_ALL'] = 'C'
1439 # fix for Git CVE-2015-7545
1450 # fix for Git CVE-2015-7545
1440 if 'GIT_ALLOW_PROTOCOL' not in env:
1451 if 'GIT_ALLOW_PROTOCOL' not in env:
1441 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1452 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1442 # unless ui.quiet is set, print git's stderr,
1453 # unless ui.quiet is set, print git's stderr,
1443 # which is mostly progress and useful info
1454 # which is mostly progress and useful info
1444 errpipe = None
1455 errpipe = None
1445 if self.ui.quiet:
1456 if self.ui.quiet:
1446 errpipe = open(os.devnull, 'w')
1457 errpipe = open(os.devnull, 'w')
1447 if self.ui._colormode and len(commands) and commands[0] == "diff":
1458 if self.ui._colormode and len(commands) and commands[0] == "diff":
1448 # insert the argument in the front,
1459 # insert the argument in the front,
1449 # the end of git diff arguments is used for paths
1460 # the end of git diff arguments is used for paths
1450 commands.insert(1, '--color')
1461 commands.insert(1, '--color')
1451 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1462 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1452 cwd=cwd, env=env, close_fds=util.closefds,
1463 cwd=cwd, env=env, close_fds=util.closefds,
1453 stdout=subprocess.PIPE, stderr=errpipe)
1464 stdout=subprocess.PIPE, stderr=errpipe)
1454 if stream:
1465 if stream:
1455 return p.stdout, None
1466 return p.stdout, None
1456
1467
1457 retdata = p.stdout.read().strip()
1468 retdata = p.stdout.read().strip()
1458 # wait for the child to exit to avoid race condition.
1469 # wait for the child to exit to avoid race condition.
1459 p.wait()
1470 p.wait()
1460
1471
1461 if p.returncode != 0 and p.returncode != 1:
1472 if p.returncode != 0 and p.returncode != 1:
1462 # there are certain error codes that are ok
1473 # there are certain error codes that are ok
1463 command = commands[0]
1474 command = commands[0]
1464 if command in ('cat-file', 'symbolic-ref'):
1475 if command in ('cat-file', 'symbolic-ref'):
1465 return retdata, p.returncode
1476 return retdata, p.returncode
1466 # for all others, abort
1477 # for all others, abort
1467 raise error.Abort(_('git %s error %d in %s') %
1478 raise error.Abort(_('git %s error %d in %s') %
1468 (command, p.returncode, self._relpath))
1479 (command, p.returncode, self._relpath))
1469
1480
1470 return retdata, p.returncode
1481 return retdata, p.returncode
1471
1482
1472 def _gitmissing(self):
1483 def _gitmissing(self):
1473 return not self.wvfs.exists('.git')
1484 return not self.wvfs.exists('.git')
1474
1485
1475 def _gitstate(self):
1486 def _gitstate(self):
1476 return self._gitcommand(['rev-parse', 'HEAD'])
1487 return self._gitcommand(['rev-parse', 'HEAD'])
1477
1488
1478 def _gitcurrentbranch(self):
1489 def _gitcurrentbranch(self):
1479 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1490 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1480 if err:
1491 if err:
1481 current = None
1492 current = None
1482 return current
1493 return current
1483
1494
1484 def _gitremote(self, remote):
1495 def _gitremote(self, remote):
1485 out = self._gitcommand(['remote', 'show', '-n', remote])
1496 out = self._gitcommand(['remote', 'show', '-n', remote])
1486 line = out.split('\n')[1]
1497 line = out.split('\n')[1]
1487 i = line.index('URL: ') + len('URL: ')
1498 i = line.index('URL: ') + len('URL: ')
1488 return line[i:]
1499 return line[i:]
1489
1500
1490 def _githavelocally(self, revision):
1501 def _githavelocally(self, revision):
1491 out, code = self._gitdir(['cat-file', '-e', revision])
1502 out, code = self._gitdir(['cat-file', '-e', revision])
1492 return code == 0
1503 return code == 0
1493
1504
1494 def _gitisancestor(self, r1, r2):
1505 def _gitisancestor(self, r1, r2):
1495 base = self._gitcommand(['merge-base', r1, r2])
1506 base = self._gitcommand(['merge-base', r1, r2])
1496 return base == r1
1507 return base == r1
1497
1508
1498 def _gitisbare(self):
1509 def _gitisbare(self):
1499 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1510 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1500
1511
1501 def _gitupdatestat(self):
1512 def _gitupdatestat(self):
1502 """This must be run before git diff-index.
1513 """This must be run before git diff-index.
1503 diff-index only looks at changes to file stat;
1514 diff-index only looks at changes to file stat;
1504 this command looks at file contents and updates the stat."""
1515 this command looks at file contents and updates the stat."""
1505 self._gitcommand(['update-index', '-q', '--refresh'])
1516 self._gitcommand(['update-index', '-q', '--refresh'])
1506
1517
1507 def _gitbranchmap(self):
1518 def _gitbranchmap(self):
1508 '''returns 2 things:
1519 '''returns 2 things:
1509 a map from git branch to revision
1520 a map from git branch to revision
1510 a map from revision to branches'''
1521 a map from revision to branches'''
1511 branch2rev = {}
1522 branch2rev = {}
1512 rev2branch = {}
1523 rev2branch = {}
1513
1524
1514 out = self._gitcommand(['for-each-ref', '--format',
1525 out = self._gitcommand(['for-each-ref', '--format',
1515 '%(objectname) %(refname)'])
1526 '%(objectname) %(refname)'])
1516 for line in out.split('\n'):
1527 for line in out.split('\n'):
1517 revision, ref = line.split(' ')
1528 revision, ref = line.split(' ')
1518 if (not ref.startswith('refs/heads/') and
1529 if (not ref.startswith('refs/heads/') and
1519 not ref.startswith('refs/remotes/')):
1530 not ref.startswith('refs/remotes/')):
1520 continue
1531 continue
1521 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1532 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1522 continue # ignore remote/HEAD redirects
1533 continue # ignore remote/HEAD redirects
1523 branch2rev[ref] = revision
1534 branch2rev[ref] = revision
1524 rev2branch.setdefault(revision, []).append(ref)
1535 rev2branch.setdefault(revision, []).append(ref)
1525 return branch2rev, rev2branch
1536 return branch2rev, rev2branch
1526
1537
1527 def _gittracking(self, branches):
1538 def _gittracking(self, branches):
1528 'return map of remote branch to local tracking branch'
1539 'return map of remote branch to local tracking branch'
1529 # assumes no more than one local tracking branch for each remote
1540 # assumes no more than one local tracking branch for each remote
1530 tracking = {}
1541 tracking = {}
1531 for b in branches:
1542 for b in branches:
1532 if b.startswith('refs/remotes/'):
1543 if b.startswith('refs/remotes/'):
1533 continue
1544 continue
1534 bname = b.split('/', 2)[2]
1545 bname = b.split('/', 2)[2]
1535 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1546 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1536 if remote:
1547 if remote:
1537 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1548 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1538 tracking['refs/remotes/%s/%s' %
1549 tracking['refs/remotes/%s/%s' %
1539 (remote, ref.split('/', 2)[2])] = b
1550 (remote, ref.split('/', 2)[2])] = b
1540 return tracking
1551 return tracking
1541
1552
1542 def _abssource(self, source):
1553 def _abssource(self, source):
1543 if '://' not in source:
1554 if '://' not in source:
1544 # recognize the scp syntax as an absolute source
1555 # recognize the scp syntax as an absolute source
1545 colon = source.find(':')
1556 colon = source.find(':')
1546 if colon != -1 and '/' not in source[:colon]:
1557 if colon != -1 and '/' not in source[:colon]:
1547 return source
1558 return source
1548 self._subsource = source
1559 self._subsource = source
1549 return _abssource(self)
1560 return _abssource(self)
1550
1561
1551 def _fetch(self, source, revision):
1562 def _fetch(self, source, revision):
1552 if self._gitmissing():
1563 if self._gitmissing():
1553 # SEC: check for safe ssh url
1564 # SEC: check for safe ssh url
1554 util.checksafessh(source)
1565 util.checksafessh(source)
1555
1566
1556 source = self._abssource(source)
1567 source = self._abssource(source)
1557 self.ui.status(_('cloning subrepo %s from %s\n') %
1568 self.ui.status(_('cloning subrepo %s from %s\n') %
1558 (self._relpath, source))
1569 (self._relpath, source))
1559 self._gitnodir(['clone', source, self._abspath])
1570 self._gitnodir(['clone', source, self._abspath])
1560 if self._githavelocally(revision):
1571 if self._githavelocally(revision):
1561 return
1572 return
1562 self.ui.status(_('pulling subrepo %s from %s\n') %
1573 self.ui.status(_('pulling subrepo %s from %s\n') %
1563 (self._relpath, self._gitremote('origin')))
1574 (self._relpath, self._gitremote('origin')))
1564 # try only origin: the originally cloned repo
1575 # try only origin: the originally cloned repo
1565 self._gitcommand(['fetch'])
1576 self._gitcommand(['fetch'])
1566 if not self._githavelocally(revision):
1577 if not self._githavelocally(revision):
1567 raise error.Abort(_('revision %s does not exist in subrepository '
1578 raise error.Abort(_('revision %s does not exist in subrepository '
1568 '"%s"\n') % (revision, self._relpath))
1579 '"%s"\n') % (revision, self._relpath))
1569
1580
1570 @annotatesubrepoerror
1581 @annotatesubrepoerror
1571 def dirty(self, ignoreupdate=False, missing=False):
1582 def dirty(self, ignoreupdate=False, missing=False):
1572 if self._gitmissing():
1583 if self._gitmissing():
1573 return self._state[1] != ''
1584 return self._state[1] != ''
1574 if self._gitisbare():
1585 if self._gitisbare():
1575 return True
1586 return True
1576 if not ignoreupdate and self._state[1] != self._gitstate():
1587 if not ignoreupdate and self._state[1] != self._gitstate():
1577 # different version checked out
1588 # different version checked out
1578 return True
1589 return True
1579 # check for staged changes or modified files; ignore untracked files
1590 # check for staged changes or modified files; ignore untracked files
1580 self._gitupdatestat()
1591 self._gitupdatestat()
1581 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1592 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1582 return code == 1
1593 return code == 1
1583
1594
1584 def basestate(self):
1595 def basestate(self):
1585 return self._gitstate()
1596 return self._gitstate()
1586
1597
1587 @annotatesubrepoerror
1598 @annotatesubrepoerror
1588 def get(self, state, overwrite=False):
1599 def get(self, state, overwrite=False):
1589 source, revision, kind = state
1600 source, revision, kind = state
1590 if not revision:
1601 if not revision:
1591 self.remove()
1602 self.remove()
1592 return
1603 return
1593 self._fetch(source, revision)
1604 self._fetch(source, revision)
1594 # if the repo was set to be bare, unbare it
1605 # if the repo was set to be bare, unbare it
1595 if self._gitisbare():
1606 if self._gitisbare():
1596 self._gitcommand(['config', 'core.bare', 'false'])
1607 self._gitcommand(['config', 'core.bare', 'false'])
1597 if self._gitstate() == revision:
1608 if self._gitstate() == revision:
1598 self._gitcommand(['reset', '--hard', 'HEAD'])
1609 self._gitcommand(['reset', '--hard', 'HEAD'])
1599 return
1610 return
1600 elif self._gitstate() == revision:
1611 elif self._gitstate() == revision:
1601 if overwrite:
1612 if overwrite:
1602 # first reset the index to unmark new files for commit, because
1613 # first reset the index to unmark new files for commit, because
1603 # reset --hard will otherwise throw away files added for commit,
1614 # reset --hard will otherwise throw away files added for commit,
1604 # not just unmark them.
1615 # not just unmark them.
1605 self._gitcommand(['reset', 'HEAD'])
1616 self._gitcommand(['reset', 'HEAD'])
1606 self._gitcommand(['reset', '--hard', 'HEAD'])
1617 self._gitcommand(['reset', '--hard', 'HEAD'])
1607 return
1618 return
1608 branch2rev, rev2branch = self._gitbranchmap()
1619 branch2rev, rev2branch = self._gitbranchmap()
1609
1620
1610 def checkout(args):
1621 def checkout(args):
1611 cmd = ['checkout']
1622 cmd = ['checkout']
1612 if overwrite:
1623 if overwrite:
1613 # first reset the index to unmark new files for commit, because
1624 # first reset the index to unmark new files for commit, because
1614 # the -f option will otherwise throw away files added for
1625 # the -f option will otherwise throw away files added for
1615 # commit, not just unmark them.
1626 # commit, not just unmark them.
1616 self._gitcommand(['reset', 'HEAD'])
1627 self._gitcommand(['reset', 'HEAD'])
1617 cmd.append('-f')
1628 cmd.append('-f')
1618 self._gitcommand(cmd + args)
1629 self._gitcommand(cmd + args)
1619 _sanitize(self.ui, self.wvfs, '.git')
1630 _sanitize(self.ui, self.wvfs, '.git')
1620
1631
1621 def rawcheckout():
1632 def rawcheckout():
1622 # no branch to checkout, check it out with no branch
1633 # no branch to checkout, check it out with no branch
1623 self.ui.warn(_('checking out detached HEAD in '
1634 self.ui.warn(_('checking out detached HEAD in '
1624 'subrepository "%s"\n') % self._relpath)
1635 'subrepository "%s"\n') % self._relpath)
1625 self.ui.warn(_('check out a git branch if you intend '
1636 self.ui.warn(_('check out a git branch if you intend '
1626 'to make changes\n'))
1637 'to make changes\n'))
1627 checkout(['-q', revision])
1638 checkout(['-q', revision])
1628
1639
1629 if revision not in rev2branch:
1640 if revision not in rev2branch:
1630 rawcheckout()
1641 rawcheckout()
1631 return
1642 return
1632 branches = rev2branch[revision]
1643 branches = rev2branch[revision]
1633 firstlocalbranch = None
1644 firstlocalbranch = None
1634 for b in branches:
1645 for b in branches:
1635 if b == 'refs/heads/master':
1646 if b == 'refs/heads/master':
1636 # master trumps all other branches
1647 # master trumps all other branches
1637 checkout(['refs/heads/master'])
1648 checkout(['refs/heads/master'])
1638 return
1649 return
1639 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1650 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1640 firstlocalbranch = b
1651 firstlocalbranch = b
1641 if firstlocalbranch:
1652 if firstlocalbranch:
1642 checkout([firstlocalbranch])
1653 checkout([firstlocalbranch])
1643 return
1654 return
1644
1655
1645 tracking = self._gittracking(branch2rev.keys())
1656 tracking = self._gittracking(branch2rev.keys())
1646 # choose a remote branch already tracked if possible
1657 # choose a remote branch already tracked if possible
1647 remote = branches[0]
1658 remote = branches[0]
1648 if remote not in tracking:
1659 if remote not in tracking:
1649 for b in branches:
1660 for b in branches:
1650 if b in tracking:
1661 if b in tracking:
1651 remote = b
1662 remote = b
1652 break
1663 break
1653
1664
1654 if remote not in tracking:
1665 if remote not in tracking:
1655 # create a new local tracking branch
1666 # create a new local tracking branch
1656 local = remote.split('/', 3)[3]
1667 local = remote.split('/', 3)[3]
1657 checkout(['-b', local, remote])
1668 checkout(['-b', local, remote])
1658 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1669 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1659 # When updating to a tracked remote branch,
1670 # When updating to a tracked remote branch,
1660 # if the local tracking branch is downstream of it,
1671 # if the local tracking branch is downstream of it,
1661 # a normal `git pull` would have performed a "fast-forward merge"
1672 # a normal `git pull` would have performed a "fast-forward merge"
1662 # which is equivalent to updating the local branch to the remote.
1673 # which is equivalent to updating the local branch to the remote.
1663 # Since we are only looking at branching at update, we need to
1674 # Since we are only looking at branching at update, we need to
1664 # detect this situation and perform this action lazily.
1675 # detect this situation and perform this action lazily.
1665 if tracking[remote] != self._gitcurrentbranch():
1676 if tracking[remote] != self._gitcurrentbranch():
1666 checkout([tracking[remote]])
1677 checkout([tracking[remote]])
1667 self._gitcommand(['merge', '--ff', remote])
1678 self._gitcommand(['merge', '--ff', remote])
1668 _sanitize(self.ui, self.wvfs, '.git')
1679 _sanitize(self.ui, self.wvfs, '.git')
1669 else:
1680 else:
1670 # a real merge would be required, just checkout the revision
1681 # a real merge would be required, just checkout the revision
1671 rawcheckout()
1682 rawcheckout()
1672
1683
1673 @annotatesubrepoerror
1684 @annotatesubrepoerror
1674 def commit(self, text, user, date):
1685 def commit(self, text, user, date):
1675 if self._gitmissing():
1686 if self._gitmissing():
1676 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1687 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1677 cmd = ['commit', '-a', '-m', text]
1688 cmd = ['commit', '-a', '-m', text]
1678 env = encoding.environ.copy()
1689 env = encoding.environ.copy()
1679 if user:
1690 if user:
1680 cmd += ['--author', user]
1691 cmd += ['--author', user]
1681 if date:
1692 if date:
1682 # git's date parser silently ignores when seconds < 1e9
1693 # git's date parser silently ignores when seconds < 1e9
1683 # convert to ISO8601
1694 # convert to ISO8601
1684 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1695 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1685 '%Y-%m-%dT%H:%M:%S %1%2')
1696 '%Y-%m-%dT%H:%M:%S %1%2')
1686 self._gitcommand(cmd, env=env)
1697 self._gitcommand(cmd, env=env)
1687 # make sure commit works otherwise HEAD might not exist under certain
1698 # make sure commit works otherwise HEAD might not exist under certain
1688 # circumstances
1699 # circumstances
1689 return self._gitstate()
1700 return self._gitstate()
1690
1701
1691 @annotatesubrepoerror
1702 @annotatesubrepoerror
1692 def merge(self, state):
1703 def merge(self, state):
1693 source, revision, kind = state
1704 source, revision, kind = state
1694 self._fetch(source, revision)
1705 self._fetch(source, revision)
1695 base = self._gitcommand(['merge-base', revision, self._state[1]])
1706 base = self._gitcommand(['merge-base', revision, self._state[1]])
1696 self._gitupdatestat()
1707 self._gitupdatestat()
1697 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1708 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1698
1709
1699 def mergefunc():
1710 def mergefunc():
1700 if base == revision:
1711 if base == revision:
1701 self.get(state) # fast forward merge
1712 self.get(state) # fast forward merge
1702 elif base != self._state[1]:
1713 elif base != self._state[1]:
1703 self._gitcommand(['merge', '--no-commit', revision])
1714 self._gitcommand(['merge', '--no-commit', revision])
1704 _sanitize(self.ui, self.wvfs, '.git')
1715 _sanitize(self.ui, self.wvfs, '.git')
1705
1716
1706 if self.dirty():
1717 if self.dirty():
1707 if self._gitstate() != revision:
1718 if self._gitstate() != revision:
1708 dirty = self._gitstate() == self._state[1] or code != 0
1719 dirty = self._gitstate() == self._state[1] or code != 0
1709 if _updateprompt(self.ui, self, dirty,
1720 if _updateprompt(self.ui, self, dirty,
1710 self._state[1][:7], revision[:7]):
1721 self._state[1][:7], revision[:7]):
1711 mergefunc()
1722 mergefunc()
1712 else:
1723 else:
1713 mergefunc()
1724 mergefunc()
1714
1725
1715 @annotatesubrepoerror
1726 @annotatesubrepoerror
1716 def push(self, opts):
1727 def push(self, opts):
1717 force = opts.get('force')
1728 force = opts.get('force')
1718
1729
1719 if not self._state[1]:
1730 if not self._state[1]:
1720 return True
1731 return True
1721 if self._gitmissing():
1732 if self._gitmissing():
1722 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1733 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1723 # if a branch in origin contains the revision, nothing to do
1734 # if a branch in origin contains the revision, nothing to do
1724 branch2rev, rev2branch = self._gitbranchmap()
1735 branch2rev, rev2branch = self._gitbranchmap()
1725 if self._state[1] in rev2branch:
1736 if self._state[1] in rev2branch:
1726 for b in rev2branch[self._state[1]]:
1737 for b in rev2branch[self._state[1]]:
1727 if b.startswith('refs/remotes/origin/'):
1738 if b.startswith('refs/remotes/origin/'):
1728 return True
1739 return True
1729 for b, revision in branch2rev.iteritems():
1740 for b, revision in branch2rev.iteritems():
1730 if b.startswith('refs/remotes/origin/'):
1741 if b.startswith('refs/remotes/origin/'):
1731 if self._gitisancestor(self._state[1], revision):
1742 if self._gitisancestor(self._state[1], revision):
1732 return True
1743 return True
1733 # otherwise, try to push the currently checked out branch
1744 # otherwise, try to push the currently checked out branch
1734 cmd = ['push']
1745 cmd = ['push']
1735 if force:
1746 if force:
1736 cmd.append('--force')
1747 cmd.append('--force')
1737
1748
1738 current = self._gitcurrentbranch()
1749 current = self._gitcurrentbranch()
1739 if current:
1750 if current:
1740 # determine if the current branch is even useful
1751 # determine if the current branch is even useful
1741 if not self._gitisancestor(self._state[1], current):
1752 if not self._gitisancestor(self._state[1], current):
1742 self.ui.warn(_('unrelated git branch checked out '
1753 self.ui.warn(_('unrelated git branch checked out '
1743 'in subrepository "%s"\n') % self._relpath)
1754 'in subrepository "%s"\n') % self._relpath)
1744 return False
1755 return False
1745 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1756 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1746 (current.split('/', 2)[2], self._relpath))
1757 (current.split('/', 2)[2], self._relpath))
1747 ret = self._gitdir(cmd + ['origin', current])
1758 ret = self._gitdir(cmd + ['origin', current])
1748 return ret[1] == 0
1759 return ret[1] == 0
1749 else:
1760 else:
1750 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1761 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1751 'cannot push revision %s\n') %
1762 'cannot push revision %s\n') %
1752 (self._relpath, self._state[1]))
1763 (self._relpath, self._state[1]))
1753 return False
1764 return False
1754
1765
1755 @annotatesubrepoerror
1766 @annotatesubrepoerror
1756 def add(self, ui, match, prefix, explicitonly, **opts):
1767 def add(self, ui, match, prefix, explicitonly, **opts):
1757 if self._gitmissing():
1768 if self._gitmissing():
1758 return []
1769 return []
1759
1770
1760 (modified, added, removed,
1771 (modified, added, removed,
1761 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1772 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1762 clean=True)
1773 clean=True)
1763
1774
1764 tracked = set()
1775 tracked = set()
1765 # dirstates 'amn' warn, 'r' is added again
1776 # dirstates 'amn' warn, 'r' is added again
1766 for l in (modified, added, deleted, clean):
1777 for l in (modified, added, deleted, clean):
1767 tracked.update(l)
1778 tracked.update(l)
1768
1779
1769 # Unknown files not of interest will be rejected by the matcher
1780 # Unknown files not of interest will be rejected by the matcher
1770 files = unknown
1781 files = unknown
1771 files.extend(match.files())
1782 files.extend(match.files())
1772
1783
1773 rejected = []
1784 rejected = []
1774
1785
1775 files = [f for f in sorted(set(files)) if match(f)]
1786 files = [f for f in sorted(set(files)) if match(f)]
1776 for f in files:
1787 for f in files:
1777 exact = match.exact(f)
1788 exact = match.exact(f)
1778 command = ["add"]
1789 command = ["add"]
1779 if exact:
1790 if exact:
1780 command.append("-f") #should be added, even if ignored
1791 command.append("-f") #should be added, even if ignored
1781 if ui.verbose or not exact:
1792 if ui.verbose or not exact:
1782 ui.status(_('adding %s\n') % match.rel(f))
1793 ui.status(_('adding %s\n') % match.rel(f))
1783
1794
1784 if f in tracked: # hg prints 'adding' even if already tracked
1795 if f in tracked: # hg prints 'adding' even if already tracked
1785 if exact:
1796 if exact:
1786 rejected.append(f)
1797 rejected.append(f)
1787 continue
1798 continue
1788 if not opts.get(r'dry_run'):
1799 if not opts.get(r'dry_run'):
1789 self._gitcommand(command + [f])
1800 self._gitcommand(command + [f])
1790
1801
1791 for f in rejected:
1802 for f in rejected:
1792 ui.warn(_("%s already tracked!\n") % match.abs(f))
1803 ui.warn(_("%s already tracked!\n") % match.abs(f))
1793
1804
1794 return rejected
1805 return rejected
1795
1806
1796 @annotatesubrepoerror
1807 @annotatesubrepoerror
1797 def remove(self):
1808 def remove(self):
1798 if self._gitmissing():
1809 if self._gitmissing():
1799 return
1810 return
1800 if self.dirty():
1811 if self.dirty():
1801 self.ui.warn(_('not removing repo %s because '
1812 self.ui.warn(_('not removing repo %s because '
1802 'it has changes.\n') % self._relpath)
1813 'it has changes.\n') % self._relpath)
1803 return
1814 return
1804 # we can't fully delete the repository as it may contain
1815 # we can't fully delete the repository as it may contain
1805 # local-only history
1816 # local-only history
1806 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1817 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1807 self._gitcommand(['config', 'core.bare', 'true'])
1818 self._gitcommand(['config', 'core.bare', 'true'])
1808 for f, kind in self.wvfs.readdir():
1819 for f, kind in self.wvfs.readdir():
1809 if f == '.git':
1820 if f == '.git':
1810 continue
1821 continue
1811 if kind == stat.S_IFDIR:
1822 if kind == stat.S_IFDIR:
1812 self.wvfs.rmtree(f)
1823 self.wvfs.rmtree(f)
1813 else:
1824 else:
1814 self.wvfs.unlink(f)
1825 self.wvfs.unlink(f)
1815
1826
1816 def archive(self, archiver, prefix, match=None, decode=True):
1827 def archive(self, archiver, prefix, match=None, decode=True):
1817 total = 0
1828 total = 0
1818 source, revision = self._state
1829 source, revision = self._state
1819 if not revision:
1830 if not revision:
1820 return total
1831 return total
1821 self._fetch(source, revision)
1832 self._fetch(source, revision)
1822
1833
1823 # Parse git's native archive command.
1834 # Parse git's native archive command.
1824 # This should be much faster than manually traversing the trees
1835 # This should be much faster than manually traversing the trees
1825 # and objects with many subprocess calls.
1836 # and objects with many subprocess calls.
1826 tarstream = self._gitcommand(['archive', revision], stream=True)
1837 tarstream = self._gitcommand(['archive', revision], stream=True)
1827 tar = tarfile.open(fileobj=tarstream, mode='r|')
1838 tar = tarfile.open(fileobj=tarstream, mode='r|')
1828 relpath = subrelpath(self)
1839 relpath = subrelpath(self)
1829 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1840 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1830 for i, info in enumerate(tar):
1841 for i, info in enumerate(tar):
1831 if info.isdir():
1842 if info.isdir():
1832 continue
1843 continue
1833 if match and not match(info.name):
1844 if match and not match(info.name):
1834 continue
1845 continue
1835 if info.issym():
1846 if info.issym():
1836 data = info.linkname
1847 data = info.linkname
1837 else:
1848 else:
1838 data = tar.extractfile(info).read()
1849 data = tar.extractfile(info).read()
1839 archiver.addfile(prefix + self._path + '/' + info.name,
1850 archiver.addfile(prefix + self._path + '/' + info.name,
1840 info.mode, info.issym(), data)
1851 info.mode, info.issym(), data)
1841 total += 1
1852 total += 1
1842 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1853 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1843 unit=_('files'))
1854 unit=_('files'))
1844 self.ui.progress(_('archiving (%s)') % relpath, None)
1855 self.ui.progress(_('archiving (%s)') % relpath, None)
1845 return total
1856 return total
1846
1857
1847
1858
1848 @annotatesubrepoerror
1859 @annotatesubrepoerror
1849 def cat(self, match, fm, fntemplate, prefix, **opts):
1860 def cat(self, match, fm, fntemplate, prefix, **opts):
1850 rev = self._state[1]
1861 rev = self._state[1]
1851 if match.anypats():
1862 if match.anypats():
1852 return 1 #No support for include/exclude yet
1863 return 1 #No support for include/exclude yet
1853
1864
1854 if not match.files():
1865 if not match.files():
1855 return 1
1866 return 1
1856
1867
1857 # TODO: add support for non-plain formatter (see cmdutil.cat())
1868 # TODO: add support for non-plain formatter (see cmdutil.cat())
1858 for f in match.files():
1869 for f in match.files():
1859 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1870 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1860 fp = cmdutil.makefileobj(self._subparent, fntemplate,
1871 fp = cmdutil.makefileobj(self._subparent, fntemplate,
1861 self._ctx.node(),
1872 self._ctx.node(),
1862 pathname=self.wvfs.reljoin(prefix, f))
1873 pathname=self.wvfs.reljoin(prefix, f))
1863 fp.write(output)
1874 fp.write(output)
1864 fp.close()
1875 fp.close()
1865 return 0
1876 return 0
1866
1877
1867
1878
1868 @annotatesubrepoerror
1879 @annotatesubrepoerror
1869 def status(self, rev2, **opts):
1880 def status(self, rev2, **opts):
1870 rev1 = self._state[1]
1881 rev1 = self._state[1]
1871 if self._gitmissing() or not rev1:
1882 if self._gitmissing() or not rev1:
1872 # if the repo is missing, return no results
1883 # if the repo is missing, return no results
1873 return scmutil.status([], [], [], [], [], [], [])
1884 return scmutil.status([], [], [], [], [], [], [])
1874 modified, added, removed = [], [], []
1885 modified, added, removed = [], [], []
1875 self._gitupdatestat()
1886 self._gitupdatestat()
1876 if rev2:
1887 if rev2:
1877 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1888 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1878 else:
1889 else:
1879 command = ['diff-index', '--no-renames', rev1]
1890 command = ['diff-index', '--no-renames', rev1]
1880 out = self._gitcommand(command)
1891 out = self._gitcommand(command)
1881 for line in out.split('\n'):
1892 for line in out.split('\n'):
1882 tab = line.find('\t')
1893 tab = line.find('\t')
1883 if tab == -1:
1894 if tab == -1:
1884 continue
1895 continue
1885 status, f = line[tab - 1], line[tab + 1:]
1896 status, f = line[tab - 1], line[tab + 1:]
1886 if status == 'M':
1897 if status == 'M':
1887 modified.append(f)
1898 modified.append(f)
1888 elif status == 'A':
1899 elif status == 'A':
1889 added.append(f)
1900 added.append(f)
1890 elif status == 'D':
1901 elif status == 'D':
1891 removed.append(f)
1902 removed.append(f)
1892
1903
1893 deleted, unknown, ignored, clean = [], [], [], []
1904 deleted, unknown, ignored, clean = [], [], [], []
1894
1905
1895 command = ['status', '--porcelain', '-z']
1906 command = ['status', '--porcelain', '-z']
1896 if opts.get(r'unknown'):
1907 if opts.get(r'unknown'):
1897 command += ['--untracked-files=all']
1908 command += ['--untracked-files=all']
1898 if opts.get(r'ignored'):
1909 if opts.get(r'ignored'):
1899 command += ['--ignored']
1910 command += ['--ignored']
1900 out = self._gitcommand(command)
1911 out = self._gitcommand(command)
1901
1912
1902 changedfiles = set()
1913 changedfiles = set()
1903 changedfiles.update(modified)
1914 changedfiles.update(modified)
1904 changedfiles.update(added)
1915 changedfiles.update(added)
1905 changedfiles.update(removed)
1916 changedfiles.update(removed)
1906 for line in out.split('\0'):
1917 for line in out.split('\0'):
1907 if not line:
1918 if not line:
1908 continue
1919 continue
1909 st = line[0:2]
1920 st = line[0:2]
1910 #moves and copies show 2 files on one line
1921 #moves and copies show 2 files on one line
1911 if line.find('\0') >= 0:
1922 if line.find('\0') >= 0:
1912 filename1, filename2 = line[3:].split('\0')
1923 filename1, filename2 = line[3:].split('\0')
1913 else:
1924 else:
1914 filename1 = line[3:]
1925 filename1 = line[3:]
1915 filename2 = None
1926 filename2 = None
1916
1927
1917 changedfiles.add(filename1)
1928 changedfiles.add(filename1)
1918 if filename2:
1929 if filename2:
1919 changedfiles.add(filename2)
1930 changedfiles.add(filename2)
1920
1931
1921 if st == '??':
1932 if st == '??':
1922 unknown.append(filename1)
1933 unknown.append(filename1)
1923 elif st == '!!':
1934 elif st == '!!':
1924 ignored.append(filename1)
1935 ignored.append(filename1)
1925
1936
1926 if opts.get(r'clean'):
1937 if opts.get(r'clean'):
1927 out = self._gitcommand(['ls-files'])
1938 out = self._gitcommand(['ls-files'])
1928 for f in out.split('\n'):
1939 for f in out.split('\n'):
1929 if not f in changedfiles:
1940 if not f in changedfiles:
1930 clean.append(f)
1941 clean.append(f)
1931
1942
1932 return scmutil.status(modified, added, removed, deleted,
1943 return scmutil.status(modified, added, removed, deleted,
1933 unknown, ignored, clean)
1944 unknown, ignored, clean)
1934
1945
1935 @annotatesubrepoerror
1946 @annotatesubrepoerror
1936 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1947 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1937 node1 = self._state[1]
1948 node1 = self._state[1]
1938 cmd = ['diff', '--no-renames']
1949 cmd = ['diff', '--no-renames']
1939 if opts[r'stat']:
1950 if opts[r'stat']:
1940 cmd.append('--stat')
1951 cmd.append('--stat')
1941 else:
1952 else:
1942 # for Git, this also implies '-p'
1953 # for Git, this also implies '-p'
1943 cmd.append('-U%d' % diffopts.context)
1954 cmd.append('-U%d' % diffopts.context)
1944
1955
1945 gitprefix = self.wvfs.reljoin(prefix, self._path)
1956 gitprefix = self.wvfs.reljoin(prefix, self._path)
1946
1957
1947 if diffopts.noprefix:
1958 if diffopts.noprefix:
1948 cmd.extend(['--src-prefix=%s/' % gitprefix,
1959 cmd.extend(['--src-prefix=%s/' % gitprefix,
1949 '--dst-prefix=%s/' % gitprefix])
1960 '--dst-prefix=%s/' % gitprefix])
1950 else:
1961 else:
1951 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1962 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1952 '--dst-prefix=b/%s/' % gitprefix])
1963 '--dst-prefix=b/%s/' % gitprefix])
1953
1964
1954 if diffopts.ignorews:
1965 if diffopts.ignorews:
1955 cmd.append('--ignore-all-space')
1966 cmd.append('--ignore-all-space')
1956 if diffopts.ignorewsamount:
1967 if diffopts.ignorewsamount:
1957 cmd.append('--ignore-space-change')
1968 cmd.append('--ignore-space-change')
1958 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1969 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1959 and diffopts.ignoreblanklines:
1970 and diffopts.ignoreblanklines:
1960 cmd.append('--ignore-blank-lines')
1971 cmd.append('--ignore-blank-lines')
1961
1972
1962 cmd.append(node1)
1973 cmd.append(node1)
1963 if node2:
1974 if node2:
1964 cmd.append(node2)
1975 cmd.append(node2)
1965
1976
1966 output = ""
1977 output = ""
1967 if match.always():
1978 if match.always():
1968 output += self._gitcommand(cmd) + '\n'
1979 output += self._gitcommand(cmd) + '\n'
1969 else:
1980 else:
1970 st = self.status(node2)[:3]
1981 st = self.status(node2)[:3]
1971 files = [f for sublist in st for f in sublist]
1982 files = [f for sublist in st for f in sublist]
1972 for f in files:
1983 for f in files:
1973 if match(f):
1984 if match(f):
1974 output += self._gitcommand(cmd + ['--', f]) + '\n'
1985 output += self._gitcommand(cmd + ['--', f]) + '\n'
1975
1986
1976 if output.strip():
1987 if output.strip():
1977 ui.write(output)
1988 ui.write(output)
1978
1989
1979 @annotatesubrepoerror
1990 @annotatesubrepoerror
1980 def revert(self, substate, *pats, **opts):
1991 def revert(self, substate, *pats, **opts):
1981 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1992 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1982 if not opts.get(r'no_backup'):
1993 if not opts.get(r'no_backup'):
1983 status = self.status(None)
1994 status = self.status(None)
1984 names = status.modified
1995 names = status.modified
1985 for name in names:
1996 for name in names:
1986 bakname = scmutil.origpath(self.ui, self._subparent, name)
1997 bakname = scmutil.origpath(self.ui, self._subparent, name)
1987 self.ui.note(_('saving current version of %s as %s\n') %
1998 self.ui.note(_('saving current version of %s as %s\n') %
1988 (name, bakname))
1999 (name, bakname))
1989 self.wvfs.rename(name, bakname)
2000 self.wvfs.rename(name, bakname)
1990
2001
1991 if not opts.get(r'dry_run'):
2002 if not opts.get(r'dry_run'):
1992 self.get(substate, overwrite=True)
2003 self.get(substate, overwrite=True)
1993 return []
2004 return []
1994
2005
1995 def shortid(self, revid):
2006 def shortid(self, revid):
1996 return revid[:7]
2007 return revid[:7]
1997
2008
1998 types = {
2009 types = {
1999 'hg': hgsubrepo,
2010 'hg': hgsubrepo,
2000 'svn': svnsubrepo,
2011 'svn': svnsubrepo,
2001 'git': gitsubrepo,
2012 'git': gitsubrepo,
2002 }
2013 }
@@ -1,403 +1,454 b''
1 #require serve
1 #require serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo>foo
5 $ echo foo>foo
6 $ hg commit -Am 1 -d '1 0'
6 $ hg commit -Am 1 -d '1 0'
7 adding foo
7 adding foo
8 $ echo bar>bar
8 $ echo bar>bar
9 $ hg commit -Am 2 -d '2 0'
9 $ hg commit -Am 2 -d '2 0'
10 adding bar
10 adding bar
11 $ mkdir baz
11 $ mkdir baz
12 $ echo bletch>baz/bletch
12 $ echo bletch>baz/bletch
13 $ hg commit -Am 3 -d '1000000000 0'
13 $ hg commit -Am 3 -d '1000000000 0'
14 adding baz/bletch
14 adding baz/bletch
15 $ hg init subrepo
15 $ hg init subrepo
16 $ touch subrepo/sub
16 $ touch subrepo/sub
17 $ hg -q -R subrepo ci -Am "init subrepo"
17 $ hg -q -R subrepo ci -Am "init subrepo"
18 $ echo "subrepo = subrepo" > .hgsub
18 $ echo "subrepo = subrepo" > .hgsub
19 $ hg add .hgsub
19 $ hg add .hgsub
20 $ hg ci -m "add subrepo"
20 $ hg ci -m "add subrepo"
21
22 $ cat >> $HGRCPATH <<EOF
23 > [extensions]
24 > share =
25 > EOF
26
27 hg subrepos are shared when the parent repo is shared
28
29 $ cd ..
30 $ hg share test shared1
31 updating working directory
32 sharing subrepo subrepo from $TESTTMP/test/subrepo
33 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ cat shared1/subrepo/.hg/sharedpath
35 $TESTTMP/test/subrepo/.hg (no-eol) (glob)
36
37 hg subrepos are shared into existence on demand if the parent was shared
38
39 $ hg clone -qr 1 test clone1
40 $ hg share clone1 share2
41 updating working directory
42 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 $ hg -R clone1 -q pull
44 $ hg -R share2 update tip
45 sharing subrepo subrepo from $TESTTMP/test/subrepo
46 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 $ cat share2/subrepo/.hg/sharedpath
48 $TESTTMP/test/subrepo/.hg (no-eol) (glob)
49 $ echo 'mod' > share2/subrepo/sub
50 $ hg -R share2 ci -Sqm 'subrepo mod'
51 $ hg -R clone1 update -C tip
52 cloning subrepo subrepo from $TESTTMP/test/subrepo
53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 $ rm -rf clone1
55
56 $ hg clone -qr 1 test clone1
57 $ hg share clone1 shared3
58 updating working directory
59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 $ hg -R clone1 -q pull
61 $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
62 sharing subrepo subrepo from $TESTTMP/test/subrepo
63 $ cat shared3/subrepo/.hg/sharedpath
64 $TESTTMP/test/subrepo/.hg (no-eol) (glob)
65 $ diff -r archive test
66 Only in test: .hg
67 Only in test/subrepo: .hg
68 [1]
69 $ rm -rf archive
70
71 $ cd test
21 $ echo "[web]" >> .hg/hgrc
72 $ echo "[web]" >> .hg/hgrc
22 $ echo "name = test-archive" >> .hg/hgrc
73 $ echo "name = test-archive" >> .hg/hgrc
23 $ echo "archivesubrepos = True" >> .hg/hgrc
74 $ echo "archivesubrepos = True" >> .hg/hgrc
24 $ cp .hg/hgrc .hg/hgrc-base
75 $ cp .hg/hgrc .hg/hgrc-base
25 > test_archtype() {
76 > test_archtype() {
26 > echo "allow_archive = $1" >> .hg/hgrc
77 > echo "allow_archive = $1" >> .hg/hgrc
27 > test_archtype_run "$@"
78 > test_archtype_run "$@"
28 > }
79 > }
29 > test_archtype_deprecated() {
80 > test_archtype_deprecated() {
30 > echo "allow$1 = True" >> .hg/hgrc
81 > echo "allow$1 = True" >> .hg/hgrc
31 > test_archtype_run "$@"
82 > test_archtype_run "$@"
32 > }
83 > }
33 > test_archtype_run() {
84 > test_archtype_run() {
34 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
85 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
35 > --config extensions.blackbox= --config blackbox.track=develwarn
86 > --config extensions.blackbox= --config blackbox.track=develwarn
36 > cat hg.pid >> $DAEMON_PIDS
87 > cat hg.pid >> $DAEMON_PIDS
37 > echo % $1 allowed should give 200
88 > echo % $1 allowed should give 200
38 > get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
89 > get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
39 > echo % $3 and $4 disallowed should both give 403
90 > echo % $3 and $4 disallowed should both give 403
40 > get-with-headers.py localhost:$HGPORT "archive/tip.$3" | head -n 1
91 > get-with-headers.py localhost:$HGPORT "archive/tip.$3" | head -n 1
41 > get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
92 > get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
42 > killdaemons.py
93 > killdaemons.py
43 > cat errors.log
94 > cat errors.log
44 > hg blackbox --config extensions.blackbox= --config blackbox.track=
95 > hg blackbox --config extensions.blackbox= --config blackbox.track=
45 > cp .hg/hgrc-base .hg/hgrc
96 > cp .hg/hgrc-base .hg/hgrc
46 > }
97 > }
47
98
48 check http return codes
99 check http return codes
49
100
50 $ test_archtype gz tar.gz tar.bz2 zip
101 $ test_archtype gz tar.gz tar.bz2 zip
51 % gz allowed should give 200
102 % gz allowed should give 200
52 200 Script output follows
103 200 Script output follows
53 % tar.bz2 and zip disallowed should both give 403
104 % tar.bz2 and zip disallowed should both give 403
54 403 Archive type not allowed: bz2
105 403 Archive type not allowed: bz2
55 403 Archive type not allowed: zip
106 403 Archive type not allowed: zip
56 $ test_archtype bz2 tar.bz2 zip tar.gz
107 $ test_archtype bz2 tar.bz2 zip tar.gz
57 % bz2 allowed should give 200
108 % bz2 allowed should give 200
58 200 Script output follows
109 200 Script output follows
59 % zip and tar.gz disallowed should both give 403
110 % zip and tar.gz disallowed should both give 403
60 403 Archive type not allowed: zip
111 403 Archive type not allowed: zip
61 403 Archive type not allowed: gz
112 403 Archive type not allowed: gz
62 $ test_archtype zip zip tar.gz tar.bz2
113 $ test_archtype zip zip tar.gz tar.bz2
63 % zip allowed should give 200
114 % zip allowed should give 200
64 200 Script output follows
115 200 Script output follows
65 % tar.gz and tar.bz2 disallowed should both give 403
116 % tar.gz and tar.bz2 disallowed should both give 403
66 403 Archive type not allowed: gz
117 403 Archive type not allowed: gz
67 403 Archive type not allowed: bz2
118 403 Archive type not allowed: bz2
68
119
69 check http return codes (with deprecated option)
120 check http return codes (with deprecated option)
70
121
71 $ test_archtype_deprecated gz tar.gz tar.bz2 zip
122 $ test_archtype_deprecated gz tar.gz tar.bz2 zip
72 % gz allowed should give 200
123 % gz allowed should give 200
73 200 Script output follows
124 200 Script output follows
74 % tar.bz2 and zip disallowed should both give 403
125 % tar.bz2 and zip disallowed should both give 403
75 403 Archive type not allowed: bz2
126 403 Archive type not allowed: bz2
76 403 Archive type not allowed: zip
127 403 Archive type not allowed: zip
77 $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
128 $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
78 % bz2 allowed should give 200
129 % bz2 allowed should give 200
79 200 Script output follows
130 200 Script output follows
80 % zip and tar.gz disallowed should both give 403
131 % zip and tar.gz disallowed should both give 403
81 403 Archive type not allowed: zip
132 403 Archive type not allowed: zip
82 403 Archive type not allowed: gz
133 403 Archive type not allowed: gz
83 $ test_archtype_deprecated zip zip tar.gz tar.bz2
134 $ test_archtype_deprecated zip zip tar.gz tar.bz2
84 % zip allowed should give 200
135 % zip allowed should give 200
85 200 Script output follows
136 200 Script output follows
86 % tar.gz and tar.bz2 disallowed should both give 403
137 % tar.gz and tar.bz2 disallowed should both give 403
87 403 Archive type not allowed: gz
138 403 Archive type not allowed: gz
88 403 Archive type not allowed: bz2
139 403 Archive type not allowed: bz2
89
140
90 $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
141 $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
91 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
142 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
92 $ cat hg.pid >> $DAEMON_PIDS
143 $ cat hg.pid >> $DAEMON_PIDS
93
144
94 check archive links' order
145 check archive links' order
95
146
96 $ get-with-headers.py localhost:$HGPORT "?revcount=1" | grep '/archive/tip.'
147 $ get-with-headers.py localhost:$HGPORT "?revcount=1" | grep '/archive/tip.'
97 <a href="/archive/tip.zip">zip</a>
148 <a href="/archive/tip.zip">zip</a>
98 <a href="/archive/tip.tar.gz">gz</a>
149 <a href="/archive/tip.tar.gz">gz</a>
99 <a href="/archive/tip.tar.bz2">bz2</a>
150 <a href="/archive/tip.tar.bz2">bz2</a>
100
151
101 invalid arch type should give 404
152 invalid arch type should give 404
102
153
103 $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
154 $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
104 404 Unsupported archive type: None
155 404 Unsupported archive type: None
105
156
106 $ TIP=`hg id -v | cut -f1 -d' '`
157 $ TIP=`hg id -v | cut -f1 -d' '`
107 $ QTIP=`hg id -q`
158 $ QTIP=`hg id -q`
108 $ cat > getarchive.py <<EOF
159 $ cat > getarchive.py <<EOF
109 > from __future__ import absolute_import
160 > from __future__ import absolute_import
110 > import os
161 > import os
111 > import sys
162 > import sys
112 > from mercurial import (
163 > from mercurial import (
113 > util,
164 > util,
114 > )
165 > )
115 > try:
166 > try:
116 > # Set stdout to binary mode for win32 platforms
167 > # Set stdout to binary mode for win32 platforms
117 > import msvcrt
168 > import msvcrt
118 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
169 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
119 > except ImportError:
170 > except ImportError:
120 > pass
171 > pass
121 > if len(sys.argv) <= 3:
172 > if len(sys.argv) <= 3:
122 > node, archive = sys.argv[1:]
173 > node, archive = sys.argv[1:]
123 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
174 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
124 > else:
175 > else:
125 > node, archive, file = sys.argv[1:]
176 > node, archive, file = sys.argv[1:]
126 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
177 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
127 > try:
178 > try:
128 > stdout = sys.stdout.buffer
179 > stdout = sys.stdout.buffer
129 > except AttributeError:
180 > except AttributeError:
130 > stdout = sys.stdout
181 > stdout = sys.stdout
131 > try:
182 > try:
132 > f = util.urlreq.urlopen('http://$LOCALIP:%s/?%s'
183 > f = util.urlreq.urlopen('http://$LOCALIP:%s/?%s'
133 > % (os.environ['HGPORT'], requeststr))
184 > % (os.environ['HGPORT'], requeststr))
134 > stdout.write(f.read())
185 > stdout.write(f.read())
135 > except util.urlerr.httperror as e:
186 > except util.urlerr.httperror as e:
136 > sys.stderr.write(str(e) + '\n')
187 > sys.stderr.write(str(e) + '\n')
137 > EOF
188 > EOF
138 $ $PYTHON getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
189 $ $PYTHON getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
139 test-archive-1701ef1f1510/.hg_archival.txt
190 test-archive-1701ef1f1510/.hg_archival.txt
140 test-archive-1701ef1f1510/.hgsub
191 test-archive-1701ef1f1510/.hgsub
141 test-archive-1701ef1f1510/.hgsubstate
192 test-archive-1701ef1f1510/.hgsubstate
142 test-archive-1701ef1f1510/bar
193 test-archive-1701ef1f1510/bar
143 test-archive-1701ef1f1510/baz/bletch
194 test-archive-1701ef1f1510/baz/bletch
144 test-archive-1701ef1f1510/foo
195 test-archive-1701ef1f1510/foo
145 test-archive-1701ef1f1510/subrepo/sub
196 test-archive-1701ef1f1510/subrepo/sub
146 $ $PYTHON getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
197 $ $PYTHON getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
147 test-archive-1701ef1f1510/.hg_archival.txt
198 test-archive-1701ef1f1510/.hg_archival.txt
148 test-archive-1701ef1f1510/.hgsub
199 test-archive-1701ef1f1510/.hgsub
149 test-archive-1701ef1f1510/.hgsubstate
200 test-archive-1701ef1f1510/.hgsubstate
150 test-archive-1701ef1f1510/bar
201 test-archive-1701ef1f1510/bar
151 test-archive-1701ef1f1510/baz/bletch
202 test-archive-1701ef1f1510/baz/bletch
152 test-archive-1701ef1f1510/foo
203 test-archive-1701ef1f1510/foo
153 test-archive-1701ef1f1510/subrepo/sub
204 test-archive-1701ef1f1510/subrepo/sub
154 $ $PYTHON getarchive.py "$TIP" zip > archive.zip
205 $ $PYTHON getarchive.py "$TIP" zip > archive.zip
155 $ unzip -t archive.zip
206 $ unzip -t archive.zip
156 Archive: archive.zip
207 Archive: archive.zip
157 testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
208 testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
158 testing: test-archive-1701ef1f1510/.hgsub*OK (glob)
209 testing: test-archive-1701ef1f1510/.hgsub*OK (glob)
159 testing: test-archive-1701ef1f1510/.hgsubstate*OK (glob)
210 testing: test-archive-1701ef1f1510/.hgsubstate*OK (glob)
160 testing: test-archive-1701ef1f1510/bar*OK (glob)
211 testing: test-archive-1701ef1f1510/bar*OK (glob)
161 testing: test-archive-1701ef1f1510/baz/bletch*OK (glob)
212 testing: test-archive-1701ef1f1510/baz/bletch*OK (glob)
162 testing: test-archive-1701ef1f1510/foo*OK (glob)
213 testing: test-archive-1701ef1f1510/foo*OK (glob)
163 testing: test-archive-1701ef1f1510/subrepo/sub*OK (glob)
214 testing: test-archive-1701ef1f1510/subrepo/sub*OK (glob)
164 No errors detected in compressed data of archive.zip.
215 No errors detected in compressed data of archive.zip.
165
216
166 test that we can download single directories and files
217 test that we can download single directories and files
167
218
168 $ $PYTHON getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
219 $ $PYTHON getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
169 test-archive-1701ef1f1510/baz/bletch
220 test-archive-1701ef1f1510/baz/bletch
170 $ $PYTHON getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
221 $ $PYTHON getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
171 test-archive-1701ef1f1510/foo
222 test-archive-1701ef1f1510/foo
172
223
173 test that we detect file patterns that match no files
224 test that we detect file patterns that match no files
174
225
175 $ $PYTHON getarchive.py "$TIP" gz foobar
226 $ $PYTHON getarchive.py "$TIP" gz foobar
176 HTTP Error 404: file(s) not found: foobar
227 HTTP Error 404: file(s) not found: foobar
177
228
178 test that we reject unsafe patterns
229 test that we reject unsafe patterns
179
230
180 $ $PYTHON getarchive.py "$TIP" gz relre:baz
231 $ $PYTHON getarchive.py "$TIP" gz relre:baz
181 HTTP Error 404: file(s) not found: relre:baz
232 HTTP Error 404: file(s) not found: relre:baz
182
233
183 $ killdaemons.py
234 $ killdaemons.py
184
235
185 $ hg archive -t tar test.tar
236 $ hg archive -t tar test.tar
186 $ tar tf test.tar
237 $ tar tf test.tar
187 test/.hg_archival.txt
238 test/.hg_archival.txt
188 test/.hgsub
239 test/.hgsub
189 test/.hgsubstate
240 test/.hgsubstate
190 test/bar
241 test/bar
191 test/baz/bletch
242 test/baz/bletch
192 test/foo
243 test/foo
193
244
194 $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
245 $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
195 archiving: 0/4 files (0.00%)
246 archiving: 0/4 files (0.00%)
196 archiving: .hgsub 1/4 files (25.00%)
247 archiving: .hgsub 1/4 files (25.00%)
197 archiving: .hgsubstate 2/4 files (50.00%)
248 archiving: .hgsubstate 2/4 files (50.00%)
198 archiving: bar 3/4 files (75.00%)
249 archiving: bar 3/4 files (75.00%)
199 archiving: foo 4/4 files (100.00%)
250 archiving: foo 4/4 files (100.00%)
200 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
251 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
201 test/.hg_archival.txt
252 test/.hg_archival.txt
202 test/.hgsub
253 test/.hgsub
203 test/.hgsubstate
254 test/.hgsubstate
204 test/bar
255 test/bar
205 test/foo
256 test/foo
206
257
207 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
258 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
208 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
259 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
209 test-1701ef1f1510/.hg_archival.txt
260 test-1701ef1f1510/.hg_archival.txt
210 test-1701ef1f1510/.hgsub
261 test-1701ef1f1510/.hgsub
211 test-1701ef1f1510/.hgsubstate
262 test-1701ef1f1510/.hgsubstate
212 test-1701ef1f1510/bar
263 test-1701ef1f1510/bar
213 test-1701ef1f1510/baz/bletch
264 test-1701ef1f1510/baz/bletch
214 test-1701ef1f1510/foo
265 test-1701ef1f1510/foo
215
266
216 $ hg archive autodetected_test.tar
267 $ hg archive autodetected_test.tar
217 $ tar tf autodetected_test.tar
268 $ tar tf autodetected_test.tar
218 autodetected_test/.hg_archival.txt
269 autodetected_test/.hg_archival.txt
219 autodetected_test/.hgsub
270 autodetected_test/.hgsub
220 autodetected_test/.hgsubstate
271 autodetected_test/.hgsubstate
221 autodetected_test/bar
272 autodetected_test/bar
222 autodetected_test/baz/bletch
273 autodetected_test/baz/bletch
223 autodetected_test/foo
274 autodetected_test/foo
224
275
225 The '-t' should override autodetection
276 The '-t' should override autodetection
226
277
227 $ hg archive -t tar autodetect_override_test.zip
278 $ hg archive -t tar autodetect_override_test.zip
228 $ tar tf autodetect_override_test.zip
279 $ tar tf autodetect_override_test.zip
229 autodetect_override_test.zip/.hg_archival.txt
280 autodetect_override_test.zip/.hg_archival.txt
230 autodetect_override_test.zip/.hgsub
281 autodetect_override_test.zip/.hgsub
231 autodetect_override_test.zip/.hgsubstate
282 autodetect_override_test.zip/.hgsubstate
232 autodetect_override_test.zip/bar
283 autodetect_override_test.zip/bar
233 autodetect_override_test.zip/baz/bletch
284 autodetect_override_test.zip/baz/bletch
234 autodetect_override_test.zip/foo
285 autodetect_override_test.zip/foo
235
286
236 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
287 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
237 > hg archive auto_test.$ext
288 > hg archive auto_test.$ext
238 > if [ -d auto_test.$ext ]; then
289 > if [ -d auto_test.$ext ]; then
239 > echo "extension $ext was not autodetected."
290 > echo "extension $ext was not autodetected."
240 > fi
291 > fi
241 > done
292 > done
242
293
243 $ cat > md5comp.py <<EOF
294 $ cat > md5comp.py <<EOF
244 > from __future__ import absolute_import, print_function
295 > from __future__ import absolute_import, print_function
245 > import hashlib
296 > import hashlib
246 > import sys
297 > import sys
247 > f1, f2 = sys.argv[1:3]
298 > f1, f2 = sys.argv[1:3]
248 > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
299 > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
249 > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
300 > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
250 > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
301 > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
251 > EOF
302 > EOF
252
303
253 archive name is stored in the archive, so create similar archives and
304 archive name is stored in the archive, so create similar archives and
254 rename them afterwards.
305 rename them afterwards.
255
306
256 $ hg archive -t tgz tip.tar.gz
307 $ hg archive -t tgz tip.tar.gz
257 $ mv tip.tar.gz tip1.tar.gz
308 $ mv tip.tar.gz tip1.tar.gz
258 $ sleep 1
309 $ sleep 1
259 $ hg archive -t tgz tip.tar.gz
310 $ hg archive -t tgz tip.tar.gz
260 $ mv tip.tar.gz tip2.tar.gz
311 $ mv tip.tar.gz tip2.tar.gz
261 $ $PYTHON md5comp.py tip1.tar.gz tip2.tar.gz
312 $ $PYTHON md5comp.py tip1.tar.gz tip2.tar.gz
262 True
313 True
263
314
264 $ hg archive -t zip -p /illegal test.zip
315 $ hg archive -t zip -p /illegal test.zip
265 abort: archive prefix contains illegal components
316 abort: archive prefix contains illegal components
266 [255]
317 [255]
267 $ hg archive -t zip -p very/../bad test.zip
318 $ hg archive -t zip -p very/../bad test.zip
268
319
269 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
320 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
270 $ unzip -t test.zip
321 $ unzip -t test.zip
271 Archive: test.zip
322 Archive: test.zip
272 testing: test/bar*OK (glob)
323 testing: test/bar*OK (glob)
273 testing: test/baz/bletch*OK (glob)
324 testing: test/baz/bletch*OK (glob)
274 testing: test/foo*OK (glob)
325 testing: test/foo*OK (glob)
275 No errors detected in compressed data of test.zip.
326 No errors detected in compressed data of test.zip.
276
327
277 $ hg archive -t tar - | tar tf - 2>/dev/null
328 $ hg archive -t tar - | tar tf - 2>/dev/null
278 test-1701ef1f1510/.hg_archival.txt
329 test-1701ef1f1510/.hg_archival.txt
279 test-1701ef1f1510/.hgsub
330 test-1701ef1f1510/.hgsub
280 test-1701ef1f1510/.hgsubstate
331 test-1701ef1f1510/.hgsubstate
281 test-1701ef1f1510/bar
332 test-1701ef1f1510/bar
282 test-1701ef1f1510/baz/bletch
333 test-1701ef1f1510/baz/bletch
283 test-1701ef1f1510/foo
334 test-1701ef1f1510/foo
284
335
285 $ hg archive -r 0 -t tar rev-%r.tar
336 $ hg archive -r 0 -t tar rev-%r.tar
286 $ [ -f rev-0.tar ]
337 $ [ -f rev-0.tar ]
287
338
288 test .hg_archival.txt
339 test .hg_archival.txt
289
340
290 $ hg archive ../test-tags
341 $ hg archive ../test-tags
291 $ cat ../test-tags/.hg_archival.txt
342 $ cat ../test-tags/.hg_archival.txt
292 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
343 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
293 node: 1701ef1f151069b8747038e93b5186bb43a47504
344 node: 1701ef1f151069b8747038e93b5186bb43a47504
294 branch: default
345 branch: default
295 latesttag: null
346 latesttag: null
296 latesttagdistance: 4
347 latesttagdistance: 4
297 changessincelatesttag: 4
348 changessincelatesttag: 4
298 $ hg tag -r 2 mytag
349 $ hg tag -r 2 mytag
299 $ hg tag -r 2 anothertag
350 $ hg tag -r 2 anothertag
300 $ hg archive -r 2 ../test-lasttag
351 $ hg archive -r 2 ../test-lasttag
301 $ cat ../test-lasttag/.hg_archival.txt
352 $ cat ../test-lasttag/.hg_archival.txt
302 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
353 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
303 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
354 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
304 branch: default
355 branch: default
305 tag: anothertag
356 tag: anothertag
306 tag: mytag
357 tag: mytag
307
358
308 $ hg archive -t bogus test.bogus
359 $ hg archive -t bogus test.bogus
309 abort: unknown archive type 'bogus'
360 abort: unknown archive type 'bogus'
310 [255]
361 [255]
311
362
312 enable progress extension:
363 enable progress extension:
313
364
314 $ cp $HGRCPATH $HGRCPATH.no-progress
365 $ cp $HGRCPATH $HGRCPATH.no-progress
315 $ cat >> $HGRCPATH <<EOF
366 $ cat >> $HGRCPATH <<EOF
316 > [progress]
367 > [progress]
317 > assume-tty = 1
368 > assume-tty = 1
318 > format = topic bar number
369 > format = topic bar number
319 > delay = 0
370 > delay = 0
320 > refresh = 0
371 > refresh = 0
321 > width = 60
372 > width = 60
322 > EOF
373 > EOF
323
374
324 $ hg archive ../with-progress
375 $ hg archive ../with-progress
325 \r (no-eol) (esc)
376 \r (no-eol) (esc)
326 archiving [ ] 0/6\r (no-eol) (esc)
377 archiving [ ] 0/6\r (no-eol) (esc)
327 archiving [======> ] 1/6\r (no-eol) (esc)
378 archiving [======> ] 1/6\r (no-eol) (esc)
328 archiving [=============> ] 2/6\r (no-eol) (esc)
379 archiving [=============> ] 2/6\r (no-eol) (esc)
329 archiving [====================> ] 3/6\r (no-eol) (esc)
380 archiving [====================> ] 3/6\r (no-eol) (esc)
330 archiving [===========================> ] 4/6\r (no-eol) (esc)
381 archiving [===========================> ] 4/6\r (no-eol) (esc)
331 archiving [==================================> ] 5/6\r (no-eol) (esc)
382 archiving [==================================> ] 5/6\r (no-eol) (esc)
332 archiving [==========================================>] 6/6\r (no-eol) (esc)
383 archiving [==========================================>] 6/6\r (no-eol) (esc)
333 \r (no-eol) (esc)
384 \r (no-eol) (esc)
334
385
335 cleanup after progress extension test:
386 cleanup after progress extension test:
336
387
337 $ cp $HGRCPATH.no-progress $HGRCPATH
388 $ cp $HGRCPATH.no-progress $HGRCPATH
338
389
339 server errors
390 server errors
340
391
341 $ cat errors.log
392 $ cat errors.log
342
393
343 empty repo
394 empty repo
344
395
345 $ hg init ../empty
396 $ hg init ../empty
346 $ cd ../empty
397 $ cd ../empty
347 $ hg archive ../test-empty
398 $ hg archive ../test-empty
348 abort: no working directory: please specify a revision
399 abort: no working directory: please specify a revision
349 [255]
400 [255]
350
401
351 old file -- date clamped to 1980
402 old file -- date clamped to 1980
352
403
353 $ touch -t 197501010000 old
404 $ touch -t 197501010000 old
354 $ hg add old
405 $ hg add old
355 $ hg commit -m old
406 $ hg commit -m old
356 $ hg archive ../old.zip
407 $ hg archive ../old.zip
357 $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
408 $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
358 Archive: ../old.zip
409 Archive: ../old.zip
359 \s*Length.* (re)
410 \s*Length.* (re)
360 *172*80*00:00*old/.hg_archival.txt (glob)
411 *172*80*00:00*old/.hg_archival.txt (glob)
361 *0*80*00:00*old/old (glob)
412 *0*80*00:00*old/old (glob)
362
413
363 show an error when a provided pattern matches no files
414 show an error when a provided pattern matches no files
364
415
365 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
416 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
366 abort: no files match the archive pattern
417 abort: no files match the archive pattern
367 [255]
418 [255]
368
419
369 $ hg archive -X * ../empty.zip
420 $ hg archive -X * ../empty.zip
370 abort: no files match the archive pattern
421 abort: no files match the archive pattern
371 [255]
422 [255]
372
423
373 $ cd ..
424 $ cd ..
374
425
375 issue3600: check whether "hg archive" can create archive files which
426 issue3600: check whether "hg archive" can create archive files which
376 are extracted with expected timestamp, even though TZ is not
427 are extracted with expected timestamp, even though TZ is not
377 configured as GMT.
428 configured as GMT.
378
429
379 $ mkdir issue3600
430 $ mkdir issue3600
380 $ cd issue3600
431 $ cd issue3600
381
432
382 $ hg init repo
433 $ hg init repo
383 $ echo a > repo/a
434 $ echo a > repo/a
384 $ hg -R repo add repo/a
435 $ hg -R repo add repo/a
385 $ hg -R repo commit -m '#0' -d '456789012 21600'
436 $ hg -R repo commit -m '#0' -d '456789012 21600'
386 $ cat > show_mtime.py <<EOF
437 $ cat > show_mtime.py <<EOF
387 > from __future__ import absolute_import, print_function
438 > from __future__ import absolute_import, print_function
388 > import os
439 > import os
389 > import sys
440 > import sys
390 > print(int(os.stat(sys.argv[1]).st_mtime))
441 > print(int(os.stat(sys.argv[1]).st_mtime))
391 > EOF
442 > EOF
392
443
393 $ hg -R repo archive --prefix tar-extracted archive.tar
444 $ hg -R repo archive --prefix tar-extracted archive.tar
394 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
445 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
395 $ $PYTHON show_mtime.py tar-extracted/a
446 $ $PYTHON show_mtime.py tar-extracted/a
396 456789012
447 456789012
397
448
398 $ hg -R repo archive --prefix zip-extracted archive.zip
449 $ hg -R repo archive --prefix zip-extracted archive.zip
399 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
450 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
400 $ $PYTHON show_mtime.py zip-extracted/a
451 $ $PYTHON show_mtime.py zip-extracted/a
401 456789012
452 456789012
402
453
403 $ cd ..
454 $ cd ..
@@ -1,1871 +1,1893 b''
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2
2
3 $ echo "[ui]" >> $HGRCPATH
3 $ echo "[ui]" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5
5
6 $ hg init t
6 $ hg init t
7 $ cd t
7 $ cd t
8
8
9 first revision, no sub
9 first revision, no sub
10
10
11 $ echo a > a
11 $ echo a > a
12 $ hg ci -Am0
12 $ hg ci -Am0
13 adding a
13 adding a
14
14
15 add first sub
15 add first sub
16
16
17 $ echo s = s > .hgsub
17 $ echo s = s > .hgsub
18 $ hg add .hgsub
18 $ hg add .hgsub
19 $ hg init s
19 $ hg init s
20 $ echo a > s/a
20 $ echo a > s/a
21
21
22 Issue2232: committing a subrepo without .hgsub
22 Issue2232: committing a subrepo without .hgsub
23
23
24 $ hg ci -mbad s
24 $ hg ci -mbad s
25 abort: can't commit subrepos without .hgsub
25 abort: can't commit subrepos without .hgsub
26 [255]
26 [255]
27
27
28 $ hg -R s add s/a
28 $ hg -R s add s/a
29 $ hg files -S
29 $ hg files -S
30 .hgsub
30 .hgsub
31 a
31 a
32 s/a (glob)
32 s/a (glob)
33
33
34 $ hg -R s ci -Ams0
34 $ hg -R s ci -Ams0
35 $ hg sum
35 $ hg sum
36 parent: 0:f7b1eb17ad24 tip
36 parent: 0:f7b1eb17ad24 tip
37 0
37 0
38 branch: default
38 branch: default
39 commit: 1 added, 1 subrepos
39 commit: 1 added, 1 subrepos
40 update: (current)
40 update: (current)
41 phases: 1 draft
41 phases: 1 draft
42 $ hg ci -m1
42 $ hg ci -m1
43
43
44 test handling .hgsubstate "added" explicitly.
44 test handling .hgsubstate "added" explicitly.
45
45
46 $ hg parents --template '{node}\n{files}\n'
46 $ hg parents --template '{node}\n{files}\n'
47 7cf8cfea66e410e8e3336508dfeec07b3192de51
47 7cf8cfea66e410e8e3336508dfeec07b3192de51
48 .hgsub .hgsubstate
48 .hgsub .hgsubstate
49 $ hg rollback -q
49 $ hg rollback -q
50 $ hg add .hgsubstate
50 $ hg add .hgsubstate
51 $ hg ci -m1
51 $ hg ci -m1
52 $ hg parents --template '{node}\n{files}\n'
52 $ hg parents --template '{node}\n{files}\n'
53 7cf8cfea66e410e8e3336508dfeec07b3192de51
53 7cf8cfea66e410e8e3336508dfeec07b3192de51
54 .hgsub .hgsubstate
54 .hgsub .hgsubstate
55
55
56 Subrepopath which overlaps with filepath, does not change warnings in remove()
56 Subrepopath which overlaps with filepath, does not change warnings in remove()
57
57
58 $ mkdir snot
58 $ mkdir snot
59 $ touch snot/file
59 $ touch snot/file
60 $ hg remove -S snot/file
60 $ hg remove -S snot/file
61 not removing snot/file: file is untracked (glob)
61 not removing snot/file: file is untracked (glob)
62 [1]
62 [1]
63 $ hg cat snot/filenot
63 $ hg cat snot/filenot
64 snot/filenot: no such file in rev 7cf8cfea66e4 (glob)
64 snot/filenot: no such file in rev 7cf8cfea66e4 (glob)
65 [1]
65 [1]
66 $ rm -r snot
66 $ rm -r snot
67
67
68 Revert subrepo and test subrepo fileset keyword:
68 Revert subrepo and test subrepo fileset keyword:
69
69
70 $ echo b > s/a
70 $ echo b > s/a
71 $ hg revert --dry-run "set:subrepo('glob:s*')"
71 $ hg revert --dry-run "set:subrepo('glob:s*')"
72 reverting subrepo s
72 reverting subrepo s
73 reverting s/a (glob)
73 reverting s/a (glob)
74 $ cat s/a
74 $ cat s/a
75 b
75 b
76 $ hg revert "set:subrepo('glob:s*')"
76 $ hg revert "set:subrepo('glob:s*')"
77 reverting subrepo s
77 reverting subrepo s
78 reverting s/a (glob)
78 reverting s/a (glob)
79 $ cat s/a
79 $ cat s/a
80 a
80 a
81 $ rm s/a.orig
81 $ rm s/a.orig
82
82
83 Revert subrepo with no backup. The "reverting s/a" line is gone since
83 Revert subrepo with no backup. The "reverting s/a" line is gone since
84 we're really running 'hg update' in the subrepo:
84 we're really running 'hg update' in the subrepo:
85
85
86 $ echo b > s/a
86 $ echo b > s/a
87 $ hg revert --no-backup s
87 $ hg revert --no-backup s
88 reverting subrepo s
88 reverting subrepo s
89
89
90 Issue2022: update -C
90 Issue2022: update -C
91
91
92 $ echo b > s/a
92 $ echo b > s/a
93 $ hg sum
93 $ hg sum
94 parent: 1:7cf8cfea66e4 tip
94 parent: 1:7cf8cfea66e4 tip
95 1
95 1
96 branch: default
96 branch: default
97 commit: 1 subrepos
97 commit: 1 subrepos
98 update: (current)
98 update: (current)
99 phases: 2 draft
99 phases: 2 draft
100 $ hg co -C 1
100 $ hg co -C 1
101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 $ hg sum
102 $ hg sum
103 parent: 1:7cf8cfea66e4 tip
103 parent: 1:7cf8cfea66e4 tip
104 1
104 1
105 branch: default
105 branch: default
106 commit: (clean)
106 commit: (clean)
107 update: (current)
107 update: (current)
108 phases: 2 draft
108 phases: 2 draft
109
109
110 commands that require a clean repo should respect subrepos
110 commands that require a clean repo should respect subrepos
111
111
112 $ echo b >> s/a
112 $ echo b >> s/a
113 $ hg backout tip
113 $ hg backout tip
114 abort: uncommitted changes in subrepository "s"
114 abort: uncommitted changes in subrepository "s"
115 [255]
115 [255]
116 $ hg revert -C -R s s/a
116 $ hg revert -C -R s s/a
117
117
118 add sub sub
118 add sub sub
119
119
120 $ echo ss = ss > s/.hgsub
120 $ echo ss = ss > s/.hgsub
121 $ hg init s/ss
121 $ hg init s/ss
122 $ echo a > s/ss/a
122 $ echo a > s/ss/a
123 $ hg -R s add s/.hgsub
123 $ hg -R s add s/.hgsub
124 $ hg -R s/ss add s/ss/a
124 $ hg -R s/ss add s/ss/a
125 $ hg sum
125 $ hg sum
126 parent: 1:7cf8cfea66e4 tip
126 parent: 1:7cf8cfea66e4 tip
127 1
127 1
128 branch: default
128 branch: default
129 commit: 1 subrepos
129 commit: 1 subrepos
130 update: (current)
130 update: (current)
131 phases: 2 draft
131 phases: 2 draft
132 $ hg ci -m2
132 $ hg ci -m2
133 committing subrepository s
133 committing subrepository s
134 committing subrepository s/ss (glob)
134 committing subrepository s/ss (glob)
135 $ hg sum
135 $ hg sum
136 parent: 2:df30734270ae tip
136 parent: 2:df30734270ae tip
137 2
137 2
138 branch: default
138 branch: default
139 commit: (clean)
139 commit: (clean)
140 update: (current)
140 update: (current)
141 phases: 3 draft
141 phases: 3 draft
142
142
143 test handling .hgsubstate "modified" explicitly.
143 test handling .hgsubstate "modified" explicitly.
144
144
145 $ hg parents --template '{node}\n{files}\n'
145 $ hg parents --template '{node}\n{files}\n'
146 df30734270ae757feb35e643b7018e818e78a9aa
146 df30734270ae757feb35e643b7018e818e78a9aa
147 .hgsubstate
147 .hgsubstate
148 $ hg rollback -q
148 $ hg rollback -q
149 $ hg status -A .hgsubstate
149 $ hg status -A .hgsubstate
150 M .hgsubstate
150 M .hgsubstate
151 $ hg ci -m2
151 $ hg ci -m2
152 $ hg parents --template '{node}\n{files}\n'
152 $ hg parents --template '{node}\n{files}\n'
153 df30734270ae757feb35e643b7018e818e78a9aa
153 df30734270ae757feb35e643b7018e818e78a9aa
154 .hgsubstate
154 .hgsubstate
155
155
156 bump sub rev (and check it is ignored by ui.commitsubrepos)
156 bump sub rev (and check it is ignored by ui.commitsubrepos)
157
157
158 $ echo b > s/a
158 $ echo b > s/a
159 $ hg -R s ci -ms1
159 $ hg -R s ci -ms1
160 $ hg --config ui.commitsubrepos=no ci -m3
160 $ hg --config ui.commitsubrepos=no ci -m3
161
161
162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
163
163
164 $ echo c > s/a
164 $ echo c > s/a
165 $ hg --config ui.commitsubrepos=no ci -m4
165 $ hg --config ui.commitsubrepos=no ci -m4
166 abort: uncommitted changes in subrepository "s"
166 abort: uncommitted changes in subrepository "s"
167 (use --subrepos for recursive commit)
167 (use --subrepos for recursive commit)
168 [255]
168 [255]
169 $ hg id
169 $ hg id
170 f6affe3fbfaa+ tip
170 f6affe3fbfaa+ tip
171 $ hg -R s ci -mc
171 $ hg -R s ci -mc
172 $ hg id
172 $ hg id
173 f6affe3fbfaa+ tip
173 f6affe3fbfaa+ tip
174 $ echo d > s/a
174 $ echo d > s/a
175 $ hg ci -m4
175 $ hg ci -m4
176 committing subrepository s
176 committing subrepository s
177 $ hg tip -R s
177 $ hg tip -R s
178 changeset: 4:02dcf1d70411
178 changeset: 4:02dcf1d70411
179 tag: tip
179 tag: tip
180 user: test
180 user: test
181 date: Thu Jan 01 00:00:00 1970 +0000
181 date: Thu Jan 01 00:00:00 1970 +0000
182 summary: 4
182 summary: 4
183
183
184
184
185 check caching
185 check caching
186
186
187 $ hg co 0
187 $ hg co 0
188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
189 $ hg debugsub
189 $ hg debugsub
190
190
191 restore
191 restore
192
192
193 $ hg co
193 $ hg co
194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 $ hg debugsub
195 $ hg debugsub
196 path s
196 path s
197 source s
197 source s
198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
199
199
200 new branch for merge tests
200 new branch for merge tests
201
201
202 $ hg co 1
202 $ hg co 1
203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
204 $ echo t = t >> .hgsub
204 $ echo t = t >> .hgsub
205 $ hg init t
205 $ hg init t
206 $ echo t > t/t
206 $ echo t > t/t
207 $ hg -R t add t
207 $ hg -R t add t
208 adding t/t (glob)
208 adding t/t (glob)
209
209
210 5
210 5
211
211
212 $ hg ci -m5 # add sub
212 $ hg ci -m5 # add sub
213 committing subrepository t
213 committing subrepository t
214 created new head
214 created new head
215 $ echo t2 > t/t
215 $ echo t2 > t/t
216
216
217 6
217 6
218
218
219 $ hg st -R s
219 $ hg st -R s
220 $ hg ci -m6 # change sub
220 $ hg ci -m6 # change sub
221 committing subrepository t
221 committing subrepository t
222 $ hg debugsub
222 $ hg debugsub
223 path s
223 path s
224 source s
224 source s
225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
226 path t
226 path t
227 source t
227 source t
228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
229 $ echo t3 > t/t
229 $ echo t3 > t/t
230
230
231 7
231 7
232
232
233 $ hg ci -m7 # change sub again for conflict test
233 $ hg ci -m7 # change sub again for conflict test
234 committing subrepository t
234 committing subrepository t
235 $ hg rm .hgsub
235 $ hg rm .hgsub
236
236
237 8
237 8
238
238
239 $ hg ci -m8 # remove sub
239 $ hg ci -m8 # remove sub
240
240
241 test handling .hgsubstate "removed" explicitly.
241 test handling .hgsubstate "removed" explicitly.
242
242
243 $ hg parents --template '{node}\n{files}\n'
243 $ hg parents --template '{node}\n{files}\n'
244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
245 .hgsub .hgsubstate
245 .hgsub .hgsubstate
246 $ hg rollback -q
246 $ hg rollback -q
247 $ hg remove .hgsubstate
247 $ hg remove .hgsubstate
248 $ hg ci -m8
248 $ hg ci -m8
249 $ hg parents --template '{node}\n{files}\n'
249 $ hg parents --template '{node}\n{files}\n'
250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
251 .hgsub .hgsubstate
251 .hgsub .hgsubstate
252
252
253 merge tests
253 merge tests
254
254
255 $ hg co -C 3
255 $ hg co -C 3
256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 $ hg merge 5 # test adding
257 $ hg merge 5 # test adding
258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 (branch merge, don't forget to commit)
259 (branch merge, don't forget to commit)
260 $ hg debugsub
260 $ hg debugsub
261 path s
261 path s
262 source s
262 source s
263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
264 path t
264 path t
265 source t
265 source t
266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
267 $ hg ci -m9
267 $ hg ci -m9
268 created new head
268 created new head
269 $ hg merge 6 --debug # test change
269 $ hg merge 6 --debug # test change
270 searching for copies back to rev 2
270 searching for copies back to rev 2
271 resolving manifests
271 resolving manifests
272 branchmerge: True, force: False, partial: False
272 branchmerge: True, force: False, partial: False
273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
274 starting 4 threads for background file closing (?)
274 starting 4 threads for background file closing (?)
275 .hgsubstate: versions differ -> m (premerge)
275 .hgsubstate: versions differ -> m (premerge)
276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
278 getting subrepo t
278 getting subrepo t
279 resolving manifests
279 resolving manifests
280 branchmerge: False, force: False, partial: False
280 branchmerge: False, force: False, partial: False
281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
282 t: remote is newer -> g
282 t: remote is newer -> g
283 getting t
283 getting t
284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 (branch merge, don't forget to commit)
285 (branch merge, don't forget to commit)
286 $ hg debugsub
286 $ hg debugsub
287 path s
287 path s
288 source s
288 source s
289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
290 path t
290 path t
291 source t
291 source t
292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
293 $ echo conflict > t/t
293 $ echo conflict > t/t
294 $ hg ci -m10
294 $ hg ci -m10
295 committing subrepository t
295 committing subrepository t
296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
297 searching for copies back to rev 2
297 searching for copies back to rev 2
298 resolving manifests
298 resolving manifests
299 branchmerge: True, force: False, partial: False
299 branchmerge: True, force: False, partial: False
300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
301 starting 4 threads for background file closing (?)
301 starting 4 threads for background file closing (?)
302 .hgsubstate: versions differ -> m (premerge)
302 .hgsubstate: versions differ -> m (premerge)
303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
304 subrepo t: both sides changed
304 subrepo t: both sides changed
305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
306 starting 4 threads for background file closing (?)
306 starting 4 threads for background file closing (?)
307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
308 merging subrepository "t"
308 merging subrepository "t"
309 searching for copies back to rev 2
309 searching for copies back to rev 2
310 resolving manifests
310 resolving manifests
311 branchmerge: True, force: False, partial: False
311 branchmerge: True, force: False, partial: False
312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
313 preserving t for resolve of t
313 preserving t for resolve of t
314 starting 4 threads for background file closing (?)
314 starting 4 threads for background file closing (?)
315 t: versions differ -> m (premerge)
315 t: versions differ -> m (premerge)
316 picked tool ':merge' for t (binary False symlink False changedelete False)
316 picked tool ':merge' for t (binary False symlink False changedelete False)
317 merging t
317 merging t
318 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
318 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
319 t: versions differ -> m (merge)
319 t: versions differ -> m (merge)
320 picked tool ':merge' for t (binary False symlink False changedelete False)
320 picked tool ':merge' for t (binary False symlink False changedelete False)
321 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
321 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
322 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
322 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
323 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
323 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
324 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
324 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
325 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
325 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 (branch merge, don't forget to commit)
327 (branch merge, don't forget to commit)
328
328
329 should conflict
329 should conflict
330
330
331 $ cat t/t
331 $ cat t/t
332 <<<<<<< local: 20a0db6fbf6c - test: 10
332 <<<<<<< local: 20a0db6fbf6c - test: 10
333 conflict
333 conflict
334 =======
334 =======
335 t3
335 t3
336 >>>>>>> other: 7af322bc1198 - test: 7
336 >>>>>>> other: 7af322bc1198 - test: 7
337
337
338 11: remove subrepo t
338 11: remove subrepo t
339
339
340 $ hg co -C 5
340 $ hg co -C 5
341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 $ hg revert -r 4 .hgsub # remove t
342 $ hg revert -r 4 .hgsub # remove t
343 $ hg ci -m11
343 $ hg ci -m11
344 created new head
344 created new head
345 $ hg debugsub
345 $ hg debugsub
346 path s
346 path s
347 source s
347 source s
348 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
348 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
349
349
350 local removed, remote changed, keep changed
350 local removed, remote changed, keep changed
351
351
352 $ hg merge 6
352 $ hg merge 6
353 remote [merge rev] changed subrepository t which local [working copy] removed
353 remote [merge rev] changed subrepository t which local [working copy] removed
354 use (c)hanged version or (d)elete? c
354 use (c)hanged version or (d)elete? c
355 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
355 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 (branch merge, don't forget to commit)
356 (branch merge, don't forget to commit)
357 BROKEN: should include subrepo t
357 BROKEN: should include subrepo t
358 $ hg debugsub
358 $ hg debugsub
359 path s
359 path s
360 source s
360 source s
361 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
361 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
362 $ cat .hgsubstate
362 $ cat .hgsubstate
363 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
363 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
364 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
364 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
365 $ hg ci -m 'local removed, remote changed, keep changed'
365 $ hg ci -m 'local removed, remote changed, keep changed'
366 BROKEN: should include subrepo t
366 BROKEN: should include subrepo t
367 $ hg debugsub
367 $ hg debugsub
368 path s
368 path s
369 source s
369 source s
370 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
370 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
371 BROKEN: should include subrepo t
371 BROKEN: should include subrepo t
372 $ cat .hgsubstate
372 $ cat .hgsubstate
373 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
373 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
374 $ cat t/t
374 $ cat t/t
375 t2
375 t2
376
376
377 local removed, remote changed, keep removed
377 local removed, remote changed, keep removed
378
378
379 $ hg co -C 11
379 $ hg co -C 11
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 $ hg merge --config ui.interactive=true 6 <<EOF
381 $ hg merge --config ui.interactive=true 6 <<EOF
382 > d
382 > d
383 > EOF
383 > EOF
384 remote [merge rev] changed subrepository t which local [working copy] removed
384 remote [merge rev] changed subrepository t which local [working copy] removed
385 use (c)hanged version or (d)elete? d
385 use (c)hanged version or (d)elete? d
386 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 (branch merge, don't forget to commit)
387 (branch merge, don't forget to commit)
388 $ hg debugsub
388 $ hg debugsub
389 path s
389 path s
390 source s
390 source s
391 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
391 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
392 $ cat .hgsubstate
392 $ cat .hgsubstate
393 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
393 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
394 $ hg ci -m 'local removed, remote changed, keep removed'
394 $ hg ci -m 'local removed, remote changed, keep removed'
395 created new head
395 created new head
396 $ hg debugsub
396 $ hg debugsub
397 path s
397 path s
398 source s
398 source s
399 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
399 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
400 $ cat .hgsubstate
400 $ cat .hgsubstate
401 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
401 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
402
402
403 local changed, remote removed, keep changed
403 local changed, remote removed, keep changed
404
404
405 $ hg co -C 6
405 $ hg co -C 6
406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 $ hg merge 11
407 $ hg merge 11
408 local [working copy] changed subrepository t which remote [merge rev] removed
408 local [working copy] changed subrepository t which remote [merge rev] removed
409 use (c)hanged version or (d)elete? c
409 use (c)hanged version or (d)elete? c
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 (branch merge, don't forget to commit)
411 (branch merge, don't forget to commit)
412 BROKEN: should include subrepo t
412 BROKEN: should include subrepo t
413 $ hg debugsub
413 $ hg debugsub
414 path s
414 path s
415 source s
415 source s
416 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
416 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
417 BROKEN: should include subrepo t
417 BROKEN: should include subrepo t
418 $ cat .hgsubstate
418 $ cat .hgsubstate
419 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
419 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
420 $ hg ci -m 'local changed, remote removed, keep changed'
420 $ hg ci -m 'local changed, remote removed, keep changed'
421 created new head
421 created new head
422 BROKEN: should include subrepo t
422 BROKEN: should include subrepo t
423 $ hg debugsub
423 $ hg debugsub
424 path s
424 path s
425 source s
425 source s
426 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
426 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
427 BROKEN: should include subrepo t
427 BROKEN: should include subrepo t
428 $ cat .hgsubstate
428 $ cat .hgsubstate
429 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
429 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
430 $ cat t/t
430 $ cat t/t
431 t2
431 t2
432
432
433 local changed, remote removed, keep removed
433 local changed, remote removed, keep removed
434
434
435 $ hg co -C 6
435 $ hg co -C 6
436 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 $ hg merge --config ui.interactive=true 11 <<EOF
437 $ hg merge --config ui.interactive=true 11 <<EOF
438 > d
438 > d
439 > EOF
439 > EOF
440 local [working copy] changed subrepository t which remote [merge rev] removed
440 local [working copy] changed subrepository t which remote [merge rev] removed
441 use (c)hanged version or (d)elete? d
441 use (c)hanged version or (d)elete? d
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 (branch merge, don't forget to commit)
443 (branch merge, don't forget to commit)
444 $ hg debugsub
444 $ hg debugsub
445 path s
445 path s
446 source s
446 source s
447 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
447 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
448 $ cat .hgsubstate
448 $ cat .hgsubstate
449 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
449 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
450 $ hg ci -m 'local changed, remote removed, keep removed'
450 $ hg ci -m 'local changed, remote removed, keep removed'
451 created new head
451 created new head
452 $ hg debugsub
452 $ hg debugsub
453 path s
453 path s
454 source s
454 source s
455 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
455 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
456 $ cat .hgsubstate
456 $ cat .hgsubstate
457 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
457 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
458
458
459 clean up to avoid having to fix up the tests below
459 clean up to avoid having to fix up the tests below
460
460
461 $ hg co -C 10
461 $ hg co -C 10
462 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 $ cat >> $HGRCPATH <<EOF
463 $ cat >> $HGRCPATH <<EOF
464 > [extensions]
464 > [extensions]
465 > strip=
465 > strip=
466 > EOF
466 > EOF
467 $ hg strip -r 11:15
467 $ hg strip -r 11:15
468 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
468 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
469
469
470 clone
470 clone
471
471
472 $ cd ..
472 $ cd ..
473 $ hg clone t tc
473 $ hg clone t tc
474 updating to branch default
474 updating to branch default
475 cloning subrepo s from $TESTTMP/t/s
475 cloning subrepo s from $TESTTMP/t/s
476 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
476 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
477 cloning subrepo t from $TESTTMP/t/t
477 cloning subrepo t from $TESTTMP/t/t
478 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
478 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 $ cd tc
479 $ cd tc
480 $ hg debugsub
480 $ hg debugsub
481 path s
481 path s
482 source s
482 source s
483 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
483 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
484 path t
484 path t
485 source t
485 source t
486 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
486 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
487
487
488 push
488 push
489
489
490 $ echo bah > t/t
490 $ echo bah > t/t
491 $ hg ci -m11
491 $ hg ci -m11
492 committing subrepository t
492 committing subrepository t
493 $ hg push
493 $ hg push
494 pushing to $TESTTMP/t (glob)
494 pushing to $TESTTMP/t (glob)
495 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
495 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
496 no changes made to subrepo s since last push to $TESTTMP/t/s
496 no changes made to subrepo s since last push to $TESTTMP/t/s
497 pushing subrepo t to $TESTTMP/t/t
497 pushing subrepo t to $TESTTMP/t/t
498 searching for changes
498 searching for changes
499 adding changesets
499 adding changesets
500 adding manifests
500 adding manifests
501 adding file changes
501 adding file changes
502 added 1 changesets with 1 changes to 1 files
502 added 1 changesets with 1 changes to 1 files
503 searching for changes
503 searching for changes
504 adding changesets
504 adding changesets
505 adding manifests
505 adding manifests
506 adding file changes
506 adding file changes
507 added 1 changesets with 1 changes to 1 files
507 added 1 changesets with 1 changes to 1 files
508
508
509 push -f
509 push -f
510
510
511 $ echo bah > s/a
511 $ echo bah > s/a
512 $ hg ci -m12
512 $ hg ci -m12
513 committing subrepository s
513 committing subrepository s
514 $ hg push
514 $ hg push
515 pushing to $TESTTMP/t (glob)
515 pushing to $TESTTMP/t (glob)
516 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
516 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
517 pushing subrepo s to $TESTTMP/t/s
517 pushing subrepo s to $TESTTMP/t/s
518 searching for changes
518 searching for changes
519 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
519 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
520 (merge or see 'hg help push' for details about pushing new heads)
520 (merge or see 'hg help push' for details about pushing new heads)
521 [255]
521 [255]
522 $ hg push -f
522 $ hg push -f
523 pushing to $TESTTMP/t (glob)
523 pushing to $TESTTMP/t (glob)
524 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
524 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
525 searching for changes
525 searching for changes
526 no changes found
526 no changes found
527 pushing subrepo s to $TESTTMP/t/s
527 pushing subrepo s to $TESTTMP/t/s
528 searching for changes
528 searching for changes
529 adding changesets
529 adding changesets
530 adding manifests
530 adding manifests
531 adding file changes
531 adding file changes
532 added 1 changesets with 1 changes to 1 files (+1 heads)
532 added 1 changesets with 1 changes to 1 files (+1 heads)
533 pushing subrepo t to $TESTTMP/t/t
533 pushing subrepo t to $TESTTMP/t/t
534 searching for changes
534 searching for changes
535 no changes found
535 no changes found
536 searching for changes
536 searching for changes
537 adding changesets
537 adding changesets
538 adding manifests
538 adding manifests
539 adding file changes
539 adding file changes
540 added 1 changesets with 1 changes to 1 files
540 added 1 changesets with 1 changes to 1 files
541
541
542 check that unmodified subrepos are not pushed
542 check that unmodified subrepos are not pushed
543
543
544 $ hg clone . ../tcc
544 $ hg clone . ../tcc
545 updating to branch default
545 updating to branch default
546 cloning subrepo s from $TESTTMP/tc/s
546 cloning subrepo s from $TESTTMP/tc/s
547 cloning subrepo s/ss from $TESTTMP/tc/s/ss (glob)
547 cloning subrepo s/ss from $TESTTMP/tc/s/ss (glob)
548 cloning subrepo t from $TESTTMP/tc/t
548 cloning subrepo t from $TESTTMP/tc/t
549 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
549 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
550
550
551 the subrepos on the new clone have nothing to push to its source
551 the subrepos on the new clone have nothing to push to its source
552
552
553 $ hg push -R ../tcc .
553 $ hg push -R ../tcc .
554 pushing to .
554 pushing to .
555 no changes made to subrepo s/ss since last push to s/ss (glob)
555 no changes made to subrepo s/ss since last push to s/ss (glob)
556 no changes made to subrepo s since last push to s
556 no changes made to subrepo s since last push to s
557 no changes made to subrepo t since last push to t
557 no changes made to subrepo t since last push to t
558 searching for changes
558 searching for changes
559 no changes found
559 no changes found
560 [1]
560 [1]
561
561
562 the subrepos on the source do not have a clean store versus the clone target
562 the subrepos on the source do not have a clean store versus the clone target
563 because they were never explicitly pushed to the source
563 because they were never explicitly pushed to the source
564
564
565 $ hg push ../tcc
565 $ hg push ../tcc
566 pushing to ../tcc
566 pushing to ../tcc
567 pushing subrepo s/ss to ../tcc/s/ss (glob)
567 pushing subrepo s/ss to ../tcc/s/ss (glob)
568 searching for changes
568 searching for changes
569 no changes found
569 no changes found
570 pushing subrepo s to ../tcc/s
570 pushing subrepo s to ../tcc/s
571 searching for changes
571 searching for changes
572 no changes found
572 no changes found
573 pushing subrepo t to ../tcc/t
573 pushing subrepo t to ../tcc/t
574 searching for changes
574 searching for changes
575 no changes found
575 no changes found
576 searching for changes
576 searching for changes
577 no changes found
577 no changes found
578 [1]
578 [1]
579
579
580 after push their stores become clean
580 after push their stores become clean
581
581
582 $ hg push ../tcc
582 $ hg push ../tcc
583 pushing to ../tcc
583 pushing to ../tcc
584 no changes made to subrepo s/ss since last push to ../tcc/s/ss (glob)
584 no changes made to subrepo s/ss since last push to ../tcc/s/ss (glob)
585 no changes made to subrepo s since last push to ../tcc/s
585 no changes made to subrepo s since last push to ../tcc/s
586 no changes made to subrepo t since last push to ../tcc/t
586 no changes made to subrepo t since last push to ../tcc/t
587 searching for changes
587 searching for changes
588 no changes found
588 no changes found
589 [1]
589 [1]
590
590
591 updating a subrepo to a different revision or changing
591 updating a subrepo to a different revision or changing
592 its working directory does not make its store dirty
592 its working directory does not make its store dirty
593
593
594 $ hg -R s update '.^'
594 $ hg -R s update '.^'
595 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
595 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
596 $ hg push
596 $ hg push
597 pushing to $TESTTMP/t (glob)
597 pushing to $TESTTMP/t (glob)
598 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
598 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
599 no changes made to subrepo s since last push to $TESTTMP/t/s
599 no changes made to subrepo s since last push to $TESTTMP/t/s
600 no changes made to subrepo t since last push to $TESTTMP/t/t
600 no changes made to subrepo t since last push to $TESTTMP/t/t
601 searching for changes
601 searching for changes
602 no changes found
602 no changes found
603 [1]
603 [1]
604 $ echo foo >> s/a
604 $ echo foo >> s/a
605 $ hg push
605 $ hg push
606 pushing to $TESTTMP/t (glob)
606 pushing to $TESTTMP/t (glob)
607 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
607 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
608 no changes made to subrepo s since last push to $TESTTMP/t/s
608 no changes made to subrepo s since last push to $TESTTMP/t/s
609 no changes made to subrepo t since last push to $TESTTMP/t/t
609 no changes made to subrepo t since last push to $TESTTMP/t/t
610 searching for changes
610 searching for changes
611 no changes found
611 no changes found
612 [1]
612 [1]
613 $ hg -R s update -C tip
613 $ hg -R s update -C tip
614 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
614 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
615
615
616 committing into a subrepo makes its store (but not its parent's store) dirty
616 committing into a subrepo makes its store (but not its parent's store) dirty
617
617
618 $ echo foo >> s/ss/a
618 $ echo foo >> s/ss/a
619 $ hg -R s/ss commit -m 'test dirty store detection'
619 $ hg -R s/ss commit -m 'test dirty store detection'
620
620
621 $ hg out -S -r `hg log -r tip -T "{node|short}"`
621 $ hg out -S -r `hg log -r tip -T "{node|short}"`
622 comparing with $TESTTMP/t (glob)
622 comparing with $TESTTMP/t (glob)
623 searching for changes
623 searching for changes
624 no changes found
624 no changes found
625 comparing with $TESTTMP/t/s
625 comparing with $TESTTMP/t/s
626 searching for changes
626 searching for changes
627 no changes found
627 no changes found
628 comparing with $TESTTMP/t/s/ss
628 comparing with $TESTTMP/t/s/ss
629 searching for changes
629 searching for changes
630 changeset: 1:79ea5566a333
630 changeset: 1:79ea5566a333
631 tag: tip
631 tag: tip
632 user: test
632 user: test
633 date: Thu Jan 01 00:00:00 1970 +0000
633 date: Thu Jan 01 00:00:00 1970 +0000
634 summary: test dirty store detection
634 summary: test dirty store detection
635
635
636 comparing with $TESTTMP/t/t
636 comparing with $TESTTMP/t/t
637 searching for changes
637 searching for changes
638 no changes found
638 no changes found
639
639
640 $ hg push
640 $ hg push
641 pushing to $TESTTMP/t (glob)
641 pushing to $TESTTMP/t (glob)
642 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
642 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
643 searching for changes
643 searching for changes
644 adding changesets
644 adding changesets
645 adding manifests
645 adding manifests
646 adding file changes
646 adding file changes
647 added 1 changesets with 1 changes to 1 files
647 added 1 changesets with 1 changes to 1 files
648 no changes made to subrepo s since last push to $TESTTMP/t/s
648 no changes made to subrepo s since last push to $TESTTMP/t/s
649 no changes made to subrepo t since last push to $TESTTMP/t/t
649 no changes made to subrepo t since last push to $TESTTMP/t/t
650 searching for changes
650 searching for changes
651 no changes found
651 no changes found
652 [1]
652 [1]
653
653
654 a subrepo store may be clean versus one repo but not versus another
654 a subrepo store may be clean versus one repo but not versus another
655
655
656 $ hg push
656 $ hg push
657 pushing to $TESTTMP/t (glob)
657 pushing to $TESTTMP/t (glob)
658 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
658 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
659 no changes made to subrepo s since last push to $TESTTMP/t/s
659 no changes made to subrepo s since last push to $TESTTMP/t/s
660 no changes made to subrepo t since last push to $TESTTMP/t/t
660 no changes made to subrepo t since last push to $TESTTMP/t/t
661 searching for changes
661 searching for changes
662 no changes found
662 no changes found
663 [1]
663 [1]
664 $ hg push ../tcc
664 $ hg push ../tcc
665 pushing to ../tcc
665 pushing to ../tcc
666 pushing subrepo s/ss to ../tcc/s/ss (glob)
666 pushing subrepo s/ss to ../tcc/s/ss (glob)
667 searching for changes
667 searching for changes
668 adding changesets
668 adding changesets
669 adding manifests
669 adding manifests
670 adding file changes
670 adding file changes
671 added 1 changesets with 1 changes to 1 files
671 added 1 changesets with 1 changes to 1 files
672 no changes made to subrepo s since last push to ../tcc/s
672 no changes made to subrepo s since last push to ../tcc/s
673 no changes made to subrepo t since last push to ../tcc/t
673 no changes made to subrepo t since last push to ../tcc/t
674 searching for changes
674 searching for changes
675 no changes found
675 no changes found
676 [1]
676 [1]
677
677
678 update
678 update
679
679
680 $ cd ../t
680 $ cd ../t
681 $ hg up -C # discard our earlier merge
681 $ hg up -C # discard our earlier merge
682 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
682 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
683 updated to "c373c8102e68: 12"
683 updated to "c373c8102e68: 12"
684 2 other heads for branch "default"
684 2 other heads for branch "default"
685 $ echo blah > t/t
685 $ echo blah > t/t
686 $ hg ci -m13
686 $ hg ci -m13
687 committing subrepository t
687 committing subrepository t
688
688
689 backout calls revert internally with minimal opts, which should not raise
689 backout calls revert internally with minimal opts, which should not raise
690 KeyError
690 KeyError
691
691
692 $ hg backout ".^" --no-commit
692 $ hg backout ".^" --no-commit
693 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
693 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
694 changeset c373c8102e68 backed out, don't forget to commit.
694 changeset c373c8102e68 backed out, don't forget to commit.
695
695
696 $ hg up -C # discard changes
696 $ hg up -C # discard changes
697 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
697 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
698 updated to "925c17564ef8: 13"
698 updated to "925c17564ef8: 13"
699 2 other heads for branch "default"
699 2 other heads for branch "default"
700
700
701 pull
701 pull
702
702
703 $ cd ../tc
703 $ cd ../tc
704 $ hg pull
704 $ hg pull
705 pulling from $TESTTMP/t (glob)
705 pulling from $TESTTMP/t (glob)
706 searching for changes
706 searching for changes
707 adding changesets
707 adding changesets
708 adding manifests
708 adding manifests
709 adding file changes
709 adding file changes
710 added 1 changesets with 1 changes to 1 files
710 added 1 changesets with 1 changes to 1 files
711 new changesets 925c17564ef8
711 new changesets 925c17564ef8
712 (run 'hg update' to get a working copy)
712 (run 'hg update' to get a working copy)
713
713
714 should pull t
714 should pull t
715
715
716 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
716 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
717 comparing with $TESTTMP/t (glob)
717 comparing with $TESTTMP/t (glob)
718 no changes found
718 no changes found
719 comparing with $TESTTMP/t/s
719 comparing with $TESTTMP/t/s
720 searching for changes
720 searching for changes
721 no changes found
721 no changes found
722 comparing with $TESTTMP/t/s/ss
722 comparing with $TESTTMP/t/s/ss
723 searching for changes
723 searching for changes
724 no changes found
724 no changes found
725 comparing with $TESTTMP/t/t
725 comparing with $TESTTMP/t/t
726 searching for changes
726 searching for changes
727 changeset: 5:52c0adc0515a
727 changeset: 5:52c0adc0515a
728 tag: tip
728 tag: tip
729 user: test
729 user: test
730 date: Thu Jan 01 00:00:00 1970 +0000
730 date: Thu Jan 01 00:00:00 1970 +0000
731 summary: 13
731 summary: 13
732
732
733
733
734 $ hg up
734 $ hg up
735 pulling subrepo t from $TESTTMP/t/t
735 pulling subrepo t from $TESTTMP/t/t
736 searching for changes
736 searching for changes
737 adding changesets
737 adding changesets
738 adding manifests
738 adding manifests
739 adding file changes
739 adding file changes
740 added 1 changesets with 1 changes to 1 files
740 added 1 changesets with 1 changes to 1 files
741 new changesets 52c0adc0515a
741 new changesets 52c0adc0515a
742 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
742 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
743 updated to "925c17564ef8: 13"
743 updated to "925c17564ef8: 13"
744 2 other heads for branch "default"
744 2 other heads for branch "default"
745 $ cat t/t
745 $ cat t/t
746 blah
746 blah
747
747
748 bogus subrepo path aborts
748 bogus subrepo path aborts
749
749
750 $ echo 'bogus=[boguspath' >> .hgsub
750 $ echo 'bogus=[boguspath' >> .hgsub
751 $ hg ci -m 'bogus subrepo path'
751 $ hg ci -m 'bogus subrepo path'
752 abort: missing ] in subrepository source
752 abort: missing ] in subrepository source
753 [255]
753 [255]
754
754
755 Issue1986: merge aborts when trying to merge a subrepo that
755 Issue1986: merge aborts when trying to merge a subrepo that
756 shouldn't need merging
756 shouldn't need merging
757
757
758 # subrepo layout
758 # subrepo layout
759 #
759 #
760 # o 5 br
760 # o 5 br
761 # /|
761 # /|
762 # o | 4 default
762 # o | 4 default
763 # | |
763 # | |
764 # | o 3 br
764 # | o 3 br
765 # |/|
765 # |/|
766 # o | 2 default
766 # o | 2 default
767 # | |
767 # | |
768 # | o 1 br
768 # | o 1 br
769 # |/
769 # |/
770 # o 0 default
770 # o 0 default
771
771
772 $ cd ..
772 $ cd ..
773 $ rm -rf sub
773 $ rm -rf sub
774 $ hg init main
774 $ hg init main
775 $ cd main
775 $ cd main
776 $ hg init s
776 $ hg init s
777 $ cd s
777 $ cd s
778 $ echo a > a
778 $ echo a > a
779 $ hg ci -Am1
779 $ hg ci -Am1
780 adding a
780 adding a
781 $ hg branch br
781 $ hg branch br
782 marked working directory as branch br
782 marked working directory as branch br
783 (branches are permanent and global, did you want a bookmark?)
783 (branches are permanent and global, did you want a bookmark?)
784 $ echo a >> a
784 $ echo a >> a
785 $ hg ci -m1
785 $ hg ci -m1
786 $ hg up default
786 $ hg up default
787 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
787 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
788 $ echo b > b
788 $ echo b > b
789 $ hg ci -Am1
789 $ hg ci -Am1
790 adding b
790 adding b
791 $ hg up br
791 $ hg up br
792 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
792 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
793 $ hg merge tip
793 $ hg merge tip
794 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
794 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
795 (branch merge, don't forget to commit)
795 (branch merge, don't forget to commit)
796 $ hg ci -m1
796 $ hg ci -m1
797 $ hg up 2
797 $ hg up 2
798 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
798 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
799 $ echo c > c
799 $ echo c > c
800 $ hg ci -Am1
800 $ hg ci -Am1
801 adding c
801 adding c
802 $ hg up 3
802 $ hg up 3
803 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
803 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
804 $ hg merge 4
804 $ hg merge 4
805 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
805 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
806 (branch merge, don't forget to commit)
806 (branch merge, don't forget to commit)
807 $ hg ci -m1
807 $ hg ci -m1
808
808
809 # main repo layout:
809 # main repo layout:
810 #
810 #
811 # * <-- try to merge default into br again
811 # * <-- try to merge default into br again
812 # .`|
812 # .`|
813 # . o 5 br --> substate = 5
813 # . o 5 br --> substate = 5
814 # . |
814 # . |
815 # o | 4 default --> substate = 4
815 # o | 4 default --> substate = 4
816 # | |
816 # | |
817 # | o 3 br --> substate = 2
817 # | o 3 br --> substate = 2
818 # |/|
818 # |/|
819 # o | 2 default --> substate = 2
819 # o | 2 default --> substate = 2
820 # | |
820 # | |
821 # | o 1 br --> substate = 3
821 # | o 1 br --> substate = 3
822 # |/
822 # |/
823 # o 0 default --> substate = 2
823 # o 0 default --> substate = 2
824
824
825 $ cd ..
825 $ cd ..
826 $ echo 's = s' > .hgsub
826 $ echo 's = s' > .hgsub
827 $ hg -R s up 2
827 $ hg -R s up 2
828 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
828 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
829 $ hg ci -Am1
829 $ hg ci -Am1
830 adding .hgsub
830 adding .hgsub
831 $ hg branch br
831 $ hg branch br
832 marked working directory as branch br
832 marked working directory as branch br
833 (branches are permanent and global, did you want a bookmark?)
833 (branches are permanent and global, did you want a bookmark?)
834 $ echo b > b
834 $ echo b > b
835 $ hg -R s up 3
835 $ hg -R s up 3
836 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
836 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
837 $ hg ci -Am1
837 $ hg ci -Am1
838 adding b
838 adding b
839 $ hg up default
839 $ hg up default
840 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
840 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
841 $ echo c > c
841 $ echo c > c
842 $ hg ci -Am1
842 $ hg ci -Am1
843 adding c
843 adding c
844 $ hg up 1
844 $ hg up 1
845 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
845 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
846 $ hg merge 2
846 $ hg merge 2
847 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
847 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
848 (branch merge, don't forget to commit)
848 (branch merge, don't forget to commit)
849 $ hg ci -m1
849 $ hg ci -m1
850 $ hg up 2
850 $ hg up 2
851 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
851 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
852 $ hg -R s up 4
852 $ hg -R s up 4
853 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
853 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
854 $ echo d > d
854 $ echo d > d
855 $ hg ci -Am1
855 $ hg ci -Am1
856 adding d
856 adding d
857 $ hg up 3
857 $ hg up 3
858 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
858 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
859 $ hg -R s up 5
859 $ hg -R s up 5
860 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
860 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
861 $ echo e > e
861 $ echo e > e
862 $ hg ci -Am1
862 $ hg ci -Am1
863 adding e
863 adding e
864
864
865 $ hg up 5
865 $ hg up 5
866 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
866 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
867 $ hg merge 4 # try to merge default into br again
867 $ hg merge 4 # try to merge default into br again
868 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
868 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
869 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
869 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
870 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
870 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
871 (branch merge, don't forget to commit)
871 (branch merge, don't forget to commit)
872 $ cd ..
872 $ cd ..
873
873
874 test subrepo delete from .hgsubstate
874 test subrepo delete from .hgsubstate
875
875
876 $ hg init testdelete
876 $ hg init testdelete
877 $ mkdir testdelete/nested testdelete/nested2
877 $ mkdir testdelete/nested testdelete/nested2
878 $ hg init testdelete/nested
878 $ hg init testdelete/nested
879 $ hg init testdelete/nested2
879 $ hg init testdelete/nested2
880 $ echo test > testdelete/nested/foo
880 $ echo test > testdelete/nested/foo
881 $ echo test > testdelete/nested2/foo
881 $ echo test > testdelete/nested2/foo
882 $ hg -R testdelete/nested add
882 $ hg -R testdelete/nested add
883 adding testdelete/nested/foo (glob)
883 adding testdelete/nested/foo (glob)
884 $ hg -R testdelete/nested2 add
884 $ hg -R testdelete/nested2 add
885 adding testdelete/nested2/foo (glob)
885 adding testdelete/nested2/foo (glob)
886 $ hg -R testdelete/nested ci -m test
886 $ hg -R testdelete/nested ci -m test
887 $ hg -R testdelete/nested2 ci -m test
887 $ hg -R testdelete/nested2 ci -m test
888 $ echo nested = nested > testdelete/.hgsub
888 $ echo nested = nested > testdelete/.hgsub
889 $ echo nested2 = nested2 >> testdelete/.hgsub
889 $ echo nested2 = nested2 >> testdelete/.hgsub
890 $ hg -R testdelete add
890 $ hg -R testdelete add
891 adding testdelete/.hgsub (glob)
891 adding testdelete/.hgsub (glob)
892 $ hg -R testdelete ci -m "nested 1 & 2 added"
892 $ hg -R testdelete ci -m "nested 1 & 2 added"
893 $ echo nested = nested > testdelete/.hgsub
893 $ echo nested = nested > testdelete/.hgsub
894 $ hg -R testdelete ci -m "nested 2 deleted"
894 $ hg -R testdelete ci -m "nested 2 deleted"
895 $ cat testdelete/.hgsubstate
895 $ cat testdelete/.hgsubstate
896 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
896 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
897 $ hg -R testdelete remove testdelete/.hgsub
897 $ hg -R testdelete remove testdelete/.hgsub
898 $ hg -R testdelete ci -m ".hgsub deleted"
898 $ hg -R testdelete ci -m ".hgsub deleted"
899 $ cat testdelete/.hgsubstate
899 $ cat testdelete/.hgsubstate
900 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
900 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
901
901
902 test repository cloning
902 test repository cloning
903
903
904 $ mkdir mercurial mercurial2
904 $ mkdir mercurial mercurial2
905 $ hg init nested_absolute
905 $ hg init nested_absolute
906 $ echo test > nested_absolute/foo
906 $ echo test > nested_absolute/foo
907 $ hg -R nested_absolute add
907 $ hg -R nested_absolute add
908 adding nested_absolute/foo (glob)
908 adding nested_absolute/foo (glob)
909 $ hg -R nested_absolute ci -mtest
909 $ hg -R nested_absolute ci -mtest
910 $ cd mercurial
910 $ cd mercurial
911 $ hg init nested_relative
911 $ hg init nested_relative
912 $ echo test2 > nested_relative/foo2
912 $ echo test2 > nested_relative/foo2
913 $ hg -R nested_relative add
913 $ hg -R nested_relative add
914 adding nested_relative/foo2 (glob)
914 adding nested_relative/foo2 (glob)
915 $ hg -R nested_relative ci -mtest2
915 $ hg -R nested_relative ci -mtest2
916 $ hg init main
916 $ hg init main
917 $ echo "nested_relative = ../nested_relative" > main/.hgsub
917 $ echo "nested_relative = ../nested_relative" > main/.hgsub
918 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
918 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
919 $ hg -R main add
919 $ hg -R main add
920 adding main/.hgsub (glob)
920 adding main/.hgsub (glob)
921 $ hg -R main ci -m "add subrepos"
921 $ hg -R main ci -m "add subrepos"
922 $ cd ..
922 $ cd ..
923 $ hg clone mercurial/main mercurial2/main
923 $ hg clone mercurial/main mercurial2/main
924 updating to branch default
924 updating to branch default
925 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
925 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
926 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
926 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
927 > mercurial2/main/nested_relative/.hg/hgrc
927 > mercurial2/main/nested_relative/.hg/hgrc
928 [paths]
928 [paths]
929 default = $TESTTMP/mercurial/nested_absolute
929 default = $TESTTMP/mercurial/nested_absolute
930 [paths]
930 [paths]
931 default = $TESTTMP/mercurial/nested_relative
931 default = $TESTTMP/mercurial/nested_relative
932 $ rm -rf mercurial mercurial2
932 $ rm -rf mercurial mercurial2
933
933
934 Issue1977: multirepo push should fail if subrepo push fails
934 Issue1977: multirepo push should fail if subrepo push fails
935
935
936 $ hg init repo
936 $ hg init repo
937 $ hg init repo/s
937 $ hg init repo/s
938 $ echo a > repo/s/a
938 $ echo a > repo/s/a
939 $ hg -R repo/s ci -Am0
939 $ hg -R repo/s ci -Am0
940 adding a
940 adding a
941 $ echo s = s > repo/.hgsub
941 $ echo s = s > repo/.hgsub
942 $ hg -R repo ci -Am1
942 $ hg -R repo ci -Am1
943 adding .hgsub
943 adding .hgsub
944 $ hg clone repo repo2
944 $ hg clone repo repo2
945 updating to branch default
945 updating to branch default
946 cloning subrepo s from $TESTTMP/repo/s
946 cloning subrepo s from $TESTTMP/repo/s
947 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
947 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
948 $ hg -q -R repo2 pull -u
948 $ hg -q -R repo2 pull -u
949 $ echo 1 > repo2/s/a
949 $ echo 1 > repo2/s/a
950 $ hg -R repo2/s ci -m2
950 $ hg -R repo2/s ci -m2
951 $ hg -q -R repo2/s push
951 $ hg -q -R repo2/s push
952 $ hg -R repo2/s up -C 0
952 $ hg -R repo2/s up -C 0
953 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
953 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
954 $ echo 2 > repo2/s/b
954 $ echo 2 > repo2/s/b
955 $ hg -R repo2/s ci -m3 -A
955 $ hg -R repo2/s ci -m3 -A
956 adding b
956 adding b
957 created new head
957 created new head
958 $ hg -R repo2 ci -m3
958 $ hg -R repo2 ci -m3
959 $ hg -q -R repo2 push
959 $ hg -q -R repo2 push
960 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
960 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
961 (merge or see 'hg help push' for details about pushing new heads)
961 (merge or see 'hg help push' for details about pushing new heads)
962 [255]
962 [255]
963 $ hg -R repo update
963 $ hg -R repo update
964 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
964 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
965
965
966 test if untracked file is not overwritten
966 test if untracked file is not overwritten
967
967
968 (this also tests that updated .hgsubstate is treated as "modified",
968 (this also tests that updated .hgsubstate is treated as "modified",
969 when 'merge.update()' is aborted before 'merge.recordupdates()', even
969 when 'merge.update()' is aborted before 'merge.recordupdates()', even
970 if none of mode, size and timestamp of it isn't changed on the
970 if none of mode, size and timestamp of it isn't changed on the
971 filesystem (see also issue4583))
971 filesystem (see also issue4583))
972
972
973 $ echo issue3276_ok > repo/s/b
973 $ echo issue3276_ok > repo/s/b
974 $ hg -R repo2 push -f -q
974 $ hg -R repo2 push -f -q
975 $ touch -t 200001010000 repo/.hgsubstate
975 $ touch -t 200001010000 repo/.hgsubstate
976
976
977 $ cat >> repo/.hg/hgrc <<EOF
977 $ cat >> repo/.hg/hgrc <<EOF
978 > [fakedirstatewritetime]
978 > [fakedirstatewritetime]
979 > # emulate invoking dirstate.write() via repo.status()
979 > # emulate invoking dirstate.write() via repo.status()
980 > # at 2000-01-01 00:00
980 > # at 2000-01-01 00:00
981 > fakenow = 200001010000
981 > fakenow = 200001010000
982 >
982 >
983 > [extensions]
983 > [extensions]
984 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
984 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
985 > EOF
985 > EOF
986 $ hg -R repo update
986 $ hg -R repo update
987 b: untracked file differs
987 b: untracked file differs
988 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
988 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
989 [255]
989 [255]
990 $ cat >> repo/.hg/hgrc <<EOF
990 $ cat >> repo/.hg/hgrc <<EOF
991 > [extensions]
991 > [extensions]
992 > fakedirstatewritetime = !
992 > fakedirstatewritetime = !
993 > EOF
993 > EOF
994
994
995 $ cat repo/s/b
995 $ cat repo/s/b
996 issue3276_ok
996 issue3276_ok
997 $ rm repo/s/b
997 $ rm repo/s/b
998 $ touch -t 200001010000 repo/.hgsubstate
998 $ touch -t 200001010000 repo/.hgsubstate
999 $ hg -R repo revert --all
999 $ hg -R repo revert --all
1000 reverting repo/.hgsubstate (glob)
1000 reverting repo/.hgsubstate (glob)
1001 reverting subrepo s
1001 reverting subrepo s
1002 $ hg -R repo update
1002 $ hg -R repo update
1003 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1003 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1004 $ cat repo/s/b
1004 $ cat repo/s/b
1005 2
1005 2
1006 $ rm -rf repo2 repo
1006 $ rm -rf repo2 repo
1007
1007
1008
1008
1009 Issue1852 subrepos with relative paths always push/pull relative to default
1009 Issue1852 subrepos with relative paths always push/pull relative to default
1010
1010
1011 Prepare a repo with subrepo
1011 Prepare a repo with subrepo
1012
1012
1013 $ hg init issue1852a
1013 $ hg init issue1852a
1014 $ cd issue1852a
1014 $ cd issue1852a
1015 $ hg init sub/repo
1015 $ hg init sub/repo
1016 $ echo test > sub/repo/foo
1016 $ echo test > sub/repo/foo
1017 $ hg -R sub/repo add sub/repo/foo
1017 $ hg -R sub/repo add sub/repo/foo
1018 $ echo sub/repo = sub/repo > .hgsub
1018 $ echo sub/repo = sub/repo > .hgsub
1019 $ hg add .hgsub
1019 $ hg add .hgsub
1020 $ hg ci -mtest
1020 $ hg ci -mtest
1021 committing subrepository sub/repo (glob)
1021 committing subrepository sub/repo (glob)
1022 $ echo test >> sub/repo/foo
1022 $ echo test >> sub/repo/foo
1023 $ hg ci -mtest
1023 $ hg ci -mtest
1024 committing subrepository sub/repo (glob)
1024 committing subrepository sub/repo (glob)
1025 $ hg cat sub/repo/foo
1025 $ hg cat sub/repo/foo
1026 test
1026 test
1027 test
1027 test
1028 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1028 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1029 [
1029 [
1030 {
1030 {
1031 "abspath": "foo",
1031 "abspath": "foo",
1032 "data": "test\ntest\n",
1032 "data": "test\ntest\n",
1033 "path": "sub/repo/foo"
1033 "path": "sub/repo/foo"
1034 }
1034 }
1035 ]
1035 ]
1036 $ mkdir -p tmp/sub/repo
1036 $ mkdir -p tmp/sub/repo
1037 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1037 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1038 $ cat tmp/sub/repo/foo_p
1038 $ cat tmp/sub/repo/foo_p
1039 test
1039 test
1040 $ mv sub/repo sub_
1040 $ mv sub/repo sub_
1041 $ hg cat sub/repo/baz
1041 $ hg cat sub/repo/baz
1042 skipping missing subrepository: sub/repo
1042 skipping missing subrepository: sub/repo
1043 [1]
1043 [1]
1044 $ rm -rf sub/repo
1044 $ rm -rf sub/repo
1045 $ mv sub_ sub/repo
1045 $ mv sub_ sub/repo
1046 $ cd ..
1046 $ cd ..
1047
1047
1048 Create repo without default path, pull top repo, and see what happens on update
1048 Create repo without default path, pull top repo, and see what happens on update
1049
1049
1050 $ hg init issue1852b
1050 $ hg init issue1852b
1051 $ hg -R issue1852b pull issue1852a
1051 $ hg -R issue1852b pull issue1852a
1052 pulling from issue1852a
1052 pulling from issue1852a
1053 requesting all changes
1053 requesting all changes
1054 adding changesets
1054 adding changesets
1055 adding manifests
1055 adding manifests
1056 adding file changes
1056 adding file changes
1057 added 2 changesets with 3 changes to 2 files
1057 added 2 changesets with 3 changes to 2 files
1058 new changesets 19487b456929:be5eb94e7215
1058 new changesets 19487b456929:be5eb94e7215
1059 (run 'hg update' to get a working copy)
1059 (run 'hg update' to get a working copy)
1060 $ hg -R issue1852b update
1060 $ hg -R issue1852b update
1061 abort: default path for subrepository not found (in subrepository "sub/repo") (glob)
1061 abort: default path for subrepository not found (in subrepository "sub/repo") (glob)
1062 [255]
1062 [255]
1063
1063
1064 Ensure a full traceback, not just the SubrepoAbort part
1064 Ensure a full traceback, not just the SubrepoAbort part
1065
1065
1066 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1066 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1067 raise error.Abort(_("default path for subrepository not found"))
1067 raise error.Abort(_("default path for subrepository not found"))
1068
1068
1069 Pull -u now doesn't help
1069 Pull -u now doesn't help
1070
1070
1071 $ hg -R issue1852b pull -u issue1852a
1071 $ hg -R issue1852b pull -u issue1852a
1072 pulling from issue1852a
1072 pulling from issue1852a
1073 searching for changes
1073 searching for changes
1074 no changes found
1074 no changes found
1075
1075
1076 Try the same, but with pull -u
1076 Try the same, but with pull -u
1077
1077
1078 $ hg init issue1852c
1078 $ hg init issue1852c
1079 $ hg -R issue1852c pull -r0 -u issue1852a
1079 $ hg -R issue1852c pull -r0 -u issue1852a
1080 pulling from issue1852a
1080 pulling from issue1852a
1081 adding changesets
1081 adding changesets
1082 adding manifests
1082 adding manifests
1083 adding file changes
1083 adding file changes
1084 added 1 changesets with 2 changes to 2 files
1084 added 1 changesets with 2 changes to 2 files
1085 new changesets 19487b456929
1085 new changesets 19487b456929
1086 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
1086 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
1087 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1087 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1088
1088
1089 Try to push from the other side
1089 Try to push from the other side
1090
1090
1091 $ hg -R issue1852a push `pwd`/issue1852c
1091 $ hg -R issue1852a push `pwd`/issue1852c
1092 pushing to $TESTTMP/issue1852c (glob)
1092 pushing to $TESTTMP/issue1852c (glob)
1093 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
1093 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
1094 searching for changes
1094 searching for changes
1095 no changes found
1095 no changes found
1096 searching for changes
1096 searching for changes
1097 adding changesets
1097 adding changesets
1098 adding manifests
1098 adding manifests
1099 adding file changes
1099 adding file changes
1100 added 1 changesets with 1 changes to 1 files
1100 added 1 changesets with 1 changes to 1 files
1101
1101
1102 Incoming and outgoing should not use the default path:
1102 Incoming and outgoing should not use the default path:
1103
1103
1104 $ hg clone -q issue1852a issue1852d
1104 $ hg clone -q issue1852a issue1852d
1105 $ hg -R issue1852d outgoing --subrepos issue1852c
1105 $ hg -R issue1852d outgoing --subrepos issue1852c
1106 comparing with issue1852c
1106 comparing with issue1852c
1107 searching for changes
1107 searching for changes
1108 no changes found
1108 no changes found
1109 comparing with issue1852c/sub/repo
1109 comparing with issue1852c/sub/repo
1110 searching for changes
1110 searching for changes
1111 no changes found
1111 no changes found
1112 [1]
1112 [1]
1113 $ hg -R issue1852d incoming --subrepos issue1852c
1113 $ hg -R issue1852d incoming --subrepos issue1852c
1114 comparing with issue1852c
1114 comparing with issue1852c
1115 searching for changes
1115 searching for changes
1116 no changes found
1116 no changes found
1117 comparing with issue1852c/sub/repo
1117 comparing with issue1852c/sub/repo
1118 searching for changes
1118 searching for changes
1119 no changes found
1119 no changes found
1120 [1]
1120 [1]
1121
1121
1122 Check that merge of a new subrepo doesn't write the uncommitted state to
1122 Check that merge of a new subrepo doesn't write the uncommitted state to
1123 .hgsubstate (issue4622)
1123 .hgsubstate (issue4622)
1124
1124
1125 $ hg init issue1852a/addedsub
1125 $ hg init issue1852a/addedsub
1126 $ echo zzz > issue1852a/addedsub/zz.txt
1126 $ echo zzz > issue1852a/addedsub/zz.txt
1127 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1127 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1128
1128
1129 $ hg clone issue1852a/addedsub issue1852d/addedsub
1129 $ hg clone issue1852a/addedsub issue1852d/addedsub
1130 updating to branch default
1130 updating to branch default
1131 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1131 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1132
1132
1133 $ echo def > issue1852a/sub/repo/foo
1133 $ echo def > issue1852a/sub/repo/foo
1134 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1134 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1135 adding tmp/sub/repo/foo_p
1135 adding tmp/sub/repo/foo_p
1136 committing subrepository sub/repo (glob)
1136 committing subrepository sub/repo (glob)
1137
1137
1138 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1138 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1139 $ echo xyz > issue1852d/sub/repo/foo
1139 $ echo xyz > issue1852d/sub/repo/foo
1140 $ hg -R issue1852d pull -u
1140 $ hg -R issue1852d pull -u
1141 pulling from $TESTTMP/issue1852a (glob)
1141 pulling from $TESTTMP/issue1852a (glob)
1142 searching for changes
1142 searching for changes
1143 adding changesets
1143 adding changesets
1144 adding manifests
1144 adding manifests
1145 adding file changes
1145 adding file changes
1146 added 1 changesets with 2 changes to 2 files
1146 added 1 changesets with 2 changes to 2 files
1147 new changesets c82b79fdcc5b
1147 new changesets c82b79fdcc5b
1148 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1148 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1149 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1149 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1150 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
1150 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
1151 searching for changes
1151 searching for changes
1152 adding changesets
1152 adding changesets
1153 adding manifests
1153 adding manifests
1154 adding file changes
1154 adding file changes
1155 added 1 changesets with 1 changes to 1 files
1155 added 1 changesets with 1 changes to 1 files
1156 new changesets 46cd4aac504c
1156 new changesets 46cd4aac504c
1157 subrepository sources for sub/repo differ (glob)
1157 subrepository sources for sub/repo differ (glob)
1158 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1158 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1160 $ cat issue1852d/.hgsubstate
1160 $ cat issue1852d/.hgsubstate
1161 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1161 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1162
1162
1163 Check status of files when none of them belong to the first
1163 Check status of files when none of them belong to the first
1164 subrepository:
1164 subrepository:
1165
1165
1166 $ hg init subrepo-status
1166 $ hg init subrepo-status
1167 $ cd subrepo-status
1167 $ cd subrepo-status
1168 $ hg init subrepo-1
1168 $ hg init subrepo-1
1169 $ hg init subrepo-2
1169 $ hg init subrepo-2
1170 $ cd subrepo-2
1170 $ cd subrepo-2
1171 $ touch file
1171 $ touch file
1172 $ hg add file
1172 $ hg add file
1173 $ cd ..
1173 $ cd ..
1174 $ echo subrepo-1 = subrepo-1 > .hgsub
1174 $ echo subrepo-1 = subrepo-1 > .hgsub
1175 $ echo subrepo-2 = subrepo-2 >> .hgsub
1175 $ echo subrepo-2 = subrepo-2 >> .hgsub
1176 $ hg add .hgsub
1176 $ hg add .hgsub
1177 $ hg ci -m 'Added subrepos'
1177 $ hg ci -m 'Added subrepos'
1178 committing subrepository subrepo-2
1178 committing subrepository subrepo-2
1179 $ hg st subrepo-2/file
1179 $ hg st subrepo-2/file
1180
1180
1181 Check that share works with subrepo
1181 Check that share works with subrepo
1182 $ hg --config extensions.share= share . ../shared
1182 $ hg --config extensions.share= share . ../shared
1183 updating working directory
1183 updating working directory
1184 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1184 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1185 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1185 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1186 $ test -f ../shared/subrepo-1/.hg/sharedpath
1187 $ find ../shared/* | sort
1187 [1]
1188 ../shared/subrepo-1
1189 ../shared/subrepo-1/.hg
1190 ../shared/subrepo-1/.hg/cache
1191 ../shared/subrepo-1/.hg/cache/storehash
1192 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1193 ../shared/subrepo-1/.hg/hgrc
1194 ../shared/subrepo-1/.hg/requires
1195 ../shared/subrepo-1/.hg/sharedpath
1196 ../shared/subrepo-2
1197 ../shared/subrepo-2/.hg
1198 ../shared/subrepo-2/.hg/branch
1199 ../shared/subrepo-2/.hg/cache
1200 ../shared/subrepo-2/.hg/cache/checkisexec (execbit !)
1201 ../shared/subrepo-2/.hg/cache/checklink (symlink !)
1202 ../shared/subrepo-2/.hg/cache/checklink-target (symlink !)
1203 ../shared/subrepo-2/.hg/cache/storehash
1204 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1205 ../shared/subrepo-2/.hg/dirstate
1206 ../shared/subrepo-2/.hg/hgrc
1207 ../shared/subrepo-2/.hg/requires
1208 ../shared/subrepo-2/.hg/sharedpath
1209 ../shared/subrepo-2/file
1188 $ hg -R ../shared in
1210 $ hg -R ../shared in
1189 abort: repository default not found!
1211 abort: repository default not found!
1190 [255]
1212 [255]
1191 $ hg -R ../shared/subrepo-2 showconfig paths
1213 $ hg -R ../shared/subrepo-2 showconfig paths
1192 paths.default=$TESTTMP/subrepo-status/subrepo-2
1214 paths.default=$TESTTMP/subrepo-status/subrepo-2
1193 $ hg -R ../shared/subrepo-1 sum --remote
1215 $ hg -R ../shared/subrepo-1 sum --remote
1194 parent: -1:000000000000 tip (empty repository)
1216 parent: -1:000000000000 tip (empty repository)
1195 branch: default
1217 branch: default
1196 commit: (clean)
1218 commit: (clean)
1197 update: (current)
1219 update: (current)
1198 remote: (synced)
1220 remote: (synced)
1199
1221
1200 Check hg update --clean
1222 Check hg update --clean
1201 $ cd $TESTTMP/t
1223 $ cd $TESTTMP/t
1202 $ rm -r t/t.orig
1224 $ rm -r t/t.orig
1203 $ hg status -S --all
1225 $ hg status -S --all
1204 C .hgsub
1226 C .hgsub
1205 C .hgsubstate
1227 C .hgsubstate
1206 C a
1228 C a
1207 C s/.hgsub
1229 C s/.hgsub
1208 C s/.hgsubstate
1230 C s/.hgsubstate
1209 C s/a
1231 C s/a
1210 C s/ss/a
1232 C s/ss/a
1211 C t/t
1233 C t/t
1212 $ echo c1 > s/a
1234 $ echo c1 > s/a
1213 $ cd s
1235 $ cd s
1214 $ echo c1 > b
1236 $ echo c1 > b
1215 $ echo c1 > c
1237 $ echo c1 > c
1216 $ hg add b
1238 $ hg add b
1217 $ cd ..
1239 $ cd ..
1218 $ hg status -S
1240 $ hg status -S
1219 M s/a
1241 M s/a
1220 A s/b
1242 A s/b
1221 ? s/c
1243 ? s/c
1222 $ hg update -C
1244 $ hg update -C
1223 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1245 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1224 updated to "925c17564ef8: 13"
1246 updated to "925c17564ef8: 13"
1225 2 other heads for branch "default"
1247 2 other heads for branch "default"
1226 $ hg status -S
1248 $ hg status -S
1227 ? s/b
1249 ? s/b
1228 ? s/c
1250 ? s/c
1229
1251
1230 Sticky subrepositories, no changes
1252 Sticky subrepositories, no changes
1231 $ cd $TESTTMP/t
1253 $ cd $TESTTMP/t
1232 $ hg id
1254 $ hg id
1233 925c17564ef8 tip
1255 925c17564ef8 tip
1234 $ hg -R s id
1256 $ hg -R s id
1235 12a213df6fa9 tip
1257 12a213df6fa9 tip
1236 $ hg -R t id
1258 $ hg -R t id
1237 52c0adc0515a tip
1259 52c0adc0515a tip
1238 $ hg update 11
1260 $ hg update 11
1239 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1261 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1240 $ hg id
1262 $ hg id
1241 365661e5936a
1263 365661e5936a
1242 $ hg -R s id
1264 $ hg -R s id
1243 fc627a69481f
1265 fc627a69481f
1244 $ hg -R t id
1266 $ hg -R t id
1245 e95bcfa18a35
1267 e95bcfa18a35
1246
1268
1247 Sticky subrepositories, file changes
1269 Sticky subrepositories, file changes
1248 $ touch s/f1
1270 $ touch s/f1
1249 $ touch t/f1
1271 $ touch t/f1
1250 $ hg add -S s/f1
1272 $ hg add -S s/f1
1251 $ hg add -S t/f1
1273 $ hg add -S t/f1
1252 $ hg id
1274 $ hg id
1253 365661e5936a+
1275 365661e5936a+
1254 $ hg -R s id
1276 $ hg -R s id
1255 fc627a69481f+
1277 fc627a69481f+
1256 $ hg -R t id
1278 $ hg -R t id
1257 e95bcfa18a35+
1279 e95bcfa18a35+
1258 $ hg update tip
1280 $ hg update tip
1259 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1281 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1260 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1282 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1261 subrepository sources for s differ
1283 subrepository sources for s differ
1262 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1284 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1263 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1285 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1264 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1286 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1265 subrepository sources for t differ
1287 subrepository sources for t differ
1266 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1288 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1267 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1289 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1268 $ hg id
1290 $ hg id
1269 925c17564ef8+ tip
1291 925c17564ef8+ tip
1270 $ hg -R s id
1292 $ hg -R s id
1271 fc627a69481f+
1293 fc627a69481f+
1272 $ hg -R t id
1294 $ hg -R t id
1273 e95bcfa18a35+
1295 e95bcfa18a35+
1274 $ hg update --clean tip
1296 $ hg update --clean tip
1275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1297 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1276
1298
1277 Sticky subrepository, revision updates
1299 Sticky subrepository, revision updates
1278 $ hg id
1300 $ hg id
1279 925c17564ef8 tip
1301 925c17564ef8 tip
1280 $ hg -R s id
1302 $ hg -R s id
1281 12a213df6fa9 tip
1303 12a213df6fa9 tip
1282 $ hg -R t id
1304 $ hg -R t id
1283 52c0adc0515a tip
1305 52c0adc0515a tip
1284 $ cd s
1306 $ cd s
1285 $ hg update -r -2
1307 $ hg update -r -2
1286 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1308 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1287 $ cd ../t
1309 $ cd ../t
1288 $ hg update -r 2
1310 $ hg update -r 2
1289 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1311 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1290 $ cd ..
1312 $ cd ..
1291 $ hg update 10
1313 $ hg update 10
1292 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1314 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1293 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1315 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1294 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1316 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1295 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1317 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1296 subrepository sources for t differ (in checked out version)
1318 subrepository sources for t differ (in checked out version)
1297 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1319 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1298 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1320 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1299 $ hg id
1321 $ hg id
1300 e45c8b14af55+
1322 e45c8b14af55+
1301 $ hg -R s id
1323 $ hg -R s id
1302 02dcf1d70411
1324 02dcf1d70411
1303 $ hg -R t id
1325 $ hg -R t id
1304 7af322bc1198
1326 7af322bc1198
1305
1327
1306 Sticky subrepository, file changes and revision updates
1328 Sticky subrepository, file changes and revision updates
1307 $ touch s/f1
1329 $ touch s/f1
1308 $ touch t/f1
1330 $ touch t/f1
1309 $ hg add -S s/f1
1331 $ hg add -S s/f1
1310 $ hg add -S t/f1
1332 $ hg add -S t/f1
1311 $ hg id
1333 $ hg id
1312 e45c8b14af55+
1334 e45c8b14af55+
1313 $ hg -R s id
1335 $ hg -R s id
1314 02dcf1d70411+
1336 02dcf1d70411+
1315 $ hg -R t id
1337 $ hg -R t id
1316 7af322bc1198+
1338 7af322bc1198+
1317 $ hg update tip
1339 $ hg update tip
1318 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1340 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1319 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1341 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1320 subrepository sources for s differ
1342 subrepository sources for s differ
1321 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1343 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1322 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1344 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1323 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1345 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1324 subrepository sources for t differ
1346 subrepository sources for t differ
1325 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1347 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1327 $ hg id
1349 $ hg id
1328 925c17564ef8+ tip
1350 925c17564ef8+ tip
1329 $ hg -R s id
1351 $ hg -R s id
1330 02dcf1d70411+
1352 02dcf1d70411+
1331 $ hg -R t id
1353 $ hg -R t id
1332 7af322bc1198+
1354 7af322bc1198+
1333
1355
1334 Sticky repository, update --clean
1356 Sticky repository, update --clean
1335 $ hg update --clean tip
1357 $ hg update --clean tip
1336 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1358 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1337 $ hg id
1359 $ hg id
1338 925c17564ef8 tip
1360 925c17564ef8 tip
1339 $ hg -R s id
1361 $ hg -R s id
1340 12a213df6fa9 tip
1362 12a213df6fa9 tip
1341 $ hg -R t id
1363 $ hg -R t id
1342 52c0adc0515a tip
1364 52c0adc0515a tip
1343
1365
1344 Test subrepo already at intended revision:
1366 Test subrepo already at intended revision:
1345 $ cd s
1367 $ cd s
1346 $ hg update fc627a69481f
1368 $ hg update fc627a69481f
1347 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1369 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 $ cd ..
1370 $ cd ..
1349 $ hg update 11
1371 $ hg update 11
1350 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1372 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1351 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1373 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1352 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1374 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1353 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1375 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1354 $ hg id -n
1376 $ hg id -n
1355 11+
1377 11+
1356 $ hg -R s id
1378 $ hg -R s id
1357 fc627a69481f
1379 fc627a69481f
1358 $ hg -R t id
1380 $ hg -R t id
1359 e95bcfa18a35
1381 e95bcfa18a35
1360
1382
1361 Test that removing .hgsubstate doesn't break anything:
1383 Test that removing .hgsubstate doesn't break anything:
1362
1384
1363 $ hg rm -f .hgsubstate
1385 $ hg rm -f .hgsubstate
1364 $ hg ci -mrm
1386 $ hg ci -mrm
1365 nothing changed
1387 nothing changed
1366 [1]
1388 [1]
1367 $ hg log -vr tip
1389 $ hg log -vr tip
1368 changeset: 13:925c17564ef8
1390 changeset: 13:925c17564ef8
1369 tag: tip
1391 tag: tip
1370 user: test
1392 user: test
1371 date: Thu Jan 01 00:00:00 1970 +0000
1393 date: Thu Jan 01 00:00:00 1970 +0000
1372 files: .hgsubstate
1394 files: .hgsubstate
1373 description:
1395 description:
1374 13
1396 13
1375
1397
1376
1398
1377
1399
1378 Test that removing .hgsub removes .hgsubstate:
1400 Test that removing .hgsub removes .hgsubstate:
1379
1401
1380 $ hg rm .hgsub
1402 $ hg rm .hgsub
1381 $ hg ci -mrm2
1403 $ hg ci -mrm2
1382 created new head
1404 created new head
1383 $ hg log -vr tip
1405 $ hg log -vr tip
1384 changeset: 14:2400bccd50af
1406 changeset: 14:2400bccd50af
1385 tag: tip
1407 tag: tip
1386 parent: 11:365661e5936a
1408 parent: 11:365661e5936a
1387 user: test
1409 user: test
1388 date: Thu Jan 01 00:00:00 1970 +0000
1410 date: Thu Jan 01 00:00:00 1970 +0000
1389 files: .hgsub .hgsubstate
1411 files: .hgsub .hgsubstate
1390 description:
1412 description:
1391 rm2
1413 rm2
1392
1414
1393
1415
1394 Test issue3153: diff -S with deleted subrepos
1416 Test issue3153: diff -S with deleted subrepos
1395
1417
1396 $ hg diff --nodates -S -c .
1418 $ hg diff --nodates -S -c .
1397 diff -r 365661e5936a -r 2400bccd50af .hgsub
1419 diff -r 365661e5936a -r 2400bccd50af .hgsub
1398 --- a/.hgsub
1420 --- a/.hgsub
1399 +++ /dev/null
1421 +++ /dev/null
1400 @@ -1,2 +0,0 @@
1422 @@ -1,2 +0,0 @@
1401 -s = s
1423 -s = s
1402 -t = t
1424 -t = t
1403 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1425 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1404 --- a/.hgsubstate
1426 --- a/.hgsubstate
1405 +++ /dev/null
1427 +++ /dev/null
1406 @@ -1,2 +0,0 @@
1428 @@ -1,2 +0,0 @@
1407 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1429 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1408 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1430 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1409
1431
1410 Test behavior of add for explicit path in subrepo:
1432 Test behavior of add for explicit path in subrepo:
1411 $ cd ..
1433 $ cd ..
1412 $ hg init explicit
1434 $ hg init explicit
1413 $ cd explicit
1435 $ cd explicit
1414 $ echo s = s > .hgsub
1436 $ echo s = s > .hgsub
1415 $ hg add .hgsub
1437 $ hg add .hgsub
1416 $ hg init s
1438 $ hg init s
1417 $ hg ci -m0
1439 $ hg ci -m0
1418 Adding with an explicit path in a subrepo adds the file
1440 Adding with an explicit path in a subrepo adds the file
1419 $ echo c1 > f1
1441 $ echo c1 > f1
1420 $ echo c2 > s/f2
1442 $ echo c2 > s/f2
1421 $ hg st -S
1443 $ hg st -S
1422 ? f1
1444 ? f1
1423 ? s/f2
1445 ? s/f2
1424 $ hg add s/f2
1446 $ hg add s/f2
1425 $ hg st -S
1447 $ hg st -S
1426 A s/f2
1448 A s/f2
1427 ? f1
1449 ? f1
1428 $ hg ci -R s -m0
1450 $ hg ci -R s -m0
1429 $ hg ci -Am1
1451 $ hg ci -Am1
1430 adding f1
1452 adding f1
1431 Adding with an explicit path in a subrepo with -S has the same behavior
1453 Adding with an explicit path in a subrepo with -S has the same behavior
1432 $ echo c3 > f3
1454 $ echo c3 > f3
1433 $ echo c4 > s/f4
1455 $ echo c4 > s/f4
1434 $ hg st -S
1456 $ hg st -S
1435 ? f3
1457 ? f3
1436 ? s/f4
1458 ? s/f4
1437 $ hg add -S s/f4
1459 $ hg add -S s/f4
1438 $ hg st -S
1460 $ hg st -S
1439 A s/f4
1461 A s/f4
1440 ? f3
1462 ? f3
1441 $ hg ci -R s -m1
1463 $ hg ci -R s -m1
1442 $ hg ci -Ama2
1464 $ hg ci -Ama2
1443 adding f3
1465 adding f3
1444 Adding without a path or pattern silently ignores subrepos
1466 Adding without a path or pattern silently ignores subrepos
1445 $ echo c5 > f5
1467 $ echo c5 > f5
1446 $ echo c6 > s/f6
1468 $ echo c6 > s/f6
1447 $ echo c7 > s/f7
1469 $ echo c7 > s/f7
1448 $ hg st -S
1470 $ hg st -S
1449 ? f5
1471 ? f5
1450 ? s/f6
1472 ? s/f6
1451 ? s/f7
1473 ? s/f7
1452 $ hg add
1474 $ hg add
1453 adding f5
1475 adding f5
1454 $ hg st -S
1476 $ hg st -S
1455 A f5
1477 A f5
1456 ? s/f6
1478 ? s/f6
1457 ? s/f7
1479 ? s/f7
1458 $ hg ci -R s -Am2
1480 $ hg ci -R s -Am2
1459 adding f6
1481 adding f6
1460 adding f7
1482 adding f7
1461 $ hg ci -m3
1483 $ hg ci -m3
1462 Adding without a path or pattern with -S also adds files in subrepos
1484 Adding without a path or pattern with -S also adds files in subrepos
1463 $ echo c8 > f8
1485 $ echo c8 > f8
1464 $ echo c9 > s/f9
1486 $ echo c9 > s/f9
1465 $ echo c10 > s/f10
1487 $ echo c10 > s/f10
1466 $ hg st -S
1488 $ hg st -S
1467 ? f8
1489 ? f8
1468 ? s/f10
1490 ? s/f10
1469 ? s/f9
1491 ? s/f9
1470 $ hg add -S
1492 $ hg add -S
1471 adding f8
1493 adding f8
1472 adding s/f10 (glob)
1494 adding s/f10 (glob)
1473 adding s/f9 (glob)
1495 adding s/f9 (glob)
1474 $ hg st -S
1496 $ hg st -S
1475 A f8
1497 A f8
1476 A s/f10
1498 A s/f10
1477 A s/f9
1499 A s/f9
1478 $ hg ci -R s -m3
1500 $ hg ci -R s -m3
1479 $ hg ci -m4
1501 $ hg ci -m4
1480 Adding with a pattern silently ignores subrepos
1502 Adding with a pattern silently ignores subrepos
1481 $ echo c11 > fm11
1503 $ echo c11 > fm11
1482 $ echo c12 > fn12
1504 $ echo c12 > fn12
1483 $ echo c13 > s/fm13
1505 $ echo c13 > s/fm13
1484 $ echo c14 > s/fn14
1506 $ echo c14 > s/fn14
1485 $ hg st -S
1507 $ hg st -S
1486 ? fm11
1508 ? fm11
1487 ? fn12
1509 ? fn12
1488 ? s/fm13
1510 ? s/fm13
1489 ? s/fn14
1511 ? s/fn14
1490 $ hg add 'glob:**fm*'
1512 $ hg add 'glob:**fm*'
1491 adding fm11
1513 adding fm11
1492 $ hg st -S
1514 $ hg st -S
1493 A fm11
1515 A fm11
1494 ? fn12
1516 ? fn12
1495 ? s/fm13
1517 ? s/fm13
1496 ? s/fn14
1518 ? s/fn14
1497 $ hg ci -R s -Am4
1519 $ hg ci -R s -Am4
1498 adding fm13
1520 adding fm13
1499 adding fn14
1521 adding fn14
1500 $ hg ci -Am5
1522 $ hg ci -Am5
1501 adding fn12
1523 adding fn12
1502 Adding with a pattern with -S also adds matches in subrepos
1524 Adding with a pattern with -S also adds matches in subrepos
1503 $ echo c15 > fm15
1525 $ echo c15 > fm15
1504 $ echo c16 > fn16
1526 $ echo c16 > fn16
1505 $ echo c17 > s/fm17
1527 $ echo c17 > s/fm17
1506 $ echo c18 > s/fn18
1528 $ echo c18 > s/fn18
1507 $ hg st -S
1529 $ hg st -S
1508 ? fm15
1530 ? fm15
1509 ? fn16
1531 ? fn16
1510 ? s/fm17
1532 ? s/fm17
1511 ? s/fn18
1533 ? s/fn18
1512 $ hg add -S 'glob:**fm*'
1534 $ hg add -S 'glob:**fm*'
1513 adding fm15
1535 adding fm15
1514 adding s/fm17 (glob)
1536 adding s/fm17 (glob)
1515 $ hg st -S
1537 $ hg st -S
1516 A fm15
1538 A fm15
1517 A s/fm17
1539 A s/fm17
1518 ? fn16
1540 ? fn16
1519 ? s/fn18
1541 ? s/fn18
1520 $ hg ci -R s -Am5
1542 $ hg ci -R s -Am5
1521 adding fn18
1543 adding fn18
1522 $ hg ci -Am6
1544 $ hg ci -Am6
1523 adding fn16
1545 adding fn16
1524
1546
1525 Test behavior of forget for explicit path in subrepo:
1547 Test behavior of forget for explicit path in subrepo:
1526 Forgetting an explicit path in a subrepo untracks the file
1548 Forgetting an explicit path in a subrepo untracks the file
1527 $ echo c19 > s/f19
1549 $ echo c19 > s/f19
1528 $ hg add s/f19
1550 $ hg add s/f19
1529 $ hg st -S
1551 $ hg st -S
1530 A s/f19
1552 A s/f19
1531 $ hg forget s/f19
1553 $ hg forget s/f19
1532 $ hg st -S
1554 $ hg st -S
1533 ? s/f19
1555 ? s/f19
1534 $ rm s/f19
1556 $ rm s/f19
1535 $ cd ..
1557 $ cd ..
1536
1558
1537 Courtesy phases synchronisation to publishing server does not block the push
1559 Courtesy phases synchronisation to publishing server does not block the push
1538 (issue3781)
1560 (issue3781)
1539
1561
1540 $ cp -R main issue3781
1562 $ cp -R main issue3781
1541 $ cp -R main issue3781-dest
1563 $ cp -R main issue3781-dest
1542 $ cd issue3781-dest/s
1564 $ cd issue3781-dest/s
1543 $ hg phase tip # show we have draft changeset
1565 $ hg phase tip # show we have draft changeset
1544 5: draft
1566 5: draft
1545 $ chmod a-w .hg/store/phaseroots # prevent phase push
1567 $ chmod a-w .hg/store/phaseroots # prevent phase push
1546 $ cd ../../issue3781
1568 $ cd ../../issue3781
1547 $ cat >> .hg/hgrc << EOF
1569 $ cat >> .hg/hgrc << EOF
1548 > [paths]
1570 > [paths]
1549 > default=../issue3781-dest/
1571 > default=../issue3781-dest/
1550 > EOF
1572 > EOF
1551 $ hg push --config devel.legacy.exchange=bundle1
1573 $ hg push --config devel.legacy.exchange=bundle1
1552 pushing to $TESTTMP/issue3781-dest (glob)
1574 pushing to $TESTTMP/issue3781-dest (glob)
1553 pushing subrepo s to $TESTTMP/issue3781-dest/s
1575 pushing subrepo s to $TESTTMP/issue3781-dest/s
1554 searching for changes
1576 searching for changes
1555 no changes found
1577 no changes found
1556 searching for changes
1578 searching for changes
1557 no changes found
1579 no changes found
1558 [1]
1580 [1]
1559 # clean the push cache
1581 # clean the push cache
1560 $ rm s/.hg/cache/storehash/*
1582 $ rm s/.hg/cache/storehash/*
1561 $ hg push # bundle2+
1583 $ hg push # bundle2+
1562 pushing to $TESTTMP/issue3781-dest (glob)
1584 pushing to $TESTTMP/issue3781-dest (glob)
1563 pushing subrepo s to $TESTTMP/issue3781-dest/s
1585 pushing subrepo s to $TESTTMP/issue3781-dest/s
1564 searching for changes
1586 searching for changes
1565 no changes found
1587 no changes found
1566 searching for changes
1588 searching for changes
1567 no changes found
1589 no changes found
1568 [1]
1590 [1]
1569 $ cd ..
1591 $ cd ..
1570
1592
1571 Test phase choice for newly created commit with "phases.subrepochecks"
1593 Test phase choice for newly created commit with "phases.subrepochecks"
1572 configuration
1594 configuration
1573
1595
1574 $ cd t
1596 $ cd t
1575 $ hg update -q -r 12
1597 $ hg update -q -r 12
1576
1598
1577 $ cat >> s/ss/.hg/hgrc <<EOF
1599 $ cat >> s/ss/.hg/hgrc <<EOF
1578 > [phases]
1600 > [phases]
1579 > new-commit = secret
1601 > new-commit = secret
1580 > EOF
1602 > EOF
1581 $ cat >> s/.hg/hgrc <<EOF
1603 $ cat >> s/.hg/hgrc <<EOF
1582 > [phases]
1604 > [phases]
1583 > new-commit = draft
1605 > new-commit = draft
1584 > EOF
1606 > EOF
1585 $ echo phasecheck1 >> s/ss/a
1607 $ echo phasecheck1 >> s/ss/a
1586 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1608 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1587 committing subrepository ss
1609 committing subrepository ss
1588 transaction abort!
1610 transaction abort!
1589 rollback completed
1611 rollback completed
1590 abort: can't commit in draft phase conflicting secret from subrepository ss
1612 abort: can't commit in draft phase conflicting secret from subrepository ss
1591 [255]
1613 [255]
1592 $ echo phasecheck2 >> s/ss/a
1614 $ echo phasecheck2 >> s/ss/a
1593 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1615 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1594 committing subrepository ss
1616 committing subrepository ss
1595 $ hg -R s/ss phase tip
1617 $ hg -R s/ss phase tip
1596 3: secret
1618 3: secret
1597 $ hg -R s phase tip
1619 $ hg -R s phase tip
1598 6: draft
1620 6: draft
1599 $ echo phasecheck3 >> s/ss/a
1621 $ echo phasecheck3 >> s/ss/a
1600 $ hg -R s commit -S -m phasecheck3
1622 $ hg -R s commit -S -m phasecheck3
1601 committing subrepository ss
1623 committing subrepository ss
1602 warning: changes are committed in secret phase from subrepository ss
1624 warning: changes are committed in secret phase from subrepository ss
1603 $ hg -R s/ss phase tip
1625 $ hg -R s/ss phase tip
1604 4: secret
1626 4: secret
1605 $ hg -R s phase tip
1627 $ hg -R s phase tip
1606 7: secret
1628 7: secret
1607
1629
1608 $ cat >> t/.hg/hgrc <<EOF
1630 $ cat >> t/.hg/hgrc <<EOF
1609 > [phases]
1631 > [phases]
1610 > new-commit = draft
1632 > new-commit = draft
1611 > EOF
1633 > EOF
1612 $ cat >> .hg/hgrc <<EOF
1634 $ cat >> .hg/hgrc <<EOF
1613 > [phases]
1635 > [phases]
1614 > new-commit = public
1636 > new-commit = public
1615 > EOF
1637 > EOF
1616 $ echo phasecheck4 >> s/ss/a
1638 $ echo phasecheck4 >> s/ss/a
1617 $ echo phasecheck4 >> t/t
1639 $ echo phasecheck4 >> t/t
1618 $ hg commit -S -m phasecheck4
1640 $ hg commit -S -m phasecheck4
1619 committing subrepository s
1641 committing subrepository s
1620 committing subrepository s/ss (glob)
1642 committing subrepository s/ss (glob)
1621 warning: changes are committed in secret phase from subrepository ss
1643 warning: changes are committed in secret phase from subrepository ss
1622 committing subrepository t
1644 committing subrepository t
1623 warning: changes are committed in secret phase from subrepository s
1645 warning: changes are committed in secret phase from subrepository s
1624 created new head
1646 created new head
1625 $ hg -R s/ss phase tip
1647 $ hg -R s/ss phase tip
1626 5: secret
1648 5: secret
1627 $ hg -R s phase tip
1649 $ hg -R s phase tip
1628 8: secret
1650 8: secret
1629 $ hg -R t phase tip
1651 $ hg -R t phase tip
1630 6: draft
1652 6: draft
1631 $ hg phase tip
1653 $ hg phase tip
1632 15: secret
1654 15: secret
1633
1655
1634 $ cd ..
1656 $ cd ..
1635
1657
1636
1658
1637 Test that commit --secret works on both repo and subrepo (issue4182)
1659 Test that commit --secret works on both repo and subrepo (issue4182)
1638
1660
1639 $ cd main
1661 $ cd main
1640 $ echo secret >> b
1662 $ echo secret >> b
1641 $ echo secret >> s/b
1663 $ echo secret >> s/b
1642 $ hg commit --secret --subrepo -m "secret"
1664 $ hg commit --secret --subrepo -m "secret"
1643 committing subrepository s
1665 committing subrepository s
1644 $ hg phase -r .
1666 $ hg phase -r .
1645 6: secret
1667 6: secret
1646 $ cd s
1668 $ cd s
1647 $ hg phase -r .
1669 $ hg phase -r .
1648 6: secret
1670 6: secret
1649 $ cd ../../
1671 $ cd ../../
1650
1672
1651 Test "subrepos" template keyword
1673 Test "subrepos" template keyword
1652
1674
1653 $ cd t
1675 $ cd t
1654 $ hg update -q 15
1676 $ hg update -q 15
1655 $ cat > .hgsub <<EOF
1677 $ cat > .hgsub <<EOF
1656 > s = s
1678 > s = s
1657 > EOF
1679 > EOF
1658 $ hg commit -m "16"
1680 $ hg commit -m "16"
1659 warning: changes are committed in secret phase from subrepository s
1681 warning: changes are committed in secret phase from subrepository s
1660
1682
1661 (addition of ".hgsub" itself)
1683 (addition of ".hgsub" itself)
1662
1684
1663 $ hg diff --nodates -c 1 .hgsubstate
1685 $ hg diff --nodates -c 1 .hgsubstate
1664 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1686 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1665 --- /dev/null
1687 --- /dev/null
1666 +++ b/.hgsubstate
1688 +++ b/.hgsubstate
1667 @@ -0,0 +1,1 @@
1689 @@ -0,0 +1,1 @@
1668 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1690 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1669 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1691 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1670 f7b1eb17ad24 000000000000
1692 f7b1eb17ad24 000000000000
1671 s
1693 s
1672
1694
1673 (modification of existing entry)
1695 (modification of existing entry)
1674
1696
1675 $ hg diff --nodates -c 2 .hgsubstate
1697 $ hg diff --nodates -c 2 .hgsubstate
1676 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1698 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1677 --- a/.hgsubstate
1699 --- a/.hgsubstate
1678 +++ b/.hgsubstate
1700 +++ b/.hgsubstate
1679 @@ -1,1 +1,1 @@
1701 @@ -1,1 +1,1 @@
1680 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1702 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1681 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1703 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1682 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1704 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1683 7cf8cfea66e4 000000000000
1705 7cf8cfea66e4 000000000000
1684 s
1706 s
1685
1707
1686 (addition of entry)
1708 (addition of entry)
1687
1709
1688 $ hg diff --nodates -c 5 .hgsubstate
1710 $ hg diff --nodates -c 5 .hgsubstate
1689 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1711 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1690 --- a/.hgsubstate
1712 --- a/.hgsubstate
1691 +++ b/.hgsubstate
1713 +++ b/.hgsubstate
1692 @@ -1,1 +1,2 @@
1714 @@ -1,1 +1,2 @@
1693 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1715 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1694 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1716 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1695 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1717 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1696 7cf8cfea66e4 000000000000
1718 7cf8cfea66e4 000000000000
1697 t
1719 t
1698
1720
1699 (removal of existing entry)
1721 (removal of existing entry)
1700
1722
1701 $ hg diff --nodates -c 16 .hgsubstate
1723 $ hg diff --nodates -c 16 .hgsubstate
1702 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1724 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1703 --- a/.hgsubstate
1725 --- a/.hgsubstate
1704 +++ b/.hgsubstate
1726 +++ b/.hgsubstate
1705 @@ -1,2 +1,1 @@
1727 @@ -1,2 +1,1 @@
1706 0731af8ca9423976d3743119d0865097c07bdc1b s
1728 0731af8ca9423976d3743119d0865097c07bdc1b s
1707 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1729 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1708 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1730 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1709 8bec38d2bd0b 000000000000
1731 8bec38d2bd0b 000000000000
1710 t
1732 t
1711
1733
1712 (merging)
1734 (merging)
1713
1735
1714 $ hg diff --nodates -c 9 .hgsubstate
1736 $ hg diff --nodates -c 9 .hgsubstate
1715 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1737 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1716 --- a/.hgsubstate
1738 --- a/.hgsubstate
1717 +++ b/.hgsubstate
1739 +++ b/.hgsubstate
1718 @@ -1,1 +1,2 @@
1740 @@ -1,1 +1,2 @@
1719 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1741 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1720 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1742 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1721 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1743 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1722 f6affe3fbfaa 1f14a2e2d3ec
1744 f6affe3fbfaa 1f14a2e2d3ec
1723 t
1745 t
1724
1746
1725 (removal of ".hgsub" itself)
1747 (removal of ".hgsub" itself)
1726
1748
1727 $ hg diff --nodates -c 8 .hgsubstate
1749 $ hg diff --nodates -c 8 .hgsubstate
1728 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1750 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1729 --- a/.hgsubstate
1751 --- a/.hgsubstate
1730 +++ /dev/null
1752 +++ /dev/null
1731 @@ -1,2 +0,0 @@
1753 @@ -1,2 +0,0 @@
1732 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1754 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1733 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1755 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1734 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1756 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1735 f94576341bcf 000000000000
1757 f94576341bcf 000000000000
1736
1758
1737 Test that '[paths]' is configured correctly at subrepo creation
1759 Test that '[paths]' is configured correctly at subrepo creation
1738
1760
1739 $ cd $TESTTMP/tc
1761 $ cd $TESTTMP/tc
1740 $ cat > .hgsub <<EOF
1762 $ cat > .hgsub <<EOF
1741 > # to clear bogus subrepo path 'bogus=[boguspath'
1763 > # to clear bogus subrepo path 'bogus=[boguspath'
1742 > s = s
1764 > s = s
1743 > t = t
1765 > t = t
1744 > EOF
1766 > EOF
1745 $ hg update -q --clean null
1767 $ hg update -q --clean null
1746 $ rm -rf s t
1768 $ rm -rf s t
1747 $ cat >> .hg/hgrc <<EOF
1769 $ cat >> .hg/hgrc <<EOF
1748 > [paths]
1770 > [paths]
1749 > default-push = /foo/bar
1771 > default-push = /foo/bar
1750 > EOF
1772 > EOF
1751 $ hg update -q
1773 $ hg update -q
1752 $ cat s/.hg/hgrc
1774 $ cat s/.hg/hgrc
1753 [paths]
1775 [paths]
1754 default = $TESTTMP/t/s
1776 default = $TESTTMP/t/s
1755 default-push = /foo/bar/s
1777 default-push = /foo/bar/s
1756 $ cat s/ss/.hg/hgrc
1778 $ cat s/ss/.hg/hgrc
1757 [paths]
1779 [paths]
1758 default = $TESTTMP/t/s/ss
1780 default = $TESTTMP/t/s/ss
1759 default-push = /foo/bar/s/ss
1781 default-push = /foo/bar/s/ss
1760 $ cat t/.hg/hgrc
1782 $ cat t/.hg/hgrc
1761 [paths]
1783 [paths]
1762 default = $TESTTMP/t/t
1784 default = $TESTTMP/t/t
1763 default-push = /foo/bar/t
1785 default-push = /foo/bar/t
1764
1786
1765 $ cd $TESTTMP/t
1787 $ cd $TESTTMP/t
1766 $ hg up -qC 0
1788 $ hg up -qC 0
1767 $ echo 'bar' > bar.txt
1789 $ echo 'bar' > bar.txt
1768 $ hg ci -Am 'branch before subrepo add'
1790 $ hg ci -Am 'branch before subrepo add'
1769 adding bar.txt
1791 adding bar.txt
1770 created new head
1792 created new head
1771 $ hg merge -r "first(subrepo('s'))"
1793 $ hg merge -r "first(subrepo('s'))"
1772 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1794 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1773 (branch merge, don't forget to commit)
1795 (branch merge, don't forget to commit)
1774 $ hg status -S -X '.hgsub*'
1796 $ hg status -S -X '.hgsub*'
1775 A s/a
1797 A s/a
1776 ? s/b
1798 ? s/b
1777 ? s/c
1799 ? s/c
1778 ? s/f1
1800 ? s/f1
1779 $ hg status -S --rev 'p2()'
1801 $ hg status -S --rev 'p2()'
1780 A bar.txt
1802 A bar.txt
1781 ? s/b
1803 ? s/b
1782 ? s/c
1804 ? s/c
1783 ? s/f1
1805 ? s/f1
1784 $ hg diff -S -X '.hgsub*' --nodates
1806 $ hg diff -S -X '.hgsub*' --nodates
1785 diff -r 000000000000 s/a
1807 diff -r 000000000000 s/a
1786 --- /dev/null
1808 --- /dev/null
1787 +++ b/s/a
1809 +++ b/s/a
1788 @@ -0,0 +1,1 @@
1810 @@ -0,0 +1,1 @@
1789 +a
1811 +a
1790 $ hg diff -S --rev 'p2()' --nodates
1812 $ hg diff -S --rev 'p2()' --nodates
1791 diff -r 7cf8cfea66e4 bar.txt
1813 diff -r 7cf8cfea66e4 bar.txt
1792 --- /dev/null
1814 --- /dev/null
1793 +++ b/bar.txt
1815 +++ b/bar.txt
1794 @@ -0,0 +1,1 @@
1816 @@ -0,0 +1,1 @@
1795 +bar
1817 +bar
1796
1818
1797 $ cd ..
1819 $ cd ..
1798
1820
1799 test for ssh exploit 2017-07-25
1821 test for ssh exploit 2017-07-25
1800
1822
1801 $ cat >> $HGRCPATH << EOF
1823 $ cat >> $HGRCPATH << EOF
1802 > [ui]
1824 > [ui]
1803 > ssh = sh -c "read l; read l; read l"
1825 > ssh = sh -c "read l; read l; read l"
1804 > EOF
1826 > EOF
1805
1827
1806 $ hg init malicious-proxycommand
1828 $ hg init malicious-proxycommand
1807 $ cd malicious-proxycommand
1829 $ cd malicious-proxycommand
1808 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1830 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1809 $ hg init s
1831 $ hg init s
1810 $ cd s
1832 $ cd s
1811 $ echo init > init
1833 $ echo init > init
1812 $ hg add
1834 $ hg add
1813 adding init
1835 adding init
1814 $ hg commit -m init
1836 $ hg commit -m init
1815 $ cd ..
1837 $ cd ..
1816 $ hg add .hgsub
1838 $ hg add .hgsub
1817 $ hg ci -m 'add subrepo'
1839 $ hg ci -m 'add subrepo'
1818 $ cd ..
1840 $ cd ..
1819 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1841 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1820 updating to branch default
1842 updating to branch default
1821 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1843 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1822 [255]
1844 [255]
1823
1845
1824 also check that a percent encoded '-' (%2D) doesn't work
1846 also check that a percent encoded '-' (%2D) doesn't work
1825
1847
1826 $ cd malicious-proxycommand
1848 $ cd malicious-proxycommand
1827 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1849 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1828 $ hg ci -m 'change url to percent encoded'
1850 $ hg ci -m 'change url to percent encoded'
1829 $ cd ..
1851 $ cd ..
1830 $ rm -r malicious-proxycommand-clone
1852 $ rm -r malicious-proxycommand-clone
1831 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1853 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1832 updating to branch default
1854 updating to branch default
1833 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1855 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1834 [255]
1856 [255]
1835
1857
1836 also check for a pipe
1858 also check for a pipe
1837
1859
1838 $ cd malicious-proxycommand
1860 $ cd malicious-proxycommand
1839 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1861 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1840 $ hg ci -m 'change url to pipe'
1862 $ hg ci -m 'change url to pipe'
1841 $ cd ..
1863 $ cd ..
1842 $ rm -r malicious-proxycommand-clone
1864 $ rm -r malicious-proxycommand-clone
1843 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1865 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1844 updating to branch default
1866 updating to branch default
1845 abort: no suitable response from remote hg!
1867 abort: no suitable response from remote hg!
1846 [255]
1868 [255]
1847 $ [ ! -f owned ] || echo 'you got owned'
1869 $ [ ! -f owned ] || echo 'you got owned'
1848
1870
1849 also check that a percent encoded '|' (%7C) doesn't work
1871 also check that a percent encoded '|' (%7C) doesn't work
1850
1872
1851 $ cd malicious-proxycommand
1873 $ cd malicious-proxycommand
1852 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1874 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1853 $ hg ci -m 'change url to percent encoded pipe'
1875 $ hg ci -m 'change url to percent encoded pipe'
1854 $ cd ..
1876 $ cd ..
1855 $ rm -r malicious-proxycommand-clone
1877 $ rm -r malicious-proxycommand-clone
1856 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1878 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1857 updating to branch default
1879 updating to branch default
1858 abort: no suitable response from remote hg!
1880 abort: no suitable response from remote hg!
1859 [255]
1881 [255]
1860 $ [ ! -f owned ] || echo 'you got owned'
1882 $ [ ! -f owned ] || echo 'you got owned'
1861
1883
1862 and bad usernames:
1884 and bad usernames:
1863 $ cd malicious-proxycommand
1885 $ cd malicious-proxycommand
1864 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1886 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1865 $ hg ci -m 'owned username'
1887 $ hg ci -m 'owned username'
1866 $ cd ..
1888 $ cd ..
1867 $ rm -r malicious-proxycommand-clone
1889 $ rm -r malicious-proxycommand-clone
1868 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1890 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1869 updating to branch default
1891 updating to branch default
1870 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1892 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1871 [255]
1893 [255]
General Comments 0
You need to be logged in to leave comments. Login now