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  }