
As we’ve discussed in length in our guides to becoming a software developer and some of the best programming tools, source control is a requirement of a successful software product. Without it, you and your team will have no change history, no collaboration ability, and no way of knowing who changed what. That leaves you blind and in the dark.
Love it or hate it, Git is the de facto standard for source control software. It’s critical to learn git if you want to track changes to your code, join a collaborative software project, or contribute to a group project at an enterprise.
Git commands can be a bit unintuitive at first glance, so let’s break down those most likely to be used on a daily basis.
Installing Git
Git works natively on Windows, Mac, and Linux. When using Windows, you’ll most likely be using the tools included in the Git for Windows initiative (Git BASH, Git GUI, and Git Shell Integration). Mac and Linux have native command line support.
To get started, go to git-scm.com/downloads to find the best download for your setup.
- If you’re on Windows, use the Windows download link.
- If you’re on Mac, use the Mac download link. This link seems to redirect to Sourceforge.
- If you’re on Linux, use your distribution’s package management commands as seen on the Linux download link.
For example, with Ubuntu, you’d use: apt install git
. You might need to augment this with sudo
if your setup requires root to install software.
Note that Git is, by default, purely a command line tool. If you’re interested in using a user interface to manage your Git repositories, check out our tooling guide for some suggestions. You can also check out the official Git GUI page.
The rest of this guide assumes that you’re on Windows, but the commands are identical regardless of your operating system.
From here on out, we will be using Git BASH to issue the various git commands. The $
at the beginning of each line is just a BASH line starter indicator.
Initializing a Local Git Repository
git init
Git works by tracking changes to files, folder, and descendants. You can’t do much until you tell Git that you want tracking to be enabled.
If you’re starting a new project or you have an existing project that you want to add to source control, use the following command at the root of whatever folder you want as the top of the repository.
$ git init
Success is indicated by a message like:
Initialized empty Git repository in <path>
You’ll also be able to confirm success by checking for a hidden folder named .git
which is a self-containing folder for all Git file history and branches.
Deleting that folder will effectively wipe out the entire repository, all change history, and all branches. Note that this means Git isn’t used on its own as a backup system because there is no redundant storage of file history. This is different from a system like SVN or TFS which is always backed by a remote server, so be careful.
To backup your repositories, you can take advantage of distributed remote services like GitHub, Bitbucket, and Azure DevOps.
Checking the Status of a Git Repository
git status
When you initialize a repository, the repository is in a state that reflects whatever the files and file contents were at the time of the command being issued.
To check the current state of the repository, use the git status
command.
You should see a printout that can indicate a few things:
- Nothing to commit
- Untracked files
- Changes to be committed
- Changes not staged for commit
- Merge conflicts (more on that later)
For example, a “clean” status which means nothing has changed would show like:
$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
We suggest using this command regularly prior to any big operations like merging, pushing, and branching. If you’re always up to date on the current status of your repository, there’s far less of a chance to get yourself into trouble.
Tracking Files and Staging Changes to be Committed
git add [file]
By default, files are not tracked by Git and must be explicitly added to Git’s tracked list. This helps prevent auto-generated files (like some compilation outputs) from being added to source control. An “untracked file” is one that didn’t appear in a previous commit and is treated by Git as a new file.
Add New File
For any files that are untracked in the repository (such as those that you’ve added as new), the git status
command will reveal a message like: Untracked files: (use "git add <file>..." to include in what will 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)
index.html
nothing added to commit but untracked files present (use "git add" to track)
To add a new file to Git’s tracking use the git add
command such as git add <file>
.
$ git add index.html
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: index.html
Modify Existing File
For files that are already tracked by Git, any time you make a change to that file such as renaming it, deleting it, or updating its contents, Git will flag that as a change.
For changes to tracked files, the git status
command will reveal a message like: Changes not staged for commit: (use "git add <file>..." to update what will be committed
.
$ 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 checkout -- <file>..." to discard changes in working directory)
modified: LICENSE
no changes added to commit (use "git add" and/or "git commit -a")
Just like you did for adding new tracked files, the same git add
command is used to stage existing tracked files for eventual commit to the repository.
$ git add LICENSE
Using the git status
command will then reveal a message like: Changes to be committed
.
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: LICENSE
Checking for Differences
git diff
Before taking action to commit your changes and enshrine those changes forever in the history of the repository, you should double check your changes to make sure you aren’t accidentally committing anything.
We can’t stress this enough. So many developers blindly commit after blanket adding changes. This causes havoc during code review and merging when files are accidentally added or changes specifically for debugging are not reverted prior to committing.
Ideally you should use a tool like SourceTree, Visual Studio, or Visual Studio Code to visualize the differences. But if you don’t want to or can’t use one of those tools, you can simply use the git diff
command.
Example:
$ git diff
diff --git a/src/SteamWebAPI2.sln b/src/SteamWebAPI2.sln
index 307ac11..7057e33 100644
--- a/src/SteamWebAPI2.sln
+++ b/src/SteamWebAPI2.sln
@@ -23,3 +23,4 @@ Global
SolutionGuid = {65138398-460F-4988-B302-B17E79B250C9}
EndGlobalSection
EndGlobal
+# I added this comment
\ No newline at end of file
At first glance, the output to this command is a bit weird, but just hang in there. The first half of the example above shows which file is being compared (file before and after changes). The second half shows which line numbers changed and, in this case, which line was added as indicated by the +
at the beginning of the line.
The equivalent diff in Visual Studio Code is shown below. Notice that the left window shows the listing of files that changed and the right two windows indicate the before and after change states. It’s a bit more intuitive in our opinion. It’s also a necessity when you’re making large changes across a project. Using git diff
alone doesn’t cut it in that case.

Committing Staged Changes
git commit -m [message]
After you’ve confirmed that only your expected changes have been added to Git’s staging list, you’re ready to commit those changes.
The simplest way to do so is to use the git commit
command with the -m
flag like so: git commit -m "This is my commit message"
. The -m
flag will let you type your commit message inline without having to open any external text editors.
If you have a complex commit message, you can type git commit
to open, by default, a vim-like interface for advanced text editing. If you aren’t familiar with vim, see this cheat sheet to prevent you from drowning. You will most likely only need to type i
to go into insert mode and then :wq
to save, quit, and commit to Git.
Once your commit is complete, you can type git log
to confirm success by making sure that the top (most recent) commit matches your author, commit message, and date/time.
Managing the .gitignore File
We’ve talked about how Git doesn’t add files to tracking until you explicitly tell it about those files. But what if you never want to track a certain file or an entire type of file? This is common for compilation outputs, libraries, or other runtime dependencies that would typically be restored by each developer individually outside the repository.
There’s a file specifically for this scenario! At the root of your repository, you can create a file named .gitignore
which contains a listing of file names, paths, and wildcards that should be ignored by Git.
Creating this file can be tricky on Windows. See this StackOverflow answer for the quickest way to do it.
If you’re using Windows it will not let you create a file without a filename in Windows Explorer. It will give you the error “You must type a file name” if you try to rename a text file as
.gitignore
.You can get around this Windows Explorer error by appending a dot to the filename without extension:
— StackOverflow answer.gitignore.
will be automatically changed to.gitignore
We suggest using a predefined and crowdsourced .gitignore
file for whatever language, framework, or SDK you are using. If you have files that you want to track that aren’t included in those crowdsourced examples, you can obviously edit them to your heart’s content.
Note that .gitignore
files don’t retroactively remove files from your repository’s history. If a file is currently tracked by Git, it will continue to be tracked even if you add that file to the .gitignore
file. To fix this, you’ll need use this command: git update-index --skip-worktree <file>
. This will update the index or directory cache of the file to tell Git that it may not need to be tracked anymore.
Creating Branches
git branch [branch_name]
git checkout [branch_name]
As we previously wrote about in 5 Essential Things Every Programmer Should Know, branching is critical to your success, your team’s success, and ultimately your project’s success.
Without the ability to create independent branches of code, team members would be conflicting with each other every time a developer made a commit. Since we also advocate for committing early and committing often, such conflicts would be an immediate deal breaker.

Let’s reference the image above and show how to create the feature-1
branch using Git commands.
The black line and dots is the master branch (line) and each commit on that branch (dot). “Master” is the name of the default branch when you initialize a repository. Another name for this branch is the “trunk.”
Let’s create a feature branch to work on a new feature which is isolated from the master branch.
$ git branch feature-1
After the branch has been created, you need to switch your current branch to it so that all changes will be made on that branch.
$ git checkout feature-1
Switched to branch 'feature-1'
Now we can check to make sure the switch was successful.
$ git status
On branch feature-1
nothing to commit, working tree clean
All changes and commits will now be made on the feature-1
branch until we switch back to master
.
Merging Branches
git checkout [destination_branch_name]
git merge [source_branch_name]
Branches are nearly useless if kept in isolation forever. Team members would be working on features that never get merged together. The application would then have its code split across a ton of different branches!
That’s where merging comes in. Git makes this fast and easy.
Use the following commands when you’re ready to merge your changes back into something like the master
branch so that everyone else on your team can start seeing your changes.
Switch to the destination branch. This is the branch that you want to merge the changes into.
$ git checkout master
Confirm that the switch was successful.
$ git status
On branch master
nothing to commit, working tree clean
Now merge changes from your feature branch into the current branch (in this case, master
). Remember, you’re merging from a destination branch (like a feature branch) into whatever branch is currently checked out (like master
, confirmed via git status
).
$ git merge feature-1
Updating 02a0825..3fb0fd3
Fast-forward
README.md | 3 ++-
1 file changes, 2 insertions(+), 1 deletion(-)
And there you have it. Your changes are now included in the master
branch ready to be rolled out to the masses.
Handling Merge Conflicts
git checkout [destination_branch_name]
git merge [source_branch_name]
[resolve merge conflicts as necessary]
git add [file]
git commit -m [message]
When multiple people are working on the same project, there are inevitably conflicts (yes, even with good branching and merging). That’s because there are sometimes new features and fixes that may overlap with code that was changed by someone else on a different branch.
So what if you edit line 13 in README.md
, and your coworker also edits line 13 in README.md
on a separate branch? Who wins? Maybe you could flip a coin, but that definitely wouldn’t be an accurate reconciliation.
Instead, we need to enlist a good strategy to resolve the conflicts so that everyone is happy.
The simplest way to resolve conflicts is by opening the affected files and reconciling the differences manually.
For example, let’s say both you and a coworker modified the README.md
text file. When you merge master
(coworkers changes) into your feature branch, Git complains that some files are now in conflict.
$ git status
On branch feature-1
nothing to commit, working tree clean
$ git merge master
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
So now let’s see what the conflicted file looks like.
1 change 1 2 change 2 3 <<<<<<< HEAD 4 your change here 5 ======= 6 coworker's change here 7 >>>>>>> master
Git modified the file with some special lines to indicate the conflicting details.
<<<<<<< HEAD
indicates the starting point of your changes.>>>>>>> [source_branch_name]
indicates the end point of your coworker’s changes.=======
is a separator for the conflicting changes.
You have some choices to make now, and that may involve a discussion with your coworker.
- Use your changes and abandon your coworker’s changes. Accomplished by removing the separator line and everything below it.
- Use your coworker’s changes and abandon your changes. Accomplished by removing your changes and all the Git-added marker lines.
- Use both changes in a specific order. This requires understanding which order the changes should be in, arranging them in that order, and then removing the Git-added marker lines.
And finally, after you’ve resolved the conflicts to your liking, you need to stage and commit your changes.
$ git add [file]
$ git commit -m [message]
Managing Remote Repositories
Up until this point, this guide has only made changes to a local repository. That’s great, but isn’t very useful if you want to contribute to a project in which developers across the world are also contributing. Distributed services like GitHub and BitBucket are great examples of services that provide remote repositories for team collaboration.
The terminology used to manage remotes can be a little confusing, so let’s break it down.
First, we need to get a remote repository from something like GitHub. This process is called “cloning” a repository.
Let’s clone the repository for the Angular framework from GitHub. This will pull down the current state of the master
branch into whatever directory is active in your shell and create a local repository.
$ git clone https://github.com/angular/angular.git
After cloning, we can check the status of any remote repositories that are linked to the new cloned local repository.
$ git remote -v
origin https://github.com/angular/angular.git (fetch)
origin https://github.com/angular/angular.git (push)
A remote repository is already linked with our local repository because we cloned it from GitHub. Notice that Git has named the default remote repository as “origin”. That terminology makes sense when you consider that the repository originated from GitHub (remote by definition).
When working on a distributed product (like Angular that we cloned above), it’s extremely common to consistently push and pull changes to and from remote repositories. You’ll be working in your local clone of that repository, but a reconciliation will need to happen against the remotely linked repository.
When contributing, you will typically follow this pattern:
- Stage changes
- Commit changes locally
- Push changes to a remote
Pushing Changes to a Remote Repository
To push any commits that you have locally, use the following commands making note of the name of the remote repository to push against and the branch in which you want your changes included.
$ git push [remote] [branch]
$ git push origin master
Pulling Changes from a Remote Repository
To fetch or pull any commits that you need to merge locally, use the following commands making note of the subtle difference between the two.
First, you can fetch changes from the remote repository but will be required to manually merge any changes into your local work.
$ git fetch origin
More conveniently, you can pull changes from the remote repository and have those changes automatically merged into your local work.
$ git pull origin
Further Reading: Once you’ve mastered the basics of Git, read through our ultimate guide to building a portfolio on GitHub.