// followGitSubmodule looks at a .git /file/ and tries to retrieve from inside // it the gitdir value, which is supposed to indicate the location of the // corresponding .git /directory/. Note: the gitdir value should point directly // to the corresponding .git directory even in the case of nested submodules. func followGitSubmodule(fs util.FileSystem, gitPath string) (string, error) { f, err := os.Open(gitPath) if err != nil { return "", err } defer f.Close() sc := bufio.NewScanner(f) if sc.Scan() { s := sc.Text() if strings.HasPrefix(s, "gitdir: ") { newGitPath := s[8:] if !filepath.IsAbs(newGitPath) { newGitPath = filepath.Join(filepath.Dir(gitPath), newGitPath) } fi, err := fs.Stat(newGitPath) if err != nil && !os.IsNotExist(err) { return "", err } if os.IsNotExist(err) || !fi.IsDir() { return "", fmt.Errorf("gitdir link in .git file %q is invalid", gitPath) } return newGitPath, nil } } return "", fmt.Errorf("unable to parse .git file %q", gitPath) }
// isValidGitRepository checks to see if there is a git repository in the // directory and if the repository is valid -- i.e. it has remotes or commits func isValidGitRepository(fs util.FileSystem, dir string) (bool, error) { gitPath := filepath.Join(strings.TrimPrefix(dir, "file://"), ".git") fi, err := fs.Stat(gitPath) if os.IsNotExist(err) { // The directory is not a git repo, no error return false, nil } if err != nil { return false, err } if !fi.IsDir() { gitPath, err = followGitSubmodule(fs, gitPath) if err != nil { return false, err } } // Search the content of the .git directory for content directories := [2]string{ filepath.Join(gitPath, "objects"), filepath.Join(gitPath, "refs"), } // For the directories we search, if the git repo has been used, there will // be some file. We don't just search the base git repository because of the // hook samples that are normally generated with `git init` isEmpty := true for _, dir := range directories { err := fs.Walk(dir, func(path string, info os.FileInfo, err error) error { // If we find a file, the git directory is "not empty" // We're looking for object blobs, and ref files if info != nil && !info.IsDir() { isEmpty = false return filepath.SkipDir } return err }) if err != nil && err != filepath.SkipDir { // There is a .git, but we've encountered an error return true, err } if !isEmpty { return true, nil } } // Since we know there's a .git directory, but there is nothing in it, we // throw an error return true, errors.NewEmptyGitRepositoryError(dir) }
// GuessEntrypoint tries to guess the valid entrypoint from the source code // repository. The valid entrypoints are defined above (run,start,exec,execute) func GuessEntrypoint(fs util.FileSystem, sourceDir string) (string, error) { files, err := fs.ReadDir(sourceDir) if err != nil { return "", err } for _, f := range files { if f.IsDir() || !f.Mode().IsRegular() { continue } if isValidEntrypoint(fs, filepath.Join(sourceDir, f.Name())) { glog.V(2).Infof("Found valid ENTRYPOINT: %s", f.Name()) return f.Name(), nil } } return "", errors.New("No valid entrypoint specified") }
// isValidEntrypoint checks if the given file exists and if it is a regular // file. Valid ENTRYPOINT must be an executable file, so the executable bit must // be set. func isValidEntrypoint(fs util.FileSystem, path string) bool { stat, err := fs.Stat(path) if err != nil { return false } found := false for _, pattern := range validEntrypoints { if pattern.MatchString(stat.Name()) { found = true break } } if !found { return false } mode := stat.Mode() return mode&0111 != 0 }
// ParseFile will see if the input string is a valid file location, where // file names have a great deal of flexibility and can even match // expect git clone spec syntax; it also provides details if the file:// // proto was explicitly specified, if we should use OS copy vs. the git // binary, and if a frag/ref has a bad format func ParseFile(fs util.FileSystem, source string) (*FileProtoDetails, *URLMods, error) { // Checking to see if the user included a "file://" in the call protoSpecified := false if strings.HasPrefix(source, "file://") && len(source) > 7 { protoSpecified = true } refSpecified := false path, ref := "", "" if strings.LastIndex(source, "#") != -1 { refSpecified = true segments := strings.SplitN(source, "#", 2) path = segments[0] ref = segments[1] } else { path = source } // in each valid case, like the prior logic in scm.go did, we'll make the // paths absolute and prepend file:// to the path which callers should // switch to _, err := fs.Stat(strings.TrimPrefix(path, "file://")) if err == nil { // Is there even a valid .git repository? isValidGit, err := isValidGitRepository(fs, path) hasGit := false if isValidGit { hasGit = hasGitBinary() } if err != nil || !isValidGit || !hasGit { details := &FileProtoDetails{ UseCopy: true, FileExists: true, BadRef: false, ProtoSpecified: protoSpecified, } mods := &URLMods{ Scheme: "file", Path: makePathAbsolute(strings.TrimPrefix(path, "file://")), Ref: ref, } return details, mods, err } // Check is the #ref is valid badRef := refSpecified && !gitSSHURLPathRef.MatchString(ref) details := &FileProtoDetails{ BadRef: badRef, FileExists: true, ProtoSpecified: protoSpecified, // this value doesn't really matter, we should not proceed if the git ref is bad // but let's fallback to "copy" mode if the ref is invalid. UseCopy: badRef, } mods := &URLMods{ Scheme: "file", Path: makePathAbsolute(strings.TrimPrefix(path, "file://")), Ref: ref, } return details, mods, nil } // File does not exist, return bad details := &FileProtoDetails{ UseCopy: false, FileExists: false, BadRef: false, ProtoSpecified: protoSpecified, } return details, nil, nil }