From 07e552b5060351364595f064d273556a9bc34f75 Mon Sep 17 00:00:00 2001 From: Spencer Malone Date: Tue, 6 Apr 2021 11:44:08 -0700 Subject: [PATCH] Add support for sparse-checkout --- README.md | 1 + cmd/git-sync/main.go | 84 ++++++++++++++++++++++++++++++++++++++++++-- test_e2e.sh | 32 +++++++++++++++++ 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 718c5c3..859348d 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ docker run -d \ | GIT_SYNC_ONE_TIME | `--one-time` | exit after the first sync | false | | GIT_SYNC_MAX_SYNC_FAILURES | `--max-sync-failures` | the number of consecutive failures allowed before aborting (the first sync must succeed, -1 will retry forever after the initial sync) | 0 | | GIT_SYNC_PERMISSIONS | `--change-permissions` | the file permissions to apply to the checked-out files (0 will not change permissions at all) | 0 | +| GIT_SYNC_SPARSE_CHECKOUT_FILE | `--sparse-checkout-file` | the location of an optional [sparse-checkout](https://git-scm.com/docs/git-sparse-checkout#_sparse_checkout) file, same syntax as a .gitignore file. | "" | | GIT_SYNC_HOOK_COMMAND | `--sync-hook-command` | the command executed with the syncing repository as its working directory after syncing a new hash of the remote repository. it is subject to the sync time out and will extend period between syncs. (doesn't support the command arguments) | "" | | GIT_SYNC_WEBHOOK_URL | `--webhook-url` | the URL for a webook notification when syncs complete | "" | | GIT_SYNC_WEBHOOK_METHOD | `--webhook-method` | the HTTP method for the webhook | "POST" | diff --git a/cmd/git-sync/main.go b/cmd/git-sync/main.go index 7f36f60..27a2c15 100644 --- a/cmd/git-sync/main.go +++ b/cmd/git-sync/main.go @@ -23,6 +23,7 @@ import ( "context" "flag" "fmt" + "io" "io/ioutil" "net" "net/http" @@ -73,6 +74,8 @@ var flChmod = flag.Int("change-permissions", envInt("GIT_SYNC_PERMISSIONS", 0), var flSyncHookCommand = flag.String("sync-hook-command", envString("GIT_SYNC_HOOK_COMMAND", ""), "the command executed with the syncing repository as its working directory after syncing a new hash of the remote repository. "+ "it is subject to the sync time out and will extend period between syncs. (doesn't support the command arguments)") +var flSparseCheckoutFile = flag.String("sparse-checkout-file", envString("GIT_SYNC_SPARSE_CHECKOUT_FILE", ""), + "the path to a sparse-checkout file.") var flWebhookURL = flag.String("webhook-url", envString("GIT_SYNC_WEBHOOK_URL", ""), "the URL for a webook notification when syncs complete (default is no webook)") @@ -610,7 +613,7 @@ func addWorktreeAndSwap(ctx context.Context, gitRoot, dest, branch, rev string, // Make a worktree for this exact git hash. worktreePath := filepath.Join(gitRoot, hash) - _, err := runCommand(ctx, gitRoot, *flGitCmd, "worktree", "add", worktreePath, "origin/"+branch) + _, err := runCommand(ctx, gitRoot, *flGitCmd, "worktree", "add", worktreePath, "origin/"+branch, "--no-checkout") log.V(0).Info("adding worktree", "path", worktreePath, "branch", fmt.Sprintf("origin/%s", branch)) if err != nil { return err @@ -629,7 +632,45 @@ func addWorktreeAndSwap(ctx context.Context, gitRoot, dest, branch, rev string, return err } - // Reset the worktree's working copy to the specific rev. + if *flSparseCheckoutFile != "" { + // This is required due to the undocumented behavior outlined here: https://public-inbox.org/git/CAPig+cSP0UiEBXSCi7Ua099eOdpMk8R=JtAjPuUavRF4z0R0Vg@mail.gmail.com/t/ + log.V(0).Info("configuring worktree sparse checkout") + checkoutFile := *flSparseCheckoutFile + + gitInfoPath := filepath.Join(gitRoot, fmt.Sprintf(".git/worktrees/%s/info", hash)) + gitSparseConfigPath := filepath.Join(gitInfoPath, "sparse-checkout") + + source, err := os.Open(checkoutFile) + if err != nil { + return err + } + defer source.Close() + + if _, err := os.Stat(gitInfoPath); os.IsNotExist(err) { + fileMode := os.FileMode(int(0755)) + err := os.Mkdir(gitInfoPath, fileMode) + if err != nil { + return err + } + } + + destination, err := os.Create(gitSparseConfigPath) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + if err != nil { + return err + } + + args := []string{"sparse-checkout", "init"} + _, err = runCommand(ctx, worktreePath, *flGitCmd, args...) + if err != nil { + return err + } + } + _, err = runCommand(ctx, worktreePath, *flGitCmd, "reset", "--hard", hash) if err != nil { return err @@ -718,6 +759,45 @@ func cloneRepo(ctx context.Context, repo, branch, rev string, depth int, gitRoot } } + if *flSparseCheckoutFile != "" { + log.V(0).Info("configuring sparse checkout") + checkoutFile := *flSparseCheckoutFile + + gitRepoPath := filepath.Join(gitRoot, ".git") + gitInfoPath := filepath.Join(gitRepoPath, "info") + gitSparseConfigPath := filepath.Join(gitInfoPath, "sparse-checkout") + + source, err := os.Open(checkoutFile) + if err != nil { + return err + } + defer source.Close() + + if _, err := os.Stat(gitInfoPath); os.IsNotExist(err) { + fileMode := os.FileMode(int(0755)) + err := os.Mkdir(gitInfoPath, fileMode) + if err != nil { + return err + } + } + + destination, err := os.Create(gitSparseConfigPath) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + if err != nil { + return err + } + + args := []string{"sparse-checkout", "init"} + _, err = runCommand(ctx, gitRoot, *flGitCmd, args...) + if err != nil { + return err + } + } + return nil } diff --git a/test_e2e.sh b/test_e2e.sh index f20545b..8c2deae 100755 --- a/test_e2e.sh +++ b/test_e2e.sh @@ -1198,6 +1198,38 @@ assert_file_eq "$ROOT"/link/file "$TESTCASE" # Wrap up pass +############################################## +# Test sparse-checkout files +############################################## +testcase "sparse-checkout" +echo "!/*" > "$DIR"/sparseconfig +echo "!/*/" >> "$DIR"/sparseconfig +echo "file2" >> "$DIR"/sparseconfig +echo "$TESTCASE" > "$REPO"/file +echo "$TESTCASE" > "$REPO"/file2 +mkdir "$REPO"/dir +echo "$TESTCASE" > "$REPO"/dir/file3 +git -C "$REPO" add file2 +git -C "$REPO" add dir +git -C "$REPO" commit -qam "$TESTCASE" +GIT_SYNC \ + --one-time \ + --repo="file://$REPO" \ + --branch=e2e-branch \ + --rev=HEAD \ + --root="$ROOT" \ + --dest="link" \ + --sparse-checkout-file="$DIR/sparseconfig" \ + > "$DIR"/log."$TESTCASE" 2>&1 +assert_link_exists "$ROOT"/link +assert_file_exists "$ROOT"/link/file2 +assert_file_absent "$ROOT"/link/file +assert_file_absent "$ROOT"/link/dir/file3 +assert_file_absent "$ROOT"/link/dir +assert_file_eq "$ROOT"/link/file2 "$TESTCASE" +# Wrap up +pass + ############################################## # Test additional git configs ##############################################