Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions bin/git-brebase
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# Interactively rebase every commit this branch added on top of its base.
#
# Usage: git brebase [base]
#
# Rebasing from the merge-base means only this branch's own commits show
# up in the todo list, however far the base has moved on since.
#
# The base branch defaults to the first of these that exists locally:
# dev, development, main, master.

set -e

BASE_BRANCH_CANDIDATES=(dev development main master)

base="$1"

if [ -z "$base" ]; then
for candidate in "${BASE_BRANCH_CANDIDATES[@]}"; do
if git show-ref --verify --quiet "refs/heads/$candidate"; then
base="$candidate"
break
fi
done
fi

git rebase -i "$(git merge-base HEAD ${base:+"$base"})"
36 changes: 36 additions & 0 deletions bin/git-cleanup
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
#
# Delete local branches that have already been merged into the base branch.
#
# Usage: git cleanup [base]
#
# The base branch defaults to the first of these that exists locally:
# dev, development, main, master.

set -e

PROTECTED_BRANCHES=(dev development main master)

base="$1"

if [ -z "$base" ]; then
for candidate in "${PROTECTED_BRANCHES[@]}"; do
if git show-ref --verify --quiet "refs/heads/$candidate"; then
base="$candidate"
break
fi
done
fi

# Never delete a protected branch, or the current one (marked "* name").
# Note this is a substring match, so a branch named "devops" is also
# skipped -- it errs towards keeping branches rather than deleting them.
pattern="$(
IFS='|'
printf '%s' "${PROTECTED_BRANCHES[*]}"
)|\*"

# With no base branch found, --merged falls back to HEAD.
git branch --merged ${base:+"$base"} \
| grep -Ev "$pattern" \
| xargs -n 1 git branch -d
62 changes: 62 additions & 0 deletions bin/git-edit
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
#
# Amend or split a commit further back in history.
#
# Usage: git edit <commit>
#
# Rebases back to <commit> and uncommits it, then lets you pick the hunks
# that stay in it via `git add -p`. Anything you leave behind is carried
# forward so it lands on the following commit instead.
#
# Deliberately avoids `set -e`: every step routes failure to abort(), so a
# half-finished edit never leaves a rebase in progress.
#
# Caveats, per the source gist:
# - added files can be dropped from a commit, deleted files cannot
# - removing every change from a commit aborts
# - a conflict later in the rebase aborts
# - bailing out of `git add -p` needs a manual `git rebase --abort`
#
# Source: https://gist.github.com/slifty/68bd451896f6494b870280c00dc4fe9c

edit_stash=0

abort() {
echo 'Aborting the edit...'
if [ "$edit_stash" = "1" ]; then
git stash drop -q
edit_stash=0
fi
git rebase --abort
git add -A
git reset
exit 1
}

if [ -z "$1" ]; then
echo "edit: usage: git edit <commit>"
exit 1
fi

# Stop the rebase at the target commit by turning its "pick" into "edit".
GIT_SEQUENCE_EDITOR="sed -i '' '1s/^pick/edit/'" git rebase -i "$1~" || abort

# Uncommit it, keeping the changes in the worktree, then choose what
# stays. The intent-to-add pass is what makes new files visible to -p.
git reset HEAD~1 || abort
git add -AN || abort
git add -p || abort
git commit -e -C HEAD@{1} || abort

if [ -z "$(git status --porcelain)" ]; then
git rebase --continue || abort
else
# Hunks left behind ride along to the next commit.
git add -A || abort
git stash -q || abort
edit_stash=1
git rebase --continue || abort
git stash pop -q || abort
edit_stash=0
git reset || abort
fi
43 changes: 43 additions & 0 deletions bin/git-sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
#
# Merge an upstream's changes into a fresh sync branch.
#
# Usage: git sync [ref]
#
# Intended for forks: run it on main/master and it branches off, merges
# the upstream ref, and leaves you on that branch to push and PR. With
# no ref it uses the upstream's default branch.
#
# The upstream remote is $GIT_UPSTREAM_NAME, defaulting to "upstream".

set -e

remote="${GIT_UPSTREAM_NAME:-upstream}"
branch="$(git rev-parse --abbrev-ref HEAD)"

if [ "$branch" != "main" ] && [ "$branch" != "master" ]; then
echo "Error: Must be on main or master branch (currently on $branch)"
exit 1
fi

git fetch "$remote" --tags

if [ -n "$1" ]; then
ref="$1"
sync_branch="sync/${ref}"
else
git remote set-head "$remote" --auto > /dev/null 2>&1 || true
upstream_branch="$(git symbolic-ref "refs/remotes/$remote/HEAD" 2> /dev/null | sed "s|refs/remotes/$remote/||")"

