Ablauf eines Pull Request

Der von Godot verwendete sogenannte "PR-Arbeitsablauf" (PR für Pull Request) wird von vielen Projekten auf Git genutzt und sollte erfahrenen Entwicklern freier Software vertraut sein. Die Idee ist, dass nur eine kleine Anzahl Entwickler (wenn überhaupt) Änderungen direkt in den master-Zweig übergeben. Stattdessen spalten (fork) die Mitwirkenden das Projekt (d.h. erstellen eine Kopie davon, die sie nach Belieben ändern können) und fordern dann über die GitHub-Schnittstelle ein pull (Änderungen ziehen) von einem Zweig ihres Fork zu einem Zweig des Originals an ( oft als Upstream bezeichnet).

Die resultierende Pull-Anfrage (PR) kann dann von anderen Mitwirkenden überprüft werden, die sie möglicherweise genehmigen, ablehnen oder meistens Änderungen anfordern. Nach der Genehmigung kann der PR von einem der Kernentwickler zusammengeführt werden, und seine Commits (der neue Code) werden Teil des Zielzweigs (normalerweise des Master-Zweigs).

Wir werden gemeinsam ein Beispiel durchgehen, um den typischen Arbeitsablauf und die zugehörigen Git-Befehle zu zeigen. Aber zuerst werfen wir einen kurzen Blick auf die Organisation von Godots Git-Repository.

Git Quell-Repository

Das Repository auf GitHub ist ein Git Code-Repository zusammen mit einem eingebetteten Problem-Tracker und einem PR-System.

Bemerkung

Zusätzlich zu dieser Dokumentation möchten Sie vielleicht auch einen Blick auf die verschiedenen Godot-Demoprojekte werfen.

Das Git-Versionskontrollsystem ist das Tool, mit dem aufeinanderfolgende Änderungen am Quellcode verfolgt werden. Um einen effizienten Beitrag zu Godot zu leisten, wird dringend empfohlen, die Grundlagen der Git-Befehlszeile zu erlernen. Es gibt einige grafische Oberflächen für Git, aber sie ermutigen Benutzer normalerweise, schlechte Gewohnheiten in Bezug auf den Git- und PR-Ablauf anzunehmen. Wir empfehlen daher, diese nicht zu verwenden. Insbesondere empfehlen wir, den Online-Editor von GitHub nicht für Code-Beiträge zu verwenden (obwohl er für kleine Korrekturen oder Dokumentationsänderungen toleriert wird), da er ein Commit pro Datei und pro Änderung erzwingt, was schnell zu PRs mit einem unlesbaren Git-Verlauf führt (insbesondere nach einer Peer Rezension).

Siehe auch

Die ersten Abschnitte von Gits "Buch" sind eine gute Einführung in die Philosophie des Tools und die verschiedenen Befehle, die Sie in Ihrem täglichen Arbeitsablauf beherrschen müssen. Sie können sie online auf der Website Git SCM lesen.

Die Zweige im Git-Repository sind wie folgt organisiert:

  • Im Master-Zweig findet die Entwicklung der nächsten Hauptversion statt. Als Entwicklungszweig kann es instabil sein und ist nicht für den Einsatz in der Produktion vorgesehen. Hier sollten PRs vorrangig durchgeführt werden.
  • Die stabilen Zweige sind nach ihrer Version benannt, z.B. 3.1 und 2.1. Sie werden verwendet um Bugfixes und Verbesserungen vom master-Zweig auf die aktuell gepflegte stabile Version (z.B. 3.1.2 oder 2.1.6) zurück zu portieren. Als Faustregel gilt, dass der letzte stabile Zweig bis zur nächsten Hauptversion beibehalten wird (z.B. wurde der Zweig 3.0 bis zur Veröffentlichung von Godot 3.1 beibehalten). Wenn Sie PRs für einen gepflegten stabilen Zweig erstellen möchten, überprüfen Sie zunächst ob Ihre Änderungen auch für den Zweig Master relevant sind und legen Sie in diesem Fall die PR für den Zweig Master vorrangig fest. Release-Manager können den Fix dann bei Bedarf für einem stabilen Zweig auswählen.
  • Es kann gelegentlich Feature-Zweige geben, die normalerweise zu einem bestimmten Zeitpunkt in den master-Zweig integriert werden sollen.

abspalten (Fork) und klonen

