##// END OF EJS Templates
exchange: extract push function from localrepo...
Pierre-Yves David -
r20345:8567b4ea default
parent child Browse files
Show More
@@ -0,0 +1,231 b''
1 # exchange.py - utily to exchange data between repo.
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 from i18n import _
9 from node import hex
10 import errno
11 import util, scmutil, changegroup
12 import discovery, phases, obsolete, bookmarks
13
14 def push(repo, remote, force=False, revs=None, newbranch=False):
15 '''Push outgoing changesets (limited by revs) from a local
16 repository to remote. Return an integer:
17 - None means nothing to push
18 - 0 means HTTP error
19 - 1 means we pushed and remote head count is unchanged *or*
20 we have outgoing changesets but refused to push
21 - other values as described by addchangegroup()
22 '''
23 if remote.local():
24 missing = set(repo.requirements) - remote.local().supported
25 if missing:
26 msg = _("required features are not"
27 " supported in the destination:"
28 " %s") % (', '.join(sorted(missing)))
29 raise util.Abort(msg)
30
31 # there are two ways to push to remote repo:
32 #
33 # addchangegroup assumes local user can lock remote
34 # repo (local filesystem, old ssh servers).
35 #
36 # unbundle assumes local user cannot lock remote repo (new ssh
37 # servers, http servers).
38
39 if not remote.canpush():
40 raise util.Abort(_("destination does not support push"))
41 unfi = repo.unfiltered()
42 def localphasemove(nodes, phase=phases.public):
43 """move <nodes> to <phase> in the local source repo"""
44 if locallock is not None:
45 phases.advanceboundary(repo, phase, nodes)
46 else:
47 # repo is not locked, do not change any phases!
48 # Informs the user that phases should have been moved when
49 # applicable.
50 actualmoves = [n for n in nodes if phase < repo[n].phase()]
51 phasestr = phases.phasenames[phase]
52 if actualmoves:
53 repo.ui.status(_('cannot lock source repo, skipping local'
54 ' %s phase update\n') % phasestr)
55 # get local lock as we might write phase data
56 locallock = None
57 try:
58 locallock = repo.lock()
59 except IOError, err:
60 if err.errno != errno.EACCES:
61 raise
62 # source repo cannot be locked.
63 # We do not abort the push, but just disable the local phase
64 # synchronisation.
65 msg = 'cannot lock source repository: %s\n' % err
66 repo.ui.debug(msg)
67 try:
68 repo.checkpush(force, revs)
69 lock = None
70 unbundle = remote.capable('unbundle')
71 if not unbundle:
72 lock = remote.lock()
73 try:
74 # discovery
75 fci = discovery.findcommonincoming
76 commoninc = fci(unfi, remote, force=force)
77 common, inc, remoteheads = commoninc
78 fco = discovery.findcommonoutgoing
79 outgoing = fco(unfi, remote, onlyheads=revs,
80 commoninc=commoninc, force=force)
81
82
83 if not outgoing.missing:
84 # nothing to push
85 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
86 ret = None
87 else:
88 # something to push
89 if not force:
90 # if repo.obsstore == False --> no obsolete
91 # then, save the iteration
92 if unfi.obsstore:
93 # this message are here for 80 char limit reason
94 mso = _("push includes obsolete changeset: %s!")
95 mst = "push includes %s changeset: %s!"
96 # plain versions for i18n tool to detect them
97 _("push includes unstable changeset: %s!")
98 _("push includes bumped changeset: %s!")
99 _("push includes divergent changeset: %s!")
100 # If we are to push if there is at least one
101 # obsolete or unstable changeset in missing, at
102 # least one of the missinghead will be obsolete or
103 # unstable. So checking heads only is ok
104 for node in outgoing.missingheads:
105 ctx = unfi[node]
106 if ctx.obsolete():
107 raise util.Abort(mso % ctx)
108 elif ctx.troubled():
109 raise util.Abort(_(mst)
110 % (ctx.troubles()[0],
111 ctx))
112 newbm = repo.ui.configlist('bookmarks', 'pushing')
113 discovery.checkheads(unfi, remote, outgoing,
114 remoteheads, newbranch,
115 bool(inc), newbm)
116
117 # TODO: get bundlecaps from remote
118 bundlecaps = None
119 # create a changegroup from local
120 if revs is None and not (outgoing.excluded
121 or repo.changelog.filteredrevs):
122 # push everything,
123 # use the fast path, no race possible on push
124 bundler = changegroup.bundle10(repo, bundlecaps)
125 cg = repo._changegroupsubset(outgoing,
126 bundler,
127 'push',
128 fastpath=True)
129 else:
130 cg = repo.getlocalbundle('push', outgoing, bundlecaps)
131
132 # apply changegroup to remote
133 if unbundle:
134 # local repo finds heads on server, finds out what
135 # revs it must push. once revs transferred, if server
136 # finds it has different heads (someone else won
137 # commit/push race), server aborts.
138 if force:
139 remoteheads = ['force']
140 # ssh: return remote's addchangegroup()
141 # http: return remote's addchangegroup() or 0 for error
142 ret = remote.unbundle(cg, remoteheads, 'push')
143 else:
144 # we return an integer indicating remote head count
145 # change
146 ret = remote.addchangegroup(cg, 'push', repo.url())
147
148 if ret:
149 # push succeed, synchronize target of the push
150 cheads = outgoing.missingheads
151 elif revs is None:
152 # All out push fails. synchronize all common
153 cheads = outgoing.commonheads
154 else:
155 # I want cheads = heads(::missingheads and ::commonheads)
156 # (missingheads is revs with secret changeset filtered out)
157 #
158 # This can be expressed as:
159 # cheads = ( (missingheads and ::commonheads)
160 # + (commonheads and ::missingheads))"
161 # )
162 #
163 # while trying to push we already computed the following:
164 # common = (::commonheads)
165 # missing = ((commonheads::missingheads) - commonheads)
166 #
167 # We can pick:
168 # * missingheads part of common (::commonheads)
169 common = set(outgoing.common)
170 nm = repo.changelog.nodemap
171 cheads = [node for node in revs if nm[node] in common]
172 # and
173 # * commonheads parents on missing
174 revset = unfi.set('%ln and parents(roots(%ln))',
175 outgoing.commonheads,
176 outgoing.missing)
177 cheads.extend(c.node() for c in revset)
178 # even when we don't push, exchanging phase data is useful
179 remotephases = remote.listkeys('phases')
180 if (repo.ui.configbool('ui', '_usedassubrepo', False)
181 and remotephases # server supports phases
182 and ret is None # nothing was pushed
183 and remotephases.get('publishing', False)):
184 # When:
185 # - this is a subrepo push
186 # - and remote support phase
187 # - and no changeset was pushed
188 # - and remote is publishing
189 # We may be in issue 3871 case!
190 # We drop the possible phase synchronisation done by
191 # courtesy to publish changesets possibly locally draft
192 # on the remote.
193 remotephases = {'publishing': 'True'}
194 if not remotephases: # old server or public only repo
195 localphasemove(cheads)
196 # don't push any phase data as there is nothing to push
197 else:
198 ana = phases.analyzeremotephases(repo, cheads, remotephases)
199 pheads, droots = ana
200 ### Apply remote phase on local
201 if remotephases.get('publishing', False):
202 localphasemove(cheads)
203 else: # publish = False
204 localphasemove(pheads)
205 localphasemove(cheads, phases.draft)
206 ### Apply local phase on remote
207
208 # Get the list of all revs draft on remote by public here.
209 # XXX Beware that revset break if droots is not strictly
210 # XXX root we may want to ensure it is but it is costly
211 outdated = unfi.set('heads((%ln::%ln) and public())',
212 droots, cheads)
213 for newremotehead in outdated:
214 r = remote.pushkey('phases',
215 newremotehead.hex(),
216 str(phases.draft),
217 str(phases.public))
218 if not r:
219 repo.ui.warn(_('updating %s to public failed!\n')
220 % newremotehead)
221 repo.ui.debug('try to push obsolete markers to remote\n')
222 obsolete.syncpush(repo, remote)
223 finally:
224 if lock is not None:
225 lock.release()
226 finally:
227 if locallock is not None:
228 locallock.release()
229
230 bookmarks.updateremote(repo.ui, unfi, remote, revs)
231 return ret
@@ -9,7 +9,7 b' from i18n import _'
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import lock as lockmod
11 import lock as lockmod
12 import transaction, store, encoding
12 import transaction, store, encoding, exchange
13 import scmutil, util, extensions, hook, error, revset
13 import scmutil, util, extensions, hook, error, revset
14 import match as matchmod
14 import match as matchmod
15 import merge as mergemod
15 import merge as mergemod
@@ -1750,223 +1750,7 b' class localrepository(object):'
1750 pass
1750 pass
1751
1751
1752 def push(self, remote, force=False, revs=None, newbranch=False):
1752 def push(self, remote, force=False, revs=None, newbranch=False):
1753 '''Push outgoing changesets (limited by revs) from the current
1753 return exchange.push(self, remote, force, revs, newbranch)
1754 repository to remote. Return an integer:
1755 - None means nothing to push
1756 - 0 means HTTP error
1757 - 1 means we pushed and remote head count is unchanged *or*
1758 we have outgoing changesets but refused to push
1759 - other values as described by addchangegroup()
1760 '''
1761 if remote.local():
1762 missing = set(self.requirements) - remote.local().supported
1763 if missing:
1764 msg = _("required features are not"
1765 " supported in the destination:"
1766 " %s") % (', '.join(sorted(missing)))
1767 raise util.Abort(msg)
1768
1769 # there are two ways to push to remote repo:
1770 #
1771 # addchangegroup assumes local user can lock remote
1772 # repo (local filesystem, old ssh servers).
1773 #
1774 # unbundle assumes local user cannot lock remote repo (new ssh
1775 # servers, http servers).
1776
1777 if not remote.canpush():
1778 raise util.Abort(_("destination does not support push"))
1779 unfi = self.unfiltered()
1780 def localphasemove(nodes, phase=phases.public):
1781 """move <nodes> to <phase> in the local source repo"""
1782 if locallock is not None:
1783 phases.advanceboundary(self, phase, nodes)
1784 else:
1785 # repo is not locked, do not change any phases!
1786 # Informs the user that phases should have been moved when
1787 # applicable.
1788 actualmoves = [n for n in nodes if phase < self[n].phase()]
1789 phasestr = phases.phasenames[phase]
1790 if actualmoves:
1791 self.ui.status(_('cannot lock source repo, skipping local'
1792 ' %s phase update\n') % phasestr)
1793 # get local lock as we might write phase data
1794 locallock = None
1795 try:
1796 locallock = self.lock()
1797 except IOError, err:
1798 if err.errno != errno.EACCES:
1799 raise
1800 # source repo cannot be locked.
1801 # We do not abort the push, but just disable the local phase
1802 # synchronisation.
1803 msg = 'cannot lock source repository: %s\n' % err
1804 self.ui.debug(msg)
1805 try:
1806 self.checkpush(force, revs)
1807 lock = None
1808 unbundle = remote.capable('unbundle')
1809 if not unbundle:
1810 lock = remote.lock()
1811 try:
1812 # discovery
1813 fci = discovery.findcommonincoming
1814 commoninc = fci(unfi, remote, force=force)
1815 common, inc, remoteheads = commoninc
1816 fco = discovery.findcommonoutgoing
1817 outgoing = fco(unfi, remote, onlyheads=revs,
1818 commoninc=commoninc, force=force)
1819
1820
1821 if not outgoing.missing:
1822 # nothing to push
1823 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1824 ret = None
1825 else:
1826 # something to push
1827 if not force:
1828 # if self.obsstore == False --> no obsolete
1829 # then, save the iteration
1830 if unfi.obsstore:
1831 # this message are here for 80 char limit reason
1832 mso = _("push includes obsolete changeset: %s!")
1833 mst = "push includes %s changeset: %s!"
1834 # plain versions for i18n tool to detect them
1835 _("push includes unstable changeset: %s!")
1836 _("push includes bumped changeset: %s!")
1837 _("push includes divergent changeset: %s!")
1838 # If we are to push if there is at least one
1839 # obsolete or unstable changeset in missing, at
1840 # least one of the missinghead will be obsolete or
1841 # unstable. So checking heads only is ok
1842 for node in outgoing.missingheads:
1843 ctx = unfi[node]
1844 if ctx.obsolete():
1845 raise util.Abort(mso % ctx)
1846 elif ctx.troubled():
1847 raise util.Abort(_(mst)
1848 % (ctx.troubles()[0],
1849 ctx))
1850 newbm = self.ui.configlist('bookmarks', 'pushing')
1851 discovery.checkheads(unfi, remote, outgoing,
1852 remoteheads, newbranch,
1853 bool(inc), newbm)
1854
1855 # TODO: get bundlecaps from remote
1856 bundlecaps = None
1857 # create a changegroup from local
1858 if revs is None and not (outgoing.excluded
1859 or self.changelog.filteredrevs):
1860 # push everything,
1861 # use the fast path, no race possible on push
1862 bundler = changegroup.bundle10(self, bundlecaps)
1863 cg = self._changegroupsubset(outgoing,
1864 bundler,
1865 'push',
1866 fastpath=True)
1867 else:
1868 cg = self.getlocalbundle('push', outgoing, bundlecaps)
1869
1870 # apply changegroup to remote
1871 if unbundle:
1872 # local repo finds heads on server, finds out what
1873 # revs it must push. once revs transferred, if server
1874 # finds it has different heads (someone else won
1875 # commit/push race), server aborts.
1876 if force:
1877 remoteheads = ['force']
1878 # ssh: return remote's addchangegroup()
1879 # http: return remote's addchangegroup() or 0 for error
1880 ret = remote.unbundle(cg, remoteheads, 'push')
1881 else:
1882 # we return an integer indicating remote head count
1883 # change
1884 ret = remote.addchangegroup(cg, 'push', self.url())
1885
1886 if ret:
1887 # push succeed, synchronize target of the push
1888 cheads = outgoing.missingheads
1889 elif revs is None:
1890 # All out push fails. synchronize all common
1891 cheads = outgoing.commonheads
1892 else:
1893 # I want cheads = heads(::missingheads and ::commonheads)
1894 # (missingheads is revs with secret changeset filtered out)
1895 #
1896 # This can be expressed as:
1897 # cheads = ( (missingheads and ::commonheads)
1898 # + (commonheads and ::missingheads))"
1899 # )
1900 #
1901 # while trying to push we already computed the following:
1902 # common = (::commonheads)
1903 # missing = ((commonheads::missingheads) - commonheads)
1904 #
1905 # We can pick:
1906 # * missingheads part of common (::commonheads)
1907 common = set(outgoing.common)
1908 nm = self.changelog.nodemap
1909 cheads = [node for node in revs if nm[node] in common]
1910 # and
1911 # * commonheads parents on missing
1912 revset = unfi.set('%ln and parents(roots(%ln))',
1913 outgoing.commonheads,
1914 outgoing.missing)
1915 cheads.extend(c.node() for c in revset)
1916 # even when we don't push, exchanging phase data is useful
1917 remotephases = remote.listkeys('phases')
1918 if (self.ui.configbool('ui', '_usedassubrepo', False)
1919 and remotephases # server supports phases
1920 and ret is None # nothing was pushed
1921 and remotephases.get('publishing', False)):
1922 # When:
1923 # - this is a subrepo push
1924 # - and remote support phase
1925 # - and no changeset was pushed
1926 # - and remote is publishing
1927 # We may be in issue 3871 case!
1928 # We drop the possible phase synchronisation done by
1929 # courtesy to publish changesets possibly locally draft
1930 # on the remote.
1931 remotephases = {'publishing': 'True'}
1932 if not remotephases: # old server or public only repo
1933 localphasemove(cheads)
1934 # don't push any phase data as there is nothing to push
1935 else:
1936 ana = phases.analyzeremotephases(self, cheads, remotephases)
1937 pheads, droots = ana
1938 ### Apply remote phase on local
1939 if remotephases.get('publishing', False):
1940 localphasemove(cheads)
1941 else: # publish = False
1942 localphasemove(pheads)
1943 localphasemove(cheads, phases.draft)
1944 ### Apply local phase on remote
1945
1946 # Get the list of all revs draft on remote by public here.
1947 # XXX Beware that revset break if droots is not strictly
1948 # XXX root we may want to ensure it is but it is costly
1949 outdated = unfi.set('heads((%ln::%ln) and public())',
1950 droots, cheads)
1951 for newremotehead in outdated:
1952 r = remote.pushkey('phases',
1953 newremotehead.hex(),
1954 str(phases.draft),
1955 str(phases.public))
1956 if not r:
1957 self.ui.warn(_('updating %s to public failed!\n')
1958 % newremotehead)
1959 self.ui.debug('try to push obsolete markers to remote\n')
1960 obsolete.syncpush(self, remote)
1961 finally:
1962 if lock is not None:
1963 lock.release()
1964 finally:
1965 if locallock is not None:
1966 locallock.release()
1967
1968 bookmarks.updateremote(self.ui, unfi, remote, revs)
1969 return ret
1970
1754
1971 def changegroupinfo(self, nodes, source):
1755 def changegroupinfo(self, nodes, source):
1972 if self.ui.verbose or source == 'bundle':
1756 if self.ui.verbose or source == 'bundle':
General Comments 0
You need to be logged in to leave comments. Login now