340 lines
10 KiB
Bash
340 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Gitea Issues Utility (Bash)
|
|
#
|
|
# Prérequis:
|
|
# - curl, jq
|
|
# - Fichier .env à la racine du projet contenant au minimum:
|
|
# GITEA_BASE_URL=https://git.example.com/api/v1
|
|
# GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
# GITEA_DEFAULT_OWNER=owner
|
|
# GITEA_DEFAULT_REPO=repo
|
|
#
|
|
# Utilisation rapide:
|
|
# ./gitea_issues.sh create -t "Titre" -b "Corps (Markdown)" [-F chemin/fichier.md] [-l label1,label2] [-a user1,user2]
|
|
# ./gitea_issues.sh list [-s open|closed|all] [-p page] [-P limit]
|
|
# ./gitea_issues.sh get <index>
|
|
# ./gitea_issues.sh update <index> [-t titre] [-b corps (Markdown) | -F chemin/fichier.md] [-l labels] [-a assignees]
|
|
# ./gitea_issues.sh close <index>
|
|
# ./gitea_issues.sh open <index>
|
|
# ./gitea_issues.sh comment <index> -m "message (Markdown)" [-M chemin/commentaire.md]
|
|
#
|
|
# Notes:
|
|
# - Les scripts lisent ../.env (depuis ./.utlis/) automatiquement.
|
|
# - Vous pouvez surcharger owner/repo via variables d'env OWNER/REPO ou flags --owner/--repo.
|
|
|
|
set -euo pipefail
|
|
|
|
# Assurer un environnement UTF-8 pour les E/S et curl/jq
|
|
ensure_utf8_locale() {
|
|
# Si LANG/LC_ALL ne sont pas déjà en UTF-8, essaye de basculer
|
|
local want="UTF-8"
|
|
if [[ "${LANG:-}" != *"$want"* || "${LC_ALL:-}" != *"$want"* ]]; then
|
|
local chosen=""
|
|
if command -v locale >/dev/null 2>&1; then
|
|
if locale -a 2>/dev/null | grep -qi '^C\.utf8\|C\.UTF-8$'; then
|
|
chosen="C.UTF-8"
|
|
elif locale -a 2>/dev/null | grep -qi '^en_US\.utf8\|en_US\.UTF-8$'; then
|
|
chosen="en_US.UTF-8"
|
|
elif locale -a 2>/dev/null | grep -qi '^fr_FR\.utf8\|fr_FR\.UTF-8$'; then
|
|
chosen="fr_FR.UTF-8"
|
|
fi
|
|
fi
|
|
if [[ -z "$chosen" ]]; then
|
|
# Fallback raisonnable si locale -a n'est pas dispo
|
|
chosen="C.UTF-8"
|
|
fi
|
|
export LANG="$chosen"
|
|
export LC_ALL="$chosen"
|
|
fi
|
|
}
|
|
|
|
ensure_utf8_locale
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
# Charger .env si présent
|
|
ENV_FILE="$REPO_ROOT/.env"
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
# shellcheck disable=SC2046
|
|
export $(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_FILE" | sed 's/#.*//')
|
|
fi
|
|
|
|
BASE_URL="${GITEA_BASE_URL:-}"
|
|
TOKEN="${GITEA_TOKEN:-}"
|
|
DEFAULT_OWNER="${GITEA_DEFAULT_OWNER:-}"
|
|
DEFAULT_REPO="${GITEA_DEFAULT_REPO:-}"
|
|
|
|
OWNER_OVERRIDE=""
|
|
REPO_OVERRIDE=""
|
|
|
|
require_tools() {
|
|
for t in curl jq; do
|
|
command -v "$t" >/dev/null 2>&1 || { echo "Erreur: outil requis introuvable: $t" >&2; exit 1; }
|
|
done
|
|
}
|
|
|
|
usage() {
|
|
sed -n '1,60p' "$0"
|
|
}
|
|
|
|
fail() { echo "Erreur: $*" >&2; exit 1; }
|
|
|
|
ensure_env() {
|
|
[[ -n "$BASE_URL" ]] || fail "GITEA_BASE_URL manquant (.env)"
|
|
[[ -n "$TOKEN" ]] || fail "GITEA_TOKEN manquant (.env)"
|
|
}
|
|
|
|
resolve_repo() {
|
|
local owner repo
|
|
owner="${OWNER_OVERRIDE:-${OWNER:-$DEFAULT_OWNER}}"
|
|
repo="${REPO_OVERRIDE:-${REPO:-$DEFAULT_REPO}}"
|
|
[[ -n "$owner" && -n "$repo" ]] || fail "OWNER/REPO non définis (utilisez .env ou --owner/--repo)"
|
|
echo "$owner" "$repo"
|
|
}
|
|
|
|
api() {
|
|
local method="$1" path="$2"; shift 2
|
|
local url="$BASE_URL$path"
|
|
curl -sS -X "$method" \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/json; charset=utf-8" \
|
|
-H "Accept-Charset: utf-8" \
|
|
"$url" "$@"
|
|
}
|
|
|
|
api_jq_or_status() {
|
|
# Lit la sortie JSON et vérifie erreurs Gitea
|
|
local http_code json
|
|
# Utilise curl avec -w pour capturer le code
|
|
json=$(curl -sS -w '\n%{http_code}' \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/json; charset=utf-8" \
|
|
-H "Accept-Charset: utf-8" \
|
|
"$@")
|
|
http_code="${json##*$'\n'}"
|
|
json="${json%$'\n'*}"
|
|
if [[ "$http_code" -ge 400 ]]; then
|
|
echo "$json" | jq . 2>/dev/null || true
|
|
fail "HTTP $http_code"
|
|
else
|
|
echo "$json" | jq .
|
|
fi
|
|
}
|
|
|
|
parse_common_flags() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--owner) OWNER_OVERRIDE="$2"; shift 2;;
|
|
--repo) REPO_OVERRIDE="$2"; shift 2;;
|
|
-h|--help) usage; exit 0;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
echo "$@"
|
|
}
|
|
|
|
cmd_create() {
|
|
local title="" body="" body_file="" labels="" assignees=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-t|--title) title="$2"; shift 2;;
|
|
-b|--body) body="$2"; shift 2;;
|
|
-F|--body-file) body_file="$2"; shift 2;;
|
|
-l|--labels) labels="$2"; shift 2;;
|
|
-a|--assignees) assignees="$2"; shift 2;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
[[ -n "$title" ]] || fail "Titre requis (-t)"
|
|
read -r owner repo < <(resolve_repo)
|
|
local data
|
|
if [[ -n "$body" ]]; then
|
|
# Inline prioritaire
|
|
data=$(jq -n \
|
|
--arg title "$title" \
|
|
--arg body "$body" \
|
|
--arg labels_csv "$labels" \
|
|
--arg assignees_csv "$assignees" '
|
|
{
|
|
title: $title,
|
|
body: $body
|
|
}
|
|
+ ( ($labels_csv|length) > 0
|
|
then { labels: ($labels_csv | split(",") | map(select(. != ""))) } else {} end)
|
|
+ ( ($assignees_csv|length) > 0
|
|
then { assignees: ($assignees_csv | split(",") | map(select(. != ""))) } else {} end)
|
|
')
|
|
if [[ -n "$body_file" ]]; then
|
|
echo "Avertissement: -b et -F fournis; -b (inline) sera prioritaire" >&2
|
|
fi
|
|
elif [[ -n "$body_file" ]]; then
|
|
[[ -f "$body_file" ]] || fail "Fichier introuvable: $body_file"
|
|
data=$(jq -n \
|
|
--arg title "$title" \
|
|
--arg labels_csv "$labels" \
|
|
--arg assignees_csv "$assignees" \
|
|
--rawfile body "$body_file" '
|
|
{
|
|
title: $title,
|
|
body: $body
|
|
}
|
|
+ ( ($labels_csv|length) > 0
|
|
then { labels: ($labels_csv | split(",") | map(select(. != ""))) } else {} end)
|
|
+ ( ($assignees_csv|length) > 0
|
|
then { assignees: ($assignees_csv | split(",") | map(select(. != ""))) } else {} end)
|
|
')
|
|
else
|
|
data=$(jq -n \
|
|
--arg title "$title" '
|
|
{ title: $title }
|
|
')
|
|
fi
|
|
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues" --data-binary "$data"
|
|
}
|
|
|
|
cmd_list() {
|
|
local state="open" page="1" limit="30"
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-s|--state) state="$2"; shift 2;;
|
|
-p|--page) page="$2"; shift 2;;
|
|
-P|--limit) limit="$2"; shift 2;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
read -r owner repo < <(resolve_repo)
|
|
api GET "/repos/$owner/$repo/issues?state=$state&page=$page&limit=$limit" | jq '.[].number as $n | {number: $n, title: .title, state: .state, labels: [.labels[].name]}'
|
|
}
|
|
|
|
cmd_get() {
|
|
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
|
|
read -r owner repo < <(resolve_repo)
|
|
api GET "/repos/$owner/$repo/issues/$index" | jq '{number, title, state, body, labels: [.labels[].name], assignees: [.assignees[].login], created_at, updated_at}'
|
|
}
|
|
|
|
cmd_update() {
|
|
local index title="" body="" body_file="" labels="" assignees="" state=""
|
|
index="$1"; shift || true
|
|
[[ -n "$index" ]] || fail "Index requis"
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-t|--title) title="$2"; shift 2;;
|
|
-b|--body) body="$2"; shift 2;;
|
|
-F|--body-file) body_file="$2"; shift 2;;
|
|
-l|--labels) labels="$2"; shift 2;;
|
|
-a|--assignees) assignees="$2"; shift 2;;
|
|
-s|--state) state="$2"; shift 2;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
read -r owner repo < <(resolve_repo)
|
|
local data
|
|
if [[ -n "$body" ]]; then
|
|
data=$(jq -n \
|
|
--arg title "$title" \
|
|
--arg body "$body" \
|
|
--arg labels_csv "$labels" \
|
|
--arg assignees_csv "$assignees" \
|
|
--arg state "$state" '
|
|
{}
|
|
+ ( ($title|length)>0 then {title:$title} else {} end )
|
|
+ {body:$body}
|
|
+ ( ($labels_csv|length)>0 then {labels: ($labels_csv|split(",")|map(select(.!="")))} else {} end )
|
|
+ ( ($assignees_csv|length)>0 then {assignees: ($assignees_csv|split(",")|map(select(.!="")))} else {} end )
|
|
+ ( ($state|length)>0 then {state:$state} else {} end )
|
|
')
|
|
if [[ -n "$body_file" ]]; then
|
|
echo "Avertissement: -b et -F fournis; -b (inline) sera prioritaire" >&2
|
|
fi
|
|
elif [[ -n "$body_file" ]]; then
|
|
[[ -f "$body_file" ]] || fail "Fichier introuvable: $body_file"
|
|
data=$(jq -n \
|
|
--arg title "$title" \
|
|
--rawfile body "$body_file" \
|
|
--arg labels_csv "$labels" \
|
|
--arg assignees_csv "$assignees" \
|
|
--arg state "$state" '
|
|
{}
|
|
+ ( ($title|length)>0 then {title:$title} else {} end )
|
|
+ {body:$body}
|
|
+ ( ($labels_csv|length)>0 then {labels: ($labels_csv|split(",")|map(select(.!="")))} else {} end )
|
|
+ ( ($assignees_csv|length)>0 then {assignees: ($assignees_csv|split(",")|map(select(.!="")))} else {} end )
|
|
+ ( ($state|length)>0 then {state:$state} else {} end )
|
|
')
|
|
else
|
|
data=$(jq -n \
|
|
--arg title "$title" '
|
|
{}
|
|
+ ( ($title|length)>0 then {title:$title} else {} end )
|
|
')
|
|
fi
|
|
api_jq_or_status -X PATCH "$BASE_URL/repos/$owner/$repo/issues/$index" --data-binary "$data"
|
|
}
|
|
|
|
cmd_close() {
|
|
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
|
|
cmd_update "$index" -s closed
|
|
}
|
|
|
|
cmd_open() {
|
|
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
|
|
cmd_update "$index" -s open
|
|
}
|
|
|
|
cmd_comment() {
|
|
local index message="" message_file=""
|
|
index="$1"; shift || true
|
|
[[ -n "$index" ]] || fail "Index requis"
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-m|--message) message="$2"; shift 2;;
|
|
-M|--message-file) message_file="$2"; shift 2;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
# Inline prioritaire
|
|
if [[ -n "$message" ]]; then
|
|
read -r owner repo < <(resolve_repo)
|
|
local data
|
|
data=$(jq -n --arg body "$message" '{body:$body}')
|
|
if [[ -n "$message_file" ]]; then
|
|
echo "Avertissement: -m et -M fournis; -m (inline) sera prioritaire" >&2
|
|
fi
|
|
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues/$index/comments" --data-binary "$data"
|
|
return
|
|
elif [[ -n "$message_file" ]]; then
|
|
[[ -f "$message_file" ]] || fail "Fichier introuvable: $message_file"
|
|
read -r owner repo < <(resolve_repo)
|
|
local data
|
|
data=$(jq -n --rawfile body "$message_file" '{body:$body}')
|
|
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues/$index/comments" --data-binary "$data"
|
|
return
|
|
else
|
|
fail "Message requis (-m) ou fichier (-M)"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
require_tools
|
|
ensure_env
|
|
local args
|
|
args=( $(parse_common_flags "$@") ) || true
|
|
set +u
|
|
local cmd="${args[0]}"; shift || true
|
|
set -u
|
|
case "$cmd" in
|
|
create) shift; cmd_create "$@" ;;
|
|
list) shift; cmd_list "$@" ;;
|
|
get) shift; cmd_get "$1" ;;
|
|
update) shift; cmd_update "$@" ;;
|
|
close) shift; cmd_close "$1" ;;
|
|
open) shift; cmd_open "$1" ;;
|
|
comment) shift; cmd_comment "$@" ;;
|
|
-h|--help|help|""|*) usage; exit 1;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|