##// END OF EJS Templates
share: add option to share bookmarks...
Ryan McElroy -
r23614:cd79fb4d default
parent child Browse files
Show More
@@ -1,129 +1,130 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 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial import cmdutil, hg, util, extensions, bookmarks
9 from mercurial import cmdutil, hg, util, extensions, bookmarks
10 from mercurial.hg import repository, parseurl
10 from mercurial.hg import repository, parseurl
11 import errno
11 import errno
12
12
13 cmdtable = {}
13 cmdtable = {}
14 command = cmdutil.command(cmdtable)
14 command = cmdutil.command(cmdtable)
15 testedwith = 'internal'
15 testedwith = 'internal'
16
16
17 @command('share',
17 @command('share',
18 [('U', 'noupdate', None, _('do not create a working copy'))],
18 [('U', 'noupdate', None, _('do not create a working copy')),
19 _('[-U] SOURCE [DEST]'),
19 ('B', 'bookmarks', None, _('also share bookmarks'))],
20 _('[-U] [-B] SOURCE [DEST]'),
20 norepo=True)
21 norepo=True)
21 def share(ui, source, dest=None, noupdate=False):
22 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
22 """create a new shared repository
23 """create a new shared repository
23
24
24 Initialize a new repository and working directory that shares its
25 Initialize a new repository and working directory that shares its
25 history with another repository.
26 history (and optionally bookmarks) with another repository.
26
27
27 .. note::
28 .. note::
28
29
29 using rollback or extensions that destroy/modify history (mq,
30 using rollback or extensions that destroy/modify history (mq,
30 rebase, etc.) can cause considerable confusion with shared
31 rebase, etc.) can cause considerable confusion with shared
31 clones. In particular, if two shared clones are both updated to
32 clones. In particular, if two shared clones are both updated to
32 the same changeset, and one of them destroys that changeset
33 the same changeset, and one of them destroys that changeset
33 with rollback, the other clone will suddenly stop working: all
34 with rollback, the other clone will suddenly stop working: all
34 operations will fail with "abort: working directory has unknown
35 operations will fail with "abort: working directory has unknown
35 parent". The only known workaround is to use debugsetparents on
36 parent". The only known workaround is to use debugsetparents on
36 the broken clone to reset it to a changeset that still exists.
37 the broken clone to reset it to a changeset that still exists.
37 """
38 """
38
39
39 return hg.share(ui, source, dest, not noupdate)
40 return hg.share(ui, source, dest, not noupdate, bookmarks)
40
41
41 @command('unshare', [], '')
42 @command('unshare', [], '')
42 def unshare(ui, repo):
43 def unshare(ui, repo):
43 """convert a shared repository to a normal one
44 """convert a shared repository to a normal one
44
45
45 Copy the store data to the repo and remove the sharedpath data.
46 Copy the store data to the repo and remove the sharedpath data.
46 """
47 """
47
48
48 if repo.sharedpath == repo.path:
49 if repo.sharedpath == repo.path:
49 raise util.Abort(_("this is not a shared repo"))
50 raise util.Abort(_("this is not a shared repo"))
50
51
51 destlock = lock = None
52 destlock = lock = None
52 lock = repo.lock()
53 lock = repo.lock()
53 try:
54 try:
54 # we use locks here because if we race with commit, we
55 # we use locks here because if we race with commit, we
55 # can end up with extra data in the cloned revlogs that's
56 # can end up with extra data in the cloned revlogs that's
56 # not pointed to by changesets, thus causing verify to
57 # not pointed to by changesets, thus causing verify to
57 # fail
58 # fail
58
59
59 destlock = hg.copystore(ui, repo, repo.path)
60 destlock = hg.copystore(ui, repo, repo.path)
60
61
61 sharefile = repo.join('sharedpath')
62 sharefile = repo.join('sharedpath')
62 util.rename(sharefile, sharefile + '.old')
63 util.rename(sharefile, sharefile + '.old')
63
64
64 repo.requirements.discard('sharedpath')
65 repo.requirements.discard('sharedpath')
65 repo._writerequirements()
66 repo._writerequirements()
66 finally:
67 finally:
67 destlock and destlock.release()
68 destlock and destlock.release()
68 lock and lock.release()
69 lock and lock.release()
69
70
70 # update store, spath, sopener and sjoin of repo
71 # update store, spath, sopener and sjoin of repo
71 repo.unfiltered().__init__(repo.baseui, repo.root)
72 repo.unfiltered().__init__(repo.baseui, repo.root)
72
73
73 def extsetup(ui):
74 def extsetup(ui):
74 extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
75 extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
75 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
76 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
76 extensions.wrapfunction(bookmarks.bmstore, 'write', write)
77 extensions.wrapfunction(bookmarks.bmstore, 'write', write)
77
78
78 def _hassharedbookmarks(repo):
79 def _hassharedbookmarks(repo):
79 """Returns whether this repo has shared bookmarks"""
80 """Returns whether this repo has shared bookmarks"""
80 try:
81 try:
81 repo.vfs.read('bookmarks.shared')
82 repo.vfs.read('bookmarks.shared')
82 return True
83 return True
83 except IOError, inst:
84 except IOError, inst:
84 if inst.errno != errno.ENOENT:
85 if inst.errno != errno.ENOENT:
85 raise
86 raise
86 return False
87 return False
87
88
88 def _getsrcrepo(repo):
89 def _getsrcrepo(repo):
89 """
90 """
90 Returns the source repository object for a given shared repository.
91 Returns the source repository object for a given shared repository.
91 If repo is not a shared repository, return None.
92 If repo is not a shared repository, return None.
92 """
93 """
93 srcrepo = None
94 srcrepo = None
94 try:
95 try:
95 # strip because some tools write with newline after
96 # strip because some tools write with newline after
96 sharedpath = repo.vfs.read('sharedpath').strip()
97 sharedpath = repo.vfs.read('sharedpath').strip()
97 # the sharedpath always ends in the .hg; we want the path to the repo
98 # the sharedpath always ends in the .hg; we want the path to the repo
98 source = sharedpath.rsplit('/.hg', 1)[0]
99 source = sharedpath.rsplit('/.hg', 1)[0]
99 srcurl, branches = parseurl(source)
100 srcurl, branches = parseurl(source)
100 srcrepo = repository(repo.ui, srcurl)
101 srcrepo = repository(repo.ui, srcurl)
101 except IOError, inst:
102 except IOError, inst:
102 if inst.errno != errno.ENOENT:
103 if inst.errno != errno.ENOENT:
103 raise
104 raise
104 return srcrepo
105 return srcrepo
105
106
106 def getbkfile(orig, self, repo):
107 def getbkfile(orig, self, repo):
107 if _hassharedbookmarks(repo):
108 if _hassharedbookmarks(repo):
108 srcrepo = _getsrcrepo(repo)
109 srcrepo = _getsrcrepo(repo)
109 if srcrepo is not None:
110 if srcrepo is not None:
110 repo = srcrepo
111 repo = srcrepo
111 return orig(self, repo)
112 return orig(self, repo)
112
113
113 def recordchange(orig, self, tr):
114 def recordchange(orig, self, tr):
114 # Continue with write to local bookmarks file as usual
115 # Continue with write to local bookmarks file as usual
115 orig(self, tr)
116 orig(self, tr)
116
117
117 if _hassharedbookmarks(self._repo):
118 if _hassharedbookmarks(self._repo):
118 srcrepo = _getsrcrepo(self._repo)
119 srcrepo = _getsrcrepo(self._repo)
119 if srcrepo is not None:
120 if srcrepo is not None:
120 category = 'share-bookmarks'
121 category = 'share-bookmarks'
121 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
122 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
122
123
123 def write(orig, self):
124 def write(orig, self):
124 # First write local bookmarks file in case we ever unshare
125 # First write local bookmarks file in case we ever unshare
125 orig(self)
126 orig(self)
126 if _hassharedbookmarks(self._repo):
127 if _hassharedbookmarks(self._repo):
127 srcrepo = _getsrcrepo(self._repo)
128 srcrepo = _getsrcrepo(self._repo)
128 if srcrepo is not None:
129 if srcrepo is not None:
129 self._writerepo(srcrepo)
130 self._writerepo(srcrepo)
@@ -1,673 +1,676 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 i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import nullid
11 from node import nullid
12
12
13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
15 import cmdutil, discovery, repoview, exchange
15 import cmdutil, discovery, repoview, exchange
16 import ui as uimod
16 import ui as uimod
17 import merge as mergemod
17 import merge as mergemod
18 import verify as verifymod
18 import verify as verifymod
19 import errno, os, shutil
19 import errno, os, shutil
20
20
21 def _local(path):
21 def _local(path):
22 path = util.expandpath(util.urllocalpath(path))
22 path = util.expandpath(util.urllocalpath(path))
23 return (os.path.isfile(path) and bundlerepo or localrepo)
23 return (os.path.isfile(path) and bundlerepo or localrepo)
24
24
25 def addbranchrevs(lrepo, other, branches, revs):
25 def addbranchrevs(lrepo, other, branches, revs):
26 peer = other.peer() # a courtesy to callers using a localrepo for other
26 peer = other.peer() # a courtesy to callers using a localrepo for other
27 hashbranch, branches = branches
27 hashbranch, branches = branches
28 if not hashbranch and not branches:
28 if not hashbranch and not branches:
29 x = revs or None
29 x = revs or None
30 if util.safehasattr(revs, 'first'):
30 if util.safehasattr(revs, 'first'):
31 y = revs.first()
31 y = revs.first()
32 elif revs:
32 elif revs:
33 y = revs[0]
33 y = revs[0]
34 else:
34 else:
35 y = None
35 y = None
36 return x, y
36 return x, y
37 revs = revs and list(revs) or []
37 revs = revs and list(revs) or []
38 if not peer.capable('branchmap'):
38 if not peer.capable('branchmap'):
39 if branches:
39 if branches:
40 raise util.Abort(_("remote branch lookup not supported"))
40 raise util.Abort(_("remote branch lookup not supported"))
41 revs.append(hashbranch)
41 revs.append(hashbranch)
42 return revs, revs[0]
42 return revs, revs[0]
43 branchmap = peer.branchmap()
43 branchmap = peer.branchmap()
44
44
45 def primary(branch):
45 def primary(branch):
46 if branch == '.':
46 if branch == '.':
47 if not lrepo:
47 if not lrepo:
48 raise util.Abort(_("dirstate branch not accessible"))
48 raise util.Abort(_("dirstate branch not accessible"))
49 branch = lrepo.dirstate.branch()
49 branch = lrepo.dirstate.branch()
50 if branch in branchmap:
50 if branch in branchmap:
51 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
51 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
52 return True
52 return True
53 else:
53 else:
54 return False
54 return False
55
55
56 for branch in branches:
56 for branch in branches:
57 if not primary(branch):
57 if not primary(branch):
58 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
58 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
59 if hashbranch:
59 if hashbranch:
60 if not primary(hashbranch):
60 if not primary(hashbranch):
61 revs.append(hashbranch)
61 revs.append(hashbranch)
62 return revs, revs[0]
62 return revs, revs[0]
63
63
64 def parseurl(path, branches=None):
64 def parseurl(path, branches=None):
65 '''parse url#branch, returning (url, (branch, branches))'''
65 '''parse url#branch, returning (url, (branch, branches))'''
66
66
67 u = util.url(path)
67 u = util.url(path)
68 branch = None
68 branch = None
69 if u.fragment:
69 if u.fragment:
70 branch = u.fragment
70 branch = u.fragment
71 u.fragment = None
71 u.fragment = None
72 return str(u), (branch, branches or [])
72 return str(u), (branch, branches or [])
73
73
74 schemes = {
74 schemes = {
75 'bundle': bundlerepo,
75 'bundle': bundlerepo,
76 'union': unionrepo,
76 'union': unionrepo,
77 'file': _local,
77 'file': _local,
78 'http': httppeer,
78 'http': httppeer,
79 'https': httppeer,
79 'https': httppeer,
80 'ssh': sshpeer,
80 'ssh': sshpeer,
81 'static-http': statichttprepo,
81 'static-http': statichttprepo,
82 }
82 }
83
83
84 def _peerlookup(path):
84 def _peerlookup(path):
85 u = util.url(path)
85 u = util.url(path)
86 scheme = u.scheme or 'file'
86 scheme = u.scheme or 'file'
87 thing = schemes.get(scheme) or schemes['file']
87 thing = schemes.get(scheme) or schemes['file']
88 try:
88 try:
89 return thing(path)
89 return thing(path)
90 except TypeError:
90 except TypeError:
91 return thing
91 return thing
92
92
93 def islocal(repo):
93 def islocal(repo):
94 '''return true if repo (or path pointing to repo) is local'''
94 '''return true if repo (or path pointing to repo) is local'''
95 if isinstance(repo, str):
95 if isinstance(repo, str):
96 try:
96 try:
97 return _peerlookup(repo).islocal(repo)
97 return _peerlookup(repo).islocal(repo)
98 except AttributeError:
98 except AttributeError:
99 return False
99 return False
100 return repo.local()
100 return repo.local()
101
101
102 def openpath(ui, path):
102 def openpath(ui, path):
103 '''open path with open if local, url.open if remote'''
103 '''open path with open if local, url.open if remote'''
104 pathurl = util.url(path, parsequery=False, parsefragment=False)
104 pathurl = util.url(path, parsequery=False, parsefragment=False)
105 if pathurl.islocal():
105 if pathurl.islocal():
106 return util.posixfile(pathurl.localpath(), 'rb')
106 return util.posixfile(pathurl.localpath(), 'rb')
107 else:
107 else:
108 return url.open(ui, path)
108 return url.open(ui, path)
109
109
110 # a list of (ui, repo) functions called for wire peer initialization
110 # a list of (ui, repo) functions called for wire peer initialization
111 wirepeersetupfuncs = []
111 wirepeersetupfuncs = []
112
112
113 def _peerorrepo(ui, path, create=False):
113 def _peerorrepo(ui, path, create=False):
114 """return a repository object for the specified path"""
114 """return a repository object for the specified path"""
115 obj = _peerlookup(path).instance(ui, path, create)
115 obj = _peerlookup(path).instance(ui, path, create)
116 ui = getattr(obj, "ui", ui)
116 ui = getattr(obj, "ui", ui)
117 for name, module in extensions.extensions(ui):
117 for name, module in extensions.extensions(ui):
118 hook = getattr(module, 'reposetup', None)
118 hook = getattr(module, 'reposetup', None)
119 if hook:
119 if hook:
120 hook(ui, obj)
120 hook(ui, obj)
121 if not obj.local():
121 if not obj.local():
122 for f in wirepeersetupfuncs:
122 for f in wirepeersetupfuncs:
123 f(ui, obj)
123 f(ui, obj)
124 return obj
124 return obj
125
125
126 def repository(ui, path='', create=False):
126 def repository(ui, path='', create=False):
127 """return a repository object for the specified path"""
127 """return a repository object for the specified path"""
128 peer = _peerorrepo(ui, path, create)
128 peer = _peerorrepo(ui, path, create)
129 repo = peer.local()
129 repo = peer.local()
130 if not repo:
130 if not repo:
131 raise util.Abort(_("repository '%s' is not local") %
131 raise util.Abort(_("repository '%s' is not local") %
132 (path or peer.url()))
132 (path or peer.url()))
133 return repo.filtered('visible')
133 return repo.filtered('visible')
134
134
135 def peer(uiorrepo, opts, path, create=False):
135 def peer(uiorrepo, opts, path, create=False):
136 '''return a repository peer for the specified path'''
136 '''return a repository peer for the specified path'''
137 rui = remoteui(uiorrepo, opts)
137 rui = remoteui(uiorrepo, opts)
138 return _peerorrepo(rui, path, create).peer()
138 return _peerorrepo(rui, path, create).peer()
139
139
140 def defaultdest(source):
140 def defaultdest(source):
141 '''return default destination of clone if none is given
141 '''return default destination of clone if none is given
142
142
143 >>> defaultdest('foo')
143 >>> defaultdest('foo')
144 'foo'
144 'foo'
145 >>> defaultdest('/foo/bar')
145 >>> defaultdest('/foo/bar')
146 'bar'
146 'bar'
147 >>> defaultdest('/')
147 >>> defaultdest('/')
148 ''
148 ''
149 >>> defaultdest('')
149 >>> defaultdest('')
150 ''
150 ''
151 >>> defaultdest('http://example.org/')
151 >>> defaultdest('http://example.org/')
152 ''
152 ''
153 >>> defaultdest('http://example.org/foo/')
153 >>> defaultdest('http://example.org/foo/')
154 'foo'
154 'foo'
155 '''
155 '''
156 path = util.url(source).path
156 path = util.url(source).path
157 if not path:
157 if not path:
158 return ''
158 return ''
159 return os.path.basename(os.path.normpath(path))
159 return os.path.basename(os.path.normpath(path))
160
160
161 def share(ui, source, dest=None, update=True):
161 def share(ui, source, dest=None, update=True, bookmarks=True):
162 '''create a shared repository'''
162 '''create a shared repository'''
163
163
164 if not islocal(source):
164 if not islocal(source):
165 raise util.Abort(_('can only share local repositories'))
165 raise util.Abort(_('can only share local repositories'))
166
166
167 if not dest:
167 if not dest:
168 dest = defaultdest(source)
168 dest = defaultdest(source)
169 else:
169 else:
170 dest = ui.expandpath(dest)
170 dest = ui.expandpath(dest)
171
171
172 if isinstance(source, str):
172 if isinstance(source, str):
173 origsource = ui.expandpath(source)
173 origsource = ui.expandpath(source)
174 source, branches = parseurl(origsource)
174 source, branches = parseurl(origsource)
175 srcrepo = repository(ui, source)
175 srcrepo = repository(ui, source)
176 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
176 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
177 else:
177 else:
178 srcrepo = source.local()
178 srcrepo = source.local()
179 origsource = source = srcrepo.url()
179 origsource = source = srcrepo.url()
180 checkout = None
180 checkout = None
181
181
182 sharedpath = srcrepo.sharedpath # if our source is already sharing
182 sharedpath = srcrepo.sharedpath # if our source is already sharing
183
183
184 destwvfs = scmutil.vfs(dest, realpath=True)
184 destwvfs = scmutil.vfs(dest, realpath=True)
185 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
185 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
186
186
187 if destvfs.lexists():
187 if destvfs.lexists():
188 raise util.Abort(_('destination already exists'))
188 raise util.Abort(_('destination already exists'))
189
189
190 if not destwvfs.isdir():
190 if not destwvfs.isdir():
191 destwvfs.mkdir()
191 destwvfs.mkdir()
192 destvfs.makedir()
192 destvfs.makedir()
193
193
194 requirements = ''
194 requirements = ''
195 try:
195 try:
196 requirements = srcrepo.opener.read('requires')
196 requirements = srcrepo.opener.read('requires')
197 except IOError, inst:
197 except IOError, inst:
198 if inst.errno != errno.ENOENT:
198 if inst.errno != errno.ENOENT:
199 raise
199 raise
200
200
201 requirements += 'shared\n'
201 requirements += 'shared\n'
202 destvfs.write('requires', requirements)
202 destvfs.write('requires', requirements)
203 destvfs.write('sharedpath', sharedpath)
203 destvfs.write('sharedpath', sharedpath)
204
204
205 r = repository(ui, destwvfs.base)
205 r = repository(ui, destwvfs.base)
206
206
207 default = srcrepo.ui.config('paths', 'default')
207 default = srcrepo.ui.config('paths', 'default')
208 if default:
208 if default:
209 fp = r.opener("hgrc", "w", text=True)
209 fp = r.opener("hgrc", "w", text=True)
210 fp.write("[paths]\n")
210 fp.write("[paths]\n")
211 fp.write("default = %s\n" % default)
211 fp.write("default = %s\n" % default)
212 fp.close()
212 fp.close()
213
213
214 if update:
214 if update:
215 r.ui.status(_("updating working directory\n"))
215 r.ui.status(_("updating working directory\n"))
216 if update is not True:
216 if update is not True:
217 checkout = update
217 checkout = update
218 for test in (checkout, 'default', 'tip'):
218 for test in (checkout, 'default', 'tip'):
219 if test is None:
219 if test is None:
220 continue
220 continue
221 try:
221 try:
222 uprev = r.lookup(test)
222 uprev = r.lookup(test)
223 break
223 break
224 except error.RepoLookupError:
224 except error.RepoLookupError:
225 continue
225 continue
226 _update(r, uprev)
226 _update(r, uprev)
227
227
228 if bookmarks:
229 r.opener('bookmarks.shared', 'w').close()
230
228 def copystore(ui, srcrepo, destpath):
231 def copystore(ui, srcrepo, destpath):
229 '''copy files from store of srcrepo in destpath
232 '''copy files from store of srcrepo in destpath
230
233
231 returns destlock
234 returns destlock
232 '''
235 '''
233 destlock = None
236 destlock = None
234 try:
237 try:
235 hardlink = None
238 hardlink = None
236 num = 0
239 num = 0
237 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
240 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
238 srcvfs = scmutil.vfs(srcrepo.sharedpath)
241 srcvfs = scmutil.vfs(srcrepo.sharedpath)
239 dstvfs = scmutil.vfs(destpath)
242 dstvfs = scmutil.vfs(destpath)
240 for f in srcrepo.store.copylist():
243 for f in srcrepo.store.copylist():
241 if srcpublishing and f.endswith('phaseroots'):
244 if srcpublishing and f.endswith('phaseroots'):
242 continue
245 continue
243 dstbase = os.path.dirname(f)
246 dstbase = os.path.dirname(f)
244 if dstbase and not dstvfs.exists(dstbase):
247 if dstbase and not dstvfs.exists(dstbase):
245 dstvfs.mkdir(dstbase)
248 dstvfs.mkdir(dstbase)
246 if srcvfs.exists(f):
249 if srcvfs.exists(f):
247 if f.endswith('data'):
250 if f.endswith('data'):
248 # 'dstbase' may be empty (e.g. revlog format 0)
251 # 'dstbase' may be empty (e.g. revlog format 0)
249 lockfile = os.path.join(dstbase, "lock")
252 lockfile = os.path.join(dstbase, "lock")
250 # lock to avoid premature writing to the target
253 # lock to avoid premature writing to the target
251 destlock = lock.lock(dstvfs, lockfile)
254 destlock = lock.lock(dstvfs, lockfile)
252 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
255 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
253 hardlink)
256 hardlink)
254 num += n
257 num += n
255 if hardlink:
258 if hardlink:
256 ui.debug("linked %d files\n" % num)
259 ui.debug("linked %d files\n" % num)
257 else:
260 else:
258 ui.debug("copied %d files\n" % num)
261 ui.debug("copied %d files\n" % num)
259 return destlock
262 return destlock
260 except: # re-raises
263 except: # re-raises
261 release(destlock)
264 release(destlock)
262 raise
265 raise
263
266
264 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
267 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
265 update=True, stream=False, branch=None):
268 update=True, stream=False, branch=None):
266 """Make a copy of an existing repository.
269 """Make a copy of an existing repository.
267
270
268 Create a copy of an existing repository in a new directory. The
271 Create a copy of an existing repository in a new directory. The
269 source and destination are URLs, as passed to the repository
272 source and destination are URLs, as passed to the repository
270 function. Returns a pair of repository peers, the source and
273 function. Returns a pair of repository peers, the source and
271 newly created destination.
274 newly created destination.
272
275
273 The location of the source is added to the new repository's
276 The location of the source is added to the new repository's
274 .hg/hgrc file, as the default to be used for future pulls and
277 .hg/hgrc file, as the default to be used for future pulls and
275 pushes.
278 pushes.
276
279
277 If an exception is raised, the partly cloned/updated destination
280 If an exception is raised, the partly cloned/updated destination
278 repository will be deleted.
281 repository will be deleted.
279
282
280 Arguments:
283 Arguments:
281
284
282 source: repository object or URL
285 source: repository object or URL
283
286
284 dest: URL of destination repository to create (defaults to base
287 dest: URL of destination repository to create (defaults to base
285 name of source repository)
288 name of source repository)
286
289
287 pull: always pull from source repository, even in local case or if the
290 pull: always pull from source repository, even in local case or if the
288 server prefers streaming
291 server prefers streaming
289
292
290 stream: stream raw data uncompressed from repository (fast over
293 stream: stream raw data uncompressed from repository (fast over
291 LAN, slow over WAN)
294 LAN, slow over WAN)
292
295
293 rev: revision to clone up to (implies pull=True)
296 rev: revision to clone up to (implies pull=True)
294
297
295 update: update working directory after clone completes, if
298 update: update working directory after clone completes, if
296 destination is local repository (True means update to default rev,
299 destination is local repository (True means update to default rev,
297 anything else is treated as a revision)
300 anything else is treated as a revision)
298
301
299 branch: branches to clone
302 branch: branches to clone
300 """
303 """
301
304
302 if isinstance(source, str):
305 if isinstance(source, str):
303 origsource = ui.expandpath(source)
306 origsource = ui.expandpath(source)
304 source, branch = parseurl(origsource, branch)
307 source, branch = parseurl(origsource, branch)
305 srcpeer = peer(ui, peeropts, source)
308 srcpeer = peer(ui, peeropts, source)
306 else:
309 else:
307 srcpeer = source.peer() # in case we were called with a localrepo
310 srcpeer = source.peer() # in case we were called with a localrepo
308 branch = (None, branch or [])
311 branch = (None, branch or [])
309 origsource = source = srcpeer.url()
312 origsource = source = srcpeer.url()
310 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
313 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
311
314
312 if dest is None:
315 if dest is None:
313 dest = defaultdest(source)
316 dest = defaultdest(source)
314 if dest:
317 if dest:
315 ui.status(_("destination directory: %s\n") % dest)
318 ui.status(_("destination directory: %s\n") % dest)
316 else:
319 else:
317 dest = ui.expandpath(dest)
320 dest = ui.expandpath(dest)
318
321
319 dest = util.urllocalpath(dest)
322 dest = util.urllocalpath(dest)
320 source = util.urllocalpath(source)
323 source = util.urllocalpath(source)
321
324
322 if not dest:
325 if not dest:
323 raise util.Abort(_("empty destination path is not valid"))
326 raise util.Abort(_("empty destination path is not valid"))
324
327
325 destvfs = scmutil.vfs(dest, expandpath=True)
328 destvfs = scmutil.vfs(dest, expandpath=True)
326 if destvfs.lexists():
329 if destvfs.lexists():
327 if not destvfs.isdir():
330 if not destvfs.isdir():
328 raise util.Abort(_("destination '%s' already exists") % dest)
331 raise util.Abort(_("destination '%s' already exists") % dest)
329 elif destvfs.listdir():
332 elif destvfs.listdir():
330 raise util.Abort(_("destination '%s' is not empty") % dest)
333 raise util.Abort(_("destination '%s' is not empty") % dest)
331
334
332 srclock = destlock = cleandir = None
335 srclock = destlock = cleandir = None
333 srcrepo = srcpeer.local()
336 srcrepo = srcpeer.local()
334 try:
337 try:
335 abspath = origsource
338 abspath = origsource
336 if islocal(origsource):
339 if islocal(origsource):
337 abspath = os.path.abspath(util.urllocalpath(origsource))
340 abspath = os.path.abspath(util.urllocalpath(origsource))
338
341
339 if islocal(dest):
342 if islocal(dest):
340 cleandir = dest
343 cleandir = dest
341
344
342 copy = False
345 copy = False
343 if (srcrepo and srcrepo.cancopy() and islocal(dest)
346 if (srcrepo and srcrepo.cancopy() and islocal(dest)
344 and not phases.hassecret(srcrepo)):
347 and not phases.hassecret(srcrepo)):
345 copy = not pull and not rev
348 copy = not pull and not rev
346
349
347 if copy:
350 if copy:
348 try:
351 try:
349 # we use a lock here because if we race with commit, we
352 # we use a lock here because if we race with commit, we
350 # can end up with extra data in the cloned revlogs that's
353 # can end up with extra data in the cloned revlogs that's
351 # not pointed to by changesets, thus causing verify to
354 # not pointed to by changesets, thus causing verify to
352 # fail
355 # fail
353 srclock = srcrepo.lock(wait=False)
356 srclock = srcrepo.lock(wait=False)
354 except error.LockError:
357 except error.LockError:
355 copy = False
358 copy = False
356
359
357 if copy:
360 if copy:
358 srcrepo.hook('preoutgoing', throw=True, source='clone')
361 srcrepo.hook('preoutgoing', throw=True, source='clone')
359 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
362 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
360 if not os.path.exists(dest):
363 if not os.path.exists(dest):
361 os.mkdir(dest)
364 os.mkdir(dest)
362 else:
365 else:
363 # only clean up directories we create ourselves
366 # only clean up directories we create ourselves
364 cleandir = hgdir
367 cleandir = hgdir
365 try:
368 try:
366 destpath = hgdir
369 destpath = hgdir
367 util.makedir(destpath, notindexed=True)
370 util.makedir(destpath, notindexed=True)
368 except OSError, inst:
371 except OSError, inst:
369 if inst.errno == errno.EEXIST:
372 if inst.errno == errno.EEXIST:
370 cleandir = None
373 cleandir = None
371 raise util.Abort(_("destination '%s' already exists")
374 raise util.Abort(_("destination '%s' already exists")
372 % dest)
375 % dest)
373 raise
376 raise
374
377
375 destlock = copystore(ui, srcrepo, destpath)
378 destlock = copystore(ui, srcrepo, destpath)
376 # copy bookmarks over
379 # copy bookmarks over
377 srcbookmarks = srcrepo.join('bookmarks')
380 srcbookmarks = srcrepo.join('bookmarks')
378 dstbookmarks = os.path.join(destpath, 'bookmarks')
381 dstbookmarks = os.path.join(destpath, 'bookmarks')
379 if os.path.exists(srcbookmarks):
382 if os.path.exists(srcbookmarks):
380 util.copyfile(srcbookmarks, dstbookmarks)
383 util.copyfile(srcbookmarks, dstbookmarks)
381
384
382 # Recomputing branch cache might be slow on big repos,
385 # Recomputing branch cache might be slow on big repos,
383 # so just copy it
386 # so just copy it
384 def copybranchcache(fname):
387 def copybranchcache(fname):
385 srcbranchcache = srcrepo.join('cache/%s' % fname)
388 srcbranchcache = srcrepo.join('cache/%s' % fname)
386 dstbranchcache = os.path.join(dstcachedir, fname)
389 dstbranchcache = os.path.join(dstcachedir, fname)
387 if os.path.exists(srcbranchcache):
390 if os.path.exists(srcbranchcache):
388 if not os.path.exists(dstcachedir):
391 if not os.path.exists(dstcachedir):
389 os.mkdir(dstcachedir)
392 os.mkdir(dstcachedir)
390 util.copyfile(srcbranchcache, dstbranchcache)
393 util.copyfile(srcbranchcache, dstbranchcache)
391
394
392 dstcachedir = os.path.join(destpath, 'cache')
395 dstcachedir = os.path.join(destpath, 'cache')
393 # In local clones we're copying all nodes, not just served
396 # In local clones we're copying all nodes, not just served
394 # ones. Therefore copy all branch caches over.
397 # ones. Therefore copy all branch caches over.
395 copybranchcache('branch2')
398 copybranchcache('branch2')
396 for cachename in repoview.filtertable:
399 for cachename in repoview.filtertable:
397 copybranchcache('branch2-%s' % cachename)
400 copybranchcache('branch2-%s' % cachename)
398
401
399 # we need to re-init the repo after manually copying the data
402 # we need to re-init the repo after manually copying the data
400 # into it
403 # into it
401 destpeer = peer(srcrepo, peeropts, dest)
404 destpeer = peer(srcrepo, peeropts, dest)
402 srcrepo.hook('outgoing', source='clone',
405 srcrepo.hook('outgoing', source='clone',
403 node=node.hex(node.nullid))
406 node=node.hex(node.nullid))
404 else:
407 else:
405 try:
408 try:
406 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
409 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
407 # only pass ui when no srcrepo
410 # only pass ui when no srcrepo
408 except OSError, inst:
411 except OSError, inst:
409 if inst.errno == errno.EEXIST:
412 if inst.errno == errno.EEXIST:
410 cleandir = None
413 cleandir = None
411 raise util.Abort(_("destination '%s' already exists")
414 raise util.Abort(_("destination '%s' already exists")
412 % dest)
415 % dest)
413 raise
416 raise
414
417
415 revs = None
418 revs = None
416 if rev:
419 if rev:
417 if not srcpeer.capable('lookup'):
420 if not srcpeer.capable('lookup'):
418 raise util.Abort(_("src repository does not support "
421 raise util.Abort(_("src repository does not support "
419 "revision lookup and so doesn't "
422 "revision lookup and so doesn't "
420 "support clone by revision"))
423 "support clone by revision"))
421 revs = [srcpeer.lookup(r) for r in rev]
424 revs = [srcpeer.lookup(r) for r in rev]
422 checkout = revs[0]
425 checkout = revs[0]
423 if destpeer.local():
426 if destpeer.local():
424 if not stream:
427 if not stream:
425 if pull:
428 if pull:
426 stream = False
429 stream = False
427 else:
430 else:
428 stream = None
431 stream = None
429 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
432 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
430 elif srcrepo:
433 elif srcrepo:
431 exchange.push(srcrepo, destpeer, revs=revs,
434 exchange.push(srcrepo, destpeer, revs=revs,
432 bookmarks=srcrepo._bookmarks.keys())
435 bookmarks=srcrepo._bookmarks.keys())
433 else:
436 else:
434 raise util.Abort(_("clone from remote to remote not supported"))
437 raise util.Abort(_("clone from remote to remote not supported"))
435
438
436 cleandir = None
439 cleandir = None
437
440
438 destrepo = destpeer.local()
441 destrepo = destpeer.local()
439 if destrepo:
442 if destrepo:
440 template = uimod.samplehgrcs['cloned']
443 template = uimod.samplehgrcs['cloned']
441 fp = destrepo.opener("hgrc", "w", text=True)
444 fp = destrepo.opener("hgrc", "w", text=True)
442 u = util.url(abspath)
445 u = util.url(abspath)
443 u.passwd = None
446 u.passwd = None
444 defaulturl = str(u)
447 defaulturl = str(u)
445 fp.write(template % defaulturl)
448 fp.write(template % defaulturl)
446 fp.close()
449 fp.close()
447
450
448 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
451 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
449
452
450 if update:
453 if update:
451 if update is not True:
454 if update is not True:
452 checkout = srcpeer.lookup(update)
455 checkout = srcpeer.lookup(update)
453 uprev = None
456 uprev = None
454 status = None
457 status = None
455 if checkout is not None:
458 if checkout is not None:
456 try:
459 try:
457 uprev = destrepo.lookup(checkout)
460 uprev = destrepo.lookup(checkout)
458 except error.RepoLookupError:
461 except error.RepoLookupError:
459 pass
462 pass
460 if uprev is None:
463 if uprev is None:
461 try:
464 try:
462 uprev = destrepo._bookmarks['@']
465 uprev = destrepo._bookmarks['@']
463 update = '@'
466 update = '@'
464 bn = destrepo[uprev].branch()
467 bn = destrepo[uprev].branch()
465 if bn == 'default':
468 if bn == 'default':
466 status = _("updating to bookmark @\n")
469 status = _("updating to bookmark @\n")
467 else:
470 else:
468 status = (_("updating to bookmark @ on branch %s\n")
471 status = (_("updating to bookmark @ on branch %s\n")
469 % bn)
472 % bn)
470 except KeyError:
473 except KeyError:
471 try:
474 try:
472 uprev = destrepo.branchtip('default')
475 uprev = destrepo.branchtip('default')
473 except error.RepoLookupError:
476 except error.RepoLookupError:
474 uprev = destrepo.lookup('tip')
477 uprev = destrepo.lookup('tip')
475 if not status:
478 if not status:
476 bn = destrepo[uprev].branch()
479 bn = destrepo[uprev].branch()
477 status = _("updating to branch %s\n") % bn
480 status = _("updating to branch %s\n") % bn
478 destrepo.ui.status(status)
481 destrepo.ui.status(status)
479 _update(destrepo, uprev)
482 _update(destrepo, uprev)
480 if update in destrepo._bookmarks:
483 if update in destrepo._bookmarks:
481 bookmarks.setcurrent(destrepo, update)
484 bookmarks.setcurrent(destrepo, update)
482 finally:
485 finally:
483 release(srclock, destlock)
486 release(srclock, destlock)
484 if cleandir is not None:
487 if cleandir is not None:
485 shutil.rmtree(cleandir, True)
488 shutil.rmtree(cleandir, True)
486 if srcpeer is not None:
489 if srcpeer is not None:
487 srcpeer.close()
490 srcpeer.close()
488 return srcpeer, destpeer
491 return srcpeer, destpeer
489
492
490 def _showstats(repo, stats):
493 def _showstats(repo, stats):
491 repo.ui.status(_("%d files updated, %d files merged, "
494 repo.ui.status(_("%d files updated, %d files merged, "
492 "%d files removed, %d files unresolved\n") % stats)
495 "%d files removed, %d files unresolved\n") % stats)
493
496
494 def updaterepo(repo, node, overwrite):
497 def updaterepo(repo, node, overwrite):
495 """Update the working directory to node.
498 """Update the working directory to node.
496
499
497 When overwrite is set, changes are clobbered, merged else
500 When overwrite is set, changes are clobbered, merged else
498
501
499 returns stats (see pydoc mercurial.merge.applyupdates)"""
502 returns stats (see pydoc mercurial.merge.applyupdates)"""
500 return mergemod.update(repo, node, False, overwrite, None,
503 return mergemod.update(repo, node, False, overwrite, None,
501 labels=['working copy', 'destination'])
504 labels=['working copy', 'destination'])
502
505
503 def update(repo, node):
506 def update(repo, node):
504 """update the working directory to node, merging linear changes"""
507 """update the working directory to node, merging linear changes"""
505 stats = updaterepo(repo, node, False)
508 stats = updaterepo(repo, node, False)
506 _showstats(repo, stats)
509 _showstats(repo, stats)
507 if stats[3]:
510 if stats[3]:
508 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
511 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
509 return stats[3] > 0
512 return stats[3] > 0
510
513
511 # naming conflict in clone()
514 # naming conflict in clone()
512 _update = update
515 _update = update
513
516
514 def clean(repo, node, show_stats=True):
517 def clean(repo, node, show_stats=True):
515 """forcibly switch the working directory to node, clobbering changes"""
518 """forcibly switch the working directory to node, clobbering changes"""
516 stats = updaterepo(repo, node, True)
519 stats = updaterepo(repo, node, True)
517 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
520 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
518 if show_stats:
521 if show_stats:
519 _showstats(repo, stats)
522 _showstats(repo, stats)
520 return stats[3] > 0
523 return stats[3] > 0
521
524
522 def merge(repo, node, force=None, remind=True):
525 def merge(repo, node, force=None, remind=True):
523 """Branch merge with node, resolving changes. Return true if any
526 """Branch merge with node, resolving changes. Return true if any
524 unresolved conflicts."""
527 unresolved conflicts."""
525 stats = mergemod.update(repo, node, True, force, False)
528 stats = mergemod.update(repo, node, True, force, False)
526 _showstats(repo, stats)
529 _showstats(repo, stats)
527 if stats[3]:
530 if stats[3]:
528 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
531 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
529 "or 'hg update -C .' to abandon\n"))
532 "or 'hg update -C .' to abandon\n"))
530 elif remind:
533 elif remind:
531 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
534 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
532 return stats[3] > 0
535 return stats[3] > 0
533
536
534 def _incoming(displaychlist, subreporecurse, ui, repo, source,
537 def _incoming(displaychlist, subreporecurse, ui, repo, source,
535 opts, buffered=False):
538 opts, buffered=False):
536 """
539 """
537 Helper for incoming / gincoming.
540 Helper for incoming / gincoming.
538 displaychlist gets called with
541 displaychlist gets called with
539 (remoterepo, incomingchangesetlist, displayer) parameters,
542 (remoterepo, incomingchangesetlist, displayer) parameters,
540 and is supposed to contain only code that can't be unified.
543 and is supposed to contain only code that can't be unified.
541 """
544 """
542 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
545 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
543 other = peer(repo, opts, source)
546 other = peer(repo, opts, source)
544 ui.status(_('comparing with %s\n') % util.hidepassword(source))
547 ui.status(_('comparing with %s\n') % util.hidepassword(source))
545 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
548 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
546
549
547 if revs:
550 if revs:
548 revs = [other.lookup(rev) for rev in revs]
551 revs = [other.lookup(rev) for rev in revs]
549 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
552 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
550 revs, opts["bundle"], opts["force"])
553 revs, opts["bundle"], opts["force"])
551 try:
554 try:
552 if not chlist:
555 if not chlist:
553 ui.status(_("no changes found\n"))
556 ui.status(_("no changes found\n"))
554 return subreporecurse()
557 return subreporecurse()
555
558
556 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
559 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
557 displaychlist(other, chlist, displayer)
560 displaychlist(other, chlist, displayer)
558 displayer.close()
561 displayer.close()
559 finally:
562 finally:
560 cleanupfn()
563 cleanupfn()
561 subreporecurse()
564 subreporecurse()
562 return 0 # exit code is zero since we found incoming changes
565 return 0 # exit code is zero since we found incoming changes
563
566
564 def incoming(ui, repo, source, opts):
567 def incoming(ui, repo, source, opts):
565 def subreporecurse():
568 def subreporecurse():
566 ret = 1
569 ret = 1
567 if opts.get('subrepos'):
570 if opts.get('subrepos'):
568 ctx = repo[None]
571 ctx = repo[None]
569 for subpath in sorted(ctx.substate):
572 for subpath in sorted(ctx.substate):
570 sub = ctx.sub(subpath)
573 sub = ctx.sub(subpath)
571 ret = min(ret, sub.incoming(ui, source, opts))
574 ret = min(ret, sub.incoming(ui, source, opts))
572 return ret
575 return ret
573
576
574 def display(other, chlist, displayer):
577 def display(other, chlist, displayer):
575 limit = cmdutil.loglimit(opts)
578 limit = cmdutil.loglimit(opts)
576 if opts.get('newest_first'):
579 if opts.get('newest_first'):
577 chlist.reverse()
580 chlist.reverse()
578 count = 0
581 count = 0
579 for n in chlist:
582 for n in chlist:
580 if limit is not None and count >= limit:
583 if limit is not None and count >= limit:
581 break
584 break
582 parents = [p for p in other.changelog.parents(n) if p != nullid]
585 parents = [p for p in other.changelog.parents(n) if p != nullid]
583 if opts.get('no_merges') and len(parents) == 2:
586 if opts.get('no_merges') and len(parents) == 2:
584 continue
587 continue
585 count += 1
588 count += 1
586 displayer.show(other[n])
589 displayer.show(other[n])
587 return _incoming(display, subreporecurse, ui, repo, source, opts)
590 return _incoming(display, subreporecurse, ui, repo, source, opts)
588
591
589 def _outgoing(ui, repo, dest, opts):
592 def _outgoing(ui, repo, dest, opts):
590 dest = ui.expandpath(dest or 'default-push', dest or 'default')
593 dest = ui.expandpath(dest or 'default-push', dest or 'default')
591 dest, branches = parseurl(dest, opts.get('branch'))
594 dest, branches = parseurl(dest, opts.get('branch'))
592 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
595 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
593 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
596 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
594 if revs:
597 if revs:
595 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
598 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
596
599
597 other = peer(repo, opts, dest)
600 other = peer(repo, opts, dest)
598 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
601 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
599 force=opts.get('force'))
602 force=opts.get('force'))
600 o = outgoing.missing
603 o = outgoing.missing
601 if not o:
604 if not o:
602 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
605 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
603 return o, other
606 return o, other
604
607
605 def outgoing(ui, repo, dest, opts):
608 def outgoing(ui, repo, dest, opts):
606 def recurse():
609 def recurse():
607 ret = 1
610 ret = 1
608 if opts.get('subrepos'):
611 if opts.get('subrepos'):
609 ctx = repo[None]
612 ctx = repo[None]
610 for subpath in sorted(ctx.substate):
613 for subpath in sorted(ctx.substate):
611 sub = ctx.sub(subpath)
614 sub = ctx.sub(subpath)
612 ret = min(ret, sub.outgoing(ui, dest, opts))
615 ret = min(ret, sub.outgoing(ui, dest, opts))
613 return ret
616 return ret
614
617
615 limit = cmdutil.loglimit(opts)
618 limit = cmdutil.loglimit(opts)
616 o, other = _outgoing(ui, repo, dest, opts)
619 o, other = _outgoing(ui, repo, dest, opts)
617 if not o:
620 if not o:
618 cmdutil.outgoinghooks(ui, repo, other, opts, o)
621 cmdutil.outgoinghooks(ui, repo, other, opts, o)
619 return recurse()
622 return recurse()
620
623
621 if opts.get('newest_first'):
624 if opts.get('newest_first'):
622 o.reverse()
625 o.reverse()
623 displayer = cmdutil.show_changeset(ui, repo, opts)
626 displayer = cmdutil.show_changeset(ui, repo, opts)
624 count = 0
627 count = 0
625 for n in o:
628 for n in o:
626 if limit is not None and count >= limit:
629 if limit is not None and count >= limit:
627 break
630 break
628 parents = [p for p in repo.changelog.parents(n) if p != nullid]
631 parents = [p for p in repo.changelog.parents(n) if p != nullid]
629 if opts.get('no_merges') and len(parents) == 2:
632 if opts.get('no_merges') and len(parents) == 2:
630 continue
633 continue
631 count += 1
634 count += 1
632 displayer.show(repo[n])
635 displayer.show(repo[n])
633 displayer.close()
636 displayer.close()
634 cmdutil.outgoinghooks(ui, repo, other, opts, o)
637 cmdutil.outgoinghooks(ui, repo, other, opts, o)
635 recurse()
638 recurse()
636 return 0 # exit code is zero since we found outgoing changes
639 return 0 # exit code is zero since we found outgoing changes
637
640
638 def revert(repo, node, choose):
641 def revert(repo, node, choose):
639 """revert changes to revision in node without updating dirstate"""
642 """revert changes to revision in node without updating dirstate"""
640 return mergemod.update(repo, node, False, True, choose)[3] > 0
643 return mergemod.update(repo, node, False, True, choose)[3] > 0
641
644
642 def verify(repo):
645 def verify(repo):
643 """verify the consistency of a repository"""
646 """verify the consistency of a repository"""
644 return verifymod.verify(repo)
647 return verifymod.verify(repo)
645
648
646 def remoteui(src, opts):
649 def remoteui(src, opts):
647 'build a remote ui from ui or repo and opts'
650 'build a remote ui from ui or repo and opts'
648 if util.safehasattr(src, 'baseui'): # looks like a repository
651 if util.safehasattr(src, 'baseui'): # looks like a repository
649 dst = src.baseui.copy() # drop repo-specific config
652 dst = src.baseui.copy() # drop repo-specific config
650 src = src.ui # copy target options from repo
653 src = src.ui # copy target options from repo
651 else: # assume it's a global ui object
654 else: # assume it's a global ui object
652 dst = src.copy() # keep all global options
655 dst = src.copy() # keep all global options
653
656
654 # copy ssh-specific options
657 # copy ssh-specific options
655 for o in 'ssh', 'remotecmd':
658 for o in 'ssh', 'remotecmd':
656 v = opts.get(o) or src.config('ui', o)
659 v = opts.get(o) or src.config('ui', o)
657 if v:
660 if v:
658 dst.setconfig("ui", o, v, 'copied')
661 dst.setconfig("ui", o, v, 'copied')
659
662
660 # copy bundle-specific options
663 # copy bundle-specific options
661 r = src.config('bundle', 'mainreporoot')
664 r = src.config('bundle', 'mainreporoot')
662 if r:
665 if r:
663 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
666 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
664
667
665 # copy selected local settings to the remote ui
668 # copy selected local settings to the remote ui
666 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
669 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
667 for key, val in src.configitems(sect):
670 for key, val in src.configitems(sect):
668 dst.setconfig(sect, key, val, 'copied')
671 dst.setconfig(sect, key, val, 'copied')
669 v = src.config('web', 'cacerts')
672 v = src.config('web', 'cacerts')
670 if v:
673 if v:
671 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
674 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
672
675
673 return dst
676 return dst
@@ -1,303 +1,303 b''
1 #require killdaemons
1 #require killdaemons
2
2
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "share = " >> $HGRCPATH
4 $ echo "share = " >> $HGRCPATH
5
5
6 prepare repo1
6 prepare repo1
7
7
8 $ hg init repo1
8 $ hg init repo1
9 $ cd repo1
9 $ cd repo1
10 $ echo a > a
10 $ echo a > a
11 $ hg commit -A -m'init'
11 $ hg commit -A -m'init'
12 adding a
12 adding a
13
13
14 share it
14 share it
15
15
16 $ cd ..
16 $ cd ..
17 $ hg share repo1 repo2
17 $ hg share repo1 repo2
18 updating working directory
18 updating working directory
19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20
20
21 share shouldn't have a store dir
21 share shouldn't have a store dir
22
22
23 $ cd repo2
23 $ cd repo2
24 $ test -d .hg/store
24 $ test -d .hg/store
25 [1]
25 [1]
26
26
27 Some sed versions appends newline, some don't, and some just fails
27 Some sed versions appends newline, some don't, and some just fails
28
28
29 $ cat .hg/sharedpath; echo
29 $ cat .hg/sharedpath; echo
30 $TESTTMP/repo1/.hg (glob)
30 $TESTTMP/repo1/.hg (glob)
31
31
32 trailing newline on .hg/sharedpath is ok
32 trailing newline on .hg/sharedpath is ok
33 $ hg tip -q
33 $ hg tip -q
34 0:d3873e73d99e
34 0:d3873e73d99e
35 $ echo '' >> .hg/sharedpath
35 $ echo '' >> .hg/sharedpath
36 $ cat .hg/sharedpath
36 $ cat .hg/sharedpath
37 $TESTTMP/repo1/.hg (glob)
37 $TESTTMP/repo1/.hg (glob)
38 $ hg tip -q
38 $ hg tip -q
39 0:d3873e73d99e
39 0:d3873e73d99e
40
40
41 commit in shared clone
41 commit in shared clone
42
42
43 $ echo a >> a
43 $ echo a >> a
44 $ hg commit -m'change in shared clone'
44 $ hg commit -m'change in shared clone'
45
45
46 check original
46 check original
47
47
48 $ cd ../repo1
48 $ cd ../repo1
49 $ hg log
49 $ hg log
50 changeset: 1:8af4dc49db9e
50 changeset: 1:8af4dc49db9e
51 tag: tip
51 tag: tip
52 user: test
52 user: test
53 date: Thu Jan 01 00:00:00 1970 +0000
53 date: Thu Jan 01 00:00:00 1970 +0000
54 summary: change in shared clone
54 summary: change in shared clone
55
55
56 changeset: 0:d3873e73d99e
56 changeset: 0:d3873e73d99e
57 user: test
57 user: test
58 date: Thu Jan 01 00:00:00 1970 +0000
58 date: Thu Jan 01 00:00:00 1970 +0000
59 summary: init
59 summary: init
60
60
61 $ hg update
61 $ hg update
62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ cat a # should be two lines of "a"
63 $ cat a # should be two lines of "a"
64 a
64 a
65 a
65 a
66
66
67 commit in original
67 commit in original
68
68
69 $ echo b > b
69 $ echo b > b
70 $ hg commit -A -m'another file'
70 $ hg commit -A -m'another file'
71 adding b
71 adding b
72
72
73 check in shared clone
73 check in shared clone
74
74
75 $ cd ../repo2
75 $ cd ../repo2
76 $ hg log
76 $ hg log
77 changeset: 2:c2e0ac586386
77 changeset: 2:c2e0ac586386
78 tag: tip
78 tag: tip
79 user: test
79 user: test
80 date: Thu Jan 01 00:00:00 1970 +0000
80 date: Thu Jan 01 00:00:00 1970 +0000
81 summary: another file
81 summary: another file
82
82
83 changeset: 1:8af4dc49db9e
83 changeset: 1:8af4dc49db9e
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
86 summary: change in shared clone
86 summary: change in shared clone
87
87
88 changeset: 0:d3873e73d99e
88 changeset: 0:d3873e73d99e
89 user: test
89 user: test
90 date: Thu Jan 01 00:00:00 1970 +0000
90 date: Thu Jan 01 00:00:00 1970 +0000
91 summary: init
91 summary: init
92
92
93 $ hg update
93 $ hg update
94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 $ cat b # should exist with one "b"
95 $ cat b # should exist with one "b"
96 b
96 b
97
97
98 hg serve shared clone
98 hg serve shared clone
99
99
100 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid
100 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid
101 $ cat hg.pid >> $DAEMON_PIDS
101 $ cat hg.pid >> $DAEMON_PIDS
102 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-file/'
102 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-file/'
103 200 Script output follows
103 200 Script output follows
104
104
105
105
106 -rw-r--r-- 4 a
106 -rw-r--r-- 4 a
107 -rw-r--r-- 2 b
107 -rw-r--r-- 2 b
108
108
109
109
110
110
111 test unshare command
111 test unshare command
112
112
113 $ hg unshare
113 $ hg unshare
114 $ test -d .hg/store
114 $ test -d .hg/store
115 $ test -f .hg/sharedpath
115 $ test -f .hg/sharedpath
116 [1]
116 [1]
117 $ hg unshare
117 $ hg unshare
118 abort: this is not a shared repo
118 abort: this is not a shared repo
119 [255]
119 [255]
120
120
121 check that a change does not propagate
121 check that a change does not propagate
122
122
123 $ echo b >> b
123 $ echo b >> b
124 $ hg commit -m'change in unshared'
124 $ hg commit -m'change in unshared'
125 $ cd ../repo1
125 $ cd ../repo1
126 $ hg id -r tip
126 $ hg id -r tip
127 c2e0ac586386 tip
127 c2e0ac586386 tip
128
128
129 $ cd ..
129 $ cd ..
130
130
131
131
132 test sharing bookmarks (manually add bookmarks.shared file for now)
132 test sharing bookmarks
133
133
134 $ hg share repo1 repo3 && touch repo3/.hg/bookmarks.shared
134 $ hg share -B repo1 repo3
135 updating working directory
135 updating working directory
136 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
136 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 $ cd repo1
137 $ cd repo1
138 $ hg bookmark bm1
138 $ hg bookmark bm1
139 $ hg bookmarks
139 $ hg bookmarks
140 * bm1 2:c2e0ac586386
140 * bm1 2:c2e0ac586386
141 $ cd ../repo2
141 $ cd ../repo2
142 $ hg book bm2
142 $ hg book bm2
143 $ hg bookmarks
143 $ hg bookmarks
144 * bm2 3:0e6e70d1d5f1
144 * bm2 3:0e6e70d1d5f1
145 $ cd ../repo3
145 $ cd ../repo3
146 $ hg bookmarks
146 $ hg bookmarks
147 bm1 2:c2e0ac586386
147 bm1 2:c2e0ac586386
148 $ hg book bm3
148 $ hg book bm3
149 $ hg bookmarks
149 $ hg bookmarks
150 bm1 2:c2e0ac586386
150 bm1 2:c2e0ac586386
151 * bm3 2:c2e0ac586386
151 * bm3 2:c2e0ac586386
152 $ cd ../repo1
152 $ cd ../repo1
153 $ hg bookmarks
153 $ hg bookmarks
154 * bm1 2:c2e0ac586386
154 * bm1 2:c2e0ac586386
155 bm3 2:c2e0ac586386
155 bm3 2:c2e0ac586386
156
156
157 test that commits work
157 test that commits work
158
158
159 $ echo 'shared bookmarks' > a
159 $ echo 'shared bookmarks' > a
160 $ hg commit -m 'testing shared bookmarks'
160 $ hg commit -m 'testing shared bookmarks'
161 $ hg bookmarks
161 $ hg bookmarks
162 * bm1 3:b87954705719
162 * bm1 3:b87954705719
163 bm3 2:c2e0ac586386
163 bm3 2:c2e0ac586386
164 $ cd ../repo3
164 $ cd ../repo3
165 $ hg bookmarks
165 $ hg bookmarks
166 bm1 3:b87954705719
166 bm1 3:b87954705719
167 * bm3 2:c2e0ac586386
167 * bm3 2:c2e0ac586386
168 $ echo 'more shared bookmarks' > a
168 $ echo 'more shared bookmarks' > a
169 $ hg commit -m 'testing shared bookmarks'
169 $ hg commit -m 'testing shared bookmarks'
170 created new head
170 created new head
171 $ hg bookmarks
171 $ hg bookmarks
172 bm1 3:b87954705719
172 bm1 3:b87954705719
173 * bm3 4:62f4ded848e4
173 * bm3 4:62f4ded848e4
174 $ cd ../repo1
174 $ cd ../repo1
175 $ hg bookmarks
175 $ hg bookmarks
176 * bm1 3:b87954705719
176 * bm1 3:b87954705719
177 bm3 4:62f4ded848e4
177 bm3 4:62f4ded848e4
178 $ cd ..
178 $ cd ..
179
179
180 test pushing bookmarks works
180 test pushing bookmarks works
181
181
182 $ hg clone repo3 repo4
182 $ hg clone repo3 repo4
183 updating to branch default
183 updating to branch default
184 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
184 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
185 $ cd repo4
185 $ cd repo4
186 $ hg boo bm4
186 $ hg boo bm4
187 $ echo foo > b
187 $ echo foo > b
188 $ hg commit -m 'foo in b'
188 $ hg commit -m 'foo in b'
189 $ hg boo
189 $ hg boo
190 bm1 3:b87954705719
190 bm1 3:b87954705719
191 bm3 4:62f4ded848e4
191 bm3 4:62f4ded848e4
192 * bm4 5:92793bfc8cad
192 * bm4 5:92793bfc8cad
193 $ hg push -B bm4
193 $ hg push -B bm4
194 pushing to $TESTTMP/repo3 (glob)
194 pushing to $TESTTMP/repo3 (glob)
195 searching for changes
195 searching for changes
196 adding changesets
196 adding changesets
197 adding manifests
197 adding manifests
198 adding file changes
198 adding file changes
199 added 1 changesets with 1 changes to 1 files
199 added 1 changesets with 1 changes to 1 files
200 exporting bookmark bm4
200 exporting bookmark bm4
201 $ cd ../repo1
201 $ cd ../repo1
202 $ hg bookmarks
202 $ hg bookmarks
203 * bm1 3:b87954705719
203 * bm1 3:b87954705719
204 bm3 4:62f4ded848e4
204 bm3 4:62f4ded848e4
205 bm4 5:92793bfc8cad
205 bm4 5:92793bfc8cad
206 $ cd ../repo3
206 $ cd ../repo3
207 $ hg bookmarks
207 $ hg bookmarks
208 bm1 3:b87954705719
208 bm1 3:b87954705719
209 * bm3 4:62f4ded848e4
209 * bm3 4:62f4ded848e4
210 bm4 5:92793bfc8cad
210 bm4 5:92793bfc8cad
211 $ cd ..
211 $ cd ..
212
212
213 test behavior when sharing a shared repo
213 test behavior when sharing a shared repo
214
214
215 $ hg share repo3 repo5 && touch repo5/.hg/bookmarks.shared
215 $ hg share -B repo3 repo5
216 updating working directory
216 updating working directory
217 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
217 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
218 $ cd repo5
218 $ cd repo5
219 $ hg book
219 $ hg book
220 bm1 3:b87954705719
220 bm1 3:b87954705719
221 bm3 4:62f4ded848e4
221 bm3 4:62f4ded848e4
222 bm4 5:92793bfc8cad
222 bm4 5:92793bfc8cad
223 $ cd ..
223 $ cd ..
224
224
225 test what happens when an active bookmark is deleted
225 test what happens when an active bookmark is deleted
226
226
227 $ cd repo1
227 $ cd repo1
228 $ hg boo -d bm3
228 $ hg boo -d bm3
229 $ hg boo
229 $ hg boo
230 * bm1 3:b87954705719
230 * bm1 3:b87954705719
231 bm4 5:92793bfc8cad
231 bm4 5:92793bfc8cad
232 $ cd ../repo3
232 $ cd ../repo3
233 $ hg boo
233 $ hg boo
234 bm1 3:b87954705719
234 bm1 3:b87954705719
235 bm4 5:92793bfc8cad
235 bm4 5:92793bfc8cad
236 $ cd ..
236 $ cd ..
237
237
238 verify that bookmarks are not written on failed transaction
238 verify that bookmarks are not written on failed transaction
239
239
240 $ cat > failpullbookmarks.py << EOF
240 $ cat > failpullbookmarks.py << EOF
241 > """A small extension that makes bookmark pulls fail, for testing"""
241 > """A small extension that makes bookmark pulls fail, for testing"""
242 > from mercurial import extensions, exchange, error
242 > from mercurial import extensions, exchange, error
243 > def _pullbookmarks(orig, pullop):
243 > def _pullbookmarks(orig, pullop):
244 > orig(pullop)
244 > orig(pullop)
245 > raise error.HookAbort('forced failure by extension')
245 > raise error.HookAbort('forced failure by extension')
246 > def extsetup(ui):
246 > def extsetup(ui):
247 > extensions.wrapfunction(exchange, '_pullbookmarks', _pullbookmarks)
247 > extensions.wrapfunction(exchange, '_pullbookmarks', _pullbookmarks)
248 > EOF
248 > EOF
249 $ cd repo4
249 $ cd repo4
250 $ hg boo
250 $ hg boo
251 bm1 3:b87954705719
251 bm1 3:b87954705719
252 bm3 4:62f4ded848e4
252 bm3 4:62f4ded848e4
253 * bm4 5:92793bfc8cad
253 * bm4 5:92793bfc8cad
254 $ cd ../repo3
254 $ cd ../repo3
255 $ hg boo
255 $ hg boo
256 bm1 3:b87954705719
256 bm1 3:b87954705719
257 bm4 5:92793bfc8cad
257 bm4 5:92793bfc8cad
258 $ hg --config "extensions.failpullbookmarks=$TESTTMP/failpullbookmarks.py" pull $TESTTMP/repo4
258 $ hg --config "extensions.failpullbookmarks=$TESTTMP/failpullbookmarks.py" pull $TESTTMP/repo4
259 pulling from $TESTTMP/repo4 (glob)
259 pulling from $TESTTMP/repo4 (glob)
260 searching for changes
260 searching for changes
261 no changes found
261 no changes found
262 adding remote bookmark bm3
262 adding remote bookmark bm3
263 abort: forced failure by extension
263 abort: forced failure by extension
264 [255]
264 [255]
265 $ hg boo
265 $ hg boo
266 bm1 3:b87954705719
266 bm1 3:b87954705719
267 bm4 5:92793bfc8cad
267 bm4 5:92793bfc8cad
268 $ hg pull $TESTTMP/repo4
268 $ hg pull $TESTTMP/repo4
269 pulling from $TESTTMP/repo4 (glob)
269 pulling from $TESTTMP/repo4 (glob)
270 searching for changes
270 searching for changes
271 no changes found
271 no changes found
272 adding remote bookmark bm3
272 adding remote bookmark bm3
273 $ hg boo
273 $ hg boo
274 bm1 3:b87954705719
274 bm1 3:b87954705719
275 * bm3 4:62f4ded848e4
275 * bm3 4:62f4ded848e4
276 bm4 5:92793bfc8cad
276 bm4 5:92793bfc8cad
277 $ cd ..
277 $ cd ..
278
278
279 verify bookmark behavior after unshare
279 verify bookmark behavior after unshare
280
280
281 $ cd repo3
281 $ cd repo3
282 $ hg unshare
282 $ hg unshare
283 $ hg boo
283 $ hg boo
284 bm1 3:b87954705719
284 bm1 3:b87954705719
285 * bm3 4:62f4ded848e4
285 * bm3 4:62f4ded848e4
286 bm4 5:92793bfc8cad
286 bm4 5:92793bfc8cad
287 $ hg boo -d bm4
287 $ hg boo -d bm4
288 $ hg boo bm5
288 $ hg boo bm5
289 $ hg boo
289 $ hg boo
290 bm1 3:b87954705719
290 bm1 3:b87954705719
291 bm3 4:62f4ded848e4
291 bm3 4:62f4ded848e4
292 * bm5 4:62f4ded848e4
292 * bm5 4:62f4ded848e4
293 $ cd ../repo1
293 $ cd ../repo1
294 $ hg boo
294 $ hg boo
295 * bm1 3:b87954705719
295 * bm1 3:b87954705719
296 bm3 4:62f4ded848e4
296 bm3 4:62f4ded848e4
297 bm4 5:92793bfc8cad
297 bm4 5:92793bfc8cad
298 $ cd ..
298 $ cd ..
299
299
300 Explicitly kill daemons to let the test exit on Windows
300 Explicitly kill daemons to let the test exit on Windows
301
301
302 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
302 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
303
303
General Comments 0
You need to be logged in to leave comments. Login now