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
23 changes: 14 additions & 9 deletions app/test/linear_cli/cli/issue_commands_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,10 @@ defmodule LinearCli.CLI.IssueCommandsTest do
# `System.tmp_dir!()` - never the real project working directory. See house
# rule 6 and `LinearCli.GitTest`'s own identical setup.
defp git_repo! do
origin_path = tmp_path("origin")
File.mkdir_p!(origin_path)
origin_path = tmp_dir!("origin")
{_output, 0} = System.cmd("git", ["init", "--bare", "-q"], cd: origin_path)

repo_path = tmp_path("repo")
File.mkdir_p!(repo_path)
repo_path = tmp_dir!("repo")
{_output, 0} = System.cmd("git", ["init", "-q"], cd: repo_path)
{_output, 0} = System.cmd("git", ["config", "user.name", "Test User"], cd: repo_path)
{_output, 0} = System.cmd("git", ["config", "user.email", "test@example.com"], cd: repo_path)
Expand All @@ -116,14 +114,21 @@ defmodule LinearCli.CLI.IssueCommandsTest do
{_output, 0} = System.cmd("git", ["remote", "add", "origin", origin_path], cd: repo_path)
{_output, 0} = System.cmd("git", ["push", "-q", "-u", "origin", "main"], cd: repo_path)

on_exit(fn ->
File.rm_rf!(origin_path)
File.rm_rf!(repo_path)
end)

repo_path
end

# `System.unique_integer/1` resets across BEAM VM restarts, so an interrupted
# prior run can reuse a stale /tmp directory. A cryptographic nonce avoids
# collisions across processes; `on_exit` is registered before any git command
# so a setup failure still cleans up.
defp tmp_dir!(prefix) do
nonce = :crypto.strong_rand_bytes(16) |> Base.url_encode64(padding: false)
path = Path.join(System.tmp_dir!(), "linear_cli_issue_commands_test_#{prefix}_#{nonce}")
File.mkdir!(path)
on_exit(fn -> File.rm_rf!(path) end)
path
end

