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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ The `--base-commit` flag allows you to specify a specific commit SHA to use as t

**Basic base commit usage:**
```bash
gh commit -B main -m "fix: update configs" -b a1b2c3d4e5f6789012345678901234567890abcd file.txt
gh commit -B main -m "fix: update configs" -b a1b2c3d4e5f6789012345678901234567890abcd -f file.txt
```

**With fast-forward (simulates force push):**
```bash
gh commit -B main -m "fix: update configs" -b a1b2c3d4e5f6789012345678901234567890abcd -f file.txt
gh commit -B main -m "fix: update configs" -b a1b2c3d4e5f6789012345678901234567890abcd --allow-fast-forward -f file.txt
```

### How It Works
Expand Down
57 changes: 43 additions & 14 deletions cmd/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,26 @@ var (
Type: "bool",
}

HeadRefFlag = Flag{Short: "H", Long: "head-ref", Description: "The name of the branch created with the base ref being `--branch`. Only relevant if used in conjunction with the --use-pr flag.", Type: "string"}
PrTitleFlag = Flag{Short: "T", Long: "title", Description: "The title of the PR created. Only relevant if used in conjunction with the --use-pr flag. If not specified, the PR title will be the commit message.", Type: "string"}
PrDescFlag = Flag{Short: "D", Long: "pr-description", Description: "The description of the PR created. Only relevant if used in conjunction with the --use-pr flag. If not specified, the PR title will be the commit message.", Type: "string"}
PrLabelFlag = Flag{Short: "l", Long: "label", Description: "A list of labels to add to the PR created. Only relevant if used in conjunction with the --use-pr flag. Labels can be added recursively -- i.e. -l feature -l blocked.", Type: "stringSlice"}
AllFlag = Flag{Short: "A", Long: "all", Description: "Commit all tracked files that have changed. Only relevant if the target branch is the same as the local branch.", Type: "bool", Default: "false"}
Untracked = Flag{Short: "U", Long: "untracked", Description: "Include untracked files in the commit. Only relevant if used in conjunction with the --all flag.", Type: "bool", Default: "false"}
DryRun = Flag{Short: "d", Long: "dry-run", Description: "Show which files would be committed.", Type: "bool", Default: "false"}
HeadRefFlag = Flag{Short: "H", Long: "head-ref", Description: "The name of the branch created with the base ref being `--branch`. Only relevant if used in conjunction with the --use-pr flag.", Type: "string"}
PrTitleFlag = Flag{Short: "T", Long: "title", Description: "The title of the PR created. Only relevant if used in conjunction with the --use-pr flag. If not specified, the PR title will be the commit message.", Type: "string"}
PrDescFlag = Flag{Short: "D", Long: "pr-description", Description: "The description of the PR created. Only relevant if used in conjunction with the --use-pr flag. If not specified, the PR title will be the commit message.", Type: "string"}
PrLabelFlag = Flag{Short: "l", Long: "label", Description: "A list of labels to add to the PR created. Only relevant if used in conjunction with the --use-pr flag. Labels can be added recursively -- i.e. -l feature -l blocked.", Type: "stringSlice"}
AllFlag = Flag{Short: "A", Long: "all", Description: "Commit all tracked files that have changed. Only relevant if the target branch is the same as the local branch.", Type: "bool", Default: "false"}
Untracked = Flag{Short: "U", Long: "untracked", Description: "Include untracked files in the commit. Only relevant if used in conjunction with the --all flag.", Type: "bool", Default: "false"}
DryRun = Flag{Short: "d", Long: "dry-run", Description: "Show which files would be committed.", Type: "bool", Default: "false"}
BaseCommitFlag = Flag{
Short: "b",
Long: "base-commit",
Description: "The base commit SHA to use as the base for your new commit",
Required: false,
Type: "string",
}
AllowFastForwardFlag = Flag{
Long: "allow-fast-forward",
Description: "Fast-forwards the branch to the specified commit, then creates the new commit on top",
Required: false,
Type: "bool",
}
)

