From 46129ec7c48fda9d5161ef5732ea8880c24d0897 Mon Sep 17 00:00:00 2001 From: Hardy Ferentschik Date: Fri, 10 Jan 2020 20:45:29 +0100 Subject: [PATCH] feat: Handling git credentials via credential helper fixes #5772 --- .../git/credentials/step_git_credentials.go | 58 ++-- .../step/verify/step_verify_environments.go | 15 +- pkg/gits/credentialhelper/git_credential.go | 122 ++++++++ .../credentialhelper/git_credentialhelper.go | 111 +++++++ .../git_credentialhelper_test.go | 215 ++++++++++++++ pkg/gits/git_cli.go | 12 + pkg/gits/git_cli_test.go | 27 +- pkg/gits/git_fake.go | 4 + pkg/gits/git_local.go | 5 + pkg/gits/helpers.go | 149 ++++++++-- pkg/gits/helpers_test.go | 274 ++---------------- pkg/gits/interface.go | 1 + pkg/gits/mocks/gitter.go | 57 ++++ pkg/gits/provider_fake.go | 5 + 14 files changed, 760 insertions(+), 295 deletions(-) create mode 100644 pkg/gits/credentialhelper/git_credential.go create mode 100644 pkg/gits/credentialhelper/git_credentialhelper.go create mode 100644 pkg/gits/credentialhelper/git_credentialhelper_test.go diff --git a/pkg/cmd/step/git/credentials/step_git_credentials.go b/pkg/cmd/step/git/credentials/step_git_credentials.go index 8727c9fbf1..71354516ab 100644 --- a/pkg/cmd/step/git/credentials/step_git_credentials.go +++ b/pkg/cmd/step/git/credentials/step_git_credentials.go @@ -4,12 +4,12 @@ import ( "bytes" "fmt" "io/ioutil" - "net/url" "os" "path/filepath" "github.com/jenkins-x/jx/pkg/auth" "github.com/jenkins-x/jx/pkg/cmd/opts/step" + "github.com/jenkins-x/jx/pkg/gits/credentialhelper" "github.com/pkg/errors" "github.com/jenkins-x/jx/pkg/cmd/helper" @@ -37,6 +37,7 @@ type StepGitCredentialsOptions struct { GitHubAppOwner string GitKind string CredentialsSecret string + CredentialHelper bool } type credentials struct { @@ -57,6 +58,9 @@ var ( # generate the Git credentials to a output file jx step git credentials -o /tmp/mycreds + + # respond to a gitcredentials request + jx step git credentials --credential-helper `) ) @@ -82,6 +86,7 @@ func NewCmdStepGitCredentials(commonOpts *opts.CommonOptions) *cobra.Command { cmd.Flags().StringVarP(&options.GitHubAppOwner, optionGitHubAppOwner, "g", "", "The owner (organisation or user name) if using GitHub App based tokens") cmd.Flags().StringVarP(&options.CredentialsSecret, "credentials-secret", "s", "", "The secret name to read the credentials from") cmd.Flags().StringVarP(&options.GitKind, "git-kind", "", "", "The git kind. e.g. github, bitbucketserver etc") + cmd.Flags().BoolVar(&options.CredentialHelper, "credential-helper", false, "respond to a gitcredentials request") return cmd } @@ -108,13 +113,12 @@ func (o *StepGitCredentialsOptions) Run() error { return errors.Wrapf(err, "failed to find secret '%s' in namespace '%s'", o.CredentialsSecret, ns) } - creds := credentials{ - user: string(secret.Data["user"]), - password: string(secret.Data["token"]), - serviceURL: string(secret.Data["url"]), + creds, err := credentialhelper.CreateGitCredentialFromURL(string(secret.Data["url"]), string(secret.Data["token"]), string(secret.Data["user"])) + if err != nil { + return errors.Wrap(err, "failed to create git credentials") } - return o.createGitCredentialsFile(outFile, []credentials{creds}) + return o.createGitCredentialsFile(outFile, []credentialhelper.GitCredential{creds}) } gha, err := o.IsGitHubAppMode() @@ -144,19 +148,38 @@ func (o *StepGitCredentialsOptions) Run() error { if err != nil { return errors.Wrap(err, "creating git credentials") } + + if o.CredentialHelper { + helper, err := credentialhelper.CreateGitCredentialsHelper(os.Stdin, os.Stdout, credentials) + if err != nil { + return errors.Wrap(err, "unable to create git credential helper") + } + // the credential helper operation (get|store|remove) is passed as last argument to the helper + err = helper.Run(os.Args[len(os.Args)-1]) + if err != nil { + return errors.Wrap(err, "error responding to git credential request") + } + return nil + } + + outFile, err = o.determineOutputFile() + if err != nil { + return err + } + return o.createGitCredentialsFile(outFile, credentials) } -func (o *StepGitCredentialsOptions) GitCredentialsFileData(credentials []credentials) ([]byte, error) { +// GitCredentialsFileData takes the given git credentials and writes them into a byte array. +func (o *StepGitCredentialsOptions) GitCredentialsFileData(credentials []credentialhelper.GitCredential) ([]byte, error) { var buffer bytes.Buffer - for _, creds := range credentials { - u, err := url.Parse(creds.serviceURL) + for _, gitCredential := range credentials { + u, err := gitCredential.URL() if err != nil { - log.Logger().Warnf("Ignoring invalid git service URL %q", creds.serviceURL) + log.Logger().Warnf("Ignoring incomplete git credentials %q", gitCredential) continue } - u.User = url.UserPassword(creds.user, creds.password) buffer.WriteString(u.String() + "\n") // Write the https protocol in case only https is set for completeness if u.Scheme == "http" { @@ -185,7 +208,7 @@ func (o *StepGitCredentialsOptions) determineOutputFile() (string, error) { } // CreateGitCredentialsFileFromUsernameAndToken creates the git credentials into file using the provided username, token & url -func (o *StepGitCredentialsOptions) createGitCredentialsFile(fileName string, credentials []credentials) error { +func (o *StepGitCredentialsOptions) createGitCredentialsFile(fileName string, credentials []credentialhelper.GitCredential) error { data, err := o.GitCredentialsFileData(credentials) if err != nil { return errors.Wrap(err, "creating git credentials") @@ -199,8 +222,8 @@ func (o *StepGitCredentialsOptions) createGitCredentialsFile(fileName string, cr } // CreateGitCredentialsFromAuthService creates the git credentials using the auth config service -func (o *StepGitCredentialsOptions) CreateGitCredentialsFromAuthService(authConfigSvc auth.ConfigService) ([]credentials, error) { - var credentialList []credentials +func (o *StepGitCredentialsOptions) CreateGitCredentialsFromAuthService(authConfigSvc auth.ConfigService) ([]credentialhelper.GitCredential, error) { + var credentialList []credentialhelper.GitCredential cfg := authConfigSvc.Config() if cfg == nil { @@ -236,10 +259,9 @@ func (o *StepGitCredentialsOptions) CreateGitCredentialsFromAuthService(authConf continue } - credential := credentials{ - user: username, - password: password, - serviceURL: server.URL, + credential, err := credentialhelper.CreateGitCredentialFromURL(server.URL, username, password) + if err != nil { + return nil, errors.Wrapf(err, "invalid git auth information") } credentialList = append(credentialList, credential) diff --git a/pkg/cmd/step/verify/step_verify_environments.go b/pkg/cmd/step/verify/step_verify_environments.go index 5a66109c6a..50d8ab6f58 100644 --- a/pkg/cmd/step/verify/step_verify_environments.go +++ b/pkg/cmd/step/verify/step_verify_environments.go @@ -479,12 +479,19 @@ func (o *StepVerifyEnvironmentsOptions) pushDevEnvironmentUpdates(environmentRep } } - userDetails := provider.UserAuth() - authenticatedPushURL, err := gitter.CreateAuthenticatedURL(environmentRepo.CloneURL, &userDetails) + remoteURL, err := gits.AddUserToURL(environmentRepo.CloneURL, provider.CurrentUsername()) if err != nil { - return errors.Wrapf(err, "failed to create push URL for %s", environmentRepo.CloneURL) + return errors.Wrapf(err, "unable to add username to git url %s", environmentRepo.CloneURL) } - err = gitter.Push(localRepoDir, authenticatedPushURL, true, "master") + remoteName, err := gits.GetRemoteForURL(localRepoDir, remoteURL, gitter) + if err != nil { + return errors.Wrapf(err, "cannot determine remote name for %s", environmentRepo.CloneURL) + } + if remoteName == "" { + return errors.Wrapf(err, "no remote configured for %s", environmentRepo.CloneURL) + } + + err = gitter.Push(localRepoDir, remoteName, true, "master") if err != nil { return errors.Wrapf(err, "unable to push %s to %s", localRepoDir, environmentRepo.URL) } diff --git a/pkg/gits/credentialhelper/git_credential.go b/pkg/gits/credentialhelper/git_credential.go new file mode 100644 index 0000000000..a6b29e10a7 --- /dev/null +++ b/pkg/gits/credentialhelper/git_credential.go @@ -0,0 +1,122 @@ +package credentialhelper + +import ( + "fmt" + "net/url" + "reflect" + "strings" + + "github.com/jenkins-x/jx/pkg/util" + "github.com/pkg/errors" +) + +// GitCredential represents the different parts of a git credential URL +// See also https://git-scm.com/docs/git-credential +type GitCredential struct { + Protocol string + Host string + Path string + Username string + Password string +} + +// CreateGitCredential creates a CreateGitCredential instance from a slice of strings where each element is a key/value pair +// separated by '='. +func CreateGitCredential(lines []string) (GitCredential, error) { + var credential GitCredential + + if lines == nil { + return credential, errors.New("no data lines provided") + } + + fieldMap, err := util.ExtractKeyValuePairs(lines, "=") + if err != nil { + return credential, errors.Wrap(err, "unable to extract git credential parameters") + } + for key, value := range fieldMap { + v := reflect.ValueOf(&credential).Elem().FieldByName(strings.Title(key)) + if v.IsValid() { + v.SetString(value) + } else { + return GitCredential{}, errors.Errorf("invalid key/value %s/%s", key, value) + } + } + + return credential, nil +} + +// CreateGitCredentialFromURL creates a CreateGitCredential instance from a URL and optional username and password. +func CreateGitCredentialFromURL(gitURL string, username string, password string) (GitCredential, error) { + var credential GitCredential + + if gitURL == "" { + return credential, errors.New("url cannot be empty") + } + + u, err := url.Parse(gitURL) + if err != nil { + return credential, errors.Wrapf(err, "unable to parse URL %s", gitURL) + } + + credential.Protocol = u.Scheme + credential.Host = u.Host + credential.Path = u.Path + if username != "" { + credential.Username = username + } + + if password != "" { + credential.Password = password + } + + return credential, nil +} + +// String returns a string representation of this instance according to the expected format of git credential helpers. +// See also https://git-scm.com/docs/git-credential +func (g *GitCredential) String() string { + answer := "" + + value := reflect.ValueOf(g).Elem() + typeOfT := value.Type() + + for i := 0; i < value.NumField(); i++ { + field := value.Field(i) + answer = answer + fmt.Sprintf("%s=%v\n", strings.ToLower(typeOfT.Field(i).Name), field.Interface()) + } + + answer = answer + "\n" + + return answer +} + +// Clone clones this GitCredential instance +func (g *GitCredential) Clone() GitCredential { + clone := GitCredential{} + + value := reflect.ValueOf(g).Elem() + typeOfT := value.Type() + for i := 0; i < value.NumField(); i++ { + field := value.Field(i) + value := field.String() + v := reflect.ValueOf(&clone).Elem().FieldByName(typeOfT.Field(i).Name) + v.SetString(value) + } + + return clone +} + +// URL returns a URL from the data of this instance. If not enough information exist an error is returned +func (g *GitCredential) URL() (url.URL, error) { + urlAsString := g.Protocol + "://" + g.Host + if g.Path != "" { + urlAsString = urlAsString + "/" + g.Path + } + u, err := url.Parse(urlAsString) + if err != nil { + return url.URL{}, errors.Wrap(err, "unable to construct URL") + } + + u.User = url.UserPassword(g.Username, g.Password) + return *u, nil +} diff --git a/pkg/gits/credentialhelper/git_credentialhelper.go b/pkg/gits/credentialhelper/git_credentialhelper.go new file mode 100644 index 0000000000..2d5b6c28ab --- /dev/null +++ b/pkg/gits/credentialhelper/git_credentialhelper.go @@ -0,0 +1,111 @@ +package credentialhelper + +import ( + "bufio" + "fmt" + "io" + + "github.com/pkg/errors" +) + +// GitCredentialsHelper is used to implement the git credential helper algorithm. +// See also https://git-scm.com/docs/git-credential. +type GitCredentialsHelper struct { + in io.Reader + out io.Writer + knownCredentials []GitCredential +} + +// CreateGitCredentialsHelper creates an instance of a git credential helper. It needs to get passed the handles to read +// the git credential data as well as write the response to. It also gets the list og known credentials. +func CreateGitCredentialsHelper(in io.Reader, out io.Writer, credentials []GitCredential) (*GitCredentialsHelper, error) { + if in == nil { + return nil, errors.New("in parameter cannot be nil") + } + + if out == nil { + return nil, errors.New("out parameter cannot be nil") + } + + if credentials == nil { + return nil, errors.New("credentials parameter cannot be nil") + } + + return &GitCredentialsHelper{ + in: in, + out: out, + knownCredentials: credentials, + }, nil +} + +// Run executes the specified git credential helper operation which must be one of get, store or erase. +// NOTE: Currently only get is implemented. +func (h *GitCredentialsHelper) Run(op string) error { + var err error + + switch op { + case "get": + err = h.Get() + case "store": + // not yet implemented (HF) + fmt.Println("") + case "erase": + // not yet implemented (HF) + fmt.Println("") + default: + err = errors.Errorf("Invalid git credential operation '%s'", op) + } + + return err +} + +// Get implements the get operation of the git credential helper protocol. It reads the authentication query from +// the reader of this instance and writes the response to the writer (usually this will be stdout and stdin). +func (h *GitCredentialsHelper) Get() error { + var data []string + scanner := bufio.NewScanner(h.in) + for scanner.Scan() { + data = append(data, scanner.Text()) + } + + if scanner.Err() != nil { + return errors.Wrap(scanner.Err(), "unable to read input from stdin") + } + + gitCredential, err := CreateGitCredential(data) + if err != nil { + return errors.Wrap(scanner.Err(), "unable to create GitCredential struct") + } + + answer := h.Fill(gitCredential) + + _, err = fmt.Fprintf(h.out, answer.String()) + if err != nil { + return errors.Wrap(err, "unable to write response to stdin") + } + + return nil +} + +// Fill creates a GitCredential instance based on a git credential helper request which represented by the passed queryCredential instance. +// If there is no auth information available an empty credential instance is returned +func (h *GitCredentialsHelper) Fill(queryCredential GitCredential) GitCredential { + for _, authCredential := range h.knownCredentials { + if queryCredential.Protocol != authCredential.Protocol { + continue + } + + if queryCredential.Host != authCredential.Host { + continue + } + + if queryCredential.Path != authCredential.Path { + continue + } + + answer := authCredential.Clone() + return answer + } + + return GitCredential{} +} diff --git a/pkg/gits/credentialhelper/git_credentialhelper_test.go b/pkg/gits/credentialhelper/git_credentialhelper_test.go new file mode 100644 index 0000000000..5a8a745af7 --- /dev/null +++ b/pkg/gits/credentialhelper/git_credentialhelper_test.go @@ -0,0 +1,215 @@ +package credentialhelper + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/MakeNowJust/heredoc" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" +) + +func TestGitCredentialHelper(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "GitCredentials Suite") +} + +var _ = Describe("GitCredential", func() { + Context("#CreateGitCredential", func() { + It("successfully creates GitCredential", func() { + data := []string{ + "Protocol=http", + "Host=github.com", + "Path=jenkins-x/jx", + } + credentials, err := CreateGitCredential(data) + Expect(err).Should(BeNil()) + Expect(credentials).To(MatchAllFields(Fields{ + "Protocol": Equal("http"), + "Host": Equal("github.com"), + "Path": Equal("jenkins-x/jx"), + "Username": BeEmpty(), + "Password": BeEmpty(), + })) + }) + + It("passing nil fails GitCredential creation", func() { + credentials, err := CreateGitCredential(nil) + Expect(err).ShouldNot(BeNil()) + Expect(credentials).To(MatchAllFields(Fields{ + "Protocol": BeEmpty(), + "Host": BeEmpty(), + "Path": BeEmpty(), + "Username": BeEmpty(), + "Password": BeEmpty(), + })) + }) + + It("missing key value pairs format fails GitCredential creation", func() { + data := []string{"foo"} + credentials, err := CreateGitCredential(data) + Expect(err).ShouldNot(BeNil()) + Expect(credentials).To(MatchAllFields(Fields{ + "Protocol": BeEmpty(), + "Host": BeEmpty(), + "Path": BeEmpty(), + "Username": BeEmpty(), + "Password": BeEmpty(), + })) + }) + + It("invalid key fails GitCredential creation", func() { + data := []string{"Protocol=http", "Foo=bar"} + credentials, err := CreateGitCredential(data) + Expect(err).ShouldNot(BeNil()) + Expect(credentials).To(MatchAllFields(Fields{ + "Protocol": BeEmpty(), + "Host": BeEmpty(), + "Path": BeEmpty(), + "Username": BeEmpty(), + "Password": BeEmpty(), + })) + }) + }) + + Context("#CreateGitCredentialFromURL", func() { + It("successfully creates GitCredential", func() { + credentials, err := CreateGitCredentialFromURL("http://github.com", "johndoe", "1234") + Expect(err).Should(BeNil()) + Expect(credentials).To(MatchAllFields(Fields{ + "Protocol": Equal("http"), + "Host": Equal("github.com"), + "Path": Equal(""), + "Username": Equal("johndoe"), + "Password": Equal("1234"), + })) + }) + }) + + Context("#Clone", func() { + var ( + testCredential GitCredential + err error + ) + + BeforeEach(func() { + data := []string{ + "Protocol=http", + "Host=github.com", + "Path=jenkins-x/jx", + "Username=johndoe", + "Password=1234", + } + testCredential, err = CreateGitCredential(data) + Expect(err).Should(BeNil()) + }) + + It("successful clone", func() { + clone := testCredential.Clone() + Expect(&clone).ShouldNot(BeIdenticalTo(&testCredential)) + Expect(clone).To(MatchAllFields(Fields{ + "Protocol": Equal("http"), + "Host": Equal("github.com"), + "Path": Equal("jenkins-x/jx"), + "Username": Equal("johndoe"), + "Password": Equal("1234"), + })) + }) + + Context("#String", func() { + It("string representation of GitCredential has additional newline (needed by git credential protocol", func() { + credential, err := CreateGitCredentialFromURL("http://github.com/jenkins-x", "johndoe", "1234") + Expect(err).Should(BeNil()) + expected := heredoc.Doc(`protocol=http + host=github.com + path=/jenkins-x + username=johndoe + password=1234 + `) + expected = expected + "\n" + Expect(credential.String()).Should(Equal(expected)) + }) + }) + }) +}) + +var _ = Describe("GitCredentialsHelper", func() { + Context("#CreateGitCredentialsHelper", func() { + var ( + testIn io.Reader + testOut io.Writer + testCredentials []GitCredential + ) + + BeforeEach(func() { + testIn = strings.NewReader("") + testOut = bytes.NewBufferString("") + testCredentials = []GitCredential{} + }) + + It("fails with no input writer", func() { + helper, err := CreateGitCredentialsHelper(nil, testOut, testCredentials) + Expect(err).ShouldNot(BeNil()) + Expect(helper).Should(BeNil()) + }) + + It("fails with no output writer", func() { + helper, err := CreateGitCredentialsHelper(testIn, nil, testCredentials) + Expect(err).ShouldNot(BeNil()) + Expect(helper).Should(BeNil()) + }) + + It("fails with no credentials", func() { + helper, err := CreateGitCredentialsHelper(testIn, testOut, nil) + Expect(err).ShouldNot(BeNil()) + Expect(helper).Should(BeNil()) + }) + + It("succeeds when all parameters are specified", func() { + helper, err := CreateGitCredentialsHelper(testIn, testOut, testCredentials) + Expect(err).Should(BeNil()) + Expect(helper).ShouldNot(BeNil()) + }) + }) + + Context("#Run", func() { + var ( + testIn io.Reader + testOut *bytes.Buffer + testCredentials []GitCredential + helper *GitCredentialsHelper + err error + ) + + BeforeEach(func() { + in := heredoc.Doc(`protocol=https + host=github.com + username=jx-bot + `) + testIn = strings.NewReader(in) + testOut = bytes.NewBufferString("") + testCredentials = []GitCredential{ + {Protocol: "https", Host: "github.com", Username: "jx-bot", Password: "1234"}, + } + helper, err = CreateGitCredentialsHelper(testIn, testOut, testCredentials) + Expect(err).Should(BeNil()) + }) + + It("succeeds filling credentials", func() { + expected := heredoc.Doc(`protocol=https + host=github.com + path= + username=jx-bot + password=1234 + `) + expected = expected + "\n" + err = helper.Run("get") + Expect(err).Should(BeNil()) + Expect(testOut.String()).Should(Equal(expected)) + }) + }) +}) diff --git a/pkg/gits/git_cli.go b/pkg/gits/git_cli.go index 9f6f8d5898..c9f382367f 100644 --- a/pkg/gits/git_cli.go +++ b/pkg/gits/git_cli.go @@ -43,9 +43,21 @@ func NewGitCLI() *GitCLI { } // Ensure that error output is in English so parsing work cli.Env["LC_ALL"] = "C" + // When jx is called as credential helper we want to make sure that potential debug trace is not interfering with the process + cli.Env["JX_LOG_LEVEL"] = "error" return cli } +// Config runs a 'git config' command in the specified directory +func (g *GitCLI) Config(dir string, args ...string) error { + if args == nil { + args = []string{"config"} + } else { + args = append([]string{"config"}, args...) + } + return g.gitCmd(dir, args...) +} + // FindGitConfigDir tries to find the `.git` directory either in the current directory or in parent directories func (g *GitCLI) FindGitConfigDir(dir string) (string, string, error) { d := dir diff --git a/pkg/gits/git_cli_test.go b/pkg/gits/git_cli_test.go index 7ec55c411a..e24c52b20f 100644 --- a/pkg/gits/git_cli_test.go +++ b/pkg/gits/git_cli_test.go @@ -8,6 +8,8 @@ import ( "reflect" "testing" + "github.com/jenkins-x/jx/pkg/util" + "github.com/jenkins-x/jx/pkg/gits/testhelpers" "github.com/jenkins-x/jx/pkg/gits" @@ -119,6 +121,29 @@ var _ = Describe("Git CLI", func() { }) }) + Describe("#Config", func() { + It("should return an error if no repoDir is specified", func() { + err := git.Config("") + Expect(err).ShouldNot(BeNil()) + }) + + It("should return error if no parameters are passed", func() { + err := git.Config(repoDir) + Expect(err).ShouldNot(BeNil()) + }) + + It("should apply the specified config", func() { + err := git.Config(repoDir, "--local", "credential.helper", "/path/to/jx step git credentials --credential-helper") + Expect(err).Should(BeNil()) + + filename := filepath.Join(repoDir, ".git", "config") + Expect(util.FileExists(filename)).Should(Equal(true)) + contents, err := ioutil.ReadFile(filename) + Expect(err).Should(BeNil()) + Expect(string(contents)).Should(ContainSubstring("helper = /path/to/jx step git credentials --credential-helper")) + }) + }) + Describe("#GetCommits", func() { var ( commitASha string @@ -195,7 +220,7 @@ var _ = Describe("Git CLI", func() { Specify("an error is returned", func() { _, err := git.GetLatestCommitSha(repoDir) Expect(err).ShouldNot(BeNil()) - // TODO Currently the error message is returned which seems odd. Should be empty string imo. + // TODO Currently the error message is returned which seems odd. Should be empty string imo (HF) //Expect(sha).Should(BeEmpty()) }) }) diff --git a/pkg/gits/git_fake.go b/pkg/gits/git_fake.go index 27112eb9f1..2d6c0bea10 100644 --- a/pkg/gits/git_fake.go +++ b/pkg/gits/git_fake.go @@ -47,6 +47,10 @@ func NewGitFake() Gitter { return &GitFake{} } +func (g *GitFake) Config(dir string, args ...string) error { + return nil +} + // FindGitConfigDir finds the git config dir func (g *GitFake) FindGitConfigDir(dir string) (string, string, error) { return dir, dir, nil diff --git a/pkg/gits/git_local.go b/pkg/gits/git_local.go index 4b886dd5a2..d4312ac450 100644 --- a/pkg/gits/git_local.go +++ b/pkg/gits/git_local.go @@ -32,6 +32,11 @@ func (g *GitLocal) FindGitConfigDir(dir string) (string, string, error) { return g.GitCLI.FindGitConfigDir(dir) } +func (g *GitLocal) Config(dir string, args ...string) error { + return g.GitCLI.Config(dir, args...) +} + + // Clone clones the given git URL into the given directory // Faked out func (g *GitLocal) Clone(url string, dir string) error { diff --git a/pkg/gits/helpers.go b/pkg/gits/helpers.go index ac28bb07b2..9fe05260db 100644 --- a/pkg/gits/helpers.go +++ b/pkg/gits/helpers.go @@ -20,6 +20,7 @@ import ( "github.com/jenkins-x/jx/pkg/util" "github.com/pkg/errors" + gitcfg "gopkg.in/src-d/go-git.v4/config" ) const ( @@ -413,6 +414,35 @@ func addLabelsToPullRequest(prInfo *PullRequestInfo, labels []string) error { return nil } +// GetRemoteForURL returns the remote name for the specified remote URL. The empty string is returned if the remoteURL is not known. +// An error is returned if there are errors processing the git repository data. +func GetRemoteForURL(repoDir string, remoteURL string, gitter Gitter) (string, error) { + _, config, err := gitter.FindGitConfigDir(repoDir) + if err != nil { + return "", errors.Wrapf(err, "failed to load git config in %s", repoDir) + } + + cfg := gitcfg.NewConfig() + data, err := ioutil.ReadFile(config) + if err != nil { + return "", errors.Wrapf(err, "failed to read git config in %s", repoDir) + } + + err = cfg.Unmarshal(data) + if err != nil { + return "", errors.Wrapf(err, "failed to parse git config in %s", repoDir) + } + + for _, remote := range cfg.Remotes { + for _, url := range remote.URLs { + if url == remoteURL { + return remote.Name, nil + } + } + } + return "", nil +} + // ForkAndPullRepo pulls the specified gitUrl into dir using gitter, creating a remote fork if needed using the git provider // // If there are existing files in dir (and dir is already a git clone), the existing files will pushed into the stash @@ -431,6 +461,8 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin return "", "", nil, nil, errors.Wrapf(err, "failed to parse gitter URL %s", gitURL) } + log.Logger().Debugf("ForkAndPullRepo gitURL: %s dir: %s baseRef: %s branchName: %s forkName: %s", gitURL, dir, baseRef, branchName, forkName) + username := provider.CurrentUsername() originalOrg := originalInfo.Organisation originalRepo := originalInfo.Name @@ -449,12 +481,28 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin return "", "", nil, nil, errors.Wrapf(err, "failed to check if directory %s dirExists", dir) } dirIsGitRepo := false + + // check whether we are going to change the upstream remote (e.g. on initial boot) + upstreamChange := false if dirExists { - d, _, err := gitter.FindGitConfigDir(dir) + d, gitConfig, err := gitter.FindGitConfigDir(dir) if err != nil { return "", "", nil, nil, errors.Wrapf(err, "checking if %s is already a git repository", dir) } dirIsGitRepo = dir == d + + currentUpstreamURL, err := gitter.DiscoverUpstreamGitURL(gitConfig) + if err != nil { + log.Logger().Warn("") + } + + finalUpstreamURL, err := AddUserToURL(gitURL, username) + if err != nil { + return "", "", nil, nil, errors.Wrapf(err, "unable to add username to git url %s", gitURL) + } + if currentUpstreamURL != finalUpstreamURL { + upstreamChange = true + } } if baseRef == "" { @@ -516,32 +564,43 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin return "", "", nil, nil, errors.WithStack(err) } } else { + err = gitter.Add(dir, ".") + if err != nil { + return "", "", nil, nil, errors.WithStack(err) + } err = gitter.StashPush(dir) - stashed = true if err != nil { return "", "", nil, nil, errors.WithStack(err) } + stashed = true + } + + // configure jx as git credential helper for this repo + err = configureJxAsGitCredentialHelper(dir, gitter) + if err != nil { + return "", "", nil, nil, errors.Wrap(err, "unable to configure jx as git credential helper") } - // The long form of "git clone" has the advantage of working fine on an existing git repo and avoids checking out master - // and then another branch - err = gitter.SetRemoteURL(dir, originRemote, originURL) + originURLWithUser, err := AddUserToURL(originURL, username) + if err != nil { + return "", "", nil, nil, errors.Wrapf(err, "unable to add username to git url %s", originURL) + } + err = gitter.SetRemoteURL(dir, originRemote, originURLWithUser) if err != nil { return "", "", nil, nil, errors.Wrapf(err, "failed to set %s url to %s", originRemote, originURL) } if fork { upstreamRemote = "upstream" - err := gitter.SetRemoteURL(dir, upstreamRemote, upstreamInfo.CloneURL) + upstreamURLWithUser, err := AddUserToURL(upstreamInfo.CloneURL, username) + if err != nil { + return "", "", nil, nil, errors.Wrapf(err, "unable to add username to git url %s", upstreamInfo.CloneURL) + } + err = gitter.SetRemoteURL(dir, upstreamRemote, upstreamURLWithUser) if err != nil { return "", "", nil, nil, errors.Wrapf(err, "setting remote upstream %q in forked environment repo", gitURL) } } - userDetails := provider.UserAuth() - originFetchURL, err := gitter.CreateAuthenticatedURL(originURL, &userDetails) - if err != nil { - return "", "", nil, nil, errors.Wrapf(err, "failed to create authenticated fetch URL for %s", originURL) - } branchNameUUID, err := uuid.NewV4() if err != nil { return "", "", nil, nil, errors.WithStack(err) @@ -549,7 +608,7 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin originFetchBranch := branchNameUUID.String() var upstreamFetchBranch string - err = gitter.FetchBranch(dir, originFetchURL, fmt.Sprintf("%s:%s", branchName, originFetchBranch)) + err = gitter.FetchBranch(dir, "origin", fmt.Sprintf("%s:%s", branchName, originFetchBranch)) if err != nil { if IsCouldntFindRemoteRefError(err, branchName) { // We can safely ignore missing remote branches, as they just don't exist @@ -560,21 +619,17 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin } if upstreamRemote != originRemote || baseRef != branchName { - upstreamFetchURL, err := gitter.CreateAuthenticatedURL(upstreamInfo.CloneURL, &userDetails) branchNameUUID, err := uuid.NewV4() if err != nil { return "", "", nil, nil, errors.WithStack(err) } upstreamFetchBranch = branchNameUUID.String() - if err != nil { - return "", "", nil, nil, errors.Wrapf(err, "failed to create authenticated fetch URL for %s", upstreamInfo.CloneURL) - } + // We're going to start our work from baseRef on the upstream - err = gitter.FetchBranch(dir, upstreamFetchURL, fmt.Sprintf("%s:%s", baseRef, upstreamFetchBranch)) + err = gitter.FetchBranch(dir, upstreamRemote, fmt.Sprintf("%s:%s", baseRef, upstreamFetchBranch)) if err != nil { return "", "", nil, nil, errors.WithStack(err) } - } // Work out what branch to use and check it out @@ -597,7 +652,7 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin if err != nil { return "", "", nil, nil, errors.WithStack(err) } - } else if dirExists { + } else if dirExists && !upstreamChange { toCherryPick, err = gitter.GetCommitsNotOnAnyRemote(dir, localBranchName) if err != nil { return "", "", nil, nil, errors.WithStack(err) @@ -615,7 +670,11 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin // Merge in any local committed changes we found if len(toCherryPick) > 0 { - log.Logger().Infof("Attempting to cherry pick commits that were on %s but not yet pushed", localBranchName) + var shas = make([]string, len(toCherryPick)) + for _, commit := range toCherryPick { + shas = append(shas, commit.SHA) + } + log.Logger().Debugf("Attempting to cherry pick commits %s that were on %s but not yet pushed", shas, localBranchName) } for _, c := range toCherryPick { err = gitter.CherryPick(dir, c.SHA) @@ -633,9 +692,6 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin log.Logger().Infof(" Cherry-picked %s", c.OneLine()) } } - if len(toCherryPick) > 0 { - log.Logger().Infof("") - } // one possibility is that the baseRef is not in the same state as an already existing branch, so let's merge in the // already existing branch from the users fork. This is idempotent (safe if that's already the state) @@ -657,6 +713,31 @@ func ForkAndPullRepo(gitURL string, dir string, baseRef string, branchName strin return dir, baseRef, upstreamInfo, forkInfo, nil } +func configureJxAsGitCredentialHelper(dir string, gitter Gitter) error { + // configure jx as git credential helper for this repo + jxProcessBinary, err := os.Executable() + if err != nil { + return errors.Wrapf(err, "unable to determine jx binary location") + } + return gitter.Config(dir, "--local", "credential.helper", fmt.Sprintf("%s step git credentials --credential-helper", jxProcessBinary)) +} + +// AddUserToURL adds the specified user to the given git URL and returns the new URL. +// An error is returned if the specified git URL is not valid. In this case the return URL is empty. +func AddUserToURL(gitURL string, user string) (string, error) { + u, err := url.Parse(gitURL) + if err != nil { + return "", errors.Wrapf(err, "invalid git URL: %s", gitURL) + } + + if user != "" && u.Scheme != "file" { + userInfo := url.User(user) + u.User = userInfo + } + + return u.String(), nil +} + // A PullRequestFilter defines a filter for finding pull requests type PullRequestFilter struct { Labels []string @@ -804,7 +885,13 @@ func DuplicateGitRepoFromCommitish(toOrg string, toName string, fromGitURL strin if err != nil { return nil, errors.WithStack(err) } - log.Logger().Debugf("cloning repo from %s", fromInfo.CloneURL) + defer func() { + err = os.RemoveAll(dir) + if err != nil { + log.Logger().Warnf("unable to delete temporary directory %s", dir) + } + }() + log.Logger().Debugf("Using %s to duplicate git repo %s", dir, fromGitURL) err = gitter.Clone(fromInfo.CloneURL, dir) if err != nil { return nil, errors.Wrapf(err, "failed to clone %s", fromInfo.CloneURL) @@ -851,16 +938,20 @@ func DuplicateGitRepoFromCommitish(toOrg string, toName string, fromGitURL strin return nil, err } - userDetails := provider.UserAuth() - duplicatePushURL, err := gitter.CreateAuthenticatedURL(duplicateInfo.CloneURL, &userDetails) + err = configureJxAsGitCredentialHelper(dir, gitter) + if err != nil { + return nil, errors.Wrap(err, "unable to configure jx as git credential helper") + } + username := provider.CurrentUsername() + cloneURLWithUser, err := AddUserToURL(duplicateInfo.CloneURL, username) if err != nil { - return nil, errors.Wrapf(err, "failed to create push URL for %s", duplicateInfo.CloneURL) + return nil, errors.Wrapf(err, "unable to add username to git url %s", duplicateInfo.CloneURL) } - err = gitter.SetRemoteURL(dir, "origin", duplicateInfo.CloneURL) + err = gitter.SetRemoteURL(dir, "origin", cloneURLWithUser) if err != nil { return nil, errors.Wrapf(err, "failed to set remote url to %s", duplicateInfo.CloneURL) } - err = gitter.Push(dir, duplicatePushURL, true, fmt.Sprintf("%s:%s", "HEAD", toBranch)) + err = gitter.Push(dir, "origin", true, fmt.Sprintf("%s:%s", "HEAD", toBranch)) if err != nil { return nil, errors.Wrapf(err, "failed to push HEAD to %s", toBranch) } diff --git a/pkg/gits/helpers_test.go b/pkg/gits/helpers_test.go index 7aa0805090..8fe4b48b3a 100644 --- a/pkg/gits/helpers_test.go +++ b/pkg/gits/helpers_test.go @@ -30,6 +30,37 @@ const ( license = "LICENSE" ) +func TestGetRemoteForURL(t *testing.T) { + gitter := gits.NewGitCLI() + + repoDir, err := ioutil.TempDir("", "get-remote-for-url") + assert.NoError(t, err) + defer func() { + _ = os.RemoveAll(repoDir) + }() + + err = gitter.Init(repoDir) + assert.NoError(t, err) + + remote, err := gits.GetRemoteForURL(repoDir, "http://github.com/acme/foo.git", gitter) + assert.NoError(t, err) + assert.Empty(t, remote) + + err = gitter.AddRemote(repoDir, "origin", "http://github.com/acme/foo.git") + assert.NoError(t, err) + + err = gitter.AddRemote(repoDir, "upstream", "http://github.com/acme/bar.git") + assert.NoError(t, err) + + remote, err = gits.GetRemoteForURL(repoDir, "http://github.com/acme/foo.git", gitter) + assert.NoError(t, err) + assert.Equal(t, "origin", remote) + + remote, err = gits.GetRemoteForURL(repoDir, "http://github.com/acme/bar.git", gitter) + assert.NoError(t, err) + assert.Equal(t, "upstream", remote) +} + func TestFetchAndMergeOneSHA(t *testing.T) { // This forkAndPullTest only uses local repos, so it's safe to use real git env := prepareFetchAndMergeTests(t) @@ -1090,249 +1121,6 @@ func TestExistingForkAndExistingCheckoutWithNonConflictingLocalModifications(t * }, }) } -func TestExistingForkAndExistingCheckoutWithExistingLocalCommits(t *testing.T) { - - runForkAndPullTestCase(t, forkAndPullTest{ - name: "existingForkAndExistingCheckoutWithExistingLocalCommits", - args: forkAndPullTestArgs{ - gitter: gits.NewGitCLI(), - initFn: func(args *forkAndPullTestArgs) error { - acmeRepo, err := gits.NewFakeRepository("acme", "roadrunner", func(dir string) error { - err := ioutil.WriteFile(filepath.Join(dir, "README"), []byte("Hello there!"), 0655) - if err != nil { - return errors.WithStack(err) - } - return nil - }, args.gitter) - args.provider = gits.NewFakeProvider(acmeRepo) - fork, err := args.provider.ForkRepository("acme", "roadrunner", "wile") - assert.NoError(t, err) - - // Add a commit to the fork that isn't on the upstream to validate later. Let's use a temp clone and push it. - dir, err := ioutil.TempDir("", "") - assert.NoError(t, err) - err = args.gitter.Clone(fork.CloneURL, dir) - assert.NoError(t, err) - err = ioutil.WriteFile(filepath.Join(dir, "CONTRIBUTING"), []byte("Welcome!"), 0655) - assert.NoError(t, err) - err = args.gitter.Add(dir, "CONTRIBUTING") - assert.NoError(t, err) - err = args.gitter.CommitDir(dir, "Second commit") - assert.NoError(t, err) - err = args.gitter.Push(dir, "origin", false, "HEAD") - assert.NoError(t, err) - - // Set the provider username to wile in order to use the fork - args.provider.User.Username = "wile" - - // Let's checkout our fork - args.dir, err = ioutil.TempDir("", "") - assert.NoError(t, err) - err = args.gitter.Clone(fork.CloneURL, args.dir) - assert.NoError(t, err) - // Let's add some local modifications that don't conflict - err = ioutil.WriteFile(filepath.Join(args.dir, "LICENSE"), []byte("TODO ;-)"), 0655) - assert.NoError(t, err) - - err = args.gitter.Add(args.dir, "LICENSE") - assert.NoError(t, err) - err = args.gitter.CommitDir(args.dir, "Local commit") - assert.NoError(t, err) - - return nil - }, - cleanFn: func(args *forkAndPullTestArgs) { - for _, o := range args.provider.Repositories { - for _, r := range o { - if r.BaseDir != "" { - err := os.RemoveAll(r.BaseDir) - assert.NoError(t, err) - } - } - } - err := os.RemoveAll(args.dir) - assert.NoError(t, err) - }, - gitURL: fmt.Sprintf("https://fake.git/acme/roadrunner.git"), - dir: "", // set by initFn - provider: nil, // set by initFn - branchName: "master", - baseRef: "master", - }, - baseRef: "master", - upstreamInfo: &gits.GitRepository{ - Name: "roadrunner", - URL: "https://fake.git/acme/roadrunner.git", - HTMLURL: "https://fake.git/acme/roadrunner", - Scheme: "https", - Host: "fake.git", - Organisation: "acme", - Fork: false, - }, - forkInfo: &gits.GitRepository{ - Name: "roadrunner", - URL: "https://fake.git/wile/roadrunner.git", - HTMLURL: "https://fake.git/wile/roadrunner", - Scheme: "https", - Host: "fake.git", - Organisation: "wile", - Project: "wile", - Fork: true, - }, - postFn: func(args *forkAndPullTestArgs, test *forkAndPullTest) error { - test.dir = args.dir //make sure we end up with the same dir we start with - test.upstreamInfo.CloneURL = fmt.Sprintf("file://%s/acme", args.provider.Repositories["acme"][0].BaseDir) - test.forkInfo.CloneURL = fmt.Sprintf("file://%s/wile", args.provider.Repositories["acme"][0].BaseDir) - _, gitConf, err := args.gitter.FindGitConfigDir(args.dir) - assert.NoError(t, err) - remotes, err := args.gitter.Remotes(args.dir) - assert.NoError(t, err) - assert.Len(t, remotes, 2) - assert.Contains(t, remotes, "origin") - assert.Contains(t, remotes, "upstream") - originURL, err := args.gitter.DiscoverRemoteGitURL(gitConf) - assert.NoError(t, err) - upstreamURL, err := args.gitter.DiscoverUpstreamGitURL(gitConf) - assert.NoError(t, err) - assert.Equal(t, fmt.Sprintf("file://%s/wile", args.provider.Repositories["acme"][0].BaseDir), originURL) - assert.Equal(t, fmt.Sprintf("file://%s/acme", args.provider.Repositories["acme"][0].BaseDir), upstreamURL) - assert.FileExists(t, filepath.Join(args.dir, "LICENSE")) - tests.AssertFileContains(t, filepath.Join(args.dir, "LICENSE"), "TODO ;-)") - return nil - }, - }) -} - -func TestExistingForkAndChangesToOriginAndExistingCheckoutWithExistingLocalCommits(t *testing.T) { - - runForkAndPullTestCase(t, forkAndPullTest{ - name: "existingForkAndChangesToOriginAndExistingCheckoutWithExistingLocalCommits", - args: forkAndPullTestArgs{ - gitter: gits.NewGitCLI(), - initFn: func(args *forkAndPullTestArgs) error { - acmeRepo, err := gits.NewFakeRepository("acme", "roadrunner", func(dir string) error { - err := ioutil.WriteFile(filepath.Join(dir, "README"), []byte("Hello there!"), 0655) - if err != nil { - return errors.WithStack(err) - } - return nil - }, args.gitter) - args.provider = gits.NewFakeProvider(acmeRepo) - fork, err := args.provider.ForkRepository("acme", "roadrunner", "wile") - assert.NoError(t, err) - - // Add a commit to the fork that isn't on the upstream to validate later. Let's use a temp clone and push it. - dir, err := ioutil.TempDir("", "") - assert.NoError(t, err) - err = args.gitter.Clone(fork.CloneURL, dir) - assert.NoError(t, err) - err = ioutil.WriteFile(filepath.Join(dir, "CONTRIBUTING"), []byte("Welcome!"), 0655) - assert.NoError(t, err) - err = args.gitter.Add(dir, "CONTRIBUTING") - assert.NoError(t, err) - err = args.gitter.CommitDir(dir, "Second commit") - assert.NoError(t, err) - err = args.gitter.Push(dir, "origin", false, "HEAD") - assert.NoError(t, err) - - // Set the provider username to wile in order to use the fork - args.provider.User.Username = "wile" - - // Let's checkout our fork - args.dir, err = ioutil.TempDir("", "") - assert.NoError(t, err) - err = args.gitter.Clone(fork.CloneURL, args.dir) - assert.NoError(t, err) - // Let's add some local modifications that don't conflict - err = ioutil.WriteFile(filepath.Join(args.dir, "LICENSE"), []byte("TODO ;-)"), 0655) - assert.NoError(t, err) - - err = args.gitter.Add(args.dir, "LICENSE") - assert.NoError(t, err) - err = args.gitter.CommitDir(args.dir, "Local commit") - assert.NoError(t, err) - - // Let's make some changes to origin - origindir, err := ioutil.TempDir("", "") - assert.NoError(t, err) - err = args.gitter.Clone(acmeRepo.GitRepo.CloneURL, origindir) - assert.NoError(t, err) - // Let's add some modifications that don't conflict - err = ioutil.WriteFile(filepath.Join(origindir, "cheese"), []byte("TODO! ;-)"), 0655) - assert.NoError(t, err) - - err = args.gitter.Add(origindir, "cheese") - assert.NoError(t, err) - err = args.gitter.CommitDir(origindir, "commit cheese") - assert.NoError(t, err) - err = args.gitter.Push(origindir, "origin", false, "HEAD:master") - assert.NoError(t, err) - - return nil - }, - cleanFn: func(args *forkAndPullTestArgs) { - for _, o := range args.provider.Repositories { - for _, r := range o { - if r.BaseDir != "" { - err := os.RemoveAll(r.BaseDir) - assert.NoError(t, err) - } - } - } - err := os.RemoveAll(args.dir) - assert.NoError(t, err) - }, - gitURL: fmt.Sprintf("https://fake.git/acme/roadrunner.git"), - dir: "", // set by initFn - provider: nil, // set by initFn - branchName: "master", - baseRef: "master", - }, - baseRef: "master", - upstreamInfo: &gits.GitRepository{ - Name: "roadrunner", - URL: "https://fake.git/acme/roadrunner.git", - HTMLURL: "https://fake.git/acme/roadrunner", - Scheme: "https", - Host: "fake.git", - Organisation: "acme", - Fork: false, - }, - forkInfo: &gits.GitRepository{ - Name: "roadrunner", - URL: "https://fake.git/wile/roadrunner.git", - HTMLURL: "https://fake.git/wile/roadrunner", - Scheme: "https", - Host: "fake.git", - Organisation: "wile", - Project: "wile", - Fork: true, - }, - postFn: func(args *forkAndPullTestArgs, test *forkAndPullTest) error { - test.dir = args.dir //make sure we end up with the same dir we start with - test.upstreamInfo.CloneURL = fmt.Sprintf("file://%s/acme", args.provider.Repositories["acme"][0].BaseDir) - test.forkInfo.CloneURL = fmt.Sprintf("file://%s/wile", args.provider.Repositories["acme"][0].BaseDir) - _, gitConf, err := args.gitter.FindGitConfigDir(args.dir) - assert.NoError(t, err) - remotes, err := args.gitter.Remotes(args.dir) - assert.NoError(t, err) - assert.Len(t, remotes, 2) - assert.Contains(t, remotes, "origin") - assert.Contains(t, remotes, "upstream") - originURL, err := args.gitter.DiscoverRemoteGitURL(gitConf) - assert.NoError(t, err) - upstreamURL, err := args.gitter.DiscoverUpstreamGitURL(gitConf) - assert.NoError(t, err) - assert.Equal(t, fmt.Sprintf("file://%s/wile", args.provider.Repositories["acme"][0].BaseDir), originURL) - assert.Equal(t, fmt.Sprintf("file://%s/acme", args.provider.Repositories["acme"][0].BaseDir), upstreamURL) - assert.FileExists(t, filepath.Join(args.dir, "LICENSE")) - tests.AssertFileContains(t, filepath.Join(args.dir, "LICENSE"), "TODO ;-)") - assert.FileExists(t, filepath.Join(args.dir, "cheese")) - tests.AssertFileContains(t, filepath.Join(args.dir, "cheese"), "TODO! ;-)") - return nil - }, - }) -} func runForkAndPullTestCase(t *testing.T, tt forkAndPullTest) { err := tt.args.initFn(&tt.args) diff --git a/pkg/gits/interface.go b/pkg/gits/interface.go index 7b7fe55a4f..3516770a39 100644 --- a/pkg/gits/interface.go +++ b/pkg/gits/interface.go @@ -176,6 +176,7 @@ type GitProvider interface { // Gitter defines common git actions used by Jenkins X via git cli //go:generate pegomock generate github.com/jenkins-x/jx/pkg/gits Gitter -o mocks/gitter.go type Gitter interface { + Config(dir string, args ...string) error FindGitConfigDir(dir string) (string, string, error) PrintCreateRepositoryGenerateAccessToken(server *auth.AuthServer, username string, o io.Writer) diff --git a/pkg/gits/mocks/gitter.go b/pkg/gits/mocks/gitter.go index b835e92db2..23e45bf8b0 100644 --- a/pkg/gits/mocks/gitter.go +++ b/pkg/gits/mocks/gitter.go @@ -291,6 +291,24 @@ func (mock *MockGitter) CommitIfChanges(_param0 string, _param1 string) error { return ret0 } +func (mock *MockGitter) Config(_param0 string, _param1 ...string) error { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockGitter().") + } + params := []pegomock.Param{_param0} + for _, param := range _param1 { + params = append(params, param) + } + result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(error) + } + } + return ret0 +} + func (mock *MockGitter) ConvertToValidBranchName(_param0 string) string { if mock == nil { panic("mock must not be nil. Use myMock := NewMockGitter().") @@ -2225,6 +2243,45 @@ func (c *MockGitter_CommitIfChanges_OngoingVerification) GetAllCapturedArguments return } +func (verifier *VerifierMockGitter) Config(_param0 string, _param1 ...string) *MockGitter_Config_OngoingVerification { + params := []pegomock.Param{_param0} + for _, param := range _param1 { + params = append(params, param) + } + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) + return &MockGitter_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MockGitter_Config_OngoingVerification struct { + mock *MockGitter + methodInvocations []pegomock.MethodInvocation +} + +func (c *MockGitter_Config_OngoingVerification) GetCapturedArguments() (string, []string) { + _param0, _param1 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1], _param1[len(_param1)-1] +} + +func (c *MockGitter_Config_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 [][]string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(c.methodInvocations)) + for u, param := range params[0] { + _param0[u] = param.(string) + } + _param1 = make([][]string, len(c.methodInvocations)) + for u := 0; u < len(c.methodInvocations); u++ { + _param1[u] = make([]string, len(params)-1) + for x := 1; x < len(params); x++ { + if params[x][u] != nil { + _param1[u][x-1] = params[x][u].(string) + } + } + } + } + return +} + func (verifier *VerifierMockGitter) ConvertToValidBranchName(_param0 string) *MockGitter_ConvertToValidBranchName_OngoingVerification { params := []pegomock.Param{_param0} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ConvertToValidBranchName", params, verifier.timeout) diff --git a/pkg/gits/provider_fake.go b/pkg/gits/provider_fake.go index a085056289..f6a1c5162d 100644 --- a/pkg/gits/provider_fake.go +++ b/pkg/gits/provider_fake.go @@ -882,6 +882,10 @@ func NewFakeRepository(owner string, repoName string, addFiles func(dir string) if err != nil { return nil, errors.WithStack(err) } + err = gitter.SetRemoteURL(cloneDir, "origin", repo.GitRepo.CloneURL) + if err != nil { + return nil, errors.WithStack(err) + } err = addFiles(cloneDir) if err != nil { return nil, errors.Wrapf(err, "adding files to %s", dir) @@ -892,6 +896,7 @@ func NewFakeRepository(owner string, repoName string, addFiles func(dir string) } err = gitter.CommitDir(cloneDir, "Initial Commit") + if err != nil { return nil, errors.WithStack(err) }