Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
3194135
chore(.yamllint): drop yamlfmt
michen00 Dec 24, 2025
817b779
test: add tests
michen00 Dec 24, 2025
a18fbbc
docs(Makefile): clarify a target
michen00 Dec 24, 2025
cb985e1
chore(Makefile): match to boilerplate
michen00 Dec 24, 2025
aaab382
build(Makefile): improve make develop
michen00 Dec 24, 2025
276b554
chore(Makefile): match to updated boilerplate
michen00 Dec 24, 2025
ab24b7c
fix(Makefile): fix verbosity
michen00 Dec 24, 2025
30fd8ec
refactor: exit on error instead of warning
michen00 Dec 24, 2025
89b7ff0
chore: address Copilot feedback
michen00 Dec 24, 2025
9daa8e3
fix(tests): update assertion for unknown option
michen00 Dec 24, 2025
2b8830d
chore: prefer switch over checkout
michen00 Dec 24, 2025
741a74f
chore(ach): clarify a message
michen00 Dec 24, 2025
5c4851d
fix(git-shed): update string search
michen00 Dec 24, 2025
5230615
fix: improve portability
michen00 Dec 24, 2025
81c6349
fix(update-mine): handle empty branches
michen00 Dec 24, 2025
ae3fde4
test(tests/gcfixup.bats): add a test
michen00 Dec 24, 2025
5cfbf6f
feat(how-big): improve security
michen00 Dec 24, 2025
d0ee5f5
fix(git-shed): fix grep pattern for target branch
michen00 Dec 24, 2025
33d7959
fix(ach): remove index from stash message
michen00 Dec 24, 2025
74e7866
fix(gcfixup): add warning for root commit
michen00 Dec 24, 2025
adaba93
fix(git-shed): add sed for whitespace handling
michen00 Dec 24, 2025
b7b3332
fix(update-mine): clarify --all option usage
michen00 Dec 24, 2025
d051b86
fix(venv-now): add python fallback
michen00 Dec 24, 2025
58a7cd2
chore: remove unused WAIT
michen00 Dec 24, 2025
86ea3e7
fix(mergewith): add upstream guard
michen00 Dec 24, 2025
d9c4554
feat(update-mine): improve error handling
michen00 Dec 24, 2025
8b27389
feat(venv-now): reject dangerous paths
michen00 Dec 24, 2025
4dbf6a5
fix(chdirx): skip symlinks
michen00 Dec 24, 2025
305fd7f
fix(git-shed): compare against the remote
michen00 Dec 24, 2025
13b7963
feat(how-big): remove pipefail
michen00 Dec 24, 2025
8a132e1
fix(mergewith): handle detached HEAD
michen00 Dec 24, 2025
c560806
test(venv-now): reject dangerous paths
michen00 Dec 24, 2025
9ddd142
test(tests/ach.bats): add a regression test
michen00 Dec 24, 2025
fa48127
ci(.github/workflows/CI.yml): bump CACHE_NUMBER
michen00 Dec 24, 2025
ac4e8ec
chore: improve portability of color codes
michen00 Dec 24, 2025
010db32
fix(ci): correct pre-commit installation command
michen00 Dec 24, 2025
f839b18
fix(tests/mergewith.bats): update remote repo path
michen00 Dec 24, 2025
5a35f55
chore(tests/gcfixup.bats): clarify a parameter
michen00 Dec 24, 2025
ea63a2e
fix(git-shed): use subshells
michen00 Dec 24, 2025
61d0391
perf: don't cache pip since we don't use it
michen00 Dec 24, 2025
582fbd1
perf: remove date from cache key
michen00 Dec 24, 2025
fe3b837
fix(3611543692): address PR review feedback (#42)
Copilot Dec 24, 2025
836385e
fix(ach): address identified issues
michen00 Dec 24, 2025
9634795
fix(gcfixup): address identified issues
michen00 Dec 24, 2025
4c5c6f8
fix(git-shed): address identified issues
michen00 Dec 24, 2025
193809d
fix(how-big): address identified issues
michen00 Dec 24, 2025
b614dd3
fix(Makefile): address identified issues
michen00 Dec 24, 2025
2d4bf82
fix(venv-now): address identified issues
michen00 Dec 24, 2025
8233886
fix: address identified issues
michen00 Dec 24, 2025
ec5f9ae
docs(README.md): remove a TODO
michen00 Dec 24, 2025
fa382fe
fix(git-shed): address identified issues
michen00 Dec 24, 2025
f5ffb0c
fix(update-mine): address identified issues
michen00 Dec 25, 2025
bd2078a
fix(Makefile): improve error handling in cleanup
michen00 Dec 25, 2025
158e402
docs(update-mine): clarify a usage string
michen00 Dec 25, 2025
066bfc6
fix: fix test cleanup
michen00 Dec 25, 2025
7b0f81f
refactor: improve test safety
michen00 Dec 25, 2025
75c62c8
ci: clarify a name
michen00 Dec 25, 2025
e1a08ca
refactor: use explicit default
michen00 Dec 25, 2025
c6514fc
style: edit whitespace
michen00 Dec 25, 2025
e7fce0f
chore: autofix via pre-commit hooks
pre-commit-ci[bot] Dec 25, 2025
ba82832
docs(blame): ignore c6514fc
michen00 Dec 25, 2025
69b05ec
docs(mergewith): add a comment
michen00 Dec 25, 2025
47597f4
test: improve coverage
michen00 Dec 25, 2025
78aaefe
fix(venv-now): use -- for directory name safety
michen00 Dec 25, 2025
6cc6d92
fix(gcfixup): improve handling of root commits
michen00 Dec 25, 2025
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
1 change: 1 addition & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ e257f44613ca0004a3156c47b7b2b45137f05869 # style(README.md): use a shorter lang
6bf14baa530feb871219b09ea82dae6844692054 # style: fix indents
1858a6813b38f1c22e7ec0811927bf07d53e47c5 # style: fix indents
6857734c07a78c4a8c0411cb258b9dea40807147 # style(.editorconfig): rearrange a table
c6514fc9d69b1f148054ce944144f240df7dcf4c # style: edit whitespace
22 changes: 14 additions & 8 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ defaults:
shell: bash -el {0}

jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup BATS
uses: bats-core/[email protected]
- name: Run BATS tests
run: bats --jobs 4 --timing tests/*.bats

run-ci:
name: Run CI
runs-on: ubuntu-latest
Expand All @@ -23,26 +33,22 @@ jobs:
- uses: actions/setup-python@v6
with:
check-latest: true
cache: "pip"
- name: Install pre-commit
run: |
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pre-commit
- name: Set cache date
run: |
echo "DATE=$(date +'%Y%m%d')" >> "$GITHUB_ENV"
- name: Cache pre-commit hooks
id: cache-pre-commit-hooks
uses: actions/cache@v5
env:
CACHE_NUMBER: 0
CACHE_NUMBER: 1
with:
path: ~/.cache/pre-commit
key: ${{ runner.os }}-precommit-${{ hashFiles('.pre-commit-config.yaml') }}-${{ env.DATE }}-${{ env.CACHE_NUMBER }} # yamllint disable-line rule:line-length
- name: Install pre-commit hooks
key: ${{ runner.os }}-precommit-${{ hashFiles('.pre-commit-config.yaml') }}-${{ env.CACHE_NUMBER }} # yamllint disable-line rule:line-length
- name: Install pre-commit hook environments
if: steps.cache-pre-commit-hooks.outputs.cache-hit != 'true'
run: |
pre-commit install
pre-commit install-hooks
- name: Run pre-commit hooks
run: |
pre-commit run --all-files
1 change: 0 additions & 1 deletion .yamllint
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
extends: default

yaml-files:
- ".yamlfmt"
- ".yamllint"
- "*.yaml"
- "*.yml"
Expand Down
123 changes: 123 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
.ONESHELL:

DEBUG ?= false
VERBOSE ?= false

ifeq ($(DEBUG),true)
MAKEFLAGS += --debug=v
else ifneq ($(VERBOSE),true)
MAKEFLAGS += --silent
endif

PRECOMMIT ?= pre-commit
ifneq ($(shell command -v prek >/dev/null 2>&1 && echo y),)
PRECOMMIT := prek
ifneq ($(filter true,$(DEBUG) $(VERBOSE)),)
$(info Using prek for pre-commit checks)
ifeq ($(DEBUG),true)
PRECOMMIT := $(PRECOMMIT) -v
endif
endif
endif

# Terminal formatting (tput with fallbacks to ANSI codes)
_COLOR := $(shell tput sgr0 2>/dev/null || printf '\033[0m')
BOLD := $(shell tput bold 2>/dev/null || printf '\033[1m')
CYAN := $(shell tput setaf 6 2>/dev/null || printf '\033[0;36m')
GREEN := $(shell tput setaf 2 2>/dev/null || printf '\033[0;32m')
RED := $(shell tput setaf 1 2>/dev/null || printf '\033[0;31m')
YELLOW := $(shell tput setaf 3 2>/dev/null || printf '\033[0;33m')

.DEFAULT_GOAL := help
.PHONY: help
help: ## Show this help message
@echo "$(BOLD)Available targets:$(_COLOR)"
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "; max = 0} \
{if (length($$1) > max) max = length($$1)} \
{targets[NR] = $$0} \
END {for (i = 1; i <= NR; i++) { \
split(targets[i], arr, FS); \
printf "$(CYAN)%-*s$(_COLOR) %s\n", max + 2, arr[1], arr[2]}}'
@echo
@echo "$(BOLD)Environment variables:$(_COLOR)"
@echo " $(YELLOW)DEBUG$(_COLOR) = true|false Set to true to enable debug output (default: false)"
@echo " $(YELLOW)VERBOSE$(_COLOR) = true|false Set to true to enable verbose output (default: false)"

.PHONY: develop
WITH_HOOKS ?= true
develop: ## Set up the project for development (WITH_HOOKS={true|false}, default=true)
@if ! git config --local --get-all include.path | grep -q ".gitconfigs/alias"; then \
git config --local --add include.path "$(CURDIR)/.gitconfigs/alias"; \
fi
@git config blame.ignoreRevsFile .git-blame-ignore-revs
@set -e; \
if command -v git-lfs >/dev/null 2>&1; then \
git lfs install --local --skip-repo || true; \
fi; \
current_branch=$$(git branch --show-current); \
stash_was_needed=0; \
cleanup() { \
exit_code=$$?; \
if [ "$$current_branch" != "$$(git branch --show-current)" ]; then \
echo "$(YELLOW)Attempting to return to $$current_branch...$(_COLOR)"; \
if git switch "$$current_branch" 2>/dev/null; then \
echo "Successfully returned to $$current_branch"; \
else \
echo "$(RED)Error: Could not return to $$current_branch. You are on $$(git branch --show-current).$(_COLOR)" >&2; \
if [ "$$exit_code" -eq 0 ]; then exit_code=1; fi; \
fi; \
fi; \
if [ $$stash_was_needed -eq 1 ] && git stash list | head -1 | grep -q "Auto stash before switching to main"; then \
echo "$(YELLOW)Note: Your stashed changes are still available. Run 'git stash pop' to restore them.$(_COLOR)"; \
fi; \
exit $$exit_code; \
}; \
trap cleanup EXIT; \
if ! git diff --quiet || ! git diff --cached --quiet; then \
git stash push -m "Auto stash before switching to main"; \
stash_was_needed=1; \
fi; \
git switch main && git pull; \
if command -v git-lfs >/dev/null 2>&1; then \
git lfs pull || true; \
fi; \
git switch "$$current_branch"; \
if [ $$stash_was_needed -eq 1 ]; then \
if git stash apply; then \
git stash drop; \
else \
echo "$(RED)Error: Stash apply had conflicts. Resolve them, then run: git stash drop$(_COLOR)"; \
fi; \
fi; \
trap - EXIT
@if [ "$(WITH_HOOKS)" = "true" ]; then \
$(MAKE) enable-pre-commit; \
fi

.PHONY: test
PARALLEL ?= true
test: ## Run all tests (PARALLEL={true|false}, default=true)
@if [ "$(PARALLEL)" = "true" ]; then \
echo "$(CYAN)Running tests in parallel...$(_COLOR)"; \
bats --jobs 4 --timing tests/*.bats; \
else \
echo "$(CYAN)Running tests sequentially...$(_COLOR)"; \
bats tests/*.bats; \
fi

.PHONY: check
check: run-pre-commit test ## Run all code quality checks and tests

.PHONY: enable-pre-commit
enable-pre-commit: ## Enable pre-commit hooks (along with commit-msg and pre-push hooks)
@if command -v pre-commit >/dev/null 2>&1; then \
pre-commit install --hook-type commit-msg --hook-type pre-commit --hook-type pre-push --hook-type prepare-commit-msg ; \
else \
echo "$(YELLOW)Warning: pre-commit is not installed. Skipping hook installation.$(_COLOR)"; \
echo "Install it with: pip install pre-commit (or brew install pre-commit on macOS)"; \
fi

.PHONY: run-pre-commit
run-pre-commit: ## Run the pre-commit checks
$(PRECOMMIT) run --all-files
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,3 @@ Add the above line to your favorite shell configuration file (e.g. `~/.bashrc`,
- [`touchx`](touchx): Create (or update) a file and add `+x` permission to it.
- [`update-mine`](update-mine): Update all branches with open pull requests authored by you.
- [`venv-now`](venv-now): Create a new Python virtual environment in ./.venv (or the given directory), activating it if sourced.

## TODO

- fix indents
19 changes: 1 addition & 18 deletions ach
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,6 @@ if git status --short "$FILE" | grep -q "^[ MADRCU?]"; then
esac
fi

STASHED=false
if [ -n "$(git diff --cached)" ]; then
echo "There are staged changes. Stashing them temporarily..."
git stash push -k -m "Temporary stash for $SCRIPT_NAME"
STASHED=true
echo "Staged changes stashed."
fi

{
if [[ $INCLUDE_SUMMARY == true ]]; then
SUMMARY=$(git log -1 --format=%s "$HASH_ENTRY" 2>/dev/null || echo "summary unavailable")
Expand All @@ -127,18 +119,9 @@ fi
fi
fi

git commit --no-verify -m "$COMMIT_MSG"
git commit --no-verify -m "$COMMIT_MSG" -- "$FILE"
echo "Successfully updated and committed $FILE."
} || {
echo "Failed to update $FILE. Resolve manually."
if [ "$STASHED" = true ]; then git stash pop || echo "Failed to apply stashed changes."; fi
exit 1
}

if [ "$STASHED" = true ]; then
echo "Reapplying stashed changes..."
git stash pop || {
echo "Failed to apply stashed changes. Resolve manually."
exit 1
}
fi
2 changes: 2 additions & 0 deletions chdirx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ process_directory() {
for file in "$dir"/.* "$dir"/*; do
# Skip if file doesn't exist (handles empty dirs) or if it's `.` or `..`
[[ -e $file && $file != "$dir/." && $file != "$dir/.." ]] || continue
# Skip symlinks to prevent loops and operating outside target tree
[[ -L $file ]] && continue
Comment on lines +70 to +71
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The chdirx symlink handling added on lines 70-71 is not covered by tests. The test file chdirx.bats doesn't include a test case for symlinks. Since symlink handling is a security-relevant feature (prevents directory traversal and loops), consider adding test cases that verify symlinks are properly skipped.

Copilot uses AI. Check for mistakes.
if [[ -f $file ]]; then
# Check if the first line starts with #!
if head -n 1 "$file" | grep -q '^#!'; then
Expand Down
17 changes: 15 additions & 2 deletions gcfixup
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ gcfixup() {
$SCRIPT_NAME - Create a fixup commit and automatically rebase with autosquash

Usage:
$SCRIPT_NAME <fixup commit hash> [options]
$SCRIPT_NAME <target commit hash> [options]

Description:
This script creates a fixup commit for the specified commit hash and then
performs an interactive rebase with autosquash and autostash enabled.

Arguments:
<fixup commit hash> The commit hash to fix up.
<target commit hash> The commit hash to fix up.
[options] Optional arguments to pass to 'git commit'.

Example:
Expand All @@ -33,6 +33,19 @@ EOF
local commit_hash="$1"
shift # Remove the commit hash from the arguments

# Validate that the commit exists
if ! git rev-parse --verify "${commit_hash}^{commit}" &>/dev/null; then
echo "Error: Invalid commit hash '$commit_hash'." >&2
return 1
fi

# Check if this is the root commit (has no parent)
if ! git rev-parse --verify "$commit_hash"~1 &>/dev/null; then
echo "Error: Cannot fixup root commit (it has no parent to rebase onto)." >&2
echo "Workaround: git commit --fixup=$commit_hash && git rebase -i --autosquash --root" >&2
return 1
fi

if ! git commit --fixup="$commit_hash" "$@"; then
return 1
fi
Expand Down
14 changes: 9 additions & 5 deletions git-shed
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@ fi
echo "Fetching latest remote info and pruning..."
git fetch --prune

# Identify local branches fully merged into the target branch.
MERGED_BRANCHES=$(git branch --merged "$TARGET_BRANCH" |
grep -v "$TARGET_BRANCH" |
# Identify local branches fully merged into the remote target branch.
# Using origin/$TARGET_BRANCH ensures we compare against the up-to-date remote.
# Wrap in subshell so filtering applies to both the primary and fallback command.
MERGED_BRANCHES=$( (git branch --merged "origin/$TARGET_BRANCH" 2>/dev/null ||
git branch --merged "$TARGET_BRANCH") |
Comment on lines +67 to +68
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The git-shed changes to use 'origin/$TARGET_BRANCH' as a fallback (lines 67-68) are not specifically tested. The existing tests use --dry-run but don't verify the behavior when the remote branch exists vs when it doesn't. Consider adding a test that verifies the fallback logic works correctly when origin/$TARGET_BRANCH is not available.

Copilot uses AI. Check for mistakes.
grep -v "^\*" |
sed 's/^ //')
sed 's/^ //' |
grep -v -F -x "$TARGET_BRANCH")

if [ -z "$MERGED_BRANCHES" ]; then
echo "No local branches merged into '$TARGET_BRANCH' found. Nothing to clean."
Expand All @@ -91,7 +94,8 @@ else
fi

# Identify local branches that no longer have a remote.
STALE_BRANCHES=$(git branch -v | grep '\[gone\]' | awk '{print $1}')
# Use -vv to show tracking info; ": gone]" indicates deleted upstream
STALE_BRANCHES=$(git branch -vv | awk '/: gone]/ {print ($1 == "*" ? $2 : $1)}')
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The awk pattern '/: gone]/' is fragile and could match false positives in branch names or commit messages that contain this string. Consider using a more specific pattern like '/ [.*: gone]/' to match the actual git output format more precisely.

Suggested change
STALE_BRANCHES=$(git branch -vv | awk '/: gone]/ {print ($1 == "*" ? $2 : $1)}')
STALE_BRANCHES=$(git branch -vv | awk '/ \[.*: gone\]/ {print ($1 == "*" ? $2 : $1)}')

Copilot uses AI. Check for mistakes.

if [ -z "$STALE_BRANCHES" ]; then
echo "No stale branches (i.e., branches with no remote) found."
Expand Down
35 changes: 30 additions & 5 deletions how-big
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/bash
set -euo pipefail
# Note: we still allow partial results when permission errors occur on some
# entries. We capture non-zero exits from find/du as "partial" rather than
# failing the script, but keep pipefail to catch genuine pipeline issues.

SCRIPT_NAME=$(basename "$0")

Expand All @@ -26,14 +29,14 @@ EOF
}

# Default behavior (show directories only)
FILES_TOO=""
SHOW_FILES="false"

# Parse command-line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-a) FILES_TOO="-a" ;; # Enable file size display
-h | --help) usage ;; # Show help and exit
*) TARGET_DIR="$1" ;; # Assume anything else is the directory argument
-a) SHOW_FILES="true" ;; # Enable file size display
-h | --help) usage ;; # Show help and exit
*) TARGET_DIR="$1" ;; # Assume anything else is the directory argument
esac
shift
done
Expand All @@ -48,4 +51,26 @@ if [[ ! -d $TARGET_DIR ]]; then
fi

# Run the disk usage command with optional file inclusion
du $FILES_TOO -h -d 1 "$TARGET_DIR" | sort -hr
DU_TMP=$(mktemp)
partial=false
trap 'rm -f "$DU_TMP"' EXIT

if [[ "$SHOW_FILES" == "true" ]]; then
# Cross-platform: use find + du for files, then du for directories
if ! find -- "$TARGET_DIR" -maxdepth 1 -type f -exec du -h {} + >>"$DU_TMP" 2>/dev/null; then
partial=true
fi
if ! du -h -d 1 -- "$TARGET_DIR" >>"$DU_TMP" 2>/dev/null; then
partial=true
fi
else
if ! du -h -d 1 -- "$TARGET_DIR" >>"$DU_TMP" 2>/dev/null; then
partial=true
fi
fi

sort -hr "$DU_TMP" | uniq

if [[ "$partial" == true ]]; then
echo "Warning: some entries were skipped (e.g., permission denied)." >&2
fi
Loading
Loading