Der erste Schritt besteht darin, das godotengine/godot repository auf GitHub abzuspalten (englisch Fork). Dazu benötigen Sie ein GitHub-Konto und müssen angemeldet sein. In der oberen rechten Ecke der GitHub-Seite des Repositorys sollten Sie die Schaltfläche "Fork" wie unten gezeigt sehen:

../../_images/github_fork_button.png

Klicken Sie darauf und nach einer Weile sollten Sie mit Ihrem GitHub-Benutzernamen als Namensraum zu Ihrer eigenen Abzweigung des Godot-Repos weitergeleitet werden:

../../_images/github_fork_url.png

Sie können dann Ihren abgespalteten Zweig (Fork) klonen, d.h. eine lokale Kopie des Online-Repositorys erstellen (in Git-Sprache den entfernten Ursprung). Wenn Sie dies noch nicht getan haben, laden Sie Git von der Website <https://git-scm.com> `_ herunter, wenn Sie Windows oder MacOS verwenden oder installieren Sie es über Ihren Paketmanager, wenn Sie Linux verwenden.

Bemerkung

Wenn Sie unter Windows arbeiten, öffnen Sie Git Bash um Befehle einzugeben. Benutzer von MacOS und Linux können ihre jeweiligen Terminals verwenden.

Verwenden Sie den folgenden Befehl, um Ihren Fork von GitHub zu klonen:

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

Bemerkung

In unseren Beispielen kennzeichnet das Zeichen "$" die Eingabeaufforderung in typischen UNIX-Shells. Es ist nicht Teil des Befehls und sollte nicht eingegeben werden.

Nach einer Weile sollten Sie ein Godot-Verzeichnis in Ihrem aktuellen Arbeitsverzeichnis haben, in das Sie mit dem Befehl cd hineinwechseln können:

$ cd godot

Wir beginnen mit der Einrichtung eines Verweises auf das ursprüngliche Repository, das wir abspaltet haben:

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

Dadurch wird eine Referenz mit dem Namen Upstream erstellt, die auf das ursprüngliche Godotengine/Godot-Repository verweist. Dies ist nützlich, wenn Sie neue Commits aus dem Zweig master ziehen möchten, um Ihren Fork zu aktualisieren. Sie haben eine andere Fernreferenz namens origin, die auf Ihren Fork zeigt (USERNAME/godot).

Sie müssen die obigen Schritte nur einmal ausführen, solange Sie diesen lokalen godot-Ordner behalten (den Sie verschieben können, wenn Sie möchten, die relevanten Metadaten sind in seinem .git-Unterordner versteckt).

Bemerkung

verzweigen, ziehen, codieren, inszenieren, bestätigen, hochschieben, neu bauen... technologisch.

Die ist die allgemeine Vorstellung, die Git-Anfänger von ihrem Workflow haben: viele seltsame Befehle, die durch Kopieren und Einfügen gelernt werden müssen, in der Hoffnung, dass sie wie erwartet funktionieren. Und das ist eigentlich kein schlechter Weg zu lernen, solange Sie neugierig sind und nicht zögern, Ihre Suchmaschine zu befragen, sollten Sie mal nicht weiter wissen. Deshalb geben wir Ihnen die grundlegenden Befehle, die Sie bei der Arbeit in Git wissen sollten.

Im Folgenden nehmen wir als Beispiel an, dass Sie eine Funktion in Godots Projektmanager implementieren möchten, die in der Datei editor/project_manager.cpp codiert ist.

Verzweigungen

Standardmäßig sollte der git clone Sie in den master-Zweig Ihres Forks setzen (Ursprung). Um Ihre eigene Feature-Entwicklung zu starten, erstellen wir einen Feature-Zweig:

# 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

Dieser Befehl entspricht:

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

Wenn Sie zum Master-Zweig zurückkehren möchten, verwenden Sie:

$ git checkout master

Mit dem Befehl git branch können Sie sehen, auf welchem Zweig Sie sich gerade befinden:

$ git branch
  2.1
* better-project-manager
  master

Stellen Sie sicher, dass Sie immer zum Zweig master zurückkehren, bevor Sie einen neuen Zweig erstellen, da Ihr aktueller Zweig als Basis für den neuen Zweig verwendet wird. Alternativ können Sie nach dem Namen des neuen Zweigs einen benutzerdefinierten Basiszweig angeben:

$ git checkout -b my-new-feature master

Ihren Zweig aktualisieren

This would not be needed the first time (just after you forked the upstream repository). However, the next time you want to work on something, you will notice that your fork's master is several commits behind the upstream master branch: pull requests from other contributors would have been merged in the meantime.

