{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "#skip\n", "! [ -e /content ] && pip install -Uqq fastai # upgrade fastai on colab" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#all_slow" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Pull requests made easy\n", "> Making your first pull request to fastai\n", "\n", "- hide_colab_badge: true\n", "- image: images/pr-head.png" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "exit # don't run this by accident!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to contribute to fastai (or any fast.ai library... or indeed most open source software!) you'll need to make a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests), also known as a *PR*. Here's an [example](https://github.com/fastai/fastai/pull/2648) of a pull request. In this case, you can see from the description that it's something that fixes some typos in the library. If you click on \"Files changed\" on that page, you can see all the changes made. We get notified when a pull request arrives, and after checking whether the changes look OK, we \"merge\" it (which means that we click a button in GitHub that causes all those changes to get automatically added to the repo).\n", "\n", "Making a pull request for the first time can feel a bit over-whelming, so I've put together this guide to help you get started. We're going to use GitHub's command line tool `gh`, which makes things faster and easier than doing things through the web-site (in my opinion, at least!). To install `gh` on Linux follow the instructions given [here](https://github.com/cli/cli/blob/trunk/docs/install_linux.md#installing-gh-on-linux). To install `gh` on Mac type: `brew install github/gh/gh`.\n", "\n", "I'm assuming in this guide that you're using Linux, and that you've already got [Anaconda](https://www.anaconda.com/products/individual) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) set up. This is the default in all of the fast.ai course guides, and is highly recommended. It should work fine on Mac too, although I haven't tested it. On Windows, use [Ubuntu on WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10). \n", "\n", "> This document is created from a Jupyter Notebook. You can run the notebook yourself by getting it [from here](https://github.com/fastai/fastai/blob/master/nbs/dev-setup.ipynb). You'll need to install the [Jupyter Bash kernel](https://github.com/takluyver/bash_kernel)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## One time only setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setting up access and `gh`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To develop fastai, you'll need to install `fastai` and `nbdev` (this also checks you have the latest versions, if they're already installed):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting package metadata (current_repodata.json): done\n" ] } ], "source": [ "conda install -y -c fastai -c pytorch -c anaconda anaconda fastai nbdev" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**NB**: if you're using miniconda instead of Anaconda, remove `-c anaconda anaconda` from the above command." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[nbdev](https://nbdev.fast.ai) is a framework for true literate programming; see [this article](https://www.fast.ai/2019/12/02/nbdev/) to learn why you should consider using it for your next project (and why we use it for fastai).\n", "\n", "You'll need to set up `ssh` access to GitHub, if you haven't already. To do so, follow [these steps](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account). Once you've created an ssh key (generally by running `ssh-keygen`), you can copy the contents of your `.~/ssh/id_rsa.pub` file and paste them into GitHub by clicking \"New SSH Key\" on [this page](https://github.com/settings/keys).\n", "\n", "Once that's working, we need to get a [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) to allow `gh` to connect to GitHub. To do so, [click here](https://github.com/settings/tokens/new) and enter \"gh\" in the \"Note\" section, and click the `repo`, `read:discussion`, and `read:org` checkboxes (note that `gh` can do this automatically for you, but it only works conveniently if you're running the code on your local machine; most fastai developers are probably using a remote GPU server, such as Paperspace, AWS, or GCP, so we show the approach below because it works for everyone)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"Personal" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then click \"Generate Token\" at the bottom of the screen, and copy the token (the long string of letters and numbers shown). You can easily do that by clicking the little clipboard icon next to the token." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"Copying" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now run this in your shell, replacing `jph01` with your GitHub username, and the string after `TOKEN=` with your copied token:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "GH_USER=jph01\n", "TOKEN=abae9e225efcf319f41c68f3f4d7c2d92f59403e" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Setup `gh` to use `ssh` to connect to GitHub, so you don't have to enter you name and password all the time:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gh config set git_protocol ssh" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create your GitHub authentication file:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "echo -e \"github.com:\\n user: $GH_USER\\n oauth_token: $TOKEN\\n\" > ~/.config/gh/hosts.yml" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set up `fastcore`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we're ready to clone the `fastcore` and `fastai` libraries. We recommend cloning both, since you might need to make changes in, or debug, the `fastcore` library that `fastai` uses heavily. First, we'll do `fastcore`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cloning into 'fastcore'...\n", "remote: Enumerating objects: 247, done.\u001b[K\n", "remote: Counting objects: 100% (247/247), done.\u001b[K\n", "remote: Compressing objects: 100% (164/164), done.\u001b[K\n", "remote: Total 1618 (delta 162), reused 139 (delta 78), pack-reused 1371\u001b[K\n", "Receiving objects: 100% (1618/1618), 3.18 MiB | 18.61 MiB/s, done.\n", "Resolving deltas: 100% (1102/1102), done.\n" ] } ], "source": [ "gh repo clone fastai/fastcore" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We update our installed version to use [editable mode](https://stackoverflow.com/questions/35064426/when-would-the-e-editable-option-be-useful-with-pip-install). This means that any changes you make to the code in your checked-out repo will automatically be used everywhere on your computer that uses this library:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cd fastcore\n", "pip install -qe ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We update the repo to create and use a [fork](https://medium.com/@tharis63/git-fork-vs-git-clone-8aad0c0e38c0#:~:text=Git%20Fork%20means%20you%20just,to%20your%20own%20GitHub%20profile.&text=Then%20make%20your%20changes%20and,it%20to%20the%20main%20repository.):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[K\u001b[90m- \u001b[90mForking \u001b[0m\u001b[1;39m\u001b[90mfastai/fastcore\u001b[0m\u001b[0m\u001b[90m...\u001b[0m\u001b[0m\n", "\u001b[0m\u001b[33m!\u001b[0m \u001b[1;39mjph01/fastcore\u001b[0m already exists\n", "\u001b[32m✓\u001b[0m Renamed \u001b[1;39morigin\u001b[0m remote to \u001b[1;39mupstream\u001b[0m\n", "\u001b[32m✓\u001b[0m Added remote \u001b[1;39morigin\u001b[0m\n" ] } ], "source": [ "gh repo fork --remote" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because all fast.ai libraries use nbdev, we need to run [nbdev_install_git_hooks](https://nbdev.fast.ai/cli#Git-hooks) the first time after we clone a repo; this ensures that our notebooks are automatically cleaned and trusted whenever we push to GitHub:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Executing: git config --local include.path ../.gitconfig\n", "Success: hooks are installed and repo's .gitconfig is now trusted\n" ] } ], "source": [ "nbdev_install_git_hooks " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set up `fastai`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we'll do the same steps for `fastai`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cloning into 'fastai'...\n", "remote: Enumerating objects: 108, done.\u001b[K\n", "remote: Counting objects: 100% (108/108), done.\u001b[K\n", "remote: Compressing objects: 100% (79/79), done.\u001b[K\n", "remote: Total 10206 (delta 41), reused 57 (delta 27), pack-reused 10098\u001b[K\n", "Receiving objects: 100% (10206/10206), 536.85 MiB | 38.67 MiB/s, done.\n", "Resolving deltas: 100% (8053/8053), done.\n", "Already up to date.\n" ] } ], "source": [ "cd ..\n", "gh repo clone fastai/fastai\n", "cd fastai" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll do an editable install of `fastai` too:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pip install -qe .[dev]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "...and fork it and install the git hooks:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[K\u001b[90m- \u001b[90mForking \u001b[0m\u001b[1;39m\u001b[90mfastai/fastai\u001b[0m\u001b[0m\u001b[90m...\u001b[0m\u001b[0m\n", "\u001b[0m\u001b[33m!\u001b[0m \u001b[1;39mjph01/fastai\u001b[0m already exists\n", "\u001b[32m✓\u001b[0m Renamed \u001b[1;39morigin\u001b[0m remote to \u001b[1;39mupstream\u001b[0m\n", "\u001b[32m✓\u001b[0m Added remote \u001b[1;39morigin\u001b[0m\n" ] } ], "source": [ "gh repo fork --remote" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Executing: git config --local include.path ../.gitconfig\n", "Success: hooks are installed and repo's .gitconfig is now trusted\n" ] } ], "source": [ "nbdev_install_git_hooks " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating your PR" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Everything above needs to be done just once. From here on are the commands to actually create your PR.\n", "\n", "Create a new [git branch](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging), by running the following, replacing `test-pr` with the name you want to give your pull request (use something that will be easy for you to remember in the future if you need to update your PR):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "M\tdocs\n", "Switched to a new branch 'test-pr'\n" ] } ], "source": [ "git checkout -b test-pr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Make whatever changes you want to make in the notebooks, and remember to run `nbdev_build_lib` when you're done to ensure that the libraries are built from your notebook changes (unless you only changed markdown, in which case that's not needed). It's also a good idea to check the output of `git diff` to ensure that you haven't accidentally made more changes than you planned.\n", "\n", "When you're ready, `commit` your work, replacing \"just testing\" here with a clear description of what you did in your commit:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[test-pr 3397cc9] just testing\n", " 2 files changed, 2 insertions(+), 1 deletion(-)\n" ] } ], "source": [ "git commit -am \"just testing\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first time you push from your fork, you need to add `-u origin HEAD`, but after the first time, you can just use `git push`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Counting objects: 4, done.\n", "Delta compression using up to 64 threads.\n", "Compressing objects: 100% (4/4), done.\n", "Writing objects: 100% (4/4), 383 bytes | 383.00 KiB/s, done.\n", "Total 4 (delta 3), reused 0 (delta 0)\n", "remote: Resolving deltas: 100% (3/3), completed with 3 local objects.\u001b[K\n", "remote: \n", "remote: Create a pull request for 'test-pr' on GitHub by visiting:\u001b[K\n", "remote: https://github.com/jph01/fastai/pull/new/test-pr\u001b[K\n", "remote: \n", "To github.com:jph01/fastai.git\n", " * [new branch] HEAD -> test-pr\n", "Branch 'test-pr' set up to track remote branch 'test-pr' from 'origin'.\n" ] } ], "source": [ "git push -u origin HEAD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you're ready to create your PR. To use the information from your commit message as the PR title, just run:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "https://github.com/fastai/fastai/pull/2664\n" ] } ], "source": [ "gh pr create -f" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To be interactively prompted for more information (including opening your editor to let you fill in a detailed description), just run `gh pr create` without the `-f` flag. As you see above, after it's done, it prints the URL of your new PR - congratulations, and thank you for your contribution!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"The" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Post-PR steps" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To keep your fork up to date with the changes to the main fastai repo, and to change from your `test-pr` branch back to master, run:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "From github.com:fastai/fastai\n", " * branch master -> FETCH_HEAD\n", "Already up to date.\n", "M\tdocs\n", "Switched to branch 'master'\n", "Your branch is up to date with 'upstream/master'.\n" ] } ], "source": [ "git pull upstream master\n", "git checkout master" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the future, once your PR has been merged or rejected, you can delete your branch if you don't need it any more:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deleted branch test-pr (was 514782a).\n", "(base) " ] }, { "ename": "", "evalue": "1", "output_type": "error", "traceback": [] } ], "source": [ "git branch -d test-pr" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Bash", "language": "bash", "name": "bash" } }, "nbformat": 4, "nbformat_minor": 4 }