Dépôts distants (remotes)#

Git est un système de contrôle de version distribué : chaque clone d’un dépôt contient l’intégralité de l’historique du projet. Il n’existe pas de serveur central obligatoire — chaque copie est un dépôt à part entière, capable de fonctionner de manière autonome. Cette architecture confère à Git une robustesse et une flexibilité remarquables, mais elle soulève une question fondamentale : comment deux dépôts indépendants échangent-ils des commits ?

C’est précisément le rôle des remotes (dépôts distants). Dans ce chapitre, nous verrons comment déclarer des dépôts distants, récupérer leurs modifications, envoyer les nôtres, et synchroniser efficacement notre travail avec celui d’autres développeurs. Nous utiliserons des dépôts bare locaux pour simuler un serveur distant, ce qui permet d’expérimenter toutes les commandes sans connexion réseau.

Qu’est-ce qu’un remote ?#

Définition 30 (Remote (dépôt distant))

Un remote est une référence nommée vers un autre dépôt Git, généralement hébergé sur un serveur (GitHub, GitLab, Bitbucket, ou un serveur personnel). Chaque remote possède un nom et une URL. Par convention, le remote créé automatiquement lors d’un git clone s’appelle origin : il pointe vers le dépôt depuis lequel vous avez cloné.

Préparons un environnement de démonstration avec un dépôt bare (sans répertoire de travail) qui jouera le rôle de serveur distant :

%%bash
cd /tmp && rm -rf demo-remote demo-remote-bare demo-remote-bob
git init --bare demo-remote-bare
git -c protocol.file.allow=always clone demo-remote-bare demo-remote
cd demo-remote
git config user.email "demo@example.com" && git config user.name "Demo"
echo "# Mon projet" > README.md
git add README.md
git commit -m "Initial commit"
git push origin main
Dépôt Git vide initialisé dans /tmp/demo-remote-bare/
Clonage dans 'demo-remote'...
avertissement : Vous semblez avoir cloné un dépôt vide.
fait.
[main (commit racine) bfa32c4] Initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 RE
ADME.md
To /tmp/demo-remote-bare
 * [new branch]      main -> main

Listons les remotes configurés avec git remote -v :

%%bash
cd /tmp/demo-remote
git remote -v
origin	/tmp/demo-remote-bare (fetch)
origin	/tmp/demo-remote-bare (push)

On voit deux lignes pour origin : une pour le fetch (téléchargement) et une pour le push (envoi). Elles pointent généralement vers la même URL, mais peuvent différer dans des configurations avancées.

Pour ajouter un remote supplémentaire, on utilise git remote add :

%%bash
cd /tmp/demo-remote
git remote add upstream /tmp/demo-remote-bare
git remote -v
origin	/tmp/demo-remote-bare (fetch)
origin	/tmp/demo-remote-bare (push)
upstream	/tmp/demo-remote-b
are (fetch)
upstream	/tmp/demo-remote-bare (push)

Remarque 28

Un dépôt peut avoir plusieurs remotes. C’est un patron courant dans les projets open source : origin pointe vers votre fork personnel, tandis qu”upstream pointe vers le dépôt original du projet. Cela permet de récupérer les dernières modifications du projet officiel tout en poussant vos contributions sur votre fork.

Fetch, Pull, Push#

La communication entre un dépôt local et un dépôt distant repose sur trois opérations fondamentales. Comprendre la différence entre elles est essentiel pour maîtriser Git.

Définition 31 (git fetch)

git fetch <remote> télécharge les objets (commits, arbres, blobs) et les références (branches, tags) depuis un dépôt distant, sans modifier votre répertoire de travail ni vos branches locales. Les informations récupérées sont stockées dans des branches de suivi distant (remote-tracking branches), comme origin/main. C’est une opération de lecture pure : elle met à jour votre vision du dépôt distant sans toucher à votre travail en cours.

Définition 32 (git pull)

git pull <remote> <branche> est un raccourci qui exécute successivement git fetch puis git merge (ou git rebase si configuré ainsi). Il télécharge les nouvelles modifications distantes et les intègre immédiatement dans votre branche courante. C’est pratique, mais cela peut provoquer des fusions inattendues si votre branche locale a divergé.

