Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ forge auth login --domain gitea.example.com --token abc123 --type gitea

Check what's configured with `forge auth status`.

Tokens are resolved in this order: CLI flags, environment variables (`FORGE_TOKEN`, `GITHUB_TOKEN`/`GH_TOKEN`, `GITLAB_TOKEN`, `GITEA_TOKEN`, `BITBUCKET_TOKEN`), then the config file at `~/.config/forge/config`. Set `FORGE_HOST` to override which domain is used when there's no git remote to infer it from.
Tokens are resolved in this order: CLI flags, environment variables (`FORGE_TOKEN`, `GITHUB_TOKEN`/`GH_TOKEN`, `GITLAB_TOKEN`, `GITEA_TOKEN`, `BITBUCKET_TOKEN`), then the config file at `~/.config/forge/config`. The target host is inferred from the current directory's git remote; use `--host` or `FORGE_HOST` to override it (for example `forge --host gitea.com repo list someone`).

### Configuration

Expand Down
2 changes: 1 addition & 1 deletion internal/cli/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,5 +680,5 @@ func repoContributorsCmd() *cobra.Command {
}

func domainFromFlags() string {
return resolve.DomainFromForgeType(flagForgeType)
return resolve.Domain(flagForgeType)
}
2 changes: 2 additions & 0 deletions internal/cli/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ func TestRepoEditMutuallyExclusiveVisibility(t *testing.T) {
}

func TestDomainFromFlags(t *testing.T) {
t.Chdir(t.TempDir())

tests := []struct {
forgeType string
want string
Expand Down
3 changes: 3 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
var (
flagRepo string
flagForgeType string
flagHost string
flagOutput string
flagRemote string
)
Expand All @@ -32,6 +33,7 @@ var rootCmd = &cobra.Command{
}
}
resolve.SetRemote(flagRemote)
resolve.SetHost(flagHost)
},
}

Expand All @@ -43,6 +45,7 @@ func Execute() error {
func init() {
rootCmd.PersistentFlags().StringVarP(&flagRepo, "repo", "R", "", "Select a repository (OWNER/REPO)")
rootCmd.PersistentFlags().StringVar(&flagForgeType, "forge-type", "", "Force forge type: github, gitlab, gitea, forgejo")
rootCmd.PersistentFlags().StringVar(&flagHost, "host", "", "Force forge host (e.g. gitea.com); overrides FORGE_HOST and remote detection")
rootCmd.PersistentFlags().StringVarP(&flagOutput, "output", "o", "table", "Output format: table, json, plain")
rootCmd.PersistentFlags().StringVar(&flagRemote, "remote", "", "Git remote to use when not specifying -R (default origin)")
}
Expand Down
45 changes: 33 additions & 12 deletions internal/resolve/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import (
"github.com/git-pkgs/forge/internal/config"
)

var remoteName = "origin"
var (
remoteName = "origin"
hostOverride string
)

// SetRemote sets which git remote to read when resolving the current
// repository. The CLI calls this from the --remote persistent flag.
Expand All @@ -27,6 +30,15 @@ func SetRemote(name string) {
}
}

// SetHost forces a specific forge domain, taking precedence over FORGE_HOST,
// --forge-type, and git remote detection. The CLI calls this from the --host
// persistent flag. An empty string is ignored.
func SetHost(host string) {
if host != "" {
hostOverride = host
}
}

