Skip to content

Commit

Permalink
feat(stack reorder): Generate plan for stack (#157)
Browse files Browse the repository at this point in the history
Getting pretty close to having an MVP on this feature.

The generated plan in the test case looks like
```
    plan_test.go:57: stack-branch one --trunk main@a50ac627fb10b6a7e19d39fde5f925ff9d2e9c29
    plan_test.go:57: pick 51f48a6  # Write file
    plan_test.go:57: pick baf3979  # Write file
    plan_test.go:57: stack-branch two --parent one
    plan_test.go:57: pick d856653  # Write fichier
    plan_test.go:57: pick 28fbcea  # Write fichier
```

The test cases for plan are a bit brittle at the moment, but I think we're still in the stage of figuring it out such that I think that's fine for now.
  • Loading branch information
twavv authored Jun 5, 2023
1 parent 11fe9a2 commit 19a33f3
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repos:
files: ".+$"
language: system
entry: |
sh -c 'go run golang.org/x/tools/cmd/goimports@latest .'
sh -c 'go run golang.org/x/tools/cmd/goimports@latest -w .'
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
Expand Down
5 changes: 1 addition & 4 deletions cmd/av/commit_amend.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,7 @@ var commitAmendCmd = &cobra.Command{
return err
}

branchesToSync, err := meta.SubsequentBranches(tx, currentBranchName)
if err != nil {
return err
}
branchesToSync := meta.SubsequentBranches(tx, currentBranchName)

err = actions.SyncStack(ctx, repo, client, tx, branchesToSync, state)
if err != nil {
Expand Down
5 changes: 1 addition & 4 deletions cmd/av/commit_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ func commitCreate(repo *git.Repo, currentBranchName string, flags struct {
return err
}

branchesToSync, err := meta.SubsequentBranches(tx, currentBranchName)
if err != nil {
return err
}
branchesToSync := meta.SubsequentBranches(tx, currentBranchName)

err = actions.SyncStack(ctx, repo, client, tx, branchesToSync, state)
if err != nil {
Expand Down
5 changes: 1 addition & 4 deletions cmd/av/stack_next.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ var stackNextCmd = &cobra.Command{
if err != nil {
return err
}
subsequentBranches, err := meta.SubsequentBranches(tx, currentBranch)
if err != nil {
return err
}
subsequentBranches := meta.SubsequentBranches(tx, currentBranch)

var branchToCheckout string
if stackNextFlags.Last {
Expand Down
5 changes: 1 addition & 4 deletions cmd/av/stack_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ var stackSubmitCmd = &cobra.Command{
if err != nil {
return err
}
subsequentBranches, err := meta.SubsequentBranches(tx, currentBranch)
if err != nil {
return err
}
subsequentBranches := meta.SubsequentBranches(tx, currentBranch)
var branchesToSubmit []string
branchesToSubmit = append(branchesToSubmit, previousBranches...)
branchesToSubmit = append(branchesToSubmit, currentBranch)
Expand Down
10 changes: 2 additions & 8 deletions cmd/av/stack_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,7 @@ base branch.
continue
}
branchesToSync = append(branchesToSync, br.Name)
nextBranches, err := meta.SubsequentBranches(tx, branchesToSync[len(branchesToSync)-1])
if err != nil {
return err
}
nextBranches := meta.SubsequentBranches(tx, branchesToSync[len(branchesToSync)-1])
branchesToSync = append(branchesToSync, nextBranches...)
}
state.Branches = branchesToSync
Expand All @@ -237,10 +234,7 @@ base branch.
return err
}
branchesToSync = append(branchesToSync, currentBranch)
nextBranches, err := meta.SubsequentBranches(tx, branchesToSync[len(branchesToSync)-1])
if err != nil {
return err
}
nextBranches := meta.SubsequentBranches(tx, branchesToSync[len(branchesToSync)-1])
branchesToSync = append(branchesToSync, nextBranches...)
state.Branches = branchesToSync
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/kr/text v0.2.0
github.com/segmentio/golines v0.11.0
github.com/shurcooL/githubv4 v0.0.0-20220115235240-a14260e6f8a2
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
Expand Down Expand Up @@ -151,7 +152,6 @@ require (
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect
github.com/securego/gosec/v2 v2.15.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect
github.com/sivchari/tenv v1.7.1 // indirect
Expand Down
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,6 @@ github.com/shurcooL/githubv4 v0.0.0-20220115235240-a14260e6f8a2 h1:82EIpiGB79OIP
github.com/shurcooL/githubv4 v0.0.0-20220115235240-a14260e6f8a2/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc=
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand Down Expand Up @@ -698,8 +696,6 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -788,8 +784,6 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand All @@ -799,8 +793,6 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
100 changes: 92 additions & 8 deletions internal/git/catfile.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package git

import (
"bufio"
"bytes"
"fmt"
"io"
"strings"

"github.com/sirupsen/logrus"

"emperror.dev/errors"
)
Expand All @@ -17,7 +21,7 @@ type GetRefsItem struct {
// The revision that was requested (exactly as given in GetRefs.Revisions)
Revision string
// The git object ID that the revision resolved to
Oid string
OID string
// The type of the git object
Type string
// The contents of the git object
Expand All @@ -33,11 +37,15 @@ func (r *Repo) GetRefs(opts *GetRefs) ([]*GetRefsItem, error) {
input.WriteString("\n")
}

res, err := r.GitStdin([]string{"cat-file", "--batch"}, input)
res, err := r.Run(&RunOpts{
Args: []string{"cat-file", "--batch"},
Stdin: input,
ExitError: true,
})
if err != nil {
return nil, err
}
output := bytes.NewBufferString(res)
output := bytes.NewBuffer(res.Stdout)
items := make([]*GetRefsItem, 0, len(opts.Revisions))
for _, rev := range opts.Revisions {
// The output *usually* looks like:
Expand All @@ -48,9 +56,9 @@ func (r *Repo) GetRefs(opts *GetRefs) ([]*GetRefsItem, error) {

item := GetRefsItem{Revision: rev}
items = append(items, &item)
_, err := fmt.Fscanf(output, "%s %s", &item.Oid, &item.Type)
_, err := fmt.Fscanf(output, "%s %s", &item.OID, &item.Type)
if err != nil {
return nil, errors.Wrap(err, "failed to read cat-file output")
return nil, errors.WrapIff(err, "failed to read cat-file output for revision %q", rev)
}

// special case where the rest of the format is ignored
Expand All @@ -69,9 +77,14 @@ func (r *Repo) GetRefs(opts *GetRefs) ([]*GetRefsItem, error) {
return nil, errors.Wrap(err, "failed to read cat-file output")
}
item.Contents = make([]byte, size)
_, err = io.ReadFull(output, item.Contents)
if err != nil {
return nil, errors.Wrap(err, "failed to read cat-file output")
if n, err := io.ReadFull(output, item.Contents); err != nil {
logrus.WithFields(logrus.Fields{
logrus.ErrorKey: err,
"revision": rev,
"size": size,
"read": n,
}).Debug("failed to read contents from cat-file output")
return nil, errors.WrapIff(err, "failed to read cat-file output for revision %q", rev)
}

// output includes a newline after the item contents
Expand All @@ -91,3 +104,74 @@ func (r *Repo) GetRefs(opts *GetRefs) ([]*GetRefsItem, error) {

return items, nil
}

type Commit struct {
Tree string
Parents []string
Author string
Committer string
Message string
}

func (c *Commit) MessageTitle() string {
if i := strings.Index(c.Message, "\n"); i >= 0 {
return c.Message[:i]
}
return c.Message
}

func ParseCommitContents(contents []byte) (Commit, error) {
// read line by line
s := bufio.NewReader(bytes.NewReader(contents))
var commit Commit
for {
line, err := s.ReadString('\n')
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return commit, err
}

if line == "\n" {
// Blank line indicates the end of the header and the start of the
// commit body.
break
}

meta, value, ok := strings.Cut(line, " ")
if !ok {
return commit, errors.New("invalid commit format")
}
switch meta {
case "tree":
commit.Tree = value
case "parent":
commit.Parents = append(commit.Parents, value)
case "author":
commit.Author = value
case "committer":
commit.Committer = value
default:
// pass
}
}
if commit.Tree == "" {
return commit, errors.New("invalid commit format: missing tree")
}
if commit.Author == "" {
return commit, errors.New("invalid commit format: missing author")
}
if commit.Committer == "" {
return commit, errors.New("invalid commit format: missing committer")
}

// The rest of the commit contents is the commit message.
// We don't need to parse it, so just read it as-is.
message, err := io.ReadAll(s)
if err != nil {
return commit, err
}
commit.Message = string(message)

return commit, nil
}
25 changes: 5 additions & 20 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ type RunOpts struct {
// If true, the standard I/Os are connected to the console, allowing the git command to
// interact with the user. Stdout and Stderr will be empty.
Interactive bool
// The standard input to the command (if any). Mutually exclusive with Interactive.
Stdin io.Reader
}

type Output struct {
Expand All @@ -123,6 +125,9 @@ func (r *Repo) Run(opts *RunOpts) (*Output, error) {
cmd.Stdout = &stdout
cmd.Stderr = &stderr
}
if opts.Stdin != nil {
cmd.Stdin = opts.Stdin
}
cmd.Env = append(os.Environ(), opts.Env...)
err := cmd.Run()
var exitError *exec.ExitError
Expand All @@ -139,26 +144,6 @@ func (r *Repo) Run(opts *RunOpts) (*Output, error) {
}, nil
}

func (r *Repo) GitStdin(args []string, stdin io.Reader) (string, error) {
cmd := exec.Command("git", args...)
cmd.Dir = r.repoDir
cmd.Stdin = stdin
r.log.Debugf("git %s", args)
out, err := cmd.Output()
if err != nil {
stderr := "<no output>"
var exitError *exec.ExitError
if errors.As(err, &exitError) {
stderr = string(exitError.Stderr)
}
r.log.Errorf("git %s failed: %s: %s", args, err, stderr)
return "", errors.Wrapf(err, "git %s", args[0])
}

// trim trailing newline
return strings.TrimSpace(string(out)), nil
}

// DetachedHead returns true if the repository is in the detached HEAD.
func (r *Repo) DetachedHead() (bool, error) {
ret, err := r.Run(&RunOpts{
Expand Down
12 changes: 5 additions & 7 deletions internal/meta/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,17 @@ func PreviousBranches(tx ReadTx, name string) ([]string, error) {

// SubsequentBranches finds all the child branches of the given branch name in
// "dependency order" (i.e., A comes before B if A is an ancestor of B).
func SubsequentBranches(tx ReadTx, name string) ([]string, error) {
// If the tree is not a straight line (which isn't explicitly supported!), the
// branches will be returned in depth-first traversal order.
func SubsequentBranches(tx ReadTx, name string) []string {
logrus.Debugf("finding subsequent branches for %q", name)
var res []string
children := Children(tx, name)
for _, child := range children {
res = append(res, child.Name)
desc, err := SubsequentBranches(tx, child.Name)
if err != nil {
return nil, err
}
res = append(res, desc...)
res = append(res, SubsequentBranches(tx, child.Name)...)
}
return res, nil
return res
}

// Trunk determines the trunk of a branch.
Expand Down
4 changes: 2 additions & 2 deletions internal/reorder/cmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ func TestState(t *testing.T) {
Head: "owouwu",
Commands: []Cmd{
StackBranchCmd{Name: "one", Trunk: "main"},
PickCmd{"abcd"},
PickCmd{"abcd", ""},
StackBranchCmd{Name: "two", Parent: "one"},
PickCmd{"efgh"},
PickCmd{"efgh", ""},
},
}

Expand Down
13 changes: 10 additions & 3 deletions internal/reorder/pick.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package reorder

import (
"fmt"
"strings"

"github.com/aviator-co/av/internal/git"
Expand All @@ -13,7 +12,8 @@ import (
// PickCmd is a command that picks a commit from the history and applies it on
// top of the current HEAD.
type PickCmd struct {
Commit string
Commit string
Comment string
}

func (p PickCmd) Execute(ctx *Context) error {
Expand Down Expand Up @@ -48,7 +48,14 @@ func (p PickCmd) Execute(ctx *Context) error {
}

func (p PickCmd) String() string {
return fmt.Sprintf("pick %s", p.Commit)
sb := strings.Builder{}
sb.WriteString("pick ")
sb.WriteString(p.Commit)
if p.Comment != "" {
sb.WriteString(" # ")
sb.WriteString(p.Comment)
}
return sb.String()
}

var _ Cmd = &PickCmd{}
Expand Down
Loading

0 comments on commit 19a33f3

Please sign in to comment.