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 "os"
22 "os/exec"
23 "path/filepath"
24 "sort"
25
26 git "github.com/go-git/go-git/v5"
27 "github.com/go-git/go-git/v5/config"
28)
29
30// Updates the zoekt.* git config options after a repo is cloned.
31// Once a repo is cloned, we can no longer use the --config flag to update all
32// of it's zoekt.* settings at once. `git config` is limited to one option at once.
33func updateZoektGitConfig(repoDest string, settings map[string]string) error {
34 var keys []string
35 for k := range settings {
36 keys = append(keys, k)
37 }
38 sort.Strings(keys)
39
40 for _, k := range keys {
41 if settings[k] != "" {
42 if err := exec.Command("git", "-C", repoDest, "config", k, settings[k]).Run(); err != nil {
43 return err
44 }
45 }
46 }
47
48 return nil
49}
50
51// CloneRepo clones one repository, adding the given config
52// settings. It returns the bare repo directory. The `name` argument
53// determines where the repo is stored relative to `destDir`. Returns
54// the directory of the repository.
55func CloneRepo(destDir, name, cloneURL string, settings map[string]string) (string, error) {
56 parent := filepath.Join(destDir, filepath.Dir(name))
57 if err := os.MkdirAll(parent, 0o755); err != nil {
58 return "", err
59 }
60
61 repoDest := filepath.Join(parent, filepath.Base(name)+".git")
62 if _, err := os.Lstat(repoDest); err == nil {
63 // Repository exists, ensure settings are in sync
64 if err := updateZoektGitConfig(repoDest, settings); err != nil {
65 return "", fmt.Errorf("failed to update repository settings: %w", err)
66 }
67 return "", nil
68 }
69
70 var keys []string
71 for k := range settings {
72 keys = append(keys, k)
73 }
74 sort.Strings(keys)
75
76 var config []string
77 for _, k := range keys {
78 if settings[k] != "" {
79 config = append(config, "--config", k+"="+settings[k])
80 }
81 }
82
83 cmd := exec.Command(
84 "git", "clone", "--bare", "--verbose", "--progress",
85 )
86 cmd.Args = append(cmd.Args, config...)
87 cmd.Args = append(cmd.Args, cloneURL, repoDest)
88
89 // Prevent prompting
90 cmd.Stdin = &bytes.Buffer{}
91 log.Println("running:", cmd.Args)
92 if err := cmd.Run(); err != nil {
93 return "", err
94 }
95
96 if err := setFetch(repoDest, "origin", "+refs/heads/*:refs/heads/*"); err != nil {
97 log.Printf("addFetch: %v", err)
98 }
99 return repoDest, nil
100}
101
102func setFetch(repoDir, remote, refspec string) error {
103 repo, err := git.PlainOpen(repoDir)
104 if err != nil {
105 return err
106 }
107
108 cfg, err := repo.Config()
109 if err != nil {
110 return err
111 }
112
113 rm := cfg.Remotes[remote]
114 if rm != nil {
115 rm.Fetch = []config.RefSpec{config.RefSpec(refspec)}
116 }
117 if err := repo.Storer.SetConfig(cfg); err != nil {
118 return err
119 }
120
121 return nil
122}