Skip to content

Commit

Permalink
Add new sequencer object
Browse files Browse the repository at this point in the history
This is similar to git's sequencer, but it's for re-stacking branches.

https://github.com/git/git/blob/master/sequencer.h

This object is responsible for re-stacking branches based on the planned
operations.
  • Loading branch information
draftcode committed May 20, 2024
1 parent e7dc4a4 commit 840654e
Show file tree
Hide file tree
Showing 4 changed files with 461 additions and 1 deletion.
15 changes: 14 additions & 1 deletion internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"emperror.dev/errors"
"github.com/go-git/go-git/v5"
"github.com/sirupsen/logrus"
giturls "github.com/whilp/git-urls"
)
Expand All @@ -21,16 +22,24 @@ var ErrRemoteNotFound = errors.Sentinel("this repository doesn't have a remote o
type Repo struct {
repoDir string
gitDir string
gitRepo *git.Repository
log logrus.FieldLogger
}

func OpenRepo(repoDir string, gitDir string) (*Repo, error) {
repo, err := git.PlainOpenWithOptions(repoDir, &git.PlainOpenOptions{
DetectDotGit: true,
EnableDotGitCommonDir: true,
})
if err != nil {
return nil, errors.Errorf("failed to open git repo: %v", err)
}
r := &Repo{
repoDir,
gitDir,
repo,
logrus.WithFields(logrus.Fields{"repo": filepath.Base(repoDir)}),
}

return r, nil
}

Expand All @@ -46,6 +55,10 @@ func (r *Repo) AvDir() string {
return filepath.Join(r.GitDir(), "av")
}

func (r *Repo) GoGitRepo() *git.Repository {
return r.gitRepo
}

func (r *Repo) AvTmpDir() string {
dir := filepath.Join(r.AvDir(), "tmp")
// Try to create the directory, but swallow the error since it will
Expand Down
65 changes: 65 additions & 0 deletions internal/sequencer/planner/planner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package planner

import (
"github.com/aviator-co/av/internal/git"
"github.com/aviator-co/av/internal/meta"
"github.com/aviator-co/av/internal/sequencer"
"github.com/go-git/go-git/v5/plumbing"
)

func PlanForRestack(tx meta.ReadTx, repo *git.Repo, targetBranches []plumbing.ReferenceName) ([]sequencer.RestackOp, error) {
var ret []sequencer.RestackOp
for _, br := range targetBranches {
avbr, _ := tx.Branch(br.Short())
if avbr.MergeCommit != "" {
// Skip rebasing branches that have merge commits.
continue
}
ret = append(ret, sequencer.RestackOp{
Name: br,
NewParent: plumbing.NewBranchReferenceName(avbr.Parent.Name),
NewParentIsTrunk: avbr.Parent.Trunk,
})
}
return ret, nil
}

func PlanForSync(tx meta.ReadTx, repo *git.Repo, targetBranches []plumbing.ReferenceName, syncToTrunkInsteadOfMergeCommit bool) ([]sequencer.RestackOp, error) {
var ret []sequencer.RestackOp
for _, br := range targetBranches {
avbr, _ := tx.Branch(br.Short())
if avbr.MergeCommit != "" {
// Skip rebasing branches that have merge commits.
continue
}
if !avbr.Parent.Trunk {
// Check if the parent branch is merged.
avpbr, _ := tx.Branch(avbr.Parent.Name)
if avpbr.MergeCommit != "" {
// The parent is merged. Sync to either trunk or merge commit.
trunk, _ := meta.Trunk(tx, br.Short())
var newParentHash plumbing.Hash
if syncToTrunkInsteadOfMergeCommit {
// By setting this to ZeroHash, the sequencer will sync to
// the remote tracking branch.
newParentHash = plumbing.ZeroHash
} else {
newParentHash = plumbing.NewHash(avpbr.MergeCommit)
}
ret = append(ret, sequencer.RestackOp{
Name: br,
NewParent: plumbing.NewBranchReferenceName(trunk),
NewParentIsTrunk: true,
NewParentHash: newParentHash,
})
continue
}
}
ret = append(ret, sequencer.RestackOp{
Name: br,
NewParent: plumbing.NewBranchReferenceName(avbr.Parent.Name),
NewParentIsTrunk: avbr.Parent.Trunk,
})
}
return ret, nil
}
93 changes: 93 additions & 0 deletions internal/sequencer/planner/targets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package planner

import (
"github.com/aviator-co/av/internal/git"
"github.com/aviator-co/av/internal/meta"
"github.com/go-git/go-git/v5/plumbing"
)

type TargetBranchMode int

const (
// Target all branches in the repository.
AllBranches TargetBranchMode = iota
// The current branch and all its predecessors.
CurrentAndParents
// The current branch and all its successors.
CurrentAndChildren
// Branches of the current stack. (The stack root and all its successors.)
CurrentStack
)

// GetTargetBranches returns the branches to be restacked.
//
// If `includeStackRoots` is true, the stack root branches (the immediate children of the trunk
// branches) are included in the result.
func GetTargetBranches(tx meta.ReadTx, repo *git.Repo, includeStackRoots bool, mode TargetBranchMode) ([]plumbing.ReferenceName, error) {
var ret []plumbing.ReferenceName
if mode == AllBranches {
for _, br := range tx.AllBranches() {
if !br.IsStackRoot() {
continue
}
if includeStackRoots {
ret = append(ret, plumbing.NewBranchReferenceName(br.Name))
}
for _, n := range meta.SubsequentBranches(tx, br.Name) {
ret = append(ret, plumbing.NewBranchReferenceName(n))
}
}
return ret, nil
}
if mode == CurrentAndParents {
curr, err := repo.CurrentBranchName()
if err != nil {
return nil, err
}
prevs, err := meta.PreviousBranches(tx, curr)
if err != nil {
return nil, err
}
for _, n := range prevs {
br, _ := tx.Branch(n)
if !br.IsStackRoot() || includeStackRoots {
ret = append(ret, plumbing.NewBranchReferenceName(n))
}
}
br, _ := tx.Branch(curr)
if !br.IsStackRoot() || includeStackRoots {
ret = append(ret, plumbing.NewBranchReferenceName(curr))
}
return ret, nil
}
if mode == CurrentAndChildren {
curr, err := repo.CurrentBranchName()
if err != nil {
return nil, err
}
br, _ := tx.Branch(curr)
if !br.IsStackRoot() || includeStackRoots {
ret = append(ret, plumbing.NewBranchReferenceName(curr))
}
// The rest of the branches cannot be a stack root.
for _, n := range meta.SubsequentBranches(tx, curr) {
ret = append(ret, plumbing.NewBranchReferenceName(n))
}
return ret, nil
}
curr, err := repo.CurrentBranchName()
if err != nil {
return nil, err
}
brs, err := meta.StackBranches(tx, curr)
if err != nil {
return nil, err
}
for _, n := range brs {
br, _ := tx.Branch(n)
if !br.IsStackRoot() || includeStackRoots {
ret = append(ret, plumbing.NewBranchReferenceName(n))
}
}
return ret, nil
}
Loading

0 comments on commit 840654e

Please sign in to comment.