Tips on using Git.

Here's a quick guide—for my own use as much as anyone else's—to the Git tasks I most often need to accomplish.

Git Tasks

This section shows you how to link individual Git commands together in order to accomplish a particular task or set of tasks.

Checking Branch Status

This will be the command you use most often. What it shows depends on the state of your current local branch. We'll see lots of examples below.

$ git status

Check Your Git Version

Naturally, Git's behavior has changed over time. For our purposes, this mostly comes down to some slight changes in output and a couple of new commands.

$ git --version
git version 2.23.0

Download a Repository

Downloading an existing git repository is called "cloning" the repository. It's called "cloning" because of the way git works--you actually keep a local copy of that repository, and commit your local changes there. (This is unlike other version control systems, which are centralized rather than distributed.)

The first step in this task is getting a URL for the existing git repository. Once you have that, you can follow the example below.

$ git clone https://github.com/someProject/SomeProject.git
Cloning into 'SomeProject'...
remote: Counting objects: 181, done.
remote: Compressing objects: 100% (134/134), done.
remote: Total 181 (delta 23), reused 179 (delta 21), pack-reused 0
Receiving objects: 100% (181/181), 136.50 KiB | 0 bytes/s, done.
Resolving deltas: 100% (23/23), done.
Checking connectivity... done.

$ cd SomeProject

Use git status: to verify the clone.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working directory clean

Commit Some Work

In Git, committing is a two-step process:

  1. Selecting the files you want to commit, called "staging."
  2. Committing the files which have been staged.

Use git status to check what has changed on your current branch.

Use "git status" to see where things stand—what you've changed, what you've staged, what's ready to be committed.

$ git status

On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

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:   requirements.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    djangosvc/newFile

no changes added to commit (use "git add" and/or "git commit -a")

