git: respect selective pull

Summary:
Some git repos (ex. pytorch) has thousands of branches that won't scale well.
Change no-argument pull to respect selective pull to mitigate it.

Reviewed By: DurhamG

Differential Revision: D33485186

fbshipit-source-id: b735704850847c49b0bfeb94972e4bb778e726a4
This commit is contained in:
Jun Wu 2022-01-20 10:19:33 -08:00 committed by Facebook GitHub Bot
parent e6d1d7fa2b
commit 77cb1f9890
5 changed files with 55 additions and 7 deletions

View File

@ -1055,6 +1055,14 @@ class remotenames(dict):
self._node2hoists.setdefault(node[0], []).append(name) self._node2hoists.setdefault(node[0], []).append(name)
return self._node2hoists return self._node2hoists
def get(self, name):
"""Resolve a remote bookmark. Return None if the bookmark does not exist"""
nodes = self["bookmarks"].get(name)
if nodes:
return nodes[0]
else:
return None
@property @property
def changecount(self): def changecount(self):
return self._changecount return self._changecount

View File

@ -4584,6 +4584,8 @@ def pull(ui, repo, source="default", **opts):
if source == "default": if source == "default":
source = "origin" source = "origin"
refspecs = (opts.get("bookmark") or []) + (opts.get("rev") or []) refspecs = (opts.get("bookmark") or []) + (opts.get("rev") or [])
if not refspecs:
refspecs = git.defaultpullrefspecs(repo, source)
ret = git.pull(repo, source, refspecs) ret = git.pull(repo, source, refspecs)
if ret == 0 and opts.get("update"): if ret == 0 and opts.get("update"):
# Figure out the node to checkout # Figure out the node to checkout

View File

@ -15,9 +15,9 @@ import subprocess
import bindings import bindings
from edenscm import tracing from edenscm import tracing
from . import error, util from . import bookmarks as bookmod, error, util
from .i18n import _ from .i18n import _
from .node import hex, nullid from .node import bin, hex, nullid
GIT_DIR_FILE = "gitdir" GIT_DIR_FILE = "gitdir"
GIT_REQUIREMENT = "git" GIT_REQUIREMENT = "git"
@ -68,7 +68,8 @@ def clone(ui, url, destpath=None, update=True):
raise error.Abort(_("git clone was not successful")) raise error.Abort(_("git clone was not successful"))
initgit(repo, "git") initgit(repo, "git")
if url: if url:
pull(repo, "origin", []) refspecs = defaultpullrefspecs(repo, "origin")
pull(repo, "origin", refspecs)
except Exception: except Exception:
repo = None repo = None
shutil.rmtree(destpath, ignore_errors=True) shutil.rmtree(destpath, ignore_errors=True)
@ -186,6 +187,9 @@ def revparse(repo, revspec):
def pull(repo, source, refspecs): def pull(repo, source, refspecs):
"""Run `git fetch` on the backing repo to perform a pull""" """Run `git fetch` on the backing repo to perform a pull"""
if not refspecs:
# Nothing to pull
return 0
ret = rungit( ret = rungit(
repo, repo,
["fetch", "--no-write-fetch-head", "--no-tags", "--prune", source] + refspecs, ["fetch", "--no-write-fetch-head", "--no-tags", "--prune", source] + refspecs,
@ -212,6 +216,41 @@ def push(repo, dest, pushnode, to, force=False):
return ret return ret
def listremote(repo, source, patterns):
"""List references of the remote peer
Return a dict of name to node.
"""
out = callgit(repo, ["ls-remote", "--refs", source] + patterns)
refs = {}
for line in out.splitlines():
if b"\t" not in line:
continue
hexnode, name = line.split(b"\t", 1)
refs[name.decode("utf-8")] = bin(hexnode)
return refs
def defaultpullrefspecs(repo, source):
"""default refspecs for 'git fetch' respecting selective pull"""
names = bookmod.selectivepullbookmarknames(repo)
patterns = ["refs/heads/%s" % name for name in names]
listed = listremote(repo, source, patterns)
refspecs = []
for name in names:
refspec = "refs/heads/%s" % name
remotenode = listed.get(refspec)
if repo._remotenames.get("%s/%s" % (source, name)) == remotenode:
# not changed, skip
continue
if remotenode is None:
# removed remotely
refspec = ":refs/remotes/%s/heads/%s" % (source, name)
refspecs.append(refspec)
return refspecs
def callgit(repo, args): def callgit(repo, args):
"""Run git command in the backing git repo, return its output""" """Run git command in the backing git repo, return its output"""
gitdir = readgitdir(repo) gitdir = readgitdir(repo)

View File

@ -22,6 +22,7 @@ autopullpattern=
disallowedto=^origin/ disallowedto=^origin/
disallowhint=please don't specify 'origin/' prefix in remote bookmark's name disallowhint=please don't specify 'origin/' prefix in remote bookmark's name
hoist=origin hoist=origin
selectivepulldefault=main,master
[smartlog] [smartlog]
names=main,master names=main,master

View File

@ -201,11 +201,10 @@ Test clone with flags (--noupdate, --updaterev):
$ hg log -r . -T '{node|short}\n' $ hg log -r . -T '{node|short}\n'
000000000000 000000000000
$ hg bookmarks --remote $ hg bookmarks --remote
origin/foo 5c9a5ee451a8
origin/master 3f5848713286 origin/master 3f5848713286
$ cd .. $ cd ..
$ hg clone "git+file://$TESTTMP/gitrepo" cloned1 $ hg clone "git+file://$TESTTMP/gitrepo" cloned1 --config remotenames.selectivepulldefault=foo,master
From file:/*/$TESTTMP/gitrepo (glob) From file:/*/$TESTTMP/gitrepo (glob)
* [new branch] foo -> origin/foo * [new branch] foo -> origin/foo
* [new branch] master -> origin/master * [new branch] master -> origin/master
@ -214,10 +213,9 @@ Test clone with flags (--noupdate, --updaterev):
5c9a5ee451a8 origin/foo alpha3 5c9a5ee451a8 origin/foo alpha3
$ cd .. $ cd ..
$ hg clone --updaterev origin/foo "git+file://$TESTTMP/gitrepo" cloned2 $ hg clone --updaterev origin/foo "git+file://$TESTTMP/gitrepo" cloned2 --config remotenames.selectivepulldefault=foo
From file:/*/$TESTTMP/gitrepo (glob) From file:/*/$TESTTMP/gitrepo (glob)
* [new branch] foo -> origin/foo * [new branch] foo -> origin/foo
* [new branch] master -> origin/master
2 files updated, 0 files merged, 0 files removed, 0 files unresolved 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg --cwd cloned2 log -r . -T '{node|short} {remotenames} {desc}\n' $ hg --cwd cloned2 log -r . -T '{node|short} {remotenames} {desc}\n'
5c9a5ee451a8 origin/foo alpha3 5c9a5ee451a8 origin/foo alpha3