github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/env/actions/commitwalk/commitwalk.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  package commitwalk
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  
    21  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    22  	"github.com/dolthub/dolt/go/store/hash"
    23  )
    24  
    25  type c struct {
    26  	ddb       *doltdb.DoltDB
    27  	commit    *doltdb.Commit
    28  	hash      hash.Hash
    29  	height    uint64
    30  	invisible bool
    31  	queued    bool
    32  }
    33  
    34  type q struct {
    35  	pending           []*c
    36  	numVisiblePending int
    37  	loaded            map[hash.Hash]*c
    38  }
    39  
    40  func (q *q) NumVisiblePending() int {
    41  	return q.numVisiblePending
    42  }
    43  
    44  func (q *q) PopPending() *c {
    45  	c := q.pending[len(q.pending)-1]
    46  	q.pending = q.pending[:len(q.pending)-1]
    47  	if !c.invisible {
    48  		q.numVisiblePending--
    49  	}
    50  	return c
    51  }
    52  
    53  func (q *q) AddPendingIfUnseen(ctx context.Context, ddb *doltdb.DoltDB, id hash.Hash) error {
    54  	c, err := q.Get(ctx, ddb, id)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	if !c.queued {
    59  		c.queued = true
    60  		var i int
    61  		for i = 0; i < len(q.pending); i++ {
    62  			if q.pending[i].height > c.height {
    63  				break
    64  			}
    65  			if q.pending[i].height < c.height {
    66  				continue
    67  			}
    68  
    69  			// if the commits have equal height, tiebreak on timestamp
    70  			pendingMeta, err := q.pending[i].commit.GetCommitMeta()
    71  			if err != nil {
    72  				return err
    73  			}
    74  			commitMeta, err := c.commit.GetCommitMeta()
    75  			if err != nil {
    76  				return err
    77  			}
    78  			if pendingMeta.UserTimestamp > commitMeta.UserTimestamp {
    79  				break
    80  			}
    81  		}
    82  		q.pending = append(q.pending, nil)
    83  		copy(q.pending[i+1:], q.pending[i:])
    84  		q.pending[i] = c
    85  		if !c.invisible {
    86  			q.numVisiblePending++
    87  		}
    88  	}
    89  	return nil
    90  }
    91  
    92  func (q *q) SetInvisible(ctx context.Context, ddb *doltdb.DoltDB, id hash.Hash) error {
    93  	c, err := q.Get(ctx, ddb, id)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if !c.invisible {
    98  		c.invisible = true
    99  		if c.queued {
   100  			q.numVisiblePending--
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  func load(ctx context.Context, ddb *doltdb.DoltDB, h hash.Hash) (*doltdb.Commit, error) {
   107  	cs, err := doltdb.NewCommitSpec(h.String())
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	c, err := ddb.Resolve(ctx, cs, nil)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return c, nil
   116  }
   117  
   118  func (q *q) Get(ctx context.Context, ddb *doltdb.DoltDB, id hash.Hash) (*c, error) {
   119  	if l, ok := q.loaded[id]; ok {
   120  		return l, nil
   121  	}
   122  
   123  	l, err := load(ctx, ddb, id)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	h, err := l.Height()
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	c := &c{ddb: ddb, commit: l, height: h, hash: id}
   133  	q.loaded[id] = c
   134  	return c, nil
   135  }
   136  
   137  func newQueue() *q {
   138  	return &q{loaded: make(map[hash.Hash]*c)}
   139  }
   140  
   141  // GetDotDotRevisions returns the commits reachable from commit at hash
   142  // `includedHead` that are not reachable from hash `excludedHead`.
   143  // `includedHead` and `excludedHead` must be commits in `ddb`. Returns up
   144  // to `num` commits, in reverse topological order starting at `includedHead`,
   145  // with tie breaking based on the height of commit graph between
   146  // concurrent commits --- higher commits appear first. Remaining
   147  // ties are broken by timestamp; newer commits appear first.
   148  //
   149  // Roughly mimics `git log master..feature`.
   150  func GetDotDotRevisions(ctx context.Context, includedDB *doltdb.DoltDB, includedHead hash.Hash, excludedDB *doltdb.DoltDB, excludedHead hash.Hash, num int) ([]*doltdb.Commit, error) {
   151  	commitList := make([]*doltdb.Commit, 0, num)
   152  	q := newQueue()
   153  	if err := q.SetInvisible(ctx, excludedDB, excludedHead); err != nil {
   154  		return nil, err
   155  	}
   156  	if err := q.AddPendingIfUnseen(ctx, excludedDB, excludedHead); err != nil {
   157  		return nil, err
   158  	}
   159  	if err := q.AddPendingIfUnseen(ctx, includedDB, includedHead); err != nil {
   160  		return nil, err
   161  	}
   162  	for q.NumVisiblePending() > 0 {
   163  		nextC := q.PopPending()
   164  		parents, err := nextC.commit.ParentHashes(ctx)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		for _, parentID := range parents {
   169  			if nextC.invisible {
   170  				if err := q.SetInvisible(ctx, nextC.ddb, parentID); err != nil {
   171  					return nil, err
   172  				}
   173  			}
   174  			if err := q.AddPendingIfUnseen(ctx, nextC.ddb, parentID); err != nil {
   175  				return nil, err
   176  			}
   177  		}
   178  		if !nextC.invisible {
   179  			commitList = append(commitList, nextC.commit)
   180  			if len(commitList) == num {
   181  				return commitList, nil
   182  			}
   183  		}
   184  	}
   185  	return commitList, nil
   186  }
   187  
   188  // GetTopologicalOrderCommits returns the commits reachable from the commit at hash `startCommitHash`
   189  // in reverse topological order, with tiebreaking done by the height of the commit graph -- higher commits
   190  // appear first. Remaining ties are broken by timestamp; newer commits appear first.
   191  func GetTopologicalOrderCommits(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash) ([]*doltdb.Commit, error) {
   192  	return GetTopNTopoOrderedCommitsMatching(ctx, ddb, startCommitHash, -1, nil)
   193  }
   194  
   195  // GetTopologicalOrderCommitIterator returns an iterator for commits generated with the same semantics as
   196  // GetTopologicalOrderCommits
   197  func GetTopologicalOrderIterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash) (doltdb.CommitItr, error) {
   198  	return newCommiterator(ctx, ddb, startCommitHash)
   199  }
   200  
   201  type commiterator struct {
   202  	ddb             *doltdb.DoltDB
   203  	startCommitHash hash.Hash
   204  	q               *q
   205  }
   206  
   207  var _ doltdb.CommitItr = (*commiterator)(nil)
   208  
   209  func newCommiterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash) (*commiterator, error) {
   210  	itr := &commiterator{
   211  		ddb:             ddb,
   212  		startCommitHash: startCommitHash,
   213  	}
   214  
   215  	err := itr.Reset(ctx)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	return itr, nil
   221  }
   222  
   223  // Next implements doltdb.CommitItr
   224  func (i *commiterator) Next(ctx context.Context) (hash.Hash, *doltdb.Commit, error) {
   225  	if i.q.NumVisiblePending() > 0 {
   226  		nextC := i.q.PopPending()
   227  		parents, err := nextC.commit.ParentHashes(ctx)
   228  		if err != nil {
   229  			return hash.Hash{}, nil, err
   230  		}
   231  
   232  		for _, parentID := range parents {
   233  			if err := i.q.AddPendingIfUnseen(ctx, nextC.ddb, parentID); err != nil {
   234  				return hash.Hash{}, nil, err
   235  			}
   236  		}
   237  
   238  		return nextC.hash, nextC.commit, nil
   239  	}
   240  
   241  	return hash.Hash{}, nil, io.EOF
   242  }
   243  
   244  // Reset implements doltdb.CommitItr
   245  func (i *commiterator) Reset(ctx context.Context) error {
   246  	i.q = newQueue()
   247  	if err := i.q.AddPendingIfUnseen(ctx, i.ddb, i.startCommitHash); err != nil {
   248  		return err
   249  	}
   250  	return nil
   251  }
   252  
   253  // GetTopNTopoOrderedCommitsMatching returns the first N commits (If N <= 0 then all commits) reachable from the commit at hash
   254  // `startCommitHash` in reverse topological order, with tiebreaking done by the height of the commit graph -- higher
   255  // commits appear first. Remaining ties are broken by timestamp; newer commits appear first.
   256  func GetTopNTopoOrderedCommitsMatching(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash, n int, matchFn func(*doltdb.Commit) (bool, error)) ([]*doltdb.Commit, error) {
   257  	itr, err := GetTopologicalOrderIterator(ctx, ddb, startCommitHash)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	var commitList []*doltdb.Commit
   263  	for n < 0 || len(commitList) < n {
   264  		_, commit, err := itr.Next(ctx)
   265  		if err == io.EOF {
   266  			break
   267  		} else if err != nil {
   268  			return nil, err
   269  		}
   270  
   271  		matches := true
   272  		if matchFn != nil {
   273  			matches, err = matchFn(commit)
   274  
   275  			if err != nil {
   276  				return nil, err
   277  			}
   278  		}
   279  
   280  		if matches {
   281  			commitList = append(commitList, commit)
   282  		}
   283  	}
   284  
   285  	return commitList, nil
   286  }