fork of https://github.com/sourcegraph/zoekt
1// Copyright 2016 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package gitindex
16
17import (
18 "bytes"
19 "fmt"
20 "log"
21 "maps"
22 "os"
23 "os/exec"
24 "path/filepath"
25 "sort"
26
27 git "github.com/go-git/go-git/v5"
28 "github.com/go-git/go-git/v5/config"
29)
30
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.
34func 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// CloneRepo clones one repository, adding the given config
53// settings. It returns the bare repo directory. The `name` argument
54// determines where the repo is stored relative to `destDir`. Returns
55// the directory of the repository.
56func CloneRepo(destDir, name, cloneURL string, settings map[string]string) (string, error) {
57 parent := filepath.Join(destDir, filepath.Dir(name))
58 if err := os.MkdirAll(parent, 0o755); err != nil {
59 return "", err
60 }
61
62 repoDest := filepath.Join(parent, filepath.Base(name)+".git")
63 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 {
68 return "", fmt.Errorf("failed to update repository settings: %w", err)
69 }
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])
83 }
84 }
85
86 cmd := exec.Command(
87 "git", "clone", "--bare", "--verbose", "--progress",
88 )
89 cmd.Args = append(cmd.Args, config...)
90 cmd.Args = append(cmd.Args, cloneURL, repoDest)
91
92 // Prevent prompting
93 cmd.Stdin = &bytes.Buffer{}
94 log.Println("running:", cmd.Args)
95 if err := cmd.Run(); err != nil {
96 return "", err
97 }
98
99 if err := setFetch(repoDest, "origin", "+refs/heads/*:refs/heads/*"); err != nil {
100 log.Printf("addFetch: %v", err)
101 }
102 return repoDest, nil
103}
104
105func setFetch(repoDir, remote, refspec string) error {
106 repo, err := git.PlainOpen(repoDir)
107 if err != nil {
108 return err
109 }
110
111 cfg, err := repo.Config()
112 if err != nil {
113 return err
114 }
115
116 rm := cfg.Remotes[remote]
117 if rm != nil {
118 rm.Fetch = []config.RefSpec{config.RefSpec(refspec)}
119 }
120 if err := repo.Storer.SetConfig(cfg); err != nil {
121 return err
122 }
123
124 return nil
125}