Pull request workflow
The so-called "PR workflow" used by Godot is common to many projects usingGit, and should be familiar to veteran free software contributors. The ideais that only a small number (if any) commit directly to themaster branch.Instead, contributorsfork the project (i.e. create a copy of it, whichthey can modify as they wish), and then use the GitHub interface to requestapull from one of their fork's branches to one branch of the original(often namedupstream) repository.
The resultingpull request (PR) can then be reviewed by other contributors,which might approve it, reject it, or most often request that modificationsbe done. Once approved, the PR can then be merged by one of the coredevelopers, and its commit(s) will become part of the target branch (usuallythemaster branch).
We will go together through an example to show the typical workflow andassociated Git commands. But first, let's have a quick look at theorganization of Godot's Git repository.
Git source repository
Therepository on GitHub is aGit code repository together with an embeddedissue tracker and PR system.
Note
If you are contributing to the documentation, its repository canbe foundhere.
The Git version control system is the tool used to keep track of successiveedits to the source code - to contribute efficiently to Godot, learning thebasics of the Git command line ishighly recommended. There exist somegraphical interfaces for Git, but they usually encourage users to take badhabits regarding the Git and PR workflow, and we therefore recommend not touse them. In particular, we advise not to use GitHub's online editor for codecontributions (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).
See also
The first sections of Git's "Book" are a good introduction tothe tool's philosophy and the various commands you need tomaster in your daily workflow. You can read them online on theGit SCM website.You can also try outGitHub's interactive guide.
The branches on the Git repository are organized as follows:
The
master
branch is where the development of the next major versionoccurs. As a development branch, it can be unstableand is not meant for use in production. This is where PRs should be donein priority.The stable branches are named after their version, e.g.
3.1
and2.1
.They are used to backport bugfixes and enhancements from themaster
branch to the currently maintained stable release (e.g. 3.1.2 or 2.1.6).As a rule of thumb, the last stable branch is maintained until the nextminor version (e.g. the3.0
branch was maintained until the release ofGodot 3.1).If you want to make PRs against a maintained stable branch, please checkfirst if your changes are also relevant for themaster
branch, and if somake the PR for themaster
branch in priority. Release managers can thencherry-pick the fix to a stable branch if relevant.There might occasionally be feature branches, usually meant to be merged intothe
master
branch at some time.
Forking and cloning
The first step is tofork thegodotengine/godotrepository on GitHub. To do so, you will need to have a GitHub account and tobe logged in. In the top right corner of the repository's GitHub page, youshould see the "Fork" button as shown below:

Click it, and after a while you should be redirected to your own fork of theGodot repo, with your GitHub username as namespace:

