Jean-Philippe Fleury

informatique libre • art libre

Gestion de projet avec Git

Ce qui suit illustre une méthode de travail avec Git.

Étapes

Création du projet

cd /tmp
mkdir projet-demo
cd projet-demo
git init
touch version.txt
git add .
git commit -m "Début de versionnage de projet-demo"
git remote add origin git@serveur.ext:utilisateur/projet-demo.git
git push origin master

Création d'une branche de développement

La branche de développement est utilisée pour préparer la prochaine version. C'est donc sur cette branche que le travail s'effectue. La branche master contient toujours le code de la dernière version stable publiée.

git checkout -b dev
touch fichier1
git add .
git commit -m "Message fichier1"
git push origin dev

Création d'une branche topique

Une branche topique, créée à partir de la branche dev, est utile pour travailler sur une nouvelle fonctionnalité, qui pourra être ajoutée plus tard dans la branche de développement.

git checkout -b fonctionnalite1
touch fonctionnalite1-fichier1
git add .
git commit -m "Message fonctionnalite1-fichier1"
touch fonctionnalite1-fichier2
git add .
git commit -m "Message fonctionnalite1-fichier2"

Continuation du travail sur la branche de développement

git checkout dev
touch fichier2
git add .

# On met de côté les modifications en cours pour travailler sur autre chose.
git stash
touch fichier3
git add .
git commit -m "Message fichier3"

# On retourne travailler sur les changements précédemment mis de côté.
git stash pop
git status
git add .
git commit -m "Message fichier2"
git push origin dev

Travail sur le même projet à partir d'un autre emplacement

git clone git@serveur.ext:utilisateur/projet-demo.git ../autre-emplacement-projet-demo
cd ../autre-emplacement-projet-demo
git checkout dev
touch fichier4
git add .
git commit -m "Message fichier4"
git push origin dev

Retour au premier emplacement

Pour mettre à jour une branche locale, il est souvent conseillé d'utiliser git pull. Cette commande effectue en réalité deux actions:

  • git fetch, pour récupérer les modifications présentes à l'emplacement distant;
  • git merge, pour intégrer ces modifications dans la branche locale courante.

Si la branche locale ne contient pas de modifications inconnues de l'emplacement distant, git merge va simplement déplacer la référence HEAD vers la dernière révision. Exemple (les graphiques ont été générés avec gitg):

  • Avant git merge:

    Historique du projet avant la fusion des modifications présentes 
à l'emplacement distant

  • Après git merge:

    La référence «HEAD» a été déplacée vers la dernière révision

Dans le cas contraire, les deux branches divergent et une nouvelle révision va être créée pour contenir les modifications de la fusion. Cette révision va bien sûr apparaître dans l'historique. Exemple:

  • Avant git merge:

    Historique du projet avant la fusion des modifications présentes 
à l'emplacement distant

  • Après git merge:

    La fusion des modifications a été effectuée et une nouvelle révision a été créée

Avec le temps, l'historique va en être considérablement alourdi. Pour cette raison, j'utilise git rebase, qui permet de réappliquer les modifications locales à la suite de la dernière révision de la branche distante. L'historique demeure donc linéaire. Exemple:

  • Avant git rebase:

    Historique du projet avant la mise à jour avec l'emplacement distant

  • Après git rebase:

    La branche locale a été mise à jour avec l'emplacement distant à l'aide d'une recombinaison des révisions

Voir l'article Rebasing Merge Commits in Git concernant l'importance d'utiliser le paramètre -p (--preserve-merges) avec git rebase.

cd ../projet-demo
git checkout dev
git fetch origin
git rebase -p origin/dev
touch fichier5
git add .
git commit -m "Message fichier5"
echo "1.0" > version.txt
git add .
git commit -m "Mise à jour du numéro de version (1.0)"
git push origin dev

Publication de la première version

Lorsque la branche de développement reflète l'état désiré pour la prochaine version du logiciel, elle est fusionnée dans la branche master, et une étiquette est créée avec le numéro de version.

Le paramètre --no-ff est utilisé lors de la fusion pour s'assurer que les révisions de la branche de développement soient regroupées dans l'historique et facilement distingables des autres révisions.

Aussi, le paramètre -s utilisé avec git tag permet de créer une étiquette signée. Utiliser le paramètre -a pour créer un objet tag sans le signer.

git checkout master
git fetch origin
git rebase -p origin/master
git merge --no-ff dev
git tag -s 1.0 -m "Message 1.0"
git push --tags origin master

Développement de la deuxième version

git checkout dev
touch fichier6
git add .
git commit -m "Message fichier6"
git push origin dev

Continuation du développement de la fonctionnalité 1

Noter l'utilisation de git rebase pour ajouter dans la branche topique les modifications de la branche de développement.

git checkout fonctionnalite1
git rebase -p dev
touch fonctionnalite1-fichier3
git add .
git commit -m "Message fonctionnalite1-fichier3"

Intégration de la fonctionnalité 1

Lors qu'une foncitonnalité est terminée, la branche topique correspondante est fusionnée à la branche de développement, et est ensuite supprimée. Noter l'utilisation du paramètre --no-ff lors de la fusion.

git checkout dev
git fetch origin
git rebase -p origin/dev
git checkout fonctionnalite1
git rebase -p dev
git checkout dev
git merge --no-ff fonctionnalite1
git branch -d fonctionnalite1

