Skip to content

Commit c3ad93d

Browse files
committed
gitindex: diff config updates for existing clones
This supersedes #635 by porting its selective config-sync idea onto current main with a smaller, easier-to-read shape. Clone orchestration stays in gitindex/clone.go, while config argument generation and existing-clone sync logic now live in gitindex/clone_config.go. For existing clones, we now diff each zoekt.* setting before writing. Unchanged values are skipped, changed values are updated, and settings that disappear are removed. CloneRepo returns the repo destination only when a setting change actually happened so the caller can trigger reindexing only when needed. cmd/zoekt-mirror-github was also cleaned up so optional integer metadata keys are only added to the config map when present, which avoids pushing empty config values downstream. Note: On #635 review it mentioned using go-git. This commit initially explored that but it ended up being a _lot_ more code due to missing utilities around easily setting values based on a git config string.
1 parent ed735fa commit c3ad93d

3 files changed

Lines changed: 108 additions & 50 deletions

File tree

cmd/zoekt-mirror-github/main.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,10 @@ func getUserRepos(client *github.Client, user string, reposFilters reposFilters)
292292
return allRepos, nil
293293
}
294294

295-
func itoa(p *int) string {
296-
if p != nil {
297-
return strconv.Itoa(*p)
295+
func setOptionalIntConfig(config map[string]string, key string, value *int) {
296+
if value != nil {
297+
config[key] = strconv.Itoa(*value)
298298
}
299-
return ""
300299
}
301300

302301
func cloneRepos(destDir string, repos []*github.Repository) error {
@@ -311,15 +310,15 @@ func cloneRepos(destDir string, repos []*github.Repository) error {
311310
"zoekt.web-url": *r.HTMLURL,
312311
"zoekt.name": filepath.Join(host.Hostname(), *r.FullName),
313312

314-
"zoekt.github-stars": itoa(r.StargazersCount),
315-
"zoekt.github-watchers": itoa(r.WatchersCount),
316-
"zoekt.github-subscribers": itoa(r.SubscribersCount),
317-
"zoekt.github-forks": itoa(r.ForksCount),
318-
319313
"zoekt.archived": marshalBool(r.Archived != nil && *r.Archived),
320314
"zoekt.fork": marshalBool(r.Fork != nil && *r.Fork),
321315
"zoekt.public": marshalBool(r.Private == nil || !*r.Private),
322316
}
317+
setOptionalIntConfig(config, "zoekt.github-stars", r.StargazersCount)
318+
setOptionalIntConfig(config, "zoekt.github-watchers", r.WatchersCount)
319+
setOptionalIntConfig(config, "zoekt.github-subscribers", r.SubscribersCount)
320+
setOptionalIntConfig(config, "zoekt.github-forks", r.ForksCount)
321+
323322
dest, err := gitindex.CloneRepo(destDir, *r.FullName, *r.CloneURL, config)
324323
if err != nil {
325324
return err

gitindex/clone.go

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,37 +18,14 @@ import (
1818
"bytes"
1919
"fmt"
2020
"log"
21-
"maps"
2221
"os"
2322
"os/exec"
2423
"path/filepath"
25-
"sort"
2624

2725
git "github.com/go-git/go-git/v5"
2826
"github.com/go-git/go-git/v5/config"
2927
)
3028

31-
// Updates the zoekt.* git config options after a repo is cloned.
32-
// Once a repo is cloned, we can no longer use the --config flag to update all
33-
// of it's zoekt.* settings at once. `git config` is limited to one option at once.
34-
func updateZoektGitConfig(repoDest string, settings map[string]string) error {
35-
var keys []string
36-
for k := range settings {
37-
keys = append(keys, k)
38-
}
39-
sort.Strings(keys)
40-
41-
for _, k := range keys {
42-
if settings[k] != "" {
43-
if err := exec.Command("git", "-C", repoDest, "config", k, settings[k]).Run(); err != nil {
44-
return err
45-
}
46-
}
47-
}
48-
49-
return nil
50-
}
51-
5229
// CloneRepo clones one repository, adding the given config
5330
// settings. It returns the bare repo directory. The `name` argument
5431
// determines where the repo is stored relative to `destDir`. Returns
@@ -61,32 +38,21 @@ func CloneRepo(destDir, name, cloneURL string, settings map[string]string) (stri
6138

6239
repoDest := filepath.Join(parent, filepath.Base(name)+".git")
6340
if _, err := os.Lstat(repoDest); err == nil {
64-
// Repository exists, ensure settings are in sync including the clone URL
65-
settings := maps.Clone(settings)
66-
settings["remote.origin.url"] = cloneURL
67-
if err := updateZoektGitConfig(repoDest, settings); err != nil {
41+
// Repository exists, ensure zoekt settings are in sync.
42+
hadUpdate, err := updateZoektGitConfig(repoDest, settings)
43+
if err != nil {
6844
return "", fmt.Errorf("failed to update repository settings: %w", err)
6945
}
70-
return "", nil
71-
}
72-
73-
var keys []string
74-
for k := range settings {
75-
keys = append(keys, k)
76-
}
77-
sort.Strings(keys)
78-
79-
var config []string
80-
for _, k := range keys {
81-
if settings[k] != "" {
82-
config = append(config, "--config", k+"="+settings[k])
46+
if hadUpdate {
47+
return repoDest, nil
8348
}
49+
return "", nil
8450
}
8551

8652
cmd := exec.Command(
8753
"git", "clone", "--bare", "--verbose", "--progress",
8854
)
89-
cmd.Args = append(cmd.Args, config...)
55+
cmd.Args = append(cmd.Args, cloneConfigArgs(settings)...)
9056
cmd.Args = append(cmd.Args, cloneURL, repoDest)
9157

9258
// Prevent prompting

gitindex/clone_config.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package gitindex
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"maps"
8+
"os/exec"
9+
"slices"
10+
"strings"
11+
)
12+
13+
func sortedKeys(settings map[string]string) []string {
14+
return slices.Sorted(maps.Keys(settings))
15+
}
16+
17+
func cloneConfigArgs(settings map[string]string) []string {
18+
args := make([]string, 0, len(settings)*2)
19+
for _, key := range sortedKeys(settings) {
20+
if value := settings[key]; value != "" {
21+
args = append(args, "--config", key+"="+value)
22+
}
23+
}
24+
return args
25+
}
26+
27+
// updateZoektGitConfig applies zoekt.* settings to an existing clone.
28+
// It returns whether the repository config changed.
29+
func updateZoektGitConfig(repoDest string, settings map[string]string) (bool, error) {
30+
changed := false
31+
for _, key := range sortedKeys(settings) {
32+
updated, err := syncGitConfigOption(repoDest, key, settings[key])
33+
if err != nil {
34+
return false, err
35+
}
36+
changed = changed || updated
37+
}
38+
return changed, nil
39+
}
40+
41+
func syncGitConfigOption(repoDest, key, value string) (bool, error) {
42+
current, ok, err := repoConfigValue(repoDest, key)
43+
if err != nil {
44+
return false, err
45+
}
46+
47+
if value == "" {
48+
if !ok {
49+
return false, nil
50+
}
51+
if err := unsetRepoConfigValue(repoDest, key); err != nil {
52+
return false, err
53+
}
54+
return true, nil
55+
}
56+
57+
if ok && current == value {
58+
return false, nil
59+
}
60+
if err := setRepoConfigValue(repoDest, key, value); err != nil {
61+
return false, err
62+
}
63+
return true, nil
64+
}
65+
66+
func repoConfigValue(repoDest, key string) (string, bool, error) {
67+
cmd := exec.Command("git", "-C", repoDest, "config", "--get", key)
68+
var out bytes.Buffer
69+
cmd.Stdout = &out
70+
if err := cmd.Run(); err == nil {
71+
return strings.TrimSuffix(out.String(), "\n"), true, nil
72+
} else {
73+
var exitErr *exec.ExitError
74+
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
75+
return "", false, nil
76+
}
77+
return "", false, fmt.Errorf("git config --get %q: %w", key, err)
78+
}
79+
}
80+
81+
func setRepoConfigValue(repoDest, key, value string) error {
82+
if err := exec.Command("git", "-C", repoDest, "config", "--replace-all", key, value).Run(); err != nil {
83+
return fmt.Errorf("git config --replace-all %q: %w", key, err)
84+
}
85+
return nil
86+
}
87+
88+
func unsetRepoConfigValue(repoDest, key string) error {
89+
if err := exec.Command("git", "-C", repoDest, "config", "--unset-all", key).Run(); err != nil {
90+
return fmt.Errorf("git config --unset-all %q: %w", key, err)
91+
}
92+
return nil
93+
}

0 commit comments

Comments
 (0)