var builders = forges.ForgeBuilders{
GitHub: ghforge.NewWithBase,
GitLab: glforge.New,
Expand All @@ -50,7 +62,7 @@ func repoFromFlag(flagRepo, flagForgeType string) (forges.Forge, string, string,
}
owner, repo := flagRepo[:lastSlash], flagRepo[lastSlash+1:]

domain := DomainFromForgeType(flagForgeType)
domain := Domain(flagForgeType)
client := newClient(domain)
f, err := forgeForDomainMaybeConfig(context.Background(), client, domain)
if err != nil {
Expand Down Expand Up @@ -221,21 +233,30 @@ func ForgeForDomain(domain string) (forges.Forge, error) {
return forgeForDomainMaybeConfig(context.Background(), client, domain)
}

// DomainFromForgeType returns the default domain for a forge type string.
// Checks FORGE_HOST first, then config default, then well-known defaults.
func DomainFromForgeType(forgeType string) string {
// Domain decides which forge host to talk to when the user supplies a bare
// owner or owner/repo argument. Precedence: --host flag, FORGE_HOST env,
// explicit --forge-type, the current directory's git remote, the config
// default forge type, then github.com.
func Domain(forgeType string) string {
if hostOverride != "" {
return hostOverride
}
if d := os.Getenv("FORGE_HOST"); d != "" {
return d
}

// If no forge type given, check config for a default
if forgeType == "" {
cfg, err := config.Load()
if err == nil && cfg != nil && cfg.Default.ForgeType != "" {
forgeType = cfg.Default.ForgeType
}
if forgeType != "" {
return defaultDomainForType(forgeType)
}
if d, _, _, err := resolveRemote(); err == nil {
return d
}
if cfg, err := config.Load(); err == nil && cfg != nil && cfg.Default.ForgeType != "" {
return defaultDomainForType(cfg.Default.ForgeType)
}
return "github.com"
}

func defaultDomainForType(forgeType string) string {
switch forgeType {
case "gitlab":
return "gitlab.com"
Expand Down
80 changes: 74 additions & 6 deletions internal/resolve/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ func TestTokenForDomainEnvFallbackForUnknownDomain(t *testing.T) {
}
}

func TestDomainFromForgeType(t *testing.T) {
func TestDomain(t *testing.T) {
t.Chdir(t.TempDir())

tests := []struct {
forgeType string
want string
Expand All @@ -137,27 +139,93 @@ func TestDomainFromForgeType(t *testing.T) {
}

for _, tt := range tests {
got := DomainFromForgeType(tt.forgeType)
got := Domain(tt.forgeType)
if got != tt.want {
t.Errorf("DomainFromForgeType(%q) = %q, want %q", tt.forgeType, got, tt.want)
t.Errorf("Domain(%q) = %q, want %q", tt.forgeType, got, tt.want)
}
}
}

func TestDomainFromForgeTypeWithForgeHost(t *testing.T) {
func TestDomainWithForgeHost(t *testing.T) {
t.Chdir(t.TempDir())
t.Setenv("FORGE_HOST", "git.example.com")

got := DomainFromForgeType("github")
got := Domain("github")
if got != "git.example.com" {
t.Errorf("expected FORGE_HOST override, got %q", got)
}

got = DomainFromForgeType("")
got = Domain("")
if got != "git.example.com" {
t.Errorf("expected FORGE_HOST override for empty type, got %q", got)
}
}

func TestDomainFallsBackToGitRemote(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not installed")
}

t.Chdir(t.TempDir())
mustGit(t, "init", "-q")
mustGit(t, "remote", "add", "origin", "https://gitea.com/someone/project.git")

got := Domain("")
if got != "gitea.com" {
t.Errorf("expected domain from git remote, got %q", got)
}
}

func TestDomainExplicitForgeTypeOverridesRemote(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not installed")
}

t.Chdir(t.TempDir())
mustGit(t, "init", "-q")
mustGit(t, "remote", "add", "origin", "https://gitea.com/someone/project.git")

got := Domain("gitlab")
if got != "gitlab.com" {
t.Errorf("expected --forge-type to override remote, got %q", got)
}
}

func TestDomainHostOverride(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not installed")
}

t.Chdir(t.TempDir())
mustGit(t, "init", "-q")
mustGit(t, "remote", "add", "origin", "https://github.com/someone/project.git")
t.Setenv("FORGE_HOST", "env.example.com")

old := hostOverride
defer func() { hostOverride = old }()
SetHost("flag.example.com")

got := Domain("gitlab")
if got != "flag.example.com" {
t.Errorf("expected --host to override everything, got %q", got)
}
}

func TestSetHost(t *testing.T) {
old := hostOverride
defer func() { hostOverride = old }()

SetHost("gitea.com")
if hostOverride != "gitea.com" {
t.Errorf("SetHost did not update hostOverride, got %q", hostOverride)
}

SetHost("")
if hostOverride != "gitea.com" {
t.Errorf("SetHost(\"\") should be a no-op, got %q", hostOverride)
}
}

func TestRemoteDefaultsToOrigin(t *testing.T) {
if remoteName != "origin" {
t.Errorf("default remote should be origin, got %q", remoteName)
Expand Down
Loading