This repository has no description
4.0 kB
159 lines
1package main
2
3import (
4 "context"
5 "crypto/sha1"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/url"
10 "os"
11 "path/filepath"
12
13 "github.com/bluesky-social/indigo/atproto/identity"
14 "github.com/bluesky-social/indigo/atproto/syntax"
15 "github.com/sourcegraph/zoekt"
16 "tangled.org/core/repoident"
17 "tangled.org/core/repoverify"
18)
19
20// 1 MB; match https://sourcegraph.sourcegraph.com/r/github.com/sourcegraph/sourcegraph/-/blob/cmd/searcher/internal/search/store.go?L32
21const MaxFileSize = 1 << 20
22
23func gitIndex(ctx context.Context, cfg *Config, dir identity.Directory, req indexRequest) error {
24 ctx, cancel := context.WithTimeout(ctx, cfg.IndexTimeout)
25 defer cancel()
26
27 repo, err := loadRepo(ctx, cfg, dir, req.Repo)
28 if err != nil {
29 return fmt.Errorf("loading repo %s: %w", req.Repo, err)
30 }
31 repo.Branches = req.Branches
32
33 gitDir, err := tmpGitDir(repo.Did.String())
34 if err != nil {
35 return err
36 }
37 defer os.RemoveAll(gitDir) // best-effort cleanup
38
39 if err := fetchRepo(ctx, gitDir, repo.CloneURL(), req.Branches); err != nil {
40 return err
41 }
42
43 if err := indexRepo(ctx, cfg, gitDir, *repo); err != nil {
44 return err
45 }
46
47 return nil
48}
49
50func loadRepo(ctx context.Context, cfg *Config, dir identity.Directory, repoDID repoident.RepoDid) (*Repo, error) {
51 ident, err := dir.LookupDID(ctx, syntax.DID(repoDID))
52 if err != nil {
53 return nil, err
54 }
55
56 knot, err := repoident.KnotURLFromIdentity(ident, cfg.KnotScheme)
57 if err != nil {
58 return nil, fmt.Errorf("repoDid %s: %w", repoDID, err)
59 }
60
61 described, err := repoverify.Describe(ctx, nil, knot, repoDID)
62 if err != nil {
63 return nil, err
64 }
65
66 return &Repo{
67 Did: repoDID,
68 Owner: described.OwnerDid,
69 Slug: described.Rkey,
70 Knot: knot,
71 }, nil
72}
73
74func fetchRepo(ctx context.Context, gitDir, cloneUrl string, branches []zoekt.RepositoryBranch) error {
75 // Create a repo to fetch into
76 if err := executeCmd(ctx,
77 "git",
78 // use a random default branch. This is so that HEAD isn't a symref to a
79 // branch that is indexed. For example if you are indexing
80 // HEAD,master. Then HEAD would be pointing to master by default.
81 "-c", "init.defaultBranch=nonExistentBranchBB0FOFCH32",
82 "init",
83 // we don't need a working copy
84 "--bare",
85 gitDir,
86 ); err != nil {
87 return err
88 }
89
90 fetchArgs := []string{
91 "-C", gitDir,
92 "-c", "protocol.version=2",
93 "fetch", "--depth=1", "--no-tags",
94 }
95 // Git's blob:limit filter excludes blobs whose size is >= the given limit,
96 // while zoekt indexes files up to and including FileLimit bytes.
97 fetchArgs = append(fetchArgs, fmt.Sprintf("--filter=blob:limit=%d", int64(MaxFileSize)+1))
98
99 fetchArgs = append(fetchArgs, cloneUrl)
100
101 var commits []string
102 for _, b := range branches {
103 commits = append(commits, b.Version)
104 }
105 fetchArgs = append(fetchArgs, commits...)
106
107 if err := executeCmd(ctx, "git", fetchArgs...); err != nil {
108 return err
109 }
110
111 for _, b := range branches {
112 ref := b.Name
113 if ref != "HEAD" {
114 ref = "refs/heads/" + ref
115 }
116 if err := executeCmd(ctx, "git", "-C", gitDir, "update-ref", ref, b.Version); err != nil {
117 return fmt.Errorf("failed update-ref %s to %s: %w", ref, b.Version, err)
118 }
119 }
120
121 return nil
122}
123
124func indexRepo(ctx context.Context, cfg *Config, gitDir string, repo Repo) error {
125 executablePath, err := os.Executable()
126 if err != nil {
127 return err
128 }
129
130 repoJson, err := json.Marshal(repo)
131 if err != nil {
132 return err
133 }
134
135 args := []string{"index"}
136 args = append(args, "-index-dir", cfg.IndexDir)
137 args = append(args, "-appview-url", cfg.AppviewUrl)
138 args = append(args, gitDir, string(repoJson))
139 if err := executeCmd(ctx, executablePath, args...); err != nil {
140 return err
141 }
142 return nil
143}
144
145func tmpGitDir(name string) (string, error) {
146 abs := url.QueryEscape(name)
147 if len(abs) > 200 {
148 h := sha1.New()
149 _, _ = io.WriteString(h, abs)
150 abs = abs[:200] + fmt.Sprintf("%x", h.Sum(nil))[:8]
151 }
152 dir := filepath.Join(os.TempDir(), abs+".git")
153 if _, err := os.Stat(dir); err == nil {
154 if err := os.RemoveAll(dir); err != nil {
155 return "", err
156 }
157 }
158 return dir, nil
159}