commit 7f47418fd49bc98fe4570c139767c057cd066409 from: Stefan Sperling date: Fri Dec 20 15:54:59 2019 UTC make 'got checkout' and 'got update' work with read-only repositories but warn users about the garbage collection problem commit - c29c428a5f2db009ac7f83085fe96c62bf48ee79 commit + 7f47418fd49bc98fe4570c139767c057cd066409 blob - 98571cf64193a2e536f09fedf3b82a1f61dfdb9a blob + e658e622b058b0f31f698572b39ed94a026bc154 --- got/got.c +++ got/got.c @@ -804,19 +804,39 @@ usage_checkout(void) exit(1); } +static void +show_worktree_base_ref_warning(void) +{ + fprintf(stderr, "%s: warning: could not create a reference " + "to the work tree's base commit; the commit could be " + "garbage-collected by Git; making the repository " + "writable and running 'got update' will prevent this\n", + getprogname()); +} + +struct got_checkout_progress_arg { + const char *worktree_path; + int had_base_commit_ref_error; +}; + static const struct got_error * checkout_progress(void *arg, unsigned char status, const char *path) { - char *worktree_path = arg; + struct got_checkout_progress_arg *a = arg; /* Base commit bump happens silently. */ if (status == GOT_STATUS_BUMP_BASE) + return NULL; + + if (status == GOT_STATUS_BASE_REF_ERR) { + a->had_base_commit_ref_error = 1; return NULL; + } while (path[0] == '/') path++; - printf("%c %s/%s\n", status, worktree_path, path); + printf("%c %s/%s\n", status, a->worktree_path, path); return NULL; } @@ -985,6 +1005,7 @@ cmd_checkout(int argc, char *argv[]) char *commit_id_str = NULL; int ch, same_path_prefix; struct got_pathlist_head paths; + struct got_checkout_progress_arg cpa; TAILQ_INIT(&paths); @@ -1140,12 +1161,16 @@ cmd_checkout(int argc, char *argv[]) error = got_pathlist_append(&paths, "", NULL); if (error) goto done; + cpa.worktree_path = worktree_path; + cpa.had_base_commit_ref_error = 0; error = got_worktree_checkout_files(worktree, &paths, repo, - checkout_progress, worktree_path, check_cancelled, NULL); + checkout_progress, &cpa, check_cancelled, NULL); if (error != NULL) goto done; printf("Now shut up and hack\n"); + if (cpa.had_base_commit_ref_error) + show_worktree_base_ref_warning(); done: got_pathlist_free(&paths); @@ -1168,7 +1193,8 @@ update_progress(void *arg, unsigned char status, const { int *did_something = arg; - if (status == GOT_STATUS_EXISTS) + if (status == GOT_STATUS_EXISTS || + status == GOT_STATUS_BASE_REF_ERR) return NULL; *did_something = 1; blob - 09b7625b9d125ef255c71f181a0dad5efddd31d0 blob + 7ee3eaeaa50ee972174740c861e33c472d33aa9a --- include/got_worktree.h +++ include/got_worktree.h @@ -36,6 +36,7 @@ struct got_fileindex; #define GOT_STATUS_REVERT 'R' #define GOT_STATUS_CANNOT_DELETE 'd' #define GOT_STATUS_BUMP_BASE 'b' +#define GOT_STATUS_BASE_REF_ERR 'B' /* * Attempt to initialize a new work tree on disk. blob - a8f682710c80855da6d8bcaa9a34b34ede492b9f blob + ba58428f5804dfcea39d26b7c33082de42e28ab5 --- lib/worktree.c +++ lib/worktree.c @@ -1881,8 +1881,14 @@ checkout_files(struct got_worktree *worktree, struct g struct diff_cb_arg arg; err = ref_base_commit(worktree, repo); - if (err) - goto done; + if (err) { + if (!(err->code == GOT_ERR_ERRNO && errno == EACCES)) + goto done; + err = (*progress_cb)(progress_arg, + GOT_STATUS_BASE_REF_ERR, worktree->root_path); + if (err) + return err; + } err = got_object_open_as_commit(&commit, repo, worktree->base_commit_id); blob - 76ad000185ee0b02fa1a80d4ee24110a2fab7191 blob + 34747be3c1c93a3c475b7e42583dca7f4ae844a4 --- regress/cmdline/checkout.sh +++ regress/cmdline/checkout.sh @@ -295,7 +295,67 @@ function test_checkout_ignores_submodules { fi test_done "$testroot" "$ret" } + +function test_checkout_read_only { + local testroot=`test_init checkout_read_only` + + # Make the repostiory read-only + chmod -R a-w $testroot/repo + + echo "A $testroot/wt/alpha" > $testroot/stdout.expected + echo "A $testroot/wt/beta" >> $testroot/stdout.expected + echo "A $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected + echo "A $testroot/wt/gamma/delta" >> $testroot/stdout.expected + echo "Now shut up and hack" >> $testroot/stdout.expected + + got checkout $testroot/repo $testroot/wt \ + > $testroot/stdout 2> $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo -n "got: warning: could not create a reference " \ + > $testroot/stderr.expected + echo -n "to the work tree's base commit; the commit could " \ + >> $testroot/stderr.expected + echo -n "be garbage-collected by Git; making the repository " \ + >> $testroot/stderr.expected + echo "writable and running 'got update' will prevent this" \ + >> $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha" > $testroot/content.expected + echo "beta" >> $testroot/content.expected + echo "zeta" >> $testroot/content.expected + echo "delta" >> $testroot/content.expected + cat $testroot/wt/alpha $testroot/wt/beta $testroot/wt/epsilon/zeta \ + $testroot/wt/gamma/delta > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + fi + chmod -R u+w $testroot/repo # make repo cleanup work + test_done "$testroot" "$ret" +} + run_test test_checkout_basic run_test test_checkout_dir_exists run_test test_checkout_dir_not_empty @@ -303,3 +363,4 @@ run_test test_checkout_sets_xbit run_test test_checkout_commit_from_wrong_branch run_test test_checkout_tag run_test test_checkout_ignores_submodules +run_test test_checkout_read_only