Définition 33 (git push)

git push <remote> <branche> envoie vos commits locaux vers le dépôt distant. Il met à jour la branche distante pour qu’elle pointe vers le même commit que votre branche locale. Le push sera rejeté si le dépôt distant contient des commits que vous n’avez pas encore intégrés (pour éviter de perdre du travail).

Illustrons ces opérations. Ajoutons d’abord quelques commits à notre dépôt :

%%bash
cd /tmp/demo-remote
echo "Ligne 1" >> README.md
git add README.md && git commit -m "Ajout ligne 1"
echo "Ligne 2" >> README.md
git add README.md && git commit -m "Ajout ligne 2"
[main 1dbbd22] Ajout ligne 1
 1 file changed, 1 insertion(+)
[main 65b5df0] Ajout ligne 2
 1 file changed, 1 insertion(+)

Envoyons ces commits vers le dépôt distant :

%%bash
cd /tmp/demo-remote
git push origin main
To /tmp/demo-remote-bare
   bfa32c4..65b5df0  main -> main

Simulons maintenant un deuxième développeur en clonant le dépôt distant :

%%bash
cd /tmp && rm -rf demo-remote-bob
git -c protocol.file.allow=always clone demo-remote-bare demo-remote-bob
cd demo-remote-bob
git config user.email "bob@example.com" && git config user.name "Bob"
git log --oneline
Clonage dans 'demo-remote-bob'...
fait.
65b5df0 Ajout ligne 2
1dbbd22 Ajout ligne 1
bfa32c4 Initial commit

Bob voit les trois commits. Maintenant, le premier développeur ajoute un commit :

%%bash
cd /tmp/demo-remote
echo "Ligne 3" >> README.md
git add README.md && git commit -m "Ajout ligne 3"
git push origin main
[main dc95a6b] Ajout ligne 3
 1 file changed, 1 insertion(+)
To /tmp/demo-remote-bare
   65b5df0..dc95a6b  main -> main

Bob récupère les modifications avec git fetch, puis inspecte l’état :

%%bash
cd /tmp/demo-remote-bob
git fetch origin
git log --oneline --all
Depuis /tmp/demo-remote-bare
   65b5df0..dc95a6b  main       -> origin/main
dc95a6b Ajout ligne 3
65b5df0 Ajout ligne 2
1dbbd22 Ajout ligne 1
bfa32c4 Initial commit

Après le fetch, Bob voit le nouveau commit dans origin/main, mais sa branche locale main n’a pas bougé. Il peut maintenant choisir de fusionner ou de rebaser :

%%bash
cd /tmp/demo-remote-bob
git merge origin/main
git log --oneline
Mise à jour 65b5df0..dc95a6b
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
dc95a6b Ajout ligne 3
65b5df0 Ajout ligne 2
1dbbd22 Ajout ligne 1
bfa32c4 Initial commit

Remarque 29

Il est généralement préférable d’utiliser git fetch suivi d’un merge ou rebase explicite plutôt que git pull directement. Cela vous laisse le temps d”inspecter les modifications distantes avant de les intégrer. Si vous préférez néanmoins git pull, l’option --rebase (git pull --rebase) effectue un rebase au lieu d’une fusion, produisant un historique linéaire plus propre.

Visualisation des échanges entre dépôts#

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as patches

fig, ax = plt.subplots(figsize=(14, 8))
ax.set_xlim(-0.5, 14)
ax.set_ylim(-1, 9)
ax.axis('off')
ax.set_title("Échanges entre dépôt local et dépôt distant", fontsize=14, fontweight='bold', pad=15)

# --- Couleurs ---
c_remote = '#e74c3c'
c_local = '#2980b9'
c_commit = '#ecf0f1'
c_border = '#2c3e50'

