Skip to content

Instantly share code, notes, and snippets.

@zorgiepoo
Last active February 5, 2026 04:25
Show Gist options
  • Select an option

  • Save zorgiepoo/4615fd7f94cc711f461d7f0c347c5c64 to your computer and use it in GitHub Desktop.

Select an option

Save zorgiepoo/4615fd7f94cc711f461d7f0c347c5c64 to your computer and use it in GitHub Desktop.
Jujutsu workflow for advancing a bookmark after creating a commit.
#!/usr/bin/env bash
# Add to jj config:
# [aliases]
# cob = ["util", "exec", "--", "jj-commit-onto-bookmark.sh"]
set -euo pipefail
target_bookmark=""
target_bookmark_revset=""
commit_args=()
usage() {
cat <<EOF
Usage:
jj cob (-b|--bookmark BOOKMARK | -r|--revisions BOOKMARK_REVSET [default: @-])
[-i|--interactive]
[-m|--message MESSAGE]
[FILESETS]
[-- jj commit advanced-args...]
Rebase the working copy commit onto a bookmark with a description and update the bookmark.
This is useful in two scenarios:
1. Advancing a bookmark from old @- after creating a new commit on top. This is the default behavior of jj cob and behaves like jj commit --advance-branches feature would.
2. Inserting a commit onto a feature branch when using the megamerge workflow and advancing that bookmark when using -b or -r
By default if no arguments are specified, the bookmark is assumed to be at @-.
Otherwise exactly one of -b/--bookmark or -r/--revisions must be specified.
-i/--interactive and -m/--message are forwarded to 'jj commit' automatically.
Other commit options must be preceded by --.
EOF
exit 1
}
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
-b|--bookmark)
[[ -n "$target_bookmark_revset" ]] && usage
shift || usage
target_bookmark="$1"
shift
;;
-r|--revisions)
[[ -n "$target_bookmark" ]] && usage
shift || usage
target_bookmark_revset="$1"
shift
;;
-i|--interactive)
commit_args+=("$1")
shift
;;
-m|--message)
commit_args+=("$1")
shift || usage
commit_args+=("$1")
shift
;;
--)
shift
# Forward everything else verbatim to jj commit
commit_args+=("$@")
break
;;
-*)
echo "Unknown option: $1" >&2
usage
;;
*)
commit_args+=("$@")
break
;;
esac
done
# Default to @- if neither was specified
if [[ -z "$target_bookmark" && -z "$target_bookmark_revset" ]]; then
target_bookmark_revset="@-"
fi
# Resolve bookmark from revset if needed
if [[ -z "$target_bookmark" ]]; then
bookmarks="$(
jj --ignore-working-copy log \
--revisions "$target_bookmark_revset" \
--no-graph \
--template 'local_bookmarks ++ " "'
)"
read -r -a bookmark_array <<<"$bookmarks"
case "${#bookmark_array[@]}" in
0)
echo "No local bookmarks found at revision: $target_bookmark_revset" >&2
exit 1
;;
1)
# Remove trailing * in case it's a local bookmark that has diverged
target_bookmark="${bookmark_array[0]%\*}"
;;
*)
echo "Multiple local bookmarks found at revision $target_bookmark_revset:" >&2
for b in "${bookmark_array[@]}"; do
echo " $b" >&2
done
echo "Please specify one with --bookmark." >&2
exit 1
;;
esac
else
if ! jj --ignore-working-copy log \
--revisions "$target_bookmark" \
--no-graph \
--template 'local_bookmarks ++ " "' \
>/dev/null 2>&1
then
echo "Error: bookmark '$target_bookmark' does not exist or is not resolvable." >&2
exit 1
fi
fi
jj commit --quiet ${commit_args[@]+"${commit_args[@]}"}
new_change_id="$(jj --ignore-working-copy log --revisions @- --no-graph --template change_id)"
# Rebase will be skipped if it's not necessary
jj --ignore-working-copy rebase --revisions "$new_change_id" --insert-after "$target_bookmark" --quiet
jj --ignore-working-copy bookmark move "$target_bookmark" --to "$new_change_id" --quiet
jj status
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment