diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index f1d7507..fbcedf7 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -17,7 +17,7 @@ jobs: go-version: "1.21" - name: Install git. run: | - sudo apt-get install -y libcurl4-openssl-dev + sudo apt-get install -y libcurl4-openssl-dev cargo git clone https://github.com/git/git.git git -C git checkout next sudo make -j 16 -C git prefix=/usr NO_GETTEXT=YesPlease all install diff --git a/internal/spokes/spokes.go b/internal/spokes/spokes.go index c3ccc55..8768d84 100644 --- a/internal/spokes/spokes.go +++ b/internal/spokes/spokes.go @@ -1078,18 +1078,16 @@ func (r *spokesReceivePack) performCheckConnectivity(ctx context.Context, comman _ = devNull.Close() }() - cmd := exec.CommandContext( - ctx, - "git", + args := []string{ "rev-list", "--objects", "--no-object-names", "--stdin", "--not", - "--exclude-hidden=receive", - "--all", - "--alternate-refs", - ) + } + args = append(args, r.connectivityStopperArgs(ctx)...) + + cmd := exec.CommandContext(ctx, "git", args...) cmd.Stderr = devNull cmd.Env = os.Environ() cmd.Env = append(cmd.Env, r.getAlternateObjectDirsEnv()...) @@ -1124,6 +1122,34 @@ func (r *spokesReceivePack) performCheckConnectivity(ctx context.Context, comman return nil } +// connectivityStopperArgs returns the rev-list arguments that mark the +// pre-existing object graph as uninteresting for the connectivity check. +func (r *spokesReceivePack) connectivityStopperArgs(ctx context.Context) []string { + if !sockstat.GetBool("spokes_receive_pack_narrow_connectivity_stopper") { + return []string{"--exclude-hidden=receive", "--all", "--alternate-refs"} + } + + args := []string{"--exclude=*/*", "--branches"} + if r.headResolves(ctx) { + args = append(args, "HEAD") + } + return args +} + +// headResolves reports whether `git rev-parse --verify --quiet HEAD` +// succeeds in the current directory. +func (r *spokesReceivePack) headResolves(ctx context.Context) bool { + cmd := exec.CommandContext(ctx, "git", "rev-parse", "--verify", "--quiet", "HEAD") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, r.getAlternateObjectDirsEnv()...) + // Discard stdout (the resolved OID on success) and stderr so we + // neither corrupt the receive-pack protocol stream on stdout nor + // leak diagnostics on stderr. + cmd.Stdout = io.Discard + cmd.Stderr = io.Discard + return cmd.Run() == nil +} + func commandsForConnectivityCheck(commands []command) []command { var res []command for _, c := range commands { diff --git a/internal/spokes/spokes_test.go b/internal/spokes/spokes_test.go index ea1e161..51995ed 100644 --- a/internal/spokes/spokes_test.go +++ b/internal/spokes/spokes_test.go @@ -5,9 +5,12 @@ import ( "context" "fmt" "os" + "os/exec" + "path/filepath" "testing" "github.com/github/spokes-receive-pack/internal/config" + "github.com/github/spokes-receive-pack/internal/sockstat" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -271,3 +274,50 @@ func TestPerformReferenceDiscovery(t *testing.T) { assert.NoError(t, r.performReferenceDiscovery(context.Background())) assert.Equal(t, expectedReferenceList, buf.String()) } + +func TestConnectivityStopperArgs(t *testing.T) { + origwd, err := os.Getwd() + require.NoError(t, err) + // A valid quarantine directory is required so that + // getAlternateObjectDirsEnv produces well-formed environment + // variables (an empty GIT_OBJECT_DIRECTORY would make git refuse + // to operate). + quarantine := t.TempDir() + + t.Run("flag off uses legacy stoppers", func(t *testing.T) { + t.Setenv(sockstat.Prefix+"spokes_receive_pack_narrow_connectivity_stopper", "") + // No filesystem operations are required: the legacy path + // never invokes git. + r := &spokesReceivePack{} + got := r.connectivityStopperArgs(context.Background()) + assert.Equal(t, []string{"--exclude-hidden=receive", "--all", "--alternate-refs"}, got) + }) + + t.Run("flag on uses --exclude=*/* --branches HEAD when HEAD resolves", func(t *testing.T) { + t.Setenv(sockstat.Prefix+"spokes_receive_pack_narrow_connectivity_stopper", "bool:true") + require.NoError(t, os.Chdir("testdata/lots-of-refs.git")) + t.Cleanup(func() { _ = os.Chdir(origwd) }) + wd, _ := os.Getwd() + r := &spokesReceivePack{repoPath: wd, quarantineFolder: quarantine} + got := r.connectivityStopperArgs(context.Background()) + assert.Equal(t, []string{"--exclude=*/*", "--branches", "HEAD"}, got) + }) + + t.Run("flag on omits HEAD when HEAD does not resolve", func(t *testing.T) { + t.Setenv(sockstat.Prefix+"spokes_receive_pack_narrow_connectivity_stopper", "bool:true") + // A fresh repo with no commits has HEAD pointing at an + // unborn branch, so rev-parse --verify HEAD fails. Use a + // non-bare repo to side-step any safe.bareRepository + // configuration the caller may have set. + dir := t.TempDir() + require.NoError(t, exec.Command("git", "init", dir).Run()) + require.NoError(t, os.Chdir(dir)) + t.Cleanup(func() { _ = os.Chdir(origwd) }) + r := &spokesReceivePack{ + repoPath: filepath.Join(dir, ".git"), + quarantineFolder: quarantine, + } + got := r.connectivityStopperArgs(context.Background()) + assert.Equal(t, []string{"--exclude=*/*", "--branches"}, got) + }) +}