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