Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package cmd
import (
"fmt"
"os"
"strings"

"github.com/pfnet-research/git-ghost/pkg/ghost"
"github.com/pfnet-research/git-ghost/pkg/ghost/git"
"github.com/pfnet-research/git-ghost/pkg/ghost/types"
"github.com/pfnet-research/git-ghost/pkg/util/errors"

Expand All @@ -43,7 +45,7 @@ func NewPushCommand() *cobra.Command {
Short: "push commits(hash1...hash2), diff(hash...current state) to your ghost repo",
Long: "push commits or diff or all to your ghost repo. If you didn't specify any subcommand, this commands works as an alias for 'push diff' command.",
Args: cobra.RangeArgs(0, 1),
Run: runPushDiffCommand(&flags),
Run: runPushCommitsCommand(&flags),
}
command.AddCommand(&cobra.Command{
Use: "commits [from-hash] [to-hash(default=HEAD)]",
Expand Down Expand Up @@ -73,6 +75,32 @@ func NewPushCommand() *cobra.Command {
return command
}

func getFirstRemoteAncestorCommit(commit string) (string, errors.GitGhostError) {
dir := globalOpts.srcDir
for {
branchNames, err := git.GetRemoteBranchesContainingCommit(dir, commit)
if err != nil {
return "", err
}
var originBranchNames []string
for _, branchName := range branchNames {
if strings.HasPrefix(branchName, fmt.Sprintf("refs/remotes/%s/", git.ORIGIN)) {
originBranchNames = append(originBranchNames, branchName)
}
}
if len(originBranchNames) > 0 {
// This commit is the first ancestor commit in the origin remote.
break
}
parentCommit, err := git.GetParentCommit(dir, commit)
if err != nil {
return "", err
}
commit = parentCommit
}
return commit, nil
}

type pushCommitsArg struct {
commitsFrom string
commitsTo string
Expand Down Expand Up @@ -111,6 +139,16 @@ func (arg pushCommitsArg) validate() errors.GitGhostError {
func runPushCommitsCommand(flags *pushFlags) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
pushArg := newPushCommitsArg(args)

// If commitsFrom is not given, find the first ancestor commit that is included in the origin remote.
if pushArg.commitsFrom == "" {
commitsFrom, err := getFirstRemoteAncestorCommit(pushArg.commitsTo)
if err != nil {
errors.LogErrorWithStack(err)
os.Exit(1)
}
pushArg.commitsFrom = commitsFrom
}
if err := pushArg.validate(); err != nil {
errors.LogErrorWithStack(err)
os.Exit(1)
Expand Down
26 changes: 26 additions & 0 deletions pkg/ghost/git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package git

import (
"bytes"
"fmt"
"os/exec"
"strings"
Expand Down Expand Up @@ -146,3 +147,28 @@ func ResetHardToBranch(dir, branch string) errors.GitGhostError {
exec.Command("git", "-C", dir, "reset", "--hard", branch),
)
}

// GetParentCommit returns the parent commit of the given commit
func GetParentCommit(dir, commit string) (string, errors.GitGhostError) {
resultBytes, err := util.JustOutputCmd(exec.Command("git", "-C", dir, "rev-parse", fmt.Sprintf("%s^", commit)))
if err != nil {
return "", errors.WithStack(gherrors.WithMessage(err, "failed to get the parent commit of a commit"))
}
return strings.TrimSuffix(string(resultBytes), "\n"), nil
}

// GetRemoteBranchesContainingCommit returns a slice of remote branch names each of which contain the given commit
func GetRemoteBranchesContainingCommit(dir, commit string) ([]string, errors.GitGhostError) {
resultBytes, err := util.JustOutputCmd(exec.Command("git", "-C", dir, "branch", "--format", "%(refname)", "--remotes", "--contains", commit))
if err != nil {
return nil, errors.WithStack(gherrors.WithMessage(err, "failed to get remote branches containing a commit"))
}
var branchNames []string
for _, line := range bytes.Split(resultBytes, []byte("\n")) {
branchName := string(line)
if len(branchName) > 0 {
branchNames = append(branchNames, branchName)
}
}
return branchNames, nil
}