Skip to content

Commit

Permalink
Merge branch 'es/worktree-add-cleanup'
Browse files Browse the repository at this point in the history
The "new-worktree-mode" hack in "checkout" that was added in
nd/multiple-work-trees topic has been removed by updating the
implementation of new "worktree add".

* es/worktree-add-cleanup: (25 commits)
  Documentation/git-worktree: fix duplicated 'from'
  Documentation/config: mention "now" and "never" for 'expire' settings
  Documentation/git-worktree: fix broken 'linkgit' invocation
  checkout: drop intimate knowledge of newly created worktree
  worktree: populate via "git reset --hard" rather than "git checkout"
  worktree: avoid resolving HEAD unnecessarily
  worktree: make setup of new HEAD distinct from worktree population
  worktree: detect branch-name/detached and error conditions locally
  worktree: add_worktree: construct worktree-population command locally
  worktree: elucidate environment variables intended for child processes
  worktree: make branch creation distinct from worktree population
  worktree: add: suppress auto-vivication with --detach and no <branch>
  worktree: make --detach mutually exclusive with -b/-B
  worktree: introduce options container
  worktree: simplify new branch (-b/-B) option checking
  worktree: improve worktree setup message
  branch: publish die_if_checked_out()
  checkout: teach check_linked_checkout() about symbolic link HEAD
  checkout: check_linked_checkout: simplify symref parsing
  checkout: check_linked_checkout: improve "already checked out" aesthetic
  ...
  • Loading branch information
Junio C Hamano committed Aug 12, 2015
2 parents 7aa2da6 + 65f9b75 commit 53860f0
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 126 deletions.
16 changes: 11 additions & 5 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1307,28 +1307,34 @@ gc.packRefs::
gc.pruneExpire::
When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'.
Override the grace period with this config variable. The value
"now" may be used to disable this grace period and always prune
unreachable objects immediately.
"now" may be used to disable this grace period and always prune
unreachable objects immediately, or "never" may be used to
suppress pruning.

gc.worktreePruneExpire::
When 'git gc' is run, it calls
'git worktree prune --expire 3.months.ago'.
This config variable can be used to set a different grace
period. The value "now" may be used to disable the grace
period and prune $GIT_DIR/worktrees immediately.
period and prune $GIT_DIR/worktrees immediately, or "never"
may be used to suppress pruning.

gc.reflogExpire::
gc.<pattern>.reflogExpire::
'git reflog expire' removes reflog entries older than
this time; defaults to 90 days. With "<pattern>" (e.g.
this time; defaults to 90 days. The value "now" expires all
entries immediately, and "never" suppresses expiration
altogether. With "<pattern>" (e.g.
"refs/stash") in the middle the setting applies only to
the refs that match the <pattern>.

gc.reflogExpireUnreachable::
gc.<ref>.reflogExpireUnreachable::
'git reflog expire' removes reflog entries older than
this time and are not reachable from the current tip;
defaults to 30 days. With "<pattern>" (e.g. "refs/stash")
defaults to 30 days. The value "now" expires all entries
immediately, and "never" suppresses expiration altogether.
With "<pattern>" (e.g. "refs/stash")
in the middle, the setting applies only to the refs that
match the <pattern>.

Expand Down
10 changes: 5 additions & 5 deletions Documentation/git-worktree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ bare repository) and zero or more linked working trees.
When you are done with a linked working tree you can simply delete it.
The working tree's administrative files in the repository (see
"DETAILS" below) will eventually be removed automatically (see
`gc.worktreePruneExpire` in linkgit::git-config[1]), or you can run
`gc.worktreePruneExpire` in linkgit:git-config[1]), or you can run
`git worktree prune` in the main or any linked working tree to
clean up any stale administrative files.

Expand All @@ -51,9 +51,9 @@ Create `<path>` and checkout `<branch>` into it. The new working directory
is linked to the current repository, sharing everything except working
directory specific files such as HEAD, index, etc.
+
If `<branch>` is omitted and neither `-b` nor `-B` is used, then, as a
convenience, a new branch based at HEAD is created automatically, as if
`-b $(basename <path>)` was specified.
If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used,
then, as a convenience, a new branch based at HEAD is created automatically,
as if `-b $(basename <path>)` was specified.

prune::

Expand Down Expand Up @@ -124,7 +124,7 @@ thumb is do not make any assumption about whether a path belongs to
$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.

