Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: 2 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ var (
ErrSubmoduleNotExist = errors.New("submodule does not exist")
ErrRevisionNotExist = errors.New("revision does not exist")
ErrRemoteNotExist = errors.New("remote does not exist")
ErrURLNotExist = errors.New("URL does not exist")
ErrExecTimeout = errors.New("execution was timed out")
ErrNoMergeBase = errors.New("no merge based was found")
ErrNotBlob = errors.New("the entry is not a blob")
ErrDelAllNonPushURL = errors.New("will not delete all non-push URLs")
)
178 changes: 178 additions & 0 deletions repo_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,181 @@ func RepoRemoveRemote(repoPath, name string, opts ...RemoveRemoteOptions) error
func (r *Repository) RemoveRemote(name string, opts ...RemoveRemoteOptions) error {
return RepoRemoveRemote(r.path, name, opts...)
}

// RemotesListOptions contains arguments for listing remotes of the repository.
// Docs: https://git-scm.com/docs/git-remote#_commands
type RemotesListOptions struct {
// The timeout duration before giving up for each shell command execution.
// The default timeout duration will be used when not supplied.
Timeout time.Duration
}

// RepoRemotesList lists remotes of the repository in given path.
func RepoRemotesList(repoPath string, opts ...RemotesListOptions) ([]string, error) {
var opt RemotesListOptions
if len(opts) > 0 {
opt = opts[0]
}

stdout, err := NewCommand("remote").RunInDirWithTimeout(opt.Timeout, repoPath)
if err != nil {
return nil, err
}

return stdoutToStringSlice(stdout), nil
}

// RemotesList lists remotes of the repository.
func (r *Repository) RemotesList(opts ...RemotesListOptions) ([]string, error) {
return RepoRemotesList(r.path, opts...)
}

// RemoteURLGetOptions contains arguments for retrieving URL(s) of a remote of the repository.
// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emget-urlem
type RemoteURLGetOptions struct {
// False: get fetch URLs
// True: get push URLs
Push bool
// True: get all URLs (lists also non-main URLs; not related with Push)
All bool
// The timeout duration before giving up for each shell command execution.
// The default timeout duration will be used when not supplied.
Timeout time.Duration
}

// RepoRemoteURLGet retrieves URL(s) of a remote of the repository in given path.
func RepoRemoteURLGet(repoPath, name string, opts ...RemoteURLGetOptions) ([]string, error) {
var opt RemoteURLGetOptions
if len(opts) > 0 {
opt = opts[0]
}

cmd := NewCommand("remote", "get-url")
if opt.Push {
cmd.AddArgs("--push")
}
if opt.All {
cmd.AddArgs("--all")
}

stdout, err := cmd.AddArgs(name).RunInDirWithTimeout(opt.Timeout, repoPath)
if err != nil {
return nil, err
}
return stdoutToStringSlice(stdout), nil
}

// RemoteURLGet retrieves URL(s) of a remote of the repository in given path.
func (r *Repository) RemoteURLGet(name string, opts ...RemoteURLGetOptions) ([]string, error) {
return RepoRemoteURLGet(r.path, name, opts...)
}

// RemoteURLSetOptions contains arguments for setting an URL of a remote of the repository.
// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emget-urlem
type RemoteURLSetOptions struct {
// False: set fetch URLs
// True: set push URLs
Push bool
// The timeout duration before giving up for each shell command execution.
// The default timeout duration will be used when not supplied.
Timeout time.Duration
}

// RepoRemoteURLSetFirst sets first URL of the remote with given name of the repository in given path.
func RepoRemoteURLSetFirst(repoPath, name, newurl string, opts ...RemoteURLSetOptions) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the "first" come into play? Not seeing "first" is used as git args?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uhh yeah so.. repo can have like
origin urls:

  • type fetch url: foobar1
  • type push url: foobar2
  • type fetch url: foobar3
  • type fetch url: foobar4

^^^ in this case, the first fetch url is foobar1, and first push url is foobar3

In reality, this almost never happens, and even if it does, the 'first' is used elsewhere as well.

It might be a good idea to add a short 'it is almost always first, and if not, you probably don't have to worry', but don't know on how to word it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the context!

Either way I think we do not need explicitly put "first" in our function name because we are basically pass-on the value from Git CLI, and whatever it returns is what we return to the caller.

var opt RemoteURLSetOptions
if len(opts) > 0 {
opt = opts[0]
}

cmd := NewCommand("remote", "set-url")
if opt.Push {
cmd.AddArgs("--push")
}

_, err := cmd.AddArgs(name, newurl).RunInDirWithTimeout(opt.Timeout, repoPath)
if err != nil && strings.Contains(err.Error(), "No such remote") {
return ErrRemoteNotExist
}
return err
}

// RemoteURLSetFirst sets the first URL of the remote with given name of the repository.
func (r *Repository) RemoteURLSetFirst(name, newurl string, opts ...RemoteURLSetOptions) error {
return RepoRemoteURLSetFirst(r.path, name, newurl, opts...)
}

// RepoRemoteURLSetRegex sets the first URL of the remote with given name and URL regex match of the repository in given path.
func RepoRemoteURLSetRegex(repoPath, name, urlregex string, newurl string, opts ...RemoteURLSetOptions) error {
var opt RemoteURLSetOptions
if len(opts) > 0 {
opt = opts[0]
}

cmd := NewCommand("remote", "set-url")
if opt.Push {
cmd.AddArgs("--push")
}

_, err := cmd.AddArgs(name, newurl, urlregex).RunInDirWithTimeout(opt.Timeout, repoPath)
if err != nil {
if strings.Contains(err.Error(), "No such URL found") {
return ErrURLNotExist
}
if strings.Contains(err.Error(), "No such remote") {
return ErrRemoteNotExist
}
return err
}
return nil
}

