Merge branch 'pr/4228'
diff --git a/include/git2/worktree.h b/include/git2/worktree.h
index 4c4f928..d3fa88e 100644
--- a/include/git2/worktree.h
+++ b/include/git2/worktree.h
@@ -74,6 +74,27 @@
  */
 GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt);
 
+typedef struct git_worktree_add_options {
+	unsigned int version;
+
+	int lock; /**< lock newly created worktree */
+} git_worktree_add_options;
+
+#define GIT_WORKTREE_ADD_OPTIONS_VERSION 1
+#define GIT_WORKTREE_ADD_OPTIONS_INIT {GIT_WORKTREE_ADD_OPTIONS_VERSION,0}
+
+/**
+ * Initializes a `git_worktree_add_options` with default vaules.
+ * Equivalent to creating an instance with
+ * GIT_WORKTREE_ADD_OPTIONS_INIT.
+ *
+ * @param opts the struct to initialize
+ * @param version Verison of struct; pass `GIT_WORKTREE_ADD_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+int git_worktree_add_init_options(git_worktree_add_options *opts,
+	unsigned int version);
+
 /**
  * Add a new working tree
  *
@@ -85,9 +106,12 @@
  * @param repo Repository to create working tree for
  * @param name Name of the working tree
  * @param path Path to create working tree at
+ * @param opts Options to modify default behavior. May be NULL
  * @return 0 or an error code
  */
-GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path);
+GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo,
+	const char *name, const char *path,
+	const git_worktree_add_options *opts);
 
 /**
  * Lock worktree if not already locked
@@ -137,23 +161,44 @@
 	GIT_WORKTREE_PRUNE_WORKING_TREE = 1u << 2,
 } git_worktree_prune_t;
 
+typedef struct git_worktree_prune_options {
+	unsigned int version;
+
+	uint32_t flags;
+} git_worktree_prune_options;
+
+#define GIT_WORKTREE_PRUNE_OPTIONS_VERSION 1
+#define GIT_WORKTREE_PRUNE_OPTIONS_INIT {GIT_WORKTREE_PRUNE_OPTIONS_VERSION,0}
+
 /**
- * Is the worktree prunable with the given set of flags?
+ * Initializes a `git_worktree_prune_options` with default vaules.
+ * Equivalent to creating an instance with
+ * GIT_WORKTREE_PRUNE_OPTIONS_INIT.
+ *
+ * @param opts the struct to initialize
+ * @param version Verison of struct; pass `GIT_WORKTREE_PRUNE_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_worktree_prune_init_options(
+	git_worktree_prune_options *opts,
+	unsigned int version);
+
+/**
+ * Is the worktree prunable with the given options?
  *
  * A worktree is not prunable in the following scenarios:
  *
  * - the worktree is linking to a valid on-disk worktree. The
- *   GIT_WORKTREE_PRUNE_VALID flag will cause this check to be
- *   ignored.
- * - the worktree is not valid but locked. The
- *   GIT_WORKRTEE_PRUNE_LOCKED flag will cause this check to be
- *   ignored.
+ *   `valid` member will cause this check to be ignored.
+ * - the worktree is locked. The `locked` flag will cause this
+ *   check to be ignored.
  *
  * If the worktree is not valid and not locked or if the above
  * flags have been passed in, this function will return a
  * positive value.
  */
-GIT_EXTERN(int) git_worktree_is_prunable(git_worktree *wt, unsigned flags);
+GIT_EXTERN(int) git_worktree_is_prunable(git_worktree *wt,
+	git_worktree_prune_options *opts);
 
 /**
  * Prune working tree
@@ -163,10 +208,12 @@
  * `git_worktree_is_prunable` succeeds.
  *
  * @param wt Worktree to prune
- * @param flags git_worktree_prune_t flags
+ * @param opts Specifies which checks to override. See
+ *        `git_worktree_is_prunable`. May be NULL
  * @return 0 or an error code
  */
-GIT_EXTERN(int) git_worktree_prune(git_worktree *wt, unsigned flags);
+GIT_EXTERN(int) git_worktree_prune(git_worktree *wt,
+	git_worktree_prune_options *opts);
 
 /** @} */
 GIT_END_DECL
diff --git a/src/worktree.c b/src/worktree.c
index 55fbf52..f224b23 100644
--- a/src/worktree.c
+++ b/src/worktree.c
@@ -269,15 +269,32 @@
 	return err;
 }
 