You can thenclone your fork, i.e. create a local copy of the onlinerepository (in Git speak, theorigin remote). If you haven't already,download Git fromits website if you're using Windows ormacOS, or install it through your package manager if you're using Linux.
Note
If you are on Windows, open Git Bash to type commands. macOS and Linux userscan use their respective terminals.
To clone your fork from GitHub, use the following command:
gitclonehttps://github.com/USERNAME/godot
After a little while, you should have agodot
directory in your currentworking directory. Move into it using thecd
command:
cdgodot
We will start by setting up a reference to the original repository that we forked:
gitremoteaddupstreamhttps://github.com/godotengine/godotgitfetchupstream
This will create a reference namedupstream
pointing to the originalgodotengine/godot
repository. This will be useful when you want to pull newcommits from itsmaster
branch to update your fork. You have anotherremote reference namedorigin
, which points to your fork (USERNAME/godot
).
You only need to do the above steps once, as long as you keep that localgodot
folder (which you can move around if you want, the relevantmetadata is hidden in its.git
subfolder).
Note
Branch it, pull it, code it, stage it, commit, push it, rebaseit... technologic.
This bad take on Daft Punk'sTechnologic shows the generalconception Git beginners have of its workflow: lots of strangecommands to learn by copy and paste, hoping they will work asexpected. And that's actually not a bad way to learn, as long asyou're curious and don't hesitate to question your search enginewhen lost, so we will give you the basic commands to know whenworking in Git.
In the following, we will assume as an example that you want to implement a feature inGodot's Project Manager, which is coded in theeditor/project_manager.cpp
file.
Branching
By default, thegitclone
should have put you on themaster
branch ofyour fork (origin
). To start your own feature development, we will createa feature branch:
# Create the branch based on the current branch (master)gitbranchbetter-project-manager# Change the current branch to the new onegitcheckoutbetter-project-manager
This command is equivalent:
# Change the current branch to a new named one, based on the current branchgitcheckout-bbetter-project-manager
If you want to go back to themaster
branch, you'd use:
gitcheckoutmaster
You can see which branch you are currently on with thegitbranch
command:
gitbranch2.1*better-project-managermaster
Be sure to always go back to themaster
branch before creating a new branch,as your current branch will be used as the base for the new one. Alternatively,you can specify a custom base branch after the new branch's name:
gitcheckout-bmy-new-featuremaster
Updating your branch
This would not be needed the first time (just after you forked the upstreamrepository). However, the next time you want to work on something, you willnotice that your fork'smaster
is several commits behind the upstreammaster
branch: pull requests from other contributors would have been mergedin the meantime.
To ensure there won't be conflicts between the feature you develop and thecurrent upstreammaster
branch, you will have to update your branch bypulling the upstream branch.
gitpull--rebaseupstreammaster
The--rebase
argument will ensure that any local changes that you committedwill be re-appliedon top of the pulled branch, which is usually what we wantin our PR workflow. This way, when you open a pull request, your own commits willbe the only difference with the upstreammaster
branch.
While rebasing, conflicts may arise if your commits modified code that has beenchanged in the upstream branch in the meantime. If that happens, Git will stop atthe conflicting commit and will ask you to resolve the conflicts. You can do sowith any text editor, then stage the changes (more on that later), and proceed withgitrebase--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 withgitrebase--abort
.You will then be back to the original state of your branch before callinggitpull--rebase
.
Note
If you omit the--rebase
argument, you will instead create a mergecommit which tells Git what to make of the two distinct branches. If anyconflicts arise, they would be resolved all at once via this merge commit.
While this is a valid workflow and the default behavior ofgitpull
,merge commits within PRs are frowned upon in our PR workflow. We only usethem when merging PRs into the upstream branch.
The philosophy is that a PR should represent the final stage of the changesmade to the codebase, and we are not interested in mistakes and fixes thatwould have been done in intermediate stages before merging.Git gives us great tools to "rewrite the history" and make it as if we gotthings right the first time, and we're happy to use it to ensure thatchanges are easy to review and understand long after they have been merged.
If you have already created a merge commit without usingrebase
, orhave made any other changes that have resulted in undesired history, the best optionis to use aninteractive rebase on the upstream branch. See thededicatedsection for instructions.
Tip
If at any time you want toreset a local branch to a given commit or branch,you can do so withgitreset--hard<commitID>
orgitreset--hard<remote>/<branch>
(e.g.gitreset--hardupstream/master
).
Be warned that this will remove any changes that you might have committed inthis branch. If you ever lose commits by mistake, use thegitreflog
commandto find the commit ID of the previous state that you would like to restore, anduse it as argument ofgitreset--hard
to go back to that state.
Making changes
You would then do your changes to our example'seditor/project_manager.cpp
file with your usual development environment(text editor, IDE, etc.).
By default, those changes areunstaged. The staging area is a layer betweenyour working directory (where you make your modifications) and the local Gitrepository (the commits and all the metadata in the.git
folder). Tobring changes from the working directory to the Git repository, you need tostage them with thegitadd
command, and then to commit them with thegitcommit
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.
gitdiff
will show you the current unstaged changes, i.e. thedifferences between your working directory and the staging area.gitcheckout--<files>
will undo the unstaged changes to the givenfiles.gitadd<files>
willstage the changes on the listed files.gitdiff--staged
will show the current staged changes, i.e. thedifferences between the staging area and the last commit.gitresetHEAD<files>
willunstage changes to the listed files.gitstatus
will show you what are the currently staged and unstagedmodifications.gitcommit
will commit the staged files. It will open a text editor(you can define the one you want to use with theGIT_EDITOR
environmentvariable or thecore.editor
setting in your Git configuration) to let youwrite a commit log. You can usegitcommit-m"Coolcommitlog"
towrite the log directly.gitcommit--amend
lets you amend the last commit with your currentlystaged changes (added withgitadd
). This is the best option if youwant to fix a mistake in the last commit (bug, typo, style issue, etc.).gitlog
will show you the last commits of your current branch. If youdid local commits, they should be shown at the top.gitshow
will show you the changes of the last commit. You can alsospecify 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 youneed 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 fromgitlog# Do changes to the Project Manager with the nano text editornanoeditor/project_manager.cpp# Find an unrelated bug in Control and fix itnanoscene/gui/control.cpp# Review changesgitstatusgitdiff# We'll do two commits for our unrelated changes,# starting by the Control changes necessary for the PM enhancementsgitaddscene/gui/control.cppgitcommit-m"Fix handling of margins in Control"# Check we did goodgitloggitshowgitstatus# Make our second commitgitaddeditor/project_manager.cppgitcommit-m"Add a pretty banner to the Project Manager"gitlog
With this, we should have two new commits in ourbetter-project-manager
branch which were not in themaster
branch. They are still only localthough, the remote fork does not know about them, nor does the upstream repo.
Pushing changes to a remote
That's wheregitpush
will come into play. In Git, a commit is alwaysdone in the local repository (unlike Subversion where a commit will modifythe remote repository directly). You need topush the new commits to aremote branch to share them with the world. The syntax for this is:
gitpush<remote><localbranch>[:<remotebranch>]
The part about the remote branch can be omitted if you want it to have thesame name as the local branch, which is our case in this example, so we willdo:
gitpushoriginbetter-project-manager
Git will ask you for your username and password. For your password, enter yourGitHub Personal Access Token (PAT). If you do not have a GitHub Personal AccessToken, or do not have one with the correct permissions for your newly forkedrepository, you will need to create one. Follow this link to create your PersonalAccess Token:Creating a personal access token.
After you have successfully verified your account using your PAT, the changeswill be sent to your remote repository. 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 somecommits behind, if yourmaster
branch was out of sync with the upstreammaster
branch).

