Pull request workflow

The so-called « PR workflow » used by Godot is common to many projects using Git, and should be familiar to veteran free software contributors. The idea is that only a small number (if any) commit directly to the master branch. Instead, contributors fork the project (i.e. create a copy of it, which they can modify as they wish), and then use the GitHub interface to request a pull from one of their fork’s branches to one branch of the original (often named upstream) repository.

The resulting pull request (PR) can then be reviewed by other contributors, which might approve it, reject it, or most often request that modifications be done. Once approved, the PR can then be merged by one of the core developers, and its commit(s) will become part of the target branch (usually the master branch).

We will go together through an example to show the typical workflow and associated Git commands. But first, let’s have a quick look at the organisation of Godot’s Git repository.

Git source repository

The repository on GitHub is a Git code repository together with an embedded issue tracker and PR system.

Note

Si vous participer à la documentation, son dépôt peut être trouvé ici.

The Git version control system is the tool used to keep track of successive edits to the source code - to contribute efficiently to Godot, learning the basics of the Git command line is highly recommended. There exist some graphical interfaces for Git, but they usually encourage users to take bad habits regarding the Git and PR workflow, and we therefore recommend not to use them. In particular, we advise not to use GitHub’s online editor for code contributions (although it’s tolerated for small fixes or documentation changes) as it enforces one commit per file and per modification, which quickly leads to PRs with an unreadable Git history (especially after peer review).

Voir aussi

The first sections of Git’s « Book » are a good introduction to the tool’s philosophy and the various commands you need to master in your daily workflow. You can read them online on the Git SCM website.

Les branches sur le dépôt Git sont organisées de cette manière :

  • La branche master est celle ou se déroule le développement de la prochaine version majeure. En tant que branche de développement, elle peut être instable et n’est pas destinée à être utilisée en production. C’est ici que les Pull Requests devraient être faites en priorité.
  • Les branches stables sont nommées d’après leur version, par exemple 3.1 et 2.1. Elles sont utilisées pour le rétro-portage des corrections de bugs et des améliorations de la branche master vers la version stable actuellement maintenue (par exemple 3.1.2 ou 2.1.6). En règle générale, la dernière branche stable est maintenue jusqu’à la prochaine version majeure (par exemple, la branche 3.0 a été maintenue jusqu’à la sortie de Godot 3.1). Si vous voulez effectuer les Pull Requests sur une branche stable maintenue, vous devrez vérifier si vos modifications sont également pertinentes pour la branche master.
  • Parfois, il peut également y avoir des branches de fonctionnalités, généralement destinées à être fusionnées dans la branche master à un moment donné.

Forker et cloner

La première étape consiste à forker le dépôt godotengine/godot sur GitHub. Pour ce faire, vous devez disposer d’un compte GitHub et y être connecté. Dans le coin en haut à droite de la page du dépôt GitHub, vous devriez voir un bouton « Fork » comme montré ci-dessous :

../../_images/github_fork_button.png

Cliquez dessus, et après un certain temps, vous devriez être redirigé vers votre propre fork du dépôt Godot, avec votre nom d’utilisateur GitHub comme espace de noms :

../../_images/github_fork_url.png

Vous pouvez ensuite cloner votre fork, c’est-à-dire créer une copie locale du dépôt en ligne (dans Git, on parle d”origin remote), Si vous ne l’avez pas déjà fait, téléchargez Git depuis `son site web<https://git-scm.com>`_ si vous utilisez Windows ou macOS, ou installez le à partir de votre gestionnaire de paquets si vous utilisez Linux.

Note

Si vous êtes sous Windows, ouvrez Git Bash pour entrer des commandes. Les utilisateurs de macOS et Linux peuvent utiliser leurs terminaux respectifs.

Pour cloner votre fork depuis GitHub, utilisez la commande suivante :

$ git clone https://github.com/USERNAME/godot

Note

Dans nos exemples, le caractère « $ » désigne l’invite de la ligne de commande sur les shells UNIX. Il ne fait pas partie de la commande et ne doit pas être tapé.

Après un certain temps, vous devriez avoir un répertoire godot dans votre répertoire de travail courant. Déplacez-vous dedans en utilisant la commande cd :

$ cd godot

Nous allons commencer par mettre en place une référence vers le dépôt original que nous avons forké :

