fork of https://github.com/sourcegraph/zoekt
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8
9 "github.com/sourcegraph/zoekt"
10 "github.com/sourcegraph/zoekt/build"
11)
12
13// mergeMeta updates the .meta files for the shards on disk for o.
14//
15// This process is best effort. If anything fails we return on the first
16// failure. This means you might have an inconsistent state on disk if an
17// error is returned. It is recommended to fallback to re-indexing in that
18// case.
19func mergeMeta(o *build.Options) error {
20 todo := map[string]string{}
21 for _, fn := range o.FindAllShards() {
22 repos, md, err := zoekt.ReadMetadataPath(fn)
23 if err != nil {
24 return err
25 }
26
27 var repo *zoekt.Repository
28 for _, cand := range repos {
29 if cand.Name == o.RepositoryDescription.Name {
30 repo = cand
31 break
32 }
33 }
34
35 if repo == nil {
36 return fmt.Errorf("mergeMeta: could not find repo %s in shard %s", o.RepositoryDescription.Name, fn)
37 }
38
39 if updated, err := repo.MergeMutable(&o.RepositoryDescription); err != nil {
40 return err
41 } else if !updated {
42 // This shouldn't happen, but ignore it if it does. We may be working on
43 // an interrupted shard. This helps us converge to something correct.
44 continue
45 }
46
47 var merged interface{}
48 if md.IndexFormatVersion >= 17 {
49 merged = repos
50 } else {
51 // <= v16 expects a single repo, not a list.
52 merged = repo
53 }
54
55 dst := fn + ".meta"
56 tmp, err := jsonMarshalTmpFile(merged, dst)
57 if err != nil {
58 return err
59 }
60
61 todo[tmp] = dst
62
63 // if we fail to rename, this defer will attempt to remove the tmp file.
64 defer os.Remove(tmp)
65 }
66
67 // best effort once we get here. Rename everything. Return error of last
68 // failure.
69 var renameErr error
70 for tmp, dst := range todo {
71 if err := os.Rename(tmp, dst); err != nil {
72 renameErr = err
73 }
74 }
75
76 return renameErr
77}
78
79// jsonMarshalFileTmp marshals v to the temporary file p + ".*.tmp" and
80// returns the file name.
81//
82// Note: .tmp is the same suffix used by Builder. indexserver knows to clean
83// them up.
84func jsonMarshalTmpFile(v interface{}, p string) (_ string, err error) {
85 b, err := json.Marshal(v)
86 if err != nil {
87 return "", err
88 }
89
90 f, err := os.CreateTemp(filepath.Dir(p), filepath.Base(p)+".*.tmp")
91 if err != nil {
92 return "", err
93 }
94 defer func() {
95 f.Close()
96 if err != nil {
97 _ = os.Remove(f.Name())
98 }
99 }()
100
101 if err := f.Chmod(0o666 &^ umask); err != nil {
102 return "", err
103 }
104 if _, err := f.Write(b); err != nil {
105 return "", err
106 }
107
108 return f.Name(), f.Close()
109}
110
111// respect process umask. build does this.
112var umask os.FileMode