##// END OF EJS Templates
unionrepo: read-only operations on a union of two localrepos...
Mads Kiilerich -
r18944:a9c443b3 default
parent child Browse files
Show More
@@ -0,0 +1,208 b''
1 # unionrepo.py - repository class for viewing union of repository changesets
2 #
3 # Derived from bundlerepo.py
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 #
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
9
10 """Repository class for "in-memory pull" of one local repository to another,
11 allowing operations like diff and log with revsets.
12 """
13
14 from node import nullid
15 from i18n import _
16 import os
17 import util, mdiff, cmdutil, scmutil
18 import localrepo, changelog, manifest, filelog, revlog
19
20 class unionrevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, revlog2, linkmapper):
22 # How it works:
23 # To retrieve a revision, we just need to know the node id so we can
24 # look it up in revlog2.
25 #
26 # To differentiate a rev in the second revlog from a rev in the revlog,
27 # we check revision against repotiprev.
28 opener = scmutil.readonlyvfs(opener)
29 revlog.revlog.__init__(self, opener, indexfile)
30 self.revlog2 = revlog2
31
32 n = len(self)
33 self.repotiprev = n - 1
34 self.bundlerevs = set() # used by 'bundle()' revset expression
35 for rev2 in self.revlog2:
36 rev = self.revlog2.index[rev2]
37 # rev numbers - in revlog2, very different from self.rev
38 _start, _csize, _rsize, _base, linkrev, p1rev, p2rev, node = rev
39
40 if linkmapper is None: # link is to same revlog
41 assert linkrev == rev2 # we never link back
42 link = n
43 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
44 link = linkmapper(linkrev)
45
46 if node in self.nodemap:
47 # this happens for the common revlog revisions
48 self.bundlerevs.add(self.nodemap[node])
49 continue
50
51 p1node = self.revlog2.node(p1rev)
52 p2node = self.revlog2.node(p2rev)
53
54 e = (None, None, None, None,
55 link, self.rev(p1node), self.rev(p2node), node)
56 self.index.insert(-1, e)
57 self.nodemap[node] = n
58 self.bundlerevs.add(n)
59 n += 1
60
61 def _chunk(self, rev):
62 if rev <= self.repotiprev:
63 return revlog.revlog._chunk(self, rev)
64 return self.revlog2._chunk(self.node(rev))
65
66 def revdiff(self, rev1, rev2):
67 """return or calculate a delta between two revisions"""
68 if rev1 > self.repotiprev and rev2 > self.repotiprev:
69 return self.revlog2.revdiff(
70 self.revlog2.rev(self.node(rev1)),
71 self.revlog2.rev(self.node(rev2)))
72 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
73 return revlog.revlog.revdiff(self, rev1, rev2)
74
75 return mdiff.textdiff(self.revision(self.node(rev1)),
76 self.revision(self.node(rev2)))
77
78 def revision(self, nodeorrev):
79 """return an uncompressed revision of a given node or revision
80 number.
81 """
82 if isinstance(nodeorrev, int):
83 rev = nodeorrev
84 node = self.node(rev)
85 else:
86 node = nodeorrev
87 rev = self.rev(node)
88
89 if node == nullid:
90 return ""
91
92 if rev > self.repotiprev:
93 text = self.revlog2.revision(node)
94 self._cache = (node, rev, text)
95 else:
96 text = revlog.revlog.revision(self, rev)
97 # already cached
98 return text
99
100 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
101 raise NotImplementedError
102 def addgroup(self, revs, linkmapper, transaction):
103 raise NotImplementedError
104 def strip(self, rev, minlink):
105 raise NotImplementedError
106 def checksize(self):
107 raise NotImplementedError
108
109 class unionchangelog(unionrevlog, changelog.changelog):
110 def __init__(self, opener, opener2):
111 changelog.changelog.__init__(self, opener)
112 linkmapper = None
113 changelog2 = changelog.changelog(opener2)
114 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
115 linkmapper)
116
117 class unionmanifest(unionrevlog, manifest.manifest):
118 def __init__(self, opener, opener2, linkmapper):
119 manifest.manifest.__init__(self, opener)
120 manifest2 = manifest.manifest(opener2)
121 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
122 linkmapper)
123
124 class unionfilelog(unionrevlog, filelog.filelog):
125 def __init__(self, opener, path, opener2, linkmapper, repo):
126 filelog.filelog.__init__(self, opener, path)
127 filelog2 = filelog.filelog(opener2, path)
128 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
129 linkmapper)
130 self._repo = repo
131
132 def _file(self, f):
133 self._repo.file(f)
134
135 class unionpeer(localrepo.localpeer):
136 def canpush(self):
137 return False
138
139 class unionrepository(localrepo.localrepository):
140 def __init__(self, ui, path, path2):
141 localrepo.localrepository.__init__(self, ui, path)
142 self.ui.setconfig('phases', 'publish', False)
143
144 self._url = 'union:%s+%s' % (util.expandpath(path),
145 util.expandpath(path2))
146 self.repo2 = localrepo.localrepository(ui, path2)
147
148 @localrepo.unfilteredpropertycache
149 def changelog(self):
150 return unionchangelog(self.sopener, self.repo2.sopener)
151
152 def _clrev(self, rev2):
153 """map from repo2 changelog rev to temporary rev in self.changelog"""
154 node = self.repo2.changelog.node(rev2)
155 return self.changelog.rev(node)
156
157 @localrepo.unfilteredpropertycache
158 def manifest(self):
159 return unionmanifest(self.sopener, self.repo2.sopener,
160 self._clrev)
161
162 def url(self):
163 return self._url
164
165 def file(self, f):
166 return unionfilelog(self.sopener, f, self.repo2.sopener,
167 self._clrev, self)
168
169 def close(self):
170 self.repo2.close()
171
172 def cancopy(self):
173 return False
174
175 def peer(self):
176 return unionpeer(self)
177
178 def getcwd(self):
179 return os.getcwd() # always outside the repo
180
181 def instance(ui, path, create):
182 if create:
183 raise util.Abort(_('cannot create new union repository'))
184 parentpath = ui.config("bundle", "mainreporoot", "")
185 if not parentpath:
186 # try to find the correct path to the working directory repo
187 parentpath = cmdutil.findrepo(os.getcwd())
188 if parentpath is None:
189 parentpath = ''
190 if parentpath:
191 # Try to make the full path relative so we get a nice, short URL.
192 # In particular, we don't want temp dir names in test outputs.
193 cwd = os.getcwd()
194 if parentpath == cwd:
195 parentpath = ''
196 else:
197 cwd = os.path.join(cwd,'')
198 if parentpath.startswith(cwd):
199 parentpath = parentpath[len(cwd):]
200 if path.startswith('union:'):
201 s = path.split(":", 1)[1].split("+", 1)
202 if len(s) == 1:
203 repopath, repopath2 = parentpath, s[0]
204 else:
205 repopath, repopath2 = s
206 else:
207 repopath, repopath2 = parentpath, path
208 return unionrepository(ui, repopath, repopath2)
@@ -0,0 +1,148 b''
1 Test unionrepo functionality
2
3 Create one repository
4
5 $ hg init repo1
6 $ cd repo1
7 $ touch repo1-0
8 $ echo repo1-0 > f
9 $ hg ci -Aqmrepo1-0
10 $ touch repo1-1
11 $ echo repo1-1 >> f
12 $ hg ci -Aqmrepo1-1
13 $ touch repo1-2
14 $ echo repo1-2 >> f
15 $ hg ci -Aqmrepo1-2
16 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
17 2:68c0685446a3 repo1-2
18 1:8a58db72e69d repo1-1
19 0:f093fec0529b repo1-0
20 $ tip1=`hg id -q`
21 $ cd ..
22
23 - and a clone with a not-completely-trivial history
24
25 $ hg clone -q repo1 --rev 0 repo2
26 $ cd repo2
27 $ touch repo2-1
28 $ sed '1irepo2-1 at top' f > f.tmp
29 $ mv f.tmp f
30 $ hg ci -Aqmrepo2-1
31 $ touch repo2-2
32 $ hg pull -q ../repo1 -r 1
33 $ hg merge -q
34 $ hg ci -Aqmrepo2-2-merge
35 $ touch repo2-3
36 $ echo repo2-3 >> f
37 $ hg ci -mrepo2-3
38 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
39 4:2f0d178c469c repo2-3
40 3:9e6fb3e0b9da repo2-2-merge
41 2:8a58db72e69d repo1-1
42 1:c337dba826e7 repo2-1
43 0:f093fec0529b repo1-0
44 $ cd ..
45
46 revisions from repo2 appear as appended / pulled to repo1
47
48 $ hg -R union:repo1+repo2 log --template '{rev}:{node|short} {desc|firstline}\n'
49 5:2f0d178c469c repo2-3
50 4:9e6fb3e0b9da repo2-2-merge
51 3:c337dba826e7 repo2-1
52 2:68c0685446a3 repo1-2
53 1:8a58db72e69d repo1-1
54 0:f093fec0529b repo1-0
55
56 manifest can be retrieved for revisions in both repos
57
58 $ hg -R union:repo1+repo2 mani -r $tip1
59 f
60 repo1-0
61 repo1-1
62 repo1-2
63 $ hg -R union:repo1+repo2 mani -r 4
64 f
65 repo1-0
66 repo1-1
67 repo2-1
68 repo2-2
69
70 files can be retrieved form both repos
71
72 $ hg -R repo1 cat repo1/f -r2
73 repo1-0
74 repo1-1
75 repo1-2
76
77 $ hg -R union:repo1+repo2 cat -r$tip1 repo1/f
78 repo1-0
79 repo1-1
80 repo1-2
81
82 $ hg -R union:repo1+repo2 cat -r4 $TESTTMP/repo1/f
83 repo2-1 at top
84 repo1-0
85 repo1-1
86
87 files can be compared across repos
88
89 $ hg -R union:repo1+repo2 diff -r$tip1 -rtip
90 diff -r 68c0685446a3 -r 2f0d178c469c f
91 --- a/f Thu Jan 01 00:00:00 1970 +0000
92 +++ b/f Thu Jan 01 00:00:00 1970 +0000
93 @@ -1,3 +1,4 @@
94 +repo2-1 at top
95 repo1-0
96 repo1-1
97 -repo1-2
98 +repo2-3
99
100 heads from both repos are found correctly
101
102 $ hg -R union:repo1+repo2 heads --template '{rev}:{node|short} {desc|firstline}\n'
103 5:2f0d178c469c repo2-3
104 2:68c0685446a3 repo1-2
105
106 revsets works across repos
107
108 $ hg -R union:repo1+repo2 id -r "ancestor($tip1, 5)"
109 8a58db72e69d
110
111 annotate works - an indication that linkrevs works
112
113 $ hg --cwd repo1 -R union:../repo2 annotate $TESTTMP/repo1/f -r tip
114 3: repo2-1 at top
115 0: repo1-0
116 1: repo1-1
117 5: repo2-3
118
119 union repos can be cloned ... and clones works correctly
120
121 $ hg clone -U union:repo1+repo2 repo3
122 requesting all changes
123 adding changesets
124 adding manifests
125 adding file changes
126 added 6 changesets with 11 changes to 6 files (+1 heads)
127
128 $ hg -R repo3 paths
129 default = union:repo1+repo2
130
131 $ hg -R repo3 verify
132 checking changesets
133 checking manifests
134 crosschecking files in changesets and manifests
135 checking files
136 6 files, 6 changesets, 11 total revisions
137
138 $ hg -R repo3 heads --template '{rev}:{node|short} {desc|firstline}\n'
139 5:2f0d178c469c repo2-3
140 2:68c0685446a3 repo1-2
141
142 $ hg -R repo3 log --template '{rev}:{node|short} {desc|firstline}\n'
143 5:2f0d178c469c repo2-3
144 4:9e6fb3e0b9da repo2-2-merge
145 3:c337dba826e7 repo2-1
146 2:68c0685446a3 repo1-2
147 1:8a58db72e69d repo1-1
148 0:f093fec0529b repo1-0
@@ -1,632 +1,633 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 from node import hex, nullid
12 import localrepo, bundlerepo, httppeer, sshpeer, statichttprepo, bookmarks
13 import lock, util, extensions, error, node, scmutil, phases, url
12 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
13 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
14 14 import cmdutil, discovery
15 15 import merge as mergemod
16 16 import verify as verifymod
17 17 import errno, os, shutil
18 18
19 19 def _local(path):
20 20 path = util.expandpath(util.urllocalpath(path))
21 21 return (os.path.isfile(path) and bundlerepo or localrepo)
22 22
23 23 def addbranchrevs(lrepo, other, branches, revs):
24 24 peer = other.peer() # a courtesy to callers using a localrepo for other
25 25 hashbranch, branches = branches
26 26 if not hashbranch and not branches:
27 27 return revs or None, revs and revs[0] or None
28 28 revs = revs and list(revs) or []
29 29 if not peer.capable('branchmap'):
30 30 if branches:
31 31 raise util.Abort(_("remote branch lookup not supported"))
32 32 revs.append(hashbranch)
33 33 return revs, revs[0]
34 34 branchmap = peer.branchmap()
35 35
36 36 def primary(branch):
37 37 if branch == '.':
38 38 if not lrepo:
39 39 raise util.Abort(_("dirstate branch not accessible"))
40 40 branch = lrepo.dirstate.branch()
41 41 if branch in branchmap:
42 42 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
43 43 return True
44 44 else:
45 45 return False
46 46
47 47 for branch in branches:
48 48 if not primary(branch):
49 49 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
50 50 if hashbranch:
51 51 if not primary(hashbranch):
52 52 revs.append(hashbranch)
53 53 return revs, revs[0]
54 54
55 55 def parseurl(path, branches=None):
56 56 '''parse url#branch, returning (url, (branch, branches))'''
57 57
58 58 u = util.url(path)
59 59 branch = None
60 60 if u.fragment:
61 61 branch = u.fragment
62 62 u.fragment = None
63 63 return str(u), (branch, branches or [])
64 64
65 65 schemes = {
66 66 'bundle': bundlerepo,
67 'union': unionrepo,
67 68 'file': _local,
68 69 'http': httppeer,
69 70 'https': httppeer,
70 71 'ssh': sshpeer,
71 72 'static-http': statichttprepo,
72 73 }
73 74
74 75 def _peerlookup(path):
75 76 u = util.url(path)
76 77 scheme = u.scheme or 'file'
77 78 thing = schemes.get(scheme) or schemes['file']
78 79 try:
79 80 return thing(path)
80 81 except TypeError:
81 82 return thing
82 83
83 84 def islocal(repo):
84 85 '''return true if repo or path is local'''
85 86 if isinstance(repo, str):
86 87 try:
87 88 return _peerlookup(repo).islocal(repo)
88 89 except AttributeError:
89 90 return False
90 91 return repo.local()
91 92
92 93 def openpath(ui, path):
93 94 '''open path with open if local, url.open if remote'''
94 95 if islocal(path):
95 96 return util.posixfile(util.urllocalpath(path), 'rb')
96 97 else:
97 98 return url.open(ui, path)
98 99
99 100 def _peerorrepo(ui, path, create=False):
100 101 """return a repository object for the specified path"""
101 102 obj = _peerlookup(path).instance(ui, path, create)
102 103 ui = getattr(obj, "ui", ui)
103 104 for name, module in extensions.extensions():
104 105 hook = getattr(module, 'reposetup', None)
105 106 if hook:
106 107 hook(ui, obj)
107 108 return obj
108 109
109 110 def repository(ui, path='', create=False):
110 111 """return a repository object for the specified path"""
111 112 peer = _peerorrepo(ui, path, create)
112 113 repo = peer.local()
113 114 if not repo:
114 115 raise util.Abort(_("repository '%s' is not local") %
115 116 (path or peer.url()))
116 117 return repo.filtered('visible')
117 118
118 119 def peer(uiorrepo, opts, path, create=False):
119 120 '''return a repository peer for the specified path'''
120 121 rui = remoteui(uiorrepo, opts)
121 122 return _peerorrepo(rui, path, create).peer()
122 123
123 124 def defaultdest(source):
124 125 '''return default destination of clone if none is given'''
125 126 return os.path.basename(os.path.normpath(util.url(source).path or ''))
126 127
127 128 def share(ui, source, dest=None, update=True):
128 129 '''create a shared repository'''
129 130
130 131 if not islocal(source):
131 132 raise util.Abort(_('can only share local repositories'))
132 133
133 134 if not dest:
134 135 dest = defaultdest(source)
135 136 else:
136 137 dest = ui.expandpath(dest)
137 138
138 139 if isinstance(source, str):
139 140 origsource = ui.expandpath(source)
140 141 source, branches = parseurl(origsource)
141 142 srcrepo = repository(ui, source)
142 143 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
143 144 else:
144 145 srcrepo = source.local()
145 146 origsource = source = srcrepo.url()
146 147 checkout = None
147 148
148 149 sharedpath = srcrepo.sharedpath # if our source is already sharing
149 150
150 151 root = os.path.realpath(dest)
151 152 roothg = os.path.join(root, '.hg')
152 153
153 154 if os.path.exists(roothg):
154 155 raise util.Abort(_('destination already exists'))
155 156
156 157 if not os.path.isdir(root):
157 158 os.mkdir(root)
158 159 util.makedir(roothg, notindexed=True)
159 160
160 161 requirements = ''
161 162 try:
162 163 requirements = srcrepo.opener.read('requires')
163 164 except IOError, inst:
164 165 if inst.errno != errno.ENOENT:
165 166 raise
166 167
167 168 requirements += 'shared\n'
168 169 util.writefile(os.path.join(roothg, 'requires'), requirements)
169 170 util.writefile(os.path.join(roothg, 'sharedpath'), sharedpath)
170 171
171 172 r = repository(ui, root)
172 173
173 174 default = srcrepo.ui.config('paths', 'default')
174 175 if default:
175 176 fp = r.opener("hgrc", "w", text=True)
176 177 fp.write("[paths]\n")
177 178 fp.write("default = %s\n" % default)
178 179 fp.close()
179 180
180 181 if update:
181 182 r.ui.status(_("updating working directory\n"))
182 183 if update is not True:
183 184 checkout = update
184 185 for test in (checkout, 'default', 'tip'):
185 186 if test is None:
186 187 continue
187 188 try:
188 189 uprev = r.lookup(test)
189 190 break
190 191 except error.RepoLookupError:
191 192 continue
192 193 _update(r, uprev)
193 194
194 195 def copystore(ui, srcrepo, destpath):
195 196 '''copy files from store of srcrepo in destpath
196 197
197 198 returns destlock
198 199 '''
199 200 destlock = None
200 201 try:
201 202 hardlink = None
202 203 num = 0
203 204 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
204 205 for f in srcrepo.store.copylist():
205 206 if srcpublishing and f.endswith('phaseroots'):
206 207 continue
207 208 src = os.path.join(srcrepo.sharedpath, f)
208 209 dst = os.path.join(destpath, f)
209 210 dstbase = os.path.dirname(dst)
210 211 if dstbase and not os.path.exists(dstbase):
211 212 os.mkdir(dstbase)
212 213 if os.path.exists(src):
213 214 if dst.endswith('data'):
214 215 # lock to avoid premature writing to the target
215 216 destlock = lock.lock(os.path.join(dstbase, "lock"))
216 217 hardlink, n = util.copyfiles(src, dst, hardlink)
217 218 num += n
218 219 if hardlink:
219 220 ui.debug("linked %d files\n" % num)
220 221 else:
221 222 ui.debug("copied %d files\n" % num)
222 223 return destlock
223 224 except: # re-raises
224 225 release(destlock)
225 226 raise
226 227
227 228 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
228 229 update=True, stream=False, branch=None):
229 230 """Make a copy of an existing repository.
230 231
231 232 Create a copy of an existing repository in a new directory. The
232 233 source and destination are URLs, as passed to the repository
233 234 function. Returns a pair of repository peers, the source and
234 235 newly created destination.
235 236
236 237 The location of the source is added to the new repository's
237 238 .hg/hgrc file, as the default to be used for future pulls and
238 239 pushes.
239 240
240 241 If an exception is raised, the partly cloned/updated destination
241 242 repository will be deleted.
242 243
243 244 Arguments:
244 245
245 246 source: repository object or URL
246 247
247 248 dest: URL of destination repository to create (defaults to base
248 249 name of source repository)
249 250
250 251 pull: always pull from source repository, even in local case
251 252
252 253 stream: stream raw data uncompressed from repository (fast over
253 254 LAN, slow over WAN)
254 255
255 256 rev: revision to clone up to (implies pull=True)
256 257
257 258 update: update working directory after clone completes, if
258 259 destination is local repository (True means update to default rev,
259 260 anything else is treated as a revision)
260 261
261 262 branch: branches to clone
262 263 """
263 264
264 265 if isinstance(source, str):
265 266 origsource = ui.expandpath(source)
266 267 source, branch = parseurl(origsource, branch)
267 268 srcpeer = peer(ui, peeropts, source)
268 269 else:
269 270 srcpeer = source.peer() # in case we were called with a localrepo
270 271 branch = (None, branch or [])
271 272 origsource = source = srcpeer.url()
272 273 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
273 274
274 275 if dest is None:
275 276 dest = defaultdest(source)
276 277 ui.status(_("destination directory: %s\n") % dest)
277 278 else:
278 279 dest = ui.expandpath(dest)
279 280
280 281 dest = util.urllocalpath(dest)
281 282 source = util.urllocalpath(source)
282 283
283 284 if not dest:
284 285 raise util.Abort(_("empty destination path is not valid"))
285 286 if os.path.exists(dest):
286 287 if not os.path.isdir(dest):
287 288 raise util.Abort(_("destination '%s' already exists") % dest)
288 289 elif os.listdir(dest):
289 290 raise util.Abort(_("destination '%s' is not empty") % dest)
290 291
291 292 srclock = destlock = cleandir = None
292 293 srcrepo = srcpeer.local()
293 294 try:
294 295 abspath = origsource
295 296 if islocal(origsource):
296 297 abspath = os.path.abspath(util.urllocalpath(origsource))
297 298
298 299 if islocal(dest):
299 300 cleandir = dest
300 301
301 302 copy = False
302 303 if (srcrepo and srcrepo.cancopy() and islocal(dest)
303 304 and not phases.hassecret(srcrepo)):
304 305 copy = not pull and not rev
305 306
306 307 if copy:
307 308 try:
308 309 # we use a lock here because if we race with commit, we
309 310 # can end up with extra data in the cloned revlogs that's
310 311 # not pointed to by changesets, thus causing verify to
311 312 # fail
312 313 srclock = srcrepo.lock(wait=False)
313 314 except error.LockError:
314 315 copy = False
315 316
316 317 if copy:
317 318 srcrepo.hook('preoutgoing', throw=True, source='clone')
318 319 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
319 320 if not os.path.exists(dest):
320 321 os.mkdir(dest)
321 322 else:
322 323 # only clean up directories we create ourselves
323 324 cleandir = hgdir
324 325 try:
325 326 destpath = hgdir
326 327 util.makedir(destpath, notindexed=True)
327 328 except OSError, inst:
328 329 if inst.errno == errno.EEXIST:
329 330 cleandir = None
330 331 raise util.Abort(_("destination '%s' already exists")
331 332 % dest)
332 333 raise
333 334
334 335 destlock = copystore(ui, srcrepo, destpath)
335 336
336 337 # Recomputing branch cache might be slow on big repos,
337 338 # so just copy it
338 339 dstcachedir = os.path.join(destpath, 'cache')
339 340 srcbranchcache = srcrepo.sjoin('cache/branchheads')
340 341 dstbranchcache = os.path.join(dstcachedir, 'branchheads')
341 342 if os.path.exists(srcbranchcache):
342 343 if not os.path.exists(dstcachedir):
343 344 os.mkdir(dstcachedir)
344 345 util.copyfile(srcbranchcache, dstbranchcache)
345 346
346 347 # we need to re-init the repo after manually copying the data
347 348 # into it
348 349 destpeer = peer(srcrepo, peeropts, dest)
349 350 srcrepo.hook('outgoing', source='clone',
350 351 node=node.hex(node.nullid))
351 352 else:
352 353 try:
353 354 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
354 355 # only pass ui when no srcrepo
355 356 except OSError, inst:
356 357 if inst.errno == errno.EEXIST:
357 358 cleandir = None
358 359 raise util.Abort(_("destination '%s' already exists")
359 360 % dest)
360 361 raise
361 362
362 363 revs = None
363 364 if rev:
364 365 if not srcpeer.capable('lookup'):
365 366 raise util.Abort(_("src repository does not support "
366 367 "revision lookup and so doesn't "
367 368 "support clone by revision"))
368 369 revs = [srcpeer.lookup(r) for r in rev]
369 370 checkout = revs[0]
370 371 if destpeer.local():
371 372 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
372 373 elif srcrepo:
373 374 srcrepo.push(destpeer, revs=revs)
374 375 else:
375 376 raise util.Abort(_("clone from remote to remote not supported"))
376 377
377 378 cleandir = None
378 379
379 380 # clone all bookmarks except divergent ones
380 381 destrepo = destpeer.local()
381 382 if destrepo and srcpeer.capable("pushkey"):
382 383 rb = srcpeer.listkeys('bookmarks')
383 384 marks = destrepo._bookmarks
384 385 for k, n in rb.iteritems():
385 386 try:
386 387 m = destrepo.lookup(n)
387 388 marks[k] = m
388 389 except error.RepoLookupError:
389 390 pass
390 391 if rb:
391 392 marks.write()
392 393 elif srcrepo and destpeer.capable("pushkey"):
393 394 for k, n in srcrepo._bookmarks.iteritems():
394 395 destpeer.pushkey('bookmarks', k, '', hex(n))
395 396
396 397 if destrepo:
397 398 fp = destrepo.opener("hgrc", "w", text=True)
398 399 fp.write("[paths]\n")
399 400 u = util.url(abspath)
400 401 u.passwd = None
401 402 defaulturl = str(u)
402 403 fp.write("default = %s\n" % defaulturl)
403 404 fp.close()
404 405
405 406 destrepo.ui.setconfig('paths', 'default', defaulturl)
406 407
407 408 if update:
408 409 if update is not True:
409 410 checkout = srcpeer.lookup(update)
410 411 uprev = None
411 412 status = None
412 413 if checkout is not None:
413 414 try:
414 415 uprev = destrepo.lookup(checkout)
415 416 except error.RepoLookupError:
416 417 pass
417 418 if uprev is None:
418 419 try:
419 420 uprev = destrepo._bookmarks['@']
420 421 update = '@'
421 422 bn = destrepo[uprev].branch()
422 423 if bn == 'default':
423 424 status = _("updating to bookmark @\n")
424 425 else:
425 426 status = _("updating to bookmark @ on branch %s\n"
426 427 % bn)
427 428 except KeyError:
428 429 try:
429 430 uprev = destrepo.branchtip('default')
430 431 except error.RepoLookupError:
431 432 uprev = destrepo.lookup('tip')
432 433 if not status:
433 434 bn = destrepo[uprev].branch()
434 435 status = _("updating to branch %s\n") % bn
435 436 destrepo.ui.status(status)
436 437 _update(destrepo, uprev)
437 438 if update in destrepo._bookmarks:
438 439 bookmarks.setcurrent(destrepo, update)
439 440
440 441 return srcpeer, destpeer
441 442 finally:
442 443 release(srclock, destlock)
443 444 if cleandir is not None:
444 445 shutil.rmtree(cleandir, True)
445 446 if srcpeer is not None:
446 447 srcpeer.close()
447 448
448 449 def _showstats(repo, stats):
449 450 repo.ui.status(_("%d files updated, %d files merged, "
450 451 "%d files removed, %d files unresolved\n") % stats)
451 452
452 453 def updaterepo(repo, node, overwrite):
453 454 """Update the working directory to node.
454 455
455 456 When overwrite is set, changes are clobbered, merged else
456 457
457 458 returns stats (see pydoc mercurial.merge.applyupdates)"""
458 459 return mergemod.update(repo, node, False, overwrite, None)
459 460
460 461 def update(repo, node):
461 462 """update the working directory to node, merging linear changes"""
462 463 stats = updaterepo(repo, node, False)
463 464 _showstats(repo, stats)
464 465 if stats[3]:
465 466 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
466 467 return stats[3] > 0
467 468
468 469 # naming conflict in clone()
469 470 _update = update
470 471
471 472 def clean(repo, node, show_stats=True):
472 473 """forcibly switch the working directory to node, clobbering changes"""
473 474 stats = updaterepo(repo, node, True)
474 475 if show_stats:
475 476 _showstats(repo, stats)
476 477 return stats[3] > 0
477 478
478 479 def merge(repo, node, force=None, remind=True):
479 480 """Branch merge with node, resolving changes. Return true if any
480 481 unresolved conflicts."""
481 482 stats = mergemod.update(repo, node, True, force, False)
482 483 _showstats(repo, stats)
483 484 if stats[3]:
484 485 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
485 486 "or 'hg update -C .' to abandon\n"))
486 487 elif remind:
487 488 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
488 489 return stats[3] > 0
489 490
490 491 def _incoming(displaychlist, subreporecurse, ui, repo, source,
491 492 opts, buffered=False):
492 493 """
493 494 Helper for incoming / gincoming.
494 495 displaychlist gets called with
495 496 (remoterepo, incomingchangesetlist, displayer) parameters,
496 497 and is supposed to contain only code that can't be unified.
497 498 """
498 499 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
499 500 other = peer(repo, opts, source)
500 501 ui.status(_('comparing with %s\n') % util.hidepassword(source))
501 502 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
502 503
503 504 if revs:
504 505 revs = [other.lookup(rev) for rev in revs]
505 506 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
506 507 revs, opts["bundle"], opts["force"])
507 508 try:
508 509 if not chlist:
509 510 ui.status(_("no changes found\n"))
510 511 return subreporecurse()
511 512
512 513 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
513 514
514 515 # XXX once graphlog extension makes it into core,
515 516 # should be replaced by a if graph/else
516 517 displaychlist(other, chlist, displayer)
517 518
518 519 displayer.close()
519 520 finally:
520 521 cleanupfn()
521 522 subreporecurse()
522 523 return 0 # exit code is zero since we found incoming changes
523 524
524 525 def incoming(ui, repo, source, opts):
525 526 def subreporecurse():
526 527 ret = 1
527 528 if opts.get('subrepos'):
528 529 ctx = repo[None]
529 530 for subpath in sorted(ctx.substate):
530 531 sub = ctx.sub(subpath)
531 532 ret = min(ret, sub.incoming(ui, source, opts))
532 533 return ret
533 534
534 535 def display(other, chlist, displayer):
535 536 limit = cmdutil.loglimit(opts)
536 537 if opts.get('newest_first'):
537 538 chlist.reverse()
538 539 count = 0
539 540 for n in chlist:
540 541 if limit is not None and count >= limit:
541 542 break
542 543 parents = [p for p in other.changelog.parents(n) if p != nullid]
543 544 if opts.get('no_merges') and len(parents) == 2:
544 545 continue
545 546 count += 1
546 547 displayer.show(other[n])
547 548 return _incoming(display, subreporecurse, ui, repo, source, opts)
548 549
549 550 def _outgoing(ui, repo, dest, opts):
550 551 dest = ui.expandpath(dest or 'default-push', dest or 'default')
551 552 dest, branches = parseurl(dest, opts.get('branch'))
552 553 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
553 554 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
554 555 if revs:
555 556 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
556 557
557 558 other = peer(repo, opts, dest)
558 559 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
559 560 force=opts.get('force'))
560 561 o = outgoing.missing
561 562 if not o:
562 563 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
563 564 return None
564 565 return o
565 566
566 567 def outgoing(ui, repo, dest, opts):
567 568 def recurse():
568 569 ret = 1
569 570 if opts.get('subrepos'):
570 571 ctx = repo[None]
571 572 for subpath in sorted(ctx.substate):
572 573 sub = ctx.sub(subpath)
573 574 ret = min(ret, sub.outgoing(ui, dest, opts))
574 575 return ret
575 576
576 577 limit = cmdutil.loglimit(opts)
577 578 o = _outgoing(ui, repo, dest, opts)
578 579 if o is None:
579 580 return recurse()
580 581
581 582 if opts.get('newest_first'):
582 583 o.reverse()
583 584 displayer = cmdutil.show_changeset(ui, repo, opts)
584 585 count = 0
585 586 for n in o:
586 587 if limit is not None and count >= limit:
587 588 break
588 589 parents = [p for p in repo.changelog.parents(n) if p != nullid]
589 590 if opts.get('no_merges') and len(parents) == 2:
590 591 continue
591 592 count += 1
592 593 displayer.show(repo[n])
593 594 displayer.close()
594 595 recurse()
595 596 return 0 # exit code is zero since we found outgoing changes
596 597
597 598 def revert(repo, node, choose):
598 599 """revert changes to revision in node without updating dirstate"""
599 600 return mergemod.update(repo, node, False, True, choose)[3] > 0
600 601
601 602 def verify(repo):
602 603 """verify the consistency of a repository"""
603 604 return verifymod.verify(repo)
604 605
605 606 def remoteui(src, opts):
606 607 'build a remote ui from ui or repo and opts'
607 608 if util.safehasattr(src, 'baseui'): # looks like a repository
608 609 dst = src.baseui.copy() # drop repo-specific config
609 610 src = src.ui # copy target options from repo
610 611 else: # assume it's a global ui object
611 612 dst = src.copy() # keep all global options
612 613
613 614 # copy ssh-specific options
614 615 for o in 'ssh', 'remotecmd':
615 616 v = opts.get(o) or src.config('ui', o)
616 617 if v:
617 618 dst.setconfig("ui", o, v)
618 619
619 620 # copy bundle-specific options
620 621 r = src.config('bundle', 'mainreporoot')
621 622 if r:
622 623 dst.setconfig('bundle', 'mainreporoot', r)
623 624
624 625 # copy selected local settings to the remote ui
625 626 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
626 627 for key, val in src.configitems(sect):
627 628 dst.setconfig(sect, key, val)
628 629 v = src.config('web', 'cacerts')
629 630 if v:
630 631 dst.setconfig('web', 'cacerts', util.expandpath(v))
631 632
632 633 return dst
General Comments 0
You need to be logged in to leave comments. Login now