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  }