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 }