The call to "git status" has returned a lot of useful information, telling you both where things stand and helping you figure out what your next command should be.

  • "On branch master" indicates which local branch you're currently working with. The next two lines tell you that you have already committed some changes to your local repository, but they have not been pushed to the repository on the network (the one you share with other developers).
  • The lines beneath the heading "Changes not staged for commit" indicate that you have modified some files, and these changes have not yet been staged for a commit.
  • "Untracked" files are files not in the repository. Usually they will be files that you've recently created, and which you'll want to add to the repository eventually. (But you can also chose to ignore them if you don't want to commit them.)

Use git add to stage files.

$ git add requirements.txt

$ git add djangosvc/newFile

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD ..." to unstage)

    modified:   requirements.txt
    new file:   djangosvc/newFile

Compare the output of the last git status command to that of the first. Now everything which has been changed is staged. The next time I commit, my staged changes —and only my staged changes—will be committed to my local copy (my clone) of the repository.

Use git commit -m to commit staged items. Committing with the -m flag (standing for "message") is usually the easiest, though other options are available. Put your commit statement immediately after the flag, in quotes.

$ git commit -m "Insert explanatory message here."
(...)

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

Push Changes to the Shared Repository

As we've mentioned, Git always has you working on a local copy of a branch in the central repository. If you're the only one working on a branch, then you don't need to worry about conflicts with other developers' changes. You can push your changes to the central repository with no problem.

But, depending on your team's workflow, you may be sharing a branch with other developers. In this case, you'll need to merge the other developers' changes into your copy of the branch before you push to the central repository. See Merge a Branch into Your Branch before going to the next step in this section.

Use git status: to verify what branch you're on.

$ git status
On branch newAlgorithm
Your branch is ahead of 'origin/newAlgorithm' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

In this example, all changes have already been added and committed locally. But you could also have some changes not added, or even some changes not committed. The behavior would be the same as in this case; namely, only committed work would be pushed.

Use git push to "publish" committed changes on the current branch to the shared remote repository. As of Git 2.0, running this command with no arguments pushes only the current branch's commits to the branch in the remote repository that it is tracking. Previous versions would push commits made on all local branches.

$ git push
(...)
Counting objects: 106, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (28/28), done.
Writing objects: 100% (49/49), 4.40 KiB | 0 bytes/s, done.
Total 49 (delta 11), reused 0 (delta 0)
(...)

With older version of Git, you'll most likely want to specify the branch. Here we assume that origin is where the tracked branch resides.

$ git push origin <branchName>

Use git status to verify your push.

$ git status
On branch newAlgorithm
Your branch is up-to-date with 'origin/newAlgorithm'.

Undo Changes

How to undo a change depends on the change you want to undo. In this section, we cover a couple of different situations:

  • Undoing an uncommitted local change to a file.
  • Undoing the uncommitted creation of a new file.
  • Undoing a git add.
  • Temporarily going back to a previous commit.
  • Permanently going back to a previous commit.


Undoing Unstaged Changes

Sometimes you want to undo changes to an unstaged working copy. In this example, we've made two changes to our working copy: we've modified a file, and we've created a new file.

$ git status   
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:   requirements.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    djangosvc/newFile

no changes added to commit (use "git add" and/or "git commit -a")

Undoing an Uncommitted File Modification

In all versions of Git, you can undo a change with git checkout -- <file>. In more recent versions, you can also use git restore <file>.

$ git checkout -- requirements.txt
$ git restore requirements.txt

In both cases, you'll find that the modified file no longer appears as part of the output of the git status command.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    djangosvc/newFile

nothing added to commit but untracked files present (use "git add" to track)

Removing an Uncommitted New File

To remove a new file, simply delete it from your file system as your normally would outside of Git.

$ rm djangosvc/newFile 

Now git status shows no changes on the current branch.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean


Undoing a 'git add'

Undo an Add or a Commit to a Single File

Suppose you've staged a file with git add and now you've changed your mind and you want to unstage it.

Use git reset filename.txt (or git reset to "un-add" all the added files).

$ git reset filename.txt

Undo Everything that Hasn't Been Pushed

You can undo all your local changes with the following. Be aware, though, that this cannot be undone.

$ git reset --hard

Reverting Back to a Previous Commit

Sometimes you want to go back to a previous version of the code. The first step is to do a "git log" to find the version you are looking for.

$ git log --since yesterday
(...)

Pick the version you want to revert to.

Temporarily Reverting

Sometimes you wonder how on earth some piece of code used to work. You want to get a temporary copy so that, for example, you can step through the code with a debugger.

For temporary reverting, it's best to use a new branch. Make sure you don't have any uncommitted code.

$  git checkout -b old_1061e 1061e5b0a4cbac0fb54fb1acd9db53931ffb1c68
Switched to a new branch 'old_1061e'

~/Documents/workspace/eliza$ git branch
  master
* old_1061e

Permantantly Reverting

Sometimes things are so messed up that you want to roll all changes back to a previous version. Start by following the steps above (using "git log" to identify the version number you want). Then, instead of checking out to a new branch, use "reset". Make sure you don't have any uncommitted code—'cause you're about to lose it.

$ git reset --hard 1061e5b0a4cbac0fb54fb1acd9db53931ffb1c68

Tell Git to Ignore Some Files

Some files created in software development should not be committed to the repository. For example, files related to a particular IDE, for instance PyCharm or Visual Studio Code, probably should not be committed if some developers on the project do not use that IDE.

In this example, our status check shows that Git detects untracked __pycache__ directories. It also doesn't know that the venv directory is for my virtual environment, and therefore should not be committed.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    djangosvc/djangosvc/__pycache__/
    djangosvc/polls/__pycache__/
    venv/

no changes added to commit (use "git add" and/or "git commit -a")

Edit .gitignore to add files to the ignore list. In your project's root, create a file named .gitignore if it doesn't exist, then open it for editing. (For those of you on Windows systems, be sure that the OS doesn't automatically add the .txt extension when you create the file.) Add these lines:

/venv/
__pycache__/

The backslash before venv means "ignore only the venv directory that is in root"; no backslash means "ignore this directory in all cases." Other patterns exist for other cases.

Use git commit -m to commit your changes. If you've just created the .gitignore file, you'll have to add it before you can commit. Here's the whole routine, with status checks before and after.

$ git status
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:   .gitignore

no changes added to commit (use "git add" and/or "git commit -a")

$ git add .gitignore 

$ git commit -m "Updated .gitignore."
[master c82603b] Updated .gitignore.
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git status                         
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

Stop Tracking a File

What often happens is that a file gets committed that you later realize should have been ignored. Then you try to ignore it by adding the file to your .gitignore file—but .gitignore only stops files from appearing as changed. It doesn't remove files from the repository. What you need to do is tell Git to stop tracking the file.

$ git rm --cached <file>

Recursively Stop Tracking Files Having the Same Name

(Here are some old notes I've found on how to stop tracking files which often appear multiple times in a project. Use these notes with caution—I haven't had a chance to verify them.)

I work on a Mac, where each directory has a "hidden" .DS_Store file. The problem is, sometimes I end up creating a repository before I've remembered to have these files ignored.

First let's see which files are being tracked by Git.

$ git ls-tree --full-tree --name-only -r HEAD
.DS_Store
src/main/java/.DS_Store
src/main/java/com/.DS_Store
src/main/java/com/interdataworking/.DS_Store
src/main/java/com/interdataworking/UntypedGateway.class
src/main/java/com/interdataworking/UntypedGateway.java
(...)

As you can see, there are three file types listed here, and it turns out I want my repository to store only the .java files.

$ git rm --cached .DS_Store
rm '.DS_Store'

$ git ls-tree --full-tree --name-only -r HEAD
src/main/java/.DS_Store
src/main/java/com/.DS_Store
src/main/java/com/interdataworking/.DS_Store
src/main/java/com/interdataworking/UntypedGateway.class
src/main/java/com/interdataworking/UntypedGateway.java
(...)

The git rm --cached command worked, but it looks as though it is useful only for a single file.

The next line, a mix of shell commands and git commands, will perform a git rm recursively, that is, on all the files whose names are .DS_Store.

The I use the same approach to remove from the repository all files whose names end in ".class".

$ find . -name .DS_Store -print0 | xargs -0 git rm -f --ignore-unmatch
rm 'src/main/java/.DS_Store'
rm 'src/main/java/com/.DS_Store'
rm 'src/main/java/com/interdataworking/.DS_Store'

$ git ls-tree --full-tree --name-only -r HEAD
src/main/java/com/interdataworking/UntypedGateway.class
src/main/java/com/interdataworking/UntypedGateway.java
(...)

$ find . -name *.class -print0 | xargs -0 git rm -f --ignore-unmatch
rm 'src/main/java/com/interdataworking/UntypedGateway.class'
(...)

$ git ls-tree --full-tree --name-only -r HEAD
src/main/java/com/interdataworking/UntypedGateway.java
(...)

Whew! So finally the unwanted .class and .DS_Store files are untracked. Now let's make sure git permanently ignores them.

$ touch .gitignore

$ vim .gitignore

Edit this file so that it has two lines, with ".class" on the first and ".DS_Store" on the second.

$ cat .gitignore
.class
.DS_Store

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    .DS_Store
    deleted:    src/main/java/.DS_Store
    deleted:    src/main/java/com/.DS_Store
    deleted:    src/main/java/com/interdataworking/.DS_Store
    deleted:    src/main/java/com/interdataworking/UntypedGateway.class
(...)

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore

The status check shows that we still have to add the .gitignore file, and then commit all of our changes.

$ git add .gitignore

$ git commit -m "Deleting unwanted files from repo. Creating .gitignore."
[master b2d6f98] Deleting unwanted files from repo. Creating .gitignore.
 126 files changed, 2 insertions(+)
 delete mode 100644 .DS_Store
 create mode 100644 .gitignore
 delete mode 100644 src/main/java/.DS_Store
 delete mode 100644 src/main/java/com/.DS_Store
 delete mode 100644 src/main/java/com/interdataworking/.DS_Store
(...)

$ git status
On branch master
nothing to commit, working directory clean

$ git ls-tree --full-tree --name-only -r HEAD
.gitignore
src/main/java/com/interdataworking/UntypedGateway.java
(...)

We committed our changes, used git status to verify that everything has been committed, then finally used git ls-tree --full-tree --name-only -r HEAD to make sure that we are tracking only .java files. Fini.

List All Files Being Tracked

The following two commands will list the files being tracked in a Git branch. Output can be quite verbose, depending on the size of the project.

$ git ls-tree -r master --name-only         
(...)

$ git ls-tree --full-tree --name-only -r HEAD
(...)

Create a New Branch

We'll cover three scenarios here:

  • You want to create a new branch from an existing branch.
  • You have made some changes on some local branch, and you want to create a new local branch with them.
  • You want to put some code into a new local Git repository.

Create a New Local Branch from an Existing Branch

This is the most common case—you want to start a new task in a new branch.

Very often you'll be starting with your local copy of master.

$ git checkout -b <newBranch> master

For other starting branches, the more general pattern to follow will be:

$ git checkout -b <newBranch> <startingBranch>

Note that startingBranch could be either local or remote. If remote, be sure to use the full remote name, and not the name of your local copy of that remote branch.


Create a New Local Branch with Current Changes

In this situation, you've started some changes in an existing branch and, suddenly, you realize the situation requires a completely new branch.

Note that these steps work only if you haven't yet made any commits.

$ git branch <newBranch>

$ git checkout <newBranch>


Create a New Local Branch with Local Changes

Suppose there's some open source code you want to have a look at, and suppose that, while having a look at it you see some things you'd like to change—and you go ahead and make them. I've done that, and then I've been very annoyed with myself for having no easy way, at some later time, to see the changes I've made.

The smart thing to do is to put the original version of the code into a repository before you make any changes. It's a simple three-step process, though I've added a few other commands so that we can see what's happening. Once you've put the original version into a repository, you can go ahead and start modifying it, knowing that you'll always be able to track the changes you've made.

We start in a directory with some open source code. In this case, it's all in a directory called src, but that doesn't change the steps you take—what matters is that the files are not currently in a Git repository.

$ ls
src

$ ls src
somefile

$ git status
fatal: not a git repository (or any of the parent directories): .git

Initialize a new repository.

$ git init            
Initialized empty Git repository in /Users/jkurlandski/workspace/testProject/.git/

$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    src/

nothing added to commit but untracked files present (use "git add" to track)

The branch in the new repository will always be named master. Don't confuse this master branch with some other branch on your computer also called master that is tracking an origin/master branch in a remote repository. (And don't try specifying a different name—for example, with $ git init testProject—because that will create a new repo in a sub-directory called testProject, and the branch in that repo will still be called master.)

Use git add to indicate you want the repository to track the files in this directory, then commit.

$ git add .

$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
    new file:   src/somefile

$ git commit -m "Original state of project."
[master (root-commit) 02417d8] Original state of project.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 src/somefile

$ git status
On branch master
nothing to commit, working tree clean

At this point we can ask the new repository to list the files it is tracking. Here are two ways of doing that. (But note that in this case they return the same thing.)

$ git ls-tree -r master --name-only         
src/somefile

$ git ls-tree --full-tree --name-only -r HEAD
src/somefile

The three key commands are git init, git add ., and git commit -m. Now you can make all the changes you want and, not only do you have a record of each and every one, but you also have the ability to roll things back to a previous version.

Work with Local and Remote Branches

Remote Repositories

Use git remote to see the remote repository/repositories.

$ git remote
origin


$ git remote -v
origin  https://me@repo.com/django1.git (fetch)
origin  https://me@repo.com/django1.git (push)

Use git ls-remote <remote> to view all the branches on a remote repository.

$ git ls-remote origin
(...)
e3aad0f828ba6717d80cb2d835328185c86082a6    refs/heads/usertype
(...)


Branch Basics

Use git branch to view all local branches.

$ git branch
* usertype
master

The asterisk indicates the branch you're currently working on.

Use git branch -a to view both local and remote-tracking branches. Remote-tracking branches are references (sometimes called "snapshots") to the state of remote branches.

$ git branch -a
* usertype
master
remotes/origin/master
remotes/origin/usertype
remotes/origin/newBranch

Use git branch -r to view only the remote-tracking branches (i.e. those that you have a local snapshot of.

$ git branch -r
origin/master
origin/usertype
origin/newBranch

Use git branch -vv: to verify tracking.

Generally you want your local clone to "track" a branch in the shared network repository. One advantage of this is that your Git commands won't have to specify which network repository they should operate on—it can be assumed from the branch being tracked.

$ git branch -vv
  master                        d0b9014 [origin/master] Update documentation.
* usertype                      7c6586e [origin/usertype] Modify user type.

The -vv in the last command means "doubly verbose." The verbiage at the far right indicates the message pushed with that branch's last commit.


Downloading ("pulling") New Remote-Tracking Branches

If you know a branch exists on a remote repository but it doesn't appear in your local view, first see if it appears in a full listing of the remote.

$ git ls-remote origin
(...)

If it appears in this output, then use git fetch to bring it locally:

$ git fetch
(...)
 * [new branch]      job-tracker -> origin/job-tracker
 * [new branch]      design-doc  -> origin/design-doc

Save ("stash") Your Work For Later

Sometimes you're in the middle of something when, suddenly, you have to do something else. Maybe that "something else" is change to a different branch; maybe it's simply to do a pull from some branch in the remote repository. If you're not ready to commit what you're currently working on, the next best way to save it is with git stash.

// First check status.
$ git status
On branch sae
Your branch is up-to-date with 'origin/sae'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   shortanswer/Wiki.java

no changes added to commit (use "git add" and/or "git commit -a")

If git status shows untracked files, you have a few options:

  • Figure out which version of Git you have, and follow the advice on this Stack Overflow page. Note especially that, in older versions of Git, the preferred advice could wipe out entire directories of ignored files.
  • Alternatively, play it safe and use git add to temporarily add the untracked files to your working copy. At a later point—when you return to working on the files you are now stashing—you can undo the git add with git reset filename.txt (or git reset to "un-add" all the added files).

Once you have only modified or new files, you're ready to stash. You can either use git stash, or git stash save "message".

$ git stash save "New short answer."
Saved working directory and index state On sae: New short answer.

// Verify that your status is now "clean".
$ git status
On branch sae
Your branch is up-to-date with 'origin/sae'.

nothing to commit, working directory clean

Now you can do whatever work you need to do. When finished, pop what you've stashed.

$ git stash pop
On branch sae
Your branch is up-to-date with 'origin/sae'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   shortanswer/Wiki.java

no changes added to commit (use "git add" and/or "git commit -a")

Dropped refs/stash@{0} (c4480e1375987e0881fe3708fdb1576ff56eefdf)

The git stash command has a number of options that are worth exploring.


View and Clear the Stash

After you've stashed a lot of stuff, the stash can get filled with a lot of garbage, which can be annoying if you mistakenly pop something. These commands let you delete the stash after you have verified that it doesn't contain anything you need.

Other stash options:

  • Use git stash list to view your local stash.
  • Use git stash drop <id> to drop one of the elements.
  • Use git stash clear to delete everying in the stash.
$ git stash list
stash@{0}: On sae: 88dfb0b SAE:  New short answer.
stash@{1}: On sae: c1c59c4 New SAE eval( ) method.
stash@{2}: On sae: b2f2248 fix cache bug;

$ git stash drop 0
Dropped refs/stash@{0} (44f8cdcf38c7c8964d1d85f9deaf92d1f0c23050)

$ git stash list
stash@{0}: On sae: c1c59c4 New SAE eval( ) method.
stash@{1}: On sae: b2f2248 fix cache bug;

$ git stash clear

$ git stash list
// Returns nothing.

Compare ("diff") Branches and Files

Suppose you and your team are working on a branch. Suppose other team members have committed and pushed their work to a remote version of the branch. Do you want their changes? Maybe you do, if it's not going to mess up your work. How do you find out?

Subversion has a convenient svn status -u command that lets you compare your local working copy to the repository by listing the status of all the files that differ between the two copies. In Git, accomplishing the same task requires two steps:

  1. Use git fetch to retrieve a local snapshot of the remote branch.
  2. Use git diff <localBranch> <remoteBranch> --name-status to compare the retrieved snapshot to your local branch at the directory level.

(Before proceeding, it might be helpful to make sure you understand the difference between 'git fetch' and 'git pull'. In paticular, you shoud keep in mind that git fetch updates only the local snapshot of the remote branch—not the code itself.)

Start by verifying you're on the right branch, and it is tracking the branch you think it is.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Since you haven't retrieved the remote branch's snapshot, you know that doing a diff without fetch will show no changes.

$ git diff master origin/master --name-status

Nothing. Now do the fetch, then check status.

$ git fetch
(...)
69d536b..e15e2f0  master -> origin/master

$ git status
On branch master
Your branch is behind 'origin/master' by 4 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

Notice that, with the snapshot updated, the status check is now able to detect differences between your local branch and the remote. Now you're also ready to do your diff.

$ git diff master origin/master --name-status
A       djangosvc/API/migrations/0008_merge_20200330_3010.py
M       djangosvc/API/models.py


Diff Two Branches at the File Level

git diff without the --name-status argument will show you the diffs of two branches at a fine-grained level, giving you the line-by-line differences between two files.

$ git diff master origin/master
(...)

In most cases, what happens now is that Git opens its less-like environment.


To See the Diff for One File Only

To see the diff for one file only on remote branches, separate the branch name from the file path with a colon. In this example, the two branches are origin/fix-name-issue and origin/master.

$ git diff origin/fix-name-issue:djangosvc/API/models.py origin/master:djangosvc/API/models.py
(...)

Do the following if you want to do a diff on a local file.

Here we compare a single file in the current branch to the same file in the local version of the master branch.

$ git diff master -- API/models.py
(...)

And here we compare that same file in the current branch to the same file in the remote version of the master branch.

$ git diff origin/master -- API/models.py
(...)


See the Contents of an Incoming File Use git show to view the contents of the incoming file.

$ git show origin/master:djangosvc/API/models.py

Merge a Branch into Your Branch

In this Git task we cover two scenarios:

  • You're working on a local copy of a branch that is tracking a branch shared in a remote repository, and you want to bring changes there local.
  • You're working on a local branch, and you want to bring into your branch changes made recently to a shared branch in the remote repository that your local branch is not tracking.

Merging Changes in a Tracked Branch into Your Branch

Our example for this scenario is the master branch: you're working on a local copy of master, your teammates have made changes to the shared version of that branch, and you want to merge their changes into your local copy.

If you want to see first see what kinds of changes your teammates have made, see Compare ("diff") Branches and Files. But let's suppose that, for this example, you don't need to see the diffs before merging.

Start by verifying you're on the right branch, and it is tracking the branch you think it is.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Before proceeding, remove any untracked files. Commit or stash any changes.

If it's a branch I'm actively working on, I am leery of the automatic merge that a git pull results in. So I prefer to fetch.

$ git fetch
(...)
69d536b..e15e2f0  master -> origin/master

$ git status
On branch master
Your branch is behind 'origin/master' by 4 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

Merge the remote branch's changes into your current branch.

$ git merge --no-commit origin/master
(...)

Automatic merge went well; stopped before committing as requested

If the message is something other than "Automatic merge went well," then see the Resolve Merge Conflicts section.

$ git status
On branch 43-read-and-validate-xml-data
Your branch is up to date with 'origin/43-read-and-validate-xml-data'.

All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
(...)
Untracked files:
(use "git add <file>..." to include in what will be committed)

.pytest_cache/
.vscode.code-workspace
.vscode/
Storm_Orchestration/DustStorm/migrations/0002_auto_20191118_1828.py
Storm_Orchestration/IonStorm/migrations/0002_auto_20191118_1828.py
backup/
  • If there is nothing to merge, the merge step would look like this:
    $ git merge --no-commit master
    Already up to date.

Merging a Different Branch into Your Branch

Recall that, in this scenario, you are working on a local branch while members of your team are pushing to a shared remote branch that your branch is not tracking. For this example, we'll suppose that your branch is called sae and the branch shared in the remote repository is master.

Either commit your work or, if you can't commit, save ("stash") your work.

Use git branch -vv to check the state of local and remote branches.

$ git branch -vv
* sae            cd01005 [origin/enable-deletion: ahead 3] Enable deletion.
  master         baf79d3 [origin/master: behind 21] test

This tells you that you're are on the sae branch, and that there have been 21 commits to master since you branched off it.

Use git checkout to switch to the master branch.

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 21 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Don't be concerned if a git status at this point shows untracked files.

$ git status
On branch master
Your branch is behind 'origin/master' by 21 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)
Untracked files:
  (use "git add <file>..." to include in what will be committed)
 djangosvc/archive.sqlite3
 nothing added to commit but untracked files present (use "git add" to track)

Use git pull to copy the latest from the remote master branch locally. The output of this command can be quite verbose.

$ git pull
(...)

Now switch back to the branch you were working on.

$ git checkout sae
Switched to branch 'sae'
Your branch is ahead of 'origin/sae' by 3 commits.
  (use "git push" to publish your local commits)

Use git merge --no-commit master to merge the local master branch into your local branch.

There are three possibilities at this point:

  1. There will be nothing to merge.
  2. Git will decide to handle the merge itself.
  3. There will be conflicts.


1. If there is nothing to merge, the merge step would look like this:

$ git merge --no-commit master
Already up to date.


2. If Git decides to handle the merge itself, this is what you will see, and these are the steps you should take.

$ git merge --no-commit master
(...)
Automatic merge went well; stopped before committing as requested

$ git status
On branch enable-deletion
Your branch is up to date with 'origin/enable-deletion'.

All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
(...)
Untracked files:
(use "git add <file>..." to include in what will be committed)

someNewFile.py

Now is probably the best time to run your unit tests.

Assuming the tests all pass, use git add to add any new untracked files. Then commit your changes. Finally, in most cases you'll want to push your changes to the remote repo's version of your branch.

$ git add someNewFile.py

$ git commit -m "Merging with master."

$ git push
(...)


3. If there are conflicts, good luck to you. Let's hope that your teammates have been responsibly verifying their code with unit tests.

Here's how Git shows you there are merge conflicts.

$ git merge --no-commit master
Auto-merging djangosvc/test/test_api_models.py
CONFLICT (content): Merge conflict in djangosvc/test/test_api_models.py
Auto-merging djangosvc/API/views.py
CONFLICT (content): Merge conflict in djangosvc/API/views.py
Automatic merge failed; fix conflicts and then commit the result.

See the Resolve Merge Conflicts section for some advice.

After resolving the merge conflicts, don't forget to run your unit tests. Then commit all your merge conflict fixes. Finally, in most cases you'll want to push your changes to the remote repo's version of your branch.

$ git commit -m "Merging with master."

$ git push
(...)

Resolve Merge Conflicts

An unpleasant but almost unavoidable consequence of working on a team is that, sooner or later, two developers will separately change the same piece of code. When Git can't safely merge their changes, it declares the file to be a "Conflict."


Assessing a Merge Conflict

The following is likely to be your first notice of a conflict.

$ git merge --no-commit master
Auto-merging djangosvc/test/test_api_models.py
CONFLICT (content): Merge conflict in djangosvc/test/test_api_models.py
Auto-merging djangosvc/API/views.py
CONFLICT (content): Merge conflict in djangosvc/API/views.py
Automatic merge failed; fix conflicts and then commit the result.

The git status command might show some files as having been successfully merged.

$ git status
On branch endable-deletion
Your branch is ahead of 'origin/endable-deletion' by 3 commits.
  (use "git push" to publish your local commits)
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)
Changes to be committed:
 modified:   .gitignore
 new file:   djangosvc/services/delete.py
