github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/versionstream/versionstreamrepo/gitrepo.go (about)

     1  package versionstreamrepo
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
    10  	"github.com/olli-ai/jx/v2/pkg/gits"
    11  
    12  	"github.com/jenkins-x/jx-logging/pkg/log"
    13  	"github.com/olli-ai/jx/v2/pkg/config"
    14  	"github.com/olli-ai/jx/v2/pkg/util"
    15  	"github.com/pkg/errors"
    16  	"gopkg.in/AlecAivazis/survey.v1"
    17  	"gopkg.in/src-d/go-git.v4"
    18  	"gopkg.in/src-d/go-git.v4/plumbing"
    19  )
    20  
    21  // CloneJXVersionsRepo clones the jenkins-x versions repo to a local working dir
    22  func CloneJXVersionsRepo(versionRepository string, versionRef string, settings *v1.TeamSettings, gitter gits.Gitter, batchMode bool, advancedMode bool, handles util.IOFileHandles) (string, string, error) {
    23  	dir, versionRef, err := cloneJXVersionsRepo(versionRepository, versionRef, settings, gitter, batchMode, advancedMode, handles)
    24  	if err != nil {
    25  		return "", "", errors.Wrapf(err, "")
    26  	}
    27  	if versionRef != "" {
    28  		resolved, err := resolveRefToTag(dir, versionRef, gitter)
    29  		if err != nil {
    30  			return "", "", errors.WithStack(err)
    31  		}
    32  		return dir, resolved, nil
    33  	}
    34  	return dir, "", nil
    35  }
    36  
    37  func cloneJXVersionsRepo(versionRepository string, versionRef string, settings *v1.TeamSettings, gitter gits.Gitter, batchMode bool, advancedMode bool, handles util.IOFileHandles) (string, string, error) {
    38  	surveyOpts := survey.WithStdio(handles.In, handles.Out, handles.Err)
    39  	configDir, err := util.ConfigDir()
    40  	if err != nil {
    41  		return "", "", fmt.Errorf("error determining config dir %v", err)
    42  	}
    43  	wrkDir := filepath.Join(configDir, "jenkins-x-versions")
    44  
    45  	if settings != nil {
    46  		if versionRepository == "" {
    47  			versionRepository = settings.VersionStreamURL
    48  		}
    49  		if versionRef == "" {
    50  			versionRef = settings.VersionStreamRef
    51  		}
    52  	}
    53  	if versionRepository == "" {
    54  		versionRepository = config.DefaultVersionsURL
    55  	}
    56  	if versionRef == "" {
    57  		versionRef = config.DefaultVersionsRef
    58  	}
    59  
    60  	log.Logger().Debugf("Current configuration dir: %s", configDir)
    61  	log.Logger().Debugf("VersionRepository: %s git ref: %s", versionRepository, versionRef)
    62  
    63  	// If the repo already exists let's try to fetch the latest version
    64  	if exists, err := util.DirExists(wrkDir); err == nil && exists {
    65  		pullLatest := false
    66  		if batchMode {
    67  			pullLatest = true
    68  		} else if advancedMode {
    69  			confirm := &survey.Confirm{
    70  				Message: "A local Jenkins X versions repository already exists, pull the latest?",
    71  				Default: true,
    72  			}
    73  			err = survey.AskOne(confirm, &pullLatest, nil, surveyOpts)
    74  			if err != nil {
    75  				log.Logger().Errorf("Error confirming if we should pull latest, skipping %s", wrkDir)
    76  			}
    77  		} else {
    78  			pullLatest = true
    79  			log.Logger().Debugf(util.QuestionAnswer("A local Jenkins X versions repository already exists, pulling the latest", util.YesNo(pullLatest)))
    80  		}
    81  		if pullLatest {
    82  			err = gitter.FetchRemoteTags(wrkDir, versionRepository)
    83  			if err != nil {
    84  				dir, err := deleteAndReClone(wrkDir, versionRepository, versionRef, gitter)
    85  				if err != nil {
    86  					return "", "", errors.WithStack(err)
    87  				}
    88  				return dir, versionRef, nil
    89  			}
    90  			err = gitter.FetchBranch(wrkDir, versionRepository, versionRef)
    91  			if err != nil {
    92  				dir, err := deleteAndReClone(wrkDir, versionRepository, versionRef, gitter)
    93  				if err != nil {
    94  					return "", "", errors.WithStack(err)
    95  				}
    96  				return dir, versionRef, nil
    97  			}
    98  
    99  			isBranch, err := gits.RefIsBranch(wrkDir, versionRef, gitter)
   100  			if err != nil {
   101  				return "", "", err
   102  			}
   103  
   104  			if versionRef == config.DefaultVersionsRef || isBranch {
   105  				err = gitter.Checkout(wrkDir, versionRef)
   106  				if err != nil {
   107  					dir, err := deleteAndReClone(wrkDir, versionRepository, versionRef, gitter)
   108  					if err != nil {
   109  						return "", "", errors.WithStack(err)
   110  					}
   111  					return dir, versionRef, nil
   112  				}
   113  				err = gitter.Reset(wrkDir, "FETCH_HEAD", true)
   114  				if err != nil {
   115  					dir, err := deleteAndReClone(wrkDir, versionRepository, versionRef, gitter)
   116  					if err != nil {
   117  						return "", "", errors.WithStack(err)
   118  					}
   119  					return dir, versionRef, nil
   120  				}
   121  			} else {
   122  				err = gitter.Checkout(wrkDir, "FETCH_HEAD")
   123  				if err != nil {
   124  					dir, err := deleteAndReClone(wrkDir, versionRepository, versionRef, gitter)
   125  					if err != nil {
   126  						return "", "", errors.WithStack(err)
   127  					}
   128  					return dir, versionRef, nil
   129  				}
   130  			}
   131  		}
   132  		return wrkDir, versionRef, err
   133  	}
   134  	dir, err := deleteAndReClone(wrkDir, versionRepository, versionRef, gitter)
   135  	if err != nil {
   136  		return "", "", errors.WithStack(err)
   137  	}
   138  	return dir, versionRef, nil
   139  }
   140  
   141  func deleteAndReClone(wrkDir string, versionRepository string, referenceName string, gitter gits.Gitter) (string, error) {
   142  	log.Logger().Debug("Deleting and cloning the Jenkins X versions repo")
   143  	err := os.RemoveAll(wrkDir)
   144  	if err != nil {
   145  		return "", errors.Wrapf(err, "failed to delete dir %s: %s\n", wrkDir, err.Error())
   146  	}
   147  	err = os.MkdirAll(wrkDir, util.DefaultWritePermissions)
   148  	if err != nil {
   149  		return "", errors.Wrapf(err, "failed to ensure directory is created %s", wrkDir)
   150  	}
   151  	_, err = clone(wrkDir, versionRepository, referenceName, gitter)
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  	return wrkDir, err
   156  }
   157  
   158  func clone(wrkDir string, versionRepository string, referenceName string, gitter gits.Gitter) (string, error) {
   159  	if referenceName == "" || referenceName == "master" {
   160  		referenceName = "refs/heads/master"
   161  	} else if !strings.Contains(referenceName, "/") {
   162  		if strings.HasPrefix(referenceName, "PR-") {
   163  			prNumber := strings.TrimPrefix(referenceName, "PR-")
   164  
   165  			log.Logger().Debugf("Cloning the Jenkins X versions repo %s with PR: %s to %s", util.ColorInfo(versionRepository), util.ColorInfo(referenceName), util.ColorInfo(wrkDir))
   166  			return "", shallowCloneGitRepositoryToDir(wrkDir, versionRepository, prNumber, "", gitter)
   167  		}
   168  		log.Logger().Debugf("Cloning the Jenkins X versions repo %s with revision %s to %s", util.ColorInfo(versionRepository), util.ColorInfo(referenceName), util.ColorInfo(wrkDir))
   169  
   170  		err := gitter.Clone(versionRepository, wrkDir)
   171  		if err != nil {
   172  			return "", errors.Wrapf(err, "failed to clone repository: %s to dir %s", versionRepository, wrkDir)
   173  		}
   174  		cmd := util.Command{
   175  			Dir:  wrkDir,
   176  			Name: "git",
   177  			Args: []string{"fetch", "origin", referenceName},
   178  		}
   179  		_, err = cmd.RunWithoutRetry()
   180  		if err != nil {
   181  			return "", errors.Wrapf(err, "failed to git fetch origin %s for repo: %s in dir %s", referenceName, versionRepository, wrkDir)
   182  		}
   183  		isBranch, err := gits.RefIsBranch(wrkDir, referenceName, gitter)
   184  		if err != nil {
   185  			return "", err
   186  		}
   187  		if isBranch {
   188  			err = gitter.Checkout(wrkDir, referenceName)
   189  			if err != nil {
   190  				return "", errors.Wrapf(err, "failed to checkout %s of repo: %s in dir %s", referenceName, versionRepository, wrkDir)
   191  			}
   192  			return "", nil
   193  		}
   194  		err = gitter.Checkout(wrkDir, "FETCH_HEAD")
   195  		if err != nil {
   196  			return "", errors.Wrapf(err, "failed to checkout FETCH_HEAD of repo: %s in dir %s", versionRepository, wrkDir)
   197  		}
   198  		return "", nil
   199  	}
   200  	log.Logger().Infof("Cloning the Jenkins X versions repo %s with ref %s to %s", util.ColorInfo(versionRepository), util.ColorInfo(referenceName), util.ColorInfo(wrkDir))
   201  	// TODO: Change this to use gitter instead, but need to understand exactly what it's doing first.
   202  	_, err := git.PlainClone(wrkDir, false, &git.CloneOptions{
   203  		URL:           versionRepository,
   204  		ReferenceName: plumbing.ReferenceName(referenceName),
   205  		SingleBranch:  true,
   206  		Progress:      nil,
   207  	})
   208  	if err != nil {
   209  		return "", errors.Wrapf(err, "failed to clone reference: %s", referenceName)
   210  	}
   211  	return "", err
   212  }
   213  
   214  func shallowCloneGitRepositoryToDir(dir string, gitURL string, pullRequestNumber string, revision string, gitter gits.Gitter) error {
   215  	if pullRequestNumber != "" {
   216  		log.Logger().Infof("shallow cloning pull request %s of repository %s to temp dir %s", gitURL,
   217  			pullRequestNumber, dir)
   218  		err := gitter.ShallowClone(dir, gitURL, "", pullRequestNumber)
   219  		if err != nil {
   220  			return errors.Wrapf(err, "shallow cloning pull request %s of repository %s to temp dir %s\n", gitURL,
   221  				pullRequestNumber, dir)
   222  		}
   223  	} else if revision != "" {
   224  		log.Logger().Infof("shallow cloning revision %s of repository %s to temp dir %s", gitURL,
   225  			revision, dir)
   226  		err := gitter.ShallowClone(dir, gitURL, revision, "")
   227  		if err != nil {
   228  			return errors.Wrapf(err, "shallow cloning revision %s of repository %s to temp dir %s\n", gitURL,
   229  				revision, dir)
   230  		}
   231  	} else {
   232  		log.Logger().Infof("shallow cloning master of repository %s to temp dir %s", gitURL, dir)
   233  		err := gitter.ShallowClone(dir, gitURL, "", "")
   234  		if err != nil {
   235  			return errors.Wrapf(err, "shallow cloning master of repository %s to temp dir %s\n", gitURL, dir)
   236  		}
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  func resolveRefToTag(dir string, commitish string, gitter gits.Gitter) (string, error) {
   243  	err := gitter.FetchTags(dir)
   244  	if err != nil {
   245  		return "", errors.Wrapf(err, "fetching tags")
   246  	}
   247  	resolved, _, err := gitter.Describe(dir, true, commitish, "0", true)
   248  	if err != nil {
   249  		return "", errors.Wrapf(err, "running git describe %s --abbrev=0", commitish)
   250  	}
   251  	if resolved != "" {
   252  		return resolved, nil
   253  	}
   254  	return resolved, nil
   255  }