herdr-worktreeinclude 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +91 -0
- package/herdr-plugin.toml +26 -0
- package/package.json +36 -0
- package/scripts/assert-release-version.mjs +16 -0
- package/scripts/branch-cleanup-pane.sh +20 -0
- package/scripts/copy-worktreeinclude.sh +82 -0
- package/scripts/create-worktree.sh +34 -0
- package/scripts/event-worktree-path.mjs +8 -0
- package/scripts/on-worktree-created.sh +56 -0
- package/scripts/on-worktree-removed.sh +36 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are published in the [GitHub Releases](https://github.com/serhii-chernenko/herdr-worktreeinclude/releases) generated from version tags.
|
|
4
|
+
|
|
5
|
+
## 0.1.0
|
|
6
|
+
|
|
7
|
+
- Initial Herdr plugin: project-local worktrees, `.worktreeinclude` copying, and UI-first branch cleanup.
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# herdr-worktreeinclude
|
|
2
|
+
|
|
3
|
+
A [Herdr](https://herdr.dev) plugin that keeps Herdr-created Git worktrees inside the project and restores selected ignored files into them.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Use Herdr's normal **New worktree** UI. After creation, the plugin automatically:
|
|
8
|
+
|
|
9
|
+
- moves the checkout from Herdr's global directory to `<project>/.herdr/worktrees/<branch>`;
|
|
10
|
+
- copies files selected by `.worktreeinclude` from the primary checkout;
|
|
11
|
+
- opens the relocated worktree as the active Herdr workspace.
|
|
12
|
+
|
|
13
|
+
When you remove a plugin-managed worktree through Herdr's normal UI, an unchanged branch (one already contained in the primary checkout's `HEAD`) is removed silently. For a branch with unique commits, Herdr shows an in-terminal overlay: press `d` to safely delete the local branch or Enter to keep it.
|
|
14
|
+
|
|
15
|
+
The plugin cannot change Herdr's built-in creation/removal dialogs. It uses lifecycle hooks immediately after those dialogs complete.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Herdr with plugin support (currently the Preview channel)
|
|
20
|
+
- Git
|
|
21
|
+
- Node.js 20 or later
|
|
22
|
+
- macOS or Linux
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
Once the repository is public and tagged with the GitHub topic `herdr-plugin`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
herdr plugin install serhii-chernenko/herdr-worktreeinclude
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To install a specific release:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
herdr plugin install serhii-chernenko/herdr-worktreeinclude --ref v0.1.0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For local development, link it into the current Herdr session:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
herdr plugin link /path/to/herdr-worktreeinclude
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Plugins are registered per Herdr session. For a named session, include its name:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
herdr --session my-session plugin link /path/to/herdr-worktreeinclude
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configure copied files
|
|
51
|
+
|
|
52
|
+
Create `.worktreeinclude` at the project root. It uses `.gitignore` pattern syntax, but a file is copied only when it is both selected by `.worktreeinclude` and ignored by Git.
|
|
53
|
+
|
|
54
|
+
```gitignore
|
|
55
|
+
# .gitignore
|
|
56
|
+
.env
|
|
57
|
+
config/local/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```gitignore
|
|
61
|
+
# .worktreeinclude
|
|
62
|
+
.env
|
|
63
|
+
config/local/
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Tracked files are never copied. Existing destination files are not overwritten. Symlinks and file modes are preserved.
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install
|
|
72
|
+
npm run check
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
After changing the manifest or scripts, relink the plugin in the target Herdr session:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
herdr plugin unlink serhii-chernenko.worktreeinclude
|
|
79
|
+
herdr plugin link "$(pwd)"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Releases
|
|
83
|
+
|
|
84
|
+
The npm package and Herdr manifest share one version. Before publishing, update both `package.json` and `herdr-plugin.toml`, add release notes to `CHANGELOG.md`, then commit and push a matching tag:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git tag v0.1.1
|
|
88
|
+
git push origin main v0.1.1
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The tag workflow verifies the version, publishes to npm through npm Trusted Publishing, and creates GitHub Release notes automatically. After claiming the package name with an initial manual publish, configure npm's Trusted Publisher for GitHub repository `serhii-chernenko/herdr-worktreeinclude` and workflow filename `publish.yml`. Subsequent matching version tags publish without a stored npm token.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
id = "serhii-chernenko.worktreeinclude"
|
|
2
|
+
name = "Project-local Worktrees"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
min_herdr_version = "0.7.0"
|
|
5
|
+
description = "Creates project-local Git worktrees and restores ignored files selected by .worktreeinclude."
|
|
6
|
+
platforms = ["macos", "linux"]
|
|
7
|
+
|
|
8
|
+
[[events]]
|
|
9
|
+
on = "worktree.created"
|
|
10
|
+
command = ["bash", "scripts/on-worktree-created.sh"]
|
|
11
|
+
|
|
12
|
+
[[events]]
|
|
13
|
+
on = "worktree.removed"
|
|
14
|
+
command = ["bash", "scripts/on-worktree-removed.sh"]
|
|
15
|
+
|
|
16
|
+
[[panes]]
|
|
17
|
+
id = "create"
|
|
18
|
+
title = "Create project-local worktree"
|
|
19
|
+
placement = "tab"
|
|
20
|
+
command = ["bash", "scripts/create-worktree.sh"]
|
|
21
|
+
|
|
22
|
+
[[panes]]
|
|
23
|
+
id = "branch-cleanup"
|
|
24
|
+
title = "Delete local branch?"
|
|
25
|
+
placement = "overlay"
|
|
26
|
+
command = ["bash", "scripts/branch-cleanup-pane.sh"]
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "herdr-worktreeinclude",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Project-local Git worktrees and .worktreeinclude support for Herdr.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"herdr",
|
|
7
|
+
"herdr-plugin",
|
|
8
|
+
"git-worktree",
|
|
9
|
+
"worktreeinclude"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/serhii-chernenko/herdr-worktreeinclude.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/serhii-chernenko/herdr-worktreeinclude/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/serhii-chernenko/herdr-worktreeinclude#readme",
|
|
19
|
+
"files": [
|
|
20
|
+
"herdr-plugin.toml",
|
|
21
|
+
"scripts/",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "bash tests/copy-worktreeinclude.sh && bash tests/create-worktree.sh",
|
|
27
|
+
"check": "bash -n scripts/*.sh tests/*.sh && node --check scripts/*.mjs && npm run test",
|
|
28
|
+
"release:check": "node scripts/assert-release-version.mjs"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const packageVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url))).version;
|
|
4
|
+
const manifest = readFileSync(new URL("../herdr-plugin.toml", import.meta.url), "utf8");
|
|
5
|
+
const manifestVersion = manifest.match(/^version\s*=\s*"([^"]+)"\s*$/m)?.[1];
|
|
6
|
+
|
|
7
|
+
if (!manifestVersion || manifestVersion !== packageVersion) {
|
|
8
|
+
throw new Error(`Version mismatch: package.json=${packageVersion}, herdr-plugin.toml=${manifestVersion ?? "missing"}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const tag = process.env.GITHUB_REF_NAME;
|
|
12
|
+
if (tag && tag !== `v${packageVersion}`) {
|
|
13
|
+
throw new Error(`Tag ${tag} does not match package version v${packageVersion}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log(`Release version ${packageVersion} is consistent.`);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Herdr overlay pane shown after a worktree checkout has been removed.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
source_root=${HERDR_WORKTREEINCLUDE_SOURCE:?Missing source repository}
|
|
6
|
+
branch=${HERDR_WORKTREEINCLUDE_BRANCH:?Missing branch name}
|
|
7
|
+
|
|
8
|
+
clear
|
|
9
|
+
printf '\n Worktree checkout removed\n\n'
|
|
10
|
+
printf ' Delete the local branch "%s" too?\n\n' "$branch"
|
|
11
|
+
printf ' [d] Delete branch [Enter] Keep branch\n\n'
|
|
12
|
+
read -r -n 1 choice || true
|
|
13
|
+
printf '\n'
|
|
14
|
+
|
|
15
|
+
if [[ "$choice" == "d" || "$choice" == "D" ]]; then
|
|
16
|
+
git -C "$source_root" branch -d -- "$branch" >&2 || true
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
herdr_bin=${HERDR_BIN_PATH:-herdr}
|
|
20
|
+
"$herdr_bin" plugin pane close "${HERDR_PANE_ID:?Missing Herdr pane id}" || true
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copy source files which are both Git-ignored and selected by .worktreeinclude.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
usage() {
|
|
6
|
+
echo "usage: $0 SOURCE_WORKTREE DESTINATION_WORKTREE" >&2
|
|
7
|
+
exit 64
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
[[ $# -eq 2 ]] || usage
|
|
11
|
+
source_root=$(git -C "$1" rev-parse --show-toplevel)
|
|
12
|
+
destination_root=$(git -C "$2" rev-parse --show-toplevel)
|
|
13
|
+
include_file="$source_root/.worktreeinclude"
|
|
14
|
+
|
|
15
|
+
if [[ ! -f "$include_file" ]]; then
|
|
16
|
+
echo "No .worktreeinclude in $source_root; nothing to copy."
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# A project-local worktree is physically nested below the primary checkout.
|
|
21
|
+
# Do not treat another checkout's ignored files as source files to copy.
|
|
22
|
+
linked_worktrees=$(git -C "$source_root" worktree list --porcelain | awk '/^worktree / { print substr($0, 10) }')
|
|
23
|
+
|
|
24
|
+
is_other_worktree_path() {
|
|
25
|
+
local candidate=$1 worktree
|
|
26
|
+
while IFS= read -r worktree; do
|
|
27
|
+
[[ -n "$worktree" && "$worktree" != "$source_root" ]] || continue
|
|
28
|
+
case "$candidate" in
|
|
29
|
+
"$worktree"|"$worktree"/*) return 0 ;;
|
|
30
|
+
esac
|
|
31
|
+
done <<<"$linked_worktrees"
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
matches=$(mktemp "${TMPDIR:-/tmp}/herdr-worktreeinclude.XXXXXX")
|
|
36
|
+
pattern_root=$(mktemp -d "${TMPDIR:-/tmp}/herdr-worktreeinclude-patterns.XXXXXX")
|
|
37
|
+
trap 'rm -f "$matches"; rm -rf "$pattern_root"' EXIT
|
|
38
|
+
|
|
39
|
+
# Evaluate selection patterns in an otherwise empty Git repository. Evaluating
|
|
40
|
+
# them inside the source repository would also apply its .gitignore, whose
|
|
41
|
+
# higher precedence could defeat a .worktreeinclude negation (for example
|
|
42
|
+
# `config/` followed by `!config/example.json`).
|
|
43
|
+
git -C "$pattern_root" init -q
|
|
44
|
+
cp "$include_file" "$pattern_root/.gitignore"
|
|
45
|
+
|
|
46
|
+
# git ls-files supplies only ignored, untracked paths. check-ignore then applies
|
|
47
|
+
# .worktreeinclude with Git's own .gitignore-compatible pattern engine.
|
|
48
|
+
git -C "$source_root" ls-files --others --ignored --exclude-standard -z \
|
|
49
|
+
| git -C "$pattern_root" check-ignore --no-index --stdin -z >"$matches" || true
|
|
50
|
+
|
|
51
|
+
copied=0
|
|
52
|
+
skipped=0
|
|
53
|
+
while IFS= read -r -d '' relative_path; do
|
|
54
|
+
# Reject paths which could escape either worktree. Git normally cannot emit
|
|
55
|
+
# these, but the guard makes copying safe even with unusual filenames.
|
|
56
|
+
[[ "$relative_path" != /* && "$relative_path" != ../* && "$relative_path" != */../* ]] || continue
|
|
57
|
+
|
|
58
|
+
source_path="$source_root/$relative_path"
|
|
59
|
+
destination_path="$destination_root/$relative_path"
|
|
60
|
+
[[ -e "$source_path" || -L "$source_path" ]] || continue
|
|
61
|
+
is_other_worktree_path "$source_path" && continue
|
|
62
|
+
|
|
63
|
+
# Never overwrite a file Git checked out in the destination.
|
|
64
|
+
if git -C "$destination_root" ls-files --error-unmatch -- "$relative_path" >/dev/null 2>&1; then
|
|
65
|
+
echo "Skipping tracked destination path: $relative_path" >&2
|
|
66
|
+
((skipped += 1))
|
|
67
|
+
continue
|
|
68
|
+
fi
|
|
69
|
+
if [[ -e "$destination_path" || -L "$destination_path" ]]; then
|
|
70
|
+
echo "Skipping existing destination path: $relative_path" >&2
|
|
71
|
+
((skipped += 1))
|
|
72
|
+
continue
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
mkdir -p "$(dirname "$destination_path")"
|
|
76
|
+
# -P preserves symlinks, -R copies recursively if Git reports a directory,
|
|
77
|
+
# and -p retains the source mode and timestamps.
|
|
78
|
+
cp -pPR "$source_path" "$destination_path"
|
|
79
|
+
((copied += 1))
|
|
80
|
+
done <"$matches"
|
|
81
|
+
|
|
82
|
+
echo "Copied $copied .worktreeinclude file(s); skipped $skipped."
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Interactive Herdr pane entrypoint. The pane starts in the source workspace.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
plugin_root=${HERDR_PLUGIN_ROOT:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"}
|
|
6
|
+
source_root=$(git rev-parse --show-toplevel)
|
|
7
|
+
|
|
8
|
+
read -r -p "Branch name: " branch
|
|
9
|
+
[[ -n "$branch" ]] || { echo "A branch name is required." >&2; exit 64; }
|
|
10
|
+
read -r -p "Base ref [HEAD]: " base
|
|
11
|
+
base=${base:-HEAD}
|
|
12
|
+
|
|
13
|
+
# Keep branches separate without allowing a branch name to traverse directories.
|
|
14
|
+
slug=$(printf '%s' "$branch" | tr '/[:space:]' '--' | tr -cd '[:alnum:]._-')
|
|
15
|
+
[[ -n "$slug" ]] || { echo "Branch name contains no usable path characters." >&2; exit 64; }
|
|
16
|
+
worktree_path="$source_root/.herdr/worktrees/$slug"
|
|
17
|
+
|
|
18
|
+
if [[ -e "$worktree_path" ]]; then
|
|
19
|
+
echo "Destination already exists: $worktree_path" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
mkdir -p "$(dirname "$worktree_path")"
|
|
24
|
+
if git show-ref --verify --quiet "refs/heads/$branch"; then
|
|
25
|
+
git worktree add "$worktree_path" "$branch"
|
|
26
|
+
else
|
|
27
|
+
git worktree add -b "$branch" "$worktree_path" "$base"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
"$plugin_root/scripts/copy-worktreeinclude.sh" "$source_root" "$worktree_path"
|
|
31
|
+
|
|
32
|
+
herdr_bin=${HERDR_BIN_PATH:-herdr}
|
|
33
|
+
"$herdr_bin" worktree open --cwd "$source_root" --path "$worktree_path" --focus
|
|
34
|
+
echo "Opened $worktree_path in Herdr."
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const event = JSON.parse(process.env.HERDR_PLUGIN_EVENT_JSON ?? "{}");
|
|
2
|
+
|
|
3
|
+
for (const candidate of [event?.worktree?.path, event?.data?.worktree?.path, event?.payload?.worktree?.path]) {
|
|
4
|
+
if (typeof candidate === "string" && candidate.startsWith("/")) {
|
|
5
|
+
process.stdout.write(candidate);
|
|
6
|
+
break;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Make Herdr's native worktree dialog project-local, then restore selected
|
|
3
|
+
# ignored files. Herdr v1 emits this hook after it has created the checkout,
|
|
4
|
+
# so relocation necessarily happens immediately after the dialog completes.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
plugin_root=${HERDR_PLUGIN_ROOT:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"}
|
|
8
|
+
target=$(node "$plugin_root/scripts/event-worktree-path.mjs")
|
|
9
|
+
[[ -n "$target" ]] || { echo "Could not find worktree path in Herdr event payload." >&2; exit 0; }
|
|
10
|
+
|
|
11
|
+
# The primary worktree provides the shared repository's local ignored files.
|
|
12
|
+
source=$(git -C "$target" worktree list --porcelain | awk '/^worktree / { print substr($0, 10); exit }')
|
|
13
|
+
[[ -n "$source" ]] || exit 0
|
|
14
|
+
branch=$(git -C "$target" branch --show-current)
|
|
15
|
+
[[ -n "$branch" ]] || { echo "Skipping detached worktree: $target" >&2; exit 0; }
|
|
16
|
+
slug=$(printf '%s' "$branch" | tr '[:space:]/' '--' | tr -cd '[:alnum:]._-')
|
|
17
|
+
[[ -n "$slug" ]] || { echo "Could not make a safe path from branch: $branch" >&2; exit 1; }
|
|
18
|
+
destination="$source/.herdr/worktrees/$slug"
|
|
19
|
+
|
|
20
|
+
# A plugin-created project-local worktree already has the right path.
|
|
21
|
+
if [[ "$target" != "$destination" ]]; then
|
|
22
|
+
if [[ -e "$destination" ]]; then
|
|
23
|
+
echo "Project-local worktree path already exists; leaving Herdr worktree at $target" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
herdr_bin=${HERDR_BIN_PATH:-herdr}
|
|
28
|
+
# Capture the workspace while Herdr still recognizes the original path.
|
|
29
|
+
workspace_id=$("$herdr_bin" worktree list --cwd "$source" --json \
|
|
30
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s).result;const w=(r.worktrees||[]).find(x=>x.path===process.argv[1]);if(w?.open_workspace_id)process.stdout.write(w.open_workspace_id)})' "$target")
|
|
31
|
+
"$plugin_root/scripts/copy-worktreeinclude.sh" "$source" "$target"
|
|
32
|
+
mkdir -p "$(dirname "$destination")"
|
|
33
|
+
git -C "$source" worktree move "$target" "$destination"
|
|
34
|
+
|
|
35
|
+
# Herdr's workspace is still attached to the old path. Close it, then open
|
|
36
|
+
# the relocated checkout as the replacement workspace.
|
|
37
|
+
[[ -z "$workspace_id" ]] || "$herdr_bin" workspace close "$workspace_id"
|
|
38
|
+
"$herdr_bin" worktree open --cwd "$source" --path "$destination" --focus
|
|
39
|
+
echo "Relocated worktree to $destination"
|
|
40
|
+
else
|
|
41
|
+
"$plugin_root/scripts/copy-worktreeinclude.sh" "$source" "$target"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Store the final checkout path. The removal event occurs after Git has
|
|
45
|
+
# deleted that checkout, so this is how the cleanup pane knows its branch and
|
|
46
|
+
# source repository.
|
|
47
|
+
managed_path=${destination:-$target}
|
|
48
|
+
state_dir=${HERDR_PLUGIN_STATE_DIR:-"$plugin_root/.state"}
|
|
49
|
+
state_file="$state_dir/managed-worktrees.tsv"
|
|
50
|
+
mkdir -p "$state_dir"
|
|
51
|
+
state_tmp=$(mktemp "$state_dir/managed-worktrees.XXXXXX")
|
|
52
|
+
if [[ -f "$state_file" ]]; then
|
|
53
|
+
awk -F '\t' -v path="$managed_path" '$1 != path' "$state_file" >"$state_tmp"
|
|
54
|
+
fi
|
|
55
|
+
printf '%s\t%s\t%s\n' "$managed_path" "$source" "$branch" >>"$state_tmp"
|
|
56
|
+
mv "$state_tmp" "$state_file"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Open an in-Herdr branch-cleanup overlay after a plugin-managed checkout is
|
|
3
|
+
# removed. This hook deliberately does not use macOS dialogs.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
plugin_root=${HERDR_PLUGIN_ROOT:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"}
|
|
7
|
+
target=$(node "$plugin_root/scripts/event-worktree-path.mjs")
|
|
8
|
+
[[ -n "$target" ]] || exit 0
|
|
9
|
+
|
|
10
|
+
state_dir=${HERDR_PLUGIN_STATE_DIR:-"$plugin_root/.state"}
|
|
11
|
+
state_file="$state_dir/managed-worktrees.tsv"
|
|
12
|
+
[[ -f "$state_file" ]] || exit 0
|
|
13
|
+
record=$(awk -F '\t' -v path="$target" '$1 == path { print; exit }' "$state_file")
|
|
14
|
+
[[ -n "$record" ]] || exit 0
|
|
15
|
+
IFS=$'\t' read -r _ source branch <<<"$record"
|
|
16
|
+
|
|
17
|
+
state_tmp=$(mktemp "$state_dir/managed-worktrees.XXXXXX")
|
|
18
|
+
awk -F '\t' -v path="$target" '$1 != path' "$state_file" >"$state_tmp"
|
|
19
|
+
mv "$state_tmp" "$state_file"
|
|
20
|
+
|
|
21
|
+
herdr_bin=${HERDR_BIN_PATH:-herdr}
|
|
22
|
+
|
|
23
|
+
# If the branch tip is already contained in the source checkout's HEAD, no
|
|
24
|
+
# work remains unique to the worktree. Delete it without interrupting the UI.
|
|
25
|
+
if git -C "$source" merge-base --is-ancestor "$branch" HEAD; then
|
|
26
|
+
git -C "$source" branch -d -- "$branch" >&2 || true
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
"$herdr_bin" plugin pane open \
|
|
31
|
+
--plugin "${HERDR_PLUGIN_ID:-serhii-chernenko.worktreeinclude}" \
|
|
32
|
+
--entrypoint branch-cleanup \
|
|
33
|
+
--placement overlay \
|
|
34
|
+
--env "HERDR_WORKTREEINCLUDE_SOURCE=$source" \
|
|
35
|
+
--env "HERDR_WORKTREEINCLUDE_BRANCH=$branch" \
|
|
36
|
+
--focus
|