github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/cmd/noms/commit_iterator.go (about) 1 // Copyright 2016 Attic Labs, Inc. All rights reserved. 2 // Licensed under the Apache License, version 2.0: 3 // http://www.apache.org/licenses/LICENSE-2.0 4 5 package main 6 7 import ( 8 "fmt" 9 10 "github.com/attic-labs/noms/go/datas" 11 "github.com/attic-labs/noms/go/types" 12 ) 13 14 type CommitIterator struct { 15 db datas.Database 16 branches branchList 17 } 18 19 // NewCommitIterator initializes a new CommitIterator with the first commit to be printed. 20 func NewCommitIterator(db datas.Database, commit types.Struct) *CommitIterator { 21 cr := types.NewRef(commit) 22 return &CommitIterator{db: db, branches: branchList{branch{cr: cr, commit: commit}}} 23 } 24 25 // Next returns information about the next commit to be printed. LogNode contains enough contextual 26 // info that the commit and associated graph can be correctly printed. 27 // This works by traversing the "commit" di-graph in a breadth-first manner. Each time it is called, 28 // the commit in the branchlist with the greatest height is returned. If that commit has multiple 29 // parents, new branches are added to the branchlist so that they can be traversed in order. When 30 // more than one branch contains the same node, that indicates that the branches are converging and so 31 // the branchlist will have branches removed to reflect that. 32 func (iter *CommitIterator) Next() (LogNode, bool) { 33 if iter.branches.IsEmpty() { 34 return LogNode{}, false 35 } 36 37 // Number of branches present when printing this commit 38 startingColCount := len(iter.branches) 39 40 branchIndexes := iter.branches.HighestBranchIndexes() 41 col := branchIndexes[0] 42 br := iter.branches[col] 43 44 // Any additional indexes, represent other branches with the same ancestor. So they are merging 45 // into a common ancestor and are no longer graphed. 46 iter.branches = iter.branches.RemoveBranches(branchIndexes[1:]) 47 48 // If this commit has parents, then a branch is splitting. Create a branch for each of the parents 49 // and splice that into the iterators list of branches. 50 branches := branchList{} 51 parents := commitRefsFromSet(br.commit.Get(datas.ParentsField).(types.Set)) 52 for _, p := range parents { 53 b := branch{cr: p, commit: iter.db.ReadValue(p.TargetHash()).(types.Struct)} 54 branches = append(branches, b) 55 } 56 iter.branches = iter.branches.Splice(col, 1, branches...) 57 58 // Collect the indexes for any newly created branches. 59 newCols := []int{} 60 for cnt := 1; cnt < len(parents); cnt++ { 61 newCols = append(newCols, col+cnt) 62 } 63 64 // Now that the branchlist has been adusted, check to see if there are branches with common 65 // ancestors that will be folded together on this commit's graph. 66 foldedCols := iter.branches.HighestBranchIndexes() 67 node := LogNode{ 68 cr: br.cr, 69 commit: br.commit, 70 startingColCount: startingColCount, 71 endingColCount: len(iter.branches), 72 col: col, 73 newCols: newCols, 74 foldedCols: foldedCols, 75 lastCommit: iter.branches.IsEmpty(), 76 } 77 return node, true 78 } 79 80 type LogNode struct { 81 cr types.Ref // typed ref of commit to be printed 82 commit types.Struct // commit that needs to be printed 83 startingColCount int // how many branches are being tracked when this commit is printed 84 endingColCount int // home many branches will be tracked when next commit is printed 85 col int // col to put the '*' character in graph 86 newCols []int // col to start using '\' in graph 87 foldedCols []int // cols with common ancestors, that will get folded together 88 lastCommit bool // this is the last commit that will be returned by iterator 89 } 90 91 func (n LogNode) String() string { 92 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()) 93 } 94 95 // Expanding reports whether this commit's graph will expand to show an additional branch 96 func (n LogNode) Expanding() bool { 97 return n.startingColCount < n.endingColCount 98 } 99 100 // Shrinking reports whether this commit's graph will show a branch being folded into another branch 101 func (n LogNode) Shrinking() bool { 102 return len(n.foldedCols) > 1 103 } 104 105 // Shrunk reports whether the previous commit showed a branch being folded into another branch. 106 func (n LogNode) Shrunk() bool { 107 return n.startingColCount > n.endingColCount 108 } 109 110 type branch struct { 111 cr types.Ref 112 commit types.Struct 113 } 114 115 func (b branch) String() string { 116 return fmt.Sprintf("%s(%d)", b.cr.TargetHash().String()[0:9], b.cr.Height()) 117 } 118 119 type branchList []branch 120 121 func (bl branchList) IsEmpty() bool { 122 return len(bl) == 0 123 } 124 125 // look through this list of branches and return the one(s) with the max height. 126 // If there are multiple nodes with max height, the result will contain a list of all nodes with 127 // maxHeight that are duplicates of the first one found. 128 // This indicates that two or more branches or converging. 129 func (bl branchList) HighestBranchIndexes() []int { 130 maxHeight := uint64(0) 131 var cr types.Ref 132 cols := []int{} 133 for i, b := range bl { 134 if b.cr.Height() > maxHeight { 135 maxHeight = b.cr.Height() 136 cr = b.cr 137 cols = []int{i} 138 } else if b.cr.Height() == maxHeight && b.cr.Equals(cr) { 139 cols = append(cols, i) 140 } 141 } 142 return cols 143 } 144 145 func (bl branchList) Splice(start int, deleteCount int, branches ...branch) branchList { 146 res := append(branchList{}, bl[:start]...) 147 res = append(res, branches...) 148 return append(res, bl[start+deleteCount:]...) 149 } 150 151 func (bl branchList) RemoveBranches(indexes []int) branchList { 152 for i := len(indexes) - 1; i >= 0; i-- { 153 bl = bl.Splice(indexes[i], 1) 154 } 155 return bl 156 } 157 158 func commitRefsFromSet(set types.Set) []types.Ref { 159 res := []types.Ref{} 160 set.IterAll(func(v types.Value) { 161 res = append(res, v.(types.Ref)) 162 }) 163 return res 164 }