$ git remote add upstream https://github.com/godotengine/godot
$ git fetch upstream

Cela va créer une référence nommée upstream pointant vers le dépôt original godotengine/godot. Cela s’avérera utile quand vous voudrez récupérer les nouveaux commits de sa branche master pour mettre à jour votre fork. Vous avez une autre référence nommée origin, qui pointe directement vers votre fork.

Vous n’avez besoin d’effectuer les étapes ci-dessus qu’une seule fois, tant que vous gardez ce répertoire godot local (que vous pouvez déplacer si vous le souhaitez, ses métadonnées importantes sont cachées dans son sous-dossier .git).

Note

Branch it, pull it, code it, stage it, commit, push it, rebase it… technologic.

Cette mauvaise interprétation de Technologic des Daft Punk montre la conception générale qu’ont les débutants du workflow Git : beaucoup de commandes étranges à apprendre par le copier-coller, en espérant qu’elles fonctionneront comme prévu. Cette manière d’apprendre n’est finalement pas si mauvaise, tant que vous êtes curieux et que vous n’hésitez pas à demander de l’aide à votre moteur de recherche quand vous êtes perdu. Nous allons donc vous donner les commandes de base à connaître quand vous travaillez avec Git.

Par la suite, nous supposerons que vous souhaitez implémenter une fonctionnalité dans le gestionnaire de projets de Godot, qui est codé dans le fichier editor/project_manager.cpp.

Branching

Par défaut, le git clone devrait vous avoir placé sur la branche master de votre fork(origin). Pour commencer le développement de votre propre fonctionnalité, nous allons créer une branche de fonctionnalité :

# Create the branch based on the current branch (master)
$ git branch better-project-manager

# Change the current branch to the new one
$ git checkout better-project-manager

Cette commande est équivalente :

# Change the current branch to a new named one, based on the current branch
$ git checkout -b better-project-manager

Si vous voulez retourner sur la branche master, vous devez utiliser :

$ git checkout master

Vous pouvez voir la branche sur laquelle vous êtes actuellement avec la commande git branch :

$ git branch
  2.1
* better-project-manager
  master

Mettre à jour votre branche

Cela n’est pas nécessaire la première fois (juste après avoir forké le dépôt upstream). Cependant, la prochaine fois que vous voudrez travailler sur quelque chose, vous remarquerez que la branche master de votre fork se trouve plusieurs commits derrière la branche master du dépôt upstream : des pull requests d’autres contributeurs ont été fusionnées entre-temps.

Pour vous assurer qu’il n’y aura pas de conflits entre la fonctionnalité que vous développez et la branche master actuelle du dépôt upstream, vous allez devoir mettre à jour votre branche en faisant un pulling de la branche de ce dépôt.

$ git pull upstream master

Cependant, si vous avez des commits locaux, cette méthode créera un « merge commit » comme on l’appelle, et vous entendrez bientôt par d’autres collègues contributeurs que ces commits ne sont pas souhaités dans les Pull Requests. Pour mettre à jour votre branche sans créer de merge commit, vous allez devoir utiliser l’option --rebase, afin que vos commit locaux soient ré-effectués par-dessus la mise à jour de la branche master du dépôt upstream. Cela va effectivement modifier l’historique Git de votre branche, mais c’est pour l’intérêt général.

Par conséquent, la commande que vous devriez (presque) toujours utiliser est :

$ git pull --rebase upstream master

Si vous avez déjà poussé le merge commit sans utiliser l’option rebase, ou si vous avez effectué d’autres changements qui ont abouti à un historique indésirable, vous pouvez utiliser une réinitialisation dure pour revenir à un commit spécifique et essayer à nouveau :

$ git reset --hard [The ID of the last desired commit]

Une fois que vous avez fait cela, vous pouvez exécuter --rebase pour fusionner la branche master correctement.

Si vous avez déjà poussé les mauvais commits sur votre branche distante, vous allez devoir forcer le push en utilisant git push --force.

Avertissement

git reset --hard can be a dangerous operation, especially if you have untracked or uncommitted changes. However, if you have committed changes that you reset using git reset --hard, you may still be able to recover them by resetting to a commit ID found with the git reflog command.

Effectuer des modifications

