431 lines
18 KiB
Markdown
431 lines
18 KiB
Markdown
# Using Git
|
|
|
|
<!-- toc -->
|
|
|
|
The Rust project uses [Git] to manage its source code. In order to
|
|
contribute, you'll need some familiarity with its features so that your changes
|
|
can be incorporated into the compiler.
|
|
|
|
[Git]: https://git-scm.com
|
|
|
|
The goal of this page is to cover some of the more common questions and
|
|
problems new contributors face. Although some Git basics will be covered here,
|
|
if you find that this is still a little too fast for you, it might make sense
|
|
to first read some introductions to Git, such as the Beginner and Getting
|
|
started sections of [this tutorial from Atlassian][atlassian-git]. GitHub also
|
|
provides [documentation] and [guides] for beginners, or you can consult the
|
|
more in depth [book from Git].
|
|
|
|
[book from Git]: https://git-scm.com/book/en/v2/
|
|
[atlassian-git]: https://www.atlassian.com/git/tutorials/what-is-version-control
|
|
[documentation]: https://docs.github.com/en/github/getting-started-with-github/set-up-git
|
|
[guides]: https://guides.github.com/introduction/git-handbook/
|
|
|
|
## Prerequisites
|
|
|
|
We'll assume that you've installed Git, forked [rust-lang/rust], and cloned the
|
|
forked repo to your PC. We'll use the command line interface to interact
|
|
with Git; there are also a number of GUIs and IDE integrations that can
|
|
generally do the same things.
|
|
|
|
[rust-lang/rust]: https://github.com/rust-lang/rust
|
|
|
|
If you've cloned your fork, then you will be able to reference it with `origin`
|
|
in your local repo. It may be helpful to also set up a remote for the official
|
|
rust-lang/rust repo via
|
|
|
|
```sh
|
|
git remote add upstream https://github.com/rust-lang/rust.git
|
|
```
|
|
|
|
if you're using HTTPS, or
|
|
|
|
```sh
|
|
git remote add upstream git@github.com:rust-lang/rust.git
|
|
```
|
|
|
|
if you're using SSH.
|
|
|
|
**NOTE:** This page is dedicated to workflows for `rust-lang/rust`, but will likely be
|
|
useful when contributing to other repositories in the Rust project.
|
|
|
|
|
|
## Standard Process
|
|
|
|
Below is the normal procedure that you're likely to use for most minor changes
|
|
and PRs:
|
|
|
|
1. Ensure that you're making your changes on top of master:
|
|
`git checkout master`.
|
|
2. Get the latest changes from the Rust repo: `git pull upstream master --ff-only`.
|
|
(see [No-Merge Policy](#keeping-things-up-to-date) for more info about this).
|
|
3. Make a new branch for your change: `git checkout -b issue-12345-fix`.
|
|
4. Make some changes to the repo and test them.
|
|
5. Stage your changes via `git add src/changed/file.rs src/another/change.rs`
|
|
and then commit them with `git commit`. Of course, making intermediate commits
|
|
may be a good idea as well. Avoid `git add .`, as it makes it too easy to
|
|
unintentionally commit changes that should not be committed, such as submodule
|
|
updates. You can use `git status` to check if there are any files you forgot
|
|
to stage.
|
|
6. Push your changes to your fork: `git push --set-upstream origin issue-12345-fix`
|
|
(After adding commits, you can use `git push` and after rebasing or
|
|
pulling-and-rebasing, you can use `git push --force-with-lease`).
|
|
7. [Open a PR][ghpullrequest] from your fork to `rust-lang/rust`'s master branch.
|
|
|
|
[ghpullrequest]: https://guides.github.com/activities/forking/#making-a-pull-request
|
|
|
|
If you end up needing to rebase and are hitting conflicts, see [Rebasing](#rebasing).
|
|
If you want to track upstream while working on long-running feature/issue, see
|
|
[Keeping things up to date](#keeping-things-up-to-date).
|
|
|
|
If your reviewer requests changes, the procedure for those changes looks much
|
|
the same, with some steps skipped:
|
|
|
|
1. Ensure that you're making changes to the most recent version of your code:
|
|
`git checkout issue-12345-fix`.
|
|
2. Make, stage, and commit your additional changes just like before.
|
|
3. Push those changes to your fork: `git push`.
|
|
|
|
## Troubleshooting git issues
|
|
|
|
You don't need to clone `rust-lang/rust` from scratch if it's out of date!
|
|
Even if you think you've messed it up beyond repair, there are ways to fix
|
|
the git state that don't require downloading the whole repository again.
|
|
Here are some common issues you might run into:
|
|
|
|
### I deleted my fork on GitHub!
|
|
|
|
This is not a problem from git's perspective. If you run `git remote -v`,
|
|
it will say something like this:
|
|
|
|
```
|
|
$ git remote -v
|
|
origin https://github.com//rust-lang/rust (fetch)
|
|
origin https://github.com//rust-lang/rust (push)
|
|
personal https://github.com/jyn514/rust (fetch)
|
|
personal https://github.com/jyn514/rust (push)
|
|
```
|
|
|
|
If you renamed your fork, you can change the URL like this:
|
|
|
|
```console
|
|
git remote set-url personal <URL>
|
|
```
|
|
|
|
where the `<URL>` is your new fork.
|
|
|
|
### I see 'Untracked Files: src/stdarch'?
|
|
|
|
This is left over from the move to the `library/` directory.
|
|
Unfortunately, `git rebase` does not follow renames for submodules, so you
|
|
have to delete the directory yourself:
|
|
|
|
```console
|
|
rm -r src/stdarch
|
|
```
|
|
|
|
### I see `<<< HEAD`?
|
|
|
|
You were probably in the middle of a rebase or merge conflict. See
|
|
[Conflicts](#conflicts) for how to fix the conflict. If you don't care about the changes
|
|
and just want to get a clean copy of the repository back, you can use `git reset`:
|
|
|
|
```console
|
|
# WARNING: this throws out any local changes you've made! Consider resolving the conflicts instead.
|
|
git reset --hard master
|
|
```
|
|
|
|
### Quick note about submodules
|
|
|
|
When updating your local repository with `git pull`, you may notice that sometimes
|
|
Git says you have modified some files that you have never edited. For example,
|
|
running `git status` gives you something like (note the `new commits` mention):
|
|
|
|
```
|
|
On branch master
|
|
Your branch is up to date with 'origin/master'.
|
|
|
|
Changes not staged for commit:
|
|
(use "git add <file>..." to update what will be committed)
|
|
(use "git restore <file>..." to discard changes in working directory)
|
|
modified: src/tools/cargo (new commits)
|
|
modified: src/tools/rls (new commits)
|
|
|
|
no changes added to commit (use "git add" and/or "git commit -a")
|
|
```
|
|
|
|
These changes are not changes to files: they are changes to submodules (more on
|
|
this [later](#git-submodules)). To get rid of those, run `git submodule update`
|
|
(or run any `x.py` command, which will automatically update the submodules).
|
|
Note that there is (as of <!-- date: 2021-07 --> July 2021) a [bug][#77620] if you use
|
|
worktrees, submodules, and x.py in a commit hook. If you run into an error
|
|
like:
|
|
|
|
```
|
|
error: failed to read `/home/joshua/rustc-worktree/src/tools/miri/cargo-miri/Cargo.toml`
|
|
|
|
Caused by:
|
|
No such file or directory (os error 2)
|
|
```
|
|
it's not anything you did wrong. There is a workaround in [the issue][#77620-workaround].
|
|
|
|
[#77620]: https://github.com/rust-lang/rust/issues/77620
|
|
[#77620-workaround]: https://github.com/rust-lang/rust/issues/77620#issuecomment-705228229
|
|
|
|
## Rebasing and Conflicts
|
|
|
|
When you edit your code locally, you are making changes to the version of
|
|
rust-lang/rust that existed when you created your feature branch. As such, when
|
|
you submit your PR it is possible that some of the changes that have been made
|
|
to rust-lang/rust since then are in conflict with the changes you've made.
|
|
|
|
When this happens, you need to resolve the conflicts before your changes can be
|
|
merged. First, get a local copy of the conflicting changes: Checkout your local
|
|
master branch with `git checkout master`, then `git pull upstream master` to
|
|
update it with the most recent changes.
|
|
|
|
### Rebasing
|
|
|
|
You're now ready to start the rebasing process. Checkout the branch with your
|
|
changes and execute `git rebase master`.
|
|
|
|
When you rebase a branch on master, all the changes on your branch are
|
|
reapplied to the most recent version of master. In other words, Git tries to
|
|
pretend that the changes you made to the old version of master were instead
|
|
made to the new version of master. During this process, you should expect to
|
|
encounter at least one "rebase conflict." This happens when Git's attempt to
|
|
reapply the changes fails because your changes conflicted with other changes
|
|
that have been made. You can tell that this happened because you'll see
|
|
lines in the output that look like
|
|
|
|
```
|
|
CONFLICT (content): Merge conflict in file.rs
|
|
```
|
|
|
|
When you open these files, you'll see sections of the form
|
|
|
|
```
|
|
<<<<<<< HEAD
|
|
Original code
|
|
=======
|
|
Your code
|
|
>>>>>>> 8fbf656... Commit fixes 12345
|
|
```
|
|
|
|
This represents the lines in the file that Git could not figure out how to
|
|
rebase. The section between `<<<<<<< HEAD` and `=======` has the code from
|
|
master, while the other side has your version of the code. You'll need to
|
|
decide how to deal with the conflict. You may want to keep your changes,
|
|
keep the changes on master, or combine the two.
|
|
|
|
Generally, resolving the conflict consists of two steps: First, fix the
|
|
particular conflict. Edit the file to make the changes you want and remove the
|
|
`<<<<<<<`, `=======` and `>>>>>>>` lines in the process. Second, check the
|
|
surrounding code. If there was a conflict, its likely there are some logical
|
|
errors lying around too! It's a good idea to run `x.py check` here to make sure
|
|
there are no glaring errors.
|
|
|
|
Once you're all done fixing the conflicts, you need to stage the files that had
|
|
conflicts in them via `git add`. Afterwards, run `git rebase --continue` to let
|
|
Git know that you've resolved the conflicts and it should finish the rebase.
|
|
|
|
Once the rebase has succeeded, you'll want to update the associated branch on
|
|
your fork with `git push --force-with-lease`.
|
|
|
|
Note that `git push` will not work properly and say something like this:
|
|
|
|
```
|
|
! [rejected] issue-xxxxx -> issue-xxxxx (non-fast-forward)
|
|
error: failed to push some refs to 'https://github.com/username/rust.git'
|
|
hint: Updates were rejected because the tip of your current branch is behind
|
|
hint: its remote counterpart. Integrate the remote changes (e.g.
|
|
hint: 'git pull ...') before pushing again.
|
|
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
|
|
```
|
|
|
|
The advice this gives is incorrect! Because of Rust's
|
|
["no-merge" policy](#no-merge-policy) the merge commit created by `git pull`
|
|
will not be allowed in the final PR, in addition to defeating the point of the
|
|
rebase! Use `git push --force-with-lease` instead.
|
|
|
|
### Keeping things up to date
|
|
|
|
The above section on [Rebasing](#rebasing) is a specific
|
|
guide on rebasing work and dealing with merge conflicts.
|
|
Here is some general advice about how to keep your local repo
|
|
up-to-date with upstream changes:
|
|
|
|
Using `git pull upstream master` while on your local master branch regularly
|
|
will keep it up-to-date. You will also want to rebase your feature branches
|
|
up-to-date as well. After pulling, you can checkout the feature branches
|
|
and rebase them:
|
|
|
|
```
|
|
git checkout master
|
|
git pull upstream master --ff-only # to make certain there are no merge commits
|
|
git checkout feature_branch
|
|
git rebase master
|
|
git push --force-with-lease (set origin to be the same as local)
|
|
```
|
|
|
|
To avoid merges as per the [No-Merge Policy][#no-merge-policy], you may want to use
|
|
`git config pull.ff only` (this will apply the config to the local repo).
|
|
to avoid merge conflicts while pulling, without needing
|
|
`--ff-only` or `--rebase` while `git pull`ing
|
|
|
|
You can also `git push --force-with-lease` from master to keep your origin's master in sync with
|
|
upstream.
|
|
|
|
## Advanced Rebasing
|
|
|
|
If your branch contains multiple consecutive rewrites of the same code, or if
|
|
the rebase conflicts are extremely severe, you can use
|
|
`git rebase --interactive master` to gain more control over the process. This
|
|
allows you to choose to skip commits, edit the commits that you do not skip,
|
|
change the order in which they are applied, or "squash" them into each other.
|
|
|
|
Alternatively, you can sacrifice the commit history like this:
|
|
|
|
```
|
|
# squash all the changes into one commit so you only have to worry about conflicts once
|
|
git rebase -i $(git merge-base master HEAD) # and squash all changes along the way
|
|
git rebase master
|
|
# fix all merge conflicts
|
|
git rebase --continue
|
|
```
|
|
|
|
"Squashing" commits into each other causes them to be merged into a single
|
|
commit. Both the upside and downside of this is that it simplifies the history.
|
|
On the one hand, you lose track of the steps in which changes were made, but
|
|
the history becomes easier to work with.
|
|
|
|
You also may want to squash just the last few commits together, possibly
|
|
because they only represent "fixups" and not real changes. For example,
|
|
`git rebase --interactive HEAD~2` will allow you to edit the two commits only.
|
|
|
|
### `git range-diff`
|
|
|
|
After completing a rebase, and before pushing up your changes, you may want to
|
|
review the changes between your old branch and your new one. You can do that
|
|
with `git range-diff master @{upstream} HEAD`.
|
|
|
|
The first argument to `range-diff`, `master` in this case, is the base revision
|
|
that you're comparing your old and new branch against. The second argument is
|
|
the old version of your branch; in this case, `@upstream` means the version that
|
|
you've pushed to GitHub, which is the same as what people will see in your pull
|
|
request. Finally, the third argument to `range-diff` is the *new* version of
|
|
your branch; in this case, it is `HEAD`, which is the commit that is currently
|
|
checked-out in your local repo.
|
|
|
|
Note that you can also use the equivalent, abbreviated form `git range-diff
|
|
master @{u} HEAD`.
|
|
|
|
Unlike in regular Git diffs, you'll see a `-` or `+` next to another `-` or `+`
|
|
in the range-diff output. The marker on the left indicates a change between the
|
|
old branch and the new branch, and the marker on the right indicates a change
|
|
you've committed. So, you can think of a range-diff as a "diff of diffs" since
|
|
it shows you the differences between your old diff and your new diff.
|
|
|
|
Here's an example of `git range-diff` output (taken from [Git's
|
|
docs][range-diff-example-docs]):
|
|
|
|
```
|
|
-: ------- > 1: 0ddba11 Prepare for the inevitable!
|
|
1: c0debee = 2: cab005e Add a helpful message at the start
|
|
2: f00dbal ! 3: decafe1 Describe a bug
|
|
@@ -1,3 +1,3 @@
|
|
Author: A U Thor <author@example.com>
|
|
|
|
-TODO: Describe a bug
|
|
+Describe a bug
|
|
@@ -324,5 +324,6
|
|
This is expected.
|
|
|
|
-+What is unexpected is that it will also crash.
|
|
++Unexpectedly, it also crashes. This is a bug, and the jury is
|
|
++still out there how to fix it best. See ticket #314 for details.
|
|
|
|
Contact
|
|
3: bedead < -: ------- TO-UNDO
|
|
```
|
|
|
|
(Note that `git range-diff` output in your terminal will probably be easier to
|
|
read than in this example because it will have colors.)
|
|
|
|
Another feature of `git range-diff` is that, unlike `git diff`, it will also
|
|
diff commit messages. This feature can be useful when amending several commit
|
|
messages so you can make sure you changed the right parts.
|
|
|
|
`git range-diff` is a very useful command, but note that it can take some time
|
|
to get used to its output format. You may also find Git's documentation on the
|
|
command useful, especially their ["Examples" section][range-diff-example-docs].
|
|
|
|
[range-diff-example-docs]: https://git-scm.com/docs/git-range-diff#_examples
|
|
|
|
## No-Merge Policy
|
|
|
|
The rust-lang/rust repo uses what is known as a "rebase workflow." This means
|
|
that merge commits in PRs are not accepted. As a result, if you are running
|
|
`git merge` locally, chances are good that you should be rebasing instead. Of
|
|
course, this is not always true; if your merge will just be a fast-forward,
|
|
like the merges that `git pull` usually performs, then no merge commit is
|
|
created and you have nothing to worry about. Running `git config merge.ff only`
|
|
(this will apply the config to the local repo).
|
|
once will ensure that all the merges you perform are of this type, so that you
|
|
cannot make a mistake.
|
|
|
|
There are a number of reasons for this decision and like all others, it is a
|
|
tradeoff. The main advantage is the generally linear commit history. This
|
|
greatly simplifies bisecting and makes the history and commit log much easier
|
|
to follow and understand.
|
|
|
|
## Git submodules
|
|
|
|
**NOTE**: submodules are a nice thing to know about, but it *isn't* an absolute
|
|
prerequisite to contribute to `rustc`. If you are using Git for the first time,
|
|
you might want to get used to the main concepts of Git before reading this section.
|
|
|
|
The `rust-lang/rust` repository uses [Git submodules] as a way to use other
|
|
Rust projects from within the `rust` repo. Examples include Rust's fork of
|
|
`llvm-project` and many devtools such as `cargo`, `rust-analyzer` and `rls`.
|
|
|
|
Those projects are developed and maintained in an separate Git (and GitHub)
|
|
repository, and they have their own Git history/commits, issue tracker and PRs.
|
|
Submodules allow us to create some sort of embedded sub-repository inside the
|
|
`rust` repository and use them like they were directories in the `rust` repository.
|
|
|
|
Take `miri` for example. `miri` is maintained in the [`rust-lang/miri`] repository,
|
|
but it is used in `rust-lang/rust` by the compiler for const evaluation. We bring it
|
|
in `rust` as a submodule, in the `src/tools/miri` folder.
|
|
|
|
The contents of submodules are ignored by Git: submodules are in some sense isolated
|
|
from the rest of the repository. However, if you try to `cd src/tools/miri` and then
|
|
run `git status`:
|
|
|
|
```
|
|
HEAD detached at 3fafb835
|
|
nothing to commit, working tree clean
|
|
```
|
|
|
|
As far as git is concerned, you are no longer in the `rust` repo, but in the `miri` repo.
|
|
You will notice that we are in "detatched HEAD" state, i.e. not on a branch but on a
|
|
particular commit.
|
|
|
|
This is because, like any dependency, we want to be able to control which version to use.
|
|
Submodules allow us to do just that: every submodule is "pinned" to a certain
|
|
commit, which doesn't change unless modified manually. If you use `git checkout <commit>`
|
|
in the `miri` directory and go back to the `rust` directory, you can stage this
|
|
change like any other. This is usually done by the maintainers of the
|
|
project, and looks like [this][miri-update].
|
|
|
|
Git submodules take some time to get used to, so don't worry if it isn't perfectly
|
|
clear yet. You will rarely have to use them directly and, again, you don't need
|
|
to know everything about submodules to contribute to Rust. Just know that they
|
|
exist and that they correspond to some sort of embedded subrepository dependency
|
|
that Git can nicely and fairly conveniently handle for us.
|
|
|
|
[Git submodules]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
|
|
[`rust-toolstate`]: https://rust-lang-nursery.github.io/rust-toolstate/
|
|
[`rust-lang/miri`]: https://github.com/rust-lang/miri
|
|
[miri-update]: https://github.com/rust-lang/rust/pull/77500/files
|