repowarden 4.4.0 → 4.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -25
- package/bin/commit +12 -6
- package/bin/pr-checks +35 -0
- package/bin/pr-label +51 -0
- package/bin/proteger +87 -9
- package/bin/repowarden +13 -1
- package/bin/run-list +58 -0
- package/bin/run-rerun +65 -0
- package/bin/statut +1 -0
- package/bin/ticket +5 -0
- package/bin/ticket-comment +32 -0
- package/bin/ticket-label +52 -0
- package/bin/tickets +14 -1
- package/ci/tickets/issue.sh +19 -4
- package/ci/tickets/pr.sh +23 -7
- package/ci/tickets/push.sh +95 -13
- package/ci/tickets/registre.sh +19 -0
- package/ci/tickets/release.sh +1 -0
- package/hooks/commit-msg +33 -0
- package/hooks/lib/common.sh +17 -0
- package/hooks/lib/i18n/en.sh +51 -2
- package/hooks/lib/i18n/fr.sh +51 -2
- package/hooks/lib/tickets.sh +73 -5
- package/hooks/lib/ui.sh +21 -1
- package/hooks/pre-push +89 -2
- package/hooks/prepare-commit-msg +4 -1
- package/package.json +4 -4
package/bin/run-rerun
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Relancer un workflow GitHub Actions échoué
|
|
3
|
+
# repowarden run rerun <run-id|--failed|--workflow>
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
7
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
8
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
9
|
+
# shellcheck source=../hooks/lib/i18n.sh
|
|
10
|
+
source "$ROOT/hooks/lib/i18n.sh"
|
|
11
|
+
|
|
12
|
+
aide() {
|
|
13
|
+
cat <<EOF
|
|
14
|
+
Usage: repowarden run rerun <run-id|--failed|--workflow <nom>>
|
|
15
|
+
|
|
16
|
+
Relance un workflow GitHub Actions.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
<run-id> ID du run à relancer
|
|
20
|
+
--failed Relance le dernier run échoué du repo
|
|
21
|
+
--workflow <nom> Relance le dernier run du workflow spécifié
|
|
22
|
+
|
|
23
|
+
Exemples:
|
|
24
|
+
repowarden run rerun 123456789
|
|
25
|
+
repowarden run rerun --failed
|
|
26
|
+
repowarden run rerun --workflow "CI"
|
|
27
|
+
EOF
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
[ $# -ge 1 ] || { aide >&2; exit 1; }
|
|
31
|
+
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
32
|
+
aide
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
36
|
+
|
|
37
|
+
arg="${1}"
|
|
38
|
+
run_id=""
|
|
39
|
+
|
|
40
|
+
case "$arg" in
|
|
41
|
+
--failed)
|
|
42
|
+
step_t run.rerun.failed
|
|
43
|
+
run_id=$(gh run list --repo "${GH_REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}" --limit 1 --json databaseId,conclusion --jq '.[] | select(.conclusion=="failure") | .databaseId')
|
|
44
|
+
[ -n "$run_id" ] || { err_t run.rerun.none_failed; exit 1; }
|
|
45
|
+
;;
|
|
46
|
+
--workflow)
|
|
47
|
+
shift
|
|
48
|
+
workflow="${1:?--workflow attend un nom}"
|
|
49
|
+
step_t run.rerun.workflow "$workflow"
|
|
50
|
+
run_id=$(gh run list --repo "${GH_REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}" --workflow "$workflow" --limit 1 --json databaseId --jq '.[0].databaseId')
|
|
51
|
+
[ -n "$run_id" ] || { err_t run.rerun.none_workflow "$workflow"; exit 1; }
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
|
55
|
+
run_id="$arg"
|
|
56
|
+
else
|
|
57
|
+
err_t install.unknown_arg "$arg"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
;;
|
|
61
|
+
esac
|
|
62
|
+
|
|
63
|
+
step_t run.rerun.running "$run_id"
|
|
64
|
+
gh run rerun "$run_id"
|
|
65
|
+
ok_t run.rerun.done "$run_id"
|
package/bin/statut
CHANGED
package/bin/ticket
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
# repowarden ticket nouveau "<titre>" [--type fix] [--description "…"]
|
|
6
6
|
# crée le ticket (« à valider » si le suivi
|
|
7
7
|
# est géré par repowarden, sinon avec sa branche)
|
|
8
|
+
# repowarden ticket comment <n> "<texte>" ajoute un commentaire sur le ticket
|
|
9
|
+
# repowarden ticket label <n> <label> [--remove]
|
|
10
|
+
# gère les labels du ticket
|
|
8
11
|
# repowarden ticket ticket de la branche courante
|
|
9
12
|
# Demande un gh connecté (https://cli.github.com).
|
|
10
13
|
set -euo pipefail
|
|
@@ -108,6 +111,8 @@ cmd_nouveau() {
|
|
|
108
111
|
case "${1:-}" in
|
|
109
112
|
-h | --help | help) aide ;;
|
|
110
113
|
nouveau | new) shift; cmd_nouveau "$@" ;;
|
|
114
|
+
comment) shift; exec bash "$ROOT/bin/ticket-comment" "$@" ;;
|
|
115
|
+
label) shift; exec bash "$ROOT/bin/ticket-label" "$@" ;;
|
|
111
116
|
"")
|
|
112
117
|
branche="$(git symbolic-ref --short HEAD 2>/dev/null || echo "")"
|
|
113
118
|
branch_ticket_r "$branche"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Ajouter un commentaire sur un ticket GitHub
|
|
3
|
+
# repowarden ticket comment <n> "<texte>"
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
7
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
8
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
9
|
+
# shellcheck source=../hooks/lib/i18n.sh
|
|
10
|
+
source "$ROOT/hooks/lib/i18n.sh"
|
|
11
|
+
|
|
12
|
+
aide() {
|
|
13
|
+
cat <<EOF
|
|
14
|
+
Usage: repowarden ticket comment <n> "<texte>"
|
|
15
|
+
|
|
16
|
+
Ajoute un commentaire sur le ticket #<n>.
|
|
17
|
+
|
|
18
|
+
Exemples:
|
|
19
|
+
repowarden ticket comment 123 "Conception retenue : ..."
|
|
20
|
+
repowarden ticket comment 456 "PR prête pour review"
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[ $# -ge 2 ] || { aide >&2; exit 1; }
|
|
25
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
26
|
+
|
|
27
|
+
n="${1}"
|
|
28
|
+
texte="${2}"
|
|
29
|
+
|
|
30
|
+
step_t tk.comment.adding "$n"
|
|
31
|
+
gh issue comment "$n" --body "$texte"
|
|
32
|
+
ok_t tk.comment.added "$n"
|
package/bin/ticket-label
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Gérer les labels d'un ticket GitHub
|
|
3
|
+
# repowarden ticket label <n> <label> [--remove]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
7
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
8
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
9
|
+
# shellcheck source=../hooks/lib/i18n.sh
|
|
10
|
+
source "$ROOT/hooks/lib/i18n.sh"
|
|
11
|
+
|
|
12
|
+
aide() {
|
|
13
|
+
cat <<EOF
|
|
14
|
+
Usage: repowarden ticket label <n> <label> [--remove]
|
|
15
|
+
|
|
16
|
+
Ajoute ou retire un label sur le ticket #<n>.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--remove Retire le label au lieu de l'ajouter
|
|
20
|
+
|
|
21
|
+
Exemples:
|
|
22
|
+
repowarden ticket label 123 "validé"
|
|
23
|
+
repowarden ticket label 123 "statut: backlog"
|
|
24
|
+
repowarden ticket label 123 "wip" --remove
|
|
25
|
+
EOF
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[ $# -ge 2 ] || { aide >&2; exit 1; }
|
|
29
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
30
|
+
|
|
31
|
+
n="${1}"
|
|
32
|
+
label="${2}"
|
|
33
|
+
remove=false
|
|
34
|
+
|
|
35
|
+
shift 2
|
|
36
|
+
while [ $# -gt 0 ]; do
|
|
37
|
+
case "$1" in
|
|
38
|
+
--remove) remove=true ;;
|
|
39
|
+
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
40
|
+
esac
|
|
41
|
+
shift
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
if [ "$remove" = true ]; then
|
|
45
|
+
step_t tk.label.removing "$label" "$n"
|
|
46
|
+
gh issue edit "$n" --remove-label "$label"
|
|
47
|
+
ok_t tk.label.removed "$label" "$n"
|
|
48
|
+
else
|
|
49
|
+
step_t tk.label.adding "$label" "$n"
|
|
50
|
+
gh issue edit "$n" --add-label "$label"
|
|
51
|
+
ok_t tk.label.added "$label" "$n"
|
|
52
|
+
fi
|
package/bin/tickets
CHANGED
|
@@ -123,11 +123,14 @@ permissions: {}
|
|
|
123
123
|
|
|
124
124
|
jobs:
|
|
125
125
|
tickets:
|
|
126
|
-
uses:
|
|
126
|
+
uses: simdevWarden/repowarden/.github/workflows/tickets.yml@v4
|
|
127
127
|
permissions: { contents: write, issues: write, pull-requests: write, statuses: write }
|
|
128
128
|
# GitHub App (repowarden app init) : ses actions déclenchent les autres workflows
|
|
129
|
+
# auto-create : true = crée un ticket « à valider » pour PR sans ticket,
|
|
130
|
+
# false = échec de la vérification avec la marche à suivre (défaut false)
|
|
129
131
|
with:
|
|
130
132
|
app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }}
|
|
133
|
+
auto-create: false
|
|
131
134
|
secrets:
|
|
132
135
|
app-key: ${{ secrets.REPOWARDEN_APP_KEY }}
|
|
133
136
|
YAML
|
|
@@ -136,6 +139,16 @@ YAML
|
|
|
136
139
|
step_t tk.init_adopt
|
|
137
140
|
GH_REPO="$REPO" AUTO_VALIDATE="${AUTO_VALIDATE:-maintainers}" "$ROOT/ci/tickets.sh" adopter | sed 's/^/ /'
|
|
138
141
|
|
|
142
|
+
# Afficher l'état de ticketAutoCreate
|
|
143
|
+
echo
|
|
144
|
+
if git config --get repowarden.ticketAutoCreate | grep -qE '^(true|oui|yes)$'; then
|
|
145
|
+
t tk.init_auto_create_on
|
|
146
|
+
hint "$_T"
|
|
147
|
+
else
|
|
148
|
+
t tk.init_auto_create_off
|
|
149
|
+
hint "$_T"
|
|
150
|
+
fi
|
|
151
|
+
|
|
139
152
|
echo
|
|
140
153
|
ok_t tk.init_done
|
|
141
154
|
dim_t tk.init_next
|
package/ci/tickets/issue.sh
CHANGED
|
@@ -52,10 +52,22 @@ cmd_issue() {
|
|
|
52
52
|
accepter "$ISSUE"
|
|
53
53
|
;;
|
|
54
54
|
assigned)
|
|
55
|
-
# Pris en charge : sa branche (s'il est validé), en cours s'il
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
# Pris en charge : sa branche (s'il est validé), en cours s'il est
|
|
56
|
+
# validé ET assigné (quelle que soit la présence du label backlog,
|
|
57
|
+
# pour gérer la race validation+assignation quasi simultanée).
|
|
58
|
+
# Ne passe en cours que s'il n'est pas déjà plus loin (relecture, préprod, done).
|
|
59
|
+
if est_valide "$ISSUE"; then
|
|
60
|
+
creer_branche "$ISSUE"
|
|
61
|
+
local labels
|
|
62
|
+
labels="$(etiquettes "$ISSUE")"
|
|
63
|
+
if grep -qxF "$S_AVALIDER" <<<"$labels" || grep -qxF "$S_BACKLOG" <<<"$labels"; then
|
|
64
|
+
# Pas de label plus avancé (relecture, préprod, done) ?
|
|
65
|
+
grep -qxF "$S_RELECTURE" <<<"$labels" && return 0
|
|
66
|
+
grep -qxF "$S_PREPROD" <<<"$labels" && return 0
|
|
67
|
+
grep -qxF "$S_DONE" <<<"$labels" && return 0
|
|
68
|
+
statut "$ISSUE" "$S_ENCOURS"
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
59
71
|
return 0
|
|
60
72
|
;;
|
|
61
73
|
closed)
|
|
@@ -71,7 +83,10 @@ cmd_issue() {
|
|
|
71
83
|
_tr tk.reopened "$fpr" "$fbase"
|
|
72
84
|
gh issue comment "$ISSUE" --body "$_T" >/dev/null
|
|
73
85
|
notice "$_T"
|
|
86
|
+
return 0
|
|
74
87
|
fi
|
|
88
|
+
# Terminé à la main ou en production : branche restante nettoyée
|
|
89
|
+
nettoyer_branche "$ISSUE"
|
|
75
90
|
return 0
|
|
76
91
|
fi
|
|
77
92
|
# Abandonné : PR fermées, branche supprimée (un ticket terminé est
|
package/ci/tickets/pr.sh
CHANGED
|
@@ -22,6 +22,14 @@ cmd_pr() {
|
|
|
22
22
|
_tr tk.pr_backmerge_empty "$HEAD" "$BASE"
|
|
23
23
|
gh pr close "$PR" --comment "$_T" >/dev/null
|
|
24
24
|
notice "$_T"
|
|
25
|
+
# Livraison ouverte par le workflow de release : celle d'une personne
|
|
26
|
+
# n'aurait jamais d'approbation de déploiement, elle resterait bloquée
|
|
27
|
+
elif [ "$HEAD" = "${INTEGRATION:-develop}" ] && [ "${DELIVERY_WORKFLOW:-false}" = true ] &&
|
|
28
|
+
[[ "${AUTHOR:-}" != *"[bot]" ]]; then
|
|
29
|
+
_tr tk.pr_delivery_manual "$HEAD" "$BASE" "$HEAD" "$HEAD"
|
|
30
|
+
gh pr close "$PR" --comment "$_T" >/dev/null
|
|
31
|
+
notice "$_T"
|
|
32
|
+
return 0
|
|
25
33
|
fi
|
|
26
34
|
fi
|
|
27
35
|
# Bots de dépendances, PR de release, livraisons et retours : pas de ticket
|
|
@@ -47,14 +55,22 @@ cmd_pr() {
|
|
|
47
55
|
return 0
|
|
48
56
|
fi
|
|
49
57
|
|
|
50
|
-
# Aucun ticket : créé à partir de la PR, à valider,
|
|
58
|
+
# Aucun ticket : si auto-create activé, créé à partir de la PR, à valider,
|
|
59
|
+
# et relié à la PR ; sinon, échec de la vérification avec la marche à suivre.
|
|
51
60
|
if [ -z "$tickets" ]; then
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
--
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
if [ "${AUTO_CREATE:-false}" = true ]; then
|
|
62
|
+
_tr tk.created_body "$PR"
|
|
63
|
+
url="$(gh issue create --title "$(gh pr view "$PR" --json title -q .title)" \
|
|
64
|
+
--body "$_T" --label "$S_AVALIDER")"
|
|
65
|
+
tickets="${url##*/}"
|
|
66
|
+
gh pr edit "$PR" --body "$(printf '%s\n\nTicket : #%s\n' "$body" "$tickets")" >/dev/null
|
|
67
|
+
notice_t tk.created "$tickets" "$PR"
|
|
68
|
+
else
|
|
69
|
+
_tr tk.created_body "$PR"
|
|
70
|
+
gh api "repos/$GH_REPO/statuses/$HEAD_SHA" -f state=failure -f context=ticket \
|
|
71
|
+
-f description="PR sans ticket : créez-en un avec 'repowarden ticket nouveau \"<titre>\"' ou citez 'Ticket : #N' dans la description" >/dev/null
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
58
74
|
fi
|
|
59
75
|
|
|
60
76
|
# Statut du travail (seulement pour un ticket validé)
|
package/ci/tickets/push.sh
CHANGED
|
@@ -4,12 +4,91 @@
|
|
|
4
4
|
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
5
|
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
6
|
|
|
7
|
+
# REPLY = rang d'impact du sujet de commit $1 : 0 rupture, 1 feat, 2 fix/perf,
|
|
8
|
+
# 3 autre type conforme
|
|
9
|
+
rang_impact() {
|
|
10
|
+
case "$1" in
|
|
11
|
+
*"!: "*) REPLY=0 ;;
|
|
12
|
+
feat*:*) REPLY=1 ;;
|
|
13
|
+
fix*:* | perf*:*) REPLY=2 ;;
|
|
14
|
+
*) REPLY=3 ;;
|
|
15
|
+
esac
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# REPLY = sujet du commit conforme de plus fort impact de la branche $1, vide
|
|
19
|
+
# si aucun commit conforme (repli du titre de PR quand le ticket n'en donne pas)
|
|
20
|
+
commit_impact_r() {
|
|
21
|
+
local s rang meilleur="" meilleur_rang=""
|
|
22
|
+
REPLY=""
|
|
23
|
+
while IFS= read -r s; do
|
|
24
|
+
[ -n "$s" ] || continue
|
|
25
|
+
[[ "$s" =~ $CC_PATTERN ]] || continue
|
|
26
|
+
rang_impact "$s"
|
|
27
|
+
rang="$REPLY"
|
|
28
|
+
if [ -z "$meilleur" ] || [ "$rang" -lt "$meilleur_rang" ]; then
|
|
29
|
+
meilleur="$s"
|
|
30
|
+
meilleur_rang="$rang"
|
|
31
|
+
fi
|
|
32
|
+
done < <(gh api "repos/$GH_REPO/compare/$INTEGRATION...$1" \
|
|
33
|
+
-q '.commits[].commit.message | split("\n")[0]' 2>/dev/null || true)
|
|
34
|
+
REPLY="$meilleur"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# REPLY = titre de la PR brouillon du ticket $1 (branche $2) : « type(portée):
|
|
38
|
+
# sujet » depuis le ticket, sinon le commit conforme de plus fort impact de la
|
|
39
|
+
# branche, sinon le titre déduit de la branche
|
|
40
|
+
titre_pr_brouillon_r() {
|
|
41
|
+
local n="$1" branche="$2" type
|
|
42
|
+
ticket_type_r "$(etiquettes "$n")"
|
|
43
|
+
type="$REPLY"
|
|
44
|
+
ticket_pr_title_r "$type" "$(gh issue view "$n" --json title -q .title)"
|
|
45
|
+
[ -z "$REPLY" ] || return 0
|
|
46
|
+
commit_impact_r "$branche"
|
|
47
|
+
[ -z "$REPLY" ] || return 0
|
|
48
|
+
suggest_pr_title_r "$branche"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# $1 = corps d'un ticket → REPLY : les puces de ses critères d'acceptation
|
|
52
|
+
# (« Terminé quand… », « Critères d'acceptation ») en cases à cocher « - [ ] »,
|
|
53
|
+
# le reste inchangé
|
|
54
|
+
criteres_coches_r() {
|
|
55
|
+
local corps="$1" ligne dedans=0 sortie=""
|
|
56
|
+
while IFS= read -r ligne; do
|
|
57
|
+
if [[ "$ligne" =~ ^#{1,6}[[:space:]] ]]; then
|
|
58
|
+
dedans=0
|
|
59
|
+
case "$ligne" in
|
|
60
|
+
*[Tt]ermin* | *[Cc]rit* | *[Aa]cceptance* | *[Dd]one*) dedans=1 ;;
|
|
61
|
+
esac
|
|
62
|
+
elif [ "$dedans" = 1 ]; then
|
|
63
|
+
# « - x » → « - [ ] x » ; une case déjà là n'est pas dupliquée
|
|
64
|
+
ligne="$(printf '%s' "$ligne" |
|
|
65
|
+
sed -E 's/^([[:space:]]*[-*][[:space:]]+)(\[[xX ]\][[:space:]]+)?/\1[ ] /')"
|
|
66
|
+
fi
|
|
67
|
+
sortie="$sortie$ligne"$'\n'
|
|
68
|
+
done <<<"$corps"
|
|
69
|
+
REPLY="${sortie%$'\n'}"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# REPLY = description de la PR brouillon du ticket $1 (branche $2) : le ticket
|
|
73
|
+
# (critères en cases à cocher), puis la liste des commits, puis « Ticket : #N »
|
|
74
|
+
description_pr_brouillon_r() {
|
|
75
|
+
local n="$1" branche="$2" corps commits
|
|
76
|
+
criteres_coches_r "$(gh issue view "$n" --json body -q .body)"
|
|
77
|
+
corps="$REPLY"
|
|
78
|
+
commits="$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$branche" \
|
|
79
|
+
-q '.commits[] | "- \(.sha[0:7]) \(.commit.message | split("\n")[0])"' 2>/dev/null || true)"
|
|
80
|
+
[ -z "$commits" ] || corps="$corps"$'\n\n## Commits\n'"$commits"
|
|
81
|
+
REPLY="$corps"$'\n\nTicket : #'"$n"
|
|
82
|
+
}
|
|
83
|
+
|
|
7
84
|
# Premier push sur la branche d'un ticket : PR brouillon vers la branche
|
|
8
|
-
# d'intégration,
|
|
9
|
-
#
|
|
10
|
-
# tourne au push suivant, ou au
|
|
85
|
+
# d'intégration, au titre et à la description repris du ticket, citant le
|
|
86
|
+
# ticket, assignée à l'auteur du push ; ticket en cours. Ouverte par le jeton
|
|
87
|
+
# des Actions, elle ne lance pas la CI : elle tourne au push suivant, ou au
|
|
88
|
+
# passage « prête pour revue ». Tant qu'elle est en brouillon, chaque push met
|
|
89
|
+
# à jour son titre et sa description ; une PR prête n'est plus touchée.
|
|
11
90
|
cmd_push() {
|
|
12
|
-
local n pr titre
|
|
91
|
+
local n pr titre corps url
|
|
13
92
|
: "${PUSH_BRANCH:?}" "${SHA:?}"
|
|
14
93
|
[ "${DELETED:-false}" != true ] || return 0
|
|
15
94
|
branch_ticket_r "$PUSH_BRANCH"
|
|
@@ -18,17 +97,20 @@ cmd_push() {
|
|
|
18
97
|
# Branche faite hors du ticket (à la main) : inscrite sur le ticket
|
|
19
98
|
inscrire_branche "$n" "$PUSH_BRANCH"
|
|
20
99
|
pr="$(gh pr list --state open --head "$PUSH_BRANCH" --json number -q '.[0].number // empty')"
|
|
21
|
-
|
|
100
|
+
# PR existante : rien à faire si elle n'est plus en brouillon
|
|
101
|
+
[ -z "$pr" ] || [ "$(gh pr view "$pr" --json isDraft -q .isDraft)" = true ] || return 0
|
|
22
102
|
[ "$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$PUSH_BRANCH" -q .ahead_by)" != 0 ] || return 0
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
103
|
+
titre_pr_brouillon_r "$n" "$PUSH_BRANCH"
|
|
104
|
+
titre="$REPLY"
|
|
105
|
+
description_pr_brouillon_r "$n" "$PUSH_BRANCH"
|
|
106
|
+
corps="$REPLY"
|
|
107
|
+
if [ -n "$pr" ]; then
|
|
108
|
+
gh pr edit "$pr" --title "$titre" --body "$corps" >/dev/null
|
|
109
|
+
notice_t tk.draft_updated "$pr" "$n"
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
30
112
|
url="$(gh pr create --draft --base "$INTEGRATION" --head "$PUSH_BRANCH" --title "$titre" \
|
|
31
|
-
--body "
|
|
113
|
+
--body "$corps" ${PUSHER:+--assignee "$PUSHER"})"
|
|
32
114
|
[ -z "${PUSHER:-}" ] || gh issue edit "$n" --add-assignee "$PUSHER" >/dev/null || true
|
|
33
115
|
est_valide "$n" && statut "$n" "$S_ENCOURS"
|
|
34
116
|
notice_t tk.draft_opened "${url##*/}" "$n"
|
package/ci/tickets/registre.sh
CHANGED
|
@@ -50,6 +50,25 @@ branche_du_ticket_r() {
|
|
|
50
50
|
done < <(gh api "repos/$GH_REPO/git/matching-refs/heads/" --paginate -q '.[].ref' 2>/dev/null || true)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
# Ticket $1 terminé : sa branche, si elle existe encore (travail passé par une
|
|
54
|
+
# autre branche), est supprimée quand elle n'apporte rien à la branche
|
|
55
|
+
# d'intégration et qu'aucune PR ouverte n'en part ; sinon gardée et signalée
|
|
56
|
+
nettoyer_branche() {
|
|
57
|
+
local n="$1" branche avance prs
|
|
58
|
+
branche_du_ticket_r "$n"
|
|
59
|
+
branche="$REPLY"
|
|
60
|
+
[ -n "$branche" ] || return 0
|
|
61
|
+
prs="$(gh pr list --state open --head "$branche" --json number -q 'map("#\(.number)") | join(" ")')"
|
|
62
|
+
avance="$(gh api "repos/$GH_REPO/compare/${INTEGRATION:-develop}...$branche" -q .ahead_by 2>/dev/null || true)"
|
|
63
|
+
if [ -z "$prs" ] && [ "$avance" = 0 ]; then
|
|
64
|
+
gh api -X DELETE "repos/$GH_REPO/git/refs/heads/$branche" >/dev/null
|
|
65
|
+
notice_t tk.branch_deleted "$branche" "$n"
|
|
66
|
+
else
|
|
67
|
+
_tr tk.branch_kept "$branche" "${avance:-?}" "${prs:--}"
|
|
68
|
+
gh issue comment "$n" --body "$_T" >/dev/null
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
53
72
|
# Ticket $1 validé : sa branche naît de la branche d'intégration, liée au
|
|
54
73
|
# ticket (panneau « Development »). Rien si elle existe déjà, ou si une PR
|
|
55
74
|
# ouverte cite déjà le ticket (travail commencé hors de sa branche).
|
package/ci/tickets/release.sh
CHANGED
package/hooks/commit-msg
CHANGED
|
@@ -39,6 +39,30 @@ if [[ "$SUBJECT" =~ ^(Merge|Revert|fixup!|squash!) ]]; then
|
|
|
39
39
|
exit 0
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
|
+
# En-tête tenté mais refusé : type inconnu (ou en majuscules), description vide
|
|
43
|
+
if ! [[ "$SUBJECT" =~ $CC_PATTERN ]] &&
|
|
44
|
+
[[ "$SUBJECT" =~ ^([A-Za-z]+)(\([^\)]*\))?(!?):[[:space:]]*(.*)$ ]]; then
|
|
45
|
+
typ="${BASH_REMATCH[1]}" portee="${BASH_REMATCH[2]}" bang="${BASH_REMATCH[3]}" desc="${BASH_REMATCH[4]}"
|
|
46
|
+
typ_min="$(printf '%s' "$typ" | tr '[:upper:]' '[:lower:]')"
|
|
47
|
+
propose="$typ_min"
|
|
48
|
+
[[ "$typ_min" =~ ^($CC_TYPES)$ ]] || { type_proche_r "$typ_min"; propose="$REPLY"; }
|
|
49
|
+
if [ -z "$desc" ]; then
|
|
50
|
+
err_t hook.commit_msg.desc_empty "${propose:-$typ}$portee$bang:"
|
|
51
|
+
echo_t hook.commit_msg.kept >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
if [ "$propose" != "$typ" ]; then
|
|
55
|
+
if [ -n "$propose" ]; then
|
|
56
|
+
err_t hook.commit_msg.type_unknown "$typ" "$propose$portee$bang: $desc"
|
|
57
|
+
else
|
|
58
|
+
err_t hook.commit_msg.type_none "$typ"
|
|
59
|
+
types_help
|
|
60
|
+
fi
|
|
61
|
+
echo_t hook.commit_msg.kept >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
42
66
|
if ! [[ "$SUBJECT" =~ $CC_PATTERN ]]; then
|
|
43
67
|
if [ "${REPOWARDEN_LANG:-}" = en ]; then
|
|
44
68
|
cat >&2 <<EOF
|
|
@@ -80,6 +104,15 @@ EOF
|
|
|
80
104
|
exit 1
|
|
81
105
|
fi
|
|
82
106
|
|
|
107
|
+
# Point final retiré, comme dans git cc (pas des points de suspension) ; seule
|
|
108
|
+
# la première ligne est réécrite, le corps reste intact
|
|
109
|
+
if [[ "$SUBJECT" == *. && "$SUBJECT" != *.. ]]; then
|
|
110
|
+
SUBJECT="${SUBJECT%.}"
|
|
111
|
+
contenu="$(awk -v s="$SUBJECT" '!fait && !/^#/ { print s; fait = 1; next } { print }' "$MSG_FILE")"
|
|
112
|
+
printf '%s\n' "$contenu" >"$MSG_FILE"
|
|
113
|
+
info_t hook.commit_msg.dot >&2
|
|
114
|
+
fi
|
|
115
|
+
|
|
83
116
|
authored_length "$SUBJECT"
|
|
84
117
|
if [ "$REPLY" -gt 72 ]; then
|
|
85
118
|
err_t hook.commit_msg.1 "$REPLY" >&2
|
package/hooks/lib/common.sh
CHANGED
|
@@ -84,6 +84,23 @@ cfg() {
|
|
|
84
84
|
CC_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"
|
|
85
85
|
CC_PATTERN="^($CC_TYPES)(\([a-z0-9._-]+\))?!?: .+"
|
|
86
86
|
|
|
87
|
+
# Type le plus proche d'un type inconnu : alias courants, puis préfixe commun
|
|
88
|
+
# (doc → docs, feature → feat, Feat → feat) ; vide si aucun
|
|
89
|
+
type_proche_r() {
|
|
90
|
+
local t="$1" c
|
|
91
|
+
case "$t" in
|
|
92
|
+
feature | features) REPLY="feat"; return ;;
|
|
93
|
+
doc | documentation) REPLY="docs"; return ;;
|
|
94
|
+
fixe | fixed | fixes | bugfix | hotfix) REPLY="fix"; return ;;
|
|
95
|
+
tests) REPLY="test"; return ;;
|
|
96
|
+
refacto | refactoring) REPLY="refactor"; return ;;
|
|
97
|
+
esac
|
|
98
|
+
REPLY=""
|
|
99
|
+
for c in ${CC_TYPES//|/ }; do
|
|
100
|
+
if [[ "$c" == "$t"* || "$t" == "$c"* ]]; then REPLY="$c"; return; fi
|
|
101
|
+
done
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
# REPLY = longueur de la première ligne écrite par l'auteur : le suffixe
|
|
88
105
|
# « (#123) » ajouté par GitHub lors d'un merge squash n'est pas compté.
|
|
89
106
|
authored_length() {
|