defp tmp_path(prefix) do
Path.join(
System.tmp_dir!(),
Expand Down
26 changes: 12 additions & 14 deletions app/test/linear_cli/cli/profile_defaults_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,10 @@ defmodule LinearCli.CLI.ProfileDefaultsTest do
# `System.tmp_dir!()` - never the real project working directory. See
# `LinearCli.CLI.IssueCommandsTest`'s own identical setup.
defp git_repo! do
origin_path = tmp_path("origin")
File.mkdir_p!(origin_path)
origin_path = tmp_dir!("origin")
{_output, 0} = System.cmd("git", ["init", "--bare", "-q"], cd: origin_path)

repo_path = tmp_path("repo")
File.mkdir_p!(repo_path)
repo_path = tmp_dir!("repo")
{_output, 0} = System.cmd("git", ["init", "-q"], cd: repo_path)
{_output, 0} = System.cmd("git", ["config", "user.name", "Test User"], cd: repo_path)
{_output, 0} = System.cmd("git", ["config", "user.email", "test@example.com"], cd: repo_path)
Expand All @@ -126,19 +124,19 @@ defmodule LinearCli.CLI.ProfileDefaultsTest do
{_output, 0} = System.cmd("git", ["remote", "add", "origin", origin_path], cd: repo_path)
{_output, 0} = System.cmd("git", ["push", "-q", "-u", "origin", "main"], cd: repo_path)

on_exit(fn ->
File.rm_rf!(origin_path)
File.rm_rf!(repo_path)
end)

repo_path
end

defp tmp_path(prefix) do
Path.join(
System.tmp_dir!(),
"linear_cli_profile_defaults_test_#{prefix}_#{System.unique_integer([:positive, :monotonic])}"
)
# `System.unique_integer/1` resets across BEAM VM restarts, so an interrupted
# prior run can reuse a stale /tmp directory. A cryptographic nonce avoids
# collisions across processes; `on_exit` is registered before any git command
# so a setup failure still cleans up.
defp tmp_dir!(prefix) do
nonce = :crypto.strong_rand_bytes(16) |> Base.url_encode64(padding: false)
path = Path.join(System.tmp_dir!(), "linear_cli_profile_defaults_test_#{prefix}_#{nonce}")
File.mkdir!(path)
on_exit(fn -> File.rm_rf!(path) end)
path
end

describe "Commands.issue_list/1 falls back to the active profile" do
Expand Down
113 changes: 3 additions & 110 deletions ci/conventional_commits.sh
Original file line number Diff line number Diff line change
@@ -1,111 +1,4 @@
#!/usr/bin/env bash

usage() {
cat <<-EOT
Validate conventional commit subjects in the current branch range.

Usage:
$0

Environment:
BASE_REF Base branch or ref. Defaults to GITHUB_BASE_REF, main, then origin/main.
HEAD_REF Head branch name. Defaults to GITHUB_HEAD_REF, then current branch.
FETCH_BASE_REF When "true", fetch BASE_REF from origin before validating.
EOT
}

die() {
printf "ERROR: %s\n\n" "$*" >&2
usage >&2
exit 1
}

git_or_die() {
output=$(git "$@" 2>&1)
status=$?

if [ "$status" -ne 0 ]
then
printf "%s\n" "$output" >&2
die "git $* failed"
fi

printf "%s" "$output"
}

repo_top=$(git_or_die rev-parse --show-toplevel)
validator="$repo_top/ci/validate_conventional_commit.sh"

[ -x "$validator" ] || die "validator is not executable: $validator"

base_input=${BASE_REF:-${GITHUB_BASE_REF:-}}
head_branch=${HEAD_REF:-${GITHUB_HEAD_REF:-}}
base_name=${base_input#refs/heads/}
base_name=${base_name#origin/}

if [ -z "$head_branch" ]
then
head_branch=$(git branch --show-current 2>/dev/null)
fi

if [ "${FETCH_BASE_REF:-}" = "true" ] && [ -n "$base_input" ]
then
git_or_die fetch --no-tags origin "$base_name:refs/remotes/origin/$base_name" >/dev/null
fi

base_ref=

if [ -n "$base_input" ]
then
base_ref_candidates="$base_input origin/$base_name $base_name"
else
base_ref_candidates="main origin/main"
fi

for candidate in $base_ref_candidates
do
if git rev-parse --verify --quiet "$candidate" >/dev/null
then
base_ref=$candidate
break
fi
done

[ -n "$base_ref" ] || die "unable to resolve commit comparison base"

base_branch=${base_name:-$base_ref}
base_branch=${base_branch#refs/heads/}
base_branch=${base_branch#origin/}
expected_update_branch_subject="Merge branch '$base_branch' into $head_branch"
base_sha=$(git_or_die merge-base HEAD "$base_ref")

validation_status=0

while IFS= read -r -d '' sha &&
IFS= read -r -d '' parents &&
IFS= read -r -d '' committer_name &&
IFS= read -r -d '' committer_email &&
IFS= read -r -d '' subject
do
parent_count=$(printf "%s\n" "$parents" | wc -w | tr -d ' ')

if [ "$parent_count" = "2" ] &&
[ "$committer_name" = "GitHub" ] &&
[ "$committer_email" = "noreply@github.com" ] &&
[ -n "$head_branch" ] &&
[ "$subject" = "$expected_update_branch_subject" ]
then
printf "Skipping GitHub update-branch merge commit: %s %s\n" "$sha" "$subject" >&2
continue
fi

"$validator" --subject "$subject"
status=$?

if [ "$status" -ne 0 ]
then
validation_status=$status
fi
done < <(git log --format='%H%x00%P%x00%cN%x00%cE%x00%s%x00' "$base_sha..HEAD")

exit "$validation_status"
# Compatibility wrapper — the implementation has moved to git-hooks/validate-commit-range.
script_dir=$(cd "$(dirname "$0")" && pwd)
exec "$script_dir/../git-hooks/validate-commit-range" "$@"
87 changes: 3 additions & 84 deletions ci/validate_conventional_commit.sh
Original file line number Diff line number Diff line change
@@ -1,85 +1,4 @@
#!/usr/bin/env bash

VALID_TYPES="fix|feat|perf|observability|obs|config|configuration|chore|ci|docs|refactor|sec|security|style|cleanup|test"

allowed_types=$VALID_TYPES
header_pattern="^(${allowed_types})(\\([A-Za-z0-9._/-]+\\))?(!)?: .+"

usage() {
cat <<-EOT
Validate a conventional commit subject.

Usage:
$0 <commit-message-file>
$0 --subject "<commit subject>"
EOT
}

die() {
printf "ERROR: %s\n\n" "$*" >&2
usage >&2
exit 1
}

if [ "$#" -eq 2 ] && [ "$1" = "--subject" ]
then
subject=$2
elif [ "$#" -eq 1 ]
then
message_file=$1
[ -f "$message_file" ] || die "Commit message file not found: $message_file"

subject=$(
sed -n \
-e '/^[[:space:]]*#/d' \
-e '/^[[:space:]]*$/d' \
-e 'p;q' \
"$message_file"
)
else
die "Invalid arguments"
fi

[ -n "${subject:-}" ] || die "Commit subject is empty"

if [[ "$subject" =~ [[:space:]]$ ]]
then
die "Commit subject must not end with whitespace: $subject"
fi

if [[ "$subject" =~ ^Merge\ branch\ .+ ]]
then
cat >&2 <<-EOT
ERROR: Merge commit subject must use conventional-commit format.

Subject:
$subject

Recommended fix:
Rebase and reword the commit with chore: in front of the merge subject:
chore: $subject
EOT
exit 1
fi

if [[ ! "$subject" =~ $header_pattern ]]
then
cat >&2 <<-EOT
ERROR: Commit subject must use conventional-commit format.

Subject:
$subject

Expected:
<type>[(scope)][!]: <description>

Allowed types:
${allowed_types//|/, }

Examples:
docs: update wallet one-pager
feat(api): add wallet debit endpoint
fix(db)!: change ledger migration format
EOT
exit 1
fi
# Compatibility wrapper — the implementation has moved to git-hooks/validate-conventional-subject.
script_dir=$(cd "$(dirname "$0")" && pwd)
exec "$script_dir/../git-hooks/validate-conventional-subject" "$@"
Loading