Vous pouvez ensuite faire vos modifications sur le fichier editor/project_manager.cpp de notre exemple avec votre environnement de développement habituel (éditeur de texte, IDE, etc.).

By default, those changes are unstaged. The staging area is a layer between your working directory (where you make your modifications) and the local git repository (the commits and all the metadata in the .git folder). To bring changes from the working directory to the Git repository, you need to stage them with the git add command, and then to commit them with the git commit command.

There are various commands you should know to review your current work, before staging it, while it is staged, and after it has been committed.

  • git diff will show you the current unstaged changes, i.e. the differences between your working directory and the staging area.
  • git checkout -- <files> will undo the unstaged changes to the given files.
  • git add <files> will stage the changes on the listed files.
  • git diff --staged will show the current staged changes, i.e. the differences between the staging area and the last commit.
  • git reset HEAD <files> will unstage changes to the listed files.
  • git status will show you what are the currently staged and unstaged modifications.
  • git commit will commit the staged files. It will open a text editor (you can define the one you want to use with the GIT_EDITOR environment variable or the core.editor setting in your Git configuration) to let you write a commit log. You can use git commit -m "Cool commit log" to write the log directly.
  • git log vous listera les dernier commits de votre branche courante. Si vous avez fait des commits locaux, ils devraient être affichés en haut.
  • git show vous montrera les modifications effectuées par le dernier commit. Vous pouvez également spécifier le hash d’un commit pour voir les modifications de ce commit.

Cela fait beaucoup à mémoriser ! Ne vous inquiétez pas, consultez juste cet aide-mémoire quand vous avez besoin de faire des modifications, et apprenez par la pratique.

Voici à quoi pourrait ressembler l’historique du shell dans notre exemple :

# It's nice to know where you're starting from
$ git log

# Do changes to the project manager with the nano text editor
$ nano editor/project_manager.cpp

# Find an unrelated bug in Control and fix it
$ nano scene/gui/control.cpp

# Review changes
$ git status
$ git diff

# We'll do two commits for our unrelated changes,
# starting by the Control changes necessary for the PM enhancements
$ git add scene/gui/control.cpp
$ git commit -m "Fix handling of margins in Control"

# Check we did good
$ git log
$ git show
$ git status

# Make our second commit
$ git add editor/project_manager.cpp
$ git commit -m "Add a pretty banner to the project manager"
$ git log

Avec cela, nous devrions avoir deux nouveaux commits dans notre branche better-project-manager qui n’étaient pas dans notre branche master. Ces modifications restent seulement locales et le fork distant, ainsi que le dépôt upstream ne savent pas qu’ils existent.

Pousser les modifications sur un dépôt distant

C’est là qu’intervient le git push. Dans Git, un commit est toujours fait sur le dépôt local (contrairement à Subversion ou un commit modifie directement le dépôt distant). Vous devez pousser les nouveaux commits sur une branche distante pour les partager au reste du monde. Sa syntaxe est la suivante :

$ git push <remote> <local branch>[:<remote branch>]

La partie à propos de la branche distante peut être omise si vous voulez qu’elle ait le même nom que la branche locale, ce qui est le cas dans notre exemple, nous ferons donc :

$ git push origin better-project-manager

Git vous demandera votre nom d’utilisateur et votre mot de passe, et les modifications seront envoyées sur votre dépôt distant. Si vous vous rendez sur la page GitHub de votre fork, vous devriez voir une nouvelle branche avec les commits que vous avez ajouté.

Issuing a pull request

When you load your fork’s branch on GitHub, you should see a line saying « This branch is 2 commits ahead of godotengine:master. » (and potentially some commits behind, if your master branch was out of sync with the upstream master branch.

../../_images/github_fork_make_pr.png

On that line, there is a « Pull request » link. Clicking it will open a form that will let you issue a pull request on the godotengine/godot upstream repository. It should show you your two commits, and state « Able to merge ». If not (e.g. it has way more commits, or says there are merge conflicts), don’t create the PR, something went wrong. Go to IRC and ask for support :)

Use an explicit title for the PR and put the necessary details in the comment area. You can drag and drop screenshots, GIFs or zipped projects if relevant, to showcase what your work implements. Click « Create a pull request », and tadaa!

Modifier une pull request

