2015-06-29 14:51:18 +02:00
|
|
|
#include "cache.h"
|
2017-11-26 20:43:54 +01:00
|
|
|
#include "checkout.h"
|
2017-06-14 20:07:36 +02:00
|
|
|
#include "config.h"
|
2015-06-29 14:51:18 +02:00
|
|
|
#include "builtin.h"
|
|
|
|
#include "dir.h"
|
|
|
|
#include "parse-options.h"
|
2020-07-28 22:23:39 +02:00
|
|
|
#include "strvec.h"
|
2015-07-18 01:00:13 +02:00
|
|
|
#include "branch.h"
|
|
|
|
#include "refs.h"
|
2015-07-06 19:30:50 +02:00
|
|
|
#include "run-command.h"
|
2015-07-06 19:30:55 +02:00
|
|
|
#include "sigchain.h"
|
2019-01-05 06:08:40 +01:00
|
|
|
#include "submodule.h"
|
2015-10-08 19:01:05 +02:00
|
|
|
#include "utf8.h"
|
|
|
|
#include "worktree.h"
|
2021-01-27 09:03:08 +01:00
|
|
|
#include "quote.h"
|
2015-06-29 14:51:18 +02:00
|
|
|
|
|
|
|
static const char * const worktree_usage[] = {
|
2018-01-17 19:32:32 +01:00
|
|
|
N_("git worktree add [<options>] <path> [<commit-ish>]"),
|
2015-10-08 19:01:05 +02:00
|
|
|
N_("git worktree list [<options>]"),
|
2016-06-13 14:18:24 +02:00
|
|
|
N_("git worktree lock [<options>] <path>"),
|
2018-02-12 10:49:36 +01:00
|
|
|
N_("git worktree move <worktree> <new-path>"),
|
2016-05-22 11:33:53 +02:00
|
|
|
N_("git worktree prune [<options>]"),
|
2018-02-12 10:49:39 +01:00
|
|
|
N_("git worktree remove [<options>] <worktree>"),
|
2016-06-13 14:18:25 +02:00
|
|
|
N_("git worktree unlock <path>"),
|
2015-06-29 14:51:18 +02:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2015-07-18 01:00:07 +02:00
|
|
|
struct add_opts {
|
|
|
|
int force;
|
|
|
|
int detach;
|
2018-08-15 22:56:30 +02:00
|
|
|
int quiet;
|
2016-03-29 12:11:01 +02:00
|
|
|
int checkout;
|
2017-04-12 15:58:05 +02:00
|
|
|
int keep_locked;
|
2015-07-18 01:00:07 +02:00
|
|
|
};
|
|
|
|
|
2015-06-29 14:51:18 +02:00
|
|
|
static int show_only;
|
|
|
|
static int verbose;
|
2017-11-29 21:04:51 +01:00
|
|
|
static int guess_remote;
|
2017-04-26 21:29:31 +02:00
|
|
|
static timestamp_t expire;
|
2015-06-29 14:51:18 +02:00
|
|
|
|
2017-11-29 21:04:51 +01:00
|
|
|
static int git_worktree_config(const char *var, const char *value, void *cb)
|
|
|
|
{
|
|
|
|
if (!strcmp(var, "worktree.guessremote")) {
|
|
|
|
guess_remote = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return git_default_config(var, value, cb);
|
|
|
|
}
|
|
|
|
|
2018-08-28 23:20:20 +02:00
|
|
|
static int delete_git_dir(const char *id)
|
2018-08-28 23:20:19 +02:00
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
2018-08-28 23:20:20 +02:00
|
|
|
int ret;
|
2018-08-28 23:20:19 +02:00
|
|
|
|
2018-08-28 23:20:20 +02:00
|
|
|
strbuf_addstr(&sb, git_common_path("worktrees/%s", id));
|
|
|
|
ret = remove_dir_recursively(&sb, 0);
|
|
|
|
if (ret < 0 && errno == ENOTDIR)
|
|
|
|
ret = unlink(sb.buf);
|
|
|
|
if (ret)
|
2018-08-28 23:20:19 +02:00
|
|
|
error_errno(_("failed to delete '%s'"), sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-08-28 23:20:26 +02:00
|
|
|
static void delete_worktrees_dir_if_empty(void)
|
|
|
|
{
|
|
|
|
rmdir(git_path("worktrees")); /* ignore failed removal */
|
|
|
|
}
|
|
|
|
|
2020-06-10 08:30:45 +02:00
|
|
|
static void prune_worktree(const char *id, const char *reason)
|
|
|
|
{
|
|
|
|
if (show_only || verbose)
|
|
|
|
printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason);
|
|
|
|
if (!show_only)
|
|
|
|
delete_git_dir(id);
|
|
|
|
}
|
|
|
|
|
2020-06-10 08:30:46 +02:00
|
|
|
static int prune_cmp(const void *a, const void *b)
|
|
|
|
{
|
|
|
|
const struct string_list_item *x = a;
|
|
|
|
const struct string_list_item *y = b;
|
|
|
|
int c;
|
|
|
|
|
|
|
|
if ((c = fspathcmp(x->string, y->string)))
|
|
|
|
return c;
|
2020-06-10 08:30:47 +02:00
|
|
|
/*
|
|
|
|
* paths same; prune_dupes() removes all but the first worktree entry
|
|
|
|
* having the same path, so sort main worktree ('util' is NULL) above
|
|
|
|
* linked worktrees ('util' not NULL) since main worktree can't be
|
|
|
|
* removed
|
|
|
|
*/
|
|
|
|
if (!x->util)
|
|
|
|
return -1;
|
|
|
|
if (!y->util)
|
|
|
|
return 1;
|
2020-06-10 08:30:46 +02:00
|
|
|
/* paths same; sort by .git/worktrees/<id> */
|
|
|
|
return strcmp(x->util, y->util);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void prune_dups(struct string_list *l)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
QSORT(l->items, l->nr, prune_cmp);
|
|
|
|
for (i = 1; i < l->nr; i++) {
|
|
|
|
if (!fspathcmp(l->items[i].string, l->items[i - 1].string))
|
|
|
|
prune_worktree(l->items[i].util, "duplicate entry");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-29 14:51:18 +02:00
|
|
|
static void prune_worktrees(void)
|
|
|
|
{
|
|
|
|
struct strbuf reason = STRBUF_INIT;
|
2020-06-10 08:30:47 +02:00
|
|
|
struct strbuf main_path = STRBUF_INIT;
|
2020-06-10 08:30:46 +02:00
|
|
|
struct string_list kept = STRING_LIST_INIT_NODUP;
|
2015-06-29 14:51:18 +02:00
|
|
|
DIR *dir = opendir(git_path("worktrees"));
|
|
|
|
struct dirent *d;
|
|
|
|
if (!dir)
|
|
|
|
return;
|
|
|
|
while ((d = readdir(dir)) != NULL) {
|
2020-06-10 08:30:46 +02:00
|
|
|
char *path;
|
2016-05-22 11:33:54 +02:00
|
|
|
if (is_dot_or_dotdot(d->d_name))
|
2015-06-29 14:51:18 +02:00
|
|
|
continue;
|
|
|
|
strbuf_reset(&reason);
|
2021-01-19 22:27:33 +01:00
|
|
|
if (should_prune_worktree(d->d_name, &reason, &path, expire))
|
2020-06-10 08:30:46 +02:00
|
|
|
prune_worktree(d->d_name, reason.buf);
|
|
|
|
else if (path)
|
|
|
|
string_list_append(&kept, path)->util = xstrdup(d->d_name);
|
2015-06-29 14:51:18 +02:00
|
|
|
}
|
|
|
|
closedir(dir);
|
2020-06-10 08:30:46 +02:00
|
|
|
|
2020-06-10 08:30:47 +02:00
|
|
|
strbuf_add_absolute_path(&main_path, get_git_common_dir());
|
|
|
|
/* massage main worktree absolute path to match 'gitdir' content */
|
|
|
|
strbuf_strip_suffix(&main_path, "/.");
|
|
|
|
string_list_append(&kept, strbuf_detach(&main_path, NULL));
|
2020-06-10 08:30:46 +02:00
|
|
|
prune_dups(&kept);
|
|
|
|
string_list_clear(&kept, 1);
|
|
|
|
|
2015-06-29 14:51:18 +02:00
|
|
|
if (!show_only)
|
2018-08-28 23:20:26 +02:00
|
|
|
delete_worktrees_dir_if_empty();
|
2015-06-29 14:51:18 +02:00
|
|
|
strbuf_release(&reason);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int prune(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
|
|
|
struct option options[] = {
|
|
|
|
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
|
2017-02-06 14:13:59 +01:00
|
|
|
OPT__VERBOSE(&verbose, N_("report pruned working trees")),
|
2015-06-29 14:51:18 +02:00
|
|
|
OPT_EXPIRY_DATE(0, "expire", &expire,
|
2017-02-06 14:13:59 +01:00
|
|
|
N_("expire working trees older than <time>")),
|
2015-06-29 14:51:18 +02:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2017-04-26 21:29:31 +02:00
|
|
|
expire = TIME_MAX;
|
2015-06-29 14:51:18 +02:00
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
|
|
if (ac)
|
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
prune_worktrees();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-06 19:30:55 +02:00
|
|
|
static char *junk_work_tree;
|
|
|
|
static char *junk_git_dir;
|
|
|
|
static int is_junk;
|
|
|
|
static pid_t junk_pid;
|
|
|
|
|
|
|
|
static void remove_junk(void)
|
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
if (!is_junk || getpid() != junk_pid)
|
|
|
|
return;
|
|
|
|
if (junk_git_dir) {
|
|
|
|
strbuf_addstr(&sb, junk_git_dir);
|
|
|
|
remove_dir_recursively(&sb, 0);
|
|
|
|
strbuf_reset(&sb);
|
|
|
|
}
|
|
|
|
if (junk_work_tree) {
|
|
|
|
strbuf_addstr(&sb, junk_work_tree);
|
|
|
|
remove_dir_recursively(&sb, 0);
|
|
|
|
}
|
|
|
|
strbuf_release(&sb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void remove_junk_on_signal(int signo)
|
|
|
|
{
|
|
|
|
remove_junk();
|
|
|
|
sigchain_pop(signo);
|
|
|
|
raise(signo);
|
|
|
|
}
|
|
|
|
|
2015-07-06 19:30:57 +02:00
|
|
|
static const char *worktree_basename(const char *path, int *olen)
|
|
|
|
{
|
|
|
|
const char *name;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
len = strlen(path);
|
|
|
|
while (len && is_dir_sep(path[len - 1]))
|
|
|
|
len--;
|
|
|
|
|
|
|
|
for (name = path + len - 1; name > path; name--)
|
|
|
|
if (is_dir_sep(*name)) {
|
|
|
|
name++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*olen = len;
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2020-06-10 08:30:48 +02:00
|
|
|
/* check that path is viable location for worktree */
|
|
|
|
static void check_candidate_path(const char *path,
|
|
|
|
int force,
|
|
|
|
struct worktree **worktrees,
|
|
|
|
const char *cmd)
|
2018-08-28 23:20:21 +02:00
|
|
|
{
|
2018-08-28 23:20:22 +02:00
|
|
|
struct worktree *wt;
|
|
|
|
int locked;
|
|
|
|
|
2018-08-28 23:20:21 +02:00
|
|
|
if (file_exists(path) && !is_empty_dir(path))
|
|
|
|
die(_("'%s' already exists"), path);
|
2018-08-28 23:20:22 +02:00
|
|
|
|
worktree: don't allow "add" validation to be fooled by suffix matching
"git worktree add <path>" performs various checks before approving
<path> as a valid location for the new worktree. Aside from ensuring
that <path> does not already exist, one of the questions it asks is
whether <path> is already a registered worktree. To perform this check,
it queries find_worktree() and disallows the "add" operation if
find_worktree() finds a match for <path>. As a convenience, however,
find_worktree() casts an overly wide net to allow users to identify
worktrees by shorthand in order to keep typing to a minimum. For
instance, it performs suffix matching which, given subtrees "foo/bar"
and "foo/baz", can correctly select the latter when asked only for
"baz".
"add" validation knows the exact path it is interrogating, so this sort
of heuristic-based matching is, at best, questionable for this use-case
and, at worst, may may accidentally interpret <path> as matching an
existing worktree and incorrectly report it as already registered even
when it isn't. (In fact, validate_worktree_add() already contains a
special case to avoid accidentally matching against the main worktree,
precisely due to this problem.)
Avoid the problem of potential accidental matching against an existing
worktree by instead taking advantage of find_worktree_by_path() which
matches paths deterministically, without applying any sort of magic
shorthand matching performed by find_worktree().
Reported-by: Cameron Gunnin <cameron.gunnin@synopsys.com>
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-24 10:08:48 +01:00
|
|
|
wt = find_worktree_by_path(worktrees, path);
|
2018-08-28 23:20:22 +02:00
|
|
|
if (!wt)
|
2020-06-10 08:30:48 +02:00
|
|
|
return;
|
2018-08-28 23:20:22 +02:00
|
|
|
|
2018-10-30 07:24:09 +01:00
|
|
|
locked = !!worktree_lock_reason(wt);
|
2020-06-10 08:30:48 +02:00
|
|
|
if ((!locked && force) || (locked && force > 1)) {
|
2018-08-28 23:20:23 +02:00
|
|
|
if (delete_git_dir(wt->id))
|
2020-06-10 08:30:48 +02:00
|
|
|
die(_("unusable worktree destination '%s'"), path);
|
|
|
|
return;
|
2018-08-28 23:20:23 +02:00
|
|
|
}
|
|
|
|
|
2018-08-28 23:20:22 +02:00
|
|
|
if (locked)
|
2020-11-20 16:09:39 +01:00
|
|
|
die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path, cmd);
|
2018-08-28 23:20:22 +02:00
|
|
|
else
|
2020-11-20 16:09:39 +01:00
|
|
|
die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), path, cmd);
|
2018-08-28 23:20:21 +02:00
|
|
|
}
|
|
|
|
|
2015-07-18 01:00:12 +02:00
|
|
|
static int add_worktree(const char *path, const char *refname,
|
2015-07-18 01:00:07 +02:00
|
|
|
const struct add_opts *opts)
|
2015-07-06 19:30:55 +02:00
|
|
|
{
|
|
|
|
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
|
2020-03-10 14:11:22 +01:00
|
|
|
struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
|
2015-07-06 19:30:55 +02:00
|
|
|
const char *name;
|
2016-08-05 22:38:44 +02:00
|
|
|
struct child_process cp = CHILD_PROCESS_INIT;
|
2020-07-28 22:24:27 +02:00
|
|
|
struct strvec child_env = STRVEC_INIT;
|
2019-02-20 17:16:48 +01:00
|
|
|
unsigned int counter = 0;
|
|
|
|
int len, ret;
|
2015-07-18 01:00:13 +02:00
|
|
|
struct strbuf symref = STRBUF_INIT;
|
|
|
|
struct commit *commit = NULL;
|
2017-12-07 22:20:17 +01:00
|
|
|
int is_branch = 0;
|
2019-03-08 10:28:34 +01:00
|
|
|
struct strbuf sb_name = STRBUF_INIT;
|
2020-06-10 08:30:48 +02:00
|
|
|
struct worktree **worktrees;
|
2015-07-06 19:30:55 +02:00
|
|
|
|
2020-06-20 01:35:44 +02:00
|
|
|
worktrees = get_worktrees();
|
2020-06-10 08:30:48 +02:00
|
|
|
check_candidate_path(path, opts->force, worktrees, "add");
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
worktrees = NULL;
|
2015-07-06 19:30:55 +02:00
|
|
|
|
2015-07-18 01:00:13 +02:00
|
|
|
/* is 'refname' a branch or commit? */
|
2016-02-15 14:35:32 +01:00
|
|
|
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
|
2017-12-07 22:20:17 +01:00
|
|
|
ref_exists(symref.buf)) {
|
|
|
|
is_branch = 1;
|
2015-07-18 01:00:13 +02:00
|
|
|
if (!opts->force)
|
2016-04-22 15:01:33 +02:00
|
|
|
die_if_checked_out(symref.buf, 0);
|
2015-07-18 01:00:13 +02:00
|
|
|
}
|
2017-12-07 22:20:17 +01:00
|
|
|
commit = lookup_commit_reference_by_name(refname);
|
|
|
|
if (!commit)
|
|
|
|
die(_("invalid reference: %s"), refname);
|
2015-07-18 01:00:13 +02:00
|
|
|
|
2015-07-06 19:30:57 +02:00
|
|
|
name = worktree_basename(path, &len);
|
2019-03-08 10:28:34 +01:00
|
|
|
strbuf_add(&sb, name, path + len - name);
|
|
|
|
sanitize_refname_component(sb.buf, &sb_name);
|
|
|
|
if (!sb_name.len)
|
|
|
|
BUG("How come '%s' becomes empty after sanitization?", sb.buf);
|
|
|
|
strbuf_reset(&sb);
|
|
|
|
name = sb_name.buf;
|
|
|
|
git_path_buf(&sb_repo, "worktrees/%s", name);
|
2015-07-06 19:30:55 +02:00
|
|
|
len = sb_repo.len;
|
|
|
|
if (safe_create_leading_directories_const(sb_repo.buf))
|
|
|
|
die_errno(_("could not create leading directories of '%s'"),
|
|
|
|
sb_repo.buf);
|
2019-02-20 17:16:48 +01:00
|
|
|
|
|
|
|
while (mkdir(sb_repo.buf, 0777)) {
|
2015-07-06 19:30:55 +02:00
|
|
|
counter++;
|
2019-02-20 17:16:48 +01:00
|
|
|
if ((errno != EEXIST) || !counter /* overflow */)
|
|
|
|
die_errno(_("could not create directory of '%s'"),
|
|
|
|
sb_repo.buf);
|
2015-07-06 19:30:55 +02:00
|
|
|
strbuf_setlen(&sb_repo, len);
|
|
|
|
strbuf_addf(&sb_repo, "%d", counter);
|
|
|
|
}
|
|
|
|
name = strrchr(sb_repo.buf, '/') + 1;
|
|
|
|
|
|
|
|
junk_pid = getpid();
|
|
|
|
atexit(remove_junk);
|
|
|
|
sigchain_push_common(remove_junk_on_signal);
|
|
|
|
|
|
|
|
junk_git_dir = xstrdup(sb_repo.buf);
|
|
|
|
is_junk = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lock the incomplete repo so prune won't delete it, unlock
|
|
|
|
* after the preparation is over.
|
|
|
|
*/
|
|
|
|
strbuf_addf(&sb, "%s/locked", sb_repo.buf);
|
2017-04-12 15:58:05 +02:00
|
|
|
if (!opts->keep_locked)
|
|
|
|
write_file(sb.buf, "initializing");
|
|
|
|
else
|
|
|
|
write_file(sb.buf, "added with --lock");
|
2015-07-06 19:30:55 +02:00
|
|
|
|
|
|
|
strbuf_addf(&sb_git, "%s/.git", path);
|
|
|
|
if (safe_create_leading_directories_const(sb_git.buf))
|
|
|
|
die_errno(_("could not create leading directories of '%s'"),
|
|
|
|
sb_git.buf);
|
|
|
|
junk_work_tree = xstrdup(path);
|
|
|
|
|
|
|
|
strbuf_reset(&sb);
|
|
|
|
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
|
2020-03-10 14:11:22 +01:00
|
|
|
strbuf_realpath(&realpath, sb_git.buf, 1);
|
|
|
|
write_file(sb.buf, "%s", realpath.buf);
|
|
|
|
strbuf_realpath(&realpath, get_git_common_dir(), 1);
|
2015-08-24 22:20:39 +02:00
|
|
|
write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
|
2020-03-10 14:11:22 +01:00
|
|
|
realpath.buf, name);
|
2015-07-06 19:30:55 +02:00
|
|
|
/*
|
|
|
|
* This is to keep resolve_ref() happy. We need a valid HEAD
|
2015-07-18 01:00:15 +02:00
|
|
|
* or is_git_directory() will reject the directory. Any value which
|
|
|
|
* looks like an object ID will do since it will be immediately
|
|
|
|
* replaced by the symbolic-ref or update-ref invocation in the new
|
|
|
|
* worktree.
|
2015-07-06 19:30:55 +02:00
|
|
|
*/
|
|
|
|
strbuf_reset(&sb);
|
|
|
|
strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
|
2019-08-18 22:04:19 +02:00
|
|
|
write_file(sb.buf, "%s", oid_to_hex(&null_oid));
|
2015-07-06 19:30:55 +02:00
|
|
|
strbuf_reset(&sb);
|
|
|
|
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
|
2015-08-24 22:20:39 +02:00
|
|
|
write_file(sb.buf, "../..");
|
2015-07-06 19:30:55 +02:00
|
|
|
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
|
|
|
|
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
|
2015-07-06 19:30:55 +02:00
|
|
|
cp.git_cmd = 1;
|
2015-07-18 01:00:14 +02:00
|
|
|
|
2017-12-07 22:20:17 +01:00
|
|
|
if (!is_branch)
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_pushl(&cp.args, "update-ref", "HEAD",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 22:26:31 +02:00
|
|
|
oid_to_hex(&commit->object.oid), NULL);
|
2018-08-15 22:56:30 +02:00
|
|
|
else {
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_pushl(&cp.args, "symbolic-ref", "HEAD",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 22:26:31 +02:00
|
|
|
symref.buf, NULL);
|
2018-08-15 22:56:30 +02:00
|
|
|
if (opts->quiet)
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_push(&cp.args, "--quiet");
|
2018-08-15 22:56:30 +02:00
|
|
|
}
|
|
|
|
|
2020-07-29 02:37:20 +02:00
|
|
|
cp.env = child_env.v;
|
2015-07-18 01:00:14 +02:00
|
|
|
ret = run_command(&cp);
|
|
|
|
if (ret)
|
|
|
|
goto done;
|
|
|
|
|
2016-03-29 12:11:01 +02:00
|
|
|
if (opts->checkout) {
|
|
|
|
cp.argv = NULL;
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_clear(&cp.args);
|
|
|
|
strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL);
|
2018-08-15 22:56:30 +02:00
|
|
|
if (opts->quiet)
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_push(&cp.args, "--quiet");
|
2020-07-29 02:37:20 +02:00
|
|
|
cp.env = child_env.v;
|
2016-03-29 12:11:01 +02:00
|
|
|
ret = run_command(&cp);
|
|
|
|
if (ret)
|
|
|
|
goto done;
|
2015-07-06 19:30:55 +02:00
|
|
|
}
|
2016-03-29 12:11:01 +02:00
|
|
|
|
|
|
|
is_junk = 0;
|
2017-06-16 01:15:49 +02:00
|
|
|
FREE_AND_NULL(junk_work_tree);
|
|
|
|
FREE_AND_NULL(junk_git_dir);
|
2016-03-29 12:11:01 +02:00
|
|
|
|
2015-07-18 01:00:14 +02:00
|
|
|
done:
|
2017-04-12 15:58:05 +02:00
|
|
|
if (ret || !opts->keep_locked) {
|
|
|
|
strbuf_reset(&sb);
|
|
|
|
strbuf_addf(&sb, "%s/locked", sb_repo.buf);
|
|
|
|
unlink_or_warn(sb.buf);
|
|
|
|
}
|
2017-12-07 22:20:17 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Hook failure does not warrant worktree deletion, so run hook after
|
|
|
|
* is_junk is cleared, but do return appropriate code when hook fails.
|
|
|
|
*/
|
worktree: add: fix 'post-checkout' not knowing new worktree location
Although "git worktree add" learned to run the 'post-checkout' hook in
ade546be47 (worktree: invoke post-checkout hook, 2017-12-07), it
neglected to change to the directory of the newly-created worktree
before running the hook. Instead, the hook runs within the directory
from which the "git worktree add" command itself was invoked, which
effectively neuters the hook since it knows nothing about the new
worktree directory.
Further, ade546be47 failed to sanitize the environment before running
the hook, which means that user-assigned values of GIT_DIR and
GIT_WORK_TREE could mislead the hook about the location of the new
worktree. In the case of "git worktree add" being run from a bare
repository, the GIT_DIR="." assigned by Git itself leaks into the hook's
environment and breaks Git commands; this is so even when the working
directory is correctly changed to the new worktree before the hook runs
since ".", relative to the new worktree directory, does not point at the
bare repository.
Fix these problems by (1) changing to the new worktree's directory
before running the hook, and (2) sanitizing the environment of GIT_DIR
and GIT_WORK_TREE so hooks can't be confused by misleading values.
Enhance the t2025 'post-checkout' tests to verify that the hook is
indeed run within the correct directory and that Git commands invoked by
the hook compute Git-dir and top-level worktree locations correctly.
While at it, also add two new tests: (1) verify that the hook is run
within the correct directory even when the new worktree is created from
a sibling worktree (as opposed to the main worktree); (2) verify that
the hook is provided with correct context when the new worktree is
created from a bare repository (test provided by Lars Schneider).
Implementation Notes:
Rather than sanitizing the environment of GIT_DIR and GIT_WORK_TREE, an
alternative would be to set them explicitly, as is already done for
other Git commands run internally by "git worktree add". This patch opts
instead to sanitize the environment in order to clearly document that
the worktree is fully functional by the time the hook is run, thus does
not require special environmental overrides.
The hook is run manually, rather than via run_hook_le(), since it needs
to change the working directory to that of the worktree, and
run_hook_le() does not provide such functionality. As this is a one-off
case, adding 'run_hook' overloads which allow the directory to be set
does not seem warranted at this time.
Reported-by: Lars Schneider <larsxschneider@gmail.com>
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-02-15 20:18:41 +01:00
|
|
|
if (!ret && opts->checkout) {
|
|
|
|
const char *hook = find_hook("post-checkout");
|
|
|
|
if (hook) {
|
|
|
|
const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
|
|
|
|
cp.git_cmd = 0;
|
|
|
|
cp.no_stdin = 1;
|
|
|
|
cp.stdout_to_stderr = 1;
|
|
|
|
cp.dir = path;
|
|
|
|
cp.env = env;
|
|
|
|
cp.argv = NULL;
|
2019-02-22 23:25:06 +01:00
|
|
|
cp.trace2_hook_name = "post-checkout";
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_pushl(&cp.args, absolute_path(hook),
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 22:26:31 +02:00
|
|
|
oid_to_hex(&null_oid),
|
|
|
|
oid_to_hex(&commit->object.oid),
|
|
|
|
"1", NULL);
|
worktree: add: fix 'post-checkout' not knowing new worktree location
Although "git worktree add" learned to run the 'post-checkout' hook in
ade546be47 (worktree: invoke post-checkout hook, 2017-12-07), it
neglected to change to the directory of the newly-created worktree
before running the hook. Instead, the hook runs within the directory
from which the "git worktree add" command itself was invoked, which
effectively neuters the hook since it knows nothing about the new
worktree directory.
Further, ade546be47 failed to sanitize the environment before running
the hook, which means that user-assigned values of GIT_DIR and
GIT_WORK_TREE could mislead the hook about the location of the new
worktree. In the case of "git worktree add" being run from a bare
repository, the GIT_DIR="." assigned by Git itself leaks into the hook's
environment and breaks Git commands; this is so even when the working
directory is correctly changed to the new worktree before the hook runs
since ".", relative to the new worktree directory, does not point at the
bare repository.
Fix these problems by (1) changing to the new worktree's directory
before running the hook, and (2) sanitizing the environment of GIT_DIR
and GIT_WORK_TREE so hooks can't be confused by misleading values.
Enhance the t2025 'post-checkout' tests to verify that the hook is
indeed run within the correct directory and that Git commands invoked by
the hook compute Git-dir and top-level worktree locations correctly.
While at it, also add two new tests: (1) verify that the hook is run
within the correct directory even when the new worktree is created from
a sibling worktree (as opposed to the main worktree); (2) verify that
the hook is provided with correct context when the new worktree is
created from a bare repository (test provided by Lars Schneider).
Implementation Notes:
Rather than sanitizing the environment of GIT_DIR and GIT_WORK_TREE, an
alternative would be to set them explicitly, as is already done for
other Git commands run internally by "git worktree add". This patch opts
instead to sanitize the environment in order to clearly document that
the worktree is fully functional by the time the hook is run, thus does
not require special environmental overrides.
The hook is run manually, rather than via run_hook_le(), since it needs
to change the working directory to that of the worktree, and
run_hook_le() does not provide such functionality. As this is a one-off
case, adding 'run_hook' overloads which allow the directory to be set
does not seem warranted at this time.
Reported-by: Lars Schneider <larsxschneider@gmail.com>
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-02-15 20:18:41 +01:00
|
|
|
ret = run_command(&cp);
|
|
|
|
}
|
|
|
|
}
|
2017-12-07 22:20:17 +01:00
|
|
|
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_clear(&child_env);
|
2015-07-06 19:30:55 +02:00
|
|
|
strbuf_release(&sb);
|
2015-07-18 01:00:13 +02:00
|
|
|
strbuf_release(&symref);
|
2015-07-06 19:30:55 +02:00
|
|
|
strbuf_release(&sb_repo);
|
|
|
|
strbuf_release(&sb_git);
|
2019-03-08 10:28:34 +01:00
|
|
|
strbuf_release(&sb_name);
|
2020-03-10 14:11:22 +01:00
|
|
|
strbuf_release(&realpath);
|
2015-07-06 19:30:55 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
worktree: improve message when creating a new worktree
Currently 'git worktree add' produces output like the following:
Preparing ../foo (identifier foo)
HEAD is now at 26da330922 <title>
The '../foo' is the path where the worktree is created, which the user
has just given on the command line. The identifier is an internal
implementation detail, which is not particularly relevant for the user
and indeed isn't mentioned explicitly anywhere in the man page.
Instead of this message, print a message that gives the user a bit more
detail of what exactly 'git worktree' is doing. There are various dwim
modes which perform some magic under the hood, which should be
helpful to users. Just from the output of the command it is not always
visible to users what exactly has happened.
Help the users a bit more by modifying the "Preparing ..." message and
adding some additional information of what 'git worktree add' did under
the hood, while not displaying the identifier anymore.
Currently there are several different cases:
- 'git worktree add -b ...' or 'git worktree add <path>', both of
which create a new branch, either through the user explicitly
requesting it, or through 'git worktree add' implicitly creating
it. This will end up with the following output:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add -B ...', which may either create a new branch if
the branch with the given name does not exist yet, or resets an
existing branch to the current HEAD, or the commit-ish given.
Depending on which action is taken, we'll end up with the following
output:
Preparing worktree (resetting branch '<branch>'; was at caa68db14)
HEAD is now at 26da330922 <title>
or:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add --detach' or 'git worktree add <path>
<commit-ish>', both of which create a new worktree with a detached
HEAD, for which we will print the following output:
Preparing worktree (detached HEAD 26da330922)
HEAD is now at 26da330922 <title>
- 'git worktree add <path> <local-branch>', which checks out the
branch and prints the following output:
Preparing worktree (checking out '<local-branch>')
HEAD is now at 47007d5 <title>
Additionally currently the "Preparing ..." line is printed to stderr,
while the "HEAD is now at ..." line is printed to stdout by 'git reset
--hard', which is used internally by 'git worktree add'. Fix this
inconsistency by printing the "Preparing ..." message to stdout as
well. As "Preparing ..." is not an error, stdout also seems like the
more appropriate output stream.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
Reviewed-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-24 23:56:33 +02:00
|
|
|
static void print_preparing_worktree_line(int detach,
|
|
|
|
const char *branch,
|
|
|
|
const char *new_branch,
|
|
|
|
int force_new_branch)
|
|
|
|
{
|
|
|
|
if (force_new_branch) {
|
|
|
|
struct commit *commit = lookup_commit_reference_by_name(new_branch);
|
|
|
|
if (!commit)
|
|
|
|
printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
|
|
|
|
else
|
|
|
|
printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"),
|
|
|
|
new_branch,
|
2018-05-23 07:38:18 +02:00
|
|
|
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
|
worktree: improve message when creating a new worktree
Currently 'git worktree add' produces output like the following:
Preparing ../foo (identifier foo)
HEAD is now at 26da330922 <title>
The '../foo' is the path where the worktree is created, which the user
has just given on the command line. The identifier is an internal
implementation detail, which is not particularly relevant for the user
and indeed isn't mentioned explicitly anywhere in the man page.
Instead of this message, print a message that gives the user a bit more
detail of what exactly 'git worktree' is doing. There are various dwim
modes which perform some magic under the hood, which should be
helpful to users. Just from the output of the command it is not always
visible to users what exactly has happened.
Help the users a bit more by modifying the "Preparing ..." message and
adding some additional information of what 'git worktree add' did under
the hood, while not displaying the identifier anymore.
Currently there are several different cases:
- 'git worktree add -b ...' or 'git worktree add <path>', both of
which create a new branch, either through the user explicitly
requesting it, or through 'git worktree add' implicitly creating
it. This will end up with the following output:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add -B ...', which may either create a new branch if
the branch with the given name does not exist yet, or resets an
existing branch to the current HEAD, or the commit-ish given.
Depending on which action is taken, we'll end up with the following
output:
Preparing worktree (resetting branch '<branch>'; was at caa68db14)
HEAD is now at 26da330922 <title>
or:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add --detach' or 'git worktree add <path>
<commit-ish>', both of which create a new worktree with a detached
HEAD, for which we will print the following output:
Preparing worktree (detached HEAD 26da330922)
HEAD is now at 26da330922 <title>
- 'git worktree add <path> <local-branch>', which checks out the
branch and prints the following output:
Preparing worktree (checking out '<local-branch>')
HEAD is now at 47007d5 <title>
Additionally currently the "Preparing ..." line is printed to stderr,
while the "HEAD is now at ..." line is printed to stdout by 'git reset
--hard', which is used internally by 'git worktree add'. Fix this
inconsistency by printing the "Preparing ..." message to stdout as
well. As "Preparing ..." is not an error, stdout also seems like the
more appropriate output stream.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
Reviewed-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-24 23:56:33 +02:00
|
|
|
} else if (new_branch) {
|
|
|
|
printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
|
|
|
|
} else {
|
|
|
|
struct strbuf s = STRBUF_INIT;
|
|
|
|
if (!detach && !strbuf_check_branch_ref(&s, branch) &&
|
|
|
|
ref_exists(s.buf))
|
|
|
|
printf_ln(_("Preparing worktree (checking out '%s')"),
|
|
|
|
branch);
|
|
|
|
else {
|
|
|
|
struct commit *commit = lookup_commit_reference_by_name(branch);
|
|
|
|
if (!commit)
|
|
|
|
die(_("invalid reference: %s"), branch);
|
|
|
|
printf_ln(_("Preparing worktree (detached HEAD %s)"),
|
2018-05-23 07:38:18 +02:00
|
|
|
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
|
worktree: improve message when creating a new worktree
Currently 'git worktree add' produces output like the following:
Preparing ../foo (identifier foo)
HEAD is now at 26da330922 <title>
The '../foo' is the path where the worktree is created, which the user
has just given on the command line. The identifier is an internal
implementation detail, which is not particularly relevant for the user
and indeed isn't mentioned explicitly anywhere in the man page.
Instead of this message, print a message that gives the user a bit more
detail of what exactly 'git worktree' is doing. There are various dwim
modes which perform some magic under the hood, which should be
helpful to users. Just from the output of the command it is not always
visible to users what exactly has happened.
Help the users a bit more by modifying the "Preparing ..." message and
adding some additional information of what 'git worktree add' did under
the hood, while not displaying the identifier anymore.
Currently there are several different cases:
- 'git worktree add -b ...' or 'git worktree add <path>', both of
which create a new branch, either through the user explicitly
requesting it, or through 'git worktree add' implicitly creating
it. This will end up with the following output:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add -B ...', which may either create a new branch if
the branch with the given name does not exist yet, or resets an
existing branch to the current HEAD, or the commit-ish given.
Depending on which action is taken, we'll end up with the following
output:
Preparing worktree (resetting branch '<branch>'; was at caa68db14)
HEAD is now at 26da330922 <title>
or:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add --detach' or 'git worktree add <path>
<commit-ish>', both of which create a new worktree with a detached
HEAD, for which we will print the following output:
Preparing worktree (detached HEAD 26da330922)
HEAD is now at 26da330922 <title>
- 'git worktree add <path> <local-branch>', which checks out the
branch and prints the following output:
Preparing worktree (checking out '<local-branch>')
HEAD is now at 47007d5 <title>
Additionally currently the "Preparing ..." line is printed to stderr,
while the "HEAD is now at ..." line is printed to stdout by 'git reset
--hard', which is used internally by 'git worktree add'. Fix this
inconsistency by printing the "Preparing ..." message to stdout as
well. As "Preparing ..." is not an error, stdout also seems like the
more appropriate output stream.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
Reviewed-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-24 23:56:33 +02:00
|
|
|
}
|
|
|
|
strbuf_release(&s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 23:56:34 +02:00
|
|
|
static const char *dwim_branch(const char *path, const char **new_branch)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
const char *s = worktree_basename(path, &n);
|
2018-04-24 23:56:35 +02:00
|
|
|
const char *branchname = xstrndup(s, n);
|
|
|
|
struct strbuf ref = STRBUF_INIT;
|
|
|
|
|
|
|
|
UNLEAK(branchname);
|
|
|
|
if (!strbuf_check_branch_ref(&ref, branchname) &&
|
|
|
|
ref_exists(ref.buf)) {
|
|
|
|
strbuf_release(&ref);
|
|
|
|
return branchname;
|
|
|
|
}
|
|
|
|
|
|
|
|
*new_branch = branchname;
|
2018-04-24 23:56:34 +02:00
|
|
|
if (guess_remote) {
|
|
|
|
struct object_id oid;
|
|
|
|
const char *remote =
|
2018-06-05 16:40:46 +02:00
|
|
|
unique_tracking_name(*new_branch, &oid, NULL);
|
2018-04-24 23:56:34 +02:00
|
|
|
return remote;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-07-06 19:30:50 +02:00
|
|
|
static int add(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
2015-07-18 01:00:07 +02:00
|
|
|
struct add_opts opts;
|
|
|
|
const char *new_branch_force = NULL;
|
2017-03-21 02:28:49 +01:00
|
|
|
char *path;
|
|
|
|
const char *branch;
|
2018-04-24 23:56:32 +02:00
|
|
|
const char *new_branch = NULL;
|
2017-11-26 20:43:53 +01:00
|
|
|
const char *opt_track = NULL;
|
2015-07-06 19:30:50 +02:00
|
|
|
struct option options[] = {
|
2018-02-09 12:01:42 +01:00
|
|
|
OPT__FORCE(&opts.force,
|
|
|
|
N_("checkout <branch> even if already checked out in other worktree"),
|
2018-02-09 12:02:20 +01:00
|
|
|
PARSE_OPT_NOCOMPLETE),
|
2018-04-24 23:56:32 +02:00
|
|
|
OPT_STRING('b', NULL, &new_branch, N_("branch"),
|
2015-07-06 19:30:53 +02:00
|
|
|
N_("create a new branch")),
|
|
|
|
OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
|
|
|
|
N_("create or reset a branch")),
|
2020-09-07 02:02:21 +02:00
|
|
|
OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
|
2016-03-29 12:11:01 +02:00
|
|
|
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
|
2017-04-12 15:58:05 +02:00
|
|
|
OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
|
2018-08-15 22:56:30 +02:00
|
|
|
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
|
2017-11-26 20:43:53 +01:00
|
|
|
OPT_PASSTHRU(0, "track", &opt_track, NULL,
|
|
|
|
N_("set up tracking mode (see git-branch(1))"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
|
2017-11-29 21:04:50 +01:00
|
|
|
OPT_BOOL(0, "guess-remote", &guess_remote,
|
|
|
|
N_("try to match the new branch name with a remote-tracking branch")),
|
2015-07-06 19:30:50 +02:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2015-07-18 01:00:07 +02:00
|
|
|
memset(&opts, 0, sizeof(opts));
|
2016-03-29 12:11:01 +02:00
|
|
|
opts.checkout = 1;
|
2015-07-06 19:30:50 +02:00
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
2018-04-24 23:56:32 +02:00
|
|
|
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
|
2015-07-18 01:00:08 +02:00
|
|
|
die(_("-b, -B, and --detach are mutually exclusive"));
|
2015-07-06 19:30:58 +02:00
|
|
|
if (ac < 1 || ac > 2)
|
|
|
|
usage_with_options(worktree_usage, options);
|
2015-07-06 19:30:50 +02:00
|
|
|
|
2017-03-21 02:22:28 +01:00
|
|
|
path = prefix_filename(prefix, av[0]);
|
2015-07-06 19:30:58 +02:00
|
|
|
branch = ac < 2 ? "HEAD" : av[1];
|
2015-07-06 19:30:50 +02:00
|
|
|
|
2016-05-27 15:17:08 +02:00
|
|
|
if (!strcmp(branch, "-"))
|
|
|
|
branch = "@{-1}";
|
|
|
|
|
2018-04-24 23:56:32 +02:00
|
|
|
if (new_branch_force) {
|
2016-02-15 14:35:33 +01:00
|
|
|
struct strbuf symref = STRBUF_INIT;
|
|
|
|
|
2018-04-24 23:56:32 +02:00
|
|
|
new_branch = new_branch_force;
|
2015-07-18 01:00:06 +02:00
|
|
|
|
2016-02-15 14:35:33 +01:00
|
|
|
if (!opts.force &&
|
2018-04-24 23:56:32 +02:00
|
|
|
!strbuf_check_branch_ref(&symref, new_branch) &&
|
2016-02-15 14:35:33 +01:00
|
|
|
ref_exists(symref.buf))
|
2016-04-22 15:01:33 +02:00
|
|
|
die_if_checked_out(symref.buf, 0);
|
2016-02-15 14:35:33 +01:00
|
|
|
strbuf_release(&symref);
|
|
|
|
}
|
|
|
|
|
2018-04-24 23:56:32 +02:00
|
|
|
if (ac < 2 && !new_branch && !opts.detach) {
|
2018-04-24 23:56:34 +02:00
|
|
|
const char *s = dwim_branch(path, &new_branch);
|
|
|
|
if (s)
|
|
|
|
branch = s;
|
2015-07-06 19:30:59 +02:00
|
|
|
}
|
|
|
|
|
2018-04-24 23:56:32 +02:00
|
|
|
if (ac == 2 && !new_branch && !opts.detach) {
|
2017-11-26 20:43:54 +01:00
|
|
|
struct object_id oid;
|
|
|
|
struct commit *commit;
|
|
|
|
const char *remote;
|
|
|
|
|
|
|
|
commit = lookup_commit_reference_by_name(branch);
|
|
|
|
if (!commit) {
|
2018-06-05 16:40:46 +02:00
|
|
|
remote = unique_tracking_name(branch, &oid, NULL);
|
2017-11-26 20:43:54 +01:00
|
|
|
if (remote) {
|
2018-04-24 23:56:32 +02:00
|
|
|
new_branch = branch;
|
2017-11-26 20:43:54 +01:00
|
|
|
branch = remote;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-15 22:56:30 +02:00
|
|
|
if (!opts.quiet)
|
|
|
|
print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);
|
worktree: improve message when creating a new worktree
Currently 'git worktree add' produces output like the following:
Preparing ../foo (identifier foo)
HEAD is now at 26da330922 <title>
The '../foo' is the path where the worktree is created, which the user
has just given on the command line. The identifier is an internal
implementation detail, which is not particularly relevant for the user
and indeed isn't mentioned explicitly anywhere in the man page.
Instead of this message, print a message that gives the user a bit more
detail of what exactly 'git worktree' is doing. There are various dwim
modes which perform some magic under the hood, which should be
helpful to users. Just from the output of the command it is not always
visible to users what exactly has happened.
Help the users a bit more by modifying the "Preparing ..." message and
adding some additional information of what 'git worktree add' did under
the hood, while not displaying the identifier anymore.
Currently there are several different cases:
- 'git worktree add -b ...' or 'git worktree add <path>', both of
which create a new branch, either through the user explicitly
requesting it, or through 'git worktree add' implicitly creating
it. This will end up with the following output:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add -B ...', which may either create a new branch if
the branch with the given name does not exist yet, or resets an
existing branch to the current HEAD, or the commit-ish given.
Depending on which action is taken, we'll end up with the following
output:
Preparing worktree (resetting branch '<branch>'; was at caa68db14)
HEAD is now at 26da330922 <title>
or:
Preparing worktree (new branch '<branch>')
HEAD is now at 26da330922 <title>
- 'git worktree add --detach' or 'git worktree add <path>
<commit-ish>', both of which create a new worktree with a detached
HEAD, for which we will print the following output:
Preparing worktree (detached HEAD 26da330922)
HEAD is now at 26da330922 <title>
- 'git worktree add <path> <local-branch>', which checks out the
branch and prints the following output:
Preparing worktree (checking out '<local-branch>')
HEAD is now at 47007d5 <title>
Additionally currently the "Preparing ..." line is printed to stderr,
while the "HEAD is now at ..." line is printed to stdout by 'git reset
--hard', which is used internally by 'git worktree add'. Fix this
inconsistency by printing the "Preparing ..." message to stdout as
well. As "Preparing ..." is not an error, stdout also seems like the
more appropriate output stream.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
Reviewed-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-04-24 23:56:33 +02:00
|
|
|
|
2018-04-24 23:56:32 +02:00
|
|
|
if (new_branch) {
|
2016-08-05 22:38:44 +02:00
|
|
|
struct child_process cp = CHILD_PROCESS_INIT;
|
2015-07-18 01:00:10 +02:00
|
|
|
cp.git_cmd = 1;
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_push(&cp.args, "branch");
|
2018-04-24 23:56:32 +02:00
|
|
|
if (new_branch_force)
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_push(&cp.args, "--force");
|
2018-08-15 22:56:30 +02:00
|
|
|
if (opts.quiet)
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_push(&cp.args, "--quiet");
|
|
|
|
strvec_push(&cp.args, new_branch);
|
|
|
|
strvec_push(&cp.args, branch);
|
2017-11-26 20:43:53 +01:00
|
|
|
if (opt_track)
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_push(&cp.args, opt_track);
|
2015-07-18 01:00:10 +02:00
|
|
|
if (run_command(&cp))
|
|
|
|
return -1;
|
2018-04-24 23:56:32 +02:00
|
|
|
branch = new_branch;
|
2017-11-26 20:43:53 +01:00
|
|
|
} else if (opt_track) {
|
|
|
|
die(_("--[no-]track can only be used if a new branch is created"));
|
2015-07-06 19:30:59 +02:00
|
|
|
}
|
|
|
|
|
add UNLEAK annotation for reducing leak false positives
It's a common pattern in git commands to allocate some
memory that should last for the lifetime of the program and
then not bother to free it, relying on the OS to throw it
away.
This keeps the code simple, and it's fast (we don't waste
time traversing structures or calling free at the end of the
program). But it also triggers warnings from memory-leak
checkers like valgrind or LSAN. They know that the memory
was still allocated at program exit, but they don't know
_when_ the leaked memory stopped being useful. If it was
early in the program, then it's probably a real and
important leak. But if it was used right up until program
exit, it's not an interesting leak and we'd like to suppress
it so that we can see the real leaks.
This patch introduces an UNLEAK() macro that lets us do so.
To understand its design, let's first look at some of the
alternatives.
Unfortunately the suppression systems offered by
leak-checking tools don't quite do what we want. A
leak-checker basically knows two things:
1. Which blocks were allocated via malloc, and the
callstack during the allocation.
2. Which blocks were left un-freed at the end of the
program (and which are unreachable, but more on that
later).
Their suppressions work by mentioning the function or
callstack of a particular allocation, and marking it as OK
to leak. So imagine you have code like this:
int cmd_foo(...)
{
/* this allocates some memory */
char *p = some_function();
printf("%s", p);
return 0;
}
You can say "ignore allocations from some_function(),
they're not leaks". But that's not right. That function may
be called elsewhere, too, and we would potentially want to
know about those leaks.
So you can say "ignore the callstack when main calls
some_function". That works, but your annotations are
brittle. In this case it's only two functions, but you can
imagine that the actual allocation is much deeper. If any of
the intermediate code changes, you have to update the
suppression.
What we _really_ want to say is that "the value assigned to
p at the end of the function is not a real leak". But
leak-checkers can't understand that; they don't know about
"p" in the first place.
However, we can do something a little bit tricky if we make
some assumptions about how leak-checkers work. They
generally don't just report all un-freed blocks. That would
report even globals which are still accessible when the
leak-check is run. Instead they take some set of memory
(like BSS) as a root and mark it as "reachable". Then they
scan the reachable blocks for anything that looks like a
pointer to a malloc'd block, and consider that block
reachable. And then they scan those blocks, and so on,
transitively marking anything reachable from a global as
"not leaked" (or at least leaked in a different category).
So we can mark the value of "p" as reachable by putting it
into a variable with program lifetime. One way to do that is
to just mark "p" as static. But that actually affects the
run-time behavior if the function is called twice (you
aren't likely to call main() twice, but some of our cmd_*()
functions are called from other commands).
Instead, we can trick the leak-checker by putting the value
into _any_ reachable bytes. This patch keeps a global
linked-list of bytes copied from "unleaked" variables. That
list is reachable even at program exit, which confers
recursive reachability on whatever values we unleak.
In other words, you can do:
int cmd_foo(...)
{
char *p = some_function();
printf("%s", p);
UNLEAK(p);
return 0;
}
to annotate "p" and suppress the leak report.
But wait, couldn't we just say "free(p)"? In this toy
example, yes. But UNLEAK()'s byte-copying strategy has
several advantages over actually freeing the memory:
1. It's recursive across structures. In many cases our "p"
is not just a pointer, but a complex struct whose
fields may have been allocated by a sub-function. And
in some cases (e.g., dir_struct) we don't even have a
function which knows how to free all of the struct
members.
By marking the struct itself as reachable, that confers
reachability on any pointers it contains (including those
found in embedded structs, or reachable by walking
heap blocks recursively.
2. It works on cases where we're not sure if the value is
allocated or not. For example:
char *p = argc > 1 ? argv[1] : some_function();
It's safe to use UNLEAK(p) here, because it's not
freeing any memory. In the case that we're pointing to
argv here, the reachability checker will just ignore
our bytes.
3. Likewise, it works even if the variable has _already_
been freed. We're just copying the pointer bytes. If
the block has been freed, the leak-checker will skip
over those bytes as uninteresting.
4. Because it's not actually freeing memory, you can
UNLEAK() before we are finished accessing the variable.
This is helpful in cases like this:
char *p = some_function();
return another_function(p);
Writing this with free() requires:
int ret;
char *p = some_function();
ret = another_function(p);
free(p);
return ret;
But with unleak we can just write:
char *p = some_function();
UNLEAK(p);
return another_function(p);
This patch adds the UNLEAK() macro and enables it
automatically when Git is compiled with SANITIZE=leak. In
normal builds it's a noop, so we pay no runtime cost.
It also adds some UNLEAK() annotations to show off how the
feature works. On top of other recent leak fixes, these are
enough to get t0000 and t0001 to pass when compiled with
LSAN.
Note the case in commit.c which actually converts a
strbuf_release() into an UNLEAK. This code was already
non-leaky, but the free didn't do anything useful, since
we're exiting. Converting it to an annotation means that
non-leak-checking builds pay no runtime cost. The cost is
minimal enough that it's probably not worth going on a
crusade to convert these kinds of frees to UNLEAKS. I did it
here for consistency with the "sb" leak (though it would
have been equally correct to go the other way, and turn them
both into strbuf_release() calls).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-09-08 08:38:41 +02:00
|
|
|
UNLEAK(path);
|
|
|
|
UNLEAK(opts);
|
2015-07-18 01:00:12 +02:00
|
|
|
return add_worktree(path, branch, &opts);
|
2015-07-06 19:30:50 +02:00
|
|
|
}
|
|
|
|
|
2015-10-08 19:01:05 +02:00
|
|
|
static void show_worktree_porcelain(struct worktree *wt)
|
|
|
|
{
|
2021-01-27 09:03:08 +01:00
|
|
|
const char *reason;
|
|
|
|
|
2015-10-08 19:01:05 +02:00
|
|
|
printf("worktree %s\n", wt->path);
|
|
|
|
if (wt->is_bare)
|
|
|
|
printf("bare\n");
|
|
|
|
else {
|
2017-10-16 00:07:08 +02:00
|
|
|
printf("HEAD %s\n", oid_to_hex(&wt->head_oid));
|
2015-10-08 19:01:05 +02:00
|
|
|
if (wt->is_detached)
|
|
|
|
printf("detached\n");
|
get_worktrees() must return main worktree as first item even on error
This is required by git-worktree.txt, stating that the main worktree is
the first line (especially in --porcelain mode when we can't just change
behavior at will).
There's only one case when get_worktrees() may skip main worktree, when
parse_ref() fails. Update the code so that we keep first item as main
worktree and return something sensible in this case:
- In user-friendly mode, since we're not constraint by anything,
returning "(error)" should do the job (we already show "(detached
HEAD)" which is not machine-friendly). Actually errors should be
printed on stderr by parse_ref() (*)
- In plumbing mode, we do not show neither 'bare', 'detached' or
'branch ...', which is possible by the format description if I read
it right.
Careful readers may realize that when the local variable "head_ref" in
get_main_worktree() is emptied, add_head_info() will do nothing to
wt->head_sha1. But that's ok because head_sha1 is zero-ized in the
previous patch.
(*) Well, it does not. But it's supposed to be a stop gap implementation
until we can reuse refs code to parse "ref: " stuff in HEAD, from
resolve_refs_unsafe(). Now may be the time since refs refactoring is
mostly done.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-11-28 10:36:54 +01:00
|
|
|
else if (wt->head_ref)
|
2015-10-08 19:01:05 +02:00
|
|
|
printf("branch %s\n", wt->head_ref);
|
|
|
|
}
|
2021-01-27 09:03:08 +01:00
|
|
|
|
|
|
|
reason = worktree_lock_reason(wt);
|
|
|
|
if (reason && *reason) {
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
quote_c_style(reason, &sb, NULL, 0);
|
|
|
|
printf("locked %s\n", sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
} else if (reason)
|
|
|
|
printf("locked\n");
|
|
|
|
|
2021-01-27 09:03:09 +01:00
|
|
|
reason = worktree_prune_reason(wt, expire);
|
|
|
|
if (reason)
|
|
|
|
printf("prunable %s\n", reason);
|
|
|
|
|
2015-10-08 19:01:05 +02:00
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
|
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
int cur_path_len = strlen(wt->path);
|
|
|
|
int path_adj = cur_path_len - utf8_strwidth(wt->path);
|
|
|
|
|
|
|
|
strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
|
|
|
|
if (wt->is_bare)
|
|
|
|
strbuf_addstr(&sb, "(bare)");
|
|
|
|
else {
|
|
|
|
strbuf_addf(&sb, "%-*s ", abbrev_len,
|
2018-03-12 03:27:30 +01:00
|
|
|
find_unique_abbrev(&wt->head_oid, DEFAULT_ABBREV));
|
2016-11-28 10:36:53 +01:00
|
|
|
if (wt->is_detached)
|
2015-10-08 19:01:05 +02:00
|
|
|
strbuf_addstr(&sb, "(detached HEAD)");
|
2017-05-04 15:59:13 +02:00
|
|
|
else if (wt->head_ref) {
|
|
|
|
char *ref = shorten_unambiguous_ref(wt->head_ref, 0);
|
|
|
|
strbuf_addf(&sb, "[%s]", ref);
|
|
|
|
free(ref);
|
|
|
|
} else
|
get_worktrees() must return main worktree as first item even on error
This is required by git-worktree.txt, stating that the main worktree is
the first line (especially in --porcelain mode when we can't just change
behavior at will).
There's only one case when get_worktrees() may skip main worktree, when
parse_ref() fails. Update the code so that we keep first item as main
worktree and return something sensible in this case:
- In user-friendly mode, since we're not constraint by anything,
returning "(error)" should do the job (we already show "(detached
HEAD)" which is not machine-friendly). Actually errors should be
printed on stderr by parse_ref() (*)
- In plumbing mode, we do not show neither 'bare', 'detached' or
'branch ...', which is possible by the format description if I read
it right.
Careful readers may realize that when the local variable "head_ref" in
get_main_worktree() is emptied, add_head_info() will do nothing to
wt->head_sha1. But that's ok because head_sha1 is zero-ized in the
previous patch.
(*) Well, it does not. But it's supposed to be a stop gap implementation
until we can reuse refs code to parse "ref: " stuff in HEAD, from
resolve_refs_unsafe(). Now may be the time since refs refactoring is
mostly done.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-11-28 10:36:54 +01:00
|
|
|
strbuf_addstr(&sb, "(error)");
|
2015-10-08 19:01:05 +02:00
|
|
|
}
|
|
|
|
|
2021-01-19 22:27:35 +01:00
|
|
|
if (worktree_lock_reason(wt))
|
2020-10-11 12:11:52 +02:00
|
|
|
strbuf_addstr(&sb, " locked");
|
|
|
|
|
2021-01-27 09:03:09 +01:00
|
|
|
if (worktree_prune_reason(wt, expire))
|
|
|
|
strbuf_addstr(&sb, " prunable");
|
|
|
|
|
2020-10-11 12:11:52 +02:00
|
|
|
printf("%s\n", sb.buf);
|
2015-10-08 19:01:05 +02:00
|
|
|
strbuf_release(&sb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; wt[i]; i++) {
|
|
|
|
int sha1_len;
|
|
|
|
int path_len = strlen(wt[i]->path);
|
|
|
|
|
|
|
|
if (path_len > *maxlen)
|
|
|
|
*maxlen = path_len;
|
2018-03-12 03:27:30 +01:00
|
|
|
sha1_len = strlen(find_unique_abbrev(&wt[i]->head_oid, *abbrev));
|
2015-10-08 19:01:05 +02:00
|
|
|
if (sha1_len > *abbrev)
|
|
|
|
*abbrev = sha1_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-20 01:35:43 +02:00
|
|
|
static int pathcmp(const void *a_, const void *b_)
|
|
|
|
{
|
|
|
|
const struct worktree *const *a = a_;
|
|
|
|
const struct worktree *const *b = b_;
|
|
|
|
return fspathcmp((*a)->path, (*b)->path);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pathsort(struct worktree **wt)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
struct worktree **p = wt;
|
|
|
|
|
|
|
|
while (*p++)
|
|
|
|
n++;
|
|
|
|
QSORT(wt, n, pathcmp);
|
|
|
|
}
|
|
|
|
|
2015-10-08 19:01:05 +02:00
|
|
|
static int list(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
|
|
|
int porcelain = 0;
|
|
|
|
|
|
|
|
struct option options[] = {
|
|
|
|
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
|
2021-01-27 09:03:09 +01:00
|
|
|
OPT_EXPIRY_DATE(0, "expire", &expire,
|
|
|
|
N_("add 'prunable' annotation to worktrees older than <time>")),
|
2015-10-08 19:01:05 +02:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2021-01-27 09:03:09 +01:00
|
|
|
expire = TIME_MAX;
|
2015-10-08 19:01:05 +02:00
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
|
|
if (ac)
|
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
else {
|
2020-06-20 01:35:44 +02:00
|
|
|
struct worktree **worktrees = get_worktrees();
|
2015-10-08 19:01:05 +02:00
|
|
|
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
|
|
|
|
|
2020-06-20 01:35:43 +02:00
|
|
|
/* sort worktrees by path but keep main worktree at top */
|
|
|
|
pathsort(worktrees + 1);
|
|
|
|
|
2015-10-08 19:01:05 +02:00
|
|
|
if (!porcelain)
|
|
|
|
measure_widths(worktrees, &abbrev, &path_maxlen);
|
|
|
|
|
|
|
|
for (i = 0; worktrees[i]; i++) {
|
|
|
|
if (porcelain)
|
|
|
|
show_worktree_porcelain(worktrees[i]);
|
|
|
|
else
|
|
|
|
show_worktree(worktrees[i], path_maxlen, abbrev);
|
|
|
|
}
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-13 14:18:24 +02:00
|
|
|
static int lock_worktree(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
|
|
|
const char *reason = "", *old_reason;
|
|
|
|
struct option options[] = {
|
|
|
|
OPT_STRING(0, "reason", &reason, N_("string"),
|
|
|
|
N_("reason for locking")),
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
struct worktree **worktrees, *wt;
|
|
|
|
|
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
|
|
if (ac != 1)
|
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
|
2020-06-20 01:35:44 +02:00
|
|
|
worktrees = get_worktrees();
|
2016-06-13 14:18:24 +02:00
|
|
|
wt = find_worktree(worktrees, prefix, av[0]);
|
|
|
|
if (!wt)
|
|
|
|
die(_("'%s' is not a working tree"), av[0]);
|
|
|
|
if (is_main_worktree(wt))
|
|
|
|
die(_("The main working tree cannot be locked or unlocked"));
|
|
|
|
|
2018-10-30 07:24:09 +01:00
|
|
|
old_reason = worktree_lock_reason(wt);
|
2016-06-13 14:18:24 +02:00
|
|
|
if (old_reason) {
|
|
|
|
if (*old_reason)
|
|
|
|
die(_("'%s' is already locked, reason: %s"),
|
|
|
|
av[0], old_reason);
|
|
|
|
die(_("'%s' is already locked"), av[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
write_file(git_common_path("worktrees/%s/locked", wt->id),
|
|
|
|
"%s", reason);
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-13 14:18:25 +02:00
|
|
|
static int unlock_worktree(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
|
|
|
struct option options[] = {
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
struct worktree **worktrees, *wt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
|
|
if (ac != 1)
|
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
|
2020-06-20 01:35:44 +02:00
|
|
|
worktrees = get_worktrees();
|
2016-06-13 14:18:25 +02:00
|
|
|
wt = find_worktree(worktrees, prefix, av[0]);
|
|
|
|
if (!wt)
|
|
|
|
die(_("'%s' is not a working tree"), av[0]);
|
|
|
|
if (is_main_worktree(wt))
|
|
|
|
die(_("The main working tree cannot be locked or unlocked"));
|
2018-10-30 07:24:09 +01:00
|
|
|
if (!worktree_lock_reason(wt))
|
2016-06-13 14:18:25 +02:00
|
|
|
die(_("'%s' is not locked"), av[0]);
|
|
|
|
ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-02-12 10:49:38 +01:00
|
|
|
static void validate_no_submodules(const struct worktree *wt)
|
|
|
|
{
|
|
|
|
struct index_state istate = { NULL };
|
2019-01-05 06:08:40 +01:00
|
|
|
struct strbuf path = STRBUF_INIT;
|
2018-02-12 10:49:38 +01:00
|
|
|
int i, found_submodules = 0;
|
|
|
|
|
2019-01-05 06:08:40 +01:00
|
|
|
if (is_directory(worktree_git_path(wt, "modules"))) {
|
|
|
|
/*
|
|
|
|
* There could be false positives, e.g. the "modules"
|
|
|
|
* directory exists but is empty. But it's a rare case and
|
|
|
|
* this simpler check is probably good enough for now.
|
|
|
|
*/
|
|
|
|
found_submodules = 1;
|
|
|
|
} else if (read_index_from(&istate, worktree_git_path(wt, "index"),
|
|
|
|
get_worktree_git_dir(wt)) > 0) {
|
2018-02-12 10:49:38 +01:00
|
|
|
for (i = 0; i < istate.cache_nr; i++) {
|
|
|
|
struct cache_entry *ce = istate.cache[i];
|
2019-01-05 06:08:40 +01:00
|
|
|
int err;
|
2018-02-12 10:49:38 +01:00
|
|
|
|
2019-01-05 06:08:40 +01:00
|
|
|
if (!S_ISGITLINK(ce->ce_mode))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
strbuf_reset(&path);
|
|
|
|
strbuf_addf(&path, "%s/%s", wt->path, ce->name);
|
|
|
|
if (!is_submodule_populated_gently(path.buf, &err))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
found_submodules = 1;
|
|
|
|
break;
|
2018-02-12 10:49:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
discard_index(&istate);
|
2019-01-05 06:08:40 +01:00
|
|
|
strbuf_release(&path);
|
2018-02-12 10:49:38 +01:00
|
|
|
|
|
|
|
if (found_submodules)
|
2018-02-12 10:49:39 +01:00
|
|
|
die(_("working trees containing submodules cannot be moved or removed"));
|
2018-02-12 10:49:38 +01:00
|
|
|
}
|
|
|
|
|
2018-02-12 10:49:36 +01:00
|
|
|
static int move_worktree(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
2018-08-28 23:20:24 +02:00
|
|
|
int force = 0;
|
2018-02-12 10:49:36 +01:00
|
|
|
struct option options[] = {
|
2018-08-28 23:20:24 +02:00
|
|
|
OPT__FORCE(&force,
|
|
|
|
N_("force move even if worktree is dirty or locked"),
|
|
|
|
PARSE_OPT_NOCOMPLETE),
|
2018-02-12 10:49:36 +01:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
struct worktree **worktrees, *wt;
|
|
|
|
struct strbuf dst = STRBUF_INIT;
|
|
|
|
struct strbuf errmsg = STRBUF_INIT;
|
2018-08-28 23:20:24 +02:00
|
|
|
const char *reason = NULL;
|
2018-02-12 10:49:36 +01:00
|
|
|
char *path;
|
|
|
|
|
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
|
|
if (ac != 2)
|
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
|
|
|
|
path = prefix_filename(prefix, av[1]);
|
|
|
|
strbuf_addstr(&dst, path);
|
|
|
|
free(path);
|
|
|
|
|
2020-06-20 01:35:44 +02:00
|
|
|
worktrees = get_worktrees();
|
2018-02-12 10:49:36 +01:00
|
|
|
wt = find_worktree(worktrees, prefix, av[0]);
|
|
|
|
if (!wt)
|
|
|
|
die(_("'%s' is not a working tree"), av[0]);
|
|
|
|
if (is_main_worktree(wt))
|
|
|
|
die(_("'%s' is a main working tree"), av[0]);
|
2018-02-12 10:49:37 +01:00
|
|
|
if (is_directory(dst.buf)) {
|
|
|
|
const char *sep = find_last_dir_sep(wt->path);
|
|
|
|
|
|
|
|
if (!sep)
|
|
|
|
die(_("could not figure out destination name from '%s'"),
|
|
|
|
wt->path);
|
|
|
|
strbuf_trim_trailing_dir_sep(&dst);
|
|
|
|
strbuf_addstr(&dst, sep);
|
|
|
|
}
|
2020-06-10 08:30:49 +02:00
|
|
|
check_candidate_path(dst.buf, force, worktrees, "move");
|
2018-02-12 10:49:36 +01:00
|
|
|
|
2018-02-12 10:49:38 +01:00
|
|
|
validate_no_submodules(wt);
|
|
|
|
|
2018-08-28 23:20:24 +02:00
|
|
|
if (force < 2)
|
2018-10-30 07:24:09 +01:00
|
|
|
reason = worktree_lock_reason(wt);
|
2018-02-12 10:49:36 +01:00
|
|
|
if (reason) {
|
|
|
|
if (*reason)
|
2018-08-28 23:20:24 +02:00
|
|
|
die(_("cannot move a locked working tree, lock reason: %s\nuse 'move -f -f' to override or unlock first"),
|
2018-02-12 10:49:36 +01:00
|
|
|
reason);
|
2018-08-28 23:20:24 +02:00
|
|
|
die(_("cannot move a locked working tree;\nuse 'move -f -f' to override or unlock first"));
|
2018-02-12 10:49:36 +01:00
|
|
|
}
|
2018-02-12 10:49:40 +01:00
|
|
|
if (validate_worktree(wt, &errmsg, 0))
|
2018-02-12 10:49:36 +01:00
|
|
|
die(_("validation failed, cannot move working tree: %s"),
|
|
|
|
errmsg.buf);
|
|
|
|
strbuf_release(&errmsg);
|
|
|
|
|
|
|
|
if (rename(wt->path, dst.buf) == -1)
|
|
|
|
die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf);
|
|
|
|
|
|
|
|
update_worktree_location(wt, dst.buf);
|
|
|
|
|
|
|
|
strbuf_release(&dst);
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-02-12 10:49:39 +01:00
|
|
|
/*
|
|
|
|
* Note, "git status --porcelain" is used to determine if it's safe to
|
|
|
|
* delete a whole worktree. "git status" does not ignore user
|
|
|
|
* configuration, so if a normal "git status" shows "clean" for the
|
|
|
|
* user, then it's ok to remove it.
|
|
|
|
*
|
|
|
|
* This assumption may be a bad one. We may want to ignore
|
|
|
|
* (potentially bad) user settings and only delete a worktree when
|
|
|
|
* it's absolutely safe to do so from _our_ point of view because we
|
|
|
|
* know better.
|
|
|
|
*/
|
|
|
|
static void check_clean_worktree(struct worktree *wt,
|
|
|
|
const char *original_path)
|
|
|
|
{
|
|
|
|
struct child_process cp;
|
|
|
|
char buf[1];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Until we sort this out, all submodules are "dirty" and
|
|
|
|
* will abort this function.
|
|
|
|
*/
|
|
|
|
validate_no_submodules(wt);
|
|
|
|
|
2020-08-27 07:25:04 +02:00
|
|
|
child_process_init(&cp);
|
2020-09-09 22:53:07 +02:00
|
|
|
strvec_pushf(&cp.env_array, "%s=%s/.git",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 22:26:31 +02:00
|
|
|
GIT_DIR_ENVIRONMENT, wt->path);
|
2020-09-09 22:53:07 +02:00
|
|
|
strvec_pushf(&cp.env_array, "%s=%s",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 22:26:31 +02:00
|
|
|
GIT_WORK_TREE_ENVIRONMENT, wt->path);
|
2020-07-28 22:24:27 +02:00
|
|
|
strvec_pushl(&cp.args, "status",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 22:26:31 +02:00
|
|
|
"--porcelain", "--ignore-submodules=none",
|
|
|
|
NULL);
|
2018-02-12 10:49:39 +01:00
|
|
|
cp.git_cmd = 1;
|
|
|
|
cp.dir = wt->path;
|
|
|
|
cp.out = -1;
|
|
|
|
ret = start_command(&cp);
|
|
|
|
if (ret)
|
|
|
|
die_errno(_("failed to run 'git status' on '%s'"),
|
|
|
|
original_path);
|
|
|
|
ret = xread(cp.out, buf, sizeof(buf));
|
|
|
|
if (ret)
|
2019-08-13 20:02:44 +02:00
|
|
|
die(_("'%s' contains modified or untracked files, use --force to delete it"),
|
2018-02-12 10:49:39 +01:00
|
|
|
original_path);
|
|
|
|
close(cp.out);
|
|
|
|
ret = finish_command(&cp);
|
|
|
|
if (ret)
|
|
|
|
die_errno(_("failed to run 'git status' on '%s', code %d"),
|
|
|
|
original_path, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int delete_git_work_tree(struct worktree *wt)
|
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
strbuf_addstr(&sb, wt->path);
|
|
|
|
if (remove_dir_recursively(&sb, 0)) {
|
|
|
|
error_errno(_("failed to delete '%s'"), sb.buf);
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
strbuf_release(&sb);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int remove_worktree(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
|
|
|
int force = 0;
|
|
|
|
struct option options[] = {
|
2018-04-17 20:19:39 +02:00
|
|
|
OPT__FORCE(&force,
|
2018-08-28 23:20:25 +02:00
|
|
|
N_("force removal even if worktree is dirty or locked"),
|
2018-04-17 20:19:39 +02:00
|
|
|
PARSE_OPT_NOCOMPLETE),
|
2018-02-12 10:49:39 +01:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
struct worktree **worktrees, *wt;
|
|
|
|
struct strbuf errmsg = STRBUF_INIT;
|
2018-08-28 23:20:25 +02:00
|
|
|
const char *reason = NULL;
|
2018-02-12 10:49:39 +01:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
|
|
|
if (ac != 1)
|
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
|
2020-06-20 01:35:44 +02:00
|
|
|
worktrees = get_worktrees();
|
2018-02-12 10:49:39 +01:00
|
|
|
wt = find_worktree(worktrees, prefix, av[0]);
|
|
|
|
if (!wt)
|
|
|
|
die(_("'%s' is not a working tree"), av[0]);
|
|
|
|
if (is_main_worktree(wt))
|
|
|
|
die(_("'%s' is a main working tree"), av[0]);
|
2018-08-28 23:20:25 +02:00
|
|
|
if (force < 2)
|
2018-10-30 07:24:09 +01:00
|
|
|
reason = worktree_lock_reason(wt);
|
2018-02-12 10:49:39 +01:00
|
|
|
if (reason) {
|
|
|
|
if (*reason)
|
2018-08-28 23:20:25 +02:00
|
|
|
die(_("cannot remove a locked working tree, lock reason: %s\nuse 'remove -f -f' to override or unlock first"),
|
2018-02-12 10:49:39 +01:00
|
|
|
reason);
|
2018-08-28 23:20:25 +02:00
|
|
|
die(_("cannot remove a locked working tree;\nuse 'remove -f -f' to override or unlock first"));
|
2018-02-12 10:49:39 +01:00
|
|
|
}
|
2018-02-12 10:49:40 +01:00
|
|
|
if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK))
|
2018-02-12 10:49:39 +01:00
|
|
|
die(_("validation failed, cannot remove working tree: %s"),
|
|
|
|
errmsg.buf);
|
|
|
|
strbuf_release(&errmsg);
|
|
|
|
|
2018-02-12 10:49:40 +01:00
|
|
|
if (file_exists(wt->path)) {
|
|
|
|
if (!force)
|
|
|
|
check_clean_worktree(wt, av[0]);
|
2018-02-12 10:49:39 +01:00
|
|
|
|
2018-02-12 10:49:40 +01:00
|
|
|
ret |= delete_git_work_tree(wt);
|
|
|
|
}
|
2018-02-12 10:49:39 +01:00
|
|
|
/*
|
|
|
|
* continue on even if ret is non-zero, there's no going back
|
|
|
|
* from here.
|
|
|
|
*/
|
2018-08-28 23:20:20 +02:00
|
|
|
ret |= delete_git_dir(wt->id);
|
2018-08-28 23:20:26 +02:00
|
|
|
delete_worktrees_dir_if_empty();
|
2018-02-12 10:49:39 +01:00
|
|
|
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-08-31 08:57:57 +02:00
|
|
|
static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)
|
|
|
|
{
|
|
|
|
if (!iserr) {
|
|
|
|
printf_ln(_("repair: %s: %s"), msg, path);
|
|
|
|
} else {
|
|
|
|
int *exit_status = (int *)cb_data;
|
|
|
|
fprintf_ln(stderr, _("error: %s: %s"), msg, path);
|
|
|
|
*exit_status = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 10:21:25 +02:00
|
|
|
static int repair(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
2020-08-31 08:57:58 +02:00
|
|
|
const char **p;
|
|
|
|
const char *self[] = { ".", NULL };
|
2020-08-27 10:21:25 +02:00
|
|
|
struct option options[] = {
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
2020-08-31 08:57:58 +02:00
|
|
|
p = ac > 0 ? av : self;
|
|
|
|
for (; *p; p++)
|
|
|
|
repair_worktree_at_path(*p, report_repair, &rc);
|
worktree: teach `repair` to fix multi-directional breakage
`git worktree repair` knows how to repair the two-way links between the
repository and a worktree as long as a link in one or the other
direction is sound. For instance, if a linked worktree is moved (without
using `git worktree move`), repair is possible because the worktree
still knows the location of the repository even though the repository no
longer knows where the worktree is. Similarly, if the repository is
moved, repair is possible since the repository still knows the locations
of the worktrees even though the worktrees no longer know where the
repository is.
However, if both the repository and the worktrees are moved, then links
are severed in both directions, and no repair is possible. This is the
case even when the new worktree locations are specified as arguments to
`git worktree repair`. The reason for this limitation is twofold. First,
when `repair` consults the worktree's gitfile (/path/to/worktree/.git)
to determine the corresponding <repo>/worktrees/<id>/gitdir file to fix,
<repo> is the old path to the repository, thus it is unable to fix the
`gitdir` file at its new location since it doesn't know where it is.
Second, when `repair` consults <repo>/worktrees/<id>/gitdir to find the
location of the worktree's gitfile (/path/to/worktree/.git), the path
recorded in `gitdir` is the old location of the worktree's gitfile, thus
it is unable to repair the gitfile since it doesn't know where it is.
Fix these shortcomings by teaching `repair` to attempt to infer the new
location of the <repo>/worktrees/<id>/gitdir file when the location
recorded in the worktree's gitfile has become stale but the file is
otherwise well-formed. The inference is intentionally simple-minded.
For each worktree path specified as an argument, `git worktree repair`
manually reads the ".git" gitfile at that location and, if it is
well-formed, extracts the <id>. It then searches for a corresponding
<id> in <repo>/worktrees/ and, if found, concludes that there is a
reasonable match and updates <repo>/worktrees/<id>/gitdir to point at
the specified worktree path. In order for <repo> to be known, `git
worktree repair` must be run in the main worktree or bare repository.
`git worktree repair` first attempts to repair each incoming
/path/to/worktree/.git gitfile to point at the repository, and then
attempts to repair outgoing <repo>/worktrees/<id>/gitdir files to point
at the worktrees. This sequence was chosen arbitrarily when originally
implemented since the order of fixes is immaterial as long as one side
of the two-way link between the repository and a worktree is sound.
However, for this new repair technique to work, the order must be
reversed. This is because the new inference mechanism, when it is
successful, allows the outgoing <repo>/worktrees/<id>/gitdir file to be
repaired, thus fixing one side of the two-way link. Once that side is
fixed, the other side can be fixed by the existing repair mechanism,
hence the order of repairs is now significant.
Two safeguards are employed to avoid hijacking a worktree from a
different repository if the user accidentally specifies a foreign
worktree as an argument. The first, as described above, is that it
requires an <id> match between the repository and the worktree. That
itself is not foolproof for preventing hijack, so the second safeguard
is that the inference will only kick in if the worktree's
/path/to/worktree/.git gitfile does not point at a repository.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-12-21 09:16:01 +01:00
|
|
|
repair_worktrees(report_repair, &rc);
|
2020-08-27 10:21:25 +02:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-06-29 14:51:18 +02:00
|
|
|
int cmd_worktree(int ac, const char **av, const char *prefix)
|
|
|
|
{
|
|
|
|
struct option options[] = {
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2017-11-29 21:04:51 +01:00
|
|
|
git_config(git_worktree_config, NULL);
|
2016-09-27 08:49:39 +02:00
|
|
|
|
2015-06-29 14:51:18 +02:00
|
|
|
if (ac < 2)
|
|
|
|
usage_with_options(worktree_usage, options);
|
2016-05-22 11:33:56 +02:00
|
|
|
if (!prefix)
|
|
|
|
prefix = "";
|
2015-07-06 19:30:50 +02:00
|
|
|
if (!strcmp(av[1], "add"))
|
|
|
|
return add(ac - 1, av + 1, prefix);
|
2015-06-29 14:51:18 +02:00
|
|
|
if (!strcmp(av[1], "prune"))
|
|
|
|
return prune(ac - 1, av + 1, prefix);
|
2015-10-08 19:01:05 +02:00
|
|
|
if (!strcmp(av[1], "list"))
|
|
|
|
return list(ac - 1, av + 1, prefix);
|
2016-06-13 14:18:24 +02:00
|
|
|
if (!strcmp(av[1], "lock"))
|
|
|
|
return lock_worktree(ac - 1, av + 1, prefix);
|
2016-06-13 14:18:25 +02:00
|
|
|
if (!strcmp(av[1], "unlock"))
|
|
|
|
return unlock_worktree(ac - 1, av + 1, prefix);
|
2018-02-12 10:49:36 +01:00
|
|
|
if (!strcmp(av[1], "move"))
|
|
|
|
return move_worktree(ac - 1, av + 1, prefix);
|
2018-02-12 10:49:39 +01:00
|
|
|
if (!strcmp(av[1], "remove"))
|
|
|
|
return remove_worktree(ac - 1, av + 1, prefix);
|
2020-08-27 10:21:25 +02:00
|
|
|
if (!strcmp(av[1], "repair"))
|
|
|
|
return repair(ac - 1, av + 1, prefix);
|
2015-06-29 14:51:18 +02:00
|
|
|
usage_with_options(worktree_usage, options);
|
|
|
|
}
|