Commit bb51a5b4af467a42569e19cbeeeeccbb44d946f0

Stefan Sperling 2020-01-13T10:28:58

add -E option to 'got checkout' allowing use of a non-empty work tree path needed by and ok kn@

diff --git a/got/got.1 b/got/got.1
index 4099167..c37e7ca 100644
--- a/got/got.1
+++ b/got/got.1
@@ -136,8 +136,14 @@ follows the globbing rules documented in
 .It Cm im
 Short alias for
 .Cm import .
-.It Cm checkout Oo Fl b Ar branch Oc Oo Fl c Ar commit Oc Oo Fl p Ar path-prefix Oc Ar repository-path Op Ar work-tree-path
+.It Cm checkout  Oo Fl E Oc Oo Fl b Ar branch Oc Oo Fl c Ar commit OcOo Fl p Ar path-prefix Oc Ar repository-path Op Ar work-tree-path
 Copy files from a repository into a new work tree.
+Show the status of each affected file, using the following status codes:
+.Bl -column YXZ description
+.It A Ta new file was added
+.It E Ta file already exists in work tree's meta-data
+.El
+.Pp
 If the
 .Ar work tree path
 is not specified, either use the last component of
@@ -151,6 +157,11 @@ The options for
 .Cm got checkout
 are as follows:
 .Bl -tag -width Ds
+.It Fl E
+Proceed with the checkout operation even if the directory at
+.Ar work-tree-path
+is not empty.
+Existing files will be left intact.
 .It Fl b Ar branch
 Check out files from a commit on the specified
 .Ar branch .
diff --git a/got/got.c b/got/got.c
index 5490ed3..9949c54 100644
--- a/got/got.c
+++ b/got/got.c
@@ -799,7 +799,7 @@ done:
 __dead static void
 usage_checkout(void)
 {
-	fprintf(stderr, "usage: %s checkout [-b branch] [-c commit] "
+	fprintf(stderr, "usage: %s checkout [-E] [-b branch] [-c commit] "
 	    "[-p prefix] repository-path [worktree-path]\n", getprogname());
 	exit(1);
 }
@@ -998,13 +998,13 @@ cmd_checkout(int argc, char *argv[])
 	const char *path_prefix = "";
 	const char *branch_name = GOT_REF_HEAD;
 	char *commit_id_str = NULL;
-	int ch, same_path_prefix;
+	int ch, same_path_prefix, allow_nonempty = 0;
 	struct got_pathlist_head paths;
 	struct got_checkout_progress_arg cpa;
 
 	TAILQ_INIT(&paths);
 
-	while ((ch = getopt(argc, argv, "b:c:p:")) != -1) {
+	while ((ch = getopt(argc, argv, "b:c:Ep:")) != -1) {
 		switch (ch) {
 		case 'b':
 			branch_name = optarg;
@@ -1014,6 +1014,9 @@ cmd_checkout(int argc, char *argv[])
 			if (commit_id_str == NULL)
 				return got_error_from_errno("strdup");
 			break;
+		case 'E':
+			allow_nonempty = 1;
+			break;
 		case 'p':
 			path_prefix = optarg;
 			break;
@@ -1100,7 +1103,8 @@ cmd_checkout(int argc, char *argv[])
 		if (!(error->code == GOT_ERR_ERRNO && errno == EISDIR) &&
 		    !(error->code == GOT_ERR_ERRNO && errno == EEXIST))
 			goto done;
-		if (!got_path_dir_is_empty(worktree_path)) {
+		if (!allow_nonempty &&
+		    !got_path_dir_is_empty(worktree_path)) {
 			error = got_error_path(worktree_path,
 			    GOT_ERR_DIR_NOT_EMPTY);
 			goto done;
diff --git a/include/got_path.h b/include/got_path.h
index 756faf8..57b823a 100644
--- a/include/got_path.h
+++ b/include/got_path.h
@@ -16,8 +16,10 @@
 
 /* Utilities for dealing with filesystem paths. */
 
-#define GOT_DEFAULT_FILE_MODE	(S_IRUSR|S_IWUSR | S_IRGRP | S_IROTH)
-#define GOT_DEFAULT_DIR_MODE	(S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH)
+#define GOT_DEFAULT_FILE_MODE	(S_IFREG | \
+	S_IRUSR|S_IWUSR | S_IRGRP | S_IROTH)
+#define GOT_DEFAULT_DIR_MODE	(S_IFDIR | \
+	S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH)
 
 /* Determine whether a path is an absolute path. */
 int got_path_is_absolute(const char *);
diff --git a/lib/worktree.c b/lib/worktree.c
index fcbeadb..3027c28 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -937,7 +937,7 @@ get_ondisk_perms(int executable, mode_t st_mode)
 
 static const struct got_error *
 install_blob(struct got_worktree *worktree, const char *ondisk_path,
-    const char *path, uint16_t te_mode, uint16_t st_mode,
+    const char *path, mode_t te_mode, mode_t st_mode,
     struct got_blob_object *blob, int restoring_missing_file,
     int reverting_versioned_file, struct got_repository *repo,
     got_worktree_checkout_cb progress_cb, void *progress_arg)
diff --git a/regress/cmdline/checkout.sh b/regress/cmdline/checkout.sh
index 34747be..6859b9b 100755
--- a/regress/cmdline/checkout.sh
+++ b/regress/cmdline/checkout.sh
@@ -356,6 +356,146 @@ function test_checkout_read_only {
 	test_done "$testroot" "$ret"
 }
 
+function test_checkout_into_nonempty_dir {
+	local testroot=`test_init checkout_into_nonempty_dir`
+
+	mkdir -p $testroot/wt
+	make_test_tree $testroot/wt
+
+	got checkout $testroot/repo $testroot/wt > $testroot/stdout \
+		2> $testroot/stderr
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "checkout succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo -n > $testroot/stdout.expected
+	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 "got: $testroot/wt: directory exists and is not empty" \
+		> $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 "U  $testroot/wt/alpha" > $testroot/stdout.expected
+	echo "U  $testroot/wt/beta" >> $testroot/stdout.expected
+	echo "U  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+	echo "U  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got checkout -E $testroot/repo $testroot/wt > $testroot/stdout
+	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 "E  $testroot/wt/alpha" > $testroot/stdout.expected
+	echo "E  $testroot/wt/beta" >> $testroot/stdout.expected
+	echo "E  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+	echo "E  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got checkout -E $testroot/repo $testroot/wt > $testroot/stdout
+	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 "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
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/wt/alpha
+
+	echo "E  $testroot/wt/alpha" > $testroot/stdout.expected
+	echo "E  $testroot/wt/beta" >> $testroot/stdout.expected
+	echo "E  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+	echo "E  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got checkout -E $testroot/repo $testroot/wt > $testroot/stdout
+	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 "modified 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
+		test_done "$testroot" "$ret"
+		return
+	fi
+
+	echo 'M  alpha' > $testroot/stdout.expected
+	(cd $testroot/wt && got status > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_checkout_basic
 run_test test_checkout_dir_exists
 run_test test_checkout_dir_not_empty
@@ -364,3 +504,4 @@ 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
+run_test test_checkout_into_nonempty_dir