Mercurial > public > mercurial-scm > hg
comparison mercurial/localrepo.py @ 20345:8567b4ea76ac
exchange: extract push function from localrepo
The localrepo class if far too big. Push and pull logic will be extracted and
reworked to better fit with the fact they now exchange more than plain changeset
bundle.
This changeset extract the push code. later changeset will slowly slice this
over 200 hundred lines and 8 indentation level function into smaller saner
brick.
The localrepo.push method is kept for now to limit impact on user code. But it
will be ultimately removed, now that the public supposed API is hold by peer.
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Thu, 30 Jan 2014 15:34:01 -0800 |
parents | a959f7167077 |
children | a6cf48b2880d |
comparison
equal
deleted
inserted
replaced
20344:b15ba2d95428 | 20345:8567b4ea76ac |
---|---|
7 from node import hex, nullid, short | 7 from node import hex, nullid, short |
8 from i18n import _ | 8 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 |
16 import tags as tagsmod | 16 import tags as tagsmod |
17 from lock import release | 17 from lock import release |
1748 command. | 1748 command. |
1749 """ | 1749 """ |
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': |
1973 self.ui.status(_("%d changesets found\n") % len(nodes)) | 1757 self.ui.status(_("%d changesets found\n") % len(nodes)) |
1974 if self.ui.debugflag: | 1758 if self.ui.debugflag: |