On that line, there is a "Pull request" link. Clicking it will open a formthat will let you issue a pull request on thegodotengine/godot
upstreamrepository. 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 yet, something went wrong. Go to ourGodot Contributors Chat and ask for support :)
Use an explicit title for the PR and put the necessary details in the commentarea. You can drag and drop screenshots, GIFs or zipped projects if relevant,to showcase what your work implements. Click "Create a pull request", andtadaa!
Modifying a pull request
While it is reviewed by other contributors, you will often need to makechanges to your yet-unmerged PR, either because contributors requested them,or because you found issues yourself while testing.
The good news is that you can modify a pull request simply by acting on thebranch you made the pull request from. You can e.g. make a new commit on thatbranch, push it to your fork, and the PR will be updated automatically:
# Check out your branch again if you had changed in the meantimegitcheckoutbetter-project-manager# Fix a mistakenanoeditor/project_manager.cppgitaddeditor/project_manager.cppgitcommit-m"Fix a typo in the banner's title"gitpushoriginbetter-project-manager
However, be aware that in our PR workflow, we favor commits that bring thecodebase from one functional state to another functional state, without havingintermediate commits fixing up bugs in your own code or style issues. Most ofthe time, we will prefer a single commit in a given PR (unless there's a goodreason to keep the changes separate). Instead of authoring a new commit,consider usinggitcommit--amend
to amend the previous commit with yourfixes. The above example would then become:
# Check out your branch again if you had changed in the meantimegitcheckoutbetter-project-manager# Fix a mistakenanoeditor/project_manager.cppgitaddeditor/project_manager.cpp# --amend will change the previous commit, so you will have the opportunity# to edit its commit message if relevant.gitcommit--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.gitpush--forceoriginbetter-project-manager
The interactive rebase
If you didn't follow the above steps closely toamend changes into a commitinstead of creating fixup commits, or if you authored your changes without beingaware of our workflow and Git usage tips, reviewers might request you torebase your branch tosquash 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 whowould want to know what happened in the Godot codebase, or when and how a givenfile was last modified.
To squash those extraneous commits into the main one, we will have torewritehistory. Right, we have that power. You may read that it's a bad practice, andit's true when it comes to branches of the upstream repo. But in your fork, youcan do whatever you want, and everything is allowed to get neat PRs :)
We will use theinteractive rebasegitrebase-i
to do this. Thiscommand takes a commit ID or a branch name as argument, and will let you modifyall commits between that commit/branch and the last one in your working branch,the so-calledHEAD
.
While you can give any commit ID togitrebase-i
and review everything inbetween, the most common and convenient workflow involves rebasing on theupstreammaster
branch, which you can do with:
gitrebase-iupstream/master
Note
Referencing branches in Git is a bit tricky due to the distinctionbetween remote and local branches. Here,upstream/master
(with a/) is a local branch which has been pulled from theupstream
remote'smaster
branch.
Interactive rebases can only be done on local branches, so the/is important here. As the upstream remote changes frequently, yourlocalupstream/master
branch may become outdated, so you canupdate it withgitfetchupstreammaster
. Contrarily togitpull--rebaseupstreammaster
which would update yourcurrently checked out branch,fetch
will only update theupstream/master
reference (which is distinct from your localmaster
branch... yes it's confusing, but you'll become familiarwith this little by little).
This will open a text editor (vi
by default, seeGit docsto configure your favorite one) with something which may look like this:
pick 1b4aad7 Add a pretty banner to the Project Managerpick e07077e Fix a typo in the banner's title
The editor will also show instructions regarding how you can act on thosecommits. In particular, it should tell you that "pick" means to use thatcommit (do nothing), and that "squash" and "fixup" can be used tomeld thecommit in its parent commit. The difference between "squash" and "fixup" isthat "fixup" will discard the commit log from the squashed commit. In ourexample, 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 Managerfixup e07077e Fix a typo in the banner's title
Upon saving and quitting the editor, the rebase will occur. The second commitwill be melded into the first one, andgitlog
andgitshow
shouldnow confirm that you have only one commit with the changes from both previouscommits.
But! You rewrote the history, and now your local and remote branches havediverged. Indeed, commit 1b4aad7 in the above example will have changed, andtherefore got a new commit hash. If you try to push to your remote branch, itwill raise an error:
gitpushoriginbetter-project-managerTohttps://github.com/akien-mga/godot![rejected]better-project-manager->better-project-manager(non-fast-forward)error:failedtopushsomerefsto'https://[email protected]/akien-mga/godot'hint:Updateswererejectedbecausethetipofyourcurrentbranchisbehindhint:itsremotecounterpart.
This is reasonable behavior, Git will not let you push changes that wouldoverride remote content. But that's actually what we want to do here, so wewill have toforce it:
gitpush--forceoriginbetter-project-manager
And tadaa! Git will happilyreplace your remote branch with what you hadlocally (so make sure that's what you wanted, usinggitlog
). This willalso update the PR accordingly.
Rebasing onto another branch
If you have accidentally opened your PR on the wrong branch, or need to target another branchfor some reason, you might need to filter out a lot of commits that differ between the old branch(for example4.2
) and the new branch (for examplemaster
). This can make rebasing difficultand tedious. Fortunatelygit
has a command just for this situation,gitrebase--onto
.
If your PR was created from the4.2
branch and you want to update it to instead start atmaster
the following stepsshould fix this in one step:
git rebase -i --onto master 4.2
This will take all the commits on your branchafter the4.2
branch, and then splice them on top ofmaster
,ignoring any commits from the4.2
branch not on themaster
branch. You may still need to do some fixing, butthis command should save you a lot of tedious work removing commits.
Just like above for the interactive rebase you need to force push your branch to handle the different changes:
gitpush--forceoriginbetter-project-manager
Deleting a Git branch
After your pull request gets merged, there's one last thing you should do: delete yourGit branch for the PR. There won't be issues if you don't delete your branch, but it'sgood practice to do so. You'll need to do this twice, once for the local branch and anotherfor the remote branch on GitHub.
To delete our better Project Manager branch locally, use this command:
gitbranch-dbetter-project-manager
Alternatively, if the branch hadn't been merged yet and we wanted to delete it anyway, insteadof-d
you would use-D
.
Next, to delete the remote branch on GitHub use this command:
gitpushorigin-dbetter-project-manager
You can also delete the remote branch from the GitHub PR itself, a button should appear onceit has been merged or closed.