To ensure there won't be conflicts between the feature you develop and the current upstream master branch, you will have to update your branch by pulling the upstream branch.

$ git pull --rebase upstream master

The --rebase argument will ensure that any local changes that you committed will be re-applied on top of the pulled branch, which is usually what we want in our PR workflow. This way, when you open a pull request, your own commits will be the only difference with the upstream master branch.

While rebasing, conflicts may arise if your commits modified code that has been changed in the upstream branch in the meantime. If that happens, Git will stop at the conflicting commit and will ask you to resolve the conflicts. You can do so with any text editor, then stage the changes (more on that later), and proceed with git rebase --continue. Repeat the operation if later commits have conflicts too, until the rebase operation completes.

If you're unsure about what is going on during a rebase and you panic (no worry, we all do the first few times), you can abort the rebase with git rebase --abort. You will then be back to the original state of your branch before calling git pull --rebase.

Bemerkung

If you omit the --rebase argument, you will instead create a merge commit which tells Git what to make of the two distinct branches. If any conflicts arise, they would be resolved all at once via this merge commit.

While this is a valid workflow and the default behavior of git pull, merge commits within PRs are frowned upon in our PR workflow. We only use them when merging PRs into the upstream branch.

The philosophy is that a PR should represent the final stage of the changes made to the codebase, and we are not interested in mistakes and fixes that would have been done in intermediate stages before merging. Git gives us great tools to "rewrite the history" and make it as if we got things right the first time, and we're happy to use it to ensure that changes are easy to review and understand long after they have been merged.

If you have already created a merge commit without using rebase, or have made any other changes that have resulted in undesired history, the best option is to use an interactive rebase on the upstream branch. See the dedicated section for instructions.

Tipp

If at any time you want to reset a local branch to a given commit or branch, you can do so with git reset --hard <commit ID> or git reset --hard <remote>/<branch> (e.g. git reset --hard upstream/master).

Be warned that this will remove any changes that you might have committed in this branch. If you ever lose commits by mistake, use the git reflog command to find the commit ID of the previous state that you would like to restore, and use it as argument of git reset --hard to go back to that state.

Änderungen vornehmen

Sie würden dann Ihre Änderungen an unserer Beispieldatei editor/project_manager.cpp mit Ihrer üblichen Entwicklungsumgebung (Texteditor, IDE usw.) vornehmen.

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.

Es gibt verschiedene Befehle die Sie kennen sollten, um Ihre aktuelle Arbeit zu überprüfen: bevor Sie sie bereitstellen, während sie bereitgestellt wird und nachdem sie übergeben wurde.

  • git diff zeigt Ihnen die aktuellen nicht bereitgestellten Änderungen, d.h. die Unterschiede zwischen Ihrem Arbeitsverzeichnis und dem Bereitstellungsbereich.
  • git checkout -- <files> macht die nicht bereitgestellten Änderungen an den angegebenen Dateien rückgängig.
  • git add <files> wird die Änderungen an den aufgelisteten Dateien bereitstellen.
  • git diff --staged zeigt die aktuell bereitgestellten Änderungen an, d.h. die Unterschiede zwischen dem bereitgestelltem Bereich und dem letzten Commit.
  • git reset HEAD <files> wird Änderungen an den aufgelisteten Dateien aufheben.
  • git status zeigt Ihnen die aktuell bereitgestellten und nicht bereitgestellten Änderungen.
  • git commit schreibt die bereitgestellten Dateien fest. Es wird ein Texteditor geöffnet (Sie können den gewünschten Editor mit der Umgebungsvariablen GIT_EDITOR oder der Einstellung core.editor in Ihrer Git-Konfiguration definieren), damit Sie ein Commit-Protokoll schreiben können. Sie können ``git commit -m "Cooles Festschreibungsprotokoll" `` verwenden, um das Protokoll direkt zu schreiben.
  • git commit --amend lets you amend the last commit with your currently staged changes (added with git add). This is the best option if you want to fix a mistake in the last commit (bug, typo, style issue, etc.).
  • git log will show you the last commits of your current branch. If you did local commits, they should be shown at the top.
  • git show will show you the changes of the last commit. You can also specify a commit hash to see the changes for that commit.

That's a lot to memorize! Don't worry, just check this cheat sheet when you need to make changes, and learn by doing.

Here's how the shell history could look like on our example:

# 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

With this, we should have two new commits in our better-project-manager branch which were not in the master branch. They are still only local though, the remote fork does not know about them, nor does the upstream repo.

Änderungen an einen entfernten Rechner übertragen

