github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/cmd/noms/commit_iterator.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package main 23 24 import ( 25 "context" 26 "fmt" 27 28 "github.com/dolthub/dolt/go/store/d" 29 30 "github.com/dolthub/dolt/go/store/datas" 31 "github.com/dolthub/dolt/go/store/types" 32 ) 33 34 type CommitIterator struct { 35 db datas.Database 36 branches branchList 37 } 38 39 // NewCommitIterator initializes a new CommitIterator with the first commit to be printed. 40 func NewCommitIterator(db datas.Database, commit types.Struct) *CommitIterator { 41 cr, err := types.NewRef(commit, db.Format()) 42 d.PanicIfError(err) 43 44 return &CommitIterator{db: db, branches: branchList{branch{cr: cr, commit: commit}}} 45 } 46 47 // Next returns information about the next commit to be printed. LogNode contains enough contextual 48 // info that the commit and associated graph can be correctly printed. 49 // This works by traversing the "commit" di-graph in a breadth-first manner. Each time it is called, 50 // the commit in the branchlist with the greatest height is returned. If that commit has multiple 51 // parents, new branches are added to the branchlist so that they can be traversed in order. When 52 // more than one branch contains the same node, that indicates that the branches are converging and so 53 // the branchlist will have branches removed to reflect that. 54 func (iter *CommitIterator) Next(ctx context.Context) (LogNode, bool) { 55 if iter.branches.IsEmpty() { 56 return LogNode{}, false 57 } 58 59 // Float of branches present when printing this commit 60 startingColCount := len(iter.branches) 61 62 branchIndexes := iter.branches.HighestBranchIndexes() 63 col := branchIndexes[0] 64 br := iter.branches[col] 65 66 // Any additional indexes, represent other branches with the same ancestor. So they are merging 67 // into a common ancestor and are no longer graphed. 68 iter.branches = iter.branches.RemoveBranches(branchIndexes[1:]) 69 70 // If this commit has parents, then a branch is splitting. Create a branch for each of the parents 71 // and splice that into the iterators list of branches. 72 branches := branchList{} 73 pFld, ok, err := br.commit.MaybeGet(datas.ParentsField) 74 d.PanicIfError(err) 75 d.PanicIfFalse(ok) 76 77 parents := commitRefsFromSet(ctx, pFld.(types.Set)) 78 for _, p := range parents { 79 v, err := iter.db.ReadValue(ctx, p.TargetHash()) 80 d.PanicIfError(err) 81 82 b := branch{cr: p, commit: v.(types.Struct)} 83 branches = append(branches, b) 84 } 85 iter.branches = iter.branches.Splice(col, 1, branches...) 86 87 // Collect the indexes for any newly created branches. 88 newCols := []int{} 89 for cnt := 1; cnt < len(parents); cnt++ { 90 newCols = append(newCols, col+cnt) 91 } 92 93 // Now that the branchlist has been adusted, check to see if there are branches with common 94 // ancestors that will be folded together on this commit's graph. 95 foldedCols := iter.branches.HighestBranchIndexes() 96 node := LogNode{ 97 cr: br.cr, 98 commit: br.commit, 99 startingColCount: startingColCount, 100 endingColCount: len(iter.branches), 101 col: col, 102 newCols: newCols, 103 foldedCols: foldedCols, 104 lastCommit: iter.branches.IsEmpty(), 105 } 106 return node, true 107 } 108 109 type LogNode struct { 110 cr types.Ref // typed ref of commit to be printed 111 commit types.Struct // commit that needs to be printed 112 startingColCount int // how many branches are being tracked when this commit is printed 113 endingColCount int // home many branches will be tracked when next commit is printed 114 col int // col to put the '*' character in graph 115 newCols []int // col to start using '\' in graph 116 foldedCols []int // cols with common ancestors, that will get folded together 117 lastCommit bool // this is the last commit that will be returned by iterator 118 } 119 120 func (n LogNode) String() string { 121 return fmt.Sprintf("cr: %s(%d), startingColCount: %d, endingColCount: %d, col: %d, newCols: %v, foldedCols: %v, expanding: %t, shrunk: %t, shrinking: %t", n.cr.TargetHash().String()[0:9], n.cr.Height(), n.startingColCount, n.endingColCount, n.col, n.newCols, n.foldedCols, n.Expanding(), n.Shrunk(), n.Shrinking()) 122 } 123 124 // Expanding reports whether this commit's graph will expand to show an additional branch 125 func (n LogNode) Expanding() bool { 126 return n.startingColCount < n.endingColCount 127 } 128 129 // Shrinking reports whether this commit's graph will show a branch being folded into another branch 130 func (n LogNode) Shrinking() bool { 131 return len(n.foldedCols) > 1 132 } 133 134 // Shrunk reports whether the previous commit showed a branch being folded into another branch. 135 func (n LogNode) Shrunk() bool { 136 return n.startingColCount > n.endingColCount 137 } 138 139 type branch struct { 140 cr types.Ref 141 commit types.Struct 142 } 143 144 func (b branch) String() string { 145 return fmt.Sprintf("%s(%d)", b.cr.TargetHash().String()[0:9], b.cr.Height()) 146 } 147 148 type branchList []branch 149 150 func (bl branchList) IsEmpty() bool { 151 return len(bl) == 0 152 } 153 154 // look through this list of branches and return the one(s) with the max height. 155 // If there are multiple nodes with max height, the result will contain a list of all nodes with 156 // maxHeight that are duplicates of the first one found. 157 // This indicates that two or more branches or converging. 158 func (bl branchList) HighestBranchIndexes() []int { 159 maxHeight := uint64(0) 160 var cr types.Ref 161 cols := []int{} 162 for i, b := range bl { 163 if b.cr.Height() > maxHeight { 164 maxHeight = b.cr.Height() 165 cr = b.cr 166 cols = []int{i} 167 } else if b.cr.Height() == maxHeight && b.cr.Equals(cr) { 168 cols = append(cols, i) 169 } 170 } 171 return cols 172 } 173 174 func (bl branchList) Splice(start int, deleteCount int, branches ...branch) branchList { 175 res := append(branchList{}, bl[:start]...) 176 res = append(res, branches...) 177 return append(res, bl[start+deleteCount:]...) 178 } 179 180 func (bl branchList) RemoveBranches(indexes []int) branchList { 181 for i := len(indexes) - 1; i >= 0; i-- { 182 bl = bl.Splice(indexes[i], 1) 183 } 184 return bl 185 } 186 187 func commitRefsFromSet(ctx context.Context, set types.Set) []types.Ref { 188 res := []types.Ref{} 189 _ = set.IterAll(ctx, func(v types.Value) error { 190 res = append(res, v.(types.Ref)) 191 return nil 192 }) 193 194 return res 195 }