3 ### git_wtree_new <name:branch name> [dir:<dir name>]
4 ### git_wtree_drop <name:branch name>
5 ### git_wtree_{cd, pushd} <name:branch name>
9 # TODO: use 'git check-ref-format --branch' to check branch's name validness
11 ### GIT_WTREE_ALIAS_ENABLED=true
13 ### For debugging purposes:
14 ### GIT_WTREE_DRY_RUN=true
15 ### GIT_WTREE_DEBUG=true
17 _git_wtree_last_error_dir=$(mktemp --suffix=git.wtree. -d)
18 _git_wtree_last_error_msg=${_git_wtree_last_error_dir}/msg
19 _git_wtree_last_error_out=${_git_wtree_last_error_dir}/out
20 trap "test -d ${_git_wtree_last_error_dir} && rm -rf '${_git_wtree_last_error_dir}'" EXIT QUIT
22 ### Execute `git' subcommand with passed arguments
23 ### $1 : routine description
24 ### $2 : git subcommand, e.g.: worktree
25 ### ${n,} : git subcomand's arguments
27 _git_wtree_set_last_error "$1 FAILED"
31 if [ -n "${GIT_WTREE_DRY_RUN}" ]; then
32 echo "[git-wtree:DRY-RUN]: ${cmd}"
36 [ -n "${GIT_WTREE_DEBUG}" ] && echo -e "git:{\n"
39 [ -n "${GIT_WTREE_DEBUG}" ] && echo -e "\n}:git"
44 ### Return key's value from arguments
46 ### ${2,n}: argv to search in
47 ### NOTE: for the sake of simplicity no long values are supported
52 while [ ! $# -eq 0 ]; do
54 --${arg_name}) echo -n $2 && break ;;
60 _git_wtree_show_last_error() {
61 echo "[git-wtree:ERROR] $(cat ${_git_wtree_last_error_msg})" >&2
62 [ -s "${_git_wtree_last_error_out}" ] && echo "[git-wtree:ERROR] $(cat ${_git_wtree_last_error_out})" >&2
66 _git_wtree_set_last_error() {
67 echo "$*" > ${_git_wtree_last_error_msg}
70 _git_wtree_worktree_root() {
71 worktree_root=$(git config worktree.root 2>${_git_wtree_last_error_out} || echo -n)
72 [ -n "${worktree_root}" -a -d "${worktree_root}" ] && echo -n "${worktree_root}" && return 0
73 _git_wtree_set_last_error "Non-defined or not accessible worktree root '${worktree_root}'. 'worktree.root' in .git/config should point to an existing dir."
77 _git_wtree_arg_dir_name() {
78 dir_name=$(_git_wtree_arg dir $*)
79 _git_wtree_set_last_error "Missing directory name. Shall be specified with --dir argument"
80 [ -n "${dir_name}" ] && echo -n "${dir_name}" && return 0
84 _git_wtree_arg_branch_name() {
85 branch_name=$(_git_wtree_arg branch $*)
87 _git_wtree_set_last_error "Missing branch name. Shall be specified with either --branch or --name argument"
88 [ -z "${branch_name}" ] && branch_name=$(_git_wtree_arg name $*)
89 [ -n "${branch_name}" ] && echo -n "${branch_name}" && return 0
93 _git_wtree_dir_by_branch_name() {
95 candidates_amount=$(git worktree list 2>${_git_wtree_last_error_out} | grep "${branch_name}" | wc -l)
97 ### TODO: won't work for names with a common prefix
98 _git_wtree_set_last_error "No exact branch '${branch_name}' is available but ${candidates_amount} candidates"
99 [ 1 -eq "${candidates_amount}" ] || return 1
101 _git_wtree_set_last_error "No worktree dir is found for branch '${branch_name}'"
102 dir_name=$(git worktree list 2>/dev/null | grep "${branch_name}" | cut -d ' ' -f 1)
103 [ -n "${dir_name}" ] || return 1
105 _git_wtree_set_last_error "No worktree dir '${dir_name}' is available for branch '${branch_name}'"
106 [ -d "${dir_name}" ] || return 1
108 echo -n "${dir_name}"
113 ### Locate a corresponding directory of specified branch.
115 ### --<name|branch> <branch_name>
117 git_wtree_cmd_locate() {
118 current_top_level=$(git rev-parse --show-toplevel 2>${_git_wtree_last_error_out} || echo -n)
119 _git_wtree_set_last_error "'$(pwd)' is not a git directory"
120 [ -z "${current_top_level}" ] && _git_wtree_show_last_error && return 1
122 branch_name=$(_git_wtree_arg_branch_name $*)
123 [ -z "${branch_name}" ] && _git_wtree_show_last_error && return 1
125 branch_dir_name=$(_git_wtree_dir_by_branch_name ${branch_name})
126 [ -z "${branch_dir_name}" ] && _git_wtree_show_last_error && return 1
128 echo -n "${branch_dir_name}"
133 #### Same as `git_wtree_cmd_locate`. Suppresses an error output. A corresponding exit code is preserved.
135 git_wtree_cmd_locate_noerror() {
136 branch_dir_name=$(git_wtree_cmd_locate $* 2>/dev/null)
137 [ 0 -eq $? ] || return 1
139 echo -n "${branch_dir_name}"
144 ### Drop worktree's directory
146 ### --<name|branch> <branch_name>
147 ### Should be specified in a relative way
149 git_wtree_cmd_drop() {
150 branch_name=$(_git_wtree_arg_branch_name $*)
151 [ -z "${branch_name}" ] && _git_wtree_show_last_error && return 1
153 branch_dir_name=$(_git_wtree_dir_by_branch_name ${branch_name})
154 [ -z "${branch_dir_name}" ] && _git_wtree_show_last_error && return 1
156 # echo "- '${branch_name}' to be dropped from '${branch_dir_name}'"
158 cmd="git worktree remove ${branch_dir_name}"
160 "Drop directory of branch '${branch_name}'" \
161 worktree remove ${branch_dir_name}
162 [ 0 -ne $? ] && _git_wtree_show_last_error && return 1
167 ### Create a new worktree branch
169 ### --<name|branch> <branch_name>
170 ### --dir <worktree_directory name>
171 ### Should be specified in a relative way
173 git_wtree_cmd_new() {
174 branch_name=$(_git_wtree_arg_branch_name $*)
175 [ -z "${branch_name}" ] && _git_wtree_show_last_error && return 1
176 branch_dir_name=$(_git_wtree_arg_dir_name $*)
177 [ -z "${branch_dir_name}" ] && _git_wtree_show_last_error && return 1
178 parent_branch_dir_name=$(_git_wtree_worktree_root)
179 [ -z "${parent_branch_dir_name}" ] && _git_wtree_show_last_error && return 1
181 branch_dir_name="${parent_branch_dir_name}/${branch_dir_name}"
182 _git_wtree_set_last_error "'${branch_dir_name}' already exists"
183 [ -e "${branch_dir_name}" ] && _git_wtree_show_last_error && return 1
185 # echo "- '${branch_name}' to be created in '${branch_dir_name}'"
188 "Create branch '${branch_name}' in '${branch_dir_name}'" \
189 worktree add -b ${branch_name} ${branch_dir_name} $(_git_wtree_arg commit $*)
190 [ 0 -ne $? ] && _git_wtree_show_last_error && return 1
195 ### List all available worktrees
200 "List available worktrees" \
201 worktree list | awk -F'[][]' '{print $2}'
202 [ 0 -ne $? ] && _git_wtree_show_last_error && return
206 ### Helper to change a current directory to a worktree's one using a specified change dir command. Nothing is executed in a case of any error.
208 ### $1 - change dir command. E.g.: cd, pushd etc.
211 git_wtree_cmd_tool_chdir() {
214 branch_dir_name=$(git_wtree_cmd_locate_noerror --name $1)
215 [ 0 -eq $? ] && eval "${chdir_cmd} ${branch_dir_name}"
219 ### Helper to change a current directory to a worktree's one using 'cd'
223 git_wtree_cmd_tool_cd() {
224 git_wtree_cmd_tool_chdir cd $1
228 ### Helper to change a current directory to a worktree's one using 'pushd'
232 git_wtree_cmd_tool_pushd() {
233 git_wtree_cmd_tool_chdir pushd $1
237 [ 0 -eq $# ] && _git_wtree_set_last_error "Command expected: new, drop, locate" && _git_wtree_show_last_error && return
246 if [ -n "${GIT_WTREE_ALIAS_ENABLED}" ]; then
247 alias git.wtree=git_wtree
248 alias git.wtree:ls=git_wtree_cmd_ls
249 alias git.wtree:new=git_wtree_cmd_new
250 alias git.wtree:drop=git_wtree_cmd_drop
251 alias git.wtree:cd=git_wtree_cmd_tool_cd
252 alias git.wtree:pushd=git_wtree_cmd_tool_pushd