github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/data/prev_revisions.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package data 6 7 import ( 8 "github.com/keybase/client/go/kbfs/kbfsmd" 9 "github.com/keybase/go-codec/codec" 10 ) 11 12 // PrevRevisionAndCount track the MD version of a previous revision of 13 // a dir entry, and how many revisions ago that was from the current 14 // revision. 15 type PrevRevisionAndCount struct { 16 Revision kbfsmd.Revision `codec:"r"` 17 Count uint8 `codec:"c"` 18 19 codec.UnknownFieldSetHandler 20 } 21 22 // minPrevRevisionSlotCounts defines the "min count" of each 23 // corresponding entry in a `PrevRevisions` slice. The length of 24 // `minPrevRevisionSlotCounts` is the max length of a `PrevRevisions` 25 // slice. 26 var minPrevRevisionSlotCounts = [...]uint8{1, 5, 10, 25, 100} 27 28 // PrevRevisions tracks several previous versions of a file in order 29 // of descending revision number, starting with the most recent. 30 type PrevRevisions []PrevRevisionAndCount 31 32 // AddRevision returns a copy of `pr` with a new immediately-previous 33 // revision added, with the existing entries moved or overwritten to 34 // accomodate the new entry, and with increased counts. Any existing 35 // revisions smaller than or equal to minRev will be removed. 36 func (pr PrevRevisions) AddRevision( 37 r, minRev kbfsmd.Revision) (ret PrevRevisions) { 38 newLength := len(pr) 39 if newLength < len(minPrevRevisionSlotCounts) { 40 newLength++ 41 } 42 ret = make(PrevRevisions, newLength) 43 copy(ret, pr) 44 earliestGoodSlot := 0 45 numDropped := 0 46 47 // First we eliminate any revisions in the current list that don't 48 // make sense anymore, either because they're greater or equal to 49 // `r`, or they're smaller or equal to `minRev` (and thus have 50 // been GC'd). For example: 51 // 52 // pr = [27, 25, 15, 10, 5] (revision numbers only) 53 // r = 27 54 // minRev = 11 55 // 56 // After this next block, we should have: 57 // 58 // ret = [0, 25, 15, 0, 0] 59 // earliestGoodSlot = 1 60 // numDropped = 2 61 // 62 // Then the next block of code will trim it appropriately. 63 for i, prc := range ret { 64 switch { 65 case prc.Count == 255: 66 // This count on this revision is too large, so remove it 67 // before it overflows. This may happen when revisions 68 // are repeatedly overwritten when on an unmerged branch, 69 // as in the case below. 70 ret[i] = PrevRevisionAndCount{ 71 Revision: kbfsmd.RevisionUninitialized, 72 Count: 0, 73 } 74 numDropped++ 75 continue 76 case prc.Revision >= r: 77 if numDropped > 0 { 78 panic("Revision too large after dropping one") 79 } 80 // The revision number is bigger than expected (e.g. it 81 // was made on an unmerged branch). 82 ret[i] = PrevRevisionAndCount{ 83 Revision: kbfsmd.RevisionUninitialized, 84 Count: 0, 85 } 86 earliestGoodSlot = i + 1 87 continue 88 case prc.Revision <= minRev: 89 // This revision is too old (or is empty), so remove it. 90 ret[i] = PrevRevisionAndCount{ 91 Revision: kbfsmd.RevisionUninitialized, 92 Count: 0, 93 } 94 numDropped++ 95 continue 96 case numDropped > 0: 97 panic("Once we've dropped one, we should drop all the rest") 98 } 99 // `minRev` < `prc.Revision` < `r`, so we keep it in the new 100 // slice and increment its count. 101 ret[i].Count++ 102 } 103 104 // Cut out the revisions that are newer than `r` (e.g., because 105 // they are from an unmerged branch). 106 // 107 // Continuing the example above, this code will leave us with: 108 // 109 // ret = [25, 15, 0, 0] 110 if earliestGoodSlot > 0 { 111 if earliestGoodSlot == len(ret) { 112 // Always leave at least one empty slot. 113 earliestGoodSlot-- 114 } 115 ret = ret[earliestGoodSlot:] 116 } 117 118 // Drop revisions off the end that are too old, but leave an empty 119 // slot available at the end for shifting everything over and 120 // putting `r` in slot 0. 121 // 122 // Continuing the example above, this code will leave us with: 123 // 124 // ret = [25, 15, 0] 125 if numDropped == len(ret) { 126 // Leave the first slot available for overwriting. 127 ret = ret[:1] 128 } else if numDropped > 1 { 129 ret = ret[:len(ret)-(numDropped-1)] 130 } 131 132 // Starting at the end, shift revisions to the right if either a) 133 // that slot is already empty or b) they satisfy the count of the 134 // slot to the right. If a revision is not going to shifted, but 135 // it is too close (in terms of count) to the revision on its 136 // right, just drop it and let the other revisions slide over -- 137 // this makes sure we have a nicely-spaced set of revision numbers 138 // even when the total number of revisions for the entry is small. 139 // 140 // Continuing the example above, this code will leave us with: 141 // 142 // ret = [0, 25, 15] 143 for i := len(ret) - 1; i >= 1; i-- { 144 // Check if we can shift over the entry in slot i-1. 145 minCount := minPrevRevisionSlotCounts[i] 146 if ret[i].Count == 0 || ret[i-1].Count >= minCount { 147 ret[i], ret[i-1] = ret[i-1], PrevRevisionAndCount{ 148 Revision: kbfsmd.RevisionUninitialized, 149 Count: 0, 150 } 151 } else if ret[i].Count-ret[i-1].Count < minCount/5 { 152 // This revision is not being shifted, but it's 153 // uncomfortablely close to its neighbor on the right, so 154 // just drop it. 155 ret[i-1] = PrevRevisionAndCount{ 156 Revision: kbfsmd.RevisionUninitialized, 157 Count: 0, 158 } 159 } 160 } 161 162 // Finally, overwrite whatever's left in the first slot with `r` 163 // and a count of 1. 164 // 165 // Continuing the example above, this code will leave us with: 166 // 167 // ret = [27, 25, 15] 168 ret[0] = PrevRevisionAndCount{ 169 Revision: r, 170 Count: 1, 171 } 172 return ret 173 }