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

Configure Feed

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

ranking: add document ranks to shards (#449)

We persist document ranks in the shards and sort file matches based on the rankings determined by the document ranks and match scores.

Co-authored-by: Keegan Carruthers-Smith <keegan.csmith@gmail.com>

+364 -50
+8
api.go
··· 36 36 // Ranking; the higher, the better. 37 37 Score float64 // TODO - hide this field? 38 38 39 + // Experimental. Ranks is a vector containing floats in the interval [0, 1]. The 40 + // length of the vector depends on the output from the ranking function at index 41 + // time. 42 + Ranks []float64 43 + 39 44 // For debugging. Needs DebugScore set, but public so tests in 40 45 // other packages can print some diagnostics. 41 46 Debug string ··· 84 89 func (m *FileMatch) sizeBytes() (sz uint64) { 85 90 // Score 86 91 sz += 8 92 + 93 + // ranks 94 + sz += 8 * uint64(len(m.Ranks)) 87 95 88 96 for _, s := range []string{ 89 97 m.Debug,
+27 -15
cmd/zoekt-sourcegraph-indexserver/index.go
··· 84 84 // only be true for repositories we explicitly enable. 85 85 UseDelta bool 86 86 87 - UseOfflineRanking bool 88 - 89 87 // DeltaShardNumberFallbackThreshold is an upper limit on the number of preexisting shards that can exist 90 88 // before attempting a delta build. 91 89 DeltaShardNumberFallbackThreshold uint64 ··· 324 322 "-submodules=false", 325 323 } 326 324 327 - // We store the document ranks as JSON in gitDir and tell zoekt-git-index where 328 - // to find the file. 329 - if o.UseOfflineRanking { 325 + if rankingEnabled { 326 + // We store the document ranks as JSON in gitDir and tell zoekt-git-index where 327 + // to find the file. 330 328 documentsRankFile := filepath.Join(gitDir, "documents.rank") 331 329 332 - args = append(args, "-offline_ranking", documentsRankFile) 330 + saveDocumentRanks := func() error { 331 + r, err := sourcegraph.GetDocumentRanks(context.Background(), o.Name) 332 + if err != nil { 333 + return fmt.Errorf("GetDocumentRanks: %w", err) 334 + } 333 335 334 - r, err := sourcegraph.GetDocumentRanks(context.Background(), o.Name) 335 - if err != nil { 336 - return fmt.Errorf("GetDocumentRanks: %w", err) 337 - } 336 + b, err := json.Marshal(r) 337 + if err != nil { 338 + return err 339 + } 338 340 339 - b, err := json.Marshal(r) 340 - if err != nil { 341 - return err 341 + if err := os.WriteFile(documentsRankFile, b, 0600); err != nil { 342 + return fmt.Errorf("failed to write %s to disk: %w", documentsRankFile, err) 343 + } 344 + 345 + return nil 342 346 } 343 347 344 - if err := os.WriteFile(documentsRankFile, b, 0600); err != nil { 345 - return fmt.Errorf("failed to write %s to disk: %w", documentsRankFile, err) 348 + if err := saveDocumentRanks(); err != nil { 349 + // log and fall back to online ranking 350 + logger.Warn( 351 + "error saving document ranks. Falling back to online ranking", 352 + sglog.Error(err), 353 + sglog.String("repo", o.Name), 354 + sglog.Uint32("id", o.RepoID), 355 + ) 356 + } else { 357 + args = append(args, "-offline_ranking", documentsRankFile) 346 358 } 347 359 } 348 360
+4 -14
cmd/zoekt-sourcegraph-indexserver/main.go
··· 33 33 "github.com/prometheus/client_golang/prometheus" 34 34 "github.com/prometheus/client_golang/prometheus/promauto" 35 35 sglog "github.com/sourcegraph/log" 36 - "github.com/sourcegraph/zoekt/internal/mountinfo" 37 36 "go.uber.org/automaxprocs/maxprocs" 38 37 "golang.org/x/net/trace" 38 + 39 + "github.com/sourcegraph/zoekt/internal/mountinfo" 39 40 40 41 "github.com/sourcegraph/zoekt" 41 42 "github.com/sourcegraph/zoekt/build" ··· 187 188 // repositoriesSkipSymbolsCalculationAllowList is an allowlist for repositories that 188 189 // we skip calculating symbols metadata for during builds 189 190 repositoriesSkipSymbolsCalculationAllowList map[string]struct{} 190 - 191 - offlineRankingAllowList map[string]struct{} 192 191 } 193 192 194 193 var debug = log.New(io.Discard, "", log.LstdFlags) ··· 513 512 return ticker 514 513 } 515 514 515 + var rankingEnabled, _ = strconv.ParseBool(os.Getenv("ENABLE_EXPERIMENTAL_RANKING")) 516 + 516 517 // Index starts an index job for repo name at commit. 517 518 func (s *Server) Index(args *indexArgs) (state indexState, err error) { 518 519 tr := trace.New("index", args.Name) ··· 538 539 if _, ok := s.deltaBuildRepositoriesAllowList[repositoryName]; ok { 539 540 tr.LazyPrintf("marking this repository for delta build") 540 541 args.UseDelta = true 541 - } 542 - 543 - if _, ok := s.offlineRankingAllowList[repositoryName]; ok { 544 - tr.LazyPrintf("marking this repository for offline ranking") 545 - args.UseOfflineRanking = true 546 542 } 547 543 548 544 args.DeltaShardNumberFallbackThreshold = s.deltaShardNumberFallbackThreshold ··· 1131 1127 debug.Printf("using delta shard builds for: %s", joinStringSet(deltaBuildRepositoriesAllowList, ", ")) 1132 1128 } 1133 1129 1134 - offlineRankingAllowList := getEnvWithDefaultEmptySet("OFFLINE_RANKING_REPOS_ALLOWLIST") 1135 - if len(offlineRankingAllowList) > 0 { 1136 - debug.Printf("using offline ranking for: %s", joinStringSet(offlineRankingAllowList, ", ")) 1137 - } 1138 - 1139 1130 deltaShardNumberFallbackThreshold := getEnvWithDefaultUint64("DELTA_SHARD_NUMBER_FALLBACK_THRESHOLD", 150) 1140 1131 if deltaShardNumberFallbackThreshold > 0 { 1141 1132 debug.Printf("setting delta shard fallback threshold to %d shard(s)", deltaShardNumberFallbackThreshold) ··· 1192 1183 deltaBuildRepositoriesAllowList: deltaBuildRepositoriesAllowList, 1193 1184 deltaShardNumberFallbackThreshold: deltaShardNumberFallbackThreshold, 1194 1185 repositoriesSkipSymbolsCalculationAllowList: reposShouldSkipSymbolsCalculation, 1195 - offlineRankingAllowList: offlineRankingAllowList, 1196 1186 }, err 1197 1187 } 1198 1188
+91 -7
contentprovider.go
··· 18 18 "bytes" 19 19 "fmt" 20 20 "log" 21 + "math" 21 22 "sort" 22 23 "strings" 23 24 "unicode/utf8" ··· 697 698 func (m chunkMatchScoreSlice) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 698 699 func (m chunkMatchScoreSlice) Less(i, j int) bool { return m[i].Score > m[j].Score } 699 700 700 - type fileMatchSlice []FileMatch 701 + type fileMatchesByScore []FileMatch 701 702 702 - func (m fileMatchSlice) Len() int { return len(m) } 703 - func (m fileMatchSlice) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 704 - func (m fileMatchSlice) Less(i, j int) bool { return m[i].Score > m[j].Score } 703 + func (m fileMatchesByScore) Len() int { return len(m) } 704 + func (m fileMatchesByScore) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 705 + func (m fileMatchesByScore) Less(i, j int) bool { return m[i].Score > m[j].Score } 705 706 706 707 func sortMatchesByScore(ms []LineMatch) { 707 708 sort.Sort(matchScoreSlice(ms)) ··· 711 712 sort.Sort(chunkMatchScoreSlice(ms)) 712 713 } 713 714 714 - // Sort a slice of results. 715 - func SortFilesByScore(ms []FileMatch) { 716 - sort.Sort(fileMatchSlice(ms)) 715 + // k = 60 is arbitrary but reportedly works well (RFF; Cormack et al., 2009). 716 + const k = 60 717 + 718 + // SortFiles sorts files matches. The order depends on the match score and, if 719 + // available, on the pre-computed document ranks. 720 + // 721 + // Rankings derived from match scores and rank vectors are combined based on 722 + // "Reciprocal Rank Fusion" (RFF). 723 + func SortFiles(ms []FileMatch) { 724 + sort.Sort(fileMatchesByScore(ms)) 725 + 726 + if hasRanks(ms) { 727 + rffScore := make([]float64, len(ms)) 728 + 729 + for i := 0; i < len(ms); i++ { 730 + rffScore[i] = 1 / (k + float64(i)) 731 + } 732 + 733 + sort.Sort(fileMatchesByRank{fileMatches: ms, rffScore: rffScore}) 734 + 735 + for i := range rffScore { 736 + rffScore[i] += 1 / (k + float64(i)) 737 + } 738 + 739 + sort.Sort(fileMatchesByRFFScore{fileMatches: ms, rffScore: rffScore}) 740 + } 741 + } 742 + 743 + // hasRanks returns true if any sr.Files has a non-zero rank vector 744 + func hasRanks(fm []FileMatch) bool { 745 + for _, f := range fm { 746 + if len(f.Ranks) > 0 { 747 + return true 748 + } 749 + } 750 + 751 + return false 752 + } 753 + 754 + type fileMatchesByRank struct { 755 + fileMatches []FileMatch 756 + rffScore []float64 757 + } 758 + 759 + func (m fileMatchesByRank) Len() int { return len(m.fileMatches) } 760 + 761 + func (m fileMatchesByRank) Swap(i, j int) { 762 + m.fileMatches[i], m.fileMatches[j] = m.fileMatches[j], m.fileMatches[i] 763 + m.rffScore[i], m.rffScore[j] = m.rffScore[j], m.rffScore[i] 764 + } 765 + 766 + const epsilon = 0.00000001 767 + 768 + func (m fileMatchesByRank) Less(i, j int) bool { 769 + r1 := m.fileMatches[i].Ranks 770 + r2 := m.fileMatches[j].Ranks 771 + 772 + l := len(r1) 773 + if len(r2) < l { 774 + l = len(r2) 775 + } 776 + for i := 0; i < l; i++ { 777 + if math.Abs(r1[i]-r2[i]) > epsilon { 778 + return r1[i] > r2[i] 779 + } 780 + } 781 + // if r1 has more entries it is more important. ie imagine right padding shorter 782 + // arrays with zeros, so they are the same length. 783 + return len(r1) > len(r2) 784 + } 785 + 786 + type fileMatchesByRFFScore struct { 787 + fileMatches []FileMatch 788 + rffScore []float64 789 + } 790 + 791 + func (m fileMatchesByRFFScore) Len() int { return len(m.fileMatches) } 792 + 793 + func (m fileMatchesByRFFScore) Swap(i, j int) { 794 + m.fileMatches[i], m.fileMatches[j] = m.fileMatches[j], m.fileMatches[i] 795 + m.rffScore[i], m.rffScore[j] = m.rffScore[j], m.rffScore[i] 796 + } 797 + 798 + func (m fileMatchesByRFFScore) Less(i, j int) bool { 799 + // Higher scores are better. 800 + return m.rffScore[i] > m.rffScore[j] 717 801 }
+28
contentprovider_test.go
··· 326 326 }) 327 327 } 328 328 } 329 + 330 + func TestSortFiles(t *testing.T) { 331 + in := []FileMatch{ 332 + {FileName: "d1", Score: 2, Ranks: []float64{0.75}}, 333 + {FileName: "d2", Score: 4, Ranks: []float64{0.25}}, 334 + {FileName: "d3", Score: 3, Ranks: []float64{1.0}}, 335 + {FileName: "d4", Score: 1, Ranks: []float64{0.5}}, 336 + } 337 + 338 + // Document RRF(Score) RFF(Ranks) SUM Rank 339 + // d3 1/(60+1) 1/(60+0) 0,0330601092896175 0 340 + // d2 1/(60+0) 1/(60+3) 0,0325396825396826 1 341 + // d1 1/(60+2) 1/(60+1) 0,0325224748810153 2 342 + // d4 1/(60+3) 1/(60+2) 0,0320020481310804 3 343 + 344 + SortFiles(in) 345 + 346 + wantOrder := []string{"d3", "d2", "d1", "d4"} 347 + 348 + var haveOrder = []string{} 349 + for _, f := range in { 350 + haveOrder = append(haveOrder, f.FileName) 351 + } 352 + 353 + if d := cmp.Diff(wantOrder, haveOrder); d != "" { 354 + t.Fatalf("-want, +got\n%s\n", d) 355 + } 356 + }
+5
eval.go
··· 369 369 370 370 // Prefer earlier docs. 371 371 fileMatch.addScore("doc-order", scoreFileOrderFactor*(1.0-float64(nextDoc)/float64(len(d.boundaries))), opts.DebugScore) 372 + 373 + if len(d.ranks) > int(nextDoc) { 374 + fileMatch.Ranks = d.ranks[nextDoc] 375 + } 376 + 372 377 fileMatch.addScore("shard-order", scoreShardRankFactor*float64(md.Rank)/maxUInt16, opts.DebugScore) 373 378 374 379 fileMatch.Branches = d.gatherBranches(nextDoc, mt, known)
+1 -9
gitindex/index.go
··· 498 498 } 499 499 } 500 500 501 - rankVecForPath := func(path string) []float64 { 502 - s, ok := ranks[path] 503 - if !ok { 504 - return nil 505 - } 506 - return s 507 - } 508 - 509 501 // we don't need to check error, since we either already have an error, or 510 502 // we returning the first call to builder.Finish. 511 503 defer builder.Finish() // nolint:errcheck ··· 558 550 Name: keyFullPath, 559 551 Content: contents, 560 552 Branches: brs, 561 - Ranks: rankVecForPath(keyFullPath), 553 + Ranks: ranks[keyFullPath], 562 554 }); err != nil { 563 555 return fmt.Errorf("error adding document with name %s: %w", keyFullPath, err) 564 556 }
+1
gitindex/tree_test.go
··· 29 29 30 30 "github.com/google/go-cmp/cmp" 31 31 "github.com/grafana/regexp" 32 + 32 33 "github.com/sourcegraph/zoekt" 33 34 "github.com/sourcegraph/zoekt/build" 34 35 "github.com/sourcegraph/zoekt/query"
+7
indexbuilder.go
··· 176 176 // docID => repoID 177 177 repos []uint16 178 178 179 + // Experimental: docID => rank vec 180 + ranks [][]float64 181 + 179 182 contentPostings *postingsBuilder 180 183 namePostings *postingsBuilder 181 184 ··· 498 501 499 502 b.subRepos = append(b.subRepos, subRepoIdx) 500 503 b.repos = append(b.repos, uint16(repoIdx)) 504 + 505 + // doc.Ranks might be nil. In case we don't use offline ranking, doc.Ranks is 506 + // always nil. 507 + b.ranks = append(b.ranks, doc.Ranks) 501 508 502 509 hasher.Write(doc.Content) 503 510
+6
indexdata.go
··· 95 95 // repository indexes for all the files 96 96 repos []uint16 97 97 98 + // Experimental: docID => rank vec 99 + ranks [][]float64 100 + 98 101 // rawConfigMasks contains the encoded RawConfig for each repository 99 102 rawConfigMasks []uint8 100 103 } ··· 306 309 sz += len(d.languages) 307 310 sz += len(d.checksums) 308 311 sz += 2 * len(d.repos) 312 + if len(d.ranks) > 0 { 313 + sz += 8 * len(d.ranks) * len(d.ranks[0]) 314 + } 309 315 sz += 8 * len(d.runeDocSections) 310 316 sz += 8 * len(d.fileBranchMasks) 311 317 sz += d.ngrams.SizeBytes()
+18
read.go
··· 385 385 return nil, err 386 386 } 387 387 388 + // roc.ranks.sz = 0 indicates that we are reading a shard without ranks, in 389 + // which case we skip reading the section and leave d.ranks = nil 390 + if toc.ranks.sz > 0 { 391 + err = d.readRanks(toc) 392 + if err != nil { 393 + return nil, err 394 + } 395 + } 396 + 388 397 if d.metaData.IndexFormatVersion >= 17 { 389 398 blob, err := d.readSectionBlob(toc.repos) 390 399 if err != nil { ··· 569 578 } 570 579 571 580 return unmarshalDocSections(blob, buf), sec.sz, nil 581 + } 582 + 583 + func (d *indexData) readRanks(toc *indexTOC) error { 584 + blob, err := d.readSectionBlob(toc.ranks) 585 + if err != nil { 586 + return err 587 + } 588 + 589 + return decodeRanks(blob, &d.ranks) 572 590 } 573 591 574 592 // NewSearcher creates a Searcher for a single index file. Search
+38
read_test.go
··· 28 28 "strconv" 29 29 "strings" 30 30 "testing" 31 + "testing/quick" 31 32 32 33 "github.com/google/go-cmp/cmp" 34 + "github.com/google/go-cmp/cmp/cmpopts" 35 + 33 36 "github.com/sourcegraph/zoekt/query" 34 37 ) 35 38 ··· 355 358 t.Fatalf("%s != %s ", have1, have2) 356 359 } 357 360 } 361 + 362 + func TestEncodeRanks(t *testing.T) { 363 + f := func(ranks [][]float64) bool { 364 + buf := bytes.Buffer{} 365 + w := &writer{w: &buf} 366 + 367 + if err := encodeRanks(w, ranks); err != nil { 368 + return false 369 + } 370 + 371 + // In case all rank vectors are empty, IE {{}, {}, ...}, we won't write anything 372 + // to w and gob decode will decode this as "nil", which will fail the 373 + // comparison even with cmpopts.EquateEmpty(). 374 + if w.off == 0 { 375 + return true 376 + } 377 + 378 + d := &indexData{} 379 + if err := decodeRanks(buf.Bytes(), &d.ranks); err != nil { 380 + return false 381 + } 382 + 383 + if d := cmp.Diff(ranks, d.ranks, cmpopts.EquateEmpty()); d != "" { 384 + t.Logf("-want, +got:\n%s\n", d) 385 + return false 386 + } 387 + 388 + return true 389 + } 390 + 391 + if err := quick.Check(f, nil); err != nil { 392 + t.Fatal(err) 393 + } 394 + 395 + }
+48 -2
section.go
··· 15 15 package zoekt 16 16 17 17 import ( 18 + "bytes" 18 19 "encoding/binary" 20 + "encoding/gob" 21 + "fmt" 19 22 "io" 20 23 "log" 21 24 ) ··· 29 32 off uint32 30 33 } 31 34 32 - func (w *writer) Write(b []byte) { 35 + func (w *writer) Write(b []byte) (int, error) { 33 36 if w.err != nil { 34 - return 37 + return 0, w.err 35 38 } 36 39 37 40 var n int 38 41 n, w.err = w.w.Write(b) 39 42 w.off += uint32(n) 43 + return n, w.err 40 44 } 41 45 42 46 func (w *writer) Off() uint32 { return w.off } ··· 68 72 b := []byte(s) 69 73 w.Varint(uint32(len(b))) 70 74 w.Write(b) 75 + } 76 + 77 + func encodeRanks(w io.Writer, ranks [][]float64) error { 78 + hasRank := false 79 + for _, r := range ranks { 80 + if len(r) > 0 { 81 + hasRank = true 82 + break 83 + } 84 + } 85 + 86 + if !hasRank { 87 + return nil 88 + } 89 + 90 + // We use the first byte to announce the encoding. This way we can easily change the 91 + // encoding without loosing backward compatability. 92 + _, err := w.Write([]byte{0}) // 0 = gob-encoding 93 + if err != nil { 94 + return err 95 + } 96 + 97 + return gob.NewEncoder(w).Encode(ranks) 98 + } 99 + 100 + func decodeRanks(blob []byte, ranks *[][]float64) error { 101 + if len(blob) == 0 { 102 + return nil 103 + } 104 + 105 + switch encoding := blob[0]; encoding { 106 + case 0: // gob-encoding 107 + dec := gob.NewDecoder(bytes.NewReader(blob[1:])) 108 + err := dec.Decode(ranks) 109 + if err != nil { 110 + return err 111 + } 112 + default: 113 + return fmt.Errorf("unknown encoding for ranks: %d\n", encoding) 114 + } 115 + 116 + return nil 71 117 } 72 118 73 119 func (s *simpleSection) start(w *writer) {
+2 -1
shards/aggregate.go
··· 62 62 agg := c.aggregate 63 63 c.aggregate = nil 64 64 65 - zoekt.SortFilesByScore(agg.Files) 65 + zoekt.SortFiles(agg.Files) 66 + 66 67 if max := c.maxDocDisplayCount; max > 0 && len(agg.Files) > max { 67 68 agg.Files = agg.Files[:max] 68 69 }
+3 -2
shards/shards.go
··· 693 693 // We split by repository instead of by priority because it is easier to set 694 694 // RepoURLs and LineFragments in zoekt.SearchResult. 695 695 func sendByRepository(result *zoekt.SearchResult, sender zoekt.Sender) { 696 + 696 697 if len(result.RepoURLs) <= 1 || len(result.Files) == 0 { 697 - zoekt.SortFilesByScore(result.Files) 698 + zoekt.SortFiles(result.Files) 698 699 sender.Send(result) 699 700 return 700 701 } 701 702 702 703 send := func(repoName string, a, b int, stats zoekt.Stats) { 703 - zoekt.SortFilesByScore(result.Files[a:b]) 704 + zoekt.SortFiles(result.Files[a:b]) 704 705 sender.Send(&zoekt.SearchResult{ 705 706 Stats: stats, 706 707 Progress: zoekt.Progress{
+1
stream/stream_test.go
··· 10 10 "testing" 11 11 12 12 "github.com/google/go-cmp/cmp" 13 + 13 14 "github.com/sourcegraph/zoekt" 14 15 "github.com/sourcegraph/zoekt/internal/mockSearcher" 15 16 "github.com/sourcegraph/zoekt/query"
+20
testdata/golden/TestReadSearch/ctagsrepo_v16.00000.golden
··· 5 5 [ 6 6 { 7 7 "Score": 910, 8 + "Ranks": null, 8 9 "Debug": "", 9 10 "FileName": "main.go", 10 11 "Repository": "repo", ··· 15 16 "LineStart": 69, 16 17 "LineEnd": 82, 17 18 "LineNumber": 10, 19 + "Before": null, 20 + "After": null, 18 21 "FileName": false, 19 22 "Score": 501, 23 + "DebugScore": "", 20 24 "LineFragments": [ 21 25 { 22 26 "LineOffset": 0, ··· 27 31 ] 28 32 } 29 33 ], 34 + "ChunkMatches": null, 30 35 "RepositoryID": 0, 31 36 "RepositoryPriority": 0, 32 37 "Content": null, ··· 40 45 [ 41 46 { 42 47 "Score": 710, 48 + "Ranks": null, 43 49 "Debug": "", 44 50 "FileName": "main.go", 45 51 "Repository": "repo", ··· 50 56 "LineStart": 0, 51 57 "LineEnd": 12, 52 58 "LineNumber": 1, 59 + "Before": null, 60 + "After": null, 53 61 "FileName": false, 54 62 "Score": 501, 63 + "DebugScore": "", 55 64 "LineFragments": [ 56 65 { 57 66 "LineOffset": 0, ··· 62 71 ] 63 72 } 64 73 ], 74 + "ChunkMatches": null, 65 75 "RepositoryID": 0, 66 76 "RepositoryPriority": 0, 67 77 "Content": null, ··· 75 85 [ 76 86 { 77 87 "Score": 7910, 88 + "Ranks": null, 78 89 "Debug": "", 79 90 "FileName": "main.go", 80 91 "Repository": "repo", ··· 85 96 "LineStart": 34, 86 97 "LineEnd": 46, 87 98 "LineNumber": 6, 99 + "Before": null, 100 + "After": null, 88 101 "FileName": false, 89 102 "Score": 7501, 103 + "DebugScore": "", 90 104 "LineFragments": [ 91 105 { 92 106 "LineOffset": 1, ··· 102 116 ] 103 117 } 104 118 ], 119 + "ChunkMatches": null, 105 120 "RepositoryID": 0, 106 121 "RepositoryPriority": 0, 107 122 "Content": null, ··· 115 130 [ 116 131 { 117 132 "Score": 5760, 133 + "Ranks": null, 118 134 "Debug": "", 119 135 "FileName": "main.go", 120 136 "Repository": "repo", ··· 125 141 "LineStart": 47, 126 142 "LineEnd": 65, 127 143 "LineNumber": 7, 144 + "Before": null, 145 + "After": null, 128 146 "FileName": false, 129 147 "Score": 5551, 148 + "DebugScore": "", 130 149 "LineFragments": [ 131 150 { 132 151 "LineOffset": 4, ··· 142 161 ] 143 162 } 144 163 ], 164 + "ChunkMatches": null, 145 165 "RepositoryID": 0, 146 166 "RepositoryPriority": 0, 147 167 "Content": null,
+20
testdata/golden/TestReadSearch/ctagsrepo_v17.00000.golden
··· 5 5 [ 6 6 { 7 7 "Score": 910, 8 + "Ranks": null, 8 9 "Debug": "", 9 10 "FileName": "main.go", 10 11 "Repository": "repo", ··· 15 16 "LineStart": 69, 16 17 "LineEnd": 82, 17 18 "LineNumber": 10, 19 + "Before": null, 20 + "After": null, 18 21 "FileName": false, 19 22 "Score": 501, 23 + "DebugScore": "", 20 24 "LineFragments": [ 21 25 { 22 26 "LineOffset": 0, ··· 27 31 ] 28 32 } 29 33 ], 34 + "ChunkMatches": null, 30 35 "RepositoryID": 0, 31 36 "RepositoryPriority": 0, 32 37 "Content": null, ··· 40 45 [ 41 46 { 42 47 "Score": 710, 48 + "Ranks": null, 43 49 "Debug": "", 44 50 "FileName": "main.go", 45 51 "Repository": "repo", ··· 50 56 "LineStart": 0, 51 57 "LineEnd": 12, 52 58 "LineNumber": 1, 59 + "Before": null, 60 + "After": null, 53 61 "FileName": false, 54 62 "Score": 501, 63 + "DebugScore": "", 55 64 "LineFragments": [ 56 65 { 57 66 "LineOffset": 0, ··· 62 71 ] 63 72 } 64 73 ], 74 + "ChunkMatches": null, 65 75 "RepositoryID": 0, 66 76 "RepositoryPriority": 0, 67 77 "Content": null, ··· 75 85 [ 76 86 { 77 87 "Score": 7910, 88 + "Ranks": null, 78 89 "Debug": "", 79 90 "FileName": "main.go", 80 91 "Repository": "repo", ··· 85 96 "LineStart": 34, 86 97 "LineEnd": 46, 87 98 "LineNumber": 6, 99 + "Before": null, 100 + "After": null, 88 101 "FileName": false, 89 102 "Score": 7501, 103 + "DebugScore": "", 90 104 "LineFragments": [ 91 105 { 92 106 "LineOffset": 1, ··· 102 116 ] 103 117 } 104 118 ], 119 + "ChunkMatches": null, 105 120 "RepositoryID": 0, 106 121 "RepositoryPriority": 0, 107 122 "Content": null, ··· 115 130 [ 116 131 { 117 132 "Score": 5760, 133 + "Ranks": null, 118 134 "Debug": "", 119 135 "FileName": "main.go", 120 136 "Repository": "repo", ··· 125 141 "LineStart": 47, 126 142 "LineEnd": 65, 127 143 "LineNumber": 7, 144 + "Before": null, 145 + "After": null, 128 146 "FileName": false, 129 147 "Score": 5551, 148 + "DebugScore": "", 130 149 "LineFragments": [ 131 150 { 132 151 "LineOffset": 4, ··· 142 161 ] 143 162 } 144 163 ], 164 + "ChunkMatches": null, 145 165 "RepositoryID": 0, 146 166 "RepositoryPriority": 0, 147 167 "Content": null,
+10
testdata/golden/TestReadSearch/repo17_v17.00000.golden
··· 5 5 [ 6 6 { 7 7 "Score": 910, 8 + "Ranks": null, 8 9 "Debug": "", 9 10 "FileName": "main.go", 10 11 "Repository": "repo17", ··· 15 16 "LineStart": 69, 16 17 "LineEnd": 82, 17 18 "LineNumber": 10, 19 + "Before": null, 20 + "After": null, 18 21 "FileName": false, 19 22 "Score": 501, 23 + "DebugScore": "", 20 24 "LineFragments": [ 21 25 { 22 26 "LineOffset": 0, ··· 27 31 ] 28 32 } 29 33 ], 34 + "ChunkMatches": null, 30 35 "RepositoryID": 0, 31 36 "RepositoryPriority": 0, 32 37 "Content": null, ··· 40 45 [ 41 46 { 42 47 "Score": 710, 48 + "Ranks": null, 43 49 "Debug": "", 44 50 "FileName": "main.go", 45 51 "Repository": "repo17", ··· 50 56 "LineStart": 0, 51 57 "LineEnd": 12, 52 58 "LineNumber": 1, 59 + "Before": null, 60 + "After": null, 53 61 "FileName": false, 54 62 "Score": 501, 63 + "DebugScore": "", 55 64 "LineFragments": [ 56 65 { 57 66 "LineOffset": 0, ··· 62 71 ] 63 72 } 64 73 ], 74 + "ChunkMatches": null, 65 75 "RepositoryID": 0, 66 76 "RepositoryPriority": 0, 67 77 "Content": null,
+6
testdata/golden/TestReadSearch/repo2_v16.00000.golden
··· 5 5 [ 6 6 { 7 7 "Score": 910, 8 + "Ranks": null, 8 9 "Debug": "", 9 10 "FileName": "main.go", 10 11 "Repository": "repo2", ··· 19 20 "After": null, 20 21 "FileName": false, 21 22 "Score": 501, 23 + "DebugScore": "", 22 24 "LineFragments": [ 23 25 { 24 26 "LineOffset": 0, ··· 29 31 ] 30 32 } 31 33 ], 34 + "ChunkMatches": null, 32 35 "RepositoryID": 0, 33 36 "RepositoryPriority": 0, 34 37 "Content": null, ··· 42 45 [ 43 46 { 44 47 "Score": 710, 48 + "Ranks": null, 45 49 "Debug": "", 46 50 "FileName": "main.go", 47 51 "Repository": "repo2", ··· 56 60 "After": null, 57 61 "FileName": false, 58 62 "Score": 501, 63 + "DebugScore": "", 59 64 "LineFragments": [ 60 65 { 61 66 "LineOffset": 0, ··· 66 71 ] 67 72 } 68 73 ], 74 + "ChunkMatches": null, 69 75 "RepositoryID": 0, 70 76 "RepositoryPriority": 0, 71 77 "Content": null,
+10
testdata/golden/TestReadSearch/repo_v16.00000.golden
··· 5 5 [ 6 6 { 7 7 "Score": 910, 8 + "Ranks": null, 8 9 "Debug": "", 9 10 "FileName": "main.go", 10 11 "Repository": "repo", ··· 15 16 "LineStart": 69, 16 17 "LineEnd": 82, 17 18 "LineNumber": 10, 19 + "Before": null, 20 + "After": null, 18 21 "FileName": false, 19 22 "Score": 501, 23 + "DebugScore": "", 20 24 "LineFragments": [ 21 25 { 22 26 "LineOffset": 0, ··· 27 31 ] 28 32 } 29 33 ], 34 + "ChunkMatches": null, 30 35 "RepositoryID": 0, 31 36 "RepositoryPriority": 0, 32 37 "Content": null, ··· 40 45 [ 41 46 { 42 47 "Score": 710, 48 + "Ranks": null, 43 49 "Debug": "", 44 50 "FileName": "main.go", 45 51 "Repository": "repo", ··· 50 56 "LineStart": 0, 51 57 "LineEnd": 12, 52 58 "LineNumber": 1, 59 + "Before": null, 60 + "After": null, 53 61 "FileName": false, 54 62 "Score": 501, 63 + "DebugScore": "", 55 64 "LineFragments": [ 56 65 { 57 66 "LineOffset": 0, ··· 62 71 ] 63 72 } 64 73 ], 74 + "ChunkMatches": null, 65 75 "RepositoryID": 0, 66 76 "RepositoryPriority": 0, 67 77 "Content": null,
testdata/shards/repo2_v16.00000.zoekt

This is a binary file and will not be displayed.

testdata/shards/repo_v16.00000.zoekt

This is a binary file and will not be displayed.

+4
toc.go
··· 97 97 runeDocSections simpleSection 98 98 99 99 repos simpleSection 100 + 101 + ranks simpleSection 100 102 } 101 103 102 104 func (t *indexTOC) sections() []section { ··· 184 186 // avoid warnings about unknown sections. 185 187 {"nameBloom", &unusedSimple}, 186 188 {"contentBloom", &unusedSimple}, 189 + 190 + {"ranks", &t.ranks}, 187 191 } 188 192 } 189 193
+6
write.go
··· 199 199 } 200 200 } 201 201 202 + toc.ranks.start(w) 203 + if err := encodeRanks(w, b.ranks); err != nil { 204 + return err 205 + } 206 + toc.ranks.end(w) 207 + 202 208 var tocSection simpleSection 203 209 204 210 tocSection.start(w)