Bien qu’elle soit examinée par les autres contributeurs, vous devrez souvent apporter des modifications à votre Pull Request encore non fusionnée, soit parce que les contributeurs le demandent, soit parce que vous avez vous-même trouvé des bugs lors de tests.

La bonne nouvelle, c’est que vous pouvez modifier une pull request simplement en agissant sur la branche depuis laquelle vous avez fait la pull request. Vous pouvez par exemple faire un nouveau commit sur cette branche, le pousser sur votre fork et la Pulll Request sera mise à jour automatiquement :

# Check out your branch again if you had changed in the meantime
$ git checkout better-project-manager

# Fix a mistake
$ nano editor/project_manager.cpp
$ git add editor/project_manager.cpp
$ git commit -m "Fix a typo in the banner's title"
$ git push origin better-project-manager

Cela devrait faire l’affaire, mais…

Mastering the PR workflow: the rebase

On the situation outlined above, your fellow contributors who are particularly pedantic regarding the Git history might ask your to rebase your branch to squash or meld the last two commits together (i.e. the two related to the project manager), as the second commit basically fixes an issue in the first one.

Once the PR is merged, it is not relevant for a changelog reader that the PR author made mistakes; instead, we want to keep only commits that bring from one working state to another working state.

To squash those two commits together, we will have to rewrite history. Right, we have that power. You may read that it’s a bad practice, and it’s true when it comes to branches of the upstream repo. But in your fork, you can do whatever you want, and everything is allowed to get neat PRs :)

We will use the interactive rebase git rebase -i to do this. This command takes a commit hash as argument, and will let you modify all commits between that commit hash and the last one of the branch, the so-called HEAD. In our example, we want to act on the last two commits, so we will do:

# The HEAD~X syntax means X commits before HEAD
$ git rebase -i HEAD~2

This will open a text editor with:

pick 1b4aad7 Add a pretty banner to the project manager
pick e07077e Fix a typo in the banner's title

The editor will also show instructions regarding how you can act on those commits. In particular, it should tell you that « pick » means to use that commit (do nothing), and that « squash » and « fixup » can be used to meld the commit in its parent commit. The difference between « squash » and « fixup » is that « fixup » will discard the commit log from the squashed commit. In our example, we are not interested in keeping the log of the « Fix a typo » commit, so we use:

pick 1b4aad7 Add a pretty banner to the project manager
fixup e07077e Fix a typo in the banner's title

Upon saving and quitting the editor, the rebase will occur. The second commit will be melded into the first one, and git log and git show should now confirm that you have only one commit with the changes from both previous commits.

Note

You could have avoided this rebase by using git commit --amend when fixing the typo. This command will write the staged changes directly into the last commit (HEAD), instead of creating a new commit like we did in this example. So it is equivalent to what we did with a new commit and then a rebase to mark it as « fixup ».

But! You rewrote the history, and now your local and remote branches have diverged. Indeed, commit 1b4aad7 in the above example will have changed, and therefore got a new commit hash. If you try to push to your remote branch, it will raise an error:

$ git push origin better-project-manager
To https://github.com/akien-mga/godot
 ! [rejected]        better-project-manager -> better-project-manager (non-fast-forward)
error: failed to push some refs to 'https://[email protected]/akien-mga/godot'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

This is a sane behaviour, Git will not let you push changes that would override remote content. But that’s actually what we want to do here, so we will have to force it:

$ git push --force origin better-project-manager

And tadaa! Git will happily replace your remote branch with what you had locally (so make sure that’s what you wanted, using git log). This will also update the PR accordingly.

Supprimer une branche Git

Une fois que votre pull request a été fusionnée, il vous reste une dernière chose à faire : supprimer la branche Git de votre pull request. Cela ne posera pas de problèmes si vous ne supprimez pas votre branche mais c’est une bonne pratique à adopter. Vous devrez le faire deux fois, une fois pour la branche locale et une autre pour la branche distante sur GitHub.

Pour supprimer notre branche better-project-manager localement, utilisez cette commande :

$ git branch -d better-project-manager

Par ailleurs, si la branche n’avait pas été encore fusionnée et que nous voulions tout de même la supprimer, vous utiliseriez -D au lieu de -d.

Ensuite, pour supprimer la branche distante sur GitHub, utilisez cette commande :

$ git push origin -d better-project-manager