fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

gitindex: diff config updates for existing clones (#1019)

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.

+108 -50
+8 -9
cmd/zoekt-mirror-github/main.go
··· 292 292 return allRepos, nil 293 293 } 294 294 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) 298 298 } 299 - return "" 300 299 } 301 300 302 301 func cloneRepos(destDir string, repos []*github.Repository) error { ··· 311 310 "zoekt.web-url": *r.HTMLURL, 312 311 "zoekt.name": filepath.Join(host.Hostname(), *r.FullName), 313 312 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 - 319 313 "zoekt.archived": marshalBool(r.Archived != nil && *r.Archived), 320 314 "zoekt.fork": marshalBool(r.Fork != nil && *r.Fork), 321 315 "zoekt.public": marshalBool(r.Private == nil || !*r.Private), 322 316 } 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 + 323 322 dest, err := gitindex.CloneRepo(destDir, *r.FullName, *r.CloneURL, config) 324 323 if err != nil { 325 324 return err
+7 -41
gitindex/clone.go
··· 18 18 "bytes" 19 19 "fmt" 20 20 "log" 21 - "maps" 22 21 "os" 23 22 "os/exec" 24 23 "path/filepath" 25 - "sort" 26 24 27 25 git "github.com/go-git/go-git/v5" 28 26 "github.com/go-git/go-git/v5/config" 29 27 ) 30 28 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 - 52 29 // CloneRepo clones one repository, adding the given config 53 30 // settings. It returns the bare repo directory. The `name` argument 54 31 // determines where the repo is stored relative to `destDir`. Returns ··· 61 38 62 39 repoDest := filepath.Join(parent, filepath.Base(name)+".git") 63 40 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 { 68 44 return "", fmt.Errorf("failed to update repository settings: %w", err) 69 45 } 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 83 48 } 49 + return "", nil 84 50 } 85 51 86 52 cmd := exec.Command( 87 53 "git", "clone", "--bare", "--verbose", "--progress", 88 54 ) 89 - cmd.Args = append(cmd.Args, config...) 55 + cmd.Args = append(cmd.Args, cloneConfigArgs(settings)...) 90 56 cmd.Args = append(cmd.Args, cloneURL, repoDest) 91 57 92 58 // Prevent prompting
+93
gitindex/clone_config.go
··· 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 + }