# --- Dépôt distant (en haut) ---
remote_box = patches.FancyBboxPatch(
    (1, 5.5), 5.5, 2.8,
    boxstyle="round,pad=0.2", linewidth=2.5,
    edgecolor=c_remote, facecolor=c_remote, alpha=0.1
)
ax.add_patch(remote_box)
remote_border = patches.FancyBboxPatch(
    (1, 5.5), 5.5, 2.8,
    boxstyle="round,pad=0.2", linewidth=2.5,
    edgecolor=c_remote, facecolor='none'
)
ax.add_patch(remote_border)
ax.text(3.75, 8.0, "Dépôt distant (origin)", ha='center', va='center',
        fontsize=12, fontweight='bold', color=c_remote)

# Commits dans le dépôt distant
remote_commits = [(2.0, 6.6), (3.2, 6.6), (4.4, 6.6), (5.6, 6.6)]
for i, (cx, cy) in enumerate(remote_commits):
    circ = plt.Circle((cx, cy), 0.3, facecolor='#fadbd8', edgecolor=c_remote, linewidth=1.5, zorder=5)
    ax.add_patch(circ)
    ax.text(cx, cy, f'C{i+1}', ha='center', va='center', fontsize=8, fontweight='bold', zorder=6)
    if i > 0:
        px, py = remote_commits[i-1]
        ax.annotate('', xy=(cx - 0.3, cy), xytext=(px + 0.3, py),
                    arrowprops=dict(arrowstyle='->', color=c_remote, lw=1.5))

ax.text(5.6, 7.15, "main", ha='center', va='center', fontsize=9,
        fontweight='bold', color='white',
        bbox=dict(boxstyle='round,pad=0.15', facecolor=c_remote, edgecolor='none'))

# --- Dépôt local (en bas) ---
local_box = patches.FancyBboxPatch(
    (1, 0.5), 12, 4.2,
    boxstyle="round,pad=0.2", linewidth=2.5,
    edgecolor=c_local, facecolor=c_local, alpha=0.1
)
ax.add_patch(local_box)
local_border = patches.FancyBboxPatch(
    (1, 0.5), 12, 4.2,
    boxstyle="round,pad=0.2", linewidth=2.5,
    edgecolor=c_local, facecolor='none'
)
ax.add_patch(local_border)
ax.text(7.0, 4.4, "Dépôt local", ha='center', va='center',
        fontsize=12, fontweight='bold', color=c_local)

# Commits locaux (C1-C3 partagés, puis commit local C5)
local_commits = [(2.0, 2.2), (3.2, 2.2), (4.4, 2.2)]
for i, (cx, cy) in enumerate(local_commits):
    circ = plt.Circle((cx, cy), 0.3, facecolor='#d6eaf8', edgecolor=c_local, linewidth=1.5, zorder=5)
    ax.add_patch(circ)
    ax.text(cx, cy, f'C{i+1}', ha='center', va='center', fontsize=8, fontweight='bold', zorder=6)
    if i > 0:
        px, py = local_commits[i-1]
        ax.annotate('', xy=(cx - 0.3, cy), xytext=(px + 0.3, py),
                    arrowprops=dict(arrowstyle='->', color=c_local, lw=1.5))

# Commit local supplémentaire
cx_local, cy_local = 5.6, 2.2
circ_local = plt.Circle((cx_local, cy_local), 0.3, facecolor='#aed6f1', edgecolor=c_local, linewidth=2, zorder=5)
ax.add_patch(circ_local)
ax.text(cx_local, cy_local, 'C5', ha='center', va='center', fontsize=8, fontweight='bold', zorder=6)
ax.annotate('', xy=(cx_local - 0.3, cy_local), xytext=(4.4 + 0.3, 2.2),
            arrowprops=dict(arrowstyle='->', color=c_local, lw=1.5))

ax.text(5.6, 2.85, "main", ha='center', va='center', fontsize=9,
        fontweight='bold', color='white',
        bbox=dict(boxstyle='round,pad=0.15', facecolor=c_local, edgecolor='none'))

# Branche de suivi distant origin/main (dans le dépôt local)
rt_x, rt_y = 4.4, 1.2
ax.annotate('', xy=(4.4, 2.2 - 0.3), xytext=(rt_x, rt_y + 0.15),
            arrowprops=dict(arrowstyle='->', color=c_remote, lw=1.2, linestyle='dashed'))