Unmerged paths:
  (use "git add <file>..." to mark resolution)
 both modified:   djangosvc/API/views.py
 both modified:   djangosvc/test/test_api_models.py

The output above tells you a couple of things worth noting:

  • .gitignore and delete.py have been successfully merged.
  • views.py and test_api_models.py are in a conflict state.


Fixing Merge Conflicts

A good resource for resolving merge conflicts is the GitHub help page for this task. Even though it was written specifically for GitHub users, it can serve as a guide for the problem in general.

As the help page indicates, when it discovers a merge conflict, Git inserts lines into the conflicted file. I can't improve on the page's explanation, so I'll quote from it directly:

To see the beginning of the merge conflict in your file, search the file for the conflict marker <<<<<<<. When you open the file in your text editor, you'll see the changes from the HEAD or base branch after the line <<<<<<< HEAD. Next, you'll see =======, which divides your changes from the changes in the other branch, followed by >>>>>>> BRANCH-NAME. In this example, one person wrote "open an issue" in the base or HEAD branch and another person wrote "ask your question in IRC" in the compare branch or branch-a.

If you have questions, please
<<<<<<< HEAD
open an issue
=======
ask your question in IRC.
>>>>>>> branch-a

In Java and similar languages, the lines will cause a compilation error; in Python, a runtime error.

