gwm-cli 1.6.0

git worktree manager — TUI + CLI, native libgit2, per-repo bootstrap
Documentation
---
title: GitLab (multi-forge)
description: Pointer gwm vers GitLab plutôt que GitHub : la clé `forge`, la CLI `glab`, et ce qui diffère du backend GitHub.
---

# GitLab (multi-forge)

Ajouté par [#419](https://github.com/kbrdn1/gwm-cli/issues/419).

gwm dialogue avec une **forge**, c'est-à-dire une plateforme d'hébergement de code, pour les recherches d'issues et de merge/pull requests. Deux backends sont livrés aujourd'hui :

| Forge | CLI | Terminologie |
|---|---|---|
| GitHub | [`gh`](https://cli.github.com) | pull request, « PR » |
| GitLab | [`glab`](https://gitlab.com/gitlab-org/cli) | merge request, « MR » |

Tout le reste est identique. Les worktrees, le bootstrap, le nommage des branches, la barrière de confiance, le daemon et le TUI ignorent sur quelle forge vous êtes ; seule la couche réseau le sait. En particulier, le [modèle de stockage des liens](/fr/integrations/github-linking#modèle-de-stockage) est inchangé : un worktree GitLab écrit les mêmes clés git-config `branch.<name>.gwm-*`, donc rien dans un dépôt ne devient spécifique à une forge sur le disque.

## sélectionner la forge

gwm lit la forge depuis votre remote `origin` **uniquement quand l'hôte dit laquelle il fait tourner**, c'est-à-dire sur les domaines propres aux éditeurs :

```
git@github.com:owner/repo.git              → GitHub
https://acme.ghe.com/team/proj.git         → GitHub (GitHub Enterprise Cloud)
https://gitlab.com/group/sub/proj.git      → GitLab
https://gitlab.example.com/team/proj.git   → refusé : nommez la forge
https://code.acme.internal/team/proj.git   → refusé : nommez la forge
```

Une **instance auto-hébergée vit sur un domaine arbitraire** et ne peut pas être détectée depuis la seule URL du remote. gwm ne devine pas là, et l'étiquette `gitlab.` n'est pas non plus une supposition qu'il fera : un nom d'hôte est choisi par le propriétaire du domaine, il ne prouve donc rien sur ce qui tourne dessus. Deviner reviendrait à envoyer un appel authentifié `gh` / `glab`, et le jeton présent dans votre environnement, vers l'hôte que le dépôt cloné se trouve nommer.

Deux questions distinctes en découlent, et il vaut la peine de les garder séparées parce que leurs réponses le sont :

1. **Quel backend pilote ce dépôt ?** La clé `forge`.
2. **gwm a-t-il le droit d'envoyer un appel authentifié à cet hôte ?** L'autorisation, et la clé `forge` n'y répond pas.

### autoriser un hôte auto-hébergé

Listez les hôtes avec lesquels vous travaillez dans **votre propre** `~/.config/gwm/config.toml`, chacun avec le backend qui le pilote :

```toml
[forge_hosts]
"gitlab.acme.com" = "gitlab"
"ghe.acme.com"    = "github"
```

Ce fichier n'est jamais livré avec un dépôt, et c'est précisément ce qui en fait une réponse : rien de ce que vous clonez ne peut y ajouter une ligne. Par hôte plutôt qu'une clé unique et globale, pour qu'une boutique faisant tourner à la fois un GitLab auto-hébergé et un GitHub Enterprise soit descriptible en une seule config. La comparaison d'hôte est insensible à la casse.

L'alternative, pour un dépôt ponctuel que vous préférez ne pas ajouter à votre config globale, est de nommer le backend dans le `.gwm.toml` du dépôt puis d'approuver le dépôt :

```toml
# .gwm.toml
forge = "gitlab"
```

```bash
gwm trust add     # dans le dépôt, une fois
```

C'est la même barrière TOFU que celle utilisée par `[[bootstrap.command]]`, et la même décision : `.gwm.toml` est livré avec le dépôt, donc le laisser nommer l'hôte tout seul offrirait à un clone hostile un appel authentifié vers son propre serveur depuis un simple `gwm status`. L'approbation couvre le fichier tel qu'il est à cet instant ; l'éditer change son empreinte et révoque l'approbation. `gwm trust list` montre ce que vous avez approuvé, `gwm trust revoke <origin>` le reprend, et `GWM_ALLOW_BOOTSTRAP=1` contourne la vérification pour les runners CI où personne ne peut répondre.

::: warning Une clé `forge` seule n'autorise rien
`forge = "gitlab"`, où que vous la posiez (dépôt ou global), énonce quel backend vous utilisez. Elle n'énonce pas quels hôtes peuvent recevoir votre jeton, et gwm ne la lit pas comme si c'était le cas. Sans quoi la configuration la plus ordinaire qui soit (une clé, posée une fois, dans une boutique GitLab) autoriserait tous les hôtes de la terre, y compris celui qu'un attaquant aurait placé dans l'`origin` d'un clone. Ce n'est pas hypothétique : avec `GITLAB_HOST` défini, `glab` envoie le `GITLAB_TOKEN` ambiant vers ce qu'il nomme, sous forme d'en-tête `Private-Token`, sans aucun cadrage d'hôte de son côté.
:::

### sur les domaines propres aux éditeurs

Rien de tout cela ne s'applique sur `github.com`, `ghe.com` ou `gitlab.com` : l'hôte énonce déjà quelle forge il fait tourner, aucune autorisation n'est donc nécessaire. La clé `forge` y est libre et l'emporte toujours dans les deux sens : `forge = "github"` force le backend GitHub même sur un remote `gitlab.com`. L'appel atteint un éditeur dans tous les cas, donc le pire scénario est gwm parlant à GitLab avec `gh`.

::: tip
`gwm doctor` sonde la CLI de forge (`gh` / `glab`) **uniquement quand `forge` est définie explicitement** : une clé explicite se lit comme « je parle à cette forge », ce qui rend l'avertissement actionnable. Les dépôts qui n'y souscrivent jamais ne gagnent aucun nouvel avertissement.
:::

## groupes imbriqués

Les slugs GitHub sont toujours `owner/repo`. Un projet GitLab peut être posé à n'importe quelle profondeur de sous-groupes, et gwm conserve le chemin entier :

```
https://gitlab.com/group/sub/deeper/proj.git   → slug `group/sub/deeper/proj`
```

Quand l'origin est un remote SSH, gwm ne peut pas connaître le point d'entrée web, donc `gwm open` et le TUI utilisent l'URL **rapportée par la forge elle-même** (`web_url`) plutôt que la supposition construite localement : le chemin CLI la récupère, le TUI réutilise un statut déjà en cache et retombe sur la supposition hors ligne.

Les URL suivent l'infixe `/-/` de GitLab (`<origin>/<path>/-/issues/42`, `<origin>/<path>/-/merge_requests/61`) et sont enracinées dans le **schéma, l'hôte et le port web** du remote `origin`, si bien que `http://gitlab.acme:8080/g/p.git` produit `http://gitlab.acme:8080/g/p/-/issues/42` plutôt qu'un `https://gitlab.acme/…` reconstruit (et mort). Un port `ssh://host:2222` est l'exception : il adresse sshd, pas l'interface web, donc il est écarté.

## état de la CI

GitHub renvoie un tableau `statusCheckRollup` par vérification. GitLab accroche un **pipeline unique** à la merge request, donc l'overlay des checks CI affiche une seule ligne, `pipeline`, pointant vers la page du pipeline avec sa durée d'exécution. Une granularité par job demanderait une seconde requête par MR et n'est pas implémentée.

Les statuts de pipeline sont mappés ainsi :

| Statut GitLab | Résultat gwm |
|---|---|
| `success`, `skipped` | réussi |
| `failed`, `canceled`, `canceling` | échoué |
| `created`, `waiting_for_resource`, `preparing`, `pending`, `running`, `scheduled`, `manual` | en cours |
| tout le reste | **inconnu** |

La dernière ligne est délibérée. Un statut que gwm ne reconnaît pas, par exemple un nouveau ajouté en amont, est rapporté comme `unknown` et n'agrège **jamais** vers une CI verte. Un fourre-tout qui retomberait sur « succès » rapporterait un pipeline passant qui ne passe pas, et le ferait silencieusement.

`manual` est rangé avec les états en cours plutôt qu'avec `skipped` pour la même raison : un pipeline rapporte `manual` tant qu'il attend un job manuel **bloquant**, il est donc suspendu et peut barrer le merge. Ce n'est pas un succès.

## quelle instance est interrogée

`gh` et `glab` résolvent tous deux l'instance depuis leur **répertoire de travail** quand rien ne l'épingle, et le cwd de gwm n'est pas fiablement le dépôt interrogé : en mode workspace c'est la racine du workspace alors que la ligne appartient à un dépôt enfant. Deux garde-fous, dans cet ordre :

1. **L'enfant est lancé à l'intérieur du dépôt.** Cela fait lire à la CLI le remote de ce dépôt, ce qui est juste pour tout type de remote et respecte votre configuration `gh` / `glab` existante.
2. **`$GITLAB_HOST` / `$GH_HOST` sont épinglés** quand l'origin est une URL `http(s)`, qui énonce le point d'entrée web sans ambiguïté. Un remote SSH ne le fait pas : `https://<hôte-ssh>` est une supposition, et forcer une supposition par-dessus une config CLI qui marche casse des installations qui allaient bien.
3. **Sur GitLab, un origin SSH écarte aussi `--repo`.** Passer un slug fait résoudre `glab` contre son hôte *par défaut*, ce qui annulerait complètement le garde-fou 1 ; ne rien lui donner le laisse lire le remote du dépôt. Les chemins REST suivent la même règle via le substitut `:fullpath` de `glab api`.

Par-dessus ces garde-fous, les **sélecteurs de dépôt hérités sont retirés** de l'enfant : `$GITLAB_REPO`, `$REMOTE_ALIAS` et `$GIT_REMOTE_URL_VAR` (et `$GH_REPO` côté GitHub) surchargent tous le projet sur lequel la CLI agit, et gwm connaît toujours le projet, soit comme un slug soit comme « le dépôt dans lequel je te lance ». Les variables d'*hôte* sont délibérément laissées telles quelles : gwm ne connaît pas toujours l'hôte, et sur un origin SSH votre `$GITLAB_HOST` exporté peut être le seul signal correct disponible.

`gh` est l'exception au garde-fou 3 : `--repo owner/repo` ne porte aucun nom d'hôte et `gh api repos/<slug>/…` inscrit le slug dans le chemin de la requête, donc ni l'un ni l'autre ne peut s'en remettre au répertoire de travail. Son hôte est par conséquent épinglé dès que le slug est connu, y compris sur github.com et y compris sur un origin SSH.

Ce dernier point compte même sur github.com : l'enfant hérite de l'environnement de gwm, donc un `GH_HOST=github.acme.internal` ambiant, chose banale chez les utilisateurs entreprise, redirigerait sinon chaque appel vers un dépôt homonyme sur un autre tenant.

### non pris en charge : les installations sous un préfixe d'URL

GitLab peut être installé sous un chemin (`https://example.com/gitlab`). gwm ne gère **pas** ce cas aujourd'hui : depuis le seul remote, `https://example.com/gitlab/group/proj.git` est indiscernable d'un projet situé à `gitlab/group/proj` sur example.com, donc gwm lit le slug comme `gitlab/group/proj` et chaque appel manque sa cible. Déclarer la racine de l'instance dans `.gwm.toml` corrigerait le problème, mais cette valeur vient d'un fichier **versionné** et alimente l'hôte de la CLI : un dépôt hostile pourrait alors rediriger des appels authentifiés vers son propre serveur. Le faire proprement suppose de le router à travers la [barrière de confiance](/fr/configuration/trust-ledger), ce qui est suivi séparément.

## surcharger le binaire

`$GWM_GLAB` surcharge le programme `glab`, en miroir de `$GWM_GH` côté GitHub. Les deux sont lus une seule fois, sur le thread qui résout la forge, donc la récupération en arrière-plan du TUI ne court jamais après une mutation d'environnement.

## les dates d'échéance de jalon sont sans heure

GitLab stocke la `due_date` d'un jalon comme une **date**, sans heure. Un `due_on` déclaré avec une heure autre que la fin de journée serait écrit comme date nue, relu comme fin de journée, et ne comparerait donc jamais égal : le jalon apparaîtrait comme modifié pour toujours et serait réécrit à chaque push. `gwm milestones push` refuse une telle valeur en nommant la cause plutôt que de boucler. Déclarez un `due_on = "2026-07-15"` nu.

## les labels de groupe ne sont pas des labels de projet

`gwm labels list / push` se cadre sur le projet. Le point d'entrée des labels de projet de GitLab renvoie aussi les labels de **groupe** ancêtres par défaut, que le diff lirait comme des extras : `--prune` proposerait alors de supprimer des labels que le projet ne possède pas. La requête ne demande que les labels de projet, et tout label de groupe qui passerait quand même (une instance auto-gérée plus ancienne ignorant le paramètre) est filtré sur `is_project_label`.

## une mise en garde sur les corps d'issue / MR

`glab` n'a pas de `--body-file`, donc un corps rendu voyage en ligne dans `--description`. gwm masque cette valeur dans la transcription des Command Logs, mais elle reste visible dans l'argv du processus (`ps`) pendant la durée de l'appel. Celle-là relève de la surface CLI de `glab`, pas de quelque chose que gwm peut refermer.

## ce qui n'est pas encore traduit

La forge expose le bon nom (`PR` / `MR`), et les chaînes construites à l'exécution l'utilisent : la ligne de lien de `gwm status`, la sortie de progression de `gwm pr` / `gwm review`, et le message de statut « no MR linked » du TUI.

Affichent encore « PR » sur GitLab :

- les libellés de rendu **statiques** et les indices de touches du TUI (titres de la barre latérale, pied de page, overlay d'aide),
- le libellé de champ `pr:` dans la sortie humaine de `gwm status`, qui reste en place délibérément : c'est une clé de sortie que les scripts grepent, et `--json` gèle le même nom.

Balayer les chaînes de rendu statiques est un changement à part.