ax.text(rt_x, rt_y - 0.15, "origin/main", ha='center', va='center', fontsize=9,
        fontweight='bold', color='white',
        bbox=dict(boxstyle='round,pad=0.15', facecolor=c_remote, edgecolor='none', alpha=0.7))

# --- Flèches fetch et push ---
# fetch : distant -> local
ax.annotate('',
    xy=(8.5, 4.2), xytext=(8.5, 5.5),
    arrowprops=dict(arrowstyle='->', color='#27ae60', lw=3,
                    connectionstyle='arc3,rad=0.0'))
ax.text(9.7, 4.85, "git fetch\n(télécharge)", ha='center', va='center',
        fontsize=10, fontweight='bold', color='#27ae60',
        bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor='#27ae60', alpha=0.95))

# push : local -> distant
ax.annotate('',
    xy=(11.0, 5.5), xytext=(11.0, 4.2),
    arrowprops=dict(arrowstyle='->', color='#8e44ad', lw=3,
                    connectionstyle='arc3,rad=0.0'))
ax.text(12.2, 4.85, "git push\n(envoie)", ha='center', va='center',
        fontsize=10, fontweight='bold', color='#8e44ad',
        bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor='#8e44ad', alpha=0.95))

plt.show()
_images/800e0d7e3d755b96d779205d61664674bf9208fcb14532618918b6d99f9e105d.png

Ce diagramme illustre les mécanismes essentiels. Le dépôt local contient à la fois ses propres branches (main) et des branches de suivi distant (origin/main) qui reflètent l’état du dépôt distant lors du dernier fetch. Le push envoie les commits locaux vers le distant, tandis que le fetch télécharge les nouveaux commits distants sans modifier les branches locales.

Branches de suivi (tracking branches)#

Définition 34 (Branche de suivi (tracking branch))

Une branche de suivi est une branche locale configurée pour suivre une branche distante spécifique. Cette association permet à Git de savoir automatiquement depuis quel remote et quelle branche effectuer les opérations git pull et git push, sans avoir à les spécifier explicitement. Par exemple, si main suit origin/main, un simple git push suffit à envoyer les commits vers origin/main.

Vérifions les informations de suivi de nos branches :

%%bash
cd /tmp/demo-remote
git branch -vv
* main dc95a6b [origin/main] Ajout ligne 3

L’option -vv affiche, pour chaque branche locale, la branche distante qu’elle suit, ainsi que l’état de synchronisation (en avance, en retard, ou à jour).

Pour associer manuellement une branche locale à une branche distante, on utilise git branch -u :

%%bash
cd /tmp/demo-remote
git branch -u origin/main
git branch -vv
la branche 'main' est paramétrée pour suivre 'origin/main'.
* main dc95a6b [origin/main] Ajout ligne 3

Lorsque l’on crée une nouvelle branche et qu’on la pousse pour la première fois, l’option -u de git push crée la branche distante et établit le suivi en une seule commande :

%%bash
cd /tmp/demo-remote
git checkout -b feature-x
echo "Nouvelle fonctionnalité" > feature.txt
git add feature.txt
git commit -m "Ajout de feature-x"
git push -u origin feature-x
git branch -vv
Basculement sur la nouvelle branche 'feature-x'
[feature-x 02b7144] Ajout de feature-x
 1 file changed, 1 insertion(+)
 create mode 100644 feature.t
xt
To /tmp/demo-remote-bare
 * [new branch]      feature-x -> feature-x
la branche 'feature-x' est paramétrée pour suivre 'origin/feature-x'.
* feature-x 02b7144 [origin/feature-x] Ajout de feature-x
  main      dc95a6b [origin/main] Ajout li
gne 3

Remarque 30

Lorsque vous exécutez git clone, Git configure automatiquement la branche par défaut (généralement main) pour suivre origin/main. C’est pourquoi git push et git pull fonctionnent immédiatement après un clone, sans configuration supplémentaire. Pour les branches créées localement, il faut établir le suivi manuellement avec git push -u lors du premier push, ou avec git branch -u après coup.

Revenons sur main pour la suite du chapitre :

