summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-29 10:52:50 +0300
committerPaul Buetow <paul@buetow.org>2026-05-29 10:52:50 +0300
commit714e81e56b3e8d49359c8098ec85dbaac8f74d4c (patch)
tree9e91ca8cf643449a5a7edca3a4606afe024c6e26
parent4513ccec70871532b5b06aed3f9ed720abf613ed (diff)
refactor(cli): inject repo client factory for mq
-rw-r--r--internal/cli/description_sync.go23
-rw-r--r--internal/cli/repo_client_factory.go54
-rw-r--r--internal/cli/repo_client_factory_injection_test.go243
-rw-r--r--internal/cli/sync_handlers.go59
-rw-r--r--internal/codeberg/codeberg.go11
-rw-r--r--internal/forge/repo_ops.go7
-rw-r--r--internal/github/github.go10
7 files changed, 372 insertions, 35 deletions
diff --git a/internal/cli/description_sync.go b/internal/cli/description_sync.go
index 6a83c1e..cb008ca 100644
--- a/internal/cli/description_sync.go
+++ b/internal/cli/description_sync.go
@@ -8,26 +8,29 @@ import (
"path/filepath"
"strings"
- "codeberg.org/snonux/gitsyncer/internal/codeberg"
"codeberg.org/snonux/gitsyncer/internal/config"
- "codeberg.org/snonux/gitsyncer/internal/github"
+ "codeberg.org/snonux/gitsyncer/internal/forge"
)
// syncRepoDescriptions ensures both platforms have the canonical description
// Precedence: Codeberg > GitHub; if Codeberg empty and GitHub has one, use GitHub.
// knownCBDesc and knownGHDesc can be empty; the function fetches as needed.
func syncRepoDescriptions(cfg *config.Config, dryRun bool, repoName, knownCBDesc, knownGHDesc string, cache map[string]string) {
+ syncRepoDescriptionsWithFactory(cfg, dryRun, repoName, knownCBDesc, knownGHDesc, cache, cliRepoClientFactory)
+}
+
+func syncRepoDescriptionsWithFactory(cfg *config.Config, dryRun bool, repoName, knownCBDesc, knownGHDesc string, cache map[string]string, factory repoClientFactory) {
// Load orgs
ghOrg := cfg.FindGitHubOrg()
cbOrg := cfg.FindCodebergOrg()
- var ghClient *github.Client
- var cbClient *codeberg.Client
+ var ghClient forge.RepoDescriptionClient
+ var cbClient forge.RepoDescriptionClient
if ghOrg != nil {
- ghClient = github.NewClient(ghOrg.GitHubToken, ghOrg.Name)
+ ghClient = factory.NewGitHubDescriptionClient(ghOrg.GitHubToken, ghOrg.Name)
}
if cbOrg != nil {
- cbClient = codeberg.NewClient(cbOrg.CodebergToken, cbOrg.Name)
+ cbClient = factory.NewCodebergDescriptionClient(cbOrg.CodebergToken, cbOrg.Name)
}
// Get current descriptions (use known if provided)
@@ -36,10 +39,10 @@ func syncRepoDescriptions(cfg *config.Config, dryRun bool, repoName, knownCBDesc
var cbExists, ghExists bool
if cbDesc == "" && cbClient != nil {
- if repo, exists, err := cbClient.GetRepo(repoName); err == nil {
+ if description, exists, err := cbClient.GetRepoDescription(repoName); err == nil {
cbExists = exists
if exists {
- cbDesc = strings.TrimSpace(repo.Description)
+ cbDesc = strings.TrimSpace(description)
}
} else {
fmt.Printf(" Warning: Codeberg repo lookup failed: %v\n", err)
@@ -50,10 +53,10 @@ func syncRepoDescriptions(cfg *config.Config, dryRun bool, repoName, knownCBDesc
if ghClient != nil {
if ghDesc == "" || !ghExists {
- if repo, exists, err := ghClient.GetRepo(repoName); err == nil {
+ if description, exists, err := ghClient.GetRepoDescription(repoName); err == nil {
ghExists = exists
if exists {
- ghDesc = strings.TrimSpace(repo.Description)
+ ghDesc = strings.TrimSpace(description)
}
} else {
fmt.Printf(" Warning: GitHub repo lookup failed: %v\n", err)
diff --git a/internal/cli/repo_client_factory.go b/internal/cli/repo_client_factory.go
new file mode 100644
index 0000000..242a1c0
--- /dev/null
+++ b/internal/cli/repo_client_factory.go
@@ -0,0 +1,54 @@
+package cli
+
+import (
+ "codeberg.org/snonux/gitsyncer/internal/codeberg"
+ "codeberg.org/snonux/gitsyncer/internal/forge"
+ "codeberg.org/snonux/gitsyncer/internal/github"
+)
+
+type githubPublicRepoClient interface {
+ HasToken() bool
+ ListPublicRepos() ([]github.Repository, error)
+}
+
+type codebergPublicRepoClient interface {
+ ListPublicRepos() ([]codeberg.Repository, error)
+ ListUserPublicRepos() ([]codeberg.Repository, error)
+}
+
+type repoClientFactory interface {
+ NewGitHubRepoClient(token, org string) forge.RepoClient
+ NewCodebergRepoClient(token, org string) forge.RepoClient
+ NewGitHubDescriptionClient(token, org string) forge.RepoDescriptionClient
+ NewCodebergDescriptionClient(token, org string) forge.RepoDescriptionClient
+ NewGitHubPublicRepoClient(token, org string) githubPublicRepoClient
+ NewCodebergPublicRepoClient(token, org string) codebergPublicRepoClient
+}
+
+type defaultRepoClientFactory struct{}
+
+func (defaultRepoClientFactory) NewGitHubRepoClient(token, org string) forge.RepoClient {
+ return github.NewClient(token, org)
+}
+
+func (defaultRepoClientFactory) NewCodebergRepoClient(token, org string) forge.RepoClient {
+ return codeberg.NewClient(token, org)
+}
+
+func (defaultRepoClientFactory) NewGitHubDescriptionClient(token, org string) forge.RepoDescriptionClient {
+ return github.NewClient(token, org)
+}
+
+func (defaultRepoClientFactory) NewCodebergDescriptionClient(token, org string) forge.RepoDescriptionClient {
+ return codeberg.NewClient(token, org)
+}
+
+func (defaultRepoClientFactory) NewGitHubPublicRepoClient(token, org string) githubPublicRepoClient {
+ return github.NewClient(token, org)
+}
+
+func (defaultRepoClientFactory) NewCodebergPublicRepoClient(token, org string) codebergPublicRepoClient {
+ return codeberg.NewClient(token, org)
+}
+
+var cliRepoClientFactory repoClientFactory = defaultRepoClientFactory{}
diff --git a/internal/cli/repo_client_factory_injection_test.go b/internal/cli/repo_client_factory_injection_test.go
new file mode 100644
index 0000000..5a572ae
--- /dev/null
+++ b/internal/cli/repo_client_factory_injection_test.go
@@ -0,0 +1,243 @@
+package cli
+
+import (
+ "testing"
+
+ "codeberg.org/snonux/gitsyncer/internal/codeberg"
+ "codeberg.org/snonux/gitsyncer/internal/config"
+ "codeberg.org/snonux/gitsyncer/internal/forge"
+ "codeberg.org/snonux/gitsyncer/internal/github"
+)
+
+type createCall struct {
+ repoName string
+ description string
+ privateRepo bool
+}
+
+type updateCall struct {
+ repoName string
+ description string
+}
+
+type stubDescriptionRepoClient struct {
+ hasToken bool
+ descriptionByRepo map[string]string
+ updatedDescription []updateCall
+ createCalls []createCall
+}
+
+func (s *stubDescriptionRepoClient) HasToken() bool { return s.hasToken }
+
+func (s *stubDescriptionRepoClient) RepoExists(repoName string) (bool, error) {
+ _, exists := s.descriptionByRepo[repoName]
+ return exists, nil
+}
+
+func (s *stubDescriptionRepoClient) CreateRepo(repoName, description string, private bool) error {
+ s.createCalls = append(s.createCalls, createCall{
+ repoName: repoName,
+ description: description,
+ privateRepo: private,
+ })
+ return nil
+}
+
+func (s *stubDescriptionRepoClient) DeleteRepo(_ string) error {
+ return nil
+}
+
+func (s *stubDescriptionRepoClient) GetRepoDescription(repoName string) (string, bool, error) {
+ description, exists := s.descriptionByRepo[repoName]
+ return description, exists, nil
+}
+
+func (s *stubDescriptionRepoClient) UpdateRepoDescription(repoName, description string) error {
+ s.updatedDescription = append(s.updatedDescription, updateCall{
+ repoName: repoName,
+ description: description,
+ })
+ if s.descriptionByRepo == nil {
+ s.descriptionByRepo = map[string]string{}
+ }
+ s.descriptionByRepo[repoName] = description
+ return nil
+}
+
+type stubGitHubPublicRepoClient struct {
+ hasToken bool
+ repos []github.Repository
+}
+
+func (s *stubGitHubPublicRepoClient) HasToken() bool {
+ return s.hasToken
+}
+
+func (s *stubGitHubPublicRepoClient) ListPublicRepos() ([]github.Repository, error) {
+ return s.repos, nil
+}
+
+type stubCodebergPublicRepoClient struct {
+ orgRepos []codeberg.Repository
+ userRepos []codeberg.Repository
+}
+
+func (s *stubCodebergPublicRepoClient) ListPublicRepos() ([]codeberg.Repository, error) {
+ return s.orgRepos, nil
+}
+
+func (s *stubCodebergPublicRepoClient) ListUserPublicRepos() ([]codeberg.Repository, error) {
+ return s.userRepos, nil
+}
+
+type stubRepoClientFactory struct {
+ githubRepoClient forge.RepoClient
+ codebergRepoClient forge.RepoClient
+ githubDescriptionClient forge.RepoDescriptionClient
+ codebergDescClient forge.RepoDescriptionClient
+ githubPublicClient githubPublicRepoClient
+ codebergPublicClient codebergPublicRepoClient
+
+ githubRepoCalls int
+ codebergRepoCalls int
+ githubDescCalls int
+ codebergDescCalls int
+ githubPublicRepoCalls int
+ codebergPublicCalls int
+}
+
+func (s *stubRepoClientFactory) NewGitHubRepoClient(_, _ string) forge.RepoClient {
+ s.githubRepoCalls++
+ return s.githubRepoClient
+}
+
+func (s *stubRepoClientFactory) NewCodebergRepoClient(_, _ string) forge.RepoClient {
+ s.codebergRepoCalls++
+ return s.codebergRepoClient
+}
+
+func (s *stubRepoClientFactory) NewGitHubDescriptionClient(_, _ string) forge.RepoDescriptionClient {
+ s.githubDescCalls++
+ return s.githubDescriptionClient
+}
+
+func (s *stubRepoClientFactory) NewCodebergDescriptionClient(_, _ string) forge.RepoDescriptionClient {
+ s.codebergDescCalls++
+ return s.codebergDescClient
+}
+
+func (s *stubRepoClientFactory) NewGitHubPublicRepoClient(_, _ string) githubPublicRepoClient {
+ s.githubPublicRepoCalls++
+ return s.githubPublicClient
+}
+
+func (s *stubRepoClientFactory) NewCodebergPublicRepoClient(_, _ string) codebergPublicRepoClient {
+ s.codebergPublicCalls++
+ return s.codebergPublicClient
+}
+
+func TestSyncRepoDescriptionsWithFactory_UsesInjectedClients(t *testing.T) {
+ t.Parallel()
+
+ githubClient := &stubDescriptionRepoClient{
+ hasToken: true,
+ descriptionByRepo: map[string]string{"demo": "from github"},
+ }
+ codebergClient := &stubDescriptionRepoClient{
+ hasToken: true,
+ descriptionByRepo: map[string]string{"demo": "from codeberg"},
+ }
+ factory := &stubRepoClientFactory{
+ githubDescriptionClient: githubClient,
+ codebergDescClient: codebergClient,
+ }
+
+ cfg := &config.Config{
+ Organizations: []config.Organization{
+ {
+ Host: "git@github.com",
+ Name: "acme",
+ GitHubToken: "gh-token",
+ CodebergToken: "",
+ },
+ {
+ Host: "git@codeberg.org",
+ Name: "acme",
+ CodebergToken: "cb-token",
+ },
+ },
+ }
+ cache := map[string]string{}
+
+ syncRepoDescriptionsWithFactory(cfg, false, "demo", "", "", cache, factory)
+
+ if factory.githubDescCalls != 1 || factory.codebergDescCalls != 1 {
+ t.Fatalf("expected one injected description client creation per forge, got github=%d codeberg=%d", factory.githubDescCalls, factory.codebergDescCalls)
+ }
+
+ if len(codebergClient.updatedDescription) != 0 {
+ t.Fatalf("expected no Codeberg update when it already has canonical description, got %d updates", len(codebergClient.updatedDescription))
+ }
+
+ if len(githubClient.updatedDescription) != 1 {
+ t.Fatalf("expected GitHub description to be updated once, got %d updates", len(githubClient.updatedDescription))
+ }
+ if githubClient.updatedDescription[0].description != "from codeberg" {
+ t.Fatalf("github description update = %q, want %q", githubClient.updatedDescription[0].description, "from codeberg")
+ }
+
+ if cache["demo"] != "from codeberg" {
+ t.Fatalf("cache description = %q, want %q", cache["demo"], "from codeberg")
+ }
+}
+
+func TestCreateRepoHelpersWithFactory_UseInjectedCreateClients(t *testing.T) {
+ t.Parallel()
+
+ githubClient := &stubDescriptionRepoClient{hasToken: true}
+ codebergClient := &stubDescriptionRepoClient{hasToken: true}
+ factory := &stubRepoClientFactory{
+ githubRepoClient: githubClient,
+ codebergRepoClient: codebergClient,
+ }
+
+ cfg := &config.Config{
+ Organizations: []config.Organization{
+ {
+ Host: "git@github.com",
+ Name: "acme",
+ GitHubToken: "gh-token",
+ },
+ {
+ Host: "git@codeberg.org",
+ Name: "acme",
+ CodebergToken: "cb-token",
+ },
+ },
+ }
+
+ if err := createGitHubRepoIfNeededWithFactory(cfg, "demo", factory); err != nil {
+ t.Fatalf("createGitHubRepoIfNeededWithFactory() error = %v", err)
+ }
+ if err := createCodebergRepoIfNeededWithFactory(cfg, "demo", factory); err != nil {
+ t.Fatalf("createCodebergRepoIfNeededWithFactory() error = %v", err)
+ }
+
+ if factory.githubRepoCalls != 1 || factory.codebergRepoCalls != 1 {
+ t.Fatalf("expected one injected create client per forge, got github=%d codeberg=%d", factory.githubRepoCalls, factory.codebergRepoCalls)
+ }
+
+ if len(githubClient.createCalls) != 1 {
+ t.Fatalf("expected one GitHub create call, got %d", len(githubClient.createCalls))
+ }
+ if len(codebergClient.createCalls) != 1 {
+ t.Fatalf("expected one Codeberg create call, got %d", len(codebergClient.createCalls))
+ }
+
+ if githubClient.createCalls[0].description != "Mirror of demo" {
+ t.Fatalf("github create description = %q, want %q", githubClient.createCalls[0].description, "Mirror of demo")
+ }
+ if codebergClient.createCalls[0].description != "Mirror of demo" {
+ t.Fatalf("codeberg create description = %q, want %q", codebergClient.createCalls[0].description, "Mirror of demo")
+ }
+}
diff --git a/internal/cli/sync_handlers.go b/internal/cli/sync_handlers.go
index e0c62de..df0b6b0 100644
--- a/internal/cli/sync_handlers.go
+++ b/internal/cli/sync_handlers.go
@@ -7,6 +7,7 @@ import (
"codeberg.org/snonux/gitsyncer/internal/codeberg"
"codeberg.org/snonux/gitsyncer/internal/config"
+ "codeberg.org/snonux/gitsyncer/internal/forge"
"codeberg.org/snonux/gitsyncer/internal/github"
"codeberg.org/snonux/gitsyncer/internal/state"
"codeberg.org/snonux/gitsyncer/internal/sync"
@@ -95,13 +96,13 @@ func HandleSyncAll(cfg *config.Config, flags *Flags) int {
}
// Initialize GitHub client if needed
- var githubClient *github.Client
+ var githubClient forge.RepoClient
if flags.CreateGitHubRepos {
githubClient = initGitHubClient(cfg)
}
// Initialize Codeberg client if needed
- var codebergClient *codeberg.Client
+ var codebergClient forge.RepoClient
if flags.CreateCodebergRepos {
codebergClient = initCodebergClient(cfg)
}
@@ -188,7 +189,7 @@ func HandleSyncCodebergPublic(cfg *config.Config, flags *Flags) int {
fmt.Printf("Fetching public repositories from Codeberg user/org: %s...\n", codebergOrg.Name)
- client := codeberg.NewClient(codebergOrg.CodebergToken, codebergOrg.Name)
+ client := cliRepoClientFactory.NewCodebergPublicRepoClient(codebergOrg.CodebergToken, codebergOrg.Name)
// Try fetching as organization first, then as user
repos, err := client.ListPublicRepos()
@@ -245,7 +246,7 @@ func HandleSyncGitHubPublic(cfg *config.Config, flags *Flags) int {
fmt.Printf("Fetching public repositories from GitHub user/org: %s...\n", githubOrg.Name)
- client := github.NewClient(githubOrg.GitHubToken, githubOrg.Name)
+ client := cliRepoClientFactory.NewGitHubPublicRepoClient(githubOrg.GitHubToken, githubOrg.Name)
if !client.HasToken() {
fmt.Println("ERROR: GitHub token required to list repositories")
fmt.Println("Set GITHUB_TOKEN env var or create ~/.gitsyncer_github_token file")
@@ -293,13 +294,17 @@ func HandleSyncGitHubPublic(cfg *config.Config, flags *Flags) int {
// Helper functions
func createGitHubRepoIfNeeded(cfg *config.Config, repoName string) error {
+ return createGitHubRepoIfNeededWithFactory(cfg, repoName, cliRepoClientFactory)
+}
+
+func createGitHubRepoIfNeededWithFactory(cfg *config.Config, repoName string, factory repoClientFactory) error {
githubOrg := cfg.FindGitHubOrg()
if githubOrg == nil {
return nil
}
fmt.Printf("Initializing GitHub client for organization: %s\n", githubOrg.Name)
- githubClient := github.NewClient(githubOrg.GitHubToken, githubOrg.Name)
+ githubClient := factory.NewGitHubRepoClient(githubOrg.GitHubToken, githubOrg.Name)
if !githubClient.HasToken() {
fmt.Println("Warning: No GitHub token found. Cannot create repository.")
return nil
@@ -310,13 +315,17 @@ func createGitHubRepoIfNeeded(cfg *config.Config, repoName string) error {
}
func createCodebergRepoIfNeeded(cfg *config.Config, repoName string) error {
+ return createCodebergRepoIfNeededWithFactory(cfg, repoName, cliRepoClientFactory)
+}
+
+func createCodebergRepoIfNeededWithFactory(cfg *config.Config, repoName string, factory repoClientFactory) error {
codebergOrg := cfg.FindCodebergOrg()
if codebergOrg == nil {
return nil
}
fmt.Printf("Initializing Codeberg client for organization: %s\n", codebergOrg.Name)
- codebergClient := codeberg.NewClient(codebergOrg.CodebergToken, codebergOrg.Name)
+ codebergClient := factory.NewCodebergRepoClient(codebergOrg.CodebergToken, codebergOrg.Name)
if !codebergClient.HasToken() {
fmt.Println("Warning: No Codeberg token found. Cannot create repository.")
return nil
@@ -326,7 +335,11 @@ func createCodebergRepoIfNeeded(cfg *config.Config, repoName string) error {
return codebergClient.CreateRepo(repoName, fmt.Sprintf("Mirror of %s", repoName), false)
}
-func initGitHubClient(cfg *config.Config) *github.Client {
+func initGitHubClient(cfg *config.Config) forge.RepoClient {
+ return initGitHubClientWithFactory(cfg, cliRepoClientFactory)
+}
+
+func initGitHubClientWithFactory(cfg *config.Config, factory repoClientFactory) forge.RepoClient {
githubOrg := cfg.FindGitHubOrg()
if githubOrg == nil {
fmt.Println("Warning: --create-github-repos specified but no GitHub organization found in config")
@@ -334,7 +347,7 @@ func initGitHubClient(cfg *config.Config) *github.Client {
}
fmt.Printf("Initializing GitHub client for organization: %s\n", githubOrg.Name)
- githubClient := github.NewClient(githubOrg.GitHubToken, githubOrg.Name)
+ githubClient := factory.NewGitHubRepoClient(githubOrg.GitHubToken, githubOrg.Name)
if !githubClient.HasToken() {
fmt.Println("Warning: No GitHub token found. Cannot create repositories.")
return nil
@@ -344,12 +357,16 @@ func initGitHubClient(cfg *config.Config) *github.Client {
return githubClient
}
-func createRepoWithClient(client *github.Client, repoName, description string) error {
+func createRepoWithClient(client forge.RepoClient, repoName, description string) error {
fmt.Printf("Checking/creating GitHub repository %s...\n", repoName)
return client.CreateRepo(repoName, description, false)
}
-func initCodebergClient(cfg *config.Config) *codeberg.Client {
+func initCodebergClient(cfg *config.Config) forge.RepoClient {
+ return initCodebergClientWithFactory(cfg, cliRepoClientFactory)
+}
+
+func initCodebergClientWithFactory(cfg *config.Config, factory repoClientFactory) forge.RepoClient {
codebergOrg := cfg.FindCodebergOrg()
if codebergOrg == nil {
fmt.Println("Warning: --create-codeberg-repos specified but no Codeberg organization found in config")
@@ -357,7 +374,7 @@ func initCodebergClient(cfg *config.Config) *codeberg.Client {
}
fmt.Printf("Initializing Codeberg client for organization: %s\n", codebergOrg.Name)
- codebergClient := codeberg.NewClient(codebergOrg.CodebergToken, codebergOrg.Name)
+ codebergClient := factory.NewCodebergRepoClient(codebergOrg.CodebergToken, codebergOrg.Name)
if !codebergClient.HasToken() {
fmt.Println("Warning: No Codeberg token found. Cannot create repositories.")
return nil
@@ -504,13 +521,9 @@ func printDeleteScript(syncer *sync.Syncer) {
func syncCodebergRepos(cfg *config.Config, flags *Flags, repos []codeberg.Repository, repoNames []string) int {
// Initialize GitHub client if needed
- var githubClient github.Client
- var hasGithubClient bool
+ var githubClient forge.RepoClient
if flags.CreateGitHubRepos {
- if client := initGitHubClient(cfg); client != nil {
- githubClient = *client
- hasGithubClient = true
- }
+ githubClient = initGitHubClient(cfg)
}
fmt.Printf("\nStarting sync of %d repositories...\n", len(repoNames))
@@ -532,7 +545,7 @@ func syncCodebergRepos(cfg *config.Config, flags *Flags, repos []codeberg.Reposi
}
// Create GitHub repo if needed
- if hasGithubClient && flags.CreateGitHubRepos {
+ if githubClient != nil && flags.CreateGitHubRepos {
codebergRepo := repoMap[repoName]
description := codebergRepo.Description
if description == "" {
@@ -575,13 +588,9 @@ func syncCodebergRepos(cfg *config.Config, flags *Flags, repos []codeberg.Reposi
func syncGitHubRepos(cfg *config.Config, flags *Flags, repos []github.Repository, repoNames []string) int {
// Initialize Codeberg client if needed
- var codebergClient codeberg.Client
- var hasCodebergClient bool
+ var codebergClient forge.RepoClient
if flags.CreateCodebergRepos {
- if client := initCodebergClient(cfg); client != nil {
- codebergClient = *client
- hasCodebergClient = true
- }
+ codebergClient = initCodebergClient(cfg)
}
fmt.Printf("\nStarting sync of %d repositories...\n", len(repoNames))
@@ -603,7 +612,7 @@ func syncGitHubRepos(cfg *config.Config, flags *Flags, repos []github.Repository
}
// Create Codeberg repo if needed
- if hasCodebergClient && flags.CreateCodebergRepos {
+ if codebergClient != nil && flags.CreateCodebergRepos {
githubRepo := repoMap[repoName]
description := githubRepo.Description
if description == "" {
diff --git a/internal/codeberg/codeberg.go b/internal/codeberg/codeberg.go
index 0d587de..f881be9 100644
--- a/internal/codeberg/codeberg.go
+++ b/internal/codeberg/codeberg.go
@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path/filepath"
+ "strings"
"time"
"codeberg.org/snonux/gitsyncer/internal/forge"
@@ -39,6 +40,7 @@ type Client struct {
}
var _ forge.RepoClient = (*Client)(nil)
+var _ forge.RepoDescriptionClient = (*Client)(nil)
// NewClient creates a new Codeberg API client
func NewClient(token, org string) *Client {
@@ -111,6 +113,15 @@ func (c *Client) GetRepo(repoName string) (Repository, bool, error) {
return repo, true, nil
}
+// GetRepoDescription fetches a repository description.
+func (c *Client) GetRepoDescription(repoName string) (string, bool, error) {
+ repo, exists, err := c.GetRepo(repoName)
+ if err != nil || !exists {
+ return "", exists, err
+ }
+ return strings.TrimSpace(repo.Description), true, nil
+}
+
// UpdateRepoDescription updates a repository description on Codeberg
func (c *Client) UpdateRepoDescription(repoName, description string) error {
if !c.HasToken() {
diff --git a/internal/forge/repo_ops.go b/internal/forge/repo_ops.go
index 8334a16..6459ec6 100644
--- a/internal/forge/repo_ops.go
+++ b/internal/forge/repo_ops.go
@@ -10,6 +10,13 @@ type RepoClient interface {
DeleteRepo(repoName string) error
}
+// RepoDescriptionClient defines shared description operations across forges.
+type RepoDescriptionClient interface {
+ RepoClient
+ GetRepoDescription(repoName string) (string, bool, error)
+ UpdateRepoDescription(repoName, description string) error
+}
+
// RepoExistsFunc checks if a repository exists.
type RepoExistsFunc func(repoName string) (bool, error)
diff --git a/internal/github/github.go b/internal/github/github.go
index e48fc50..66e0f67 100644
--- a/internal/github/github.go
+++ b/internal/github/github.go
@@ -21,6 +21,7 @@ type Client struct {
}
var _ forge.RepoClient = (*Client)(nil)
+var _ forge.RepoDescriptionClient = (*Client)(nil)
// NewClient creates a new GitHub API client
func NewClient(token, org string) *Client {
@@ -231,6 +232,15 @@ func (c *Client) GetRepo(repoName string) (Repository, bool, error) {
return repo, true, nil
}
+// GetRepoDescription fetches a repository description.
+func (c *Client) GetRepoDescription(repoName string) (string, bool, error) {
+ repo, exists, err := c.GetRepo(repoName)
+ if err != nil || !exists {
+ return "", exists, err
+ }
+ return strings.TrimSpace(repo.Description), true, nil
+}
+
// UpdateRepoDescription updates the repository description
func (c *Client) UpdateRepoDescription(repoName, description string) error {
if c.token == "" {