diff --git a/changeset/commit.go b/changeset/commit.go index 52c77998f0..3668dfebd1 100644 --- a/changeset/commit.go +++ b/changeset/commit.go @@ -17,6 +17,7 @@ limitations under the License. package changeset import ( + "bufio" "fmt" "io/ioutil" "os" @@ -28,6 +29,10 @@ import ( const ( commitIDFile = "HEAD" koDataPathEnvName = "KO_DATA_PATH" + // packedRefsFile is a file containing a list of refs, used to compact the + // list of refs instead of storing them on the filesystem directly. + // See https://git-scm.com/docs/git-pack-refs + packedRefsFile = "packed-refs" ) var commitIDRE = regexp.MustCompile(`^[a-f0-9]{40}$`) @@ -41,9 +46,27 @@ func Get() (string, error) { } commitID := strings.TrimSpace(string(data)) if rID := strings.TrimPrefix(commitID, "ref: "); rID != commitID { + // First try to read from the direct ref file - e.g. refs/heads/main data, err := readFileFromKoData(rID) if err != nil { - return "", err + if !os.IsNotExist(err) { + return "", err + } + + // Ref file didn't exist - it might be contained in the packed-refs + // file. + var pferr error + data, pferr = findPackedRef(rID) + // Only return the sub-error if the packed-refs file exists, otherwise + // just let the original error return (e.g. treat it as if we didn't + // even attempt the operation). This is primarily to keep the error + // messages clean. + if pferr != nil { + if os.IsNotExist(pferr) { + return "", err + } + return "", pferr + } } commitID = strings.TrimSpace(string(data)) } @@ -58,9 +81,49 @@ func Get() (string, error) { // to be wrapped into the container from /kodata by ko. If it fails, returns // the error it gets. func readFileFromKoData(filename string) ([]byte, error) { + f, err := koDataFile(filename) + if err != nil { + return nil, err + } + defer f.Close() + return ioutil.ReadAll(f) +} + +// readFileFromKoData tries to open the file with given name under KO_DATA_PATH. +// The file is expected to be wrapped into the container from /kodata by ko. +// If it fails, returns the error it gets. +func koDataFile(filename string) (*os.File, error) { koDataPath := os.Getenv(koDataPathEnvName) if koDataPath == "" { return nil, fmt.Errorf("%q does not exist or is empty", koDataPathEnvName) } - return ioutil.ReadFile(filepath.Join(koDataPath, filename)) + return os.Open(filepath.Join(koDataPath, filename)) +} + +// findPackedRef searches the packed-ref file for ref values. +// This can happen if the # of refs in a repo grows too much - git will try +// and condense them into a file. +// See https://git-scm.com/docs/git-pack-refs +func findPackedRef(ref string) ([]byte, error) { + f, err := koDataFile(packedRefsFile) + if err != nil { + return nil, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + // We only care about lines with ` ` pairs. + // Why this might happen: + // 1. Lines starting with ^ refer to unpeeled tag SHAs + // (e.g. the commits pointed to by annotated tags) + s := strings.Split(scanner.Text(), " ") + if len(s) != 2 { + continue + } + if ref == s[1] { + return []byte(s[0]), nil + } + } + return nil, fmt.Errorf("%q ref not found in packed-refs", ref) } diff --git a/changeset/commit_test.go b/changeset/commit_test.go index 6552971eb0..057bccfe0c 100644 --- a/changeset/commit_test.go +++ b/changeset/commit_test.go @@ -70,6 +70,15 @@ func TestReadFile(t *testing.T) { koDataPath: "testdata/nonexisting", wantErr: true, err: errors.New("open testdata/nonexisting/HEAD: no such file or directory"), + }, { + name: "packed-ref", + koDataPath: "testdata/packed-refs", + want: testCommitID, + }, { + name: "no refs with packed-ref", + koDataPath: "testdata/noncommited-packed-refs", + wantErr: true, + err: fmt.Errorf("%q ref not found in packed-refs", nonCommittedHeadRef), }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/changeset/testdata/noncommited-packed-refs/HEAD b/changeset/testdata/noncommited-packed-refs/HEAD new file mode 100644 index 0000000000..89fe2d3567 --- /dev/null +++ b/changeset/testdata/noncommited-packed-refs/HEAD @@ -0,0 +1 @@ +ref: refs/heads/non_committed_branch diff --git a/changeset/testdata/noncommited-packed-refs/packed-refs b/changeset/testdata/noncommited-packed-refs/packed-refs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changeset/testdata/packed-refs/HEAD b/changeset/testdata/packed-refs/HEAD new file mode 100644 index 0000000000..19af7398c8 --- /dev/null +++ b/changeset/testdata/packed-refs/HEAD @@ -0,0 +1 @@ +ref: refs/heads/foo diff --git a/changeset/testdata/packed-refs/packed-refs b/changeset/testdata/packed-refs/packed-refs new file mode 100644 index 0000000000..634ca6720b --- /dev/null +++ b/changeset/testdata/packed-refs/packed-refs @@ -0,0 +1,4 @@ +a2d1bdfe929516d7da141aef68631a7ee6941b2d refs/tags/bar +^51be315ed160e68627a3bc51a244ec77e470c667 +a2d1bdfe929516d7da141aef68631a7ee6941b2d refs/tags/baz +a2d1bdfe929516d7da141aef68631a7ee6941b2d refs/heads/foo