repowarden 4.3.0-next.6 → 4.4.0-next.4
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/bin/tickets +1 -0
- package/ci/tickets/adopter.sh +38 -0
- package/ci/tickets/commun.sh +63 -0
- package/ci/tickets/issue.sh +92 -0
- package/ci/tickets/pr.sh +70 -0
- package/ci/tickets/push.sh +49 -0
- package/ci/tickets/registre.sh +83 -0
- package/ci/tickets/release.sh +20 -0
- package/ci/tickets/verification.sh +47 -0
- package/ci/tickets.sh +68 -0
- package/package.json +3 -1
package/bin/tickets
CHANGED
|
@@ -70,6 +70,7 @@ type: fix|d73a4a|Correction de bug (branche fix/…)
|
|
|
70
70
|
type: docs|0075ca|Documentation (branche docs/…)
|
|
71
71
|
type: refactor|cfd3d7|Restructuration (branche refactor/…)
|
|
72
72
|
type: chore|ededed|Maintenance (branche chore/…)
|
|
73
|
+
urgent|b60205|Livraison immédiate au merge (faille, régression bloquante)
|
|
73
74
|
ETIQUETTES
|
|
74
75
|
|
|
75
76
|
# 2. Modèle de ticket et workflow (jamais écrasés)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, mise en route sur un dépôt existant (ci/tickets.sh
|
|
3
|
+
# adopter, appelé par repowarden tickets init).
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
# Mise en route sur un dépôt existant (tickets init, relançable) : tickets
|
|
8
|
+
# ouverts sans statut rangés (validé ou ouvert par un mainteneur → backlog,
|
|
9
|
+
# sinon à valider) ; branches des PR ouvertes et mergées inscrites sur les
|
|
10
|
+
# tickets qu'elles citent. Aucune branche créée en masse : elle naît quand le
|
|
11
|
+
# ticket est pris (repowarden ticket N).
|
|
12
|
+
cmd_adopter() {
|
|
13
|
+
local n assoc labels pr head ranges=0 t
|
|
14
|
+
while IFS=$'\t' read -r n assoc; do
|
|
15
|
+
[ -n "$n" ] || continue
|
|
16
|
+
labels="$(etiquettes "$n")"
|
|
17
|
+
! grep -q '^statut: ' <<<"$labels" || continue
|
|
18
|
+
if grep -qxF "$VALIDE" <<<"$labels"; then
|
|
19
|
+
statut "$n" "$S_BACKLOG"
|
|
20
|
+
elif [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] && est_mainteneur "$assoc"; then
|
|
21
|
+
gh issue edit "$n" --add-label "$VALIDE" >/dev/null
|
|
22
|
+
statut "$n" "$S_BACKLOG"
|
|
23
|
+
else
|
|
24
|
+
statut "$n" "$S_AVALIDER"
|
|
25
|
+
fi
|
|
26
|
+
ranges=$((ranges + 1))
|
|
27
|
+
done < <(gh api "repos/$GH_REPO/issues?state=open&per_page=100" --paginate \
|
|
28
|
+
-q '.[] | select(.pull_request | not) | [.number, .author_association] | @tsv')
|
|
29
|
+
while IFS=$'\t' read -r pr head; do
|
|
30
|
+
[ -n "$pr" ] || continue
|
|
31
|
+
[[ "$head" != "${INTEGRATION:-develop}" && "$head" != release-please--* ]] || continue
|
|
32
|
+
pr_tickets_r "$(gh pr view "$pr" --json body -q .body)" "$head"
|
|
33
|
+
for t in $REPLY; do inscrire_branche "$t" "$head" "PR #$pr"; done
|
|
34
|
+
done < <(gh pr list --state all --limit 1000 --json number,headRefName,state \
|
|
35
|
+
-q '.[] | select(.state != "CLOSED") | [.number, .headRefName] | @tsv')
|
|
36
|
+
_tr tk.adopted "$ranges" "$INSCRITES"
|
|
37
|
+
echo "$_T"
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, outils communs : étiquettes et assignés d'un ticket,
|
|
3
|
+
# changement de statut, ticket validé, mainteneur, tickets cités par une PR,
|
|
4
|
+
# PR qui a fermé un ticket, notices.
|
|
5
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
6
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
7
|
+
|
|
8
|
+
# REPLY = numéros de tickets (uniques) cités par la description $1 (« Ticket : #12 »,
|
|
9
|
+
# « Tickets : #3, #4 », ou un mot-clé de fermeture) et la branche $2 (type/12-sujet)
|
|
10
|
+
pr_tickets_r() {
|
|
11
|
+
local body="$1" branch="$2" found="" n line
|
|
12
|
+
while IFS= read -r line; do
|
|
13
|
+
if [[ "$line" =~ ^[[:space:]]*(Tickets?|Refs?|Closes|Fixes|Resolves)[[:space:]]*:?[[:space:]]*(.*)$ ]]; then
|
|
14
|
+
for n in $(grep -oE '#[0-9]+' <<<"${BASH_REMATCH[2]}" | tr -d '#'); do
|
|
15
|
+
[[ " $found " == *" $n "* ]] || found="$found $n"
|
|
16
|
+
done
|
|
17
|
+
fi
|
|
18
|
+
done <<<"$body"
|
|
19
|
+
branch_ticket_r "$branch"
|
|
20
|
+
n="$REPLY"
|
|
21
|
+
if [ -n "$n" ]; then
|
|
22
|
+
[[ " $found " == *" $n "* ]] || found="$found $n"
|
|
23
|
+
fi
|
|
24
|
+
REPLY="${found# }"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Statut $2 sur le ticket $1 : remplace les autres étiquettes « statut: »
|
|
28
|
+
statut() {
|
|
29
|
+
local n="$1" s="$2" l retirer=()
|
|
30
|
+
while IFS= read -r l; do
|
|
31
|
+
[[ "$l" == "statut: "* && "$l" != "$s" ]] && retirer+=(--remove-label "$l")
|
|
32
|
+
done < <(gh issue view "$n" --json labels -q '.labels[].name' 2>/dev/null || true)
|
|
33
|
+
# ${a[@]+…} : tableau vide accepté sous set -u par le bash 3.2 de macOS
|
|
34
|
+
gh issue edit "$n" --add-label "$s" ${retirer[@]+"${retirer[@]}"} >/dev/null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
etiquettes() { gh issue view "$1" --json labels -q '.labels[].name' 2>/dev/null || true; }
|
|
38
|
+
|
|
39
|
+
# REPLY = « <n° de PR> <branche cible> » de la PR dont le merge a fermé le
|
|
40
|
+
# ticket $1, vide si le ticket a été fermé autrement (à la main, release…)
|
|
41
|
+
fermee_par_pr_r() {
|
|
42
|
+
# shellcheck disable=SC2016 # variables GraphQL, pas du shell
|
|
43
|
+
REPLY="$(gh api graphql -f query='query($o: String!, $r: String!, $n: Int!) {
|
|
44
|
+
repository(owner: $o, name: $r) { issue(number: $n) {
|
|
45
|
+
timelineItems(last: 1, itemTypes: CLOSED_EVENT) { nodes { ... on ClosedEvent {
|
|
46
|
+
closer { ... on PullRequest { number baseRefName } } } } } } } }' \
|
|
47
|
+
-f o="${GH_REPO%%/*}" -f r="${GH_REPO#*/}" -F n="$1" \
|
|
48
|
+
-q '.data.repository.issue.timelineItems.nodes[0].closer // empty | "\(.number) \(.baseRefName)"' \
|
|
49
|
+
2>/dev/null || true)"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Personnes assignées au ticket $1 (une par ligne)
|
|
53
|
+
assignes() { gh issue view "$1" --json assignees -q '.assignees[].login' 2>/dev/null || true; }
|
|
54
|
+
|
|
55
|
+
est_mainteneur() { [[ "${1:-}" =~ ^(OWNER|MEMBER|COLLABORATOR)$ ]]; }
|
|
56
|
+
|
|
57
|
+
est_valide() { grep -qxF "$VALIDE" <<<"$(etiquettes "$1")"; }
|
|
58
|
+
|
|
59
|
+
notice() { echo "::notice title=repowarden::$*"; }
|
|
60
|
+
|
|
61
|
+
notice_t() { _tr "$@"; notice "$_T"; }
|
|
62
|
+
|
|
63
|
+
# Chargé par les tests (source) : fonctions seulement
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, événements de ticket (ci/tickets.sh issue) : validation
|
|
3
|
+
# (d'office pour un mainteneur), assignation, fermeture.
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
# Ticket $1 validé : backlog s'il attendait la validation, sa branche, et
|
|
8
|
+
# vérification relancée sur les PR ouvertes qui l'attendaient
|
|
9
|
+
accepter() {
|
|
10
|
+
local n="$1" pr head sha body base brouillon
|
|
11
|
+
if grep -qxF "$S_AVALIDER" <<<"$(etiquettes "$n")"; then
|
|
12
|
+
statut "$n" "$S_BACKLOG"
|
|
13
|
+
fi
|
|
14
|
+
# La branche naît quand le ticket est pris (assigné) : un ticket validé
|
|
15
|
+
# pour plus tard n'a pas de branche qui vieillit en attendant
|
|
16
|
+
if [ -n "$(assignes "$n")" ]; then
|
|
17
|
+
creer_branche "$n"
|
|
18
|
+
else
|
|
19
|
+
_tr tk.validated_waiting "$n"
|
|
20
|
+
gh issue comment "$n" --body "$_T" >/dev/null
|
|
21
|
+
fi
|
|
22
|
+
while IFS=$'\t' read -r pr head sha base brouillon; do
|
|
23
|
+
[ -n "$pr" ] || continue
|
|
24
|
+
body="$(gh pr view "$pr" --json body -q .body)"
|
|
25
|
+
pr_tickets_r "$body" "$head"
|
|
26
|
+
[[ " $REPLY " == *" $n "* ]] || continue
|
|
27
|
+
if verifier "$pr" "$sha" "$REPLY"; then
|
|
28
|
+
merge_auto "$pr" "$base" "$brouillon"
|
|
29
|
+
fi
|
|
30
|
+
done < <(gh pr list --state open --json number,headRefName,headRefOid,baseRefName,isDraft \
|
|
31
|
+
-q '.[] | [.number, .headRefName, .headRefOid, .baseRefName, .isDraft] | @tsv')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
cmd_issue() {
|
|
35
|
+
local pr sha head body
|
|
36
|
+
case "$ACTION" in
|
|
37
|
+
opened)
|
|
38
|
+
est_valide "$ISSUE" && return 0
|
|
39
|
+
# Ouvert par un mainteneur (droits d'écriture) : c'est déjà sa
|
|
40
|
+
# décision, validé d'office ; sinon, un mainteneur pose « validé »
|
|
41
|
+
if [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] &&
|
|
42
|
+
est_mainteneur "${ISSUE_ASSOCIATION:-}"; then
|
|
43
|
+
gh issue edit "$ISSUE" --add-label "$VALIDE" >/dev/null
|
|
44
|
+
statut "$ISSUE" "$S_AVALIDER"
|
|
45
|
+
accepter "$ISSUE"
|
|
46
|
+
else
|
|
47
|
+
statut "$ISSUE" "$S_AVALIDER"
|
|
48
|
+
fi
|
|
49
|
+
;;
|
|
50
|
+
labeled)
|
|
51
|
+
[ "${LABEL:-}" = "$VALIDE" ] || return 0
|
|
52
|
+
accepter "$ISSUE"
|
|
53
|
+
;;
|
|
54
|
+
assigned)
|
|
55
|
+
# Pris en charge : sa branche (s'il est validé), en cours s'il
|
|
56
|
+
# attendait dans le backlog
|
|
57
|
+
est_valide "$ISSUE" && creer_branche "$ISSUE"
|
|
58
|
+
grep -qxF "$S_BACKLOG" <<<"$(etiquettes "$ISSUE")" && statut "$ISSUE" "$S_ENCOURS"
|
|
59
|
+
return 0
|
|
60
|
+
;;
|
|
61
|
+
closed)
|
|
62
|
+
# Fermé par GitHub au merge d'une PR liée dans la branche par défaut
|
|
63
|
+
# (develop) : pas encore en production, rouvert en préprod. Une
|
|
64
|
+
# fermeture à la main (sans PR) est respectée.
|
|
65
|
+
if [ "${STATE_REASON:-}" = completed ] && ! grep -qxF "$S_DONE" <<<"$(etiquettes "$ISSUE")"; then
|
|
66
|
+
fermee_par_pr_r "$ISSUE"
|
|
67
|
+
local fpr="${REPLY%% *}" fbase="${REPLY#* }"
|
|
68
|
+
if [ -n "$fpr" ] && [ "$fbase" != "${MAIN:-main}" ]; then
|
|
69
|
+
gh issue reopen "$ISSUE" >/dev/null
|
|
70
|
+
statut "$ISSUE" "$S_PREPROD"
|
|
71
|
+
_tr tk.reopened "$fpr" "$fbase"
|
|
72
|
+
gh issue comment "$ISSUE" --body "$_T" >/dev/null
|
|
73
|
+
notice "$_T"
|
|
74
|
+
fi
|
|
75
|
+
return 0
|
|
76
|
+
fi
|
|
77
|
+
# Abandonné : PR fermées, branche supprimée (un ticket terminé est
|
|
78
|
+
# fermé par la release, sa branche est déjà supprimée au merge)
|
|
79
|
+
[ "${STATE_REASON:-}" = not_planned ] || return 0
|
|
80
|
+
branche_du_ticket_r "$ISSUE"
|
|
81
|
+
[ -n "$REPLY" ] || return 0
|
|
82
|
+
local branche="$REPLY"
|
|
83
|
+
while IFS= read -r pr; do
|
|
84
|
+
[ -n "$pr" ] || continue
|
|
85
|
+
_tr tk.abandoned "$ISSUE"
|
|
86
|
+
gh pr close "$pr" --comment "$_T" >/dev/null
|
|
87
|
+
done < <(gh pr list --state open --head "$branche" --json number -q '.[].number')
|
|
88
|
+
gh api -X DELETE "repos/$GH_REPO/git/refs/heads/$branche" >/dev/null
|
|
89
|
+
notice_t tk.branch_deleted "$branche" "$ISSUE"
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
}
|
package/ci/tickets/pr.sh
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, événements de PR (ci/tickets.sh pr) : ticket créé s'il
|
|
3
|
+
# manque, statuts, PR sans objet depuis main ou develop, vérification.
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
cmd_pr() {
|
|
8
|
+
local body tickets n url
|
|
9
|
+
: "${PR:?}" "${HEAD:?}" "${HEAD_SHA:?}" "${ACTION:?}"
|
|
10
|
+
# PR partant d'une branche persistante : seules la livraison (develop →
|
|
11
|
+
# main) et le retour (main → develop, si main a un contenu propre) ont un
|
|
12
|
+
# sens ; les autres sont fermées, avec la marche à suivre
|
|
13
|
+
if [[ "$HEAD" == "${INTEGRATION:-develop}" || "$HEAD" == "${MAIN:-main}" ]] &&
|
|
14
|
+
[[ "$ACTION" == opened || "$ACTION" == reopened ]]; then
|
|
15
|
+
if [[ "$HEAD" == "${INTEGRATION:-develop}" && "${BASE:-}" != "${MAIN:-main}" ]] ||
|
|
16
|
+
[[ "$HEAD" == "${MAIN:-main}" && "${BASE:-}" != "${INTEGRATION:-develop}" ]]; then
|
|
17
|
+
_tr tk.pr_inverted "$HEAD" "${BASE:-}" "${INTEGRATION:-develop}"
|
|
18
|
+
gh pr close "$PR" --comment "$_T" >/dev/null
|
|
19
|
+
notice "$_T"
|
|
20
|
+
elif [ "$HEAD" = "${MAIN:-main}" ] &&
|
|
21
|
+
[ "$(gh api "repos/$GH_REPO/compare/$BASE...$HEAD" -q '.files | length')" = 0 ]; then
|
|
22
|
+
_tr tk.pr_backmerge_empty "$HEAD" "$BASE"
|
|
23
|
+
gh pr close "$PR" --comment "$_T" >/dev/null
|
|
24
|
+
notice "$_T"
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
# Bots de dépendances, PR de release, livraisons et retours : pas de ticket
|
|
28
|
+
# exigé. Les PR ouvertes par le suivi des tickets lui-même (jeton des
|
|
29
|
+
# Actions, github-actions[bot]) citent leur ticket : suivies normalement.
|
|
30
|
+
if [[ "${AUTHOR:-}" =~ ^(dependabot|renovate)(\[bot\])?$ || "$HEAD" == release-please--* ||
|
|
31
|
+
"$HEAD" == "${INTEGRATION:-develop}" || "$HEAD" == "${MAIN:-main}" ]]; then
|
|
32
|
+
gh api "repos/$GH_REPO/statuses/$HEAD_SHA" -f state=success -f context=ticket \
|
|
33
|
+
-f description="Sans ticket (bot, release ou livraison)" >/dev/null
|
|
34
|
+
return 0
|
|
35
|
+
fi
|
|
36
|
+
body="$(gh pr view "$PR" --json body -q .body)"
|
|
37
|
+
pr_tickets_r "$body" "$HEAD"
|
|
38
|
+
tickets="$REPLY"
|
|
39
|
+
|
|
40
|
+
if [ "$ACTION" = closed ]; then
|
|
41
|
+
[ "${MERGED:-false}" = true ] || return 0
|
|
42
|
+
for n in $tickets; do
|
|
43
|
+
statut "$n" "$S_PREPROD"
|
|
44
|
+
_tr tk.merged "$PR" "$BASE"
|
|
45
|
+
gh issue comment "$n" --body "$_T" >/dev/null
|
|
46
|
+
done
|
|
47
|
+
return 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Aucun ticket : créé à partir de la PR, à valider, et relié à la PR
|
|
51
|
+
if [ -z "$tickets" ]; then
|
|
52
|
+
_tr tk.created_body "$PR"
|
|
53
|
+
url="$(gh issue create --title "$(gh pr view "$PR" --json title -q .title)" \
|
|
54
|
+
--body "$_T" --label "$S_AVALIDER")"
|
|
55
|
+
tickets="${url##*/}"
|
|
56
|
+
gh pr edit "$PR" --body "$(printf '%s\n\nTicket : #%s\n' "$body" "$tickets")" >/dev/null
|
|
57
|
+
notice_t tk.created "$tickets" "$PR"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Statut du travail (seulement pour un ticket validé)
|
|
61
|
+
for n in $tickets; do
|
|
62
|
+
est_valide "$n" || continue
|
|
63
|
+
if [ "${DRAFT:-false}" = true ]; then statut "$n" "$S_ENCOURS"; else statut "$n" "$S_RELECTURE"; fi
|
|
64
|
+
done
|
|
65
|
+
# Le blocage est porté par le statut « ticket » (exigé par la protection) :
|
|
66
|
+
# le job reste vert, un rouge signale une vraie panne
|
|
67
|
+
if verifier "$PR" "$HEAD_SHA" "$tickets"; then
|
|
68
|
+
merge_auto "$PR" "${BASE:-}" "${DRAFT:-false}"
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, premier push d'une branche de ticket (PR brouillon) et
|
|
3
|
+
# revues (ci/tickets.sh push, revue).
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
# Premier push sur la branche d'un ticket : PR brouillon vers la branche
|
|
8
|
+
# d'intégration, citant le ticket, assignée à l'auteur du push ; ticket en
|
|
9
|
+
# cours. Ouverte par le jeton des Actions, elle ne lance pas la CI : elle
|
|
10
|
+
# tourne au push suivant, ou au passage « prête pour revue ».
|
|
11
|
+
cmd_push() {
|
|
12
|
+
local n pr titre type url
|
|
13
|
+
: "${PUSH_BRANCH:?}" "${SHA:?}"
|
|
14
|
+
[ "${DELETED:-false}" != true ] || return 0
|
|
15
|
+
branch_ticket_r "$PUSH_BRANCH"
|
|
16
|
+
n="$REPLY"
|
|
17
|
+
[ -n "$n" ] || return 0
|
|
18
|
+
# Branche faite hors du ticket (à la main) : inscrite sur le ticket
|
|
19
|
+
inscrire_branche "$n" "$PUSH_BRANCH"
|
|
20
|
+
pr="$(gh pr list --state open --head "$PUSH_BRANCH" --json number -q '.[0].number // empty')"
|
|
21
|
+
[ -z "$pr" ] || return 0
|
|
22
|
+
[ "$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$PUSH_BRANCH" -q .ahead_by)" != 0 ] || return 0
|
|
23
|
+
titre="$(gh issue view "$n" --json title -q .title)"
|
|
24
|
+
type="${PUSH_BRANCH%%/*}"
|
|
25
|
+
case "$type" in feature) type=feat ;; bugfix | hotfix) type=fix ;; esac
|
|
26
|
+
# Titre conventionnel (message du commit en squash) : type: titre du ticket
|
|
27
|
+
titre="$type: $(tr '[:upper:]' '[:lower:]' <<<"${titre:0:1}")${titre:1}"
|
|
28
|
+
authored_length "$titre"
|
|
29
|
+
[ "$REPLY" -le 72 ] || titre="${titre:0:72}"
|
|
30
|
+
url="$(gh pr create --draft --base "$INTEGRATION" --head "$PUSH_BRANCH" --title "$titre" \
|
|
31
|
+
--body "Ticket : #$n" ${PUSHER:+--assignee "$PUSHER"})"
|
|
32
|
+
[ -z "${PUSHER:-}" ] || gh issue edit "$n" --add-assignee "$PUSHER" >/dev/null || true
|
|
33
|
+
est_valide "$n" && statut "$n" "$S_ENCOURS"
|
|
34
|
+
notice_t tk.draft_opened "${url##*/}" "$n"
|
|
35
|
+
verifier "${url##*/}" "$SHA" "$n" || true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Revue : corrections demandées → le ticket repasse en cours
|
|
39
|
+
cmd_revue() {
|
|
40
|
+
local body n
|
|
41
|
+
: "${PR:?}" "${HEAD:?}"
|
|
42
|
+
[ "${REVIEW_STATE:-}" = changes_requested ] || return 0
|
|
43
|
+
body="$(gh pr view "$PR" --json body -q .body)"
|
|
44
|
+
pr_tickets_r "$body" "$HEAD"
|
|
45
|
+
for n in $REPLY; do
|
|
46
|
+
est_valide "$n" && statut "$n" "$S_ENCOURS"
|
|
47
|
+
done
|
|
48
|
+
return 0
|
|
49
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, branche du ticket : registre des branches (repère dans
|
|
3
|
+
# les commentaires du bot), branche existante, création liée au ticket.
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
# Registre des branches d'un ticket : repère invisible dans les commentaires
|
|
8
|
+
# du bot. GitHub ne lie une branche au ticket qu'à sa création, et une branche
|
|
9
|
+
# liée supprimée y perd son nom : le registre garde le nom de chaque branche.
|
|
10
|
+
REPERE="repowarden:branche"
|
|
11
|
+
|
|
12
|
+
repere() { printf '<!-- %s %s -->' "$REPERE" "$1"; }
|
|
13
|
+
|
|
14
|
+
# REPLY = branches inscrites sur le ticket $1 (séparées par des espaces)
|
|
15
|
+
branches_inscrites_r() {
|
|
16
|
+
local corps
|
|
17
|
+
corps="$(gh issue view "$1" --json comments -q '.comments[].body' 2>/dev/null || true)"
|
|
18
|
+
REPLY="$(grep -oE "<!-- $REPERE [^ ]+ -->" <<<"$corps" | awk '{ printf "%s ", $3 }' || true)"
|
|
19
|
+
REPLY="${REPLY% }"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Inscrit la branche $2 sur le ticket $1, une seule fois ($3 : origine, ex. PR #7).
|
|
23
|
+
# Une seconde branche pour le même ticket est inscrite et signalée.
|
|
24
|
+
INSCRITES=0
|
|
25
|
+
|
|
26
|
+
inscrire_branche() {
|
|
27
|
+
local n="$1" b="$2" origine="${3:-}"
|
|
28
|
+
branches_inscrites_r "$n"
|
|
29
|
+
[[ " $REPLY " != *" $b "* ]] || return 0
|
|
30
|
+
if [ -n "$REPLY" ]; then
|
|
31
|
+
_tr tk.branch_second "$b" "${REPLY// /, }"
|
|
32
|
+
notice "$_T"
|
|
33
|
+
else
|
|
34
|
+
_tr tk.branch_registered "$b"
|
|
35
|
+
fi
|
|
36
|
+
[ -z "$origine" ] || _T="$_T ($origine)"
|
|
37
|
+
gh issue comment "$n" --body "$_T"$'\n\n'"$(repere "$b")" >/dev/null
|
|
38
|
+
INSCRITES=$((INSCRITES + 1))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# REPLY = branche du dépôt portant le ticket $1 (type/<n>-…), vide si aucune
|
|
42
|
+
branche_du_ticket_r() {
|
|
43
|
+
local ref
|
|
44
|
+
REPLY=""
|
|
45
|
+
while IFS= read -r ref; do
|
|
46
|
+
ref="${ref#refs/heads/}"
|
|
47
|
+
branch_ticket_r "$ref"
|
|
48
|
+
if [ "$REPLY" = "$1" ]; then REPLY="$ref"; return 0; fi
|
|
49
|
+
REPLY=""
|
|
50
|
+
done < <(gh api "repos/$GH_REPO/git/matching-refs/heads/" --paginate -q '.[].ref' 2>/dev/null || true)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Ticket $1 validé : sa branche naît de la branche d'intégration, liée au
|
|
54
|
+
# ticket (panneau « Development »). Rien si elle existe déjà, ou si une PR
|
|
55
|
+
# ouverte cite déjà le ticket (travail commencé hors de sa branche).
|
|
56
|
+
creer_branche() {
|
|
57
|
+
local n="$1" titre labels branche pr head body
|
|
58
|
+
# Travail déjà mergé ou en production : plus de branche à (re)créer
|
|
59
|
+
travail_termine "$(etiquettes "$n")" && return 0
|
|
60
|
+
branche_du_ticket_r "$n"
|
|
61
|
+
[ -z "$REPLY" ] || return 0
|
|
62
|
+
while IFS=$'\t' read -r pr head; do
|
|
63
|
+
[ -n "$pr" ] || continue
|
|
64
|
+
body="$(gh pr view "$pr" --json body -q .body)"
|
|
65
|
+
pr_tickets_r "$body" "$head"
|
|
66
|
+
[[ " $REPLY " != *" $n "* ]] || return 0
|
|
67
|
+
done < <(gh pr list --state open --json number,headRefName -q '.[] | [.number, .headRefName] | @tsv')
|
|
68
|
+
titre="$(gh issue view "$n" --json title -q .title)"
|
|
69
|
+
labels="$(etiquettes "$n")"
|
|
70
|
+
ticket_type_r "$labels"
|
|
71
|
+
ticket_branch_r "$n" "$titre" "$REPLY"
|
|
72
|
+
branche="$REPLY"
|
|
73
|
+
# shellcheck disable=SC2016 # variables GraphQL, pas du shell
|
|
74
|
+
gh api graphql -f query='mutation($issue: ID!, $repo: ID!, $oid: GitObjectID!, $name: String!) {
|
|
75
|
+
createLinkedBranch(input: {issueId: $issue, repositoryId: $repo, oid: $oid, name: $name}) { linkedBranch { id } }
|
|
76
|
+
}' -f issue="$(gh issue view "$n" --json id -q .id)" \
|
|
77
|
+
-f repo="$(gh api "repos/$GH_REPO" -q .node_id)" \
|
|
78
|
+
-f oid="$(gh api "repos/$GH_REPO/git/ref/heads/$INTEGRATION" -q .object.sha)" \
|
|
79
|
+
-f name="$branche" >/dev/null
|
|
80
|
+
_tr tk.branch_created "$branche" "$INTEGRATION" "$n" "$branche"
|
|
81
|
+
gh issue comment "$n" --body "$_T"$'\n\n'"$(repere "$branche")" >/dev/null
|
|
82
|
+
notice_t tk.branch_notice "$branche" "$n"
|
|
83
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, préversion et release (ci/tickets.sh release) : tickets
|
|
3
|
+
# prévenus, ou passés en done et fermés.
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
cmd_release() {
|
|
8
|
+
local n
|
|
9
|
+
while IFS= read -r n; do
|
|
10
|
+
[ -n "$n" ] || continue
|
|
11
|
+
if [[ "$TAG" == *-* ]]; then
|
|
12
|
+
_tr tk.prerelease "$TAG"
|
|
13
|
+
gh issue comment "$n" --body "$_T" >/dev/null
|
|
14
|
+
else
|
|
15
|
+
_tr tk.released "$TAG"
|
|
16
|
+
statut "$n" "$S_DONE"
|
|
17
|
+
gh issue close "$n" --comment "$_T" >/dev/null
|
|
18
|
+
fi
|
|
19
|
+
done < <(gh issue list --state open --label "$S_PREPROD" --limit 500 --json number -q '.[].number')
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Suivi des tickets, vérification « ticket » d'une PR (statut de commit
|
|
3
|
+
# exigé par la protection) et merge automatique des PR de travail.
|
|
4
|
+
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
|
+
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
|
+
|
|
7
|
+
# Vérification « ticket » (statut de commit) de la PR $1, commit $2
|
|
8
|
+
verifier() {
|
|
9
|
+
local pr="$1" sha="$2" tickets="$3" n attente="" liste
|
|
10
|
+
for n in $tickets; do est_valide "$n" || attente="$attente #$n"; done
|
|
11
|
+
if [ -z "$tickets" ]; then
|
|
12
|
+
_tr tk.none; etat=failure
|
|
13
|
+
elif [ -n "$attente" ]; then
|
|
14
|
+
_tr tk.waiting "${attente# }" "$VALIDE"; etat=failure
|
|
15
|
+
else
|
|
16
|
+
liste=""
|
|
17
|
+
for n in $tickets; do liste="$liste #$n"; done
|
|
18
|
+
_tr tk.ok "${liste# }"; etat=success
|
|
19
|
+
fi
|
|
20
|
+
echo "$_T"
|
|
21
|
+
gh api "repos/$GH_REPO/statuses/$sha" -f state="$etat" -f context=ticket \
|
|
22
|
+
-f description="${_T:0:140}" >/dev/null
|
|
23
|
+
[ "$etat" = success ]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Merge automatique (squash) de la PR de travail $1, cible $2, brouillon $3 :
|
|
27
|
+
# armé quand elle est prête, vise la branche d'intégration et que son ticket
|
|
28
|
+
# est validé (appelé après une vérification réussie) ; désarmé en brouillon.
|
|
29
|
+
# Seulement avec le jeton de l'App : un merge fait avec le jeton des Actions ne
|
|
30
|
+
# déclencherait aucun workflow (préversion, livraison, tickets).
|
|
31
|
+
merge_auto() {
|
|
32
|
+
local pr="$1" base="$2" brouillon="$3"
|
|
33
|
+
[ "${AUTO_MERGE:-true}" = true ] && [ "$base" = "${INTEGRATION:-develop}" ] || return 0
|
|
34
|
+
if [ "$brouillon" = true ]; then
|
|
35
|
+
gh pr merge "$pr" --disable-auto >/dev/null 2>&1 || true
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
if [ "${APP_TOKEN:-false}" != true ]; then
|
|
39
|
+
notice_t tk.automerge_no_app "$pr"
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
if gh pr merge "$pr" --auto --squash >/dev/null 2>&1; then
|
|
43
|
+
notice_t tk.automerge_armed "$pr"
|
|
44
|
+
else
|
|
45
|
+
notice_t tk.automerge_failed "$pr"
|
|
46
|
+
fi
|
|
47
|
+
}
|
package/ci/tickets.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Suivi des tickets (issues GitHub) : chaque PR est reliée à un ticket validé,
|
|
3
|
+
# et le ticket suit le travail jusqu'à la production.
|
|
4
|
+
#
|
|
5
|
+
# ci/tickets.sh pr événement pull_request : ticket créé s'il manque,
|
|
6
|
+
# statut du ticket, vérification « ticket » de la PR
|
|
7
|
+
# ci/tickets.sh issue événement issues : nouveau ticket à valider ;
|
|
8
|
+
# étiquette « validé » → backlog, branche du ticket
|
|
9
|
+
# créée (liée au ticket), PR débloquées ; assigné →
|
|
10
|
+
# en cours ; fermé « not planned » → PR et branche
|
|
11
|
+
# fermées
|
|
12
|
+
# ci/tickets.sh push premier push sur la branche d'un ticket : PR
|
|
13
|
+
# brouillon ouverte, ticket en cours
|
|
14
|
+
# ci/tickets.sh revue corrections demandées en revue → en cours
|
|
15
|
+
# ci/tickets.sh release release publiée : tickets en préprod → done (fermés),
|
|
16
|
+
# ou prévenus de la préversion
|
|
17
|
+
# ci/tickets.sh adopter mise en route (tickets init) : tickets existants
|
|
18
|
+
# rangés, branches des PR inscrites sur leurs tickets
|
|
19
|
+
#
|
|
20
|
+
# Le ticket d'abord, la branche en découle : à valider → (validé : branche
|
|
21
|
+
# type/12-titre) backlog → en cours → en relecture → préprod → done. Seul un
|
|
22
|
+
# mainteneur pose « validé » (droits GitHub).
|
|
23
|
+
# Lien PR → ticket : « Ticket : #12 » dans la description, ou une branche
|
|
24
|
+
# feat/12-sujet. « Closes #12 » fermerait le ticket dès le merge dans develop :
|
|
25
|
+
# c'est la release qui le ferme.
|
|
26
|
+
#
|
|
27
|
+
# Variables : GH_TOKEN, GH_REPO, INTEGRATION ; pr : PR, ACTION, MERGED, DRAFT,
|
|
28
|
+
# HEAD, BASE, HEAD_SHA, AUTHOR ; issue : ISSUE, ACTION, LABEL, STATE_REASON ;
|
|
29
|
+
# push : PUSH_BRANCH, DELETED, PUSHER, SHA ; revue : PR, HEAD, REVIEW_STATE ;
|
|
30
|
+
# release : TAG
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
REPOWARDEN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
34
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
35
|
+
source "$REPOWARDEN_DIR/hooks/lib/common.sh"
|
|
36
|
+
# shellcheck source=../hooks/lib/tickets.sh
|
|
37
|
+
source "$REPOWARDEN_DIR/hooks/lib/tickets.sh"
|
|
38
|
+
INTEGRATION="${INTEGRATION:-develop}"
|
|
39
|
+
|
|
40
|
+
# Modules (un rôle chacun, voir leur en-tête)
|
|
41
|
+
# shellcheck source=tickets/commun.sh
|
|
42
|
+
source "$REPOWARDEN_DIR/ci/tickets/commun.sh"
|
|
43
|
+
# shellcheck source=tickets/registre.sh
|
|
44
|
+
source "$REPOWARDEN_DIR/ci/tickets/registre.sh"
|
|
45
|
+
# shellcheck source=tickets/verification.sh
|
|
46
|
+
source "$REPOWARDEN_DIR/ci/tickets/verification.sh"
|
|
47
|
+
# shellcheck source=tickets/pr.sh
|
|
48
|
+
source "$REPOWARDEN_DIR/ci/tickets/pr.sh"
|
|
49
|
+
# shellcheck source=tickets/issue.sh
|
|
50
|
+
source "$REPOWARDEN_DIR/ci/tickets/issue.sh"
|
|
51
|
+
# shellcheck source=tickets/push.sh
|
|
52
|
+
source "$REPOWARDEN_DIR/ci/tickets/push.sh"
|
|
53
|
+
# shellcheck source=tickets/release.sh
|
|
54
|
+
source "$REPOWARDEN_DIR/ci/tickets/release.sh"
|
|
55
|
+
# shellcheck source=tickets/adopter.sh
|
|
56
|
+
source "$REPOWARDEN_DIR/ci/tickets/adopter.sh"
|
|
57
|
+
|
|
58
|
+
[[ "${BASH_SOURCE[0]}" == "$0" ]] || return 0
|
|
59
|
+
|
|
60
|
+
case "${1:-}" in
|
|
61
|
+
pr) cmd_pr ;;
|
|
62
|
+
issue) cmd_issue ;;
|
|
63
|
+
push) cmd_push ;;
|
|
64
|
+
revue) cmd_revue ;;
|
|
65
|
+
release) cmd_release ;;
|
|
66
|
+
adopter) cmd_adopter ;;
|
|
67
|
+
*) echo "usage : ci/tickets.sh pr|issue|push|revue|release|adopter" >&2; exit 2 ;;
|
|
68
|
+
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repowarden",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0-next.4",
|
|
4
4
|
"description": "Hooks git et CI multi-langages : messages de commit, nommage des branches, secrets, formatage, tests ciblés, code mort, releases.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git-hooks",
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"bin/",
|
|
27
|
+
"ci/tickets.sh",
|
|
28
|
+
"ci/tickets/",
|
|
27
29
|
"hooks/",
|
|
28
30
|
"install.sh"
|
|
29
31
|
],
|