// Copyright 2019 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // This file incorporates work covered by the following copyright and // permission notice: // // Copyright 2016 Attic Labs, Inc. All rights reserved. // Licensed under the Apache License, version 2.0: // http://www.apache.org/licenses/LICENSE-2.0 package main import ( "context" "fmt" "github.com/dolthub/dolt/go/store/d" "github.com/dolthub/dolt/go/store/datas" "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/dolt/go/store/types" ) type CommitIterator struct { vr types.ValueReader branches branchList } // NewCommitIterator initializes a new CommitIterator with the first commit to be printed. func NewCommitIterator(vr types.ValueReader, commit types.Value) *CommitIterator { cr, err := types.NewRef(commit, vr.Format()) d.PanicIfError(err) return &CommitIterator{vr: vr, branches: branchList{branch{addr: cr.TargetHash(), height: cr.Height(), commit: commit}}} } // Next returns information about the next commit to be printed. LogNode contains enough contextual // info that the commit and associated graph can be correctly printed. // This works by traversing the "commit" di-graph in a breadth-first manner. Each time it is called, // the commit in the branchlist with the greatest height is returned. If that commit has multiple // parents, new branches are added to the branchlist so that they can be traversed in order. When // more than one branch contains the same node, that indicates that the branches are converging and so // the branchlist will have branches removed to reflect that. func (iter *CommitIterator) Next(ctx context.Context) (LogNode, bool) { if iter.branches.IsEmpty() { return LogNode{}, false } // Float of branches present when printing this commit startingColCount := len(iter.branches) branchIndexes := iter.branches.HighestBranchIndexes() col := branchIndexes[0] br := iter.branches[col] // Any additional indexes, represent other branches with the same ancestor. So they are merging // into a common ancestor and are no longer graphed. iter.branches = iter.branches.RemoveBranches(branchIndexes[1:]) // If this commit has parents, then a branch is splitting. Create a branch for each of the parents // and splice that into the iterators list of branches. branches := branchList{} parents, err := datas.GetCommitParents(ctx, iter.vr, br.commit) d.PanicIfError(err) for _, p := range parents { v := p.NomsValue() b := branch{height: p.Height(), addr: p.Addr(), commit: v} branches = append(branches, b) } iter.branches = iter.branches.Splice(col, 1, branches...) // Collect the indexes for any newly created branches. newCols := []int{} for cnt := 1; cnt < len(parents); cnt++ { newCols = append(newCols, col+cnt) } // Now that the branchlist has been adjusted, check to see if there are branches with common // ancestors that will be folded together on this commit's graph. foldedCols := iter.branches.HighestBranchIndexes() node := LogNode{ height: br.height, addr: br.addr, commit: br.commit, startingColCount: startingColCount, endingColCount: len(iter.branches), col: col, newCols: newCols, foldedCols: foldedCols, lastCommit: iter.branches.IsEmpty(), } return node, true } type LogNode struct { commit types.Value // commit that needs to be printed newCols []int // col to start using '\' in graph foldedCols []int // cols with common ancestors, that will get folded together height uint64 startingColCount int // how many branches are being tracked when this commit is printed endingColCount int // home many branches will be tracked when next commit is printed col int // col to put the '*' character in graph addr hash.Hash lastCommit bool // this is the last commit that will be returned by iterator } func (n LogNode) String() string { return fmt.Sprintf("cr: %s(%d), startingColCount: %d, endingColCount: %d, col: %d, newCols: %v, foldedCols: %v, expanding: %t, shrunk: %t, shrinking: %t", n.addr.String()[0:9], n.height, n.startingColCount, n.endingColCount, n.col, n.newCols, n.foldedCols, n.Expanding(), n.Shrunk(), n.Shrinking()) } // Expanding reports whether this commit's graph will expand to show an additional branch func (n LogNode) Expanding() bool { return n.startingColCount < n.endingColCount } // Shrinking reports whether this commit's graph will show a branch being folded into another branch func (n LogNode) Shrinking() bool { return len(n.foldedCols) > 1 } // Shrunk reports whether the previous commit showed a branch being folded into another branch. func (n LogNode) Shrunk() bool { return n.startingColCount > n.endingColCount } type branch struct { commit types.Value height uint64 addr hash.Hash } func (b branch) String() string { return fmt.Sprintf("%s(%d)", b.addr.String()[0:9], b.height) } type branchList []branch func (bl branchList) IsEmpty() bool { return len(bl) == 0 } // look through this list of branches and return the one(s) with the max height. // If there are multiple nodes with max height, the result will contain a list of all nodes with // maxHeight that are duplicates of the first one found. // This indicates that two or more branches or converging. func (bl branchList) HighestBranchIndexes() []int { maxHeight := uint64(0) var br branch cols := []int{} for i, b := range bl { if b.height > maxHeight { maxHeight = b.height br = b cols = []int{i} } else if b.height == maxHeight && b.addr == br.addr { cols = append(cols, i) } } return cols } func (bl branchList) Splice(start int, deleteCount int, branches ...branch) branchList { res := append(branchList{}, bl[:start]...) res = append(res, branches...) return append(res, bl[start+deleteCount:]...) } func (bl branchList) RemoveBranches(indexes []int) branchList { for i := len(indexes) - 1; i >= 0; i-- { bl = bl.Splice(indexes[i], 1) } return bl }