Votre première contribution à gitrust¶
Objectifs¶
À la fin de ce tutoriel, vous saurez :
- O1. Créer une branche de fonctionnalité depuis
mainet identifier un good first issue dans le backlog. - O2. Écrire un test unitaire qui échoue avant d'écrire le code de production (cycle TDD rouge-vert).
- O3. Appliquer la chaîne QA complète (
cargo fmt,cargo clippy,cargo test) et ouvrir une pull request sur la plateforme gitrust.
Pré-requis¶
- Technique : Rust ≥ 1.77 installé,
git≥ 2.40, accès en lecture/écriture à une instance gitrust (locale oudemo.gitrust.eu), tutoriel01-getting-startedcomplété (environnement local qui compile). - Pédagogique : tutoriel
01-getting-startedcomplété. - Temps estimé : ~90 minutes.
Vue d'ensemble¶
Contribuer à gitrust suit un cycle court et répétable : fork ou branche → test rouge → code minimal → tests verts → QA → PR.
sequenceDiagram
participant Vous
participant Git
participant Cargo
participant Plateforme
Vous->>Git: git switch -c feat/mon-fix
Vous->>Cargo: cargo test → ROUGE (test échoue)
Vous->>Cargo: écrire le code minimal
Vous->>Cargo: cargo test → VERT
Vous->>Cargo: cargo fmt + cargo clippy
Vous->>Git: git push origin feat/mon-fix
Vous->>Plateforme: ouvrir Pull Request
Le cycle TDD (Test-Driven Development) est essentiel ici : en écrivant d'abord le test, vous définissez précisément ce que votre code doit faire avant de l'écrire. C'est ce que gitrust appelle rouge → vert → refactor.
Étape 1 : Identifier un good first issue¶
Rendez-vous sur la page issues du dépôt gitrust (ou sur votre instance locale). Filtrez par le label de classification good-first-issue.
Lisez l'issue attentivement. Pour ce tutoriel, nous allons simuler le cas d'un issue fictif :
Issue #42 —
RepoSlugdevrait rejeter les slugs commençant par un tiret.
Assignez-vous l'issue (bouton « Assignee » dans la sidebar) pour signaler que vous travaillez dessus.
Checkpoint : vous devriez voir votre nom dans la liste des assignés de l'issue.
Étape 2 : Créer une branche de travail¶
Depuis la racine du dépôt gitrust, créez une branche nommée selon la convention fix/<numéro>-description-courte ou feat/<numéro>-description-courte :
Sortie attendue :
Checkpoint : git branch doit afficher votre branche avec un * devant.
Étape 3 : Écrire un test qui échoue (rouge)¶
Avant d'écrire le moindre code de production, écrivez le test qui décrit le comportement attendu. Ouvrez le fichier contenant la validation des slugs :
Repérez le bloc #[cfg(test)] existant en bas du fichier (ou créez-le s'il est absent). Ajoutez ce test dans la section tests :
#[cfg(test)]
mod tests {
use super::*;
// Tests existants...
#[test]
fn repo_slug_rejects_leading_dash() {
// Un slug qui commence par '-' doit être invalide.
// Ce test DOIT échouer avant notre correctif.
let result = RepoSlug::new("-invalid-slug");
assert!(
result.is_err(),
"Un slug commençant par '-' devrait être rejeté"
);
}
}
Exécutez maintenant les tests pour confirmer que ce test échoue :
Sortie attendue (rouge) :
test models::repository::tests::repo_slug_rejects_leading_dash ... FAILED
failures:
models::repository::tests::repo_slug_rejects_leading_dash
test result: FAILED. 0 passed; 1 failed
Checkpoint : le test échoue. C'est normal — c'est exactement l'état « rouge ».
Jargon :
RepoSlugest un newtype — un type Rust qui enveloppeStringet applique une validation à la construction viaRepoSlug::new(). Cette technique garantit qu'unRepoSlugvalide ne peut jamais exister sans passer par la validation.
Étape 4 : Écrire le code minimal (vert)¶
Localisez la fonction RepoSlug::new dans crates/gitrust-core/src/models/repository.rs. Elle ressemble à ceci (simplifié) :
impl RepoSlug {
pub fn new(s: &str) -> Result<Self, ValidationError> {
if s.is_empty() || s.len() > 64 {
return Err(ValidationError::InvalidSlug("longueur invalide".into()));
}
if s.contains("..") || s.contains('/') {
return Err(ValidationError::InvalidSlug("caractères interdits".into()));
}
// ... autres validations
Ok(Self(s.to_lowercase()))
}
}
Ajoutez uniquement la vérification manquante — ne modifiez rien d'autre :
impl RepoSlug {
pub fn new(s: &str) -> Result<Self, ValidationError> {
if s.is_empty() || s.len() > 64 {
return Err(ValidationError::InvalidSlug("longueur invalide".into()));
}
// Nouveau : rejeter les slugs commençant par un tiret
if s.starts_with('-') {
return Err(ValidationError::InvalidSlug(
"un slug ne peut pas commencer par un tiret".into(),
));
}
if s.contains("..") || s.contains('/') {
return Err(ValidationError::InvalidSlug("caractères interdits".into()));
}
Ok(Self(s.to_lowercase()))
}
}
Relancez le test :
Sortie attendue (vert) :
test models::repository::tests::repo_slug_rejects_leading_dash ... ok
test result: ok. 1 passed; 0 failed
Checkpoint : le test passe. Vous êtes en état « vert ».
Étape 5 : Vérifier que rien d'autre ne régresse¶
Un seul test qui passe ne suffit pas. Vérifiez l'ensemble de la suite du crate :
Sortie attendue :
Checkpoint : aucun test ne régresse.
Étape 6 : Passer la chaîne QA¶
Gitrust impose trois gates obligatoires avant toute PR. Exécutez-les dans cet ordre :
Gate 1 — Formatage :
Si des différences apparaissent, appliquez le format automatiquement :
Gate 2 — Linting (zéro warning) :
Sortie attendue :
Gate 3 — Tests complets :
Sortie attendue :
Checkpoint : les trois commandes se terminent sans erreur ni warning.
Étape 7 : Committer selon la convention¶
Gitrust utilise des messages de commit conventionnels (feat:, fix:, test:, refactor:, docs:). Commitez votre travail :
git add crates/gitrust-core/src/models/repository.rs
git commit -m "fix: rejeter les slugs commençant par un tiret (issue #42)"
Ne committez jamais avec
git add -Asans avoir inspectégit diff --stagedau préalable. Vérifiez qu'aucun fichier.env,target/, ou fichier généré n'est inclus.
Checkpoint : git log --oneline -3 montre votre commit en tête.
Étape 8 : Pousser et ouvrir une PR¶
Poussez votre branche vers l'origine :
Rendez-vous sur la plateforme gitrust. Un bandeau « Vous venez de pousser une branche — ouvrir une PR ? » devrait apparaître. Sinon, naviguez vers /{votre-username}/gitrust/pulls/new.
Remplissez le formulaire :
- Titre :
fix: rejeter les slugs commençant par un tiret - Corps : Renseignez le contexte, la cause du bug, la solution choisie, et ajoutez
Closes #42pour lier automatiquement l'issue. - Branche source :
fix/42-repo-slug-rejects-leading-dash - Branche cible :
main
Cliquez sur « Ouvrir la pull request ».
Checkpoint : la PR apparaît dans la liste /{owner}/gitrust/pulls avec le statut open.
Récapitulatif¶
- O1 accompli en identifiant l'issue #42 et en créant la branche
fix/42-repo-slug-rejects-leading-dash. - O2 accompli en écrivant
repo_slug_rejects_leading_dashqui échouait avant l'ajout des.starts_with('-'). - O3 accompli en passant
cargo fmt,cargo clippy -- -D warningsetcargo test --workspacesans erreur, puis en ouvrant la PR liée à l'issue.
Et si ça ne marche pas¶
| Symptôme | Cause probable | Correction |
|---|---|---|
cargo clippy signale needless_pass_by_value |
Votre nouvelle fonction prend une String alors qu'un &str suffit |
Changez le paramètre en &str |
cargo test échoue sur un test non lié |
Votre modification a cassé un invariant adjacent | Relisez les tests voisins ; revenez à un diff minimal |
git push refusé avec remote: pre-receive hook declined |
La branche est protégée ou votre token SSH n'est pas enregistré | Vérifiez /settings/keys sur la plateforme |