For each conflict inserted into the file, you have three options:

  1. Keep your changes.
  2. Keep the other branch's (the "incoming") changes.
  3. Make a new change, often done by incorporating parts of the two separate changes.

Delete the conflict markers <<<<<<<, =======, >>>>>>> and make the changes you want in the final merge. In this example, both changes are incorporated into the final merge:

If you have questions, please open an issue or ask in our IRC channel if it's more urgent.

You will probably find that an IDE can help you resolve conflicts. For example, Visual Studio Code displays tiny buttons you can click on with the labels Accept Current Change, Accept Incoming Change, Accept Both Changes and Compare Changes.


Wrapping up Your Merge Conflict Fixes

For each file having a merge conflict, you have to add it to the staging area.

$ git add djangosvc/API/views.py

$ git add djangosvc/test/test_api_models.py

Don't forget to run your unit tests. Then commit all your merge conflict fixes.

$ git commit -m "Merging with master."

See Recent Commits to a Branch

Use git log: to view a branch's commit messages.

Using git log in its bare form gives you output which is very unpleasant to look at. Here's a command that makes it pretty—or, at less unsightly.

$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative <branch_name>
* c82603b - (HEAD -> master) Updated .gitignore. (9 days ago)
* b538899 - (origin/master, user-profile) Initial setup of project. (10 weeks ago)

