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
4 changes: 2 additions & 2 deletions uspecs/u/actn-uarchive.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Parameters:
- Active Change Folder path
- Output
- Folder moved to `{changes_folder}/archive`
- If on PR branch and Engineer confirms: git commit and push with message, branch and refs removed
- If on PR branch and Engineer confirms: git commit and push with message, branch and refs removed, deleted branch hash and restore instructions reported

Flow:

Expand All @@ -26,7 +26,7 @@ Flow:
1. Archive + git cleanup (commit, push, delete local branch and remote tracking ref)
2. Archive only (no git operations)
3. Cancel
- On option 1: `bash uspecs/u/scripts/uspecs.sh change archive <change-folder-name> -d`
- On option 1: `bash uspecs/u/scripts/uspecs.sh change archive <change-folder-name> -d`; script output includes deleted branch hash and restore instructions - relay to Engineer
- On option 2: `bash uspecs/u/scripts/uspecs.sh change archive <change-folder-name>`
- On option 3: abort, no action taken
- Otherwise: `bash uspecs/u/scripts/uspecs.sh change archive <change-folder-name>`
Expand Down
12 changes: 4 additions & 8 deletions uspecs/u/actn-upr.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,15 @@ Parameters:

Flow:

- Validate preconditions:
- Run `bash uspecs/u/scripts/uspecs.sh pr mergedef --validate`
- Merge default branch into change_branch:
- Run `bash uspecs/u/scripts/uspecs.sh pr mergedef`
- If script exits with error: report the error and stop
- Parse `change_branch` and `default_branch` from script output
- Parse `change_branch`, `default_branch`, and `change_branch_head` from script output
- Read Active Change Folder (change.md) to determine `issue_url` (may be absent) and derive `issue_id` from the URL (last path segment)
- Present Engineer with the following options:
1. Create PR (squash-merge `change_branch` into `{change_branch}--pr`, delete `change_branch`, create PR on GitHub)
2. Cancel
- On option 2: stop
- Merge default branch into change_branch:
- Run `bash uspecs/u/scripts/uspecs.sh pr mergedef`
- If script exits with error: report the error and stop
- If merge fails (conflicts): report error and ask Engineer to resolve conflicts and re-run
- Get specs diff to derive PR title and body:
- Run `bash uspecs/u/scripts/uspecs.sh diff specs`
- From the diff output identify `draft_title` and `draft_body`; construct `pr_title` and `pr_body` per `{templates_folder}/tmpl-pr.md`
Expand All @@ -45,4 +41,4 @@ Flow:
- Note: `pr_title` is passed on the command line; ensure it contains no shell-special characters (`<`, `>`, `$`, backticks)
- If script exits with error: report the error and stop
- Parse `pr_url` from script output
- Report `pr_url` to Engineer; inform that Engineer is now on `pr_branch` to address review comments
- Report `pr_url` and `change_branch_head` to Engineer; inform that Engineer is now on `pr_branch` to address review comments and that the deleted change branch can be restored with `git branch {change_branch} {change_branch_head}`
58 changes: 24 additions & 34 deletions uspecs/u/scripts/_lib/pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ set -Eeuo pipefail
# Fetch pr_remote and create a local branch from its default branch.
#
# pr.sh mergedef
# Fetch pr_remote and merge pr_remote/default_branch into the current branch.
# Validate preconditions, fetch pr_remote/default_branch, and merge it into the current branch.
# On success outputs:
# change_branch=<name>
# default_branch=<name>
# change_branch_head=<sha> (HEAD before the merge)
#
# pr.sh diff specs
# Output git diff of the specs folder between HEAD and pr_remote/default_branch.
Expand All @@ -44,10 +48,11 @@ set -Eeuo pipefail
# If no changes exist, switch to --next-branch and exit cleanly.
#
# pr.sh ffdefault
# Fetch pr_remote/default and fast-forward current branch to it
# Fetch pr_remote/default_branch and fast-forward the local default branch to it.
# Switches to the default branch if not already on it, and leaves there after completion.
# Fail fast if any of the following conditions are true:
# current branch is not default
# current branch is not clean
# working directory is not clean
# branches have diverged (fast-forward not possible)



