Skip to content

Commit 475845d

Browse files
author
lu0
committed
Support worktree-tags and detached checkouts
1 parent d95f6f0 commit 475845d

File tree

3 files changed

+63
-35
lines changed

3 files changed

+63
-35
lines changed

libs/_branch.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ branch::_delete_worktree_and_branch() {
2828
local bare_dir to_dir worktree_abs_dir
2929

3030
bare_dir="$(utils::get_bare_dir)"
31-
to_dir=$(utils::branch_to_dir_name "${branch}")
31+
to_dir=$(utils::branch_to_ref_name "${branch}")
3232

3333
worktree_abs_dir="${bare_dir%%/}/${to_dir}"
3434

libs/_checkout.sh

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ checkout::override_commands() {
2525
esac
2626

2727

28-
if [[ -z "${3:-}" ]] && [[ $(utils::git branch --list "${2:-}") != "" ]]; then
28+
if [[ -z "${3:-}" ]] && [[ $(utils::is_worktreable "${2:-}") == 1 ]]; then
2929
# If `git checkout` received only 1 argument (other than the previous
30-
# cases) and is a branch, switch and cd into it ...
31-
checkout::_switch_to_branch_and_worktree "${@:-}"
32-
30+
# cases) and is a "worktreable" reference, switch and cd into it ...
31+
checkout::_switch_to_ref_and_worktree "${@:-}"
3332
else
3433
# ... otherwise, either an unsupported option was passed ...
3534
if echo "${@:-}" | grep -qo " -" ; then
@@ -47,11 +46,10 @@ checkout::override_commands() {
4746
# - `git checkout [-b|B] ${to} ${from}`
4847
# - Call: `checkout::_create_branch_and_worktree $opt $to $from`
4948
checkout::_create_branch_and_worktree() {
50-
5149
local opt="${2:-}" to="${3:-}" from="${4:-}"
5250
local to_dir existing_branch
5351

54-
to_dir=$(utils::branch_to_dir_name "${to}")
52+
to_dir=$(utils::ref_to_dir_name "${to}")
5553
existing_branch=$(utils::git branch --list "${to}")
5654

5755
if [ "${existing_branch:-}" ] && [ "${opt}" == "-b" ]; then
@@ -67,7 +65,7 @@ checkout::_create_branch_and_worktree() {
6765
fi
6866

6967
# Create worktree if does not exist
70-
worktree_dir=$(checkout::__create_worktree_from_branch "${to}")
68+
worktree_dir=$(checkout::__create_worktree_from_ref "${to}")
7169

7270
# cd into the worktree dir
7371
cd "${worktree_dir}"
@@ -87,16 +85,16 @@ checkout::_create_branch_and_worktree() {
8785
}
8886

8987

90-
# Switch to a branch/worktree, emulates git command:
91-
# - `git checkout ${branch}`
92-
# - Call: `checkout::_switch_to_branch_and_worktree $branch`
93-
checkout::_switch_to_branch_and_worktree() {
88+
# Switch to a branch/tag/worktree, emulates git command:
89+
# - `git checkout ${to}`
90+
# - Call: `checkout::_switch_to_ref_and_worktree $to`
91+
checkout::_switch_to_ref_and_worktree() {
9492
local to="${2:-}"
9593
local existing_branch
9694
local worktree_dir
9795

9896
# Create worktree if does not exist
99-
worktree_dir=$(checkout::__create_worktree_from_branch "${to}")
97+
worktree_dir=$(checkout::__create_worktree_from_ref "${to}")
10098

10199
if [ -d "${worktree_dir:-}" ]; then
102100
# If successful, cd to the created worktree
@@ -107,22 +105,23 @@ checkout::_switch_to_branch_and_worktree() {
107105
}
108106

109107

110-
# Create worktree from a given branch if the worktree does not exist.
108+
# Creates worktree from a given reference if the worktree does not exist.
111109
# Worktree directories are created in the root of the bare repository.
112-
# Vanilla git handles non-existence of the given branch.
110+
# Vanilla git handles non-existence of the given reference.
113111
# Post-checkout hooks are disabled within this function.
114-
# - Call: `checkout::__create_worktree_from_branch "${branch}"`
115-
# - Returns: Path to the created or existing worktree for `branch` or
112+
# - Call: `checkout::__create_worktree_from_ref "${ref}"`
113+
# - Returns: Path to the created or existing worktree for `ref` or
116114
# an empty string if the creation was not successful.
117-
checkout::__create_worktree_from_branch() {
115+
checkout::__create_worktree_from_ref() {
116+
local to="${1}"
118117
local to_dir
119118
local bare_dir
120119

121120
bare_dir=$(utils::get_bare_dir)
122-
to_dir=$(utils::branch_to_dir_name "${to}")
121+
to_dir=$(utils::ref_to_dir_name "${to}")
123122

124123
# Create worktree if does not exist
125-
if [ -z "$(utils::get_branch_path "${to}")" ]; then
124+
if [ -z "$(utils::get_ref_path "${to}")" ]; then
126125
# Created in the directory of the bare repository
127126
cd "${bare_dir}"
128127

@@ -134,13 +133,19 @@ checkout::__create_worktree_from_branch() {
134133
fi
135134
fi
136135

137-
utils::get_branch_path "${to}"
136+
utils::get_ref_path "${to}"
138137
}
139138

140139

141140
# Triggers post-checkout hooks by executing a silent
142-
# checkout to the current branch using vanilla git.
141+
# checkout to the current branch or reference using vanilla git.
143142
# - Call: `checkout::__trigger_post_hook`
144143
checkout::__trigger_post_hook() {
145-
utils::git checkout -q "$(utils::git branch --show-current)"
144+
local branch
145+
branch="$(utils::git branch --show-current)"
146+
if [[ "${branch}" ]]; then
147+
utils::git checkout -q "${branch}"
148+
else
149+
utils::git checkout -q "$(git rev-parse HEAD)"
150+
fi
146151
}

libs/_utils.sh

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,44 @@ utils::git() {
1717
}
1818

1919

20+
# Returns 1 if the specified reference is "worktreable".
21+
# A reference is "worktreable" if it's a branch or a tag,
22+
# this enables detached checkouts to commits within a worktree.
23+
utils::is_worktreable() {
24+
local ref="${1}"
25+
local is_branch is_tag
26+
27+
is_branch=$(utils::git show-ref --verify -q refs/heads/"${ref}" && echo 1)
28+
is_tag=$(utils::git show-ref --verify -q refs/tags/"${ref}" && echo 1)
29+
30+
if [[ "${is_tag}" == 1 ]] || [[ "${is_branch}" == 1 ]]; then
31+
echo 1
32+
fi
33+
}
34+
35+
36+
2037
# Non intrusive messages
2138
utils::info() {
2239
echo -e >&2 "\ngit-worktree-wrapper:\n\t${1}\n"
2340
}
2441

2542

2643
# Use `.` instead of `/` to name worktree directories
27-
utils::branch_to_dir_name() {
44+
utils::ref_to_dir_name() {
2845
echo "${1//'/'/'.'}"
2946
}
3047

3148

32-
utils::get_branch_path() {
33-
utils::git worktree list | grep -w "\[${1}\]" | cut -d" " -f1
49+
# Returns the absolute path to the worktree of a given reference,
50+
# the result is empty if the reference does not have a worktree.
51+
utils::get_ref_path() {
52+
local ref="${1}"
53+
local bare_dir ref_dirname ref_path_to_check
54+
bare_dir=$(utils::get_bare_dir)
55+
ref_dirname=$(utils::ref_to_dir_name "${ref}")
56+
ref_path_to_check="${bare_dir}/${ref_dirname}"
57+
utils::git worktree list | grep -o "${ref_path_to_check} " | xargs
3458
}
3559

3660

@@ -39,21 +63,20 @@ utils::get_bare_dir() {
3963
}
4064

4165

42-
utils::__store_last_checkedout() {
43-
local branch worktree_path info
44-
branch=$(git branch --show-current)
45-
worktree_path=$(utils::get_branch_path "${branch}")
46-
info="${worktree_path}\t[${branch}]"
47-
echo -e "${info}" > "$(utils::get_bare_dir)"/.lastcheckedout
66+
utils::__store_current_worktree_info() {
67+
local info_file_path current_worktree_info
68+
info_file_path="$(utils::get_bare_dir)/.lastcheckedout"
69+
current_worktree_info=$(utils::git worktree list | grep "$PWD " | xargs)
70+
echo -e "${current_worktree_info}" > "${info_file_path}"
4871
}
4972

5073

51-
# Open the current worktree in a enabled and set editor and
52-
# store a reference to the worktree directory in the repository's root
74+
# Opens the current worktree in a enabled and set editor and
75+
# stores the information of the current worktree directory in the repository's root
5376
utils::open_editor_and_store_reference() {
5477
if [[ "${EDITOR:-}" ]] && [[ "${DISABLE_GIT_WORKTREE_EDITOR:-}" != "1" ]]; then
5578
utils::info "Opening in editor: ${EDITOR:-}"
5679
${EDITOR:-} .
5780
fi
58-
utils::__store_last_checkedout
81+
utils::__store_current_worktree_info
5982
}

0 commit comments

Comments
 (0)