This milestone adds comprehensive remote synchronization and stacked-diff publishing capabilities to Onyx. ## New Features ### Rebase Engine (internal/git/rebase.go) - Stacked rebase with sequential commit rebasing - Conflict detection and handling - Integration with rerere for automatic conflict resolution - Support for rebase continuation and abort operations ### Rerere Integration (internal/git/rerere.go) - Conflict resolution recording and replay - Cache location: .onx/rerere_cache - Automatic application of previous conflict resolutions - Normalized conflict pattern matching via SHA1 hashing ### Conflict Resolution UI (internal/git/conflicts.go) - Index-based conflict detection (stage 1/2/3) - Clear conflict presentation with file paths and hashes - User-friendly resolution guidance - Conflict marker extraction and analysis ### Remote Commands #### onx sync (internal/commands/sync.go) - Fetch latest changes from remote - Rebase workstream stack onto updated base branch - Automatic rerere conflict resolution - Transaction-based with full undo support - Progress reporting and clear error messages #### onx push (internal/commands/push.go) - Push all workstream branches to remote - Support for force push operations - Per-branch progress reporting - Clear summary of pushed branches ### Remote Helpers (internal/git/remote.go) - Remote validation and configuration - Support for multiple remotes with origin default - URL retrieval and remote existence checking ## Implementation Details - All operations wrapped in oplog transactions for undo support - Comprehensive error handling and user feedback - Integration with existing workstream management - CLI commands registered in cmd/onx/main.go ## Status Milestone 4 is now complete. All core synchronization and remote interaction features are implemented and tested. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
2.3 KiB
Go
107 lines
2.3 KiB
Go
package git
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/config"
|
|
)
|
|
|
|
// RemoteHelper provides utilities for working with Git remotes
|
|
type RemoteHelper struct {
|
|
repo *git.Repository
|
|
}
|
|
|
|
// NewRemoteHelper creates a new RemoteHelper instance
|
|
func NewRemoteHelper(repo *git.Repository) *RemoteHelper {
|
|
return &RemoteHelper{repo: repo}
|
|
}
|
|
|
|
// GetRemote retrieves a remote by name, defaults to "origin" if name is empty
|
|
func (rh *RemoteHelper) GetRemote(name string) (*git.Remote, error) {
|
|
if name == "" {
|
|
name = "origin"
|
|
}
|
|
|
|
remote, err := rh.repo.Remote(name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("remote '%s' not found: %w", name, err)
|
|
}
|
|
|
|
return remote, nil
|
|
}
|
|
|
|
// ListRemotes returns all configured remotes
|
|
func (rh *RemoteHelper) ListRemotes() ([]*git.Remote, error) {
|
|
remotes, err := rh.repo.Remotes()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list remotes: %w", err)
|
|
}
|
|
|
|
return remotes, nil
|
|
}
|
|
|
|
// ValidateRemote checks if a remote exists and is properly configured
|
|
func (rh *RemoteHelper) ValidateRemote(name string) error {
|
|
if name == "" {
|
|
name = "origin"
|
|
}
|
|
|
|
remote, err := rh.GetRemote(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if remote has URLs configured
|
|
cfg := remote.Config()
|
|
if len(cfg.URLs) == 0 {
|
|
return fmt.Errorf("remote '%s' has no URLs configured", name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDefaultRemoteName returns the default remote name (origin)
|
|
func (rh *RemoteHelper) GetDefaultRemoteName() string {
|
|
return "origin"
|
|
}
|
|
|
|
// GetRemoteURL returns the fetch URL for a remote
|
|
func (rh *RemoteHelper) GetRemoteURL(name string) (string, error) {
|
|
if name == "" {
|
|
name = "origin"
|
|
}
|
|
|
|
remote, err := rh.GetRemote(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cfg := remote.Config()
|
|
if len(cfg.URLs) == 0 {
|
|
return "", fmt.Errorf("remote '%s' has no URLs configured", name)
|
|
}
|
|
|
|
return cfg.URLs[0], nil
|
|
}
|
|
|
|
// GetRemoteConfig returns the configuration for a remote
|
|
func (rh *RemoteHelper) GetRemoteConfig(name string) (*config.RemoteConfig, error) {
|
|
if name == "" {
|
|
name = "origin"
|
|
}
|
|
|
|
remote, err := rh.GetRemote(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return remote.Config(), nil
|
|
}
|
|
|
|
// HasRemote checks if a remote with the given name exists
|
|
func (rh *RemoteHelper) HasRemote(name string) bool {
|
|
_, err := rh.repo.Remote(name)
|
|
return err == nil
|
|
}
|