Commit Diff


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