If the branch has a long history, Git will open its less-like environment. Here we're showing output that is short enough that Git automatically takes you back to the command prompt.

As you can see, output is in reverse-chronological order. This means that for older projects you'll probably want to pipe the output to head. (Note that in the example below, piping with head does not change the output because there is so little of it.)

$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative <branch_name> | head
* c82603b - (HEAD -> master) Updated .gitignore. (9 days ago)
* b538899 - (origin/master, user-profile) Initial setup of project. (10 weeks ago)

Other Useful Commands

See your changes for a particular file before adding or committing it.

$ git diff <filename>

Search entire codebase for this string. Prints out lines containing the string.

$ git grep "<string>"

View commit messages in pretty format for a branch.

$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative <branch name> 

For a file:

    $ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative -- requirements.txt

You can't do a git pull because of a file ignored:

$ git pull
Username for 'https://10.312.265.105': someUser
Password for 'https://someUser@10.312.265.105': 
warning: redirecting to https://10.312.265.105/qae/GUI/qae_center.git/
Updating 7c2586e..01c4d35
error: Your local changes to the following files would be overwritten by merge:
  QAnswering/archivalSQL.sqlite3
Please commit your changes or stash them before you merge.
Aborting

$ git update-index --no-skip-worktree QAnswering/archivalSQL.sqlite3