That's where git push will come into play. In Git, a commit is always done in the local repository (unlike Subversion where a commit will modify the remote repository directly). You need to push the new commits to a remote branch to share them with the world. The syntax for this is:

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

Der Teil über den Remote-Zweig kann weggelassen werden, wenn Sie möchten, dass er denselben Namen wie der lokale Zweig hat. Dies ist in diesem Beispiel der Fall. Wir werden also Folgendes tun:

$ git push origin better-project-manager

Git will ask you for your username and password, and the changes will be sent to your remote. If you check the fork's page on GitHub, you should see a new branch with your added commits.

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!

Pull-Anfrage ändern

Während es von anderen Mitwirkenden überprüft wird, müssen Sie häufig Änderungen an Ihrer noch nicht zusammengeführten PR vornehmen, entweder weil die Mitwirkenden sie angefordert haben oder weil Sie beim Testen selbst Probleme festgestellt haben.

Die gute Nachricht ist, dass Sie eine Pull-Anfrage ändern können, indem Sie einfach auf den Zweig reagieren, von dem aus Sie die Pull-Anfrage gestellt haben. Sie können z.B. ein neues Commit für diesen Zweig erstellen, schieben Sie es an Ihren Fork und die PR wird automatisch aktualisiert:

# 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

However, be aware that in our PR workflow, we favor commits that bring the codebase from one functional state to another functional state, without having intermediate commits fixing up bugs in your own code or style issues. Most of the time, we will prefer a single commit in a given PR (unless there's a good reason to keep the changes separate), so instead of authoring a new commit, considering using git commit --amend to amend the previous commit with your fixes. The above example would then become:

# 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
# --amend will change the previous commit, so you will have the opportunity
# to edit its commit message if relevant.
$ git commit --amend
# As we modified the last commit, it no longer matches the one from your
# remote branch, so we need to force push to overwrite that branch.
$ git push --force origin better-project-manager

The interactive rebase

If you didn't follow the above steps closely to amend changes into a commit instead of creating fixup commits, or if you authored your changes without being aware of our workflow and Git usage tips, reviewers might request of your to rebase your branch to squash some or all of the commits into one.

Indeed, if some commits have been made following reviews to fix bugs, typos, etc. in the original commit, they are not relevant to a future changelog reader who would want to know what happened in the Godot codebase, or when and how a given file was last modified.

To squash those extraneous commits into the main one, 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 ID or a branch name as argument, and will let you modify all commits between that commit/branch and the last one in your working branch, the so-called HEAD.

While you can give any commit ID to git rebase -i and review everything in between, the most common and convenient workflow involves rebasing on the upstream ``master`` branch, which you can do with:

$ git rebase -i upstream/master

Bemerkung

Referencing branches in Git is a bit tricky due to the distinction between remote and local branches. Here, upstream/master (with a /) is a local branch which has been pulled from the upstream remote's master branch.

Interactive rebases can only be done on local branches, so the / is important here. As the upstream remote changes frequently, your local upstream/master branch may become outdated, so you can update it with git fetch upstream master. Contrarily to git pull --rebase upstream master which would update your currently checked out branch, fetch will only update the upstream/master reference (which is distinct from your local master branch... yes it's confusing, but you'll become familiar with this little by little).

This will open a text editor (vi by default, see Git docs <https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_editor>__ to configure your favorite one) with something which may look like this:

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.

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://akien-mga@github.com/akien-mga/godot'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

This is a sane behavior, 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.

Löschen eines Git-Zweigs

Nachdem Ihre Pull-Anfrage zusammengeführt wurde, sollten Sie noch etwas tun: Löschen Sie Ihren Git-Zweig für die PR. Es gibt keine Probleme, wenn Sie Ihren Zweig nicht löschen, aber es wird empfohlen, dies zu tun. Sie müssen dies zweimal tun, einmal für den lokalen Zweig und einmal für den Remote-Zweig auf GitHub.

Verwenden Sie diesen Befehl, um unseren besseren Projektmanager-Zweig lokal zu löschen:

$ git branch -d better-project-manager

Alternatively, if the branch hadn't been merged yet and we wanted to delete it anyway, instead of -d you would use -D.

Verwenden Sie als Nächstes den folgenden Befehl, um den Remote-Zweig auf GitHub zu löschen:

$ git push origin -d better-project-manager

Sie können den Remote-Zweig auch aus dem GitHub PR selbst löschen. Nach dem Zusammenführen oder Schließen sollte eine Schaltfläche angezeigt werden.