diff --git a/Gopkg.lock b/Gopkg.lock index b8b1ce084af66..6fa16fe41de2c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -59,6 +59,7 @@ name = "github.com/argoproj/pkg" packages = [ "exec", + "kube/cli", "time", ] pruneopts = "" @@ -157,6 +158,21 @@ revision = "3658237ded108b4134956c1b3050349d93e7b895" version = "v2.7.1" +[[projects]] + digest = "1:ba7c75e38d81b9cf3e8601c081567be3b71bccca8c11aee5de98871360aa4d7b" + name = "github.com/emirpasic/gods" + packages = [ + "containers", + "lists", + "lists/arraylist", + "trees", + "trees/binaryheap", + "utils", + ] + pruneopts = "" + revision = "f6c17b524822278a87e3b3bd809fec33b51f5b46" + version = "v1.9.0" + [[projects]] digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22" name = "github.com/ghodss/yaml" @@ -458,6 +474,14 @@ revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" +[[projects]] + branch = "master" + digest = "1:95abc4eba158a39873bd4fabdee576d0ae13826b550f8b710881d80ae4093a0f" + name = "github.com/jbenet/go-context" + packages = ["io"] + pruneopts = "" + revision = "d14ea06fba99483203c19d92cfcd13ebe73135f4" + [[projects]] digest = "1:dd5cdbd84daf24b2a009364f3c24859b1e4de1eab87c451fb3bce09935d909fc" name = "github.com/json-iterator/go" @@ -474,6 +498,14 @@ pruneopts = "" revision = "ae77be60afb1dcacde03767a8c37337fad28ac14" +[[projects]] + digest = "1:41e0bed5df4f9fd04c418bf9b6b7179b3671e416ad6175332601ca1c8dc74606" + name = "github.com/kevinburke/ssh_config" + packages = ["."] + pruneopts = "" + revision = "81db2a75821ed34e682567d48be488a1c3121088" + version = "0.5" + [[projects]] digest = "1:f4975a8aa19d1a4e512cfebf9a2c3751faedd8939be80d193d157463b47eb334" name = "github.com/ksonnet/ksonnet" @@ -525,6 +557,14 @@ pruneopts = "" revision = "32fa128f234d041f196a9f3e0fea5ac9772c08e1" +[[projects]] + digest = "1:096a8a9182648da3d00ff243b88407838902b6703fc12657f76890e08d1899bf" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + pruneopts = "" + revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4" + version = "v1.0.0" + [[projects]] branch = "master" digest = "1:eb9117392ee8e7aa44f78e0db603f70b1050ee0ebda4bd40040befb5b218c546" @@ -541,6 +581,14 @@ revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0" version = "v2.1.0" +[[projects]] + digest = "1:049b5bee78dfdc9628ee0e557219c41f683e5b06c5a5f20eaba0105ccc586689" + name = "github.com/pelletier/go-buffruneio" + packages = ["."] + pruneopts = "" + revision = "c37440a7cf42ac63b919c752ca73a85067e05992" + version = "v0.2.0" + [[projects]] digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" name = "github.com/pkg/errors" @@ -632,6 +680,19 @@ pruneopts = "" revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" +[[projects]] + digest = "1:b1861b9a1aa0801b0b62945ed7477c1ab61a4bd03b55dfbc27f6d4f378110c8c" + name = "github.com/src-d/gcfg" + packages = [ + ".", + "scanner", + "token", + "types", + ] + pruneopts = "" + revision = "f187355171c936ac84a82793659ebb4936bc1c23" + version = "v1.3.0" + [[projects]] digest = "1:306417ea2f31ea733df356a2b895de63776b6a5107085b33458e5cd6eb1d584d" name = "github.com/stretchr/objx" @@ -662,6 +723,14 @@ revision = "a053f3dac71df214bfe8b367f34220f0029c9c02" version = "v3.3.1" +[[projects]] + digest = "1:afc0b8068986a01e2d8f449917829753a54f6bd4d1265c2b4ad9cba75560020f" + name = "github.com/xanzy/ssh-agent" + packages = ["."] + pruneopts = "" + revision = "640f0ab560aeb89d523bb6ac322b1244d5c3796c" + version = "v0.2.0" + [[projects]] digest = "1:529ed3f98838f69e13761788d0cc71b44e130058fab13bae2ce09f7a176bced4" name = "github.com/yudai/gojsondiff" @@ -688,8 +757,21 @@ packages = [ "bcrypt", "blowfish", + "cast5", + "curve25519", "ed25519", "ed25519/internal/edwards25519", + "internal/chacha20", + "openpgp", + "openpgp/armor", + "openpgp/elgamal", + "openpgp/errors", + "openpgp/packet", + "openpgp/s2k", + "poly1305", + "ssh", + "ssh/agent", + "ssh/knownhosts", "ssh/terminal", ] pruneopts = "" @@ -898,6 +980,77 @@ revision = "76dd09796242edb5b897103a75df2645c028c960" version = "v2.1.6" +[[projects]] + digest = "1:c8f3ff1edaf7208bf7633e5952ffb8d697552343f8010aee12427400b434ae63" + name = "gopkg.in/src-d/go-billy.v4" + packages = [ + ".", + "helper/chroot", + "helper/polyfill", + "osfs", + "util", + ] + pruneopts = "" + revision = "59952543636f55de3f860b477b615093d5c2c3e4" + version = "v4.2.1" + +[[projects]] + digest = "1:a77c60b21f224f0ddc9c7e9407008c6e7dfbca88e5a6e827aa27ecf80497ebb6" + name = "gopkg.in/src-d/go-git.v4" + packages = [ + ".", + "config", + "internal/revision", + "plumbing", + "plumbing/cache", + "plumbing/filemode", + "plumbing/format/config", + "plumbing/format/diff", + "plumbing/format/gitignore", + "plumbing/format/idxfile", + "plumbing/format/index", + "plumbing/format/objfile", + "plumbing/format/packfile", + "plumbing/format/pktline", + "plumbing/object", + "plumbing/protocol/packp", + "plumbing/protocol/packp/capability", + "plumbing/protocol/packp/sideband", + "plumbing/revlist", + "plumbing/storer", + "plumbing/transport", + "plumbing/transport/client", + "plumbing/transport/file", + "plumbing/transport/git", + "plumbing/transport/http", + "plumbing/transport/internal/common", + "plumbing/transport/server", + "plumbing/transport/ssh", + "storage", + "storage/filesystem", + "storage/filesystem/dotgit", + "storage/memory", + "utils/binary", + "utils/diff", + "utils/ioutil", + "utils/merkletrie", + "utils/merkletrie/filesystem", + "utils/merkletrie/index", + "utils/merkletrie/internal/frame", + "utils/merkletrie/noder", + ] + pruneopts = "" + revision = "d3cec13ac0b195bfb897ed038a08b5130ab9969e" + version = "v4.7.0" + +[[projects]] + digest = "1:ceec7e96590fb8168f36df4795fefe17051d4b0c2acc7ec4e260d8138c4dafac" + name = "gopkg.in/warnings.v0" + packages = ["."] + pruneopts = "" + revision = "ec4a0fea49c7b46c2aeb0b51aac55779c607e52b" + version = "v0.1.2" + [[projects]] digest = "1:81314a486195626940617e43740b4fa073f265b0715c9f54ce2027fee1cb5f61" name = "gopkg.in/yaml.v2" @@ -1236,6 +1389,7 @@ input-imports = [ "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1", "github.com/argoproj/pkg/exec", + "github.com/argoproj/pkg/kube/cli", "github.com/argoproj/pkg/time", "github.com/casbin/casbin", "github.com/casbin/casbin/model", @@ -1257,6 +1411,7 @@ "github.com/golang/protobuf/proto", "github.com/golang/protobuf/protoc-gen-go", "github.com/golang/protobuf/ptypes/empty", + "github.com/google/go-jsonnet", "github.com/grpc-ecosystem/go-grpc-middleware", "github.com/grpc-ecosystem/go-grpc-middleware/auth", "github.com/grpc-ecosystem/go-grpc-middleware/logging", @@ -1283,6 +1438,7 @@ "github.com/yudai/gojsondiff", "github.com/yudai/gojsondiff/formatter", "golang.org/x/crypto/bcrypt", + "golang.org/x/crypto/ssh", "golang.org/x/crypto/ssh/terminal", "golang.org/x/net/context", "golang.org/x/oauth2", @@ -1300,6 +1456,13 @@ "gopkg.in/go-playground/webhooks.v3/bitbucket", "gopkg.in/go-playground/webhooks.v3/github", "gopkg.in/go-playground/webhooks.v3/gitlab", + "gopkg.in/src-d/go-git.v4", + "gopkg.in/src-d/go-git.v4/config", + "gopkg.in/src-d/go-git.v4/plumbing", + "gopkg.in/src-d/go-git.v4/plumbing/transport", + "gopkg.in/src-d/go-git.v4/plumbing/transport/http", + "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh", + "gopkg.in/src-d/go-git.v4/storage/memory", "k8s.io/api/apps/v1", "k8s.io/api/apps/v1beta1", "k8s.io/api/apps/v1beta2", diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index 223fdcf6b9c54..c667375dbc7f2 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -61,17 +61,7 @@ func NewService(gitFactory git.ClientFactory, cache cache.Cache) *Service { // ListDir lists the contents of a GitHub repo func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, error) { - appRepoPath := tempRepoPath(q.Repo.Repo) - s.repoLock.Lock(appRepoPath) - defer s.repoLock.Unlock(appRepoPath) - - gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey) - err := gitClient.Init() - if err != nil { - return nil, err - } - - commitSHA, err := gitClient.LsRemote(q.Revision) + gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision) if err != nil { return nil, err } @@ -83,7 +73,13 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er return &res, nil } - err = checkoutRevision(gitClient, commitSHA) + s.repoLock.Lock(gitClient.Root()) + defer s.repoLock.Unlock(gitClient.Root()) + err = gitClient.Init() + if err != nil { + return nil, err + } + commitSHA, err = checkoutRevision(gitClient, commitSHA) if err != nil { return nil, err } @@ -97,7 +93,7 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er Items: lsFiles, } err = s.cache.Set(&cache.Item{ - Key: cacheKey, + Key: listDirCacheKey(commitSHA, q), Object: &res, Expiration: DefaultRepoCacheExpiration, }) @@ -108,20 +104,25 @@ func (s *Service) ListDir(ctx context.Context, q *ListDirRequest) (*FileList, er } func (s *Service) GetFile(ctx context.Context, q *GetFileRequest) (*GetFileResponse, error) { - appRepoPath := tempRepoPath(q.Repo.Repo) - s.repoLock.Lock(appRepoPath) - defer s.repoLock.Unlock(appRepoPath) - - gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey) - err := gitClient.Init() + gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision) if err != nil { return nil, err } - commitSHA, err := gitClient.LsRemote(q.Revision) + cacheKey := getFileCacheKey(commitSHA, q) + var res GetFileResponse + err = s.cache.Get(cacheKey, &res) + if err == nil { + log.Infof("getfile cache hit: %s", cacheKey) + return &res, nil + } + + s.repoLock.Lock(gitClient.Root()) + defer s.repoLock.Unlock(gitClient.Root()) + err = gitClient.Init() if err != nil { return nil, err } - err = checkoutRevision(gitClient, commitSHA) + commitSHA, err = checkoutRevision(gitClient, commitSHA) if err != nil { return nil, err } @@ -129,36 +130,27 @@ func (s *Service) GetFile(ctx context.Context, q *GetFileRequest) (*GetFileRespo if err != nil { return nil, err } - res := GetFileResponse{ + res = GetFileResponse{ Data: data, } + err = s.cache.Set(&cache.Item{ + Key: getFileCacheKey(commitSHA, q), + Object: &res, + Expiration: DefaultRepoCacheExpiration, + }) + if err != nil { + log.Warnf("getfile cache set error %s: %v", cacheKey, err) + } return &res, nil } func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*ManifestResponse, error) { - var res ManifestResponse - if git.IsCommitSHA(q.Revision) { - cacheKey := manifestCacheKey(q.Revision, q) - err := s.cache.Get(cacheKey, res) - if err == nil { - log.Infof("manifest cache hit: %s", cacheKey) - return &res, nil - } - } - appRepoPath := tempRepoPath(q.Repo.Repo) - s.repoLock.Lock(appRepoPath) - defer s.repoLock.Unlock(appRepoPath) - - gitClient := s.gitFactory.NewClient(q.Repo.Repo, appRepoPath, q.Repo.Username, q.Repo.Password, q.Repo.SSHPrivateKey) - err := gitClient.Init() - if err != nil { - return nil, err - } - commitSHA, err := gitClient.LsRemote(q.Revision) + gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision) if err != nil { return nil, err } cacheKey := manifestCacheKey(commitSHA, q) + var res ManifestResponse err = s.cache.Get(cacheKey, &res) if err == nil { log.Infof("manifest cache hit: %s", cacheKey) @@ -170,11 +162,17 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani log.Infof("manifest cache miss: %s", cacheKey) } - err = checkoutRevision(gitClient, commitSHA) + s.repoLock.Lock(gitClient.Root()) + defer s.repoLock.Unlock(gitClient.Root()) + err = gitClient.Init() if err != nil { return nil, err } - appPath := path.Join(appRepoPath, q.Path) + commitSHA, err = checkoutRevision(gitClient, commitSHA) + if err != nil { + return nil, err + } + appPath := path.Join(gitClient.Root(), q.Path) genRes, err := generateManifests(appPath, q) if err != nil { @@ -183,7 +181,7 @@ func (s *Service) GenerateManifest(c context.Context, q *ManifestRequest) (*Mani res = *genRes res.Revision = commitSHA err = s.cache.Set(&cache.Item{ - Key: cacheKey, + Key: manifestCacheKey(commitSHA, q), Object: res, Expiration: DefaultRepoCacheExpiration, }) @@ -223,7 +221,6 @@ func generateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, e if err != nil { return nil, err } - // TODO(jessesuen): we need to sort objects based on their dependency order of creation manifests := make([]string, len(targetObjs)) for i, target := range targetObjs { @@ -285,16 +282,17 @@ func IdentifyAppSourceTypeByAppPath(appFilePath string) AppSourceType { } // checkoutRevision is a convenience function to initialize a repo, fetch, and checkout a revision -func checkoutRevision(gitClient git.Client, commitSHA string) error { +// Returns the 40 character commit SHA after the checkout has been performed +func checkoutRevision(gitClient git.Client, commitSHA string) (string, error) { err := gitClient.Fetch() if err != nil { - return err + return "", err } err = gitClient.Checkout(commitSHA) if err != nil { - return err + return "", err } - return nil + return gitClient.CommitSHA() } func manifestCacheKey(commitSHA string, q *ManifestRequest) string { @@ -307,6 +305,10 @@ func listDirCacheKey(commitSHA string, q *ListDirRequest) string { return fmt.Sprintf("ldir|%s|%s", q.Path, commitSHA) } +func getFileCacheKey(commitSHA string, q *GetFileRequest) string { + return fmt.Sprintf("gfile|%s|%s", q.Path, commitSHA) +} + // ksShow runs `ks show` in an app directory after setting any component parameter overrides func ksShow(appPath, envName string, overrides []*v1alpha1.ComponentParameter) ([]*unstructured.Unstructured, []*v1alpha1.ComponentParameter, *app.EnvironmentConfig, error) { ksApp, err := ksonnet.NewKsonnetApp(appPath) @@ -411,3 +413,18 @@ func pathExists(name string) bool { } return true } + +// newClientResolveRevision is a helper to perform the common task of instantiating a git client +// and resolving a revision to a commit SHA +func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision string) (git.Client, string, error) { + appRepoPath := tempRepoPath(repo.Repo) + gitClient, err := s.gitFactory.NewClient(repo.Repo, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey) + if err != nil { + return nil, "", err + } + commitSHA, err := gitClient.LsRemote(revision) + if err != nil { + return nil, "", err + } + return gitClient, commitSHA, nil +} diff --git a/util/git/client.go b/util/git/client.go index 3ad04c23a12a5..3bb8b163e7a88 100644 --- a/util/git/client.go +++ b/util/git/client.go @@ -10,6 +10,14 @@ import ( "strings" log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + git "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/config" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + ssh2 "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh" + "gopkg.in/src-d/go-git.v4/storage/memory" ) // Client is a generic git client interface @@ -27,7 +35,7 @@ type Client interface { // ClientFactory is a factory of Git Clients // Primarily used to support creation of mock git clients during unit testing type ClientFactory interface { - NewClient(repoURL, path, username, password, sshPrivateKey string) Client + NewClient(repoURL, path, username, password, sshPrivateKey string) (Client, error) } // nativeGitClient implements Client interface using git CLI @@ -37,6 +45,7 @@ type nativeGitClient struct { username string password string sshPrivateKey string + auth transport.AuthMethod } type factory struct{} @@ -45,14 +54,27 @@ func NewFactory() ClientFactory { return &factory{} } -func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey string) Client { - return &nativeGitClient{ +func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey string) (Client, error) { + clnt := nativeGitClient{ repoURL: repoURL, root: path, username: username, password: password, sshPrivateKey: sshPrivateKey, } + if sshPrivateKey != "" { + signer, err := ssh.ParsePrivateKey([]byte(sshPrivateKey)) + if err != nil { + return nil, err + } + auth := &ssh2.PublicKeys{User: "git", Signer: signer} + auth.HostKeyCallback = ssh.InsecureIgnoreHostKey() + clnt.auth = auth + } else if username != "" || password != "" { + auth := &http.BasicAuth{Username: username, Password: password} + clnt.auth = auth + } + return &clnt, nil } func (m *nativeGitClient) Root() string { @@ -201,25 +223,76 @@ func (m *nativeGitClient) Checkout(revision string) error { return nil } -// LsRemote returns the commit SHA of a specific branch, tag, or HEAD +// LsRemote resolves the commit SHA of a specific branch, tag, or HEAD. If the supplied revision +// does not resolve, and "looks" like a 7+ hexadecimal commit SHA, it return the revision string. +// Otherwise, it returns an error indicating that the revision could not be resolved. This method +// runs with in-memory storage and is safe to run concurrently, or to be run without a git +// repository locally cloned. func (m *nativeGitClient) LsRemote(revision string) (string, error) { - var args []string - if revision == "" || revision == "HEAD" { - args = []string{"ls-remote", "origin", "HEAD"} - } else { - args = []string{"ls-remote", "--head", "--tags", "origin", revision} - + if IsCommitSHA(revision) { + return revision, nil + } + repo, err := git.Init(memory.NewStorage(), nil) + if err != nil { + return "", err + } + remote, err := repo.CreateRemote(&config.RemoteConfig{ + Name: git.DefaultRemoteName, + URLs: []string{m.repoURL}, + }) + if err != nil { + return "", err } - out, err := m.runCmd("git", args...) + refs, err := remote.List(&git.ListOptions{}) if err != nil { return "", err } - if out == "" { - // if doesn't exist in remote, assume revision is a commit sha and return it + if revision == "" { + revision = "HEAD" + } + // refToHash keeps a maps of remote refs to their hash + // (e.g. refs/heads/master -> a67038ae2e9cb9b9b16423702f98b41e36601001) + refToHash := make(map[string]string) + // refToResolve remembers ref name of the supplied revision if we determine the revision is a + // symbolic reference (like HEAD), in which case we will resolve it from the refToHash map + refToResolve := "" + for _, ref := range refs { + refName := ref.Name().String() + if refName != "HEAD" && !strings.HasPrefix(refName, "refs/heads/") && !strings.HasPrefix(refName, "refs/tags/") { + // ignore things like 'refs/pull/' 'refs/reviewable' + continue + } + hash := ref.Hash().String() + if ref.Type() == plumbing.HashReference { + refToHash[refName] = hash + } + //log.Debugf("%s\t%s", hash, refName) + if ref.Name().Short() == revision { + if ref.Type() == plumbing.HashReference { + log.Debugf("revision '%s' resolved to '%s'", revision, hash) + return hash, nil + } + if ref.Type() == plumbing.SymbolicReference { + refToResolve = ref.Target().String() + } + } + } + if refToResolve != "" { + // If refToResolve is non-empty, we are resolving symbolic reference (e.g. HEAD). + // It should exist in our refToHash map + if hash, ok := refToHash[refToResolve]; ok { + log.Debugf("symbolic reference '%s' (%s) resolved to '%s'", revision, refToResolve, hash) + return hash, nil + } + } + // We support the ability to use a truncated commit-SHA (e.g. first 7 characters of a SHA) + if IsTruncatedCommitSHA(revision) { + log.Debugf("revision '%s' assumed to be commit sha", revision) return revision, nil } - // 3f4ec0ab2263038ba91d3b594b2188fc108fc8d7 refs/heads/master - return strings.Fields(out)[0], nil + // If we get here, revision string had non hexadecimal characters (indicating its a branch, tag, + // or symbolic ref) and we were unable to resolve it to a commit SHA. + return "", fmt.Errorf("Unable to resolve '%s' to a commit SHA", revision) } // CommitSHA returns current commit sha from `git rev-parse HEAD` diff --git a/util/git/git.go b/util/git/git.go index 74ae82fa986f6..2b907e1f2322f 100644 --- a/util/git/git.go +++ b/util/git/git.go @@ -34,6 +34,13 @@ func IsCommitSHA(sha string) bool { return commitSHARegex.MatchString(sha) } +var truncatedCommitSHARegex = regexp.MustCompile("^[0-9A-Fa-f]{7,}$") + +// IsTruncatedCommitSHA returns whether or not a string is a truncated SHA-1 +func IsTruncatedCommitSHA(sha string) bool { + return truncatedCommitSHARegex.MatchString(sha) +} + // NormalizeGitURL normalizes a git URL for lookup and storage func NormalizeGitURL(repo string) string { // preprocess diff --git a/util/git/git_test.go b/util/git/git_test.go index dded62d2f44fe..d33d722fa5a99 100644 --- a/util/git/git_test.go +++ b/util/git/git_test.go @@ -13,6 +13,9 @@ func TestIsCommitSHA(t *testing.T) { assert.False(t, IsCommitSHA("master")) assert.False(t, IsCommitSHA("HEAD")) assert.False(t, IsCommitSHA("9d921f6")) // only consider 40 characters hex strings as a commit-sha + assert.True(t, IsTruncatedCommitSHA("9d921f6")) + assert.False(t, IsTruncatedCommitSHA("9d921f")) // we only consider 7+ characters + assert.False(t, IsTruncatedCommitSHA("branch-name")) } func TestEnsurePrefix(t *testing.T) { @@ -86,3 +89,36 @@ func TestNormalizeUrl(t *testing.T) { assert.Equal(t, v, NormalizeGitURL(k)) } } + +func TestLsRemote(t *testing.T) { + clnt, err := NewFactory().NewClient("https://github.com/argoproj/argo-cd.git", "/tmp", "", "", "") + assert.NoError(t, err) + xpass := []string{ + "HEAD", + "master", + "release-0.8", + "v0.8.0", + "4e22a3cb21fa447ca362a05a505a69397c8a0d44", + //"4e22a3c", + } + for _, revision := range xpass { + commitSHA, err := clnt.LsRemote(revision) + assert.NoError(t, err) + assert.True(t, IsCommitSHA(commitSHA)) + } + + // We do not resolve truncated git hashes and return the commit as-is if it appears to be a commit + commitSHA, err := clnt.LsRemote("4e22a3c") + assert.NoError(t, err) + assert.False(t, IsCommitSHA(commitSHA)) + assert.True(t, IsTruncatedCommitSHA(commitSHA)) + + xfail := []string{ + "unresolvable", + "4e22a3", // too short (6 characters) + } + for _, revision := range xfail { + _, err := clnt.LsRemote(revision) + assert.Error(t, err) + } +}