To prevent a $GIT_DIR/worktrees entry from from being pruned (which
To prevent a $GIT_DIR/worktrees entry from being pruned (which
can be useful in some situations, such as when the
entry's working tree is stored on a portable device), add a file named
'locked' to the entry's directory. The file contains the reason in
Expand Down
67 changes: 67 additions & 0 deletions branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,70 @@ void remove_branch_state(void)
unlink(git_path("MERGE_MODE"));
unlink(git_path("SQUASH_MSG"));
}

static void check_linked_checkout(const char *branch, const char *id)
{
struct strbuf sb = STRBUF_INIT;
struct strbuf path = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;

/*
* $GIT_COMMON_DIR/HEAD is practically outside
* $GIT_DIR so resolve_ref_unsafe() won't work (it
* uses git_path). Parse the ref ourselves.
*/
if (id)
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
else
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());

if (!strbuf_readlink(&sb, path.buf, 0)) {
if (!starts_with(sb.buf, "refs/") ||
check_refname_format(sb.buf, 0))
goto done;
} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
starts_with(sb.buf, "ref:")) {
strbuf_remove(&sb, 0, strlen("ref:"));
strbuf_trim(&sb);
} else
goto done;
if (strcmp(sb.buf, branch))
goto done;
if (id) {
strbuf_reset(&path);
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
goto done;
strbuf_rtrim(&gitdir);
} else
strbuf_addstr(&gitdir, get_git_common_dir());
skip_prefix(branch, "refs/heads/", &branch);
strbuf_strip_suffix(&gitdir, ".git");
die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
done:
strbuf_release(&path);
strbuf_release(&sb);
strbuf_release(&gitdir);
}

void die_if_checked_out(const char *branch)
{
struct strbuf path = STRBUF_INIT;
DIR *dir;
struct dirent *d;

check_linked_checkout(branch, NULL);

strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
dir = opendir(path.buf);
strbuf_release(&path);
if (!dir)
return;

while ((d = readdir(dir)) != NULL) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
check_linked_checkout(branch, d->d_name);
}
closedir(dir);
}
7 changes: 7 additions & 0 deletions branch.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ extern void install_branch_config(int flag, const char *local, const char *origi
*/
extern int read_branch_desc(struct strbuf *, const char *branch_name);

/*
* Check if a branch is checked out in the main worktree or any linked
* worktree and die (with a message describing its checkout location) if
* it is.
*/
extern void die_if_checked_out(const char *branch);

#endif
82 changes: 6 additions & 76 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ struct checkout_opts {
const char *prefix;
struct pathspec pathspec;
struct tree *source_tree;

int new_worktree_mode;
};

static int post_checkout_hook(struct commit *old, struct commit *new,
Expand Down Expand Up @@ -509,7 +507,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.dir->flags |= DIR_SHOW_IGNORED;
setup_standard_excludes(topts.dir);
}
tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
tree = parse_tree_indirect(old->commit ?
old->commit->object.sha1 :
EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
Expand Down Expand Up @@ -830,8 +828,7 @@ static int switch_branches(const struct checkout_opts *opts,
return ret;
}

if (!opts->quiet && !old.path && old.commit &&
new->commit != old.commit && !opts->new_worktree_mode)
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
orphaned_commit_warning(old.commit, new->commit);

update_refs_for_switch(opts, &old, new);
Expand Down Expand Up @@ -896,71 +893,6 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
return NULL;
}

static void check_linked_checkout(struct branch_info *new, const char *id)
{
struct strbuf sb = STRBUF_INIT;
struct strbuf path = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
const char *start, *end;

if (id)
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
else
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());

if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
!skip_prefix(sb.buf, "ref:", &start))
goto done;
while (isspace(*start))
start++;
end = start;
while (*end && !isspace(*end))
end++;
if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
goto done;
if (id) {
strbuf_reset(&path);
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
goto done;
strbuf_rtrim(&gitdir);
} else
strbuf_addstr(&gitdir, get_git_common_dir());
die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
done:
strbuf_release(&path);
strbuf_release(&sb);
strbuf_release(&gitdir);
}

static void check_linked_checkouts(struct branch_info *new)
{
struct strbuf path = STRBUF_INIT;
DIR *dir;
struct dirent *d;

strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
if ((dir = opendir(path.buf)) == NULL) {
strbuf_release(&path);
return;
}

/*
* $GIT_COMMON_DIR/HEAD is practically outside
* $GIT_DIR so resolve_ref_unsafe() won't work (it
* uses git_path). Parse the ref ourselves.
*/
check_linked_checkout(new, NULL);

while ((d = readdir(dir)) != NULL) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
check_linked_checkout(new, d->d_name);
}
strbuf_release(&path);
closedir(dir);
}

static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new,
Expand Down Expand Up @@ -1168,14 +1100,14 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("Cannot switch branch to a non-commit '%s'"),
new->name);

if (new->path && !opts->force_detach && !opts->new_branch) {
if (new->path && !opts->force_detach && !opts->new_branch &&
!opts->ignore_other_worktrees) {
unsigned char sha1[20];
int flag;
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
if (head_ref &&
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
!opts->ignore_other_worktrees)
check_linked_checkouts(new);
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
die_if_checked_out(new->path);
free(head_ref);
}

Expand Down Expand Up @@ -1239,8 +1171,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);

opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;

if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
Expand Down
Loading

0 comments on commit 53860f0

Please sign in to comment.