if [ -z "$upstream_branch" ]; then
echo "Error: Could not determine default branch for $remote"
exit 1
fi

ref="$remote/$upstream_branch"
# Timestamped, since the default branch syncs repeatedly.
sync_branch="sync/$(date +%s)-${upstream_branch}"
fi

git checkout -b "$sync_branch"
git merge "$ref" -m "Merge upstream ${ref}"
66 changes: 66 additions & 0 deletions bin/git-up
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
#
# Bring the current branch up to date with its base branch.
#
# Usage: git up [base]
#
# The base branch is, in order of preference:
# 1. the argument passed to this command
# 2. `branch.<current>.base` from git config
# 3. the remote's default branch (e.g. origin/HEAD)
#
# On the base branch itself this fast-forwards. On any other branch it
# rebases onto the base, autostashing local changes.

set -e

branch="$(git symbolic-ref --quiet --short HEAD)" || {
echo "up: not on a branch"
exit 1
}

remote="$(git config --get "branch.$branch.remote" || echo origin)"

# Determine the base branch, falling back to the remote's default branch.
base="${1:-$(git config --get "branch.$branch.base" || true)}"

if [ -z "$base" ]; then
git remote set-head "$remote" --auto > /dev/null 2>&1 || true
base="$(git symbolic-ref --quiet --short "refs/remotes/$remote/HEAD" | sed "s|^$remote/||")"
fi

if [ -z "$base" ]; then
echo "up: could not determine a base branch"
exit 1
fi

# Already on the base branch: just fast-forward it.
if [ "$branch" = "$base" ]; then
git fetch "$remote" "$base"
git merge --ff-only "$remote/$base"
exit 0
fi

if git show-ref --verify --quiet "refs/heads/$base"; then
# Fast-forward the local base branch without checking it out. If that
# fails the local branch has diverged (or the remote has no such
# branch), so leave it alone and carry on with whatever we have.
if ! git fetch "$remote" "$base:$base" 2> /dev/null; then
if git fetch "$remote" "$base" 2> /dev/null; then
echo "up: local $base has diverged; leaving it alone"
else
echo "up: no $base on $remote; using local $base"
fi
fi
else
git fetch "$remote" "$base"
fi

# Prefer the remote-tracking ref when one exists.
target="$base"
if git rev-parse --verify --quiet "refs/remotes/$remote/$base" > /dev/null; then
target="$remote/$base"
fi

echo "up: rebasing $branch onto $target"
git rebase --autostash "$target"
4 changes: 3 additions & 1 deletion claude/config/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ take precedence where they conflict.
nothing else. No braces, brackets, quotes, or other punctuation wrapping
either part. Look the title up rather than guessing it.

Issue #142 Fix login redirect loop
```
Issue #142 Fix login redirect loop
```