-int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree)
+int git_worktree_add_init_options(git_worktree_add_options *opts,
+	unsigned int version)
+{
+	GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version,
+		git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT);
+	return 0;
+}
+
+int git_worktree_add(git_worktree **out, git_repository *repo,
+	const char *name, const char *worktree,
+	const git_worktree_add_options *opts)
 {
 	git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT;
 	git_reference *ref = NULL, *head = NULL;
 	git_commit *commit = NULL;
 	git_repository *wt = NULL;
 	git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT;
+	git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT;
 	int err;
 
+	GITERR_CHECK_VERSION(
+		opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options");
+
+	if (opts)
+		memcpy(&wtopts, opts, sizeof(wtopts));
+
 	assert(out && repo && name && worktree);
 
 	*out = NULL;
@@ -301,6 +318,21 @@
 	if ((err = git_path_prettify_dir(&wddir, worktree, NULL)) < 0)
 		goto out;
 
+	if (wtopts.lock) {
+		int fd;
+
+		if ((err = git_buf_joinpath(&buf, gitdir.ptr, "locked")) < 0)
+			goto out;
+
+		if ((fd = p_creat(buf.ptr, 0644)) < 0) {
+			err = fd;
+			goto out;
+		}
+
+		p_close(fd);
+		git_buf_clear(&buf);
+	}
+
 	/* Create worktree .git file */
 	if ((err = git_buf_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0)
 		goto out;
@@ -424,11 +456,29 @@
 	return ret;
 }
 
-int git_worktree_is_prunable(git_worktree *wt, unsigned flags)
+int git_worktree_prune_init_options(
+	git_worktree_prune_options *opts,
+	unsigned int version)
+{
+	GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version,
+		git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT);
+	return 0;
+}
+
+int git_worktree_is_prunable(git_worktree *wt,
+	git_worktree_prune_options *opts)
 {
 	git_buf reason = GIT_BUF_INIT;
+	git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
 
-	if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 &&
+	GITERR_CHECK_VERSION(
+		opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION,
+		"git_worktree_prune_options");
+
+	if (opts)
+		memcpy(&popts, opts, sizeof(popts));
+
+	if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 &&
 		git_worktree_is_locked(&reason, wt))
 	{
 		if (!reason.size)
@@ -439,7 +489,7 @@
 		return 0;
 	}
 
-	if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
+	if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
 		git_worktree_validate(wt) == 0)
 	{
 		giterr_set(GITERR_WORKTREE, "Not pruning valid working tree");
@@ -449,13 +499,22 @@
 	return 1;
 }
 
-int git_worktree_prune(git_worktree *wt, unsigned flags)
+int git_worktree_prune(git_worktree *wt,
+	git_worktree_prune_options *opts)
 {
+	git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
 	git_buf path = GIT_BUF_INIT;
 	char *wtpath;
 	int err;
 
-	if (!git_worktree_is_prunable(wt, flags)) {
+	GITERR_CHECK_VERSION(
+		opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION,
+		"git_worktree_prune_options");
+
+	if (opts)
+		memcpy(&popts, opts, sizeof(popts));
+
+	if (!git_worktree_is_prunable(wt, &popts)) {
 		err = -1;
 		goto out;
 	}
@@ -474,7 +533,7 @@
 
 	/* Skip deletion of the actual working tree if it does
 	 * not exist or deletion was not requested */
-	if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
+	if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
 		!git_path_exists(wt->gitlink_path))
 	{
 		goto out;
diff --git a/tests/worktree/refs.c b/tests/worktree/refs.c
index 95b173e..a10f50a 100644
--- a/tests/worktree/refs.c
+++ b/tests/worktree/refs.c
@@ -118,11 +118,14 @@
 
 void test_worktree_refs__delete_succeeds_after_pruning_worktree(void)
 {
+	git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
 	git_reference *branch;
 	git_worktree *worktree;
 
+	opts.flags = GIT_WORKTREE_PRUNE_VALID;
+
 	cl_git_pass(git_worktree_lookup(&worktree, fixture.repo, fixture.worktreename));
-	cl_git_pass(git_worktree_prune(worktree, GIT_WORKTREE_PRUNE_VALID));
+	cl_git_pass(git_worktree_prune(worktree, &opts));
 	git_worktree_free(worktree);
 
 	cl_git_pass(git_branch_lookup(&branch, fixture.repo,
diff --git a/tests/worktree/submodule.c b/tests/worktree/submodule.c
index 5620775..2943852 100644
--- a/tests/worktree/submodule.c
+++ b/tests/worktree/submodule.c
@@ -74,7 +74,7 @@
 	cl_git_pass(git_repository_open(&child.repo, WORKTREE_CHILD));
 
 	/* Create worktree of submodule repository */
-	cl_git_pass(git_worktree_add(&wt, child.repo, "subdir", wt_path.ptr));
+	cl_git_pass(git_worktree_add(&wt, child.repo, "subdir", wt_path.ptr, NULL));
 	cl_git_pass(git_repository_open_from_worktree(&repo, wt));
 
 	cl_git_pass(git_submodule_resolve_url(&sm_relative_path, repo,
diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c
index 73991bf..4ac3b8b 100644
--- a/tests/worktree/worktree.c
+++ b/tests/worktree/worktree.c
@@ -215,7 +215,7 @@
 	git_buf path = GIT_BUF_INIT;
 
 	cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
-	cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
+	cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL));
 
 	/* Open and verify created repo */
 	cl_git_pass(git_repository_open(&repo, path.ptr));
@@ -228,6 +228,31 @@
 	git_repository_free(repo);
 }
 
+void test_worktree_worktree__add_locked(void)
+{
+	git_worktree *wt;
+	git_repository *repo;
+	git_reference *branch;
+	git_buf path = GIT_BUF_INIT;
+	git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT;
+
+	opts.lock = 1;
+
+	cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-locked"));
+	cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-locked", path.ptr, &opts));
+
+	/* Open and verify created repo */
+	cl_assert(git_worktree_is_locked(NULL, wt));
+	cl_git_pass(git_repository_open(&repo, path.ptr));
+	cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-locked/") == 0);
+	cl_git_pass(git_branch_lookup(&branch, repo, "worktree-locked", GIT_BRANCH_LOCAL));
+
+	git_buf_free(&path);
+	git_worktree_free(wt);
+	git_reference_free(branch);
+	git_repository_free(repo);
+}
+
 void test_worktree_worktree__init_existing_branch(void)
 {
 	git_reference *head, *branch;
@@ -240,7 +265,7 @@
 	cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false));
 
 	cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
-	cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
+	cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL));
 
 	git_buf_free(&path);
 	git_commit_free(commit);
@@ -254,7 +279,7 @@
 	git_buf path = GIT_BUF_INIT;
 
 	cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
-	cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr));
+	cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr, NULL));
 
 	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
 	cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink);