var allFlags = []Flag{
Expand All @@ -73,6 +86,8 @@ var allFlags = []Flag{
AllFlag,
Untracked,
DryRun,
BaseCommitFlag,
AllowFastForwardFlag,
}

type PrSettings struct {
Expand All @@ -84,8 +99,10 @@ type PrSettings struct {
}

type CommitSettings struct {
CommitMessage string
CommitToBranch string
CommitMessage string
CommitToBranch string
BaseCommit string
AllowFastForward bool
}

type RepoSettings struct {
Expand Down Expand Up @@ -181,6 +198,8 @@ func ValidateAndConfigureRun(args []string, cmd *cobra.Command, rs *RepoSettings
usePr, _ := cmd.Flags().GetBool(UsePrFlag.Long)
branch, _ := cmd.Flags().GetString(BranchFlag.Long)
commitMessage, _ := cmd.Flags().GetString(MessageFlag.Long)
baseCommit, _ := cmd.Flags().GetString(BaseCommitFlag.Long)
allowFastForward, _ := cmd.Flags().GetBool(AllowFastForwardFlag.Long)

if usePr {
headRef, _ := cmd.Flags().GetString(HeadRefFlag.Long)
Expand Down Expand Up @@ -210,15 +229,19 @@ func ValidateAndConfigureRun(args []string, cmd *cobra.Command, rs *RepoSettings
}

commitSettings = &CommitSettings{
CommitMessage: commitMessage,
CommitToBranch: headRef,
CommitMessage: commitMessage,
CommitToBranch: headRef,
BaseCommit: baseCommit,
AllowFastForward: allowFastForward,
}

} else {
prSettings = nil
commitSettings = &CommitSettings{
CommitMessage: commitMessage,
CommitToBranch: branch,
CommitMessage: commitMessage,
CommitToBranch: branch,
BaseCommit: baseCommit,
AllowFastForward: allowFastForward,
}
}

Expand Down Expand Up @@ -303,10 +326,16 @@ var rootCmd = &cobra.Command{
return fmt.Errorf("--message and --branch are both required flags")
}

baseCommit, _ := cmd.Flags().GetString(BaseCommitFlag.Long)
allowFastForward, _ := cmd.Flags().GetBool(AllowFastForwardFlag.Long)
if allowFastForward && baseCommit == "" {
return fmt.Errorf("--%s is required if --%s is passed", BaseCommitFlag.Long, AllowFastForwardFlag.Long)
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {

cmd.SilenceUsage = true
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an error occurs here, showing the usage is too much noise, hence silencing it. Notice in PreRunE we can still show the usage as it makes sense,.

path, err := ValidateLocalGit()
if err != nil {
return err
Expand Down
33 changes: 20 additions & 13 deletions cmd/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,38 +79,45 @@ func (rn *RunSettings) Commit() error {
var err error
var commitSha string

// Create branches so we don't have to worry about those errors later
if rn.PrSettings != nil {
commitSha, err = EnsureBranchesExist(rn.PrSettings.BaseRef, rn.PrSettings.HeadRef, rn.RepoSettings)
if rn.CommitSettings.BaseCommit == "" {
// Create branches so we don't have to worry about those errors later
if rn.PrSettings != nil {
commitSha, err = EnsureBranchesExist(rn.PrSettings.BaseRef, rn.PrSettings.HeadRef, rn.RepoSettings)
} else {
commitSha, err = EnsureBranchesExist(rn.CommitSettings.CommitToBranch, "", rn.RepoSettings)
}
if err != nil {
return fmt.Errorf("ensuring branch exists: %w", err)
}
} else {
commitSha, err = EnsureBranchesExist(rn.CommitSettings.CommitToBranch, "", rn.RepoSettings)
}
if err != nil {
return err
commitSha = rn.CommitSettings.BaseCommit
}

// Commits reference trees. Trees have their own hashes. Get the hash
// of the tip of the tree that we are pushing to
currentTreeSha, err := GetTreeTip(commitSha)
if err != nil {
return err
return fmt.Errorf("getting tree tip: %w", err)
}

blobs, err := CreateBlobs(rn.FileSelection)
if err != nil {
return err
return fmt.Errorf("creating blobs: %w", err)
}

newTreeSha, err := CreateTree(currentTreeSha, blobs)
newCommit, err := CreateCommitFromTree(commitSha, newTreeSha, rn.CommitSettings.CommitMessage)
if err != nil {
return fmt.Errorf("creating tree: %w", err)
}

newCommit, err := CreateCommitFromTree(commitSha, newTreeSha, rn.CommitSettings.CommitMessage)
if err != nil {
return err
return fmt.Errorf("creating commit from tree: %w", err)
}

err = AssociateCommitWithBranch(rn.CommitSettings.CommitToBranch, newCommit)
err = AssociateCommitWithBranch(rn.CommitSettings.CommitToBranch, newCommit, rn.CommitSettings.AllowFastForward)
if err != nil {
return err
return fmt.Errorf("associating commit with branch: %w", err)
}

if rn.PrSettings != nil {
Expand Down
7 changes: 6 additions & 1 deletion cmd/gh.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,15 @@ func CreateCommitFromTree(latestCommit, treeSha, commitMessage string) (string,
return newCommitResponse.Sha, nil
}

func AssociateCommitWithBranch(branch string, commitSha string) error {
func AssociateCommitWithBranch(branch string, commitSha string, allowFastForward bool) error {
body := map[string]interface{}{
"sha": commitSha,
}

if allowFastForward {
body["force"] = true
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

marshalled, _ := json.Marshal(body)
err := client.Post(fmt.Sprintf("repos/%s/%s/git/refs/heads/%s", repo.Owner(), repo.Name(), branch), bytes.NewBuffer(marshalled), nil)
if err != nil {
Expand Down