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  }