%%bash
cd /tmp/demo-remote
git checkout main
Basculement sur la branche 'main'
Votre branche est à jour avec 'origin/main'.

Protocoles de communication#

Git supporte plusieurs protocoles pour communiquer avec les dépôts distants. Les deux plus courants sont HTTPS et SSH.

HTTPS utilise des URL de la forme https://github.com/user/repo.git. Il fonctionne à travers la plupart des pare-feux et proxys d’entreprise. L’authentification se fait par nom d’utilisateur et jeton d’accès personnel (les mots de passe classiques ne sont plus acceptés par la plupart des hébergeurs). L’inconvénient principal est que Git peut demander les identifiants à chaque opération, sauf si l’on configure un credential helper.

SSH utilise des URL de la forme git@github.com:user/repo.git. L’authentification repose sur une paire de clés cryptographiques (clé publique déposée sur le serveur, clé privée conservée sur votre machine). Une fois configurée, aucune saisie de mot de passe n’est nécessaire.

Voici comment vérifier le protocole utilisé par un remote existant :

%%bash
cd /tmp/demo-remote
git remote -v
origin	/tmp/demo-remote-bare (fetch)
origin	/tmp/demo-remote-bare (push)
upstream	/tmp/demo-remote-b
are (fetch)
upstream	/tmp/demo-remote-bare (push)

Pour changer le protocole d’un remote existant, on utilise git remote set-url :

# Passer de HTTPS à SSH
git remote set-url origin git@github.com:user/repo.git

# Passer de SSH à HTTPS
git remote set-url origin https://github.com/user/repo.git

Remarque 31

Pour un usage régulier, SSH est recommandé : une fois la clé configurée, toutes les opérations sont transparentes et sans saisie de mot de passe. HTTPS est plus simple pour un clone ponctuel ou dans des environnements où le port SSH (22) est bloqué. Sur GitHub, la commande gh auth setup-git configure automatiquement l’authentification HTTPS via le CLI GitHub.

Synchroniser son travail#

Dans un projet collaboratif, la synchronisation est une opération quotidienne. Le workflow recommandé se décompose en quatre étapes : fetch, inspecter, intégrer, push.

Exemple 9 (Scénario collaboratif : Alice et Bob)

Illustrons un scénario complet de collaboration. Alice et Bob travaillent sur le même dépôt.

Étape 1. Alice ajoute un fichier et pousse ses modifications vers origin/main.

Étape 2. Bob, qui n’a pas encore récupéré le travail d’Alice, crée un commit de son côté.

Étape 3. Bob exécute git fetch origin pour télécharger les nouveaux commits, inspecte le graphe avec git log --all --graph, constate que sa branche locale et origin/main ont divergé, puis exécute git rebase origin/main pour replacer son commit après celui d’Alice.

Étape 4. Bob pousse son travail avec git push origin main. L’historique est linéaire et propre.

%%bash
# Alice travaille dans demo-remote (elle représente le premier développeur)
cd /tmp/demo-remote
echo "Contribution d'Alice" > alice.txt
git add alice.txt
git commit -m "Alice : ajout de alice.txt"
git push origin main
[main a5a0701] Alice : ajout de alice.txt
 1 file changed, 1 insertion(+)
 create mode 100644 alice.
txt
To /tmp/demo-remote-bare
   dc95a6b..a5a0701  main -> main
%%bash
# Bob a travaillé de son côté sans avoir récupéré les changements d'Alice
cd /tmp/demo-remote-bob
echo "Contribution de Bob" > bob.txt
git add bob.txt
git commit -m "Bob : ajout de bob.txt"
[main 23f02b1] Bob : ajout de bob.txt
 1 file changed, 1 insertion(+)
 create mode 100644 bob.txt

Bob veut maintenant pousser son travail, mais il doit d’abord récupérer les modifications d’Alice :

%%bash
cd /tmp/demo-remote-bob
# Étape 1 : récupérer les modifications distantes
git fetch origin

# Étape 2 : inspecter l'état
echo "=== État des branches ==="
git log --oneline --all --graph
Depuis /tmp/demo-remote-bare
   dc95a6b..a5a0701  main       -> origin/main
 * [nouvelle branche] feature-x  -> origin/feature-x
