make 'got checkout' and 'got update' work with read-only repositories but warn users about the garbage collection problem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
diff --git a/got/got.c b/got/got.c
index 98571cf..e658e62 100644
--- a/got/got.c
+++ b/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 char *path)
{
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;
diff --git a/include/got_worktree.h b/include/got_worktree.h
index 09b7625..7ee3eae 100644
--- a/include/got_worktree.h
+++ b/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.
diff --git a/lib/worktree.c b/lib/worktree.c
index a8f6827..ba58428 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -1881,8 +1881,14 @@ checkout_files(struct got_worktree *worktree, struct got_fileindex *fileindex,
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);
diff --git a/regress/cmdline/checkout.sh b/regress/cmdline/checkout.sh
index 76ad000..34747be 100755
--- a/regress/cmdline/checkout.sh
+++ b/regress/cmdline/checkout.sh
@@ -296,6 +296,66 @@ function test_checkout_ignores_submodules {
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