From 7e5f1fc78426c6428e88ee05efe3b0e0783adea8 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Apr 2025 15:18:04 +0100 Subject: [PATCH 1/4] use revision flag to simplify repo cloning --- pkg/mirror/repository.go | 51 ++++------------------------------------ 1 file changed, 4 insertions(+), 47 deletions(-) diff --git a/pkg/mirror/repository.go b/pkg/mirror/repository.go index 629aae0..49b30fb 100644 --- a/pkg/mirror/repository.go +++ b/pkg/mirror/repository.go @@ -330,24 +330,14 @@ func (r *Repository) Clone(ctx context.Context, dst, ref, pathspec string, rmGit r.lock.RLock() defer r.lock.RUnlock() - if IsCommitHash(ref) { - return r.cloneByRef(ctx, dst, ref, pathspec, rmGitDir) - } - return r.cloneByBranch(ctx, dst, ref, pathspec, rmGitDir) -} - -func (r *Repository) cloneByBranch(ctx context.Context, dst, branch, pathspec string, rmGitDir bool) (string, error) { - args := []string{"clone", "--no-checkout", "--single-branch"} - if branch != "HEAD" { - args = append(args, "-b", branch) - } - args = append(args, r.dir, dst) - // git clone --no-checkout --single-branch [-b ] + // create a thin clone of a repository that only contains the history of the given revision + // git clone --no-checkout --revision + args := []string{"clone", "--no-checkout", "--revision", ref, r.dir, dst} if _, err := runGitCommand(ctx, r.log, nil, "", args...); err != nil { return "", err } - args = []string{"checkout", branch} + args = []string{"checkout", "HEAD"} if pathspec != "" { args = append(args, "--", pathspec) } @@ -376,39 +366,6 @@ func (r *Repository) cloneByBranch(ctx context.Context, dst, branch, pathspec st return hash, nil } -func (r *Repository) cloneByRef(ctx context.Context, dst, ref, pathspec string, rmGitDir bool) (string, error) { - // git clone --no-checkout - if _, err := runGitCommand(ctx, r.log, nil, "", "clone", "--no-checkout", r.dir, dst); err != nil { - return "", err - } - - args := []string{"reset", "--hard", ref} - // git reset --hard - if _, err := runGitCommand(ctx, r.log, nil, dst, args...); err != nil { - return "", err - } - - // get the hash of the repos HEAD - args = []string{"log", "--pretty=format:%H", "-n", "1", "HEAD"} - if pathspec != "" { - args = append(args, "--", pathspec) - } - - // git log --pretty=format:%H -n 1 HEAD [-- ] - hash, err := runGitCommand(ctx, r.log, nil, dst, args...) - if err != nil { - return "", err - } - - if rmGitDir { - if err := os.RemoveAll(filepath.Join(dst, ".git")); err != nil { - return "", fmt.Errorf("unable to delete git dir err:%w", err) - } - } - - return hash, nil -} - // IsRunning returns if repositories mirror loop is running or not func (r *Repository) IsRunning() bool { r.lock.RLock() From 247a42d1cfbdaa87fa423d2d93857fa9215eb15b Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Apr 2025 16:20:59 +0100 Subject: [PATCH 2/4] git checkout supports multiple pathspecs --- pkg/mirror/repo_pool.go | 4 +- pkg/mirror/repository.go | 12 +++--- pkg/mirror/z_e2e_test.go | 80 ++++++++++++++++++++-------------------- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/pkg/mirror/repo_pool.go b/pkg/mirror/repo_pool.go index 734bce5..fa6605d 100644 --- a/pkg/mirror/repo_pool.go +++ b/pkg/mirror/repo_pool.go @@ -303,12 +303,12 @@ func (rp *RepoPool) ObjectExists(ctx context.Context, remote, obj string) error } // Clone is wrapper around repositories Clone method -func (rp *RepoPool) Clone(ctx context.Context, remote, dst, branch, pathspec string, rmGitDir bool) (string, error) { +func (rp *RepoPool) Clone(ctx context.Context, remote, dst, branch string, pathspecs []string, rmGitDir bool) (string, error) { repo, err := rp.Repository(remote) if err != nil { return "", err } - return repo.Clone(ctx, dst, branch, pathspec, rmGitDir) + return repo.Clone(ctx, dst, branch, pathspecs, rmGitDir) } // MergeCommits is wrapper around repositories MergeCommits method diff --git a/pkg/mirror/repository.go b/pkg/mirror/repository.go index 49b30fb..a0725ff 100644 --- a/pkg/mirror/repository.go +++ b/pkg/mirror/repository.go @@ -302,7 +302,7 @@ func (r *Repository) ObjectExists(ctx context.Context, obj string) error { // if ref is commit hash then pathspec will be ignored. // if rmGitDir is true `.git` folder will be deleted after the clone. // if dst not empty all its contents will be removed. -func (r *Repository) Clone(ctx context.Context, dst, ref, pathspec string, rmGitDir bool) (string, error) { +func (r *Repository) Clone(ctx context.Context, dst, ref string, pathspecs []string, rmGitDir bool) (string, error) { if ref == "" { ref = "HEAD" } @@ -338,8 +338,9 @@ func (r *Repository) Clone(ctx context.Context, dst, ref, pathspec string, rmGit } args = []string{"checkout", "HEAD"} - if pathspec != "" { - args = append(args, "--", pathspec) + if len(pathspecs) > 0 { + args = append(args, "--") + args = append(args, pathspecs...) } // git checkout -- if _, err := runGitCommand(ctx, r.log, nil, dst, args...); err != nil { @@ -348,10 +349,7 @@ func (r *Repository) Clone(ctx context.Context, dst, ref, pathspec string, rmGit // get the hash of the repos HEAD args = []string{"log", "--pretty=format:%H", "-n", "1", "HEAD"} - if pathspec != "" { - args = append(args, "--", pathspec) - } - // git log --pretty=format:%H -n 1 HEAD [-- ] + // git log --pretty=format:%H -n 1 HEAD hash, err := runGitCommand(ctx, r.log, nil, dst, args...) if err != nil { return "", err diff --git a/pkg/mirror/z_e2e_test.go b/pkg/mirror/z_e2e_test.go index 8059ed3..d1a1818 100644 --- a/pkg/mirror/z_e2e_test.go +++ b/pkg/mirror/z_e2e_test.go @@ -976,7 +976,7 @@ func Test_clone_branch(t *testing.T) { repo := mustCreateRepoAndMirror(t, upstream, root, "", "") - if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA { @@ -986,7 +986,7 @@ func Test_clone_branch(t *testing.T) { assertFile(t, filepath.Join(tempClone, filepath.Join("dir1", "file")), t.Name()+"-dir1-main-1") } - if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA { @@ -998,9 +998,9 @@ func Test_clone_branch(t *testing.T) { t.Log("TEST-2: forward HEAD and create other-branch") - remoteDir1SHA := mustCommit(t, upstream, filepath.Join("dir1", "file"), t.Name()+"-dir1-main-2") + mustCommit(t, upstream, filepath.Join("dir1", "file"), t.Name()+"-dir1-main-2") mustExec(t, upstream, "git", "checkout", "-q", "-b", otherBranch) - remoteDir2SHA := mustCommit(t, upstream, filepath.Join("dir2", "file"), t.Name()+"-dir2-other-2") + mustCommit(t, upstream, filepath.Join("dir2", "file"), t.Name()+"-dir2-other-2") remoteOtherSHA := mustCommit(t, upstream, "file", t.Name()+"-other-2") mustExec(t, upstream, "git", "checkout", "-q", testMainBranch) remoteSHA2 := mustCommit(t, upstream, "file", t.Name()+"-main-2") @@ -1011,7 +1011,7 @@ func Test_clone_branch(t *testing.T) { } // Clone other branch - if cloneSHA, err := repo.Clone(txtCtx, tempClone, otherBranch, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, otherBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteOtherSHA { @@ -1023,11 +1023,11 @@ func Test_clone_branch(t *testing.T) { } // Clone other branch with dir2 pathspec - if cloneSHA, err := repo.Clone(txtCtx, tempClone, otherBranch, "dir2", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, otherBranch, []string{"dir2"}, false); err != nil { t.Fatalf("unexpected error %s", err) } else { - if cloneSHA != remoteDir2SHA { - t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteDir2SHA) + if cloneSHA != remoteOtherSHA { + t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteOtherSHA) } assertMissingFile(t, tempClone, "file") assertMissingFile(t, filepath.Join(tempClone, "dir1"), "/file") @@ -1035,7 +1035,7 @@ func Test_clone_branch(t *testing.T) { } // Clone main - if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA2 { @@ -1046,11 +1046,11 @@ func Test_clone_branch(t *testing.T) { } // Clone main - if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, "dir1", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, []string{"dir1"}, false); err != nil { t.Fatalf("unexpected error %s", err) } else { - if cloneSHA != remoteDir1SHA { - t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteDir1SHA) + if cloneSHA != remoteSHA2 { + t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteSHA2) } assertMissingFile(t, tempClone, "file") assertFile(t, filepath.Join(tempClone, filepath.Join("dir1", "file")), t.Name()+"-dir1-main-2") @@ -1058,7 +1058,7 @@ func Test_clone_branch(t *testing.T) { } // Clone HEAD - if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA2 { @@ -1069,11 +1069,11 @@ func Test_clone_branch(t *testing.T) { } // Clone HEAD - if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", "dir1", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", []string{"dir1"}, false); err != nil { t.Fatalf("unexpected error %s", err) } else { - if cloneSHA != remoteDir1SHA { - t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteDir1SHA) + if cloneSHA != remoteSHA2 { + t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteSHA2) } assertMissingFile(t, tempClone, "file") assertFile(t, filepath.Join(tempClone, filepath.Join("dir1", "file")), t.Name()+"-dir1-main-2") @@ -1089,7 +1089,7 @@ func Test_clone_branch(t *testing.T) { t.Fatalf("unable to mirror error: %v", err) } - if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, "", true); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, testMainBranch, nil, true); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA { @@ -1100,7 +1100,7 @@ func Test_clone_branch(t *testing.T) { assertMissingFile(t, tempClone, ".git") } - if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", "", true); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, "HEAD", nil, true); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA { @@ -1113,11 +1113,11 @@ func Test_clone_branch(t *testing.T) { // we still have other branch // Clone other branch with dir2 pathspec - if cloneSHA, err := repo.Clone(txtCtx, tempClone, otherBranch, "dir1", true); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, otherBranch, []string{"dir1"}, true); err != nil { t.Fatalf("unexpected error %s", err) } else { - if cloneSHA != remoteDir1SHA { - t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteDir1SHA) + if cloneSHA != remoteOtherSHA { + t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteOtherSHA) } assertMissingFile(t, tempClone, "file") assertMissingFile(t, filepath.Join(tempClone, "dir2"), "/file") @@ -1133,7 +1133,7 @@ func Test_clone_branch(t *testing.T) { t.Fatalf("unable to mirror error: %v", err) } - if _, err := repo.Clone(txtCtx, tempClone, otherBranch, "", true); err == nil { + if _, err := repo.Clone(txtCtx, tempClone, otherBranch, nil, true); err == nil { t.Errorf("unexpected success for non-existent branch:%s", otherBranch) } } @@ -1159,7 +1159,7 @@ func Test_clone_tag_sha(t *testing.T) { repo := mustCreateRepoAndMirror(t, upstream, root, "", "") - if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != sha { @@ -1169,7 +1169,7 @@ func Test_clone_tag_sha(t *testing.T) { assertFile(t, filepath.Join(tempClone, filepath.Join("dir1", "file")), t.Name()+"-dir1-main-1") } - if cloneSHA, err := repo.Clone(txtCtx, tempClone, sha, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, sha, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != sha { @@ -1181,7 +1181,7 @@ func Test_clone_tag_sha(t *testing.T) { t.Log("TEST-2: forward HEAD and create other-branch") - remoteDir1SHA := mustCommit(t, upstream, filepath.Join("dir1", "file"), t.Name()+"-dir1-main-2") + mustCommit(t, upstream, filepath.Join("dir1", "file"), t.Name()+"-dir1-main-2") mustExec(t, upstream, "git", "checkout", "-q", "-b", otherBranch) remoteDir2SHA := mustCommit(t, upstream, filepath.Join("dir2", "file"), t.Name()+"-dir2-other-2") remoteOtherSHA := mustCommit(t, upstream, "file", t.Name()+"-other-2") @@ -1195,7 +1195,7 @@ func Test_clone_tag_sha(t *testing.T) { } // Clone sha without path - if cloneSHA, err := repo.Clone(txtCtx, tempClone, remoteOtherSHA, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, remoteOtherSHA, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteOtherSHA { @@ -1207,7 +1207,7 @@ func Test_clone_tag_sha(t *testing.T) { } // Clone sha with path - if cloneSHA, err := repo.Clone(txtCtx, tempClone, remoteDir2SHA, "dir2", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, remoteDir2SHA, []string{"dir2"}, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteDir2SHA { @@ -1217,7 +1217,7 @@ func Test_clone_tag_sha(t *testing.T) { } // Clone tag without path - if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != remoteSHA2 { @@ -1228,11 +1228,11 @@ func Test_clone_tag_sha(t *testing.T) { } // Clone tag with path - if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, "dir1", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, []string{"dir1"}, false); err != nil { t.Fatalf("unexpected error %s", err) } else { - if cloneSHA != remoteDir1SHA { - t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteDir1SHA) + if cloneSHA != remoteSHA2 { + t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, remoteSHA2) } assertMissingFile(t, tempClone, "file") assertFile(t, filepath.Join(tempClone, filepath.Join("dir1", "file")), t.Name()+"-dir1-main-2") @@ -1249,7 +1249,7 @@ func Test_clone_tag_sha(t *testing.T) { t.Fatalf("unable to mirror error: %v", err) } - if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, tag, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != sha { @@ -1259,7 +1259,7 @@ func Test_clone_tag_sha(t *testing.T) { assertFile(t, filepath.Join(tempClone, filepath.Join("dir1", "file")), t.Name()+"-dir1-main-1") } - if cloneSHA, err := repo.Clone(txtCtx, tempClone, sha, "", false); err != nil { + if cloneSHA, err := repo.Clone(txtCtx, tempClone, sha, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != sha { @@ -1401,7 +1401,7 @@ func Test_RepoPool_Success(t *testing.T) { assertLinkedFile(t, root, "link1", "file", t.Name()+"-u1-main-1") assertLinkedFile(t, root, "link2", "file", t.Name()+"-u2-main-1") - if cloneSHA, err := rp.Clone(txtCtx, remote1, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := rp.Clone(txtCtx, remote1, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileU1SHA1 { @@ -1410,7 +1410,7 @@ func Test_RepoPool_Success(t *testing.T) { assertFile(t, filepath.Join(tempClone, "file"), t.Name()+"-u1-main-1") } - if cloneSHA, err := rp.Clone(txtCtx, remote2, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := rp.Clone(txtCtx, remote2, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileU2SHA1 { @@ -1451,7 +1451,7 @@ func Test_RepoPool_Success(t *testing.T) { assertLinkedFile(t, root, "link3", "file", t.Name()+"-u1-main-2") assertLinkedFile(t, root, "link2", "file", t.Name()+"-u2-main-2") - if cloneSHA, err := rp.Clone(txtCtx, remote1, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := rp.Clone(txtCtx, remote1, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileU1SHA2 { @@ -1460,7 +1460,7 @@ func Test_RepoPool_Success(t *testing.T) { assertFile(t, filepath.Join(tempClone, "file"), t.Name()+"-u1-main-2") } - if cloneSHA, err := rp.Clone(txtCtx, remote2, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := rp.Clone(txtCtx, remote2, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileU2SHA2 { @@ -1500,7 +1500,7 @@ func Test_RepoPool_Success(t *testing.T) { } assertMissingLink(t, root, "link2") - if cloneSHA, err := rp.Clone(txtCtx, remote1, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := rp.Clone(txtCtx, remote1, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileU1SHA1 { @@ -1509,7 +1509,7 @@ func Test_RepoPool_Success(t *testing.T) { assertFile(t, filepath.Join(tempClone, "file"), t.Name()+"-u1-main-1") } - if cloneSHA, err := rp.Clone(txtCtx, remote2, tempClone, testMainBranch, "", false); err != nil { + if cloneSHA, err := rp.Clone(txtCtx, remote2, tempClone, testMainBranch, nil, false); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileU2SHA1 { @@ -1620,7 +1620,7 @@ func Test_RepoPool_Error(t *testing.T) { } else if err != ErrNotExist { t.Errorf("error mismatch got:%s want:%s", err, ErrNotExist) } - if _, err := rp.Clone(context.Background(), nonExistingRemote, testTmpDir, "HEAD", "", false); err == nil { + if _, err := rp.Clone(context.Background(), nonExistingRemote, testTmpDir, "HEAD", nil, false); err == nil { t.Errorf("unexpected success for non existing repo") } else if err != ErrNotExist { t.Errorf("error mismatch got:%s want:%s", err, ErrNotExist) From 62cacc925cf7607bc0b98c9b8cbd526a70eacddf Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Apr 2025 16:49:29 +0100 Subject: [PATCH 3/4] install latest git to run tests --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fad17be..c39159f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,9 @@ FROM golang:1.24-alpine AS builder ARG TARGETOS ARG TARGETARCH -RUN apk --no-cache add git openssh-client +# '--repository' flag used to install latest git v 2.49 +# can be removed once alpine is updated to 3.22 +RUN apk --no-cache add git openssh-client --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main WORKDIR /workspace # Copy the Go Modules manifests From 24a90f0763383b04edaf5f6818c021854a3bab4b Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Apr 2025 16:52:30 +0100 Subject: [PATCH 4/4] fix race test --- pkg/mirror/z_e2e_race_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/mirror/z_e2e_race_test.go b/pkg/mirror/z_e2e_race_test.go index 70c1103..9323877 100644 --- a/pkg/mirror/z_e2e_race_test.go +++ b/pkg/mirror/z_e2e_race_test.go @@ -93,7 +93,7 @@ func Test_mirror_detect_race_clone(t *testing.T) { tempClone := mustTmpDir(t) defer os.RemoveAll(tempClone) - if cloneSHA, err := repo.Clone(ctx, tempClone, testMainBranch, "", i%2 == 0); err != nil { + if cloneSHA, err := repo.Clone(ctx, tempClone, testMainBranch, nil, i%2 == 0); err != nil { t.Fatalf("unexpected error %s", err) } else { if cloneSHA != fileSHA2 {