zulip/docs/git/using.md

458 lines
16 KiB
Markdown

# Using Git as you work
## Know what branch you're working on
When using Git, it's important to know which branch you currently have checked
out because most Git commands implicitly operate on the current branch. You can
determine the currently checked out branch several ways.
One way is with [git status][gitbook-git-status]:
```console
$ git status
On branch issue-demo
nothing to commit, working directory clean
```
Another is with [git branch][gitbook-git-branch] which will display all local
branches, with a star next to the current branch:
```console
$ git branch
* issue-demo
main
```
To see even more information about your branches, including remote branches,
use `git branch -vva`:
```console
$ git branch -vva
* issue-123 517468b troubleshooting tip about provisioning
main f0eaee6 [origin/main] bug: Fix traceback in get_missed_message_token_from_address().
remotes/origin/HEAD -> origin/main
remotes/origin/issue-1234 4aeccb7 Another test commit, with longer message.
remotes/origin/main f0eaee6 bug: Fix traceback in get_missed_message_token_from_address().
remotes/upstream/main dbeab6a Optimize checks of test database state by moving into Python.
```
You can also configure [Bash][gitbook-other-envs-bash] and
[Zsh][gitbook-other-envs-zsh] to display the current branch in your prompt.
## Keep your fork up to date
You'll want to [keep your fork][github-help-sync-fork] up-to-date with changes
from Zulip's main repositories.
**Note about `git pull`**: You might be used to using `git pull` on other
projects. With Zulip, because we don't use merge commits, you'll want to avoid
it. Rather than using `git pull`, which by default is a shortcut for
`git fetch && git merge FETCH_HEAD` ([docs][gitbook-git-pull]), you
should use `git fetch` and then `git rebase`.
First, [fetch][gitbook-fetch] changes from Zulip's upstream repository you
configured in the step above:
```console
$ git fetch upstream
```
Next, check out your `main` branch and [rebase][gitbook-git-rebase] it on top
of `upstream/main`:
```console
$ git checkout main
Switched to branch 'main'
$ git rebase upstream/main
```
This will rollback any changes you've made to `main`, update it from
`upstream/main`, and then re-apply your changes. Rebasing keeps the commit
history clean and readable.
When you're ready, [push your changes][github-help-push] to your remote fork.
Make sure you're in branch `main` and then run `git push`:
```console
$ git checkout main
$ git push origin main
```
You can keep any branch up to date using this method. If you're working on a
feature branch (see next section), which we recommend, you would change the
command slightly, using the name of your `feature-branch` rather than `main`:
```console
$ git checkout feature-branch
Switched to branch 'feature-branch'
$ git rebase upstream/main
$ git push origin feature-branch
```
## Work on a feature branch
One way to keep your work organized is to create a branch for each issue or
feature. Recall from [how Git is different][how-git-is-different] that
**Git is designed for lightweight branching and merging.** You can and should
create as many branches as you'd like.
First, make sure your `main` branch is up-to-date with Zulip upstream ([see
how][zulip-git-guide-up-to-date]).
Next, from your `main` branch, create a new tracking branch, providing a
descriptive name for your feature branch:
```console
$ git checkout main
Switched to branch 'main'
$ git checkout -b issue-1755-fail2ban
Switched to a new branch 'issue-1755-fail2ban'
```
Alternatively, you can create a new branch explicitly based off
`upstream/main`:
```console
$ git checkout -b issue-1755-fail2ban upstream/main
Switched to a new branch 'issue-1755-fail2ban'
```
Now you're ready to work on the issue or feature.
## Run linters and tests locally
In addition to having GitHub Actions run tests and linters each time you
push a new commit, you can also run them locally. See
[testing](../testing/testing.md) for details.
## Stage changes
Recall that files tracked with Git have possible three states:
committed, modified, and staged.
To prepare a commit, first add the files with changes that you want
to include in your commit to your staging area. You _add_ both new files and
existing ones. You can also remove files from staging when necessary.
### Get status of working directory
To see what files in the working directory have changes that have not been
staged, use `git status`.
If you have no changes in the working directory, you'll see something like
this:
```console
$ git status
On branch issue-123
nothing to commit, working directory clean
```
If you have unstaged changes, you'll see something like this:
```console
On branch issue-123
Untracked files:
(use "git add <file>..." to include in what will be committed)
newfile.py
nothing added to commit but untracked files present (use "git add" to track)
```
### Stage additions with `git add`
To add changes to your staging area, use `git add <filename>`. Because
`git add` is all about staging the changes you want to commit, you use
it to add _new files_ as well as _files with changes_ to your staging
area.
Continuing our example from above, after we run `git add newfile.py`, we'll see
the following from `git status`:
```console
On branch issue-123
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: newfile.py
```
You can view the changes in files you have staged with `git diff --cached`. To
view changes to files you haven't yet staged, just use `git diff`.
If you want to add all changes in the working directory, use `git add -A`
([documentation][gitbook-add]).
You can also stage changes using your graphical Git client.
If you stage a file, you can undo it with `git reset HEAD <filename>`. Here's
an example where we stage a file `test3.txt` and then unstage it:
```console
$ git add test3.txt
On branch issue-1234
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: test3.txt
$ git reset HEAD test3.txt
$ git status
On branch issue-1234
Untracked files:
(use "git add <file>..." to include in what will be committed)
test3.txt
nothing added to commit but untracked files present (use "git add" to track)
```
### Stage deletions with `git rm`
To remove existing files from your repository, use `git rm`
([documentation][gitbook-rm]). This command can either stage the file for
removal from your repository AND delete it from your working directory or just
stage the file for deletion and leave it in your working directory.
To stage a file for deletion and **remove** it from your working directory, use
`git rm <filename>`:
```console
$ git rm test.txt
rm 'test.txt'
$ git status
On branch issue-1234
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: test.txt
$ ls test.txt
ls: No such file or directory
```
To stage a file for deletion and **keep** it in your working directory, use
`git rm --cached <filename>`:
```console
$ git rm --cached test2.txt
rm 'test2.txt'
$ git status
On branch issue-1234
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: test2.txt
$ ls test2.txt
test2.txt
```
If you stage a file for deletion with the `--cached` option, and haven't yet
run `git commit`, you can undo it with `git reset HEAD <filename>`:
```console
$ git reset HEAD test2.txt
```
Unfortunately, you can't restore a file deleted with `git rm` if you didn't use
the `--cache` option. However, `git rm` only deletes files it knows about.
Files you have never added to Git won't be deleted.
## Commit changes
When you've staged all your changes, you're ready to commit. You can do this
with `git commit -m "My commit message."` to include a commit message.
Here's an example of committing with the `-m` for a one-line commit message:
```console
$ git commit -m "Add a test commit for docs."
[issue-123 173e17a] Add a test commit for docs.
1 file changed, 1 insertion(+)
create mode 100644 newfile.py
```
You can also use `git commit` without the `-m` option and your editor to open,
allowing you to easily draft a multi-line commit message.
How long your commit message should be depends on where you are in your work.
Using short, one-line messages for commits related to in-progress work makes
sense. For a commit that you intend to be final or that encompasses a
significant amount or complex work, you should include a longer message.
Keep in mind that your commit should contain a 'minimal coherent idea' and have
a quality commit message. See Zulip docs [Commit
Discipline][zulip-rtd-commit-discipline] and [Commit
messages][zulip-rtd-commit-messages] for details.
Here's an example of a longer commit message that will be used for a pull request:
```text
Integrate Fail2Ban.
Updates Zulip logging to put an unambiguous entry into the logs such
that fail2ban can be configured to look for these entries.
Tested on my local Ubuntu development server, but would appreciate
someone testing on a production install with more users.
Fixes #1755.
```
The first line is the summary. It's a complete sentence, ending in a period. It
uses a present-tense action verb, "Integrate", rather than "Integrates" or
"Integrating".
The following paragraphs are full prose and explain why and how the change was
made. It explains what testing was done and asks specifically for further
testing in a more production-like environment.
The final paragraph indicates that this commit addresses and fixes issue #1755.
When you submit your pull request, GitHub will detect and link this reference
to the appropriate issue. Once your commit is merged into `upstream/main`, GitHub
will automatically close the referenced issue. See [Closing issues via commit
messages][github-help-closing-issues] for details.
Note in particular that GitHub's regular expressions for this feature
are sloppy, so phrases like `Partially fixes #1234` will automatically
close the issue. Phrases like `Fixes part of #1234` are a good
alternative.
Make as many commits as you need to address the issue or implement your feature.
## Push your commits to GitHub
As you're working, it's a good idea to frequently push your changes to GitHub.
This ensures your work is backed up should something happen to your local
machine and allows others to follow your progress. It also allows you to
[work from multiple computers][self-multiple-computers] without losing work.
Pushing to a feature branch is just like pushing to `main`:
```console
$ git push origin <branch-name>
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 658 bytes | 0 bytes/s, done.
Total 6 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 1 local objects.
To git@github.com:christi3k/zulip.git
* [new branch] issue-demo -> issue-demo
```
If you want to see what Git will do without actually performing the push, add
the `-n` (dry-run) option: `git push -n origin <branch-name>`. If everything
looks good, re-run the push command without `-n`.
If the feature branch does not already exist on GitHub, it will be created when
you push and you'll see `* [new branch]` in the command output.
## Examine and tidy your commit history
Examining your commit history prior to submitting your pull request is a good
idea. Is it tidy such that each commit represents a minimally coherent idea
(see [commit discipline][zulip-rtd-commit-discipline])? Do your commit messages
follow [Zulip's style][zulip-rtd-commit-messages]? Will the person reviewing
your commit history be able to clearly understand your progression of work?
On the command line, you can use the `git log` command to display an easy to
read list of your commits:
```console
$ git log --all --graph --oneline --decorate
* 4f8d75d (HEAD -> 1754-docs-add-git-workflow) docs: Add details about configuring Travis CI.
* bfb2433 (origin/1754-docs-add-git-workflow) docs: Add section for keeping fork up-to-date to Git Guide.
* 4fe10f8 docs: Add sections for creating and configuring fork to Git Guide.
* 985116b docs: Add graphic client recs to Git Guide.
* 3c40103 docs: Add stubs for remaining Git Guide sections.
* fc2c01e docs: Add git guide quickstart.
| * f0eaee6 (upstream/main) bug: Fix traceback in get_missed_message_token_from_address().
```
Alternatively, use your graphical client to view the history for your feature branch.
If you need to update any of your commits, you can do so with an interactive
[rebase][github-help-rebase]. Common reasons to use an interactive rebase
include:
- squashing several commits into fewer commits
- splitting a single commit into two or more
- rewriting one or more commit messages
There is ample documentation on how to rebase, so we won't go into details
here. We recommend starting with GitHub's help article on
[rebasing][github-help-rebase] and then consulting Git's documentation for
[git-rebase][gitbook-git-rebase] if you need more details.
If all you need to do is edit the commit message for your last commit, you can
do that with `git commit --amend`. See [Git Basics - Undoing
Things][gitbook-basics-undoing] for details on this and other useful commands.
## Force-push changes to GitHub after you've altered your history
Any time you alter history for commits you have already pushed to GitHub,
you'll need to prefix the name of your branch with a `+`. Without this, your
updates will be rejected with a message such as:
```console
$ git push origin 1754-docs-add-git-workflow
To git@github.com:christi3k/zulip.git
! [rejected] 1754-docs-add-git-workflow -> 1754-docs-add-git-workflow (non-fast-forward)
error: failed to push some refs to 'git@github.com:christi3k/zulip.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.
```
Re-running the command with `+<branch>` allows the push to continue by
re-writing the history for the remote repository:
```console
$ git push origin +1754-docs-add-git-workflow
Counting objects: 12, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (12/12), 3.71 KiB | 0 bytes/s, done.
Total 12 (delta 8), reused 0 (delta 0)
remote: Resolving deltas: 100% (8/8), completed with 2 local objects.
To git@github.com:christi3k/zulip.git
+ 2d49e2d...bfb2433 1754-docs-add-git-workflow -> 1754-docs-add-git-workflow (forced update)
```
This is perfectly okay to do on your own feature branches, especially if you're
the only one making changes to the branch. If others are working along with
you, they might run into complications when they retrieve your changes because
anyone who has based their changes off a branch you rebase will have to do a
complicated rebase.
[gitbook-add]: https://git-scm.com/docs/git-add
[gitbook-basics-undoing]: https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things
[gitbook-fetch]: https://git-scm.com/docs/git-fetch
[gitbook-git-branch]: https://git-scm.com/docs/git-branch
[gitbook-git-pull]: https://git-scm.com/docs/git-pull
[gitbook-git-rebase]: https://git-scm.com/docs/git-rebase
[gitbook-git-status]: https://git-scm.com/docs/git-status
[gitbook-other-envs-bash]: https://git-scm.com/book/en/v2/Git-in-Other-Environments-Git-in-Bash
[gitbook-other-envs-zsh]: https://git-scm.com/book/en/v2/Git-in-Other-Environments-Git-in-Zsh
[gitbook-rm]: https://git-scm.com/docs/git-rm
[github-help-closing-issues]: https://help.github.com/en/articles/closing-issues-via-commit-messages
[github-help-push]: https://help.github.com/en/articles/pushing-to-a-remote
[github-help-rebase]: https://help.github.com/en/articles/using-git-rebase
[github-help-sync-fork]: https://help.github.com/en/articles/syncing-a-fork
[how-git-is-different]: the-git-difference.md
[self-multiple-computers]: troubleshooting.md#working-from-multiple-computers
[zulip-git-guide-up-to-date]: #keep-your-fork-up-to-date
[zulip-rtd-commit-discipline]: ../contributing/commit-discipline.md
[zulip-rtd-commit-messages]: ../contributing/commit-discipline.md