$ git checkout -- QAnswering/archivalSQL.sqlite3

$ git pull

Git's "less-like" Environment

If the output of a command like git diff or git log is very long, it will open what I call a "less-like" environment. I call it "less-like" because you use the same commands to navigate through this environment as you would when you call the Linux less command on a file. Here are the basics:

  • Ctrl-F to go foward.
  • Ctrl-B to go backward.
  • ? to exit.

'git fetch' vs. 'git pull'

When you bring a branch in the remote repo local, you're bringing in just a snapshot—a copy of what's in the remote repository. The local copy is updated only when you explicitly update it. You explicitly update it in one of two ways: - git fetch downloads the latest data from the remote repository, but it doesn't integrate this data into your working copy. - git pull downloads the data, and merges it into your work.

From this description of git fetch, it follows that git fetch is harmless—it never changes your local copy. Run the fetch command as often as you'd like, without worry. If after fetching you want to merge the fetched changes into your code, follow the git fetch with a git merge. In other words:

git fetch + git merge = git pull

Git Resources

http://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository

http://git-scm.com/book/en/v2/Getting-Started-Git-Basics

https://www.git-tower.com/learn/git/faq/track-remote-upstream-branch

https://stackoverflow.com/questions/13072111/gits-local-repository-and-remote-repository-confusing-concepts