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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ Requires Bash 4.2+. On macOS, use Homebrew Bash instead of the system `/bin/bash
File editing helpers built on the stdlib, including idempotent
marker-delimited file section updates.
- [`lib/bash/git/lib_git.sh`](lib/bash/git/README.md)
Git helper functions built on the stdlib for lightweight repository
inspection, update, and script freshness checks.
Git helper functions built on the stdlib for default-branch, worktree,
upstream, remote, repository update, and script freshness checks.
- [`lib/bash/gh/lib_gh.sh`](lib/bash/gh/README.md)
GitHub CLI helper functions built on the stdlib for command readiness,
authentication diagnostics, and checked `gh` execution.
authentication diagnostics, remote parsing, API retries, and checked `gh`
execution.
- [`lib/bash/str/lib_str.sh`](lib/bash/str/README.md)
String helpers built on the stdlib for case conversion, trimming,
predicates, splitting, and joining.
- [`lib/bash/arg/lib_arg.sh`](lib/bash/arg/README.md)
Argument parsing helpers built on the stdlib for exact flag and value
options without hidden parser globals.
Argument parsing helpers built on the stdlib for exact flag, scalar value,
and repeatable value options without hidden parser globals.
- [`lib/bash/list/lib_list.sh`](lib/bash/list/README.md)
Indexed-array helpers built on the stdlib for in-place mutation,
membership checks, deduplication, and length results.
Expand Down
5 changes: 4 additions & 1 deletion lib/bash/arg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ helpers are available.

## Public API

- `arg_parse`
- `arg_parse <options_array> <positionals_array> <specs_array> -- [args...]`
Parse exact flag, value, and repeatable options into caller-owned arrays.
Returns `0` on success and `2` for malformed specs, unknown options, or
missing values; caller-owned outputs are published only after a successful
parse.

## Usage

Expand Down
14 changes: 9 additions & 5 deletions lib/bash/file/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ Source `lib/bash/std/lib_std.sh` before this library so logging and error helper

## Public API

- `file_section_exists`
- `file_section_exists <target> <start_marker> <end_marker>`
Inspect whether a valid marker-delimited block is present without changing
the file.
- `file_section_needs_update`
the file; returns `0` for present, `1` for absent, and `2` for invalid
marker order or counts.
- `file_section_needs_update <target> <start_marker> <end_marker> [content...]`
Inspect whether adding or replacing a marker-delimited block would change the
file.
- `update_file_section`
file; returns `0` when an update is needed, `1` when unchanged, and `2` for
invalid marker order or counts.
- `update_file_section <target> <start_marker> <end_marker> [-r|content...]`
Idempotently add, replace, or remove a marker-delimited block inside a file.
It mutates the target or symlink referent and returns nonzero on validation or
filesystem failure.

## Usage

Expand Down
9 changes: 9 additions & 0 deletions lib/bash/gh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ import "/path/to/base-bash-libs/lib/bash/gh/lib_gh.sh"
502/503/504-style failures. `BASE_GH_API_MAX_ATTEMPTS` defaults to `2`.
`BASE_GH_API_RETRY_DELAY_SECONDS` defaults to `2` when the error output does
not include a `Retry-After` value.

All GitHub helper failures return a nonzero status and preserve the underlying
`gh` status where applicable. The remote parser and origin inference helpers
leave caller-owned result variables unchanged on failure; use `--optional` with
`gh_infer_repo_from_origin` when a missing or non-GitHub origin is expected.
## Boundary

This library is intentionally generic. It does not know about Base branch
names, issue categories, GitHub Project fields, repository baselines, generated
pull request bodies, or any other Base workflow policy.

## Tests

BATS coverage lives in `lib/bash/gh/tests/lib_gh.bats`.
26 changes: 14 additions & 12 deletions lib/bash/git/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,32 @@ Source `lib/bash/std/lib_std.sh` before this library so logging and shared error

## Public API

- `git_update_repo`
- `git_update_repo <repo> [allowed_dirty_path] [expected_branch]`
Update a repository on its detected default branch, optionally allowing tracked changes in one specific path.
- `git_get_current_branch`
- `git_get_current_branch <directory> <result_var>`
Return the current branch name through a caller-provided variable, or `detached head`.
- `git_detect_default_branch`
- `git_detect_default_branch <repo> <result_var>`
Detect a repository's default branch from its remote HEAD and standard local
fallbacks.
- `git_worktree_path_for_branch`
- `git_worktree_path_for_branch <branch> [repo]`
Print the worktree path attached to a local branch.
- `git_list_worktree_branches`
- `git_list_worktree_branches [repo]`
Print tab-separated worktree path and branch rows.
- `git_branch_upstream`
- `git_branch_upstream <repo> <branch>`
Print the configured upstream ref for a local branch.
- `git_branch_merged_to_ref`
- `git_branch_merged_to_ref <repo> <branch> <ref>`
Check whether a local branch is an ancestor of a ref.
- `git_list_remote_branches`
- `git_list_remote_branches [repo]`
Print branch names from the `origin` remote.
- `check_script_up_to_date`
- `check_script_up_to_date [--fetch] <script>`
Check whether a tracked script appears current relative to its configured upstream.

## Internal Helper
## Internal Helpers

- `_git_only_path_dirty`
Internal predicate used by `git_update_repo` when an allowed dirty path is provided.
The `__git_*__` functions used by `git_update_repo` are implementation details
and are not part of the public API. In particular, the path-dirty predicate
checks whether tracked changes stay within an allowed path, while the update
helpers manage branch selection, retries, and cleanup.

## Usage

Expand Down
38 changes: 19 additions & 19 deletions lib/bash/git/lib_git.sh
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ git_list_remote_branches() {
#
# @param $1 allowed_path Path in repository root that may be dirty (for example "shared").
#
_git_path_matches_allowed_path() {
__git_path_matches_allowed_path__() {
local path="$1" allowed_path="$2"

[[ "$path" == "$allowed_path" || "$path" == "$allowed_path/"* ]]
}

_git_only_path_dirty() {
__git_only_path_dirty__() {
local allowed_path="$1"
local status_output line path source_path destination_path

Expand All @@ -175,21 +175,21 @@ _git_only_path_dirty() {
if [[ "$path" == *" -> "* ]]; then
source_path="${path%% -> *}"
destination_path="${path#* -> }"
if ! _git_path_matches_allowed_path "$source_path" "$allowed_path" ||
! _git_path_matches_allowed_path "$destination_path" "$allowed_path"; then
if ! __git_path_matches_allowed_path__ "$source_path" "$allowed_path" ||
! __git_path_matches_allowed_path__ "$destination_path" "$allowed_path"; then
return 1
fi
continue
fi
if ! _git_path_matches_allowed_path "$path" "$allowed_path"; then
if ! __git_path_matches_allowed_path__ "$path" "$allowed_path"; then
return 1
fi
done <<< "$status_output"

return 0
}

_git_expected_update_branch() {
__git_expected_update_branch__() {
local configured_branch="${1:-}"
local default_branch

Expand Down Expand Up @@ -229,7 +229,7 @@ _git_expected_update_branch() {
printf '%s\n' main
}

_git_update_repo_finish() {
__git_update_repo_finish__() {
local git_log="${1:-}"
local should_popd="${2:-false}"
local status="${3:-0}"
Expand All @@ -250,7 +250,7 @@ _git_update_repo_finish() {
return "$status"
}

_git_pull_with_retry() {
__git_pull_with_retry__() {
local git_log="$1"
local max_attempts="${BASE_GIT_PULL_MAX_ATTEMPTS:-2}"
local attempt=1
Expand Down Expand Up @@ -318,24 +318,24 @@ git_update_repo() {
# the submodule update sequence below needs the repository as its cwd.
if ! pushd "$git_repo" > /dev/null; then
# If cd fails, we can't proceed.
_git_update_repo_finish "$git_log" false 1
__git_update_repo_finish__ "$git_log" false 1
return $?
fi

# Check if it's a valid git repo
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
log_error "'$git_repo' is not a Git repository."
_git_update_repo_finish "$git_log" true 1
__git_update_repo_finish__ "$git_log" true 1
return $?
fi

# Make sure the current branch is the expected update branch.
local current_branch
expected_branch="$(_git_expected_update_branch "$expected_branch")"
expected_branch="$(__git_expected_update_branch__ "$expected_branch")"
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [[ "$current_branch" != "$expected_branch" ]]; then
log_debug "Current branch of '$git_repo' is '${current_branch}', not '$expected_branch'. Skipping update."
_git_update_repo_finish "$git_log" true 0
__git_update_repo_finish__ "$git_log" true 0
return $?
fi

Expand All @@ -347,37 +347,37 @@ git_update_repo() {
dirty=true
fi
if [[ "$dirty" == true ]]; then
if [[ -n "$allowed_dirty_path" ]] && _git_only_path_dirty "$allowed_dirty_path"; then
if [[ -n "$allowed_dirty_path" ]] && __git_only_path_dirty__ "$allowed_dirty_path"; then
log_debug "Repo '$git_repo' only has tracked changes in '$allowed_dirty_path'; attempting git pull."
else
log_debug "Repo '$git_repo' has local changes; skipping auto-update. Commit or stash to enable git pull."
_git_update_repo_finish "$git_log" true 0
__git_update_repo_finish__ "$git_log" true 0
return $?
fi
fi

if ! _git_pull_with_retry "$git_log"; then
if ! __git_pull_with_retry__ "$git_log"; then
log_error "git pull failed on repo '$git_repo'"
[[ -s "$git_log" ]] && log_info_file "$git_log"
_git_update_repo_finish "$git_log" true 1
__git_update_repo_finish__ "$git_log" true 1
return $?
fi

# it is safe to run submodule commands even if the repo has no submodules
if ! std_make_temp_file submodule_log git-submodule-log; then
log_error "Unable to create temporary submodule log file."
_git_update_repo_finish "$git_log" true 1
__git_update_repo_finish__ "$git_log" true 1
return $?
fi
if ! { git submodule init && git submodule sync && git submodule update; } >"$submodule_log" 2>&1; then
log_error "git submodule update failed on repo '$git_repo'"
[[ -s "$submodule_log" ]] && log_info_file "$submodule_log"
_git_update_repo_finish "$git_log" true 1 "$submodule_log"
__git_update_repo_finish__ "$git_log" true 1 "$submodule_log"
return $?
fi

log_debug "Git repo '$git_repo' updated to latest '$expected_branch'"
_git_update_repo_finish "$git_log" true 0 "$submodule_log"
__git_update_repo_finish__ "$git_log" true 0 "$submodule_log"
return $?
}

Expand Down
Loading
Loading