Not `Issue #142 {Fix login redirect loop}` — any placeholder markup in these
instructions indicates text to replace, and never appears in the output.
Expand Down
4 changes: 1 addition & 3 deletions claude/config/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
"deny": ["Bash(sed -i *)"]
},
"attribution": {
"commit": "",
"pr": "",
"sessionUrl": false
"sessionUrl": true
},
"spinnerVerbs": {
"mode": "replace",
Expand Down
9 changes: 5 additions & 4 deletions git/gitconfig.symlink
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@
# NOTE: [user] section (name, email, signingkey) is configured in ~/.gitconfig.local
# This file is generated from git/gitconfig.local.symlink.example during bootstrap
[alias]
cleanup = "!f() { declare -a BASE_BRANCH_NAMES_DESC_PRIORTY=("dev" "development" "main" "master") && for i in "${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; do if [ -z "$BASE_BRANCH" ]; then if $(git show-ref --verify --quiet refs/heads/$i); then BASE_BRANCH="$i"; fi; fi; done; BRANCH_NAMES_STRING="${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; GREP_PARAMS="${BRANCH_NAMES_STRING// /\\\\|}\\\\\\|\\\\*"; git branch --merged ${1-$BASE_BRANCH} | grep -v "$GREP_PARAMS" | xargs -n 1 git branch -d; }; f"
brebase = "!f() { declare -a BASE_BRANCH_NAMES_DESC_PRIORTY=("dev" "development" "main" "master") && for i in "${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; do if [ -z "$BASE_BRANCH" ]; then if $(git show-ref --verify --quiet refs/heads/$i); then BASE_BRANCH="$i"; fi; fi; done; git rebase -i `git merge-base HEAD ${1-$BASE_BRANCH}`; }; f"
cleanup = !~/.dotfiles/bin/git-cleanup
brebase = !~/.dotfiles/bin/git-brebase
plog = log --oneline --graph --all --decorate --pretty='format:%C(yellow)%h%C(auto)%d %Creset%s %C(cyan)[%an] %C(blue)%ar'
edit = !"f() { EDIT_STASH=0 && GIT_SEQUENCE_EDITOR=\"sed -i '' '1s/^pick/edit/'\" git rebase -i \"$1~\" && git reset HEAD~1 && git add -AN && git add -p && git commit -e -C HEAD@{1} && if [ -z \"$(git status --porcelain)\" ]; then git rebase --continue; else git add -A && EDIT_STASH=1 && git stash -q && git rebase --continue && git stash pop -q && EDIT_STASH=0 && git reset; fi || (echo 'Aborting the edit...' && if [ \"$EDIT_STASH\" = \"1\" ]; then git stash drop -q && EDIT_STASH=0; fi && git rebase --abort && git add -A && git reset); }; f"
edit = !~/.dotfiles/bin/git-edit
cremit = commit --reedit-message=HEAD
sync = "!f() { remote=\"${GIT_UPSTREAM_NAME:-upstream}\"; branch=\"$(git rev-parse --abbrev-ref HEAD)\"; if [ \"$branch\" != \"main\" ] && [ \"$branch\" != \"master\" ]; then echo \"Error: Must be on main or master branch (currently on $branch)\"; return 1; fi; git fetch \"$remote\" --tags || return 1; if [ -n \"$1\" ]; then ref=\"$1\"; sync_branch=\"sync/${ref}\"; else git remote set-head \"$remote\" --auto 2>/dev/null; upstream_branch=\"$(git symbolic-ref \"refs/remotes/$remote/HEAD\" 2>/dev/null | sed \"s|refs/remotes/$remote/||\")\" || { echo \"Error: Could not determine default branch for $remote\"; return 1; }; ref=\"$remote/$upstream_branch\"; sync_branch=\"sync/$(date +%s)-${upstream_branch}\"; fi; git checkout -b \"$sync_branch\" && git merge \"$ref\" -m \"Merge upstream ${ref}\"; }; f"
sync = !~/.dotfiles/bin/git-sync
fpush = push --force-with-lease
track = !git branch --set-upstream-to=origin/$(git symbolic-ref --short HEAD)
up = !~/.dotfiles/bin/git-up
[pull]
default = current
rebase = true
Expand Down
1 change: 0 additions & 1 deletion git/gitignore.symlink
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*.swp
.sublime-workspace
.sublime-project
.java-version
.nvmrc
.vscode
.envrc
Expand Down
32 changes: 29 additions & 3 deletions system/install.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
#!/usr/bin/env bash
set -e
source ~/.dotfiles/lib/functions.sh

# Make sure our bin files are placed where we expect (this is added to path in ./path.zsh)
ln -ns ~/.dotfiles/bin $HOME/bin
if [ -L "$HOME/bin" ]; then
info 'bin is already linked'
else
# A real bin would otherwise swallow the symlink as $HOME/bin/bin
if [ -e "$HOME/bin" ]; then
if [ -e "$HOME/bin.backup" ]; then
fail 'bin exists and bin.backup is already taken, move one aside first'
fi
warn 'bin exists and will be replaced with a symlink'
info 'Backing up existing bin to bin.backup'
mv "$HOME/bin" "$HOME/bin.backup"
fi

info 'Linking bin'
ln -ns ~/.dotfiles/bin "$HOME/bin"
fi
Comment on lines +6 to +21

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in b8711e3.

Worth noting the failure is quieter than described: ln -ns against an existing directory doesn't fail at all. It creates the link inside it as $HOME/bin/bin, exits 0, and set -e never fires — so bootstrap reports success while the dotfiles bin is not where path.zsh expects it. Only the plain-file case exits non-zero.

Existing paths now move to bin.backup before linking, following the pattern in maestral/install.sh, with a collision guard so an existing backup is an error rather than a second clobber. Verified across four states: no bin, already linked, real directory, real file.

🤖 Generated with Claude Code

Session: d019263c-0e35-4452-b1c2-f685d76d5c18


# Open things that need to be opened once in order to boot on load
open /Applications/Rectangle.app
open /Applications/SoundSource.app
for app in Rectangle SoundSource; do
if [ -d "/Applications/$app.app" ]; then
open "/Applications/$app.app"
else
warn "$app is not installed, skipping"
fi
done

success 'System setup complete'