Continuation et fin du développement de la deuxième version

touch fichier7
git add .
git commit -m "Message fichier7"
echo "2.0" > version.txt
git add .
git commit -m "Mise à jour du numéro de version (2.0)"
git push origin dev

Publication de la deuxième version

git checkout master
git fetch origin
git rebase -p origin/master
git merge --no-ff dev
git tag -s 2.0 -m "Message 2.0"
git push --tags origin master

Historique

L'historique résultant de ce projet de démonstration est le suivant:

Alias

Notes:

  • Pour utiliser un alias, passer son nom comme argument de git. Par exemple, un alias créé comme ceci:

    git config --global alias.NOM 'CODE DE L'ALIAS'
    

    pourra être utilisé ainsi:

    git NOM
    
  • Pour que l'alias soit actif seulement pour le projet courant, enlever le paramètre --global.


Voici des suggestions d'alias:

  • Afficher le nom de la branche courante:

    git config --global alias.b '!branche=$(git symbolic-ref HEAD 2>&-) && echo "${branche#refs/heads/}"'
    
  • Mise à jour de la branche locale courante avec les dernières révisions distantes:

    git config --global alias.fr '!git fetch origin && git rebase -p origin/$(git b)'
    
  • Mettre à jour la branche distante avec les dernières révisions locales:

    git config --global alias.po '!git push origin $(git b)'
    
  • Mettre à jour la branche distante avec les dernières révisions locales (étiquettes comprises):

    git config --global alias.pto '!git push --tags origin $(git b)'
    
  • Fusionner la branche topique courante dans une autre précisée en argument:

    git config --global alias.fbt '!fbt() { \
    if test -z "$1"; then echo "Veuillez préciser la branche dans laquelle se placer avant de fusionner la branche topique courante ($(git b))."; exit 1; fi && \
    git checkout "$1" && \
    git fr && \
    git checkout @{-2} && \
    git rebase -p "$1" && \
    git checkout "$1" && \
    git merge --no-ff @{-1}; \
    }; fbt'
    
  • Publier une nouvelle version en fusionnant la branche dev dans la branche master (à ajuster selon sa méthode de travail ou celle du projet courant):

    git config --global alias.v '!v() { \
    if test -z "$1"; then echo "Veuillez préciser le numéro ou le nom de la nouvelle version."; exit 1; fi && \
    version=$1 && \
    hui=$(date +%Y-%m-%d) && \
    echo "$version ($hui)" > doc/version.txt && \
    git add doc/version.txt && \
    if test -f doc/LISEZ-MOI.mkd; then perl -0777 -pi -e "s/^(## Développement\n\nDernière version: )[^\n]+/\${1}$version ($hui)/m" doc/LISEZ-MOI.mkd && \
    git add doc/LISEZ-MOI.mkd; fi && \
    if test -f doc/README.mkd; then perl -0777 -pi -e "s/^(## Development\n\nLast version: )[^\n]+/\${1}$version ($hui)/m" doc/README.mkd && \
    git add doc/README.mkd; fi && \
    git commit -m "Mise à jour du numéro de version ($version)" && \
    git push origin dev && \
    git checkout master && \
    git fr && \
    git merge --no-ff dev && \
    git tag -s "$version" -m "Version $version" && \
    git pto && \
    git checkout dev && \
    versionDev=$version+ && \
    echo "$versionDev" > doc/version.txt && \
    git add doc/version.txt && \
    git commit -m "Passage à un numéro de version de développement ($versionDev)" && \
    git po; \
    }; v'
    
  • Publier une nouvelle version en fusionnant une branche topique de développement (par exemple dev-v2) dans la branche principale correspondante (par exemple v2) [à ajuster selon sa méthode de travail ou celle du projet courant]:

    git config --global alias.vbt '!vbt() { \
    if test -z "$1"; then echo "Veuillez préciser le numéro ou le nom de la nouvelle version."; exit 1; fi && \
    if test -z "$2"; then echo "Veuillez préciser la branche dans laquelle se placer avant de fusionner la branche topique courante ($(git b))."; exit 1; fi && \
    version=$1 && \
    bt=$2 && \
    btdev=$(git b) && \
    hui=$(date +%Y-%m-%d) && \
    echo "$version ($hui)" > doc/version.txt && \
    git add doc/version.txt && \
    if test -f doc/LISEZ-MOI.mkd; then perl -0777 -pi -e "s/^(## Développement\n\nDernière version: )[^\n]+/\${1}$version ($hui)/m" doc/LISEZ-MOI.mkd && \
    git add doc/LISEZ-MOI.mkd; fi && \
    if test -f doc/README.mkd; then perl -0777 -pi -e "s/^(## Development\n\nLast version: )[^\n]+/\${1}$version ($hui)/m" doc/README.mkd && \
    git add doc/README.mkd; fi && \
    git commit -m "Mise à jour du numéro de version ($version)" && \
    git push origin "$btdev" && \
    git checkout "$bt" && \
    git fr && \
    git merge --no-ff "$btdev" && \
    git tag -s "$version" -m "Version $version" && \
    git pto && \
    git checkout "$btdev" && \
    versionDev=$version+ && \
    echo "$versionDev" > doc/version.txt && \
    git add doc/version.txt && \
    git commit -m "Passage à un numéro de version de développement ($versionDev)" && \
    git po; \
    }; vbt'
    

Haut