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
9 changes: 9 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package client wires together the repos and remotes layers and exposes a unified Client.
package client

import (
Expand All @@ -23,6 +24,7 @@ import (
"golang.org/x/time/rate"
)

// Client provides the full set of cross-repo git operations backed by the GitHub API.
type Client struct {
*repos.Repos
remoteMgr *remotes.Remotes
Expand All @@ -32,6 +34,7 @@ type Client struct {
ghHTTPSAuth *sshgit.Password
}

// New constructs a Client from cfg, initialising SSH auth, HTTPS auth, the GitHub API client, and rate limiter.
func New(cfg *config.Config) (*Client, error) {
pool := trust.New()

Expand Down Expand Up @@ -145,6 +148,7 @@ func knownHostsCallback() (ssh.HostKeyCallback, error) {
return cb, nil
}

// GetLogins returns the authenticated user's login and all org logins, lowercased.
func (c *Client) GetLogins(ctx context.Context) ([]string, error) {
logins := []string{}

Expand Down Expand Up @@ -181,22 +185,27 @@ func (c *Client) GetLogins(ctx context.Context) ([]string, error) {
return logins, nil
}

// Remotes runs git remote across all dirs.
func (c *Client) Remotes(ctx context.Context, dirs []string, args ...string) error {
return c.remoteMgr.Remotes(ctx, dirs, args...)
}

// Add runs git remote add across all dirs, building the remote URL from baseURL and each dir basename.
func (c *Client) Add(ctx context.Context, dirs []string, name, baseURL string) error {
return c.remoteMgr.Add(ctx, dirs, name, baseURL)
}

// Remove runs git remote remove across all dirs.
func (c *Client) Remove(ctx context.Context, dirs []string, name string) error {
return c.remoteMgr.Remove(ctx, dirs, name)
}

// Rename runs git remote rename across all dirs.
func (c *Client) Rename(ctx context.Context, dirs []string, oldName, newName string) error {
return c.remoteMgr.Rename(ctx, dirs, oldName, newName)
}

// SetURLs runs git remote set-url across all dirs, building the URL from baseURL and each dir basename.
func (c *Client) SetURLs(ctx context.Context, dirs []string, name, baseURL string) error {
return c.remoteMgr.SetURLs(ctx, dirs, name, baseURL)
}
1 change: 1 addition & 0 deletions client/clienter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/google/go-github/github"
)

// Clienter is the interface implemented by Client, used to inject fakes in cmd tests.
type Clienter interface {
Add(ctx context.Context, dirs []string, name, baseURL string) error
Branches(ctx context.Context, repoDirs []string, args ...string) error
Expand Down
10 changes: 10 additions & 0 deletions client/context/context.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package context provides request-scoped state helpers used throughout the align client.
package context

import (
Expand All @@ -17,13 +18,16 @@ var (
excludesContextKey reposContext = 1
verboseKey = verboseContextKey{}

// ErrReposNotFoundInContext is returned by RepoMap when no repository map has been stored in the context.
ErrReposNotFoundInContext = errors.New("repos map not found in context")
)

// WithVerbose returns a copy of ctx with the verbose flag set.
func WithVerbose(ctx context.Context, verbose bool) context.Context {
return context.WithValue(ctx, verboseKey, verbose)
}

// Verbose retrieves the verbose flag from ctx, returning false if unset.
func Verbose(ctx context.Context) bool {
v := ctx.Value(verboseKey)
verbose, ok := v.(bool)
Expand All @@ -33,16 +37,19 @@ func Verbose(ctx context.Context) bool {
return verbose
}

// Repository holds the name and SSH URL of a repository to clone.
type Repository struct {
Name string
URL string
}

// WithRepos stores the given GitHub repositories as a dir→[]Repository map in ctx.
func WithRepos(ctx context.Context, repos []*github.Repository) context.Context {
repoMap := parseDirRepoMap(repos)
return context.WithValue(ctx, reposContextKey, repoMap)
}

// RepoMap retrieves the directory→repository map from ctx.
func RepoMap(ctx context.Context) (map[string][]*Repository, error) {
v := ctx.Value(reposContextKey)
repoMap, ok := v.(map[string][]*Repository)
Expand All @@ -52,10 +59,12 @@ func RepoMap(ctx context.Context) (map[string][]*Repository, error) {
return repoMap, nil
}

// WithExcludes stores the repository exclusion list in ctx.
func WithExcludes(ctx context.Context, repos []*Repository) context.Context {
return context.WithValue(ctx, excludesContextKey, repos)
}

// Excludes retrieves the exclusion list from ctx, returning nil if unset.
func Excludes(ctx context.Context) ([]*Repository, error) {
v := ctx.Value(excludesContextKey)
excludes, ok := v.([]*Repository)
Expand Down Expand Up @@ -83,6 +92,7 @@ func parseDirRepoMap(repos []*github.Repository) map[string][]*Repository {
return dirRepo
}

// RemoveExcludes returns a copy of repoMap with excluded repositories removed.
func RemoveExcludes(ctx context.Context, repoMap map[string][]*Repository) (map[string][]*Repository, error) {
newMap := map[string][]*Repository{}

Expand Down
1 change: 1 addition & 0 deletions client/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path"
)

// GetDirs returns subdirectories of baseDir that contain a .git directory.
func (c *Client) GetDirs(ctx context.Context, baseDir string) ([]string, error) {
files, err := os.ReadDir(baseDir)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions client/repos/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import (
"strings"
)

// DiffConfig controls filtering and output options for DiffRepos.
type DiffConfig struct {
IgnoreEmpty bool
IgnoreFilePrefix []string
MatchExtension []string
Args []string
}

// DiffRepos runs git diff across all dirs using cfg to filter output.
func (r *Repos) DiffRepos(ctx context.Context, dirs []string, cfg *DiffConfig) error {
args := append([]string{"diff"}, cfg.Args...)

Expand Down
7 changes: 7 additions & 0 deletions client/repos/repos.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Package repos implements git operations fanned out across multiple repository directories.
//
// The simple fan-out methods (CheckoutRepos, CommitRepos, FetchRepos, MergeRepos, PullRepos,
// PushRepos, ResetRepos, StageFiles, StashRepos) each prepend their git sub-command and
// delegate to fanOut. They carry no individual doc comments.
package repos

import (
Expand All @@ -6,12 +11,14 @@ import (
"golang.org/x/time/rate"
)

// Repos holds the scribe, GitHub client, and rate limiter shared by all operations.
type Repos struct {
scrb scribe.Scriber
ghClient *github.Client
rate *rate.Limiter
}

// New returns a Repos using the provided scribe, GitHub client, and rate limiter.
func New(scrb scribe.Scriber, ghClient *github.Client, rate *rate.Limiter) *Repos {
return &Repos{
scrb: scrb,
Expand Down
9 changes: 4 additions & 5 deletions config/file.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package config handles reading and writing the align configuration at ~/.align/config.
package config

import (
Expand All @@ -23,19 +24,17 @@ var defaultConfig = Config{
},
}

// Config represents the config file for align
// Config holds the top-level align configuration read from ~/.align/config.
type Config struct {
Github *GithubHost `yaml:"github.com"`
}

// New takes a token string and creates the most basic config capable of being
// written.
// New returns a minimal Config with the given GitHub token set.
func New(tkn string) *Config {
return &Config{Github: &GithubHost{Token: tkn}}
}

// WriteFile writes the file to the defined location for the current user, and
// returns any errors encountered doing so.
// WriteFile marshals and writes the config to ~/.align/config.
func (c *Config) WriteFile() error {
b, err := yaml.Marshal(c)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions config/github.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package config

// GithubHost represents a single host for which align has a configuration
// GithubHost holds authentication and rate-limit settings for a GitHub host.
type GithubHost struct {
Token string `yaml:"token"`
Username string `yaml:"username"`
Expand All @@ -9,7 +9,7 @@ type GithubHost struct {
Limits *Limits `yaml:"limits"`
}

// Limits represents a limits override for the client
// Limits configures GitHub API client request rates.
type Limits struct {
RequestsPerSecond int `yaml:"request_per_second"`
Burst int `yaml:"burst"`
Expand Down
14 changes: 4 additions & 10 deletions config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (
"gopkg.in/yaml.v2"
)

// ParseFromFile reads the align config file from the home directory. It returns
// any errors it encounters with parsing the file.
// ParseFromFile reads ~/.align/config, creating the config directory if absent.
func ParseFromFile() (*Config, error) {
usr, err := user.Current()
if err != nil {
Expand Down Expand Up @@ -54,9 +53,7 @@ func ParseFromFile() (*Config, error) {
return &conf, nil
}

// DirExists returns a bool and error representing whether or not a config
// directory exists for the current user, and any errors it encounters with
// statting the existence of the directory.
// DirExists reports whether the ~/.align config directory exists.
func DirExists() (bool, error) {
usr, err := user.Current()
if err != nil {
Expand All @@ -74,9 +71,7 @@ func DirExists() (bool, error) {
return true, nil
}

// FileExists returns a bool and error representing whether or not a
// config file exists for the current user, and any errors it encounters with
// statting the existence of the file.
// FileExists reports whether the ~/.align/config file exists.
func FileExists() (bool, error) {
usr, err := user.Current()
if err != nil {
Expand All @@ -94,8 +89,7 @@ func FileExists() (bool, error) {
return true, nil
}

// CreateDir creates the config directory and all necessary parent directories
// missing. It returns any error it encounters with creating the directory.
// CreateDir creates the ~/.align config directory, including any missing parents.
func CreateDir() error {
usr, err := user.Current()
if err != nil {
Expand Down
Loading