Expand Down Expand Up @@ -75,7 +80,7 @@ read_conf_param() {
fi

local line raw
line=$(grep -E "^- ${param_name}:" "$conf_file" | head -1 || true)
line=$(_grep -E "^- ${param_name}:" "$conf_file" | head -1 || true)
raw="${line#*: }"

if [ -z "$raw" ]; then
Expand All @@ -93,7 +98,7 @@ error() {
}

determine_pr_remote() {
if git remote | grep -q '^upstream$'; then
if git remote | _grep -q '^upstream$'; then
echo "upstream"
else
echo "origin"
Expand All @@ -120,17 +125,8 @@ gh_create_pr() {

check_prerequisites() {
# Check if git repository exists
local dir="$PWD"
local found_git=false
while [[ "$dir" != "/" ]]; do
if [[ -d "$dir/.git" ]]; then
found_git=true
break
fi
dir=$(dirname "$dir")
done
if [[ "$found_git" == "false" ]]; then
error "No git repository found"
if ! is_git_repo "$PWD"; then
error "No git repository found at $PWD"
fi

# Check if GitHub CLI is installed
Expand All @@ -139,7 +135,7 @@ check_prerequisites() {
fi

# Check if origin remote exists
if ! git remote | grep -q '^origin$'; then
if ! git remote | _grep -q '^origin$'; then
error "'origin' remote does not exist"
fi

Expand Down Expand Up @@ -198,15 +194,16 @@ cmd_ffdefault() {
current_branch=$(git symbolic-ref --short HEAD)

if [[ "$current_branch" != "$default_branch" ]]; then
error "Current branch '$current_branch' is not the default branch '$default_branch'"
echo "Switching to '$default_branch'..."
git checkout "$default_branch"
fi

echo "Fetching $pr_remote/$default_branch..."
git fetch "$pr_remote" "$default_branch" 2>&1

echo "Fast-forwarding $current_branch..."
echo "Fast-forwarding $default_branch..."
if ! git merge --ff-only "$pr_remote/$default_branch" 2>&1; then
error "Cannot fast-forward '$current_branch' to '$pr_remote/$default_branch'. The branches have diverged."
error "Cannot fast-forward '$default_branch' to '$pr_remote/$default_branch'. The branches have diverged."
fi
}

Expand Down Expand Up @@ -275,14 +272,6 @@ cmd_pr() {
}

cmd_mergedef() {
local validate_only=false
while [[ $# -gt 0 ]]; do
case "$1" in
--validate) validate_only=true; shift ;;
*) error "Unknown flag: $1" ;;
esac
done

check_prerequisites

local pr_remote default_branch current_branch
Expand All @@ -298,17 +287,18 @@ cmd_mergedef() {
error "Current branch '$current_branch' ends with '--pr'; cannot create PR from a PR branch"
fi

if [[ "$validate_only" == "true" ]]; then
echo "change_branch=$current_branch"
echo "default_branch=$default_branch"
return 0
fi
local change_branch_head
change_branch_head=$(git rev-parse HEAD)

echo "Fetching $pr_remote/$default_branch..."
git fetch "$pr_remote" "$default_branch" 2>&1

echo "Merging $pr_remote/$default_branch into $current_branch..."
git merge "$pr_remote/$default_branch" 2>&1

echo "change_branch=$current_branch"
echo "default_branch=$default_branch"
echo "change_branch_head=$change_branch_head"
}

cmd_diff() {
Expand Down
63 changes: 61 additions & 2 deletions uspecs/u/scripts/_lib/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ checkcmds() {
done
}

# get_pr_info <pr_sh_path> <map_nameref>
# get_pr_info <pr_sh_path> <map_nameref> [project_dir]
# Calls pr.sh info and parses the key=value output into the given associative array.
# Keys populated: pr_remote, default_branch
# project_dir: directory to run pr.sh from (defaults to $PWD)
# Returns non-zero if pr.sh info fails.
get_pr_info() {
local pr_sh="$1"
local -n _pr_info_map="$2"
local project_dir="${3:-$PWD}"
local output
output=$(bash "$pr_sh" info) || return 1
output=$(cd "$project_dir" && bash "$pr_sh" info) || return 1
while IFS='=' read -r key value; do
[[ -z "$key" ]] && continue
_pr_info_map["$key"]="$value"
Expand All @@ -35,6 +37,63 @@ is_tty() {
[ -t 0 ]
}

# is_git_repo <dir>
# Returns 0 if <dir> is inside a git repository, 1 otherwise.
is_git_repo() {
local dir="$1"
(cd "$dir" && git rev-parse --git-dir > /dev/null 2>&1)
}

# _GREP_BIN caches the resolved grep binary path for _grep.
_GREP_BIN=""

# _grep [grep-args...]
# Portable grep wrapper. On Windows (msys/cygwin) resolves grep from the git
# installation and fails fast if not found. On other platforms uses system grep.
_grep() {
if [[ -z "$_GREP_BIN" ]]; then
case "$OSTYPE" in
msys*|cygwin*)
# Use where.exe to get real Windows paths, then pick the grep
# that lives inside the Git for Windows installation.
local git_path git_root candidate
git_path=$(where.exe git 2>/dev/null | head -1 | tr -d $'\r' | tr $'\\\\' / || true)
if [[ -z "$git_path" ]]; then
echo "Error: git not found; cannot locate git's bundled grep" >&2
exit 1
fi
git_root=$(dirname "$(dirname "$git_path")")
# Try direct path first (works even if grep is not on PATH).
# Also try one level up to handle mingw64/bin/git.exe layout where
# two dirnames give .../mingw64 instead of the git installation root.
if [[ -x "$git_root/usr/bin/grep.exe" ]]; then
_GREP_BIN="$git_root/usr/bin/grep.exe"
elif [[ -x "$(dirname "$git_root")/usr/bin/grep.exe" ]]; then
git_root=$(dirname "$git_root")
_GREP_BIN="$git_root/usr/bin/grep.exe"
else
# Fall back to where.exe grep, pick the one under git root
while IFS= read -r candidate; do
candidate=$(echo "$candidate" | tr -d $'\r' | tr $'\\\\' /)
if [[ "$candidate" == "$git_root/"* ]]; then
_GREP_BIN="$candidate"
break
fi
done < <(where.exe grep 2>/dev/null || true)
fi
if [[ -z "$_GREP_BIN" ]]; then
echo "Error: grep not found under git root: $git_root" >&2
exit 1
fi
;;
*)
_GREP_BIN="grep"
;;
esac
fi
"$_GREP_BIN" "$@"
}

# sed_inplace file sed-args...
# Portable in-place sed. Uses -i.bak for BSD compatibility.
# Restores the original file on failure.
Expand Down
Loading
Loading