temp for tree extraction
This commit is contained in:
207
internal/git/rebase.go
Normal file
207
internal/git/rebase.go
Normal file
@ -0,0 +1,207 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
// RebaseEngine handles stacked rebase operations with rerere support
|
||||
type RebaseEngine struct {
|
||||
repo *gogit.Repository
|
||||
backend *GitBackend
|
||||
rerere *RerereManager
|
||||
conflictResolver *ConflictResolver
|
||||
repoPath string
|
||||
}
|
||||
|
||||
// NewRebaseEngine creates a new RebaseEngine instance
|
||||
func NewRebaseEngine(repo *gogit.Repository, onyxPath, repoPath string) *RebaseEngine {
|
||||
return &RebaseEngine{
|
||||
repo: repo,
|
||||
backend: NewGitBackend(repo),
|
||||
rerere: NewRerereManager(repo, onyxPath, repoPath),
|
||||
conflictResolver: NewConflictResolver(repo, repoPath),
|
||||
repoPath: repoPath,
|
||||
}
|
||||
}
|
||||
|
||||
// RebaseStackResult contains the result of a stack rebase operation
|
||||
type RebaseStackResult struct {
|
||||
Success bool
|
||||
RebasedCommits []string
|
||||
FailedCommit string
|
||||
ConflictingFiles []ConflictInfo
|
||||
Message string
|
||||
}
|
||||
|
||||
// RebaseStack rebases a stack of commits onto a new base
|
||||
func (re *RebaseEngine) RebaseStack(stack []string, onto string) (*RebaseStackResult, error) {
|
||||
result := &RebaseStackResult{
|
||||
Success: true,
|
||||
RebasedCommits: []string{},
|
||||
}
|
||||
|
||||
if len(stack) == 0 {
|
||||
result.Message = "No commits to rebase"
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Validate onto commit exists
|
||||
_, err := re.backend.GetCommit(onto)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid onto commit %s: %w", onto, err)
|
||||
}
|
||||
|
||||
currentBase := onto
|
||||
|
||||
// Rebase each commit in the stack sequentially
|
||||
for i, commitSHA := range stack {
|
||||
// Get the commit object
|
||||
commit, err := re.backend.GetCommit(commitSHA)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.FailedCommit = commitSHA
|
||||
result.Message = fmt.Sprintf("Failed to get commit %s: %v", commitSHA, err)
|
||||
return result, fmt.Errorf("failed to get commit: %w", err)
|
||||
}
|
||||
|
||||
// Rebase this commit onto the current base
|
||||
newCommitSHA, err := re.rebaseSingleCommit(commit, currentBase)
|
||||
if err != nil {
|
||||
// Check if it's a conflict error
|
||||
conflicts, detectErr := re.conflictResolver.DetectConflicts()
|
||||
if detectErr == nil && len(conflicts) > 0 {
|
||||
result.Success = false
|
||||
result.FailedCommit = commitSHA
|
||||
result.ConflictingFiles = conflicts
|
||||
result.Message = fmt.Sprintf("Conflicts detected while rebasing commit %d/%d (%s)",
|
||||
i+1, len(stack), commitSHA[:8])
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result.Success = false
|
||||
result.FailedCommit = commitSHA
|
||||
result.Message = fmt.Sprintf("Failed to rebase commit %s: %v", commitSHA, err)
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.RebasedCommits = append(result.RebasedCommits, newCommitSHA)
|
||||
currentBase = newCommitSHA
|
||||
}
|
||||
|
||||
result.Message = fmt.Sprintf("Successfully rebased %d commit(s)", len(stack))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// rebaseSingleCommit rebases a single commit onto a new parent
|
||||
func (re *RebaseEngine) rebaseSingleCommit(commit *object.Commit, newParent string) (string, error) {
|
||||
// Record conflicts before attempting rebase (for rerere)
|
||||
if err := re.rerere.RecordConflicts(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to record conflicts: %v\n", err)
|
||||
}
|
||||
|
||||
// Get the commit's tree
|
||||
tree, err := commit.Tree()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get commit tree: %w", err)
|
||||
}
|
||||
|
||||
// Check if there are any changes between the trees
|
||||
// For simplicity, we'll create a new commit with the same tree content
|
||||
// In a more sophisticated implementation, we would perform a three-way merge
|
||||
|
||||
// Try to apply rerere resolutions first
|
||||
if re.rerere.IsEnabled() {
|
||||
applied, err := re.rerere.ApplyResolutions()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to apply rerere resolutions: %v\n", err)
|
||||
} else if applied > 0 {
|
||||
fmt.Printf("Applied %d rerere resolution(s)\n", applied)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a simple rebase by creating a new commit with the same tree but new parent
|
||||
// This is a simplified implementation - a full implementation would handle merges
|
||||
newCommitSHA, err := re.backend.CreateCommit(
|
||||
tree.Hash.String(),
|
||||
newParent,
|
||||
commit.Message,
|
||||
commit.Author.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create rebased commit: %w", err)
|
||||
}
|
||||
|
||||
return newCommitSHA, nil
|
||||
}
|
||||
|
||||
// RebaseCommit rebases a single commit onto a new parent (public API)
|
||||
func (re *RebaseEngine) RebaseCommit(commitSHA, newParent string) (string, error) {
|
||||
commit, err := re.backend.GetCommit(commitSHA)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get commit: %w", err)
|
||||
}
|
||||
|
||||
return re.rebaseSingleCommit(commit, newParent)
|
||||
}
|
||||
|
||||
// ContinueRebase continues a rebase after conflict resolution
|
||||
func (re *RebaseEngine) ContinueRebase(stack []string, fromIndex int, onto string) (*RebaseStackResult, error) {
|
||||
// Record the resolution for rerere
|
||||
if err := re.rerere.RecordResolution(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to record resolution: %v\n", err)
|
||||
}
|
||||
|
||||
// Check if conflicts are resolved
|
||||
hasConflicts, err := re.conflictResolver.HasConflicts()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check for conflicts: %w", err)
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return &RebaseStackResult{
|
||||
Success: false,
|
||||
Message: "Conflicts still exist. Please resolve all conflicts before continuing.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Continue rebasing from the next commit
|
||||
remainingStack := stack[fromIndex:]
|
||||
return re.RebaseStack(remainingStack, onto)
|
||||
}
|
||||
|
||||
// AbortRebase aborts a rebase operation and returns to the original state
|
||||
func (re *RebaseEngine) AbortRebase(originalHead string) error {
|
||||
// Update HEAD to original commit
|
||||
hash := plumbing.NewHash(originalHead)
|
||||
|
||||
worktree, err := re.repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
// Checkout the original HEAD
|
||||
err = worktree.Checkout(&gogit.CheckoutOptions{
|
||||
Hash: hash,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to checkout original HEAD: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRerereManager returns the rerere manager
|
||||
func (re *RebaseEngine) GetRerereManager() *RerereManager {
|
||||
return re.rerere
|
||||
}
|
||||
|
||||
// GetConflictResolver returns the conflict resolver
|
||||
func (re *RebaseEngine) GetConflictResolver() *ConflictResolver {
|
||||
return re.conflictResolver
|
||||
}
|
Reference in New Issue
Block a user