@@ -279,7 +304,7 @@
 	}
 
 	cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree"));
-	cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
+	cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL));
 
 	/* Verify files have not been re-created */
 	for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
@@ -303,7 +328,7 @@
 	cl_git_pass(git_buf_joinpath(&path, repo->workdir, "sm_unchanged"));
 	cl_git_pass(git_repository_open(&sm, path.ptr));
 	cl_git_pass(git_buf_joinpath(&path, repo->workdir, "../worktree/"));
-	cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr));
+	cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr, NULL));
 	cl_git_pass(git_repository_open_from_worktree(&wt, worktree));
 
 	cl_git_pass(git_path_prettify_dir(&path, path.ptr, NULL));
@@ -429,13 +454,31 @@
 	git_worktree_free(wt);
 }
 
-void test_worktree_worktree__prune_valid(void)
+void test_worktree_worktree__prune_without_opts_fails(void)
 {
 	git_worktree *wt;
 	git_repository *repo;
 
 	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
-	cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
+	cl_git_fail(git_worktree_prune(wt, NULL));
+
+	/* Assert the repository is still valid */
+	cl_git_pass(git_repository_open_from_worktree(&repo, wt));
+
+	git_worktree_free(wt);
+	git_repository_free(repo);
+}
+
+void test_worktree_worktree__prune_valid(void)
+{
+	git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
+	git_worktree *wt;
+	git_repository *repo;
+
+	opts.flags = GIT_WORKTREE_PRUNE_VALID;
+
+	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
+	cl_git_pass(git_worktree_prune(wt, &opts));
 
 	/* Assert the repository is not valid anymore */
 	cl_git_fail(git_repository_open_from_worktree(&repo, wt));
@@ -446,27 +489,33 @@
 
 void test_worktree_worktree__prune_locked(void)
 {
+	git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
 	git_worktree *wt;
 	git_repository *repo;
 
 	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
 	cl_git_pass(git_worktree_lock(wt, NULL));
-	cl_git_fail(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
-	cl_git_fail(git_worktree_prune(wt, ~GIT_WORKTREE_PRUNE_LOCKED));
 
+	opts.flags = GIT_WORKTREE_PRUNE_VALID;
+	cl_git_fail(git_worktree_prune(wt, &opts));
 	/* Assert the repository is still valid */
 	cl_git_pass(git_repository_open_from_worktree(&repo, wt));
 
+	opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_LOCKED;
+	cl_git_pass(git_worktree_prune(wt, &opts));
+
 	git_worktree_free(wt);
 	git_repository_free(repo);
 }
 
-void test_worktree_worktree__prune_gitdir(void)
+void test_worktree_worktree__prune_gitdir_only(void)
 {
+	git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
 	git_worktree *wt;
 
+	opts.flags = GIT_WORKTREE_PRUNE_VALID;
 	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
-	cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
+	cl_git_pass(git_worktree_prune(wt, &opts));
 
 	cl_assert(!git_path_exists(wt->gitdir_path));
 	cl_assert(git_path_exists(wt->gitlink_path));
@@ -474,12 +523,15 @@
 	git_worktree_free(wt);
 }
 
-void test_worktree_worktree__prune_both(void)
+void test_worktree_worktree__prune_worktree(void)
 {
+	git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
 	git_worktree *wt;
 
+	opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_WORKING_TREE;
+
 	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
-	cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_WORKING_TREE | GIT_WORKTREE_PRUNE_VALID));
+	cl_git_pass(git_worktree_prune(wt, &opts));
 
 	cl_assert(!git_path_exists(wt->gitdir_path));
 	cl_assert(!git_path_exists(wt->gitlink_path));