// RemoteURLSetRegex sets the first URL of the remote with given name and URL regex match of the repository.
func (r *Repository) RemoteURLSetRegex(name, urlregex, newurl string, opts ...RemoteURLSetOptions) error {
return RepoRemoteURLSetRegex(r.path, name, urlregex, newurl, opts...)
}

// RepoRemoteURLAdd adds an URL to the remote with given name of the repository in given path.
func RepoRemoteURLAdd(repoPath, name, newurl string, opts ...RemoteURLSetOptions) error {
var opt RemoteURLSetOptions
if len(opts) > 0 {
opt = opts[0]
}

cmd := NewCommand("remote", "set-url", "--add")
if opt.Push {
cmd.AddArgs("--push")
}

_, err := cmd.AddArgs(name, newurl).RunInDirWithTimeout(opt.Timeout, repoPath)
return err
}

// RemoteURLAdd adds an URL to the remote with given name of the repository.
func (r *Repository) RemoteURLAdd(name, newvalue string, opts ...RemoteURLSetOptions) error {
return RepoRemoteURLAdd(r.path, name, newvalue, opts...)
}

// RepoRemoteURLDelRegex Deletes all URLs matchin regex of the remote with given name of the repository in given path.
func RepoRemoteURLDelRegex(repoPath, name, urlregex string, opts ...RemoteURLSetOptions) error {
var opt RemoteURLSetOptions
if len(opts) > 0 {
opt = opts[0]
}

cmd := NewCommand("remote", "set-url", "--delete")
if opt.Push {
cmd.AddArgs("--push")
}

_, err := cmd.AddArgs(name, urlregex).RunInDirWithTimeout(opt.Timeout, repoPath)
if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") {
return ErrDelAllNonPushURL
}
return err
}

// RemoteURLDelRegex // RepoRemoteURLDelRegex Deletes all URLs matchin regex of the remote with given name of the repository.
func (r *Repository) RemoteURLDelRegex(name, urlregex string, opts ...RemoteURLSetOptions) error {
return RepoRemoteURLDelRegex(r.path, name, urlregex, opts...)
}
75 changes: 75 additions & 0 deletions repo_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,78 @@ func TestRepository_RemoveRemote(t *testing.T) {
err = r.RemoveRemote("origin", RemoveRemoteOptions{})
assert.Equal(t, ErrRemoteNotExist, err)
}

func TestRepository_RemotesList(t *testing.T) {
r, cleanup, err := setupTempRepo()
if err != nil {
t.Fatal(err)
}
defer cleanup()

// 1 remote
remotes, err := r.RemotesList()
assert.Nil(t, err)
assert.Equal(t, []string{"origin"}, remotes)

// 2 remotes
err = r.AddRemote("t", "t")
assert.Nil(t, err)

remotes, err = r.RemotesList()
assert.Nil(t, err)
assert.Equal(t, []string{"origin", "t"}, remotes)
assert.Len(t, remotes, 2)

// 0 remotes
err = r.RemoveRemote("t")
assert.Nil(t, err)
err = r.RemoveRemote("origin")
assert.Nil(t, err)

remotes, err = r.RemotesList()
assert.Nil(t, err)
assert.Equal(t, []string{}, remotes)
assert.Len(t, remotes, 0)
}

func TestRepository_RemoteURLFamily(t *testing.T) {
r, cleanup, err := setupTempRepo()
if err != nil {
t.Fatal(err)
}
defer cleanup()

err = r.RemoteURLDelRegex("origin", ".*")
assert.Equal(t, ErrDelAllNonPushURL, err)

err = r.RemoteURLSetFirst("notexist", "t")
assert.Equal(t, ErrRemoteNotExist, err)

err = r.RemoteURLSetRegex("notexist", "t", "t")
assert.Equal(t, ErrRemoteNotExist, err)

// default origin URL is not easily testable
err = r.RemoteURLSetFirst("origin", "t")
assert.Nil(t, err)
URLs, err := r.RemoteURLGet("origin")
assert.Nil(t, err)
assert.Equal(t, []string{"t"}, URLs)

err = r.RemoteURLAdd("origin", "e")
assert.Nil(t, err)
URLs, err = r.RemoteURLGet("origin", RemoteURLGetOptions{All: true})
assert.Nil(t, err)
assert.Equal(t, []string{"t", "e"}, URLs)

err = r.RemoteURLSetRegex("origin", "e", "s")
assert.Nil(t, err)
URLs, err = r.RemoteURLGet("origin", RemoteURLGetOptions{All: true})
assert.Nil(t, err)
assert.Equal(t, []string{"t", "s"}, URLs)

err = r.RemoteURLDelRegex("origin", "t")
assert.Nil(t, err)
URLs, err = r.RemoteURLGet("origin", RemoteURLGetOptions{All: true})
assert.Nil(t, err)
assert.Equal(t, []string{"s"}, URLs)
}
10 changes: 10 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package git
import (
"fmt"
"os"
"strings"
"sync"
)

Expand Down Expand Up @@ -71,3 +72,12 @@ func concatenateError(err error, stderr string) error {
}
return fmt.Errorf("%v - %s", err, stderr)
}

// turns "a\n\list\n" to []string{"a", "list"}
func stdoutToStringSlice(stdout []byte) []string {
o := strings.TrimRight(string(stdout), "\n")
if o == "" { // empty (not {""}, len=1)
return []string{}
}
return strings.Split(o, "\n")
}