diff --git a/pkg/cmd/step/verify/step_verify_environments.go b/pkg/cmd/step/verify/step_verify_environments.go index 4f8f3b6dfe..34d5084726 100644 --- a/pkg/cmd/step/verify/step_verify_environments.go +++ b/pkg/cmd/step/verify/step_verify_environments.go @@ -342,7 +342,12 @@ func (o *StepVerifyEnvironmentsOptions) pushDevEnvironmentUpdates(environmentRep return errors.Wrap(err, "failed to modify dev environment config") } - err = gitter.AddCommit(localRepoDir, "chore(config): update configuration") + err = gitter.Add(localRepoDir, ".") + if err != nil { + return errors.Wrap(err, "unable to add stage commit") + } + + err = gitter.CommitDir(localRepoDir, "chore(config): update configuration") if err != nil { return errors.Wrapf(err, "unable to commit changes to environment repo in %s", localRepoDir) } diff --git a/pkg/gits/helpers.go b/pkg/gits/helpers.go index 52851daf86..203fcf561d 100644 --- a/pkg/gits/helpers.go +++ b/pkg/gits/helpers.go @@ -6,9 +6,13 @@ import ( "net/url" "os" "os/user" + "path" + "path/filepath" "sort" "strings" + "gopkg.in/src-d/go-git.v4/config" + uuid "github.com/satori/go.uuid" "github.com/jenkins-x/jx/pkg/util" @@ -730,63 +734,115 @@ func FindTagForVersion(dir string, version string, gitter Gitter) (string, error // head of the toBranch on the duplicated repo to fromCommitish. It returns the GitRepository for the duplicated repo func DuplicateGitRepoFromCommitish(toOrg string, toName string, fromGitURL string, fromCommitish string, toBranch string, private bool, provider GitProvider, gitter Gitter) (*GitRepository, error) { duplicateInfo, err := provider.GetRepository(toOrg, toName) + if err == nil { + return duplicateInfo, nil + } + // If the duplicate doesn't exist create it + log.Logger().Debugf(errors.Wrapf(err, "getting repository %s/%s", toOrg, toName).Error()) + fromInfo, err := ParseGitURL(fromGitURL) if err != nil { - log.Logger().Debugf(errors.Wrapf(err, "getting repository %s/%s", toOrg, toName).Error()) - fromInfo, err := ParseGitURL(fromGitURL) - if err != nil { - return nil, errors.Wrapf(err, "parsing %s", fromGitURL) - } - fromInfo, err = provider.GetRepository(fromInfo.Organisation, fromInfo.Name) - if err != nil { - return nil, errors.Wrapf(err, "getting repo for %s", fromGitURL) - } - duplicateInfo, err = provider.CreateRepository(toOrg, toName, private) - if err != nil { - return nil, errors.Wrapf(err, "failed to create GitHub repo %s/%s", toOrg, toName) - } - dir, err := ioutil.TempDir("", "") - if err != nil { - return nil, errors.WithStack(err) - } - err = gitter.Clone(fromInfo.CloneURL, dir) - if err != nil { - return nil, errors.Wrapf(err, "failed to clone %s", fromInfo.CloneURL) - } - if !strings.Contains(fromCommitish, "/") { - // if the commitish looks like a tag, fetch the tags - err = gitter.FetchTags(dir) - if err != nil { - return nil, errors.Wrapf(err, "failed to fetch tags fromGitURL %s", fromInfo.CloneURL) - } - } else { - parts := strings.Split(fromCommitish, "/") - err = gitter.FetchBranch(dir, parts[0], parts[1]) - if err != nil { - return nil, errors.Wrapf(err, "failed to fetch %s fromGitURL %s", fromCommitish, fromInfo.CloneURL) - } - } - err = gitter.Reset(dir, fromCommitish, true) - if err != nil { - return nil, errors.Wrapf(err, "failed to reset to %s", fromCommitish) - } - userDetails := provider.UserAuth() - duplicatePushURL, err := gitter.CreateAuthenticatedURL(duplicateInfo.CloneURL, &userDetails) + return nil, errors.Wrapf(err, "parsing %s", fromGitURL) + } + fromInfo, err = provider.GetRepository(fromInfo.Organisation, fromInfo.Name) + if err != nil { + return nil, errors.Wrapf(err, "getting repo for %s", fromGitURL) + } + duplicateInfo, err = provider.CreateRepository(toOrg, toName, private) + if err != nil { + return nil, errors.Wrapf(err, "failed to create GitHub repo %s/%s", toOrg, toName) + } + dir, err := ioutil.TempDir("", "") + if err != nil { + return nil, errors.WithStack(err) + } + err = gitter.Clone(fromInfo.CloneURL, dir) + if err != nil { + return nil, errors.Wrapf(err, "failed to clone %s", fromInfo.CloneURL) + } + if !strings.Contains(fromCommitish, "/") { + // if the commitish looks like a tag, fetch the tags + err = gitter.FetchTags(dir) if err != nil { - return nil, errors.Wrapf(err, "failed to create push URL for %s", duplicateInfo.CloneURL) + return nil, errors.Wrapf(err, "failed to fetch tags fromGitURL %s", fromInfo.CloneURL) } - err = gitter.SetRemoteURL(dir, "origin", duplicateInfo.CloneURL) + } else { + parts := strings.Split(fromCommitish, "/") + err = gitter.FetchBranch(dir, parts[0], parts[1]) if err != nil { - return nil, errors.Wrapf(err, "failed to set remote url to %s", duplicateInfo.CloneURL) + return nil, errors.Wrapf(err, "failed to fetch %s fromGitURL %s", fromCommitish, fromInfo.CloneURL) } - err = gitter.Push(dir, duplicatePushURL, true, false, fmt.Sprintf("%s:%s", "HEAD", toBranch)) + } + err = gitter.Reset(dir, fromCommitish, true) + if err != nil { + return nil, errors.Wrapf(err, "failed to reset to %s", fromCommitish) + } + + err = SquashIntoSingleCommit(dir, fmt.Sprintf("initial config based of %s/%s with ref %s", toOrg, toName, fromCommitish), gitter) + if err != nil { + return nil, err + } + + userDetails := provider.UserAuth() + duplicatePushURL, err := gitter.CreateAuthenticatedURL(duplicateInfo.CloneURL, &userDetails) + if err != nil { + return nil, errors.Wrapf(err, "failed to create push URL for %s", duplicateInfo.CloneURL) + } + err = gitter.SetRemoteURL(dir, "origin", duplicateInfo.CloneURL) + if err != nil { + return nil, errors.Wrapf(err, "failed to set remote url to %s", duplicateInfo.CloneURL) + } + err = gitter.Push(dir, duplicatePushURL, true, false, fmt.Sprintf("%s:%s", "HEAD", toBranch)) + if err != nil { + return nil, errors.Wrapf(err, "failed to push HEAD to %s", toBranch) + } + log.Logger().Infof("Duplicated Git repository %s to %s\n", util.ColorInfo(fromInfo.HTMLURL), util.ColorInfo(duplicateInfo.HTMLURL)) + log.Logger().Infof("Setting upstream to %s\n", util.ColorInfo(duplicateInfo.HTMLURL)) + + return duplicateInfo, nil +} + +// SquashIntoSingleCommit takes the git repository in the specified directory and squashes all commits into a single +// one using the specified message. +func SquashIntoSingleCommit(repoDir string, commitMsg string, gitter Gitter) error { + cfg := config.NewConfig() + data, err := ioutil.ReadFile(filepath.Join(repoDir, ".git", "config")) + if err != nil { + return errors.Wrapf(err, "failed to load git config from %s", repoDir) + } + + err = cfg.Unmarshal(data) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal %s", repoDir) + } + + err = os.RemoveAll(path.Join(repoDir, ".git")) + if err != nil { + return errors.Wrap(err, "unable to squash") + } + + err = gitter.Init(repoDir) + if err != nil { + return errors.Wrap(err, "unable to init git") + } + + for _, remote := range cfg.Remotes { + err = gitter.AddRemote(repoDir, remote.Name, remote.URLs[0]) if err != nil { - return nil, errors.Wrapf(err, "failed to push HEAD to %s", toBranch) + return errors.Wrap(err, "unable to update remote") } - log.Logger().Infof("Duplicated Git repository %s to %s\n", util.ColorInfo(fromInfo.HTMLURL), util.ColorInfo(duplicateInfo.HTMLURL)) - log.Logger().Infof("Setting upstream to %s\n", util.ColorInfo(duplicateInfo.HTMLURL)) } - return duplicateInfo, nil + + err = gitter.Add(repoDir, ".") + if err != nil { + return errors.Wrap(err, "unable to add stage commit") + } + + err = gitter.CommitDir(repoDir, commitMsg) + if err != nil { + return errors.Wrap(err, "unable to add initial commit") + } + return nil } // GetGitInfoFromDirectory obtains remote origin HTTPS and current branch of a given directory and fails if it's not a git repository diff --git a/pkg/gits/helpers_test.go b/pkg/gits/helpers_test.go index f317b4d69c..18e4481828 100644 --- a/pkg/gits/helpers_test.go +++ b/pkg/gits/helpers_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "strings" "testing" @@ -2257,3 +2258,56 @@ func TestGetGitInfoFromDirectoryNoGit(t *testing.T) { assert.Equal(t, fmt.Sprintf("there was a problem obtaining the remote Git URL of directory %s: failed to unmarshal due to no GitConfDir defined", dir), err.Error()) } + +func Test_SquashIntoSingleCommit(t *testing.T) { + gitDir, err := ioutil.TempDir("", "test-repo") + assert.NoError(t, err) + defer func() { + os.RemoveAll(gitDir) + }() + + gitter := gits.NewGitCLI() + + err = gitter.Init(gitDir) + assert.NoError(t, err) + + readmePath := filepath.Join(gitDir, readme) + err = ioutil.WriteFile(readmePath, []byte("readme"), 0600) + assert.NoError(t, err) + err = gitter.Add(gitDir, readme) + assert.NoError(t, err) + err = gitter.CommitDir(gitDir, "adding readme") + assert.NoError(t, err) + + contributingPath := filepath.Join(gitDir, contributing) + err = ioutil.WriteFile(contributingPath, []byte("contribute"), 0600) + assert.NoError(t, err) + err = gitter.Add(gitDir, contributing) + assert.NoError(t, err) + err = gitter.CommitDir(gitDir, "adding contribute") + assert.NoError(t, err) + + assert.Equal(t, 2, commitCount(t, gitDir)) + + err = gits.SquashIntoSingleCommit(gitDir, "squashed", gitter) + assert.NoError(t, err) + + assert.Equal(t, 1, commitCount(t, gitDir)) + assert.FileExists(t, filepath.Join(gitDir, readme)) + assert.FileExists(t, filepath.Join(gitDir, contributing)) +} + +func commitCount(t *testing.T, repoDir string) int { + args := []string{"rev-list", "--count", "HEAD"} + cmd := util.Command{ + Dir: repoDir, + Name: "git", + Args: args, + } + out, err := cmd.RunWithoutRetry() + assert.NoError(t, err) + + count, err := strconv.Atoi(out) + assert.NoError(t, err) + return count +}