Git is a powerful yet complex version control system. Even for contributors
experienced at using version control, it can be confusing. The good news is
that nearly all Git actions add information to the Git database, rather than
removing it. As such, it’s hard to make Git perform actions that you can’t
undo. However, git can’t undo what it doesn’t know about, so it’s a good
practice to frequently commit your changes and frequently push your commits to
your remote repository.

A merge commit is a special type of commit that has two parent commits. It’s
created by Git when you merge one branch into another and the last commit on
your current branch is not a direct ancestor of the branch you are trying to
merge in. This happens quite often in a busy project like Zulip where there are
many contributors because upstream/zulip will have new commits while you’re
working on a feature or bugfix. In order for Git to merge your changes and the
changes that have occurred on zulip/upstream since you first started your work,
it must perform a three-way merge and create a merge commit.

A merge commit is usually created when you’ve run gitpull or gitmerge.
You’ll know you’re creating a merge commit if you’re prompted for a commit
message and the default is something like this:

Mergebranch'master'ofhttps://github.com/zulip/zulip# Please enter a commit message to explain why this merge is necessary,# especially if it merges an updated upstream into a topic branch.## Lines starting with '#' will be ignored, and an empty message aborts# the commit.

Reflog output will be long. The most recent git refs will be listed at the top.
In the example above e5f8211HEAD@{0}: is the merge commit made automatically
by gitpull and 13bea0eHEAD@{1}: is the last commit I made before running
gitpull, the commit that I want to rollback to.

Once you’d identified the ref you want to revert to, you can do so with git
reset:

$ git reset --hard 13bea0e
HEAD is now at 13bea0e test commit for docs.

Important:gitreset--hard<commit> will discard all changes in your
working directory and index since the commit you’re resetting to with
<commit>. This is the main way you can lose work in Git. If you need to
keep any changes that are in your working directory or that you have committed,
use gitreset--merge<commit> instead.

You can also use the relative reflog HEAD@{1} instead of the commit hash,
just keep in mind that this changes as you run git commands.

Now when you look at the output of gitreflog, you should see that the tip of your branch points to your
last commit 13bea0e before the merge:

We’ve mentioned you can use gitreset--hard to rollback to a previous
commit. What if you run gitreset--hard and then realize you actually need
one or more of the commits you just discarded? No problem, you can restore them
with gitcherry-pick (docs).

For example, let’s say you just committed “some work” and your gitlog looks
like this:

One situation in which gitrebase will fail and require you to intervene is
when your change, which git will try to re-apply on top of new commits from
which ever branch you are rebasing on top of, is to code that has been changed
by those new commits.

For example, while I’m working on a file, another contributor makes a change to
that file, submits a pull request and has their code merged into master.
Usually this is not a problem, but in this case the other contributor made a
change to a part of the file I also want to change. When I try to bring my
branch up to date with gitfetch and then gitrebaseupstream/master, I see
the following:

To fix, open all the files with conflicts in your editor and decide which edits
should be applied. Git uses standard conflict-resolution (<<<<<<<, =======,
and >>>>>>>) markers to indicate where in files there are conflicts.

Tip: You can see recent changes made to a file by running the following
commands:

gitfetchupstreamgitlog-pupstream/master--/path/to/file

You can use this to compare the changes that you have made to a file with the
ones in upstream, helping you avoid undoing changes from a previous commit when
you are rebasing.

Once you’ve done that, save the file(s), stage them with gitadd and then
continue the rebase with gitrebase--continue:

Working from multiple computers with Zulip and Git is fine, but you’ll need to
pay attention and do a bit of work to ensure all of your work is readily
available.

Recall that most Git operations are local. When you commit your changes with
gitcommit they are safely stored in your local Git database only. That is,
until you push the commits to GitHub, they are only available on the computer
where you committed them.

So, before you stop working for the day, or before you switch computers, push
all of your commits to GitHub with gitpush:

If you have already made commits on the second computer that you need to
keep, you’ll need to use gitlogFETCH_HEAD to identify that hashes of the
commits you want to keep and then gitcherry-pick<commit> those commits into
whichever branch you need to update.