final using git
Some checks failed
CI / Test (pull_request) Failing after 6s
CI / Build (pull_request) Failing after 6s
CI / Lint (pull_request) Failing after 12s

This commit is contained in:
2025-10-15 02:33:19 -04:00
parent c5c2ee9516
commit 98e51d2ecf
6 changed files with 481 additions and 21 deletions

View File

@ -3,6 +3,7 @@ package commands
import (
"fmt"
"os"
"strings"
"git.dws.rip/DWS/onyx/internal/core"
"git.dws.rip/DWS/onyx/internal/git"
@ -15,28 +16,41 @@ import (
func NewPushCmd() *cobra.Command {
var remoteName string
var force bool
var stacked bool
cmd := &cobra.Command{
Use: "push",
Short: "Push the current workstream to the remote repository",
Long: `Push all branches in the current workstream to the remote repository.
Long: `Push the current workstream to the remote repository.
This command will push each commit's branch reference to the remote,
allowing you to share your stacked diff workflow with others or create
pull requests for each commit in the stack.`,
By default, pushes as a single branch (clean, traditional workflow).
Use --stacked to push each commit as a separate branch (advanced stacked diffs).
Single-branch mode (default):
- Pushes workstream as one branch with all commits
- Clean remote UI (1 branch per workstream)
- Perfect for traditional PR workflows
- Example: 'milestone-4' branch
Stacked mode (--stacked):
- Pushes each commit as a separate branch
- Enables stacked diff workflow (Meta/Google style)
- Each commit can have its own PR
- Example: 'onyx/workstreams/milestone-4/commit-1', 'commit-2', etc.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runPush(remoteName, force)
return runPush(remoteName, force, stacked)
},
}
cmd.Flags().StringVarP(&remoteName, "remote", "r", "origin", "Remote to push to")
cmd.Flags().BoolVarP(&force, "force", "f", false, "Force push (use with caution)")
cmd.Flags().BoolVar(&stacked, "stacked", false, "Push each commit as separate branch (stacked diffs)")
return cmd
}
// runPush executes the push command
func runPush(remoteName string, force bool) error {
func runPush(remoteName string, force, stacked bool) error {
// Get current directory
cwd, err := os.Getwd()
if err != nil {
@ -57,7 +71,10 @@ func runPush(remoteName string, force bool) error {
// Use ExecuteWithTransaction to capture state
err = core.ExecuteWithTransaction(repo, "push", "Pushed to remote", func() error {
return executePush(repo, remoteName, force)
if stacked {
return executePushStacked(repo, remoteName, force)
}
return executePushSingleBranch(repo, remoteName, force)
})
if err != nil {
@ -68,8 +85,89 @@ func runPush(remoteName string, force bool) error {
return nil
}
// executePush performs the actual push operation
func executePush(repo *core.OnyxRepository, remoteName string, force bool) error {
// executePushSingleBranch pushes the workstream as a single branch (default behavior)
func executePushSingleBranch(repo *core.OnyxRepository, remoteName string, force bool) error {
gitRepo := repo.GetGitRepo()
// 1. Validate remote exists
remoteHelper := git.NewRemoteHelper(gitRepo)
if err := remoteHelper.ValidateRemote(remoteName); err != nil {
return fmt.Errorf("remote validation failed: %w", err)
}
// 2. Get current workstream
wsManager := core.NewWorkstreamManager(repo)
currentWorkstream, err := wsManager.GetCurrentWorkstream()
if err != nil {
return fmt.Errorf("no active workstream: %w", err)
}
if currentWorkstream.IsEmpty() {
return fmt.Errorf("workstream has no commits to push")
}
// 3. Get the remote
remote, err := remoteHelper.GetRemote(remoteName)
if err != nil {
return fmt.Errorf("failed to get remote: %w", err)
}
// 4. Get the latest commit in the workstream
latestCommit, err := currentWorkstream.GetLatestCommit()
if err != nil {
return fmt.Errorf("failed to get latest commit: %w", err)
}
// 5. Build refspec to push the latest commit to a branch named after the workstream
branchName := currentWorkstream.Name
localRef := latestCommit.BranchRef
remoteRef := fmt.Sprintf("refs/heads/%s", branchName)
refSpec := config.RefSpec(fmt.Sprintf("%s:%s", localRef, remoteRef))
if force {
refSpec = config.RefSpec(fmt.Sprintf("+%s:%s", localRef, remoteRef))
}
// 6. Get authentication for the remote
remoteURL, err := remoteHelper.GetRemoteURL(remoteName)
if err != nil {
return fmt.Errorf("failed to get remote URL: %w", err)
}
authProvider := git.NewAuthProvider()
authMethod, err := authProvider.GetAuthMethod(remoteURL)
if err != nil {
// Log the error but continue - some remotes might not need auth
fmt.Fprintf(os.Stderr, "Warning: authentication not available: %v\n", err)
fmt.Fprintf(os.Stderr, "Attempting push without authentication...\n")
}
// 7. Push to remote
fmt.Printf("Pushing workstream '%s' to %s...\n", branchName, remoteName)
err = remote.Push(&gogit.PushOptions{
Auth: authMethod,
RefSpecs: []config.RefSpec{refSpec},
Progress: os.Stdout,
})
if err != nil {
if err == gogit.NoErrAlreadyUpToDate {
fmt.Println("Already up to date")
return nil
}
return fmt.Errorf("failed to push: %w", err)
}
fmt.Printf("✓ Pushed branch '%s' with %d commit(s)\n", branchName, len(currentWorkstream.Commits))
fmt.Printf("\nTo create a pull request:\n")
fmt.Printf(" gh pr create --base %s --head %s\n", currentWorkstream.BaseBranch, branchName)
return nil
}
// executePushStacked pushes each commit as a separate branch (stacked diffs)
func executePushStacked(repo *core.OnyxRepository, remoteName string, force bool) error {
gitRepo := repo.GetGitRepo()
// 1. Validate remote exists
@ -136,10 +234,25 @@ func executePush(repo *core.OnyxRepository, remoteName string, force bool) error
return fmt.Errorf("no branches to push")
}
// 5. Push to remote
// 5. Get authentication for the remote
remoteURL, err := remoteHelper.GetRemoteURL(remoteName)
if err != nil {
return fmt.Errorf("failed to get remote URL: %w", err)
}
authProvider := git.NewAuthProvider()
authMethod, err := authProvider.GetAuthMethod(remoteURL)
if err != nil {
// Log the error but continue - some remotes might not need auth
fmt.Fprintf(os.Stderr, "Warning: authentication not available: %v\n", err)
fmt.Fprintf(os.Stderr, "Attempting push without authentication...\n")
}
// 6. Push to remote
fmt.Printf("Pushing %d branch(es) to %s...\n", len(refspecs), remoteName)
err = remote.Push(&gogit.PushOptions{
Auth: authMethod,
RefSpecs: refspecs,
Progress: os.Stdout,
})
@ -154,15 +267,21 @@ func executePush(repo *core.OnyxRepository, remoteName string, force bool) error
fmt.Printf("✓ Pushed %d branch(es) successfully\n", len(refspecs))
// 6. Print summary of pushed branches
fmt.Println("\nPushed branches:")
// 7. Print summary of pushed branches
fmt.Println("\nPushed branches (stacked diffs):")
if baseBranch != "" {
fmt.Printf(" - %s (base branch)\n", baseBranch)
}
for i, commit := range currentWorkstream.Commits {
remoteBranch := fmt.Sprintf("onyx/workstreams/%s/commit-%d", currentWorkstream.Name, i+1)
fmt.Printf(" - %s: %s\n", remoteBranch, commit.Message)
commitTitle := strings.Split(commit.Message, "\n")[0]
if len(commitTitle) > 60 {
commitTitle = commitTitle[:57] + "..."
}
fmt.Printf(" - %s: %s\n", remoteBranch, commitTitle)
}
fmt.Printf("\nTip: Each branch can have its own PR for incremental review\n")
return nil
}

View File

@ -96,7 +96,21 @@ func executeSync(repo *core.OnyxRepository, repoPath, remoteName string) error {
return fmt.Errorf("workstream has no commits to sync")
}
// 3. Fetch from remote
// 3. Get authentication for the remote
remoteURL, err := remoteHelper.GetRemoteURL(remoteName)
if err != nil {
return fmt.Errorf("failed to get remote URL: %w", err)
}
authProvider := git.NewAuthProvider()
authMethod, err := authProvider.GetAuthMethod(remoteURL)
if err != nil {
// Log the error but continue - some remotes might not need auth
fmt.Fprintf(os.Stderr, "Warning: authentication not available: %v\n", err)
fmt.Fprintf(os.Stderr, "Attempting fetch without authentication...\n")
}
// 4. Fetch from remote
fmt.Printf("Fetching from %s...\n", remoteName)
remote, err := remoteHelper.GetRemote(remoteName)
if err != nil {
@ -104,6 +118,7 @@ func executeSync(repo *core.OnyxRepository, repoPath, remoteName string) error {
}
err = remote.Fetch(&gogit.FetchOptions{
Auth: authMethod,
RefSpecs: []config.RefSpec{
config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/remotes/%s/%s",
currentWorkstream.BaseBranch, remoteName, currentWorkstream.BaseBranch)),
@ -118,7 +133,7 @@ func executeSync(repo *core.OnyxRepository, repoPath, remoteName string) error {
fmt.Println("Already up to date")
}
// 4. Get the updated base branch HEAD
// 5. Get the updated base branch HEAD
gitBackend := git.NewGitBackend(gitRepo)
remoteRef := fmt.Sprintf("refs/remotes/%s/%s", remoteName, currentWorkstream.BaseBranch)
newBaseSHA, err := gitBackend.GetRef(remoteRef)
@ -126,24 +141,24 @@ func executeSync(repo *core.OnyxRepository, repoPath, remoteName string) error {
return fmt.Errorf("failed to get remote base branch: %w", err)
}
// 5. Build the commit stack from the workstream
// 6. Build the commit stack from the workstream
stack := []string{}
for _, commit := range currentWorkstream.Commits {
stack = append(stack, commit.SHA)
}
// 6. Create rebase engine with rerere support
// 7. Create rebase engine with rerere support
rebaseEngine := git.NewRebaseEngine(gitRepo, onyxPath, repoPath)
fmt.Printf("Rebasing %d commit(s) onto %s...\n", len(stack), newBaseSHA[:8])
// 7. Perform the rebase
// 8. Perform the rebase
result, err := rebaseEngine.RebaseStack(stack, newBaseSHA)
if err != nil {
return fmt.Errorf("rebase failed: %w", err)
}
// 8. Handle rebase result
// 9. Handle rebase result
if !result.Success {
if len(result.ConflictingFiles) > 0 {
// Present conflicts to user
@ -155,12 +170,12 @@ func executeSync(repo *core.OnyxRepository, repoPath, remoteName string) error {
return fmt.Errorf("rebase failed: %s", result.Message)
}
// 9. Update workstream commits with new SHAs
// 10. Update workstream commits with new SHAs
if err := updateWorkstreamCommits(repo, currentWorkstream, result.RebasedCommits); err != nil {
return fmt.Errorf("failed to update workstream: %w", err)
}
// 10. Update the base commit metadata
// 11. Update the base commit metadata
currentWorkstream.Metadata["base_commit"] = newBaseSHA
wsCollection, err := storage.LoadWorkstreams(filepath.Join(onyxPath, "workstreams.json"))
if err != nil {