=== État des branches ===
* 23f02b1 Bob : ajout de bob.txt
| * a5a0701 Alice : ajout de alice.txt
|/  
| * 02b7144 Ajout de fe
ature-x
|/  
* dc95a6b Ajout ligne 3
* 65b5df0 Ajout ligne 2
* 1dbbd22 Ajout ligne 1
* bfa32c4 Initial commit
%%bash
cd /tmp/demo-remote-bob
# Étape 3 : intégrer par rebase (historique linéaire)
git rebase origin/main

# Étape 4 : pousser
git push origin main

echo "=== Historique final ==="
git log --oneline
Rebasage (1/1)
Rebasage et mise à jour de refs/heads/main avec succès.
To /tmp/demo-remote-bare
   a5a0701..7ee99d1  main -> main
=== Historique final ===
7ee99d1 Bob : ajout de bob.txt
a5a0701 Alice : ajout de alice.txt
dc95a6b Ajout ligne 3
65b5df0 Ajout ligne 2
1dbbd22 Ajout ligne 1
bfa32c4 Initial commit

L’historique de Bob est maintenant linéaire : son commit apparaît après celui d’Alice, comme s’il avait travaillé séquentiellement.

Le push forcé sécurisé#

Il arrive parfois qu’un rebase local réécrive des commits déjà poussés. Dans ce cas, un git push classique est rejeté car l’historique distant et l’historique local ont divergé. La solution sécurisée est git push --force-with-lease :

%%bash
cd /tmp/demo-remote-bob
# Simulons une réécriture : modifier le dernier message de commit
git commit --amend -m "Bob : ajout de bob.txt (corrigé)"
# Le push normal échouerait ici car l'historique a été réécrit
# git push --force-with-lease est la solution sécurisée
git push --force-with-lease origin main
[main 307138c] Bob : ajout de bob.txt (corrigé)
 Date: Sun Mar 15 21:25:25 2026 +0100
 1 file chang
ed, 1 insertion(+)
 create mode 100644 bob.txt
To /tmp/demo-remote-bare
 + 7ee99d1...307138c main -> main (forced update)

Remarque 32

git push --force-with-lease est une alternative sécurisée à git push --force. La différence est cruciale : --force écrase inconditionnellement la branche distante, même si d’autres développeurs ont poussé des commits entre-temps. --force-with-lease vérifie d’abord que la branche distante n’a pas changé depuis votre dernier fetch : si quelqu’un a poussé de nouveaux commits, le push est refusé, vous protégeant ainsi contre la perte de travail. En règle générale, ne jamais utiliser --force sur une branche partagée ; préférer systématiquement --force-with-lease.

Résumé#

Le tableau suivant récapitule les commandes essentielles pour travailler avec des dépôts distants :

Commande

Description

git remote -v

Lister les remotes configurés avec leurs URL

git remote add <nom> <url>

Ajouter un nouveau remote

git remote remove <nom>

Supprimer un remote

git remote set-url <nom> <url>

Modifier l’URL d’un remote

git fetch <remote>

Télécharger les objets et refs sans modifier le travail local

git pull <remote> <branche>

Fetch + merge (ou rebase) en une commande

git pull --rebase

Fetch + rebase au lieu de merge

git push <remote> <branche>

Envoyer les commits locaux vers le remote

git push -u <remote> <branche>

Push et configurer le suivi en même temps

git push --force-with-lease

Push forcé sécurisé (refuse si le remote a changé)

git branch -vv

Afficher les branches locales et leurs branches de suivi

git branch -u <remote>/<branche>

Configurer le suivi pour la branche courante

Remarque 33

La maîtrise des dépôts distants marque le passage d’un usage solo de Git à un usage collaboratif. Les concepts clés à retenir sont : les remotes ne sont que des références nommées vers d’autres dépôts ; fetch est une opération de lecture sans risque ; push et pull modifient respectivement le dépôt distant et le dépôt local. Dans le chapitre suivant, nous verrons comment organiser la collaboration au sein d’une équipe grâce aux workflows (centralized, feature branch, Gitflow, forking workflow).