-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodespace-utils
More file actions
executable file
·260 lines (220 loc) · 7.42 KB
/
codespace-utils
File metadata and controls
executable file
·260 lines (220 loc) · 7.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/usr/bin/env bash
# shared utilities for codespace scripts
# this file should be sourced, not executed directly.
# sanitize string for use in directory/file names
cs_cleanpath() {
echo "$1" | tr '/: ' '_'
}
# Extract repo name from clone URL
# git@github.com:org/repo.git -> repo
# https://github.com/org/repo.git -> repo
cs_repo_name_from_url() {
url="$1"
basename "$url" .git
}
# Get default branch using git mechanisms
cs_get_default_branch() {
# 1. Try remote HEAD symbolic ref
default_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|^refs/remotes/origin/||')"
test -n "$default_branch" && { echo "$default_branch"; return 0; }
# 2. Try current branch (useful when in main repo clone)
default_branch="$(git branch --show-current 2>/dev/null)"
test -n "$default_branch" && { echo "$default_branch"; return 0; }
# 3. Try git config init.defaultBranch
default_branch="$(git config init.defaultBranch 2>/dev/null)"
test -n "$default_branch" && { echo "$default_branch"; return 0; }
>&2 echo "err: could not determine default branch"
>&2 echo "configure with:"
echo "git config --global init.defaultBranch master"
return 1
}
# Infer clone URL for a repo name from sibling repos in the same org
# Args: repo_name, org_dir
cs_infer_clone_url() {
target_repo="$1"
org_dir="$2"
# Find any sibling repo with a remote
for sibling in "$org_dir"/*/.git; do
test -d "$sibling" || continue
sibling_repo="$(dirname "$sibling")"
origin_url="$(git -C "$sibling_repo" remote get-url origin 2>/dev/null)" || continue
# Derive URL by replacing repo name in the URL
sibling_name="$(basename "$sibling_repo")"
# Handle both SSH (git@host:org/repo.git) and HTTPS (https://host/org/repo.git) URLs
echo "$origin_url" | sed "s|/$sibling_name\\.git\$|/$target_repo.git|; s|:$sibling_name\\.git\$|:$target_repo.git|"
return 0
done
return 1
}
# Fetch remote branch if it looks like a remote ref
# Args: branch_ref (e.g., "origin/main", "refs/remotes/origin/main", "main")
cs_maybe_fetch_remote() {
local ref="$1"
local remote=""
local ref_branch=""
local remainder=""
local tracking=""
# Pattern: refs/remotes/<remote>/<branch>
if [[ "$ref" == refs/remotes/* ]]; then
remainder="${ref#refs/remotes/}"
remote="${remainder%%/*}"
ref_branch="${remainder#*/}"
# Pattern: <remote>/<branch> (e.g., origin/main)
elif [[ "$ref" == */* ]]; then
remote="${ref%%/*}"
ref_branch="${ref#*/}"
# Verify it's actually a remote (not a local branch with slash)
if ! git remote | grep -qx "$remote"; then
remote=""
ref_branch=""
fi
else
# Local branch name - check if it has a remote-tracking branch
tracking="$(git rev-parse --abbrev-ref "$ref@{upstream}" 2>/dev/null)" || true
if [ -n "$tracking" ] && [[ "$tracking" == */* ]]; then
remote="${tracking%%/*}"
ref_branch="${tracking#*/}"
elif git remote | grep -qx "origin"; then
remote="origin"
ref_branch="$ref"
fi
fi
if [ -n "$remote" ] && [ -n "$ref_branch" ]; then
>&2 echo "fetching $remote/$ref_branch..."
git fetch "$remote" "$ref_branch" || {
>&2 echo "warn: fetch failed, continuing with local state"
}
fi
}
# Ensure branch exists locally (fetch from remote if needed, or create new)
# Args: branch, [base_branch_for_new]
# Returns: 0 if branch now exists locally
cs_ensure_branch() {
local branch="$1"
local base_branch="${2:-}"
# Check if branch exists locally
if git show-ref --verify --quiet "refs/heads/$branch"; then
return 0
fi
# Check if branch exists on remote
if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
>&2 echo "branch '$branch' exists on remote, fetching..."
git fetch origin "$branch"
git branch --track "$branch" "origin/$branch"
return 0
fi
# Branch doesn't exist anywhere - create from base if provided
if [ -n "$base_branch" ]; then
git branch --no-track "$branch" "$base_branch"
return 0
fi
>&2 echo "err: branch '$branch' not found and no base branch provided"
return 1
}
# Core worktree creation (no path computation, no editor opening)
# Args: branch, base_repo_path, dest_path, [base_branch]
cs_create_worktree_core() {
branch="$1"
base_repo="$2"
dest="$3"
base_branch="${4:-}"
(
cd "$base_repo"
test -n "$base_branch" || base_branch="$(cs_get_default_branch)"
# Fetch remote branch before creating worktree (unless CS_NO_FETCH is set)
if [ -z "${CS_NO_FETCH:-}" ]; then
cs_maybe_fetch_remote "$base_branch"
fi
# Prefer remote ref for latest state after fetch
if [[ "$base_branch" != */* ]] && git show-ref --verify --quiet "refs/remotes/origin/$base_branch"; then
base_branch="origin/$base_branch"
fi
cs_ensure_branch "$branch" "$base_branch"
git worktree add "$dest" "$branch"
)
echo "$dest"
}
# Core clone creation (no path computation, no editor opening)
# Args: clone_url, dest_path, branch
cs_create_clone_core() {
clone_url="$1"
dest="$2"
branch="$3"
git clone "$clone_url" "$dest"
(cd "$dest" && cs_ensure_branch "$branch" && git checkout "$branch")
echo "$dest"
}
# Get absolute path
cs_abspath() {
realpath "$1"
}
# Get absolute path to base repository (resolves through worktrees)
cs_abs_path_base_repo() {
realpath "$(git rev-parse --git-common-dir)/.." "$@"
}
# Check if we're in a checkout (not worktree) of the main repo
cs_are_we_in_checkout_not_worktree() {
GIT_DIR="$(realpath "$(git rev-parse --git-dir)")"
GIT_COMMON_DIR="$(realpath "$(git rev-parse --git-common-dir)")"
test "$GIT_DIR" = "$GIT_COMMON_DIR" && echo 1 || echo 0
}
# Get path to clone marker file
cs_clone_marker_path() {
if [ -n "${1:-}" ]; then
echo "$1/.git/CODESPACE_IS_CLONE"
else
echo "$(git rev-parse --git-dir 2>/dev/null)/CODESPACE_IS_CLONE"
fi
}
# Get clone marker content (repo name) if exists
cs_clone_marker() {
MARKER_PATH="$(cs_clone_marker_path "${1:-}")"
test -f "$MARKER_PATH" && cat "$MARKER_PATH"
}
# Get repo ID for clone (relative path)
cs_repo_id_of_clone() {
clone_repo_name="$(cs_clone_marker)"
realpath "$(pwd)/../$clone_repo_name" "$@"
}
# Get repo ID (for config path resolution)
cs_repo_id() {
rel_to="${1:-$HOME}"
if [ -n "$(cs_clone_marker)" ]; then
cs_repo_id_of_clone --relative-to="$rel_to"
else
cs_abs_path_base_repo --relative-to="$rel_to"
fi
}
# Get config path for current repo
cs_config_path() {
echo "$CODESPACE_CONFIG_ROOT/$(cs_repo_id)"
}
# Get absolute path from repo name and branch
cs_abs_path_from_repo_name_and_branch() {
branch="${1:-$(git branch --show-current)}"
repo_name="$(basename "$(cs_abs_path_base_repo)")"
path="$(cs_cleanpath "$repo_name/$branch")"
cs_abspath "../$path"
}
# Run post-create script for a codespace
cs_post_create() {
WT_PATH="${1:-$(cs_abs_path_from_repo_name_and_branch)}"
test -n "${CODESPACE_CONFIG_ROOT:-}" || {
>&2 echo "warn: env var '\$CODESPACE_CONFIG_ROOT' not set, cannot run post-create script"
return 0
}
cd "$WT_PATH"
POST_CREATE_CMD_PATH="$(cs_config_path)/.codespace/post-create"
if [ -f "$POST_CREATE_CMD_PATH" ]; then
BASE_REPO_PATH="$(cs_abs_path_base_repo)"
if [ "$(cs_are_we_in_checkout_not_worktree)" = "1" ]; then
# we are NOT in a worktree; we're in the main repository, and just checked out the branch
>&2 echo "note: you are in branch checkout, not in a worktree. post-create script will behave differently."
(ARE_WE_IN_CHECKOUT_NOT_WORKTREE=1 "$POST_CREATE_CMD_PATH" "$BASE_REPO_PATH")
else
("$POST_CREATE_CMD_PATH" "$BASE_REPO_PATH")
fi
else
>&2 echo "warn: 'post-create' script not found in '$POST_